diff --git a/MainLayout.java b/MainLayout.java new file mode 100644 index 0000000..ca59a36 --- /dev/null +++ b/MainLayout.java @@ -0,0 +1,85 @@ +package com.example.application.views; + +import com.vaadin.flow.component.applayout.AppLayout; +import com.vaadin.flow.component.applayout.DrawerToggle; +import com.vaadin.flow.component.html.Footer; +import com.vaadin.flow.component.html.H1; +import com.vaadin.flow.component.html.Header; +import com.vaadin.flow.component.html.Span; +import com.vaadin.flow.component.icon.SvgIcon; +import com.vaadin.flow.component.orderedlayout.Scroller; +import com.vaadin.flow.component.sidenav.SideNav; +import com.vaadin.flow.component.sidenav.SideNavItem; +import com.vaadin.flow.router.Layout; +import com.vaadin.flow.server.auth.AnonymousAllowed; +import com.vaadin.flow.server.menu.MenuConfiguration; +import com.vaadin.flow.server.menu.MenuEntry; +import com.vaadin.flow.theme.lumo.LumoUtility; +import java.util.List; + +/** + * The main view is a top-level placeholder for other views. + */ +@Layout +@AnonymousAllowed +public class MainLayout extends AppLayout { + + private H1 viewTitle; + + public MainLayout() { + setPrimarySection(Section.DRAWER); + addDrawerContent(); + addHeaderContent(); + } + + private void addHeaderContent() { + DrawerToggle toggle = new DrawerToggle(); + toggle.setAriaLabel("Menu toggle"); + + viewTitle = new H1(); + viewTitle.addClassNames(LumoUtility.FontSize.LARGE, LumoUtility.Margin.NONE); + + addToNavbar(true, toggle, viewTitle); + } + + private void addDrawerContent() { + Span appName = new Span("ConferenceIO"); + appName.addClassNames(LumoUtility.FontWeight.SEMIBOLD, LumoUtility.FontSize.LARGE); + Header header = new Header(appName); + + Scroller scroller = new Scroller(createNavigation()); + + addToDrawer(header, scroller, createFooter()); + } + + private SideNav createNavigation() { + SideNav nav = new SideNav(); + + List menuEntries = MenuConfiguration.getMenuEntries(); + menuEntries.forEach(entry -> { + if (entry.icon() != null) { + nav.addItem(new SideNavItem(entry.title(), entry.path(), new SvgIcon(entry.icon()))); + } else { + nav.addItem(new SideNavItem(entry.title(), entry.path())); + } + }); + + return nav; + } + + private Footer createFooter() { + Footer layout = new Footer(); + + return layout; + } + + @Override + protected void afterNavigation() { + super.afterNavigation(); + viewTitle.setText(getCurrentPageTitle()); + } + + private String getCurrentPageTitle() { + return MenuConfiguration.getPageHeader(getContent()).orElse(""); + } +} diff --git a/_PRA/PRA06-jose.md b/_PRA/PRA06-jose.md new file mode 100644 index 0000000..ab1cefd --- /dev/null +++ b/_PRA/PRA06-jose.md @@ -0,0 +1,40 @@ +# PRA06 + +## Remarks + +- Created LayoutRender for Book and Speaker +- Binder working with SelectBox. Definition of equals and hashCode of Book and Speaker +- Used ComponentRender in SelectBox for Book and Speaker +- Used ComponentRender in Grid for Book and Speaker + + +## Tasks + +1. [x] Enhance Data Model + + - Create `Conference` and `Speaker` entities + - Implement OneToMany and ManyToOne relationships + - Use `Conference` as a join table + +1. [x] Configure Data Loading + + - Create `data.sql` file for initial data load + - Configure `application.properties` to load data only on first start + - Remember to use SQL data loading: `spring.sql.init.mode=always` in `application.properties` just the first time, then `never`. + +1. [x] Create Vaadin Views + + - Implement `MainLayout` + - Create one view per entity (`Conference`, `Speaker`) + - Use Vaadin components: `Avatar`, `ComboBox`, `DatePicker` + +1. [x] Implement CRUD Operations + + - Bind views to repositories + - Implement **Create, Read, Update, and Delete** operations in views + +1. [x] Styling and Layout + + - Ensure responsive design + - Apply consistent styling across views + diff --git a/_PRA/screenshots/01-welcome.png b/_PRA/screenshots/01-welcome.png new file mode 100644 index 0000000..7159de1 Binary files /dev/null and b/_PRA/screenshots/01-welcome.png differ diff --git a/_PRA/screenshots/02-books.png b/_PRA/screenshots/02-books.png new file mode 100644 index 0000000..33b8dff Binary files /dev/null and b/_PRA/screenshots/02-books.png differ diff --git a/_PRA/screenshots/03-speakers.png b/_PRA/screenshots/03-speakers.png new file mode 100644 index 0000000..72177e6 Binary files /dev/null and b/_PRA/screenshots/03-speakers.png differ diff --git a/_PRA/screenshots/04-conferences-none.selected.png b/_PRA/screenshots/04-conferences-none.selected.png new file mode 100644 index 0000000..5c7fd91 Binary files /dev/null and b/_PRA/screenshots/04-conferences-none.selected.png differ diff --git a/_PRA/screenshots/05-conferences-one-selected.png b/_PRA/screenshots/05-conferences-one-selected.png new file mode 100644 index 0000000..a7271f9 Binary files /dev/null and b/_PRA/screenshots/05-conferences-one-selected.png differ diff --git a/books/BooksView.java b/books/BooksView.java new file mode 100644 index 0000000..2b853cd --- /dev/null +++ b/books/BooksView.java @@ -0,0 +1,18 @@ +package com.example.application.views.books; + +import com.vaadin.flow.component.Composite; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.router.Menu; +import com.vaadin.flow.router.PageTitle; +import com.vaadin.flow.router.Route; + +@PageTitle("Books") +@Route("books") +@Menu(order = 1, icon = "line-awesome/svg/pencil-ruler-solid.svg") +public class BooksView extends Composite { + + public BooksView() { + getContent().setWidth("100%"); + getContent().getStyle().set("flex-grow", "1"); + } +} diff --git a/conferences/ConferencesView.java b/conferences/ConferencesView.java new file mode 100644 index 0000000..a1a46b6 --- /dev/null +++ b/conferences/ConferencesView.java @@ -0,0 +1,18 @@ +package com.example.application.views.conferences; + +import com.vaadin.flow.component.Composite; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.router.Menu; +import com.vaadin.flow.router.PageTitle; +import com.vaadin.flow.router.Route; + +@PageTitle("Conferences") +@Route("conferences") +@Menu(order = 3, icon = "line-awesome/svg/pencil-ruler-solid.svg") +public class ConferencesView extends Composite { + + public ConferencesView() { + getContent().setWidth("100%"); + getContent().getStyle().set("flex-grow", "1"); + } +} diff --git a/main/MainView.java b/main/MainView.java new file mode 100644 index 0000000..26ed2d5 --- /dev/null +++ b/main/MainView.java @@ -0,0 +1,37 @@ +package com.example.application.views.main; + +import com.vaadin.flow.component.Composite; +import com.vaadin.flow.component.orderedlayout.HorizontalLayout; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.router.Menu; +import com.vaadin.flow.router.PageTitle; +import com.vaadin.flow.router.Route; +import com.vaadin.flow.theme.lumo.LumoUtility.Gap; + +@PageTitle("Main") +@Route("") +@Menu(order = 0, icon = "line-awesome/svg/pencil-ruler-solid.svg") +public class MainView extends Composite { + + public MainView() { + HorizontalLayout layoutRow2 = new HorizontalLayout(); + HorizontalLayout layoutRow = new HorizontalLayout(); + VerticalLayout layoutColumn2 = new VerticalLayout(); + VerticalLayout layoutColumn3 = new VerticalLayout(); + getContent().setWidth("100%"); + getContent().getStyle().set("flex-grow", "1"); + layoutRow2.addClassName(Gap.MEDIUM); + layoutRow2.setWidth("100%"); + layoutRow2.setHeight("min-content"); + layoutRow.addClassName(Gap.MEDIUM); + layoutRow.setWidth("100%"); + layoutRow.getStyle().set("flex-grow", "1"); + layoutColumn2.getStyle().set("flex-grow", "1"); + layoutColumn3.setWidth("100%"); + layoutColumn3.getStyle().set("flex-grow", "1"); + getContent().add(layoutRow2); + getContent().add(layoutRow); + layoutRow.add(layoutColumn2); + layoutRow.add(layoutColumn3); + } +} diff --git a/speakers/SpeakersView.java b/speakers/SpeakersView.java new file mode 100644 index 0000000..91428ce --- /dev/null +++ b/speakers/SpeakersView.java @@ -0,0 +1,18 @@ +package com.example.application.views.speakers; + +import com.vaadin.flow.component.Composite; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.router.Menu; +import com.vaadin.flow.router.PageTitle; +import com.vaadin.flow.router.Route; + +@PageTitle("Speakers") +@Route("speakers") +@Menu(order = 2, icon = "line-awesome/svg/pencil-ruler-solid.svg") +public class SpeakersView extends Composite { + + public SpeakersView() { + getContent().setWidth("100%"); + getContent().getStyle().set("flex-grow", "1"); + } +} diff --git a/src/main/java/io/bcn/springConference/model/Book.java b/src/main/java/io/bcn/springConference/model/Book.java index 5638c89..5b41e31 100644 --- a/src/main/java/io/bcn/springConference/model/Book.java +++ b/src/main/java/io/bcn/springConference/model/Book.java @@ -6,6 +6,8 @@ import lombok.NoArgsConstructor; import lombok.Setter; import org.hibernate.annotations.GenericGenerator; + +import java.util.Objects; import java.util.UUID; @@ -36,4 +38,25 @@ public class Book { /* @OneToMany(mappedBy = "book") private List conferences;*/ + + // https://stackoverflow.com/questions/17298314/java-vaadin-nativeselect-setvalue-not-working/17299605#17299605 + @Override + public boolean equals(Object obj) { + if(this == obj) { + return true; + } + if(!(obj instanceof Book)){ + return false; + } + Book that = (Book) obj; + return this.id.equals(that.id) && + this.author.equals(that.author) && + this.title.equals(that.title) && + this.ISBN.equals(that.ISBN); + } + + @Override + public int hashCode() { + return Objects.hash(id, title, author, ISBN); + } } diff --git a/src/main/java/io/bcn/springConference/model/Conference.java b/src/main/java/io/bcn/springConference/model/Conference.java index 15d79d2..bad9fbb 100644 --- a/src/main/java/io/bcn/springConference/model/Conference.java +++ b/src/main/java/io/bcn/springConference/model/Conference.java @@ -1,4 +1,42 @@ package io.bcn.springConference.model; +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.hibernate.annotations.GenericGenerator; + +import java.time.LocalDate; +import java.util.UUID; + +@Entity +@Table(name = "conferences") +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor public class Conference { + @Id + @GeneratedValue(generator = "UUID") + @GenericGenerator(name = "UUID", + strategy = "org.hibernate.id.UUIDGenerator") + @Column(name = "id", updatable = false, + nullable = false) + private UUID id; + private String conference; + private String title; + private LocalDate date; + private String youtube; + private String content; + private int duration; + private String room; + + @ManyToOne + @JoinColumn(name="book_id", nullable = false) + private Book book; + @ManyToOne + @JoinColumn(name="speaker_id", nullable = false) + private Speaker speaker; + } diff --git a/src/main/java/io/bcn/springConference/model/Speaker.java b/src/main/java/io/bcn/springConference/model/Speaker.java index db76a55..2fab147 100644 --- a/src/main/java/io/bcn/springConference/model/Speaker.java +++ b/src/main/java/io/bcn/springConference/model/Speaker.java @@ -1,4 +1,49 @@ package io.bcn.springConference.model; +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.hibernate.annotations.GenericGenerator; + +import java.util.Objects; +import java.util.UUID; + +@Entity +@Table(name = "speakers") +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor public class Speaker { + @Id + @GeneratedValue(generator = "UUID") + @GenericGenerator(name = "UUID", + strategy = "org.hibernate.id.UUIDGenerator") + @Column(name = "id", updatable = false, + nullable = false) + private UUID id; + private String name; + private String bio; + private String email; + +// @ManyToOne +// @JoinColumn(name = "conference_id") +// private Conference conference; + + + // https://stackoverflow.com/questions/17298314/java-vaadin-nativeselect-setvalue-not-working/17299605#17299605 + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof Speaker speaker)) return false; + return Objects.equals(id, speaker.id) && Objects.equals(name, speaker.name) && + Objects.equals(bio, speaker.bio) && Objects.equals(email, speaker.email); + } + + @Override + public int hashCode() { + return Objects.hash(id, name, bio, email); + } } diff --git a/src/main/java/io/bcn/springConference/repository/BookRepository.java b/src/main/java/io/bcn/springConference/repository/BookRepository.java index f764c1f..bf34229 100644 --- a/src/main/java/io/bcn/springConference/repository/BookRepository.java +++ b/src/main/java/io/bcn/springConference/repository/BookRepository.java @@ -2,7 +2,10 @@ import io.bcn.springConference.model.Book; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + import java.util.UUID; +@Repository public interface BookRepository extends JpaRepository { } diff --git a/src/main/java/io/bcn/springConference/repository/ConferenceRepository.java b/src/main/java/io/bcn/springConference/repository/ConferenceRepository.java new file mode 100644 index 0000000..33f66d7 --- /dev/null +++ b/src/main/java/io/bcn/springConference/repository/ConferenceRepository.java @@ -0,0 +1,8 @@ +package io.bcn.springConference.repository; + +import io.bcn.springConference.model.Conference; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.UUID; + +public interface ConferenceRepository extends JpaRepository {} diff --git a/src/main/java/io/bcn/springConference/repository/SpeakerRepository.java b/src/main/java/io/bcn/springConference/repository/SpeakerRepository.java new file mode 100644 index 0000000..17bb14f --- /dev/null +++ b/src/main/java/io/bcn/springConference/repository/SpeakerRepository.java @@ -0,0 +1,10 @@ +package io.bcn.springConference.repository; + +import io.bcn.springConference.model.Speaker; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.UUID; + +@Repository +public interface SpeakerRepository extends JpaRepository {} diff --git a/src/main/java/io/bcn/springConference/utilities/Views.java b/src/main/java/io/bcn/springConference/utilities/Views.java new file mode 100644 index 0000000..15dc5ec --- /dev/null +++ b/src/main/java/io/bcn/springConference/utilities/Views.java @@ -0,0 +1,121 @@ +package io.bcn.springConference.utilities; + +import com.vaadin.flow.component.avatar.Avatar; +import com.vaadin.flow.component.formlayout.FormLayout; +import com.vaadin.flow.component.html.Div; +import com.vaadin.flow.component.orderedlayout.FlexComponent; +import com.vaadin.flow.component.orderedlayout.FlexLayout; +import com.vaadin.flow.component.orderedlayout.HorizontalLayout; +import com.vaadin.flow.data.renderer.ComponentRenderer; +import com.vaadin.flow.theme.lumo.LumoUtility; +import io.bcn.springConference.model.Book; +import io.bcn.springConference.model.Conference; +import io.bcn.springConference.model.Speaker; + +import java.util.Arrays; +import java.util.List; + +public class Views { + + public static HorizontalLayout getNewRow(){ + HorizontalLayout layoutRow = new HorizontalLayout(); + + layoutRow.setWidthFull(); + layoutRow.addClassName(LumoUtility.Gap.MEDIUM); + layoutRow.setWidth("100%"); + layoutRow.setHeight("min-content"); + layoutRow.setAlignItems(FlexComponent.Alignment.CENTER); + layoutRow.setJustifyContentMode(FlexComponent.JustifyContentMode.CENTER); + + return layoutRow; + } + + public static List getResponsiveStepsForForms(){ + // https://getbootstrap.com/docs/5.0/layout/breakpoints/#available-breakpoints + // $grid-breakpoints: ( + // xs: 0, + // sm: 576px, + // md: 768px, + // lg: 992px, + // xl: 1200px, + // xxl: 1400px + //); + return Arrays.asList( + new FormLayout.ResponsiveStep("0",1), + new FormLayout.ResponsiveStep("600px",2), + new FormLayout.ResponsiveStep("900px",3), + new FormLayout.ResponsiveStep("1200px",4), + new FormLayout.ResponsiveStep("1500px",5) + ); + } + + private static FlexLayout getBookLayoutRender(Book book){ + FlexLayout wrapper = new FlexLayout(); + wrapper.setAlignItems(FlexComponent.Alignment.CENTER); + + Div info = new Div(); + info.setText(book.getTitle()); + + Div author = new Div(); + author.setText(book.getAuthor()); + author.getStyle().set("font-size", "var(--lumo-font-size-s)"); + author.getStyle().set("color", "var(--lumo-secondary-text-color)"); + info.add(author); + + wrapper.add(info); + + return wrapper; + } + + public static ComponentRenderer getBookRender(){ + return new ComponentRenderer( + book -> { + return getBookLayoutRender(book); + } + ); + } + + public static ComponentRenderer getConferenceBookRender(){ + return new ComponentRenderer( + conference -> { + return getBookLayoutRender(conference.getBook()); + } + ); + } + + private static FlexLayout getSpeakerLayoutRender(Speaker speaker){ + FlexLayout wrapper = new FlexLayout(); + wrapper.setAlignItems(FlexComponent.Alignment.CENTER); + + Avatar avatar = new Avatar(speaker.getName()); +// Image image = new Image(); +// image.setSrc(person.getPictureUrl()); +// image.setAlt("Portrait of " + person.getFirstName() + " " +// + person.getLastName()); +// image.setWidth("var(--lumo-size-m)"); +// image.getStyle().set("margin-right", "var(--lumo-space-s)"); + + Div info = new Div(); + info.setText(speaker.getName()); + + wrapper.add(avatar,info); + + return wrapper; + } + + public static ComponentRenderer getSpeakerRender(){ + return new ComponentRenderer( + speaker -> { + return getSpeakerLayoutRender(speaker); + } + ); + } + + public static ComponentRenderer getConferenceSpeakerRender(){ + return new ComponentRenderer( + conference -> { + return getSpeakerLayoutRender(conference.getSpeaker()); + } + ); + } +} diff --git a/src/main/java/io/bcn/springConference/view/ConferenceView.java b/src/main/java/io/bcn/springConference/view/ConferenceView.java deleted file mode 100644 index 4e99b93..0000000 --- a/src/main/java/io/bcn/springConference/view/ConferenceView.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - -package io.bcn.springConference.view; - -import com.vaadin.flow.component.Component; -import com.vaadin.flow.component.button.Button; -import com.vaadin.flow.component.grid.Grid; -import com.vaadin.flow.component.html.H2; -import com.vaadin.flow.component.orderedlayout.HorizontalLayout; -import com.vaadin.flow.component.orderedlayout.VerticalLayout; -import com.vaadin.flow.component.textfield.TextField; -import com.vaadin.flow.data.binder.Binder; -import com.vaadin.flow.router.Route; - - -@Route("/conference") -public class ConferenceView extends VerticalLayout { - - - - private final TextField name = new TextField("Name"); - private final TextField email = new TextField("Email"); - private final TextField phoneNumber = new TextField("Phone Number"); - private final Button save = new Button("Save"); - private final Button delete = new Button("Delete"); - - - - - // Method to create the main layout - private Component createMainLayout() { - // Create the 3-column layout - HorizontalLayout mainLayout = new HorizontalLayout(); - mainLayout.setSizeFull(); - mainLayout.setPadding(false); - mainLayout.setSpacing(false); - - // Left column (empty for spacing) - VerticalLayout leftColumn = new VerticalLayout(); - leftColumn.setWidth("20%"); - - // Center column (contains all the components) - VerticalLayout centerColumn = new VerticalLayout(); - centerColumn.setWidth("60%"); - centerColumn.setAlignItems(Alignment.CENTER); - - // Right column (empty for spacing) - VerticalLayout rightColumn = new VerticalLayout(); - rightColumn.setWidth("20%"); - - // Create a form layout - HorizontalLayout formLayout = new HorizontalLayout(name, email, phoneNumber); - formLayout.setWidth("100%"); - formLayout.setJustifyContentMode(JustifyContentMode.CENTER); - - // Create a button layout - HorizontalLayout buttonLayout = new HorizontalLayout(save, delete); - buttonLayout.setJustifyContentMode(JustifyContentMode.CENTER); - - // Add components to the center column - centerColumn.add( - new H2("Customer Management"), - formLayout, - buttonLayout - ); - - // Add all columns to the main layout - mainLayout.add(leftColumn, centerColumn, rightColumn); - - return mainLayout; - } - -} -*/ diff --git a/src/main/java/io/bcn/springConference/view/MainLayout.java b/src/main/java/io/bcn/springConference/view/MainLayout.java new file mode 100644 index 0000000..58db1fe --- /dev/null +++ b/src/main/java/io/bcn/springConference/view/MainLayout.java @@ -0,0 +1,85 @@ +package io.bcn.springConference.view; + +import com.vaadin.flow.component.applayout.AppLayout; +import com.vaadin.flow.component.applayout.DrawerToggle; +import com.vaadin.flow.component.html.Footer; +import com.vaadin.flow.component.html.H1; +import com.vaadin.flow.component.html.Header; +import com.vaadin.flow.component.html.Span; +import com.vaadin.flow.component.icon.SvgIcon; +import com.vaadin.flow.component.orderedlayout.Scroller; +import com.vaadin.flow.component.sidenav.SideNav; +import com.vaadin.flow.component.sidenav.SideNavItem; +import com.vaadin.flow.router.Layout; +import com.vaadin.flow.server.auth.AnonymousAllowed; +import com.vaadin.flow.server.menu.MenuConfiguration; +import com.vaadin.flow.server.menu.MenuEntry; +import com.vaadin.flow.theme.lumo.LumoUtility; +import java.util.List; + +/** + * The main view is a top-level placeholder for other views. + */ +@Layout +@AnonymousAllowed +public class MainLayout extends AppLayout { + + private H1 viewTitle; + + public MainLayout() { + setPrimarySection(Section.DRAWER); + addDrawerContent(); + addHeaderContent(); + } + + private void addHeaderContent() { + DrawerToggle toggle = new DrawerToggle(); + toggle.setAriaLabel("Menu toggle"); + + viewTitle = new H1(); + viewTitle.addClassNames(LumoUtility.FontSize.LARGE, LumoUtility.Margin.NONE); + + addToNavbar(true, toggle, viewTitle); + } + + private void addDrawerContent() { + Span appName = new Span("ConferenceIO"); + appName.addClassNames(LumoUtility.FontWeight.SEMIBOLD, LumoUtility.FontSize.LARGE); + Header header = new Header(appName); + + Scroller scroller = new Scroller(createNavigation()); + + addToDrawer(header, scroller, createFooter()); + } + + private SideNav createNavigation() { + SideNav nav = new SideNav(); + + List menuEntries = MenuConfiguration.getMenuEntries(); + menuEntries.forEach(entry -> { + if (entry.icon() != null) { + nav.addItem(new SideNavItem(entry.title(), entry.path(), new SvgIcon(entry.icon()))); + } else { + nav.addItem(new SideNavItem(entry.title(), entry.path())); + } + }); + + return nav; + } + + private Footer createFooter() { + Footer layout = new Footer(); + + return layout; + } + + @Override + protected void afterNavigation() { + super.afterNavigation(); + viewTitle.setText(getCurrentPageTitle()); + } + + private String getCurrentPageTitle() { + return MenuConfiguration.getPageHeader(getContent()).orElse(""); + } +} diff --git a/src/main/java/io/bcn/springConference/view/books/BooksView.java b/src/main/java/io/bcn/springConference/view/books/BooksView.java new file mode 100644 index 0000000..b95c293 --- /dev/null +++ b/src/main/java/io/bcn/springConference/view/books/BooksView.java @@ -0,0 +1,138 @@ +package io.bcn.springConference.view.books; + +import com.vaadin.flow.component.Composite; +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.formlayout.FormLayout; +import com.vaadin.flow.component.html.Hr; +import com.vaadin.flow.component.orderedlayout.FlexComponent.Alignment; +import com.vaadin.flow.component.orderedlayout.FlexComponent.JustifyContentMode; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.component.orderedlayout.HorizontalLayout; +import com.vaadin.flow.component.textfield.TextField; +import com.vaadin.flow.data.binder.Binder; +import com.vaadin.flow.router.Menu; +import com.vaadin.flow.router.PageTitle; +import com.vaadin.flow.router.Route; + +import com.vaadin.flow.component.grid.Grid; +import com.vaadin.flow.component.grid.GridVariant; +import com.vaadin.flow.theme.lumo.LumoUtility; +import io.bcn.springConference.model.Book; +import io.bcn.springConference.repository.BookRepository; +import io.bcn.springConference.utilities.Views; +import jakarta.annotation.PostConstruct; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.List; +import java.util.UUID; + + +@PageTitle("Books") +@Route("books") +@Menu(order = 1, icon = "line-awesome/svg/pencil-ruler-solid.svg") +public class BooksView extends Composite { + + @Autowired + BookRepository bookRepository; + + private final Grid booksGrid; + + private final TextField textFieldTitle; + private final TextField textFieldAuthor; + private final TextField textFieldISBN; + + private final Binder binder; + + public BooksView() { + textFieldTitle = new TextField("Title"); + textFieldAuthor = new TextField("Author"); + textFieldISBN = new TextField("ISBN"); + booksGrid = new Grid<>(Book.class); + + booksGrid.addThemeVariants(GridVariant.LUMO_ROW_STRIPES); + booksGrid.setWidth("100%"); + booksGrid.getStyle().set("flex-grow", "0"); + + getContent().setWidth("100%"); + getContent().getStyle().set("flex-grow", "1"); + getContent().add(booksGrid); + getContent().add(new Hr()); + getContent().add(getBookForm()); + + binder = new Binder<>(Book.class); + binder.bind(textFieldAuthor, Book::getAuthor, Book::setAuthor); + binder.bind(textFieldTitle, Book::getTitle, Book::setTitle); + binder.bind(textFieldISBN, Book::getISBN, Book::setISBN); + + booksGrid.asSingleSelect().addValueChangeListener( + event ->{ + if(event.getValue() != null){ + binder.setBean(event.getValue()); + } else{ + clearBookForm(); + } + } + ); + } + + @PostConstruct + private void setGridData() { + if (bookRepository != null) { + booksGrid.setItems(bookRepository.findAll()); + } + } + + private void saveBook() { + if (bookRepository == null) { + return; + } + + Book book = binder.getBean(); + if( book == null ){ + book = new Book(); + } + if( book.getId() == null ){ + book.setId(UUID.randomUUID()); + } + bookRepository.save(book); + clearBookForm(); + setGridData(); + } + + private void deleteBook(){ + if( bookRepository == null ){ + return; + } + + Book book = binder.getBean(); + UUID uuid = book.getId(); + if( uuid != null ){ + bookRepository.deleteById(uuid); + clearBookForm(); + setGridData(); + } + } + + private void clearBookForm(){ + binder.setBean(new Book()); + } + + private FormLayout getBookForm(){ + FormLayout form = new FormLayout(); + + Button buttonSave = new Button("Insert / Update", clickEvent -> { saveBook(); }); + Button buttonDelete = new Button("Delete", clickEvent -> { deleteBook(); }); + HorizontalLayout row = Views.getNewRow(); + row.setMargin(true); + row.add(buttonSave,buttonDelete); + + List steps = Views.getResponsiveStepsForForms(); + form.setResponsiveSteps(steps); + form.add(textFieldTitle, textFieldAuthor, textFieldISBN, row); + // force buttons in one line + form.setColspan(row, steps.size()); + + return form; + } + +} diff --git a/src/main/java/io/bcn/springConference/view/conferences/ConferencesView.java b/src/main/java/io/bcn/springConference/view/conferences/ConferencesView.java new file mode 100644 index 0000000..79f9220 --- /dev/null +++ b/src/main/java/io/bcn/springConference/view/conferences/ConferencesView.java @@ -0,0 +1,261 @@ +package io.bcn.springConference.view.conferences; + +import com.vaadin.flow.component.Composite; +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.datepicker.DatePicker; +import com.vaadin.flow.component.formlayout.FormLayout; +import com.vaadin.flow.component.grid.Grid; +import com.vaadin.flow.component.grid.GridVariant; +import com.vaadin.flow.component.html.Div; +import com.vaadin.flow.component.html.Hr; +import com.vaadin.flow.component.html.Span; +import com.vaadin.flow.component.orderedlayout.FlexComponent; +import com.vaadin.flow.component.orderedlayout.FlexLayout; +import com.vaadin.flow.component.orderedlayout.HorizontalLayout; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.component.select.Select; +import com.vaadin.flow.component.textfield.IntegerField; +import com.vaadin.flow.component.textfield.TextField; +import com.vaadin.flow.data.binder.Binder; +import com.vaadin.flow.data.binder.PropertyId; +import com.vaadin.flow.data.renderer.ComponentRenderer; +import com.vaadin.flow.router.Menu; +import com.vaadin.flow.router.PageTitle; +import com.vaadin.flow.router.Route; +import io.bcn.springConference.model.Book; +import io.bcn.springConference.model.Conference; +import io.bcn.springConference.model.Speaker; +import io.bcn.springConference.repository.BookRepository; +import io.bcn.springConference.repository.ConferenceRepository; +import io.bcn.springConference.repository.SpeakerRepository; +import io.bcn.springConference.utilities.Views; +import jakarta.annotation.PostConstruct; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.List; +import java.util.Locale; +import java.util.UUID; + +@PageTitle("Conferences") +@Route("conferences") +@Menu(order = 3, icon = "line-awesome/svg/pencil-ruler-solid.svg") +public class ConferencesView extends Composite { + + @Autowired + ConferenceRepository conferenceRepository; + + @Autowired + BookRepository bookRepository; + + @Autowired + SpeakerRepository speakerRepository; + + private final Grid conferencesGrid; + + private final TextField textFieldConference; + private final TextField textFieldTitle; + private final DatePicker datePickerDate; + private final TextField textFieldYoutube; + private final TextField textFieldContent; + private final IntegerField integerFieldDuration; + private final TextField textFieldRoom; + + @PropertyId("book") + private final Select selectBook; + @PropertyId("Speaker") + private final Select selectSpeaker; + + private final Binder binder; + + public ConferencesView() { + textFieldConference = new TextField("Conference"); + textFieldTitle = new TextField("Title"); + datePickerDate = new DatePicker("Date"); + // YYYY-MM-DD format + // https://stackoverflow.com/questions/61384400/format-date-in-vaadin-date-picker + datePickerDate.setLocale(Locale.CANADA_FRENCH); + textFieldYoutube = new TextField("Youtube"); + textFieldContent = new TextField("Content"); + integerFieldDuration = new IntegerField("Duration"); + textFieldRoom = new TextField("Room"); + conferencesGrid = getConferencesGrid(); + + selectBook = new Select<>(); + selectBook.setLabel("Book"); + // selectBook.setItemLabelGenerator(Book::getTitle); + selectBook.setRenderer( Views.getBookRender() ); + selectSpeaker = new Select<>(); + selectSpeaker.setLabel("Speaker"); + // selectSpeaker.setItemLabelGenerator(Speaker::getName); + selectSpeaker.setRenderer(Views.getSpeakerRender()); + + conferencesGrid.addThemeVariants(GridVariant.LUMO_ROW_STRIPES); + conferencesGrid.setWidth("100%"); + conferencesGrid.getStyle().set("flex-grow", "0"); + + getContent().setWidth("100%"); + getContent().getStyle().set("flex-grow", "1"); + getContent().add(conferencesGrid); + getContent().add(new Hr()); + getContent().add(getConferenceForm()); + + binder = new Binder<>(Conference.class, true); + binder.bind(textFieldConference, Conference::getConference, Conference::setConference); + binder.bind(textFieldTitle, Conference::getTitle, Conference::setTitle); + binder.bind(datePickerDate, Conference::getDate, Conference::setDate); + binder.bind(textFieldYoutube, Conference::getYoutube, Conference::setYoutube); + binder.bind(textFieldContent, Conference::getContent, Conference::setContent); + binder.bind(integerFieldDuration, Conference::getDuration, Conference::setDuration); + binder.bind(textFieldRoom, Conference::getRoom, Conference::setRoom); + binder.bind(selectBook, Conference::getBook, Conference::setBook); + binder.bind(selectSpeaker, Conference::getSpeaker, Conference::setSpeaker); + + conferencesGrid.asSingleSelect().addValueChangeListener( + event ->{ + Conference conference = event.getValue(); + if(conference != null){ + binder.setBean(conference); + } else{ + clearConferenceForm(); + } + } + ); + } + + private Grid getConferencesGrid(){ + Grid grid = new Grid<>(Conference.class,false); + + Grid.Column col = grid.addColumn(Conference::getId); + col.setVisible(false); + grid.addColumn(Conference::getConference).setHeader("Conference"); + grid.addColumn(Conference::getTitle).setHeader("Title"); + grid.addColumn(Conference::getDate).setHeader("Date"); + grid.addColumn(Conference::getYoutube).setHeader("Youtube"); + grid.addColumn(Conference::getContent).setHeader("Content"); + grid.addColumn(Conference::getDuration).setHeader("Duration"); + grid.addColumn(Conference::getRoom).setHeader("Room"); + +// grid.addColumn(new ComponentRenderer<>( conference -> { +// return new Span(((Conference)conference).getBook().getTitle()); +// })).setHeader("Book"); + grid.addColumn(Views.getConferenceBookRender()).setHeader("Book"); +// grid.addColumn(new ComponentRenderer<>( conference -> { +// return new Span(((Conference)conference).getSpeaker().getName()); +// })).setHeader("Speaker"); + grid.addColumn(Views.getConferenceSpeakerRender()).setHeader("Speaker"); + + grid.getColumns().forEach( column -> column.setSortable(true) ); + + return grid; + } + + + @PostConstruct + private void setGridData() { + if (conferenceRepository != null) { + conferencesGrid.setItems(conferenceRepository.findAll()); + } + } + + @PostConstruct + private void setSelectsData() { + if (bookRepository != null) { + selectBook.setItems(bookRepository.findAll()); + } + if (speakerRepository != null) { + selectSpeaker.setItems(speakerRepository.findAll()); + } + } + + private void saveConference() { + if (conferenceRepository == null) { + return; + } + + Conference conference = binder.getBean(); + if( conference == null ){ + conference = new Conference(); + } + if( conference.getId() == null ){ + conference.setId(UUID.randomUUID()); + } + conferenceRepository.save(conference); + clearConferenceForm(); + setGridData(); + } + + private void deleteConference(){ + if( conferenceRepository == null ){ + return; + } + + Conference conference = binder.getBean(); + UUID uuid = conference.getId(); + if( uuid != null ){ + conferenceRepository.deleteById(uuid); + clearConferenceForm(); + setGridData(); + } + } + + private void clearConferenceForm(){ + binder.setBean(new Conference()); + } + + private FormLayout getConferenceForm(){ + FormLayout form = new FormLayout(); + + Button buttonSave = new Button("Insert / Update", clickEvent -> { saveConference(); }); + Button buttonDelete = new Button("Delete", clickEvent -> { deleteConference(); }); + HorizontalLayout row = Views.getNewRow(); + row.setMargin(true); + row.add(buttonSave,buttonDelete); + + List steps = Views.getResponsiveStepsForForms(); + form.setResponsiveSteps(steps); + form.add(textFieldConference, textFieldTitle, datePickerDate, textFieldYoutube); + form.add(textFieldContent, integerFieldDuration, textFieldRoom); + form.add(selectBook, selectSpeaker, row); + // force buttons in one line + form.setColspan(row, steps.size()); + + return form; + } + + private HorizontalLayout getRow1ConferenceFields(){ + HorizontalLayout row = Views.getNewRow(); + + row.add(textFieldConference); + row.add(textFieldTitle); + row.add(datePickerDate); + row.add(textFieldYoutube); + + return row; + } + + private HorizontalLayout getRow2ConferenceFields(){ + HorizontalLayout row = Views.getNewRow(); + + row.add(textFieldContent); + row.add(integerFieldDuration); + row.add(textFieldRoom); + row.add(selectBook); + row.add(selectSpeaker); + + return row; + } + + private HorizontalLayout getRowConferenceButtons() { + HorizontalLayout row = Views.getNewRow(); + + Button buttonSave = new Button("Insert / Update", clickEvent -> { saveConference(); }); + Button buttonDelete = new Button("Delete", clickEvent -> { deleteConference(); }); + + row.add(buttonSave); + row.add(buttonDelete); + + return row; + } + + +} diff --git a/src/main/java/io/bcn/springConference/view/init/InitView.java b/src/main/java/io/bcn/springConference/view/init/InitView.java new file mode 100644 index 0000000..489beb5 --- /dev/null +++ b/src/main/java/io/bcn/springConference/view/init/InitView.java @@ -0,0 +1,23 @@ +package io.bcn.springConference.view.init; + +import com.vaadin.flow.component.Composite; +import com.vaadin.flow.component.html.Paragraph; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.router.Menu; +import com.vaadin.flow.router.PageTitle; +import com.vaadin.flow.router.Route; + +@PageTitle("Welcome") +@Route("") +@Menu(order = 0, icon = "line-awesome/svg/pencil-ruler-solid.svg") +public class InitView extends Composite { + + public InitView() { + getContent().setWidth("100%"); + getContent().getStyle().set("flex-grow", "1"); + + Paragraph paragraph = new Paragraph("Welcome to Conference IO"); + getContent().add(paragraph); + + } +} diff --git a/src/main/java/io/bcn/springConference/view/speakers/SpeakersView.java b/src/main/java/io/bcn/springConference/view/speakers/SpeakersView.java new file mode 100644 index 0000000..457ba77 --- /dev/null +++ b/src/main/java/io/bcn/springConference/view/speakers/SpeakersView.java @@ -0,0 +1,139 @@ +package io.bcn.springConference.view.speakers; + +import com.vaadin.flow.component.Composite; +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.formlayout.FormLayout; +import com.vaadin.flow.component.grid.Grid; +import com.vaadin.flow.component.grid.GridVariant; +import com.vaadin.flow.component.html.Hr; +import com.vaadin.flow.component.orderedlayout.HorizontalLayout; +import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.component.textfield.EmailField; +import com.vaadin.flow.component.textfield.TextField; +import com.vaadin.flow.data.binder.Binder; +import com.vaadin.flow.router.Menu; +import com.vaadin.flow.router.PageTitle; +import com.vaadin.flow.router.Route; +import jakarta.annotation.PostConstruct; + +import java.util.List; +import java.util.UUID; +import org.springframework.beans.factory.annotation.Autowired; + +import io.bcn.springConference.model.Speaker; +import io.bcn.springConference.repository.SpeakerRepository; + + +import io.bcn.springConference.utilities.Views; + +@PageTitle("Speakers") +@Route("speakers") +@Menu(order = 2, icon = "line-awesome/svg/pencil-ruler-solid.svg") +public class SpeakersView extends Composite { + + @Autowired + SpeakerRepository speakerRepository; + + private final Grid speakersGrid; + + private final TextField textFieldName; + private final TextField textFieldBio; + // private final TextField textFieldEmail; + private final EmailField email; + + private final Binder binder; + + public SpeakersView() { + textFieldName = new TextField("Name"); + textFieldBio = new TextField("Bio"); + // textFieldEmail = new TextField("Email"); + email = new EmailField("Email"); + speakersGrid = new Grid<>(Speaker.class); + + speakersGrid.addThemeVariants(GridVariant.LUMO_ROW_STRIPES); + speakersGrid.setWidth("100%"); + speakersGrid.getStyle().set("flex-grow", "0"); + + getContent().setWidth("100%"); + getContent().getStyle().set("flex-grow", "1"); + getContent().add(speakersGrid); + getContent().add(new Hr()); + getContent().add(getSpeakerForm()); + + binder = new Binder<>(Speaker.class); + binder.bind(textFieldName, Speaker::getName, Speaker::setName); + binder.bind(textFieldBio, Speaker::getBio, Speaker::setBio); + binder.bind(email, Speaker::getEmail, Speaker::setEmail); + + speakersGrid.asSingleSelect().addValueChangeListener( + event ->{ + if(event.getValue() != null){ + binder.setBean(event.getValue()); + } else{ + clearSpeakerForm(); + } + } + ); + } + + @PostConstruct + private void setGridData() { + if (speakerRepository != null) { + speakersGrid.setItems(speakerRepository.findAll()); + } + } + + private void saveSpeaker() { + if (speakerRepository == null) { + return; + } + + Speaker speaker = binder.getBean(); + if( speaker == null ){ + speaker = new Speaker(); + } + if( speaker.getId() == null ){ + speaker.setId(UUID.randomUUID()); + } + speakerRepository.save(speaker); + clearSpeakerForm(); + setGridData(); + } + + private void deleteSpeaker(){ + if( speakerRepository == null ){ + return; + } + + Speaker speaker = binder.getBean(); + UUID uuid = speaker.getId(); + if( uuid != null ){ + speakerRepository.deleteById(uuid); + clearSpeakerForm(); + setGridData(); + } + } + + private void clearSpeakerForm(){ + binder.setBean(new Speaker()); + } + + private FormLayout getSpeakerForm(){ + FormLayout form = new FormLayout(); + + Button buttonSave = new Button("Insert / Update", clickEvent -> { saveSpeaker(); }); + Button buttonDelete = new Button("Delete", clickEvent -> { deleteSpeaker(); }); + HorizontalLayout row = Views.getNewRow(); + row.setMargin(true); + row.add(buttonSave,buttonDelete); + + List steps = Views.getResponsiveStepsForForms(); + form.setResponsiveSteps(steps); + form.add(textFieldName, textFieldBio, email, row); + // force buttons in one line + form.setColspan(row, steps.size()); + + return form; + } + +} diff --git a/src/main/resources/application-local.properties b/src/main/resources/application-local.properties index 2298f56..d09d578 100644 --- a/src/main/resources/application-local.properties +++ b/src/main/resources/application-local.properties @@ -1,7 +1,11 @@ # H2 LOCAL DB SERVER -spring.datasource.url=jdbc:h2:/home/albert/MyProjects/DataBase/conference/conferenceDB -spring.datasource.username=albert -spring.datasource.password=1234 +spring.datasource.url=jdbc:h2:/home/jc/Documents/SpringConferenceDB/conferenceDB +spring.datasource.username=jc +spring.datasource.password= + +#spring.datasource.url=jdbc:h2:/home/albert/MyProjects/DataBase/conference/conferenceDB +#spring.datasource.username=albert +#spring.datasource.password=1234 # DDL OPTIONS: create-drop, create, update, none, validate -spring.jpa.hibernate.ddl-auto=update +spring.jpa.hibernate.ddl-auto=create diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 4db059e..dfb448c 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -19,7 +19,7 @@ spring.sql.init.platform=h2 # Ensure SQL initialization is performed #spring.datasource.initialization-mode=always spring.sql.init.mode=always -#spring.sql.init.mode=never +# spring.sql.init.mode=never # Defer datasource initialization spring.jpa.defer-datasource-initialization=true diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql index 42ace5a..9e047fb 100644 --- a/src/main/resources/data.sql +++ b/src/main/resources/data.sql @@ -3,4 +3,23 @@ INSERT INTO books (id, title, author, isbn) VALUES (RANDOM_UUID(), 'Spring Boot in Action', 'Craig Walls', '9781617292545'), (RANDOM_UUID(), 'Spring Security in Action', 'Laurentiu Spilca', '9781617297731'), (RANDOM_UUID(), 'Reactive Spring', 'Josh Long', '9781732910225'), -(RANDOM_UUID(), 'Native Image Definitive Guide', 'Oleg Šelajev', '9781492078531'); \ No newline at end of file +(RANDOM_UUID(), 'Native Image Definitive Guide', 'Oleg Šelajev', '9781492078531'); + + +-- Insert Speakers +INSERT INTO speakers (id, name, bio, email) VALUES +(RANDOM_UUID(), 'Stéphane Nicoll', 'Spring Framework committer', 'snicoll@pivotal.io'), +(RANDOM_UUID(), 'Rob Winch', 'Spring Security lead', 'rwinch@pivotal.io'), +(RANDOM_UUID(), 'Josh Long', 'Spring Developer Advocate', 'jlong@pivotal.io'), +(RANDOM_UUID(), 'Sébastien Deleuze', 'Spring Framework committer', 'sdeleuze@vmware.com'); + + +-- Insert Conferences +INSERT INTO conferences (id, date, youtube, title, conference, content, duration, room, book_id, speaker_id) VALUES +(RANDOM_UUID(), '2023-05-17', 'https://www.youtube.com/watch?v=TtQTF7M9xPo', 'Spring Boot 3.0 & Spring Framework 6.0: What''s New', 'Spring I/O 2023', 'Overview of new features in Spring Boot 3.0 and Spring Framework 6.0.', 50, 'Auditorium 1', (SELECT id FROM books WHERE title='Spring Boot in Action'), (SELECT id FROM speakers WHERE name='Stéphane Nicoll')), +(RANDOM_UUID(), '2023-05-18', 'https://www.youtube.com/watch?v=wYYKNhCXVVE', 'Securing Spring Boot 3 Applications', 'Spring I/O 2023', 'Exploration of security features in Spring Boot.', 45, 'Room B', (SELECT id FROM books WHERE title='Spring Security in Action'), (SELECT id FROM speakers WHERE name='Rob Winch')), +(RANDOM_UUID(), '2022-05-26', 'https://www.youtube.com/watch?v=TKK4Oi1Xc-Y', 'Reactive Spring', 'Spring I/O 2022', 'Deep dive into reactive programming with Spring.', 55, 'Main Hall', (SELECT id FROM books WHERE title='Reactive Spring'), (SELECT id FROM speakers WHERE name='Josh Long')), +(RANDOM_UUID(), '2024-05-23', 'https://www.youtube.com/placeholder_2024', 'Spring Native and GraalVM: The Future of Spring Applications', 'Spring I/O 2024', 'Exploring advancements in Spring Native.', 60, 'Innovation Theater', (SELECT id FROM books WHERE title='Native Image Definitive Guide'), (SELECT id FROM speakers WHERE name='Sébastien Deleuze')); + + +