Implementing moving rows

And so, let's start building our application. As mentioned earlier, our application will consist of two parts of the graphical interface: a container with moving lines at the top and a bottom part where the application configuration editing elements will be located.

First, let's create a template called moving-containers and a templates/blocks directory to place the template in.

$ cd [PROJECT_DIRECTORY]/templates $ mkdir blocks $ cd blocks $ fcfmngr create template moving-containers

Let's display our template on the main page of the application.

File :templates/pages/main-page.tmpl:

... //~TEMPLATE body @{{ render.template(":templates/blocks/moving-containers.tmpl"); }}@

Now let's connect the CSS file with prepared styles to the :templates/blocks/moving-containers.tmpl template. Download the CSS file moving-containers.css and place it in the :templates/blocks directory and add an entry to the template.

File moving-containers.tmpl:

//~OPTIONS { .. include: ["moving-containers.css"], .. } ..

And so, in the moving-containers template, we will place the storage of an array of strings in the _strings argument, which we will read on the server side from a system variable. Accordingly, the moving-containers template must be rendered on the server side and the clientRendering template option must be set to false. Starting with this example, template arguments that are internal variables and should not be modified by external code will be named with an underscore.

Let's declare the _strings program argument in the :templates/blocks/moving-containers.tmpl file and write its content in the hooksProgrammableArgument template hook.

File :templates/blocks/moving-containers.tmpl

... //~ARGUMENTS { _strings: fcf.argProg(), } ...

File :templates/blocks/moving-containers.hooks.js

... // // Object of hooks for programmatically populated arguments. The function can be asynchronous // hooksProgrammableArgument: { // // Hook of the assembly of a programmatically populated argument created by the fcf.argProg() method with the name ARG_NAME // The function can be asynchronous // @result Returns the value of an argument or a Promise object // _strings: (a_taskInfo) => { return fcf.application.getSystemVariable("application:strings"); }, }, ...

Let's display a container with our strings in the template. Each row will be a separate view-item subtemplate with its own timer that will shift their position, and when shifted outside the nested container, change the motion vector.

Файл :templates/blocks/moving-containers.tmpl

... //~TEMPLATE <div class="moving-containers-view"> %{{ for(let i = 0; i < args._strings.length; ++i) { }}% @{{ render.template( "+view-item", { string: fcf.argRef(`_strings[${i}]`), })}}@ %{{ } }}% </div> //~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //~ SUB TEMPLATE view-item //~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //~OPTIONS view-item { autoUpdate: true, clientRendering: true, } //~ARGUMENTS view-item { fcfClassInner: "moving-containers-view-item", string: "", } //~TEMPLATE view-item @{{args.string}}@

And so, let's analyze what we wrote in the template. We have a main template that renders view-item subtemplates containing the strings themselves.

In the main template code, we output all rows using the reference argument. As you can see from the code, a 2-level address of the argument reference is used. The path to the reference argument must be specified in Javascript notation, using dots as field separators or square brackets []. We used passing the value through a reference argument so that when the row is edited, the view-item subpattern is automatically updated.

Also note the settings of the view-item subtemplate. The autoUpdate option is set to true to automatically update the template when editing. The clientRendering option is true because the main template does its rendering on the server side and all nested templates also inherit this setting. But for this template, this is not necessary.

Since our template only outputs one line, we set the CSS class of the wrapper DOM element using the fcfClassInner system argument.

Now let's write the wrapper implementation code for the view-item subpattern. First, let's create a wrapper file.

$ cd [PROJECT_DIRECTORY]/templates/blocks $ fcfmngr create wrapper moving-containers+view-item

And write the logic for moving lines inside the parent DOM element into the created file. The view-item template moves in the direction of a certain radian, and when it crosses the boundaries of the outer container, it changes direction. And yet, for beauty, we will paint our lines in different colors, the setColor() function will be responsible for this :-).

File :templates/blocks/moving-containers+view-item.wrapper.js

fcf.module({ name: "templates/blocks/moving-containers+view-item.wrapper.js", dependencies: ["fcf:NClient/Wrapper.js"], module: function(Wrapper){ return class WrapperImpl extends Wrapper{ constructor(a_initializeOptions){ super(a_initializeOptions); this._radians = Math.PI * Math.random() * 2; this._x = 0; this._y = 0; } initialize() { this.timer(); return super.initialize(); } attach(){ this.setPosition(); this.setColor(); return super.attach(); } timer() { let self = this; if (this._destroy) return; this.setPosition(); setTimeout(()=>{ self.timer(); }, 30); } setPosition(){ let selfRect = fcf.getRect(this.getDomElement()); let ownerRect = fcf.getRect(this.getParent().select(".moving-containers-view")[0]); let dx = Math.cos(this._radians) / 200; let dy = Math.sin(this._radians) / 200; if ((dx > 0 && (this._x + (selfRect.width / ownerRect.width) + dx > 1)) || (dx < 0 && (this._x + dx < 0))) { this._radians += ((Math.PI / 2) - this._radians) * 2; dx = -dx; } else if (((dy > 0) && (this._y + (selfRect.height / ownerRect.height) + dy > 1)) || ((dy < 0) && (this._y + dy < 0))){ this._radians += (Math.PI - this._radians) * 2; dy = -dy; } this._x += dx; this._y += dy; let rect = fcf.getRect(this.getParent().select(".moving-containers-view")[0]); this.getDomElement().style.left = (this._x * rect.width + rect.left) + "px"; this.getDomElement().style.top = (this._y * rect.height + rect.top) + "px"; } setColor(){ let string = this.getArg("string"); let r = 0; let g = 0; let b = 0; for(let i = 0; i < string.length; ++i) { r += string.charCodeAt(i)*9; g += string.charCodeAt(i)*99; b += string.charCodeAt(i)*999; } r = Math.floor(r % 255); g = Math.floor(g % 255); b = Math.floor(b % 255); let color = "#" + fcf.padStart(r.toString(16), 2, "0") + fcf.padStart(g.toString(16), 2, "0") + fcf.padStart(b.toString(16), 2, "0"); this.getDomElement().style.color = color; } }; } });

The wrapper code performs a move, which is performed by the setPosition() method called by the timer (the timer() method) every 30 milliseconds. The timer starts when the wrapper is initialized and finishes its execution after calling the fcf.NClient.Wrapper.destroy() method, analyzing the state of the internal class variable _destroy.

Since we are moving the subpattern and changing the color by changing the style property of the DOM element, we need to position it on re-render. This is done by overloading the fcf.NClient.Wrapper.attach() method and calling the setPosition() and setColor() methods.

It is also recommended that you familiarize yourself with the auxiliary functions used in the code.

Now you can run the application and see the result