4.5.6.3. Using a JavaScript library

In this example, we will use the Slider component from the jQuery UI library. The slider will have two drag handlers that define a values range.

Create a new project in CUBA Studio and name it jscomponent.

Click the New UI component button on the Project properties navigator section. The UI component generation will open. Select the JavaScript component value in the Component type section.

studio js component wizard

Enter SliderServerComponent in the Vaadin component class name field.

Deselect the Integrate into Generic UI flag. The process of integration into the Generic UI is the same as described at Integrating a Vaadin Component into the Generic UI, so we won’t repeat it here.

After clicking the OK button Studio will generate the following files:

  • SliderServerComponent - a Vaadin component integrated with JavaScript.

  • SliderState - a state class of the Vaadin component.

  • slider-connector.js - a JavaScript connector for the Vaadin component.

Let’s examine the generated files and make necessary changes in the source code.

  • SlideState state class defines what data is transferred between the server and the client. In our case it is a minimal possible value, maximum possible value and selected values.

    package com.company.jscomponent.web.toolkit.ui.slider;
    
    import com.vaadin.shared.ui.JavaScriptComponentState;
    
    public class SliderState extends JavaScriptComponentState {
        public double[] values;
        public double minValue;
        public double maxValue;
    }
  • Vaadin server-side component SliderServerComponent.

    package com.company.jscomponent.web.toolkit.ui.slider;
    
    import com.vaadin.annotations.StyleSheet;
    import com.vaadin.ui.AbstractJavaScriptComponent;
    import com.vaadin.annotations.JavaScript;
    import elemental.json.JsonArray;
    
    @JavaScript({"slider-connector.js", "jquery-ui.js"})
    @StyleSheet({"jquery-ui.css"})
    public class SliderServerComponent extends AbstractJavaScriptComponent {
    
        public interface ValueChangeListener {
            void valueChanged(double[] newValue);
        }
    
        private ValueChangeListener listener;
    
        public SliderServerComponent() {
            addFunction("valueChanged", arguments -> {
                JsonArray array = arguments.getArray(0);
                double[] values = new double[2];
                values[0] = array.getNumber(0);
                values[1] = array.getNumber(1);
    
                getState(false).values = values;
    
                listener.valueChanged(values);
            });
        }
    
        public void setValue(double[] value) {
            getState().values = value;
        }
    
        public double[] getValue() {
            return getState().values;
        }
    
        public double getMinValue() {
            return getState().minValue;
        }
    
        public void setMinValue(double minValue) {
            getState().minValue = minValue;
        }
    
        public double getMaxValue() {
            return getState().maxValue;
        }
    
        public void setMaxValue(double maxValue) {
            getState().maxValue = maxValue;
        }
    
        @Override
        protected SliderState getState() {
            return (SliderState) super.getState();
        }
    
        @Override
        public SliderState getState(boolean markAsDirty) {
            return (SliderState) super.getState(markAsDirty);
        }
    
        public ValueChangeListener getListener() {
            return listener;
        }
    
        public void setListener(ValueChangeListener listener) {
            this.listener = listener;
        }
    }

    The server component defines getters and setters to work with the slider state and an interface of value change listeners. The class extends AbstractJavaScriptComponent.

    The addFunction() method invocation in the class constructor defines a handler for an RPC-call of the valueChanged() function from the client.

    The @JavaScript and @StyleSheet annotations point to files that must be loaded on the web page. In our example, these are JavaScript files of the jquery-ui library, the connector and the stylesheet for jquery-ui. You should place these files to the Java package of the Vaadin server component.

Download an archive with jQuery UI from http://jqueryui.com/download and put files jquery-ui.js and jquery-ui.css from the archive to the Java package of the SliderServerComponent class. At the jQuery UI download page, you can select which components should be put into the archive. For this demo, it is enough to select only the Slider item of the Widgets group.

js project structure
  • JavaScript connector slider-connector.js.

    com_company_jscomponent_web_toolkit_ui_slider_SliderServerComponent = function() {
        var connector = this;
        var element = connector.getElement();
        $(element).html("<div/>");
        $(element).css("padding", "5px 10px");
    
        var slider = $("div", element).slider({
            range: true,
            slide: function(event, ui) {
                connector.valueChanged(ui.values);
            }
        });
    
        connector.onStateChange = function() {
            var state = connector.getState();
            slider.slider("values", state.values);
            slider.slider("option", "min", state.minValue);
            slider.slider("option", "max", state.maxValue);
            $(element).width(state.width);
        }
    }

    Connector is a function that initializes a JavaScript component when the web page is loaded. The function name must correspond to the server component class name where dots in package name are replaced with underscore characters.

    Vaadin adds several useful methods to the connector function. this.getElement() returns an HTML DOM element of the component, this.getState() returns a state object.

    Our connector does the following:

    • Initializes the slider component of the jQuery UI library. The slide() function is invoked when the position of any drag handler changes. This function in turn invokes the valueChanged() connector method. valuedChanged() is the method that we defined on the server side in the SliderServerComponent class.

    • Defines the onStateChange() function. It is called when the state object is changed on the server side.

To demonstrate how the component works, let’s create the Product entity with three attributes:

  • name of type String

  • minDiscount of type Double

  • maxDiscount of type Double

Generate standard screens for the entity. Ensure that the value of the In module field is Web Module.

The slider component will set minimal and maximum discount values of a product.

Open the product-edit.xml file. Make minDiscount and maxDiscount fields not editable by adding the editable="false" attribute to the corresponding elements. Then add the new custom slider field to the fieldGroup.

As a result, the XML descriptor of the editor screen should look as follows:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<window xmlns="http://schemas.haulmont.com/cuba/window.xsd"
        caption="msg://editCaption"
        class="com.company.jscomponent.web.product.ProductEdit"
        datasource="productDs"
        focusComponent="fieldGroup"
        messagesPack="com.company.jscomponent.web.product">
    <dsContext>
        <datasource id="productDs"
                    class="com.company.jscomponent.entity.Product"
                    view="_local"/>
    </dsContext>
    <layout expand="windowActions"
            spacing="true">
        <fieldGroup id="fieldGroup"
                    datasource="productDs">
            <column width="250px">
                <field property="name"/>
                <field property="minDiscount" editable="false"/>
                <field property="maxDiscount" editable="false"/>
                <field id="slider" custom="true"/>
            </column>
        </fieldGroup>
        <frame id="windowActions"
               screen="editWindowActions"/>
    </layout>
</window>

Open the ProductEit.java file. Replace its content with the following code:

package com.company.jscomponent.web.product;

import com.company.jscomponent.web.toolkit.ui.slider.SliderServerComponent;
import com.haulmont.cuba.gui.components.AbstractEditor;
import com.company.jscomponent.entity.Product;
import com.haulmont.cuba.gui.components.Component;
import com.haulmont.cuba.gui.components.FieldGroup;
import com.haulmont.cuba.gui.components.VBoxLayout;
import com.haulmont.cuba.gui.data.Datasource;
import com.haulmont.cuba.gui.xml.layout.ComponentsFactory;
import com.haulmont.cuba.web.gui.components.WebComponentsHelper;
import com.vaadin.ui.Layout;

import javax.inject.Inject;

public class ProductEdit extends AbstractEditor<Product> {

    @Inject
    private FieldGroup fieldGroup;

    @Inject
    private ComponentsFactory componentsFactory;

    @Inject
    private Datasource<Product> productDs;

    @Override
    protected void initNewItem(Product item) {
        super.initNewItem(item);
        item.setMinDiscount(15.0);
        item.setMaxDiscount(70.0);
    }

    @Override
    protected void postInit() {
        super.postInit();

        Component box = componentsFactory.createComponent(VBoxLayout.class);
        Layout vBox = (Layout) WebComponentsHelper.unwrap(box);
        SliderServerComponent slider = new SliderServerComponent();
        slider.setValue(new double[]{getItem().getMinDiscount(), getItem().getMaxDiscount()});
        slider.setMinValue(0);
        slider.setMaxValue(100);
        slider.setWidth("240px");
        slider.setListener(newValue -> {
            getItem().setMinDiscount(newValue[0]);
            getItem().setMaxDiscount(newValue[1]);
        });
        vBox.addComponent(slider);
        fieldGroup.getFieldNN("slider").setComponent(box);
    }
}

The initNewItem() method sets initial values for discounts of a new product.

Method init() initializes the slider custom field. It sets current, minimal and maximum values of the slider and defines the value change listener. When the drag handler moves, a new value will be set to the corresponding field of the editable entity.

Start the application server and open the product editor screen. Changing the drop handler position must change the value of the text fields.

product edit