Directory

← Back

Sasak UI Basic Components

Multi tab UI Framework for maximum user interface real estate

Author

Rating

Popularity

<100

Sasak UI provide unique integrated components which aims to maximize user interface real estate with reduced development time while embracing the now relaxed styling customization in Vaadin Framework.

We aim to provide a complete complementary UI framework for pro-grade application development. Consisting of Main Window with multi tab UI, which each dialog is contained within its own tab, in pursuit of pure multi tab experience under single browser tab.

Of course, client-server communication is still centralized as normally on Vaadin App. So you can't hope to open another tab while waiting for the blue line to finish its job.

I hope you can like it.

Sample code

@Tag("login-view")
@Route("login")
@PageTitle("COOK BIZ - Sasak UI Basic Demo")
public class LoginView extends DefaultLoginView implements BeforeEnterObserver {

    public LoginView() {

        setAppTitle("COOK BIZ");
        setAppFooter("© 2021 Sasak UI");
        setAppBackground("./images/pexels-flora-westbrook-1924815-small.jpg");
        setAppIcon("./icons/icon.png");
        initLayout();
        setLoginHandler(u -> {

            try {
                Thread.sleep(500);
            } catch (Exception ex) {

            }

            MasterUser user = new MasterUser(0, u.getKey(), u.getKey(), u.getValue());
            Session.login(user);
            UI.getCurrent().navigate(MainView.class);

        });

    }

    @Override
    public void beforeEnter(BeforeEnterEvent beforeEnterEvent) {

        if (Session.isLoggedIn()) {
            beforeEnterEvent.rerouteTo(MainView.class);
        }

    }
}

@Route("")
@PageTitle("COOK BIZ - Sasak UI Basic Demo")
@Theme(Lumo.class)
@Push(transport = Transport.WEBSOCKET_XHR)
@PWA(name = "COOK BIZ - Sasak UI Basic Demo", shortName = "COOK BIZ")
@BodySize(height = "100vh")
@PreserveOnRefresh
@SuppressWarnings("Duplicates")
public class MainView extends DefaultMainView implements BeforeEnterObserver {

    private Tabs tabs = new Tabs();
    private ModuleTabAdd newTabButton = new ModuleTabAdd();
    private List<ModuleTab> tabList = new ArrayList<>();

    private Div contentContainer = new Div();

    private List<ModuleBrowser> moduleBrowsers = new ArrayList<>();

    public MainView() {

        if (!Session.isLoggedIn()) {
            return;
        }

        setAppTitle("COOK BIZ");
        setAppFooter("© 2021 Sasak UI");
        setAppBackground("./images/pexels-flora-westbrook-1924815-small.jpg");
        setAppIcon("./icons/icon.png");

        if (Session.getCookBiz() == null) {
            CookBiz cookBiz = new CookBiz();
            Session.setCookBiz(cookBiz);
        }

        setMenuList(Session.getCookBiz().getMenuList());


        TopMenuItem menuUser = new TopMenuItem(VaadinIcon.USER.create(),
                Session.getCurrentUser().getFullName());
        menuUser.addClickListener(listener -> {
            Dialogs.ask("Log Out", "Are you sure want to log out?",
                    () -> {Session.logOut(); UI.getCurrent().navigate(LoginView.class);});
        });

        TopMenuItem menuAbout = new TopMenuItem(VaadinIcon.QUESTION_CIRCLE_O.create(), null);
        menuAbout.addClickListener(l -> {
            Dialogs.notifyInfoEx("Vaadin Framework © Vaadin Ltd.\n" +
                    "SO Chart © Syam Pillai\n" +
                    "Photo by Flora Westbrook from Pexels\n" +
                    "\n" +
                    "Sasak UI Basic © 2021 Sasak UI\n\n" +
                    "Demo written by Husein Musawa 2021");
        });

        List<TopMenuItem> topMenuItemList = new ArrayList<>();
        topMenuItemList.add(menuUser);
        topMenuItemList.add(menuAbout);

        setTopMenuItemList(topMenuItemList);
        initLayout();

    }

    @Override
    public void beforeEnter(BeforeEnterEvent beforeEnterEvent) {

        if (!Session.isLoggedIn()) beforeEnterEvent.rerouteTo("login");

    }
}
public class MasterUtensilBrowser extends ModuleBrowser {

    private ModuleToolbar moduleToolbar;
    private Grid<MasterUtensil> grid = new Grid<>();
    private ModuleBottomBar moduleBottomBar = new ModuleBottomBar(true,
            true);

    private ModuleGridLayout gridLayout = new ModuleGridLayout();
    private Div chartLayout = new Div();

    private String moduleName = "MasterUtensil";

    private List<MasterUtensil> utensilList;

    public MasterUtensilBrowser() {

        start();

    }

    public void start() {

        initLayout();
        loadDB();

    }

    private void initLayout() {

        initGridLayout();
        initChartLayout();

        moduleToolbar = new ModuleToolbar(moduleName, this);
        moduleToolbar.addGoBackListener(() -> {

            for (ModuleToolbar.GoBackListener listener : super.getGoBackListeners()) {
                listener.goBack();
            }

        });

        moduleToolbar.addRefreshListener(this::loadDB);
        moduleToolbar.addSearchTextChangedListener(s -> loadDB());
        moduleToolbar.addFilterClickedListener(() -> Dialogs.notifyWarning("Filter implementation goes here!"));
        moduleBottomBar.addAddButtonClickListener(this::addNew);
        moduleBottomBar.addReportButtonClickListener(() -> Dialogs.notifyWarning("Report implementation goes here!"));

        Div innerContainer = new Div(gridLayout, chartLayout);
        innerContainer.addClassName("module-inner-container");

        add(moduleToolbar, innerContainer);

    }

    private void initGridLayout() {

        grid.setWidth("100%");
        grid.getStyle().set("min-height", "400px");
        grid.getStyle().set("flex-grow", "1");
        grid.setHeight("100%");
        grid.addThemeVariants(GridVariant.LUMO_ROW_STRIPES);

        grid.addColumn(TemplateRenderer.<MasterUtensil>of(
                "<Label style=\"padding-left: 5px;\"><small>[[item.name]]</small></Label>")
                .withProperty("name", MasterUtensil::getName))
                .setHeader(new BoldLabel("Role", "5px"))
                .setResizable(true)
                .setWidth("60%");

        grid.addItemClickListener(listener -> {

            MasterUtensil masterUtensil = listener.getItem();
            
            MasterUtensilEditor editor = new MasterUtensilEditor(true, masterUtensil.getId(),
                    i -> loadDB(), i -> loadDB());
            if (editor.start()) add(editor);

        });

        gridLayout.add(grid, moduleBottomBar);

    }

    private void initChartLayout() {

        chartLayout.getStyle().set("height", "-moz-fit-content");

    }

    private void reloadCharts() {

        chartLayout.removeAll();
        initChart1();

    }

    private void loadDB() {
        
        utensilList = Session.getCookBiz().getUtensilList();
        List<MasterUtensil> finalList = new ArrayList<>();

        String s = moduleToolbar.getSearchText();

        if ((s != null) && (!s.trim().isEmpty())) {
            utensilList.forEach(i ->
            {if (i.getName().toLowerCase().contains(s)) finalList.add(i);});
        } else {
            finalList.addAll(utensilList);
        }

        finalList.sort((i1, i2) -> i1.getName().toLowerCase().compareTo(i2.getName().toLowerCase()));
        grid.setItems(finalList);
        moduleBottomBar.updateStatus(Utilities.formatNumber(utensilList.size()) + " item" +
                (utensilList.size() > 1 ? "s" : ""));
        reloadCharts();

    }

    @Override
    public void hideContent() {

        grid.setVisible(false);

    }

    @Override
    public void refreshContent() {

        grid.setVisible(true);

    }

    private void initChart1() {

        SOChart chart = new SOChart();
        chart.setSize("500px", "300px");

        PieChart pieChart = new PieChart();
        pieChart.setName("Total Usage");
        Position p = new Position();
        p.setTop(Size.pixels(20));
        pieChart.setPosition(p);

        DataMatrix dataMatrix = new DataMatrix();
        List<String> deptList = new ArrayList<>();

        List<MasterUtensil> utensilList = Session.getCookBiz().getUtensilList();
        List<MasterRecipe> recipeList = Session.getCookBiz().getRecipeList();
        
        Map<String, Integer> volumeSubject = new HashMap<>();

        try {
            for (MasterUtensil utensil : utensilList) {
                int totalUsage = 0;

                for (MasterRecipe recipe : recipeList) {
                    if (recipe.getUtensilList() != null) {
                        for (MasterRecipeUtensil i : recipe.getUtensilList()) {
                            if (i.getUtensilId() == utensil.getId()) {
                                totalUsage++;
                            }
                        }
                    }
                }

                if (totalUsage > 0) volumeSubject.put(utensil.getName(), totalUsage);
            }
        } catch (Exception ex) {
            Dialogs.notifyError(moduleName, 0,
                    "Failed to load Utensil chart data: \n" + ex);
            return;
        }

        Map<String, Integer> volumeSubjectCounted = volumeSubject
                .entrySet()
                .stream()
                .sorted(Collections.reverseOrder(Map.Entry.comparingByValue()))
                .collect(toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e2, LinkedHashMap::new));

        Map<String, Integer> volumeSubjectSummarized = new HashMap<>();

        int cn = 0;
        int totalLain2 = 0;

        for (Map.Entry<String, Integer> entry : volumeSubjectCounted.entrySet()) {
            cn ++;

            if (cn <= 10) {
                volumeSubjectSummarized.put(entry.getKey(), entry.getValue());
            } else {
                totalLain2 += entry.getValue();
            }
        }

        Map<String, Integer> volumeSubjectFinal = volumeSubjectSummarized
                .entrySet()
                .stream()
                .sorted(Collections.reverseOrder(Map.Entry.comparingByValue()))
                .collect(toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new));

        if (totalLain2 > 0) volumeSubjectFinal.put("OTHERS", totalLain2);

        String[] columns = new String[volumeSubjectFinal.size()];

        Data data = new Data();
        cn = 0;
        for (Map.Entry<String, Integer> entry : volumeSubjectFinal.entrySet()) {
            columns[cn] = entry.getKey();
            data.add(entry.getValue());
            cn++;
        }

        dataMatrix.setColumnNames(columns);
        dataMatrix.addRow(data);

        pieChart.setItemNames(dataMatrix.getColumnNames());
        pieChart.setData(dataMatrix.getRow(0));

        TextStyle whiteText = new TextStyle();
        whiteText.setColor(new Color("white"));

        Title title = new Title("Recipe Occurence");
        p = new Position();
        p.justifyCenter();
        title.setPosition(p);
        title.setTextStyle(whiteText);

        Toolbox toolbox = new Toolbox();
        toolbox.addButton(new Toolbox.Download());
        chart.add(pieChart, title, toolbox);
        chart.disableDefaultLegend();
        Div chartContainer = new Div(chart);
        chartContainer.getStyle().set("padding", "5px");
        chartContainer.getStyle().set("margin-bottom", "20px");
        chartLayout.add(chartContainer);

    }

    private void addNew() {

        MasterUtensilEditor editor = new MasterUtensilEditor(false, 0,
                i -> {
                    loadDB();
                    moduleBottomBar.getMenuAdd().focus();
                }, null);

        if (editor.start()) add(editor);

    }

    private void showReport() {}
}
public class MasterUtensilEditor extends Window {

    private static String moduleName = "MasterUtensil";
    private boolean editMode;
    private long id;
    private MasterUtensil masterUtensil = new MasterUtensil();

    private Consumer<MasterUtensil> onUpdate;
    private Consumer<MasterUtensil> onDelete;

    private NullableTextField name = new NullableTextField();

    private Button btnSave = new Button("Save");
    private Button btnDelete = new Button("Delete");
    private Button btnClose = new Button("Cancel");

    private Div vlMain;
    private FormLayout formLayout = new FormLayout();

    private Div buttons;

    {
        formLayout.setResponsiveSteps(new FormLayout.ResponsiveStep("0", 1));
        formLayout.getStyle().set("padding", "10px");

    }

    public MasterUtensilEditor(boolean editMode, long id, Consumer<MasterUtensil> onUpdate,
                               Consumer<MasterUtensil> onDelete) {

        this.editMode = editMode;
        this.id = id;
        this.onUpdate = onUpdate;
        this.onDelete = onDelete;

    }

    public boolean start() {

        initLayout();

        if (editMode) {
            if (!loadDB()) return false;
        }

        if (!editMode) {
            super.setTitle("Add Utensil");
        } else {
            super.setTitle("Utensil - " + masterUtensil.getName());
        }

        return true;
    }

    private void initLayout() {

        initFormLayout();
        initButtons();

        vlMain = getContent();
        vlMain.add(formLayout);
        vlMain.setWidth("510px");

        name.focus();

    }

    private void initFormLayout() {

        name.setWidthFull();
        name.addKeyPressListener(Key.ENTER, l -> btnSave.focus());

        formLayout.addFormItem(name, "Name");
        formLayout.setWidth("calc(100% - 20px)");

    }

    private void initButtons() {

        btnDelete.addThemeVariants(ButtonVariant.LUMO_ERROR);
        btnDelete.addClickListener(clickEvent -> delete());
        btnDelete.setEnabled(editMode);

        btnSave.getElement().setAttribute("theme", "primary");
        btnSave.addClickListener(clickEvent -> doSave());

        btnClose.getElement().setAttribute("theme", "secondary");
        btnClose.addClickListener(clickEvent -> {

            getElement().removeFromParent();

        });

        HorizontalLayout leftButtons = new HorizontalLayout(btnDelete);
        leftButtons.setSpacing(true);
        leftButtons.setSizeUndefined();

        HorizontalLayout rightButtons = new HorizontalLayout(btnSave, btnClose);
        rightButtons.setSpacing(true);
        rightButtons.setSizeUndefined();

        buttons = getFooter();
        buttons.add(leftButtons, new FullWidthSpacer(), rightButtons);

    }

    private void delete() {

        if (id < 1) return;
        if (masterUtensil == null) return;

        Dialogs.ask("Utensil", "Are you sure want to delete this item?", () -> {
            try {
                if (onDelete != null) {
                    Session.getCookBiz().getUtensilList().removeIf(i -> i.getId() == id);
                    onDelete.accept(masterUtensil);
                    Dialogs.notifyInfo("Data deleted.");
                }
                getElement().removeFromParent();
                return;
            } catch (Exception ex) {
                Dialogs.notifyError(moduleName, id, "Failed to delete Utensil: \n" + ex);
                return;
            }
        }, () -> {});

    }

    private boolean doSave() {

        if (!CheckField.checkTextField(name, "Utensil Name")) return false;

        masterUtensil.setName(name.getValue());

        if (!editMode) {
            if (Session.getCookBiz() == null) {
                CookBiz cookBiz = new CookBiz();
                Session.setCookBiz(cookBiz);
            }

            id = 1;

            for (MasterUtensil utensil : Session.getCookBiz().getUtensilList()) {
                if (utensil.getId() > id) {
                    id++;
                }
            }

            masterUtensil.setId(id);
            Session.getCookBiz().getUtensilList().add(masterUtensil);
            if (onUpdate != null) {
                onUpdate.accept(masterUtensil);
            }
        } else {
            for (MasterUtensil utensil : Session.getCookBiz().getUtensilList()) {
                if (utensil.getId() == masterUtensil.getId()) {
                    utensil.setName(masterUtensil.getName());
                }
            }
        }

        Dialogs.notifyDataSaved();
        if (onUpdate != null) onUpdate.accept(masterUtensil);
        getElement().removeFromParent();
        return true;
    }

    private boolean loadDB() {

        for (MasterUtensil i : Session.getCookBiz().getUtensilList()) {
            if (i.getId() == id) {
                masterUtensil = i;
                break;
            }
        }

        name.setValue(masterUtensil.getName());

        return true;

    }

}
@JsModule("./styles/dashboard-styles.js")
public class SaleDashboardBrowser extends ModuleBrowser {

    private final String moduleName = "DashboardSurveillance";

    private ModuleToolbar moduleToolbar;
    private FlexLayout backgroundToolbar;
    private FlexLayout chartLayout = new FlexLayout();

    private List<String> colorList = Arrays.asList("none", "white", "lightgray", "aliceblue", "blanchedalmond",
            "floralwhite", "paleturquoise", "lightyellow");

    private ComboBox<String> backgroundSelector = new ComboBox<>();

    @Data
    private class SurveillanceFindingStatus {String montName; int totalPendingApproval; int totalOpen; int totalClosed;}

    @Data
    class SurveillanceFindingArea {String areaCode; String areaName; int count;}

    public SaleDashboardBrowser() {

        initLayout();
    }

    private void initLayout() {

        moduleToolbar = new ModuleToolbar(moduleName, this);
        moduleToolbar.addGoBackListener(() -> {

            for (ModuleToolbar.GoBackListener listener : super.getGoBackListeners()) {
                listener.goBack();
            }

        });
        moduleToolbar.addRefreshListener(this::loadDB);
        moduleToolbar.setYear(null);
        moduleToolbar.setSearchTextFieldVisible(false);
        moduleToolbar.setFilterButtonVisible(false);

        initBackgroundSelector();
        initChartLayout();
        loadDB();

        ModuleInnerContainer innerContainer = new ModuleInnerContainer(chartLayout, backgroundToolbar);
        add(moduleToolbar, innerContainer);

    }

    private void initBackgroundSelector() {

        chartLayout.getStyle().set("background-color", "white");

        backgroundSelector.setClassName("color-selector");
        backgroundSelector.setItems(colorList);
        backgroundSelector.setValue("white");
        backgroundSelector.setAllowCustomValue(true);
        backgroundSelector.addCustomValueSetListener(l -> {
            if (l.getDetail() == null) return;
            if (l.getDetail().trim().length() < 1) {
                return;
            }

            colorList.add(l.getDetail());
            backgroundSelector.setItems(colorList);
        });

        backgroundSelector.addValueChangeListener(l -> {
            if ((l.getValue() == null) || (l.getValue().trim().isEmpty()) ||
                    (l.getValue().trim().length() < 3) ||
                    (l.getValue().trim().equals("none"))) {
                chartLayout.getStyle().remove("background-color");
            } else {
                chartLayout.getStyle().set("background-color", l.getValue());
            }
        });

        FormLayout formLayout = new FormLayout();
        formLayout.setResponsiveSteps(new FormLayout.ResponsiveStep("0px", 1,
                FormLayout.ResponsiveStep.LabelsPosition.ASIDE));
        Label backgroundLabel = new Label("Background:");
        backgroundLabel.getStyle().set("color", "white");
        formLayout.addFormItem(backgroundSelector, backgroundLabel);

        backgroundToolbar = new FlexLayout(new FullWidthSpacer(), formLayout);
        backgroundToolbar.setWidthFull();
        backgroundToolbar.getStyle().set("margin-right", "10px");

    }

    private void initChartLayout() {

        chartLayout.setFlexDirection(FlexDirection.ROW);
        chartLayout.setFlexWrap(FlexWrap.WRAP);
        chartLayout.setWidth("calc(100% - 40px)");
        chartLayout.setHeight("calc(100% - 90px)");
        chartLayout.getStyle().set("padding", "20px");
        chartLayout.getStyle().set("overflow", "auto");
    }

    private boolean loadDB() {

        chartLayout.removeAll();
        initSalesPerformanceChart();
        initSaleByProductChart();
        initIngredientOccurenceChart();
        initUtensilUsageChart();
        return true;

    }
/*
Chart implementation code not included due to insufficient sample code space. You can check the complete code on Github.
*/
}

Compatibility

(Loading compatibility data...)

Was this helpful? Need more help?
Leave a comment or a question below. You can also join the chat on Discord or ask questions on StackOverflow.

Version

Firefox compatibility issue fixes, including menu items crumpled on certain screen sizes. Window CSS fixes for better UI proportions.

Released
2021-08-16
Maturity
STABLE
License
Apache License 2.0

Compatibility

Framework
Vaadin 14+
Vaadin 10+ in 1.0.1
Browser
Firefox
Opera
Safari
Google Chrome
iOS Browser
Android Browser
Windows Phone
Microsoft Edge

Sasak UI Basic Components - Vaadin Add-on Directory

Multi tab UI Framework for maximum user interface real estate Sasak UI Basic Components - Vaadin Add-on Directory
Sasak UI provide unique integrated components which aims to maximize user interface real estate with reduced development time while embracing the now relaxed styling customization in Vaadin Framework. We aim to provide a complete complementary UI framework for pro-grade application development. Consisting of Main Window with multi tab UI, which each dialog is contained within its own tab, in pursuit of pure multi tab experience under single browser tab. Of course, client-server communication is still centralized as normally on Vaadin App. So you can't hope to open another tab while waiting for the blue line to finish its job. I hope you can like it.
Author Homepage
Online Demo
View on GitHub

Sasak UI Basic Components version 1.0.1
The component is pretty stable. I've made many applications using this component. A notable bug is when you open two or more grids on different tab (inside this UI) on Firefox, scrolling one of them will probably affect scrolling position on another. I haven't thoroughly investigating this, could be related to Vaadin or Firefox implementation on many things. For this moment, if this bother you, I suggest sticking with Chrome based browser or Safari.

Sasak UI Basic Components version 1.0.2
Firefox compatibility issue fixes, including menu items crumpled on certain screen sizes. Window CSS fixes for better UI proportions.

Online