martes, 21 de abril de 2020

31. Vaadin 14. Integrating external Js libraries from npm (3). Apexcharts

0. Introduction

Please read this previous post to get more details. I have tested this code with Vaadin 15 and it works!

Let's use the magnific library "apexcharts" .

Steps:
  1. Create a folder in the frontend called for instance "apex-charts".
  2. Create a file for instance "apex-charts-test.js" into the previous folder.
  3. Create a class that can contain other Html elements (like a Div) that will contain the chart.  


1. apex-charts-tests.js

Line 1: Import the required dependencies.
Line 5: A function for rendering a chart. The parameters are:
  • jsElem that is a representation of the right-hand assignment of a javascript variable that will contain the graph definition. In the apexchart web is an example that is used here. The value is defined in line 3 of the Main component that will display the component inside. and is a right-hand assignment of a variable in javascript (last source code of the post)
  • divId: the id of an Html element that will contain the char object.
Line 7: Construct a javascript sentence for evaluating the chart definition. In the next sentence is evaluated and the variable myCharDef is assigned the value of the chart.

Line 9: Retrieve the Html element whose id is the one defined in the variable divId.
Line10. Creates the chart object and in the next line is rendered into the Html element.

Line 15. The function is assigned to the window object so that it gets global presence and can be called from a Vaadin component.

Here is the code

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
import ApexCharts from "apexcharts";

class ApexChartsEdu  {
 
 render(jsElem, divId) {
  var myCharDef={};
  var kk="myCharDef="+jsElem;
  eval(kk);
  var aDiv=document.querySelector("#"+divId);
  this.chart=new ApexCharts(aDiv, myCharDef);
  this.chart.render();
 }
}

window.renderApexChart = function (jsValue, anId) {
 const mychart= new ApexChartsEdu();
 mychart.render(jsValue, anId);
}

2. The ApexChart class

Line 8: Import the "apex-charts-test.js"  referenced from the frontend folder
Line 9: Import the library from the npn repository.
Line 15: Constructor that requires the id of the div component so that it will be accesible from the javascript function
Line 20: Renders the chart with the  jsString definition.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package openadmin.ui.forms.elements.customform.apexcharts;

import com.vaadin.flow.component.dependency.JsModule;
import com.vaadin.flow.component.dependency.NpmPackage;
import com.vaadin.flow.component.html.Div;

@SuppressWarnings("serial")
@JsModule("./apex-charts/apex-charts-test.js")
@NpmPackage(value = "apexcharts", version = "3.18.1")

public class ApexCharts extends Div{

 private String myId="";
 
 public ApexCharts(String myId) {
  this.myId=myId;
  this.setId(myId);
    }
 
 public void render(String jsString) {
  System.out.println("myId="+ myId);
  getElement().executeJs("window.renderApexChart($0, $1)", jsString, myId);
 }
}


3. Creating a chart and inserting it into a component (Main)

Line 1: Create the Div component that will contain the chart
Line 2: add the component to the main component
Line 3: Define the right-hand assignment of the definition of the char
Line 16: rendering the component.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
ApexCharts apex= new ApexCharts("mysuperdiv");
add(apex);
String jsStuff="{" + 
  "  chart: {" + 
  "    type: 'line'" + 
  "  }," + 
  "  series: [{" + 
  "    name: 'sales'," + 
  "    data: [30,40,35,50,49,60,70,91,125]" + 
  "  }]," + 
  "  xaxis: {" + 
  "    categories: [1991,1992,1993,1994,1995,1996,1997, 1998,1999]" + 
  "  }" + 
  "}";
  
apex.render(jsStuff);




jueves, 9 de abril de 2020

30. Vaadin 14. Integrating external Js libraries from npm (2). TinyMce. NOT WORKING!!!!!

0. Introduction

Now that I have tried to use TinyMce in my Vaadin projects, but integrating with Vaadin 14 is rather difficult because the component tries to find themes and skins and cannot find them. This maybe can be due to the shadow dom, which can encapsulate most of the code so that variables and paths get local and cannot be accessed.

The main ideas of this post are from the Vaadin forum.

The steps to follow are:

1. Create a js file to get the function tinymce.init() global so that it can be accessed by the Vaadin component. this file will be put in the frontend folder. Remember that to make a function global, it should be incorporated into the "window" object.
2. Create the component class with the @NpmPackage annotation to import the tinymce module from npm
3. Execute the build process so that the npm_modules folder will be filled with the npm dependencies. If you, instead of executing the build process, execute Run As-> Run on server, then you will get an error that cannot find the "./tinymceEdu/themes/silver.index.js". To solve this error, you should comment the line 18 that tries to import this module by means of the @JsModule annotation.
4. Copy the folder npm_modules/tinymce/skins to src/main/webapp folder and we will get the folder src/main/webapp/skins.
5. Create a folder "tinymceEdu" (or any other name different from "tinymce") in the folder frontend.
6. Copy the folder npm_modules/tinymce/themes to the last folder, getting frontend/tinymceEdu/themes folder
7. Create the main form and include this component

1. The js file

I have called it tinymce-test.js . It will be placed in the "frontend" folder


1
2
3
4
5
import * as TINY from "tinymce";

window.initTinymce = function() {
 tinymce.init({selector:'textarea'});
}


2. The component class



 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package com.gmail.ximo;
import java.util.UUID;
import com.vaadin.flow.component.AttachEvent;
import com.vaadin.flow.component.Tag;
import com.vaadin.flow.component.customfield.CustomField;
import com.vaadin.flow.component.dependency.JsModule;
import com.vaadin.flow.component.dependency.NpmPackage;

@SuppressWarnings("serial")

//Our js file used to make the library functions global
@JsModule("./tinymce-test.js") // New! [1]

//Theme
@JsModule("./tinymceEdu/themes/silver/index.js") // Modified! [2]

//Npm
@NpmPackage(value = "tinymce", version = "5.2.1") // Modified! [3]
@Tag("textarea")
public class TinyMce extends CustomField<String> {
    public TinyMce(){
        this.setId(UUID.randomUUID().toString());
    }

    @Override
    protected String generateModelValue() {
        return getElement().getText();
    }

    @Override
    protected void setPresentationValue(String s) {
        getElement().setText(s);
    }

    @Override
    protected void onAttach(AttachEvent attachEvent) {
        super.onAttach(attachEvent);
        this.getElement().executeJs("window.initTinymce()"); //Modified [4]
        this.setWidth("500px"); // Modified [5]
    }
}

Line 12: references the file" tinymce-text.js" so that we can use the "window.initTinymce()" function declared in this js file. The execution of this function is in line  38.

Line 15: references the copied folder themes from npm_modules/tinymce/themes to frontend/tinymceEdu/themes

Line 18: the reference to the tinymce package from the npm repository

Line 19: the tag used in html

Line 38: The use of the function tinymce.init() which has been "globalized" in the "tinymce-test.js" file passing it to the window object


3. The main view class

Here is the simple code


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.gmail.ximo;

import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.server.PWA;
import javax.annotation.PostConstruct;


/**
 * The main view contains a simple label element and a template element.
 */
@SuppressWarnings("serial")
@Route("")
@PWA(name = "Project Base for Vaadin Flow with CDI", shortName = "Project Base")
public class MainView1 extends VerticalLayout {

    @PostConstruct
    public void init() {
        TinyMce tiny= new TinyMce(); 
        add(tiny);
    }
}

Now you can run it (if you use the start-up project from Vaadin as Run As- Maven Build.. - Goals : "clean package tomee:run" or Run -Run on Server ... if you are using Tomcat server and have configured the pom.xml as in this post).



Happy coding!

29. Vaadin 14. Integrating external Js libraries from npm (1). Playing a note with Tone.js

0. Introduction

This post is based on a contribution to Vaadin form by Arthur Signell. This is a very important and useful contribution.

The steps for achieving these aims are:
  1. Create a js file (in the frontend folder) that defines the calls to the js library. 
  2. Create a java file that imports the js library from the npm and execute the functions of the previously created js file

1. JS important tips

1. Nearly all the variables and functions created are local! That means that they cannot be used in a Vaadin Java class. For instance variable "a" and function "myfunct" are local:
  
     var a = 5; 
     myfunc ( c, d) { return c + d; }
  
2. If you want a variable or function that can be recognized by a Vaadin component, (that means to make them global), you must attach it to window object. For instance  variable "b" and function "otherfunct" are global:

    window.b = 5;
    window.otherfunct= function ( c, d) { return c + d; }

3. If you want to make an "import" of the external javascript library for instance:

    import * as TONE from "tone";
 
  The reference to the external javascript library must be made by means of @NpmPackage  annotation in the Vaadin component class that uses it:

   @NpmPackage (value = "tone", version = "13.8.25")

Here we are importing the library "tone.js" version "13.8.25" from the npm repository

4. To reference the created js file from the Vaadin Component class, the @JsModule annotation can be used:
 
   @JsModule("./tone-test.js")

2. Create the js file

This simple file makes visible (global) the variables and functions we need to use and call from our Vaadin component.

This file must be in the "frontend" folder!. 

Here is a file ( tone-test.js )  that plays a note with a specific duration using tone.jshttps://tonejs.github.io/ library 


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import * as TONE from "tone";

class ToneTest {
 //Create the synthesizer
 init() {
  this.synth = new TONE.Synth().toMaster();
 }
 
 // Play notes
 play(note, duration) {
  this.synth.triggerAttackRelease(note, duration)
 }  
}  

//Make the functions global

//Create the synthesizer
window.initTone = function() {
 window.tt = new ToneTest(); //makes the variable global
 window.tt.init();
}

// Play the notes
window.play = function (note,duration) {
 window.tt.play(note,duration);
}

Now it is possible to call the functions "window.initTone" and "window.play" as they are attached to the window that is a global object (Lines 18 and 24)

Don't forget to make the imports (Line 1)

There is an intermediate class (ToneTest)  for managing direct calls to tone.js functions (Lines 3-13)


3. The Vaadin Component Class

It is important to note:
  1.  Line 21: the @NpmPackage annotation for importing the library tone.js version 13.8.25 from npm
  2. Line 20: The @JsModule that import the previous "tone-test.js" javascript file that was saved in the frontend folder. If we had saved it in an inner folder of the frontend folder, for instance, frontend/tone the annotation would have been @JsModule("./tone/tone-test.js")
  3. Line 28: In the init() method annotated as PostConstruct, we make sure to initialize the synthesizer. To execute a js code the getElement().executeJs(function,parameters...) is used.
  4. Line 32: The click event of the button is assigned to the js call to play a "C4" note for a duration of an 8th of a whole note.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package com.gmail.ximo;

import com.vaadin.flow.component.Key;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.dependency.JsModule;
import com.vaadin.flow.component.dependency.NpmPackage;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.server.PWA;

import javax.annotation.PostConstruct;
import javax.inject.Inject;

/**
 * The main view contains a simple label element and a template element.
 */
@SuppressWarnings("serial")
@Route("")
@PWA(name = "Project Base for Vaadin Flow with CDI", shortName = "Project Base")
@JsModule("./tone-test.js")
@NpmPackage(value = "tone", version = "13.8.25")
public class MainView extends VerticalLayout {


    @PostConstruct
    public void init() {
        //Initialize the synthesizer        
        this.getElement().executeJs("window.initTone()");

        // Play a C4 Note with a duration of a 8th
        Button button = new Button("Say hello",
            e -> getElement().executeJs("window.play($0,$1)", "C4", "8n"));
        // You can specify keyboard shortcuts for buttons.
        // Example: Pressing enter in this view clicks the Button.
        button.addClickShortcut(Key.ENTER);

        add(button);
    }

}

I hope you have a good time!