Binding an event and rendering from a template argument.

Handling events from template arguments

Some templates may provide DOM events. In particular, the @controls:button template fires a click event when the button is clicked, which can be handled.

You can attach an event handler to DOM events raised on a template DOM element using a template argument. Template arguments that begin with the fcfEvent prefix followed by the name of the event, with a leading large character, are a string that is considered a Javascript event handler string instruction. Within each event handler, the following variables are available:

  • fcf.NClient.Wrapper wrapper - wrapper object on which the event was fired
  • fcf.NClient.Wrapper parent - wrapper owner object
  • Event event - event object

Rendering from a template argument

We will need to host a row property editing panel, but so far without statefulness on the server side. We will use the tab bar (template @controls:tabs), which will consist of two tabs. In the first tab we will place the line editor. The second tab will change the background image, but we will implement this in the next step.

The tab template @controls:tabs has the template argument items - a list of tabs, the element of which must contain the "content" field, which in turn must be the string content of the tab.

In our case, the content will be a template that can be rendered using the template argument created by the fcf.argTmpl() function. This function can take 2 arguments: the path to the template and an optional parameter - an object with template arguments. As a result of its execution, an object is returned with information about the rules for assembling the template argument. The template argument is expanded into a string containing the result of rendering the template when assembling the template arguments and can be used in the final rendering.

Line editor implementation

Let's add tabs for editing the configuration. As the content of the string editing tab, we will create a strings subtemplate that will contain a string editor with the ability to add and remove a single string. Each line will be edited by the @controls:text-edit line edit template, and adding and deleting lines will be bound to the @controls:button buttons.

File :templates/blocks/moving-containers.tmpl

// Basic inheritance template
// Default: undefined
// extends:"",

// An array of roles that have permission to access the template
// Default: ["*"]
// access: ["*"],

// Automatic template update mode when the argument changes.
// Acceptable values:
// true|"all" - The update is performed on any change
// "external" - The update is performed only if the external template was the initiator of the change.
// false - The template is not being updated
// This option can be overridden by the fcfAutoUpdate template argument.
// Default: false
//autoUpdate: false,

// If true, the rendering is performed on the client side.
// Acceptable values:
// true|"all" - Rendering is done on the client, when done on the browser side
// "update" - The first render is done on the server side and the update is on the client side
// "update_np" - The first render is done on the server side and the update is on the client side.
// Parameters of the programmable type are not recalculated.
// false - Rendering is always done on the server side
// This option can be overridden by the fcfClientRendering template argument, but only if the option is true.
// Default: false
//clientRendering: false,

// Additional JS & CSS files to connect (JS files are also connected on the server side)
// Default: []
include: ["moving-containers.css"],

// Plug-in additional JS & CSS files on the client side
// Default: []
//clientInclude: [],

// If the parameter is false, the template is not wrapped in a container,
// a wrapper is not created for it, and its arguments are not available on the client.
// Default: true
//wrapper: true,

// DOM elements merge flag.
// If true, then existing items are not replaced when updated, but updated.
// Default: false
//merge: false,

// If set to true, the DOM elements are not changed by default when the template is updated.
// To update DOM elements on update, you need to call the update|reload|refresh methods with the updateStatic flag.
// Default: false
//static: false,

// Saving the initial values of children template arguments.
// This option can be overridden by the fcfInitialStorageOfChildren template argument.
// Default: false
//initialStorageOfChildren: false,

// The template is displayed when the template is locked or false,
// then the lock is performed only by the transparent container.
// If the option is true, then @controls:lock is used.
// This option can be overridden by the fcfLockTemplate template argument.
// Default: true
//lockTemplate: true
//~ARGUMENTS { _strings: fcf.argProg(), } //~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> <fieldset> <legend>Editor</legend> @{{ render.template("@controls:tabs", { items: fcf.argVal({ strings: { title: "Strings", content: fcf.argTmpl("+strings", {strings: fcf.argRef("_strings")}) }, settings: { title: "Settings", content: "", } }) }); }}@ </fieldset> //~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //~ SUBTEMPLATE view-item //~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //~OPTIONS view-item { autoUpdate: true, clientRendering: true, } //~ARGUMENTS view-item { fcfClassInner: "moving-containers-view-item", string: "", } //~TEMPLATE view-item @{{args.string}}@ //~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //~ SUBTEMPLATE strings //~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //~TEMPLATE strings %{{ for(let i = 0; i < args.strings.length; ++i) { }}% <p> <table width="100%"><tr> <td width="100%"> @{{ render.template("@controls:text-edit", {value: fcf.argRef(`strings[${i}]`), width: "100%"}); }}@ </td> <td> @{{ render.template("@controls:button", {title: fcf.t("Remove"), fcfEventClick: `parent.getParent().getParent().onRemoveString(${i})`}); }}@ </td> </tr></table> </p> %{{ } }}% @{{ render.template("@controls:button", {title: fcf.t("Add record"), fcfEventClick: "parent.getParent().getParent().onAddString()"}); }}@

And add the wrapper code of the main template, which will implement adding and deleting a string in the _strings array.

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

fcf.module({ name: "templates/blocks/moving-containers.wrapper.js", dependencies: ["fcf:NClient/Wrapper.js"], module: function(Wrapper){ return class WrapperImpl extends Wrapper{ constructor(a_initializeOptions){ super(a_initializeOptions); } onRemoveString(a_index){ let strings = this.getArg("_strings"); strings.splice(a_index, 1); this.setArg("_strings", strings); this.update(); } onAddString() { let strings = this.getArg("_strings"); strings.push("") this.setArg("_strings", strings); this.update(); } }; } });

In the above code, pay attention to three nuances.

The rendering of the tab template uses nesting the fcf.argTmpl() template argument in the "items" template argument created by the fcf.argVal() function. This is done because if a template argument is represented by a value, then its contents are not parsed for nested data and the arguments contained in it are not opened, and tokenization is not performed. But the data provided via the fcf.argVal() function is being processed and so we can use a nested template.

Also pay attention to how the onRemoveString() and onAddString() methods of the main template are called. Method data is accessed via the getParent() method. That is, the appeal occurs along the chain: the strings subpattern; control @controls:tabs; main template.

In the wrapper code, inside the onRemoveString() and onAddString() methods, the update method is called to update the template. This is because the main template and the strings subtemplate both have the autoUpdate template option set to false, and no updates will be automatically performed if the value inside the template arguments changes. This method was chosen because when editing a single line, there is no need to redraw the entire template, especially considering that its rendering is performed on the server side.

Now open the application in a browser window and see the result.