Model, events, DOM

Basic

Never mind, do you use callbacks or controller as result you always will get reference to model of your pattern - results.model.

Let’s see closer on it. We will use template of login popup.

_patterns.get({ url : '/patterns/popup/pattern.html', node : document.body, hooks : { id : flex.unique(), title : 'Test dialog window', content : _patterns.get({ url : '/patterns/login/pattern.html', hooks : { login : _patterns.get({ url : '/patterns/controls/textinput_hidden_model/pattern.html', hooks : { type : 'text', not_valid_message : 'Sorry, but your login should not be shorter than 2 symbols. Please, try again.', not_valid : false } }), password: _patterns.get({ url : '/patterns/controls/textinput_hidden_model/pattern.html', hooks : { type : 'password', not_valid_message : 'Sorry, but your password should not be shorter than 6 symbols. Please, try again.', not_valid : false } }), controls: _patterns.get({ url : '/patterns/buttons/flat/pattern.html', hooks : [{ title: 'login', id: 'login_button' }, { title: 'cancel', id: 'cancel_button' }] }), }, }) }, onReady: function (results) { var instance = this, model = results.model; model[0]._content_[0]._controls_[0].$button.on('click', function () { var _model = model[0]._content_[0]; if (_model._login_[0].value.length <= 2) { _model._login_[0].not_valid = true; setTimeout(function () { _model._login_[0].not_valid = false; }, 2000); } if (_model._password_[0].value.length <= 4) { _model._password_[0].not_valid = true; setTimeout(function () { _model._password_[0].not_valid = false; }, 2000); } }); } }).render(); <div data-type="Pattern.Login"> <p>Login</p> {{ login }} <p>Password</p> {{ password }} <div data-type="Pattern.Controls">{{ controls }}</div> </div> <p>{{ ::value }}</p> <div data-type="TextInput.Wrapper"> <div data-type="TextInput.Container"> <input data-type="TextInput" type="{{ type }}" value="{{ ::value }}" name="TestInput" {{ $input }} onblur="{{ @onblur }}"/> </div> <!--type=password--> <div data-type="TextInput.Info.Icon"></div> <div data-type="TextInput.Info.Popup"> <p data-type="TextInput.Info.Popup">You can use in password only letters, number and _</p> </div> <!--type--> </div> <!--showinfo=show--> <p data-type="TextInput.Error" data-not_valid="{{ ::not_valid }}">{{ not_valid_message }}</p> <!--showinfo--> <a data-type="Buttons.Flat" id="{{ id }}" {{ $button }} onclick="{{ @onclick }}">{{ title }}</a>

For this template we will get next model.

In model we can have next properties:

Let’s go step by step.

Model

_patterns.get({ url : 'some_url_to_pattern', onReady : function (results) { var instance = this, //or instance = results.instance listener = results.listener, model = results.model, exchange = results.exchange; }, onFail : function () { }, }).render(); var Controller = function (results) { this.instance = results.instance; this.listener = results.listener; this.model = results.model; this.exchange = results.exchange; }; Controller.prototype = { //Pay your attantion, here is same names, like callbacks have onReady : function (results) { }, onUpdate : function (results) { }, setInstance : function (results) { }, myMethod_0 : function () { }, myMethod_1 : function () { }, myMethod_N : function () { }, }; _patterns.get({ url : 'some_url_to_pattern', controller : Controller }).render();

Model – is a same thing as hook (and default value of model is defining as hook), but you can get access to model-property in any time without updating pattern. For example, to change value of hook you should use method instance.update ( read more).

To define model you should use next syntax in your pattern (HMTL file): {{ ::name }}. You can define model for:

Let’s see, what is model with an example ( life).

<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>Flex.Template</title> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <link rel="stylesheet" type="text/css" href="pattern.css" /> </head> <body> <table data-type="Demo.Table"> <tr style="color:{{ ::color }};background:{{ ::background }};"> <th>{{ titles.column_0 }}</th> <th>{{ titles.column_1 }}</th> <th>{{ titles.column_2 }}</th> <th>{{ titles.column_3 }}</th> </tr> {{ rows }} </table> </body> </html> <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>Flex.Template</title> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <link rel="stylesheet" type="text/css" href="pattern.css" /> <script type="text/javascript" src="conditions.js"></script> </head> <body> <tr> <td style="background:{{ ::background_0 }};color:{{ ::color_0 }}" data-type="{{ ::just_an_example }}">{{ ::column_0 }}</td> <td style="background:{{ ::background_1 }};color:{{ ::color_1 }}">{{ ::column_1 }}</td> <td style="background:{{ ::background_2 }};color:{{ ::color_2 }}">{{ ::column_2 }}</td> <td style="background:{{ ::background_3 }};color:{{ ::color_3 }}">{{ ::column_3 }}</td> </tr> </body> </html>

As you can see, we’ve defined several references to model: column_0-3, background_0-3, color_0-3 and just_an_example (just to show, where can be defined model).

Now we can get access to these properties via model.

var data_source = []; for (var i = 0; i < 20; i += 1) { data_source.push({ column_0: (Math.random() * 1000).toFixed(4), column_1: (Math.random() * 1000).toFixed(4), column_2: (Math.random() * 1000).toFixed(4), column_3: (Math.random() * 1000).toFixed(4), }); } _patterns.get({ url : '/patterns/table/container/pattern.html', node : document.body, hooks : { titles : { column_0: 'Column #0', column_1: 'Column #1', column_2: 'Column #2', column_3: 'Column #3', }, rows : _patterns.get({ url: '/patterns/table/row_con/pattern.html', hooks: data_source, }) }, onReady: function (results) { (function (model) { var fun = function () { var r = Math.round(19 * Math.random()), c = Math.round(3 * Math.random()); model[0]._rows_[r]['column_' + c] = (Math.random() * 1000).toFixed(4); model[0]._rows_[r]['background_' + c] = 'rgb(' + Math.round(255 * Math.random()) + ', ' + Math.round(255 * Math.random()) + ', ' + Math.round(255 * Math.random()) + ')'; model[0]._rows_[r]['color_' + c] = 'rgb(' + Math.round(255 * Math.random()) + ', ' + Math.round(255 * Math.random()) + ', ' + Math.round(255 * Math.random()) + ')'; setTimeout(fun, Math.ceil(50 * Math.random())); }; fun(); }(results.model)); } }).render();

Pay your attention on the next scheme and you will see, how properties mapped in model object.

All nested levels in your pattern has same names as hook has, but with symbol "_" at the begin and at the end - _rows_. All names of properties are same, like it was defined in pattern (HTML-file).

Array functionality

As you have noticed, all levels of model are arrays. So, you can make modification of your template using all features of array. For example, you can add or remove some values.

//Add new row model[0]._rows_.push({ column_0: (Math.random() * 1000).toFixed(4), column_1: (Math.random() * 1000).toFixed(4), column_2: (Math.random() * 1000).toFixed(4), column_3: (Math.random() * 1000).toFixed(4), }); //Remove row model[0]._rows_.splice(0, 1);

All changes will be immediately rendered. Here you can try an example.

Model changes

If you have in your pattern model-reference {{ ::name }} you will get next object: $$$name. This object has only two methods.

Method Return Description
bind(handle [function]) id [string] Add handle to define model reference. Will be called each time model reference changes.
unbind(id [string]) [bool] Remove handle by ID

DOM manipulations

Flex-Patterns allows use direct access to DOM in opposite of many other patterns-engines (let’s say reactJS for example). If developer understands, what he does – why we should deny do it?

To define some node as "will be used for DOM manipulations" you should mark such node as {{ $name }} Let’s see with example (which was used before).

<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>Flex.Template</title> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <link rel="stylesheet" type="text/css" href="pattern.css" /> </head> <body> <table data-type="Demo.Table"> <tr {{ $title }}> <th>{{ titles.column_0 }}</th> <th>{{ titles.column_1 }}</th> <th>{{ titles.column_2 }}</th> <th>{{ titles.column_3 }}</th> </tr> {{ rows }} </table> </body> </html> <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>Flex.Template</title> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <link rel="stylesheet" type="text/css" href="pattern.css" /> <script type="text/javascript" src="conditions.js"></script> </head> <body> <tr> <td {{ $column_0, cells }}>{{ ::column_0 }}</td> <td {{ $column_1, cells }}>{{ ::column_1 }}</td> <td {{ $column_2, cells }}>{{ ::column_2 }}</td> <td {{ $column_3, cells }}> <div> <p>{{ ::column_3 }}</p> </div> <div data-style="Buttons"> <a {{ $remove }}>Remove</a> <a {{ $add }}>Add new</a> </div> </td> </tr> </body> </html>

As you can see we added: $title, $column_0-3, $cells, $add and $remove. It means, that in our model will appear references to DOM manipulation objects.

You can see, that DOM-references have same names as were defined in template (HTML-file), but with symbol "$" at the begin.

Each DOM-reference is an object (bound with defined (in template) node(s)), which has several useful methods:

Method Description
add(nodeList [NodeList]) Add nodes into collection. All methods will be done for all nodes in collection.
css(css [object]) Change styles of all nodes in collection.
addClass(className [string]) Add CSS-class to all nodes in collection.
removeClass(className [string]) Remove CSS-class from all nodes in collection.
show() Show all nodes in collection.
hide() Hide all nodes in collection.
remove() Remove from page all nodes in collection. Pay your attention, collection will be cleared too.
append(parent [node]) Append all nodes from collection to target node.
insertBefore(parent [node], before [node]) Insert before defined node all nodes from collection.
attr(name [string], value [string]) Get or set defined attribute to all nodes in collection.
removeAttr(name [string]) Remove defined attribute from all nodes in collection.
on(event [string], handle [function]) Attach event-handle to all nodes in collection.
getAsArray() Return collection of all nodes as Array.

Now we can call our pattern and attach necessary events using DOM-references.

_patterns.get({ url : '/patterns/table/container/pattern.html', node : document.body, hooks : { titles : { column_0: 'Column #0', column_1: 'Column #1', column_2: 'Column #2', column_3: 'Column #3', }, rows : _patterns.get({ url : '/patterns/table/row_con_buttons/pattern.html', hooks : [], }) }, onReady: function (res) { function add(event, indexes) { var index = indexes !== void 0 ? indexes[1] : rows.length; rows.splice(index, 0, { column_0: (Math.random() * 1000).toFixed(4), column_1: (Math.random() * 1000).toFixed(4), column_2: (Math.random() * 1000).toFixed(4), column_3: (Math.random() * 1000).toFixed(4), }); rows[index].$add.on('click', add); rows[index].$remove.on('click', remove); }; function remove(event, indexes) { rows.splice(indexes[1], 1); }; var rows = res.model[0]._rows_; _node('a[id="add_row"]').events().add('click', add); } }).render();

In addition, you can add your own functionality. Let’s create method, which add background to all nodes in collection.

_patterns.classes.NODE_LIST.addMethod('setBackground', function (bg) { Array.prototype.forEach.call(this.collections, function (collection) { Array.prototype.forEach.call(collection, function (node) { node.style.background = bg; }); }); });

Now you can call your method for any element in DOM-reference.

model[0]._rows_[0].$cells.setBackground('rgb(0,200,0)');

Events

Sure, in case, when you need just attach some event to node, it isn’t nice way define node as DOM-reference and bind handle to node’s event "manually", like it was in previous example.

Fortunately, you have much more easy way: define name of handle in pattern (HTML-file).

Let’s see it on example – popup for authorization process.

<div data-type="Pattern.Login"> <p>Login</p> {{ login }} <p>Password</p> {{ password }} <div data-type="Pattern.Controls">{{ controls }}</div> </div> <div data-style="Popup" id="{ {id }}"> <div data-style="Popup.Container"> <div data-style="Popup.Title"> <p data-style="Popup.Title">{{ title }}</p> </div> <div data-style="Popup.Content">{{ content }}</div> <div data-style="Popup.Bottom"> <p data-style="Popup.Bottom">{{ bottom }}</p> </div> </div> </div> <p>{{ ::value }}</p> <div data-type="TextInput.Wrapper"> <div data-type="TextInput.Container"> <input data-type="TextInput" type="{{ type }}" value="{{ ::value }}" name="TestInput"/> </div> </div> <a data-type="Buttons.Flat" id="{{ id }}" onclick="{{ @onButtonClick }}">{{ title }}</a>

Pay your attention on pattern of button (last one). You can see where event onclick and name of handle onButtonClick with symbol "@" before (by this symbol event’s handles should be defined).

You can use any standard event (according W3C): onclick, onchange, onfocus and etc. Also, it’s never mind: onclick or onClick.

To implement handle of event you should define it in controller.

var Controller = function (results) { this.instance = results.instance; this.listener = results.listener; this.model = results.model; this.exchange = results.exchange; }; Controller.prototype = { onReady : function (results) { }, onUpdate : function (results) { }, setInstance : function (results) { }, content : { controls: { onButtonClick: function (event, indexes) { //Do something with it //In case of button "login" -> indexes === [0, 0] => (model[0]._content_._controls_[0]) //In case of button "cancel" -> indexes === [0, 1] => (model[0]._content_._controls_[1]) } } } }; var Controller = function (results) { this.instance = results.instance; this.listener = results.listener; this.model = results.model; this.exchange = results.exchange; }; Controller.prototype = { onReady : function (results) { }, onUpdate : function (results) { }, setInstance : function (results) { }, content : { controls: { onButtonClick: [ function onLogin(event, indexes) { //Do something with it }, function onCancel(event, indexes) { //Do something with it } ] } } }; _patterns.get({ url : '/patterns/popup/pattern.html', node : document.body, hooks : { id : id, title : 'Test dialog window', content : { login : { type: 'text' }, password: { type: 'password' }, controls: [{ title: 'login', id: 'login_button' }, { title: 'cancel', id: 'cancel_button' }], } }, controller: Controller }).render();

As you can see you have two ways: you can define common handle (for both buttons) or define handle for each button separately. For authorization popup both ways are okay. But when we are talking about for example tables, we can use only common handle.

Detect which button was clicked you can by argument index, which will be always in arguments of handle.

Hook's values accessors

For each hook in your pattern will be created accessor (prefix "__" in model). Accessor is function, which get only two arguments (value, safely)

Let’s see on examples. In next example we will change title of our popup in 1 second after pattern will be rendered.

_patterns.get({ url : '/patterns/popup/pattern.html', node : document.body, hooks : { id : id, title : 'Test dialog window', content : _patterns.get({ ... }) }, onReady: function (results) { var instance = this, model = results.model[0]; setTimeout(function () { model.__title('Updated title'); }, 2000); } }).render();

In this example we will place buttons into title area and place text into buttons area.

_patterns.get({ url : '/components/login/component.html', node : document.body, hooks : { id : id, title : 'Title' }, onReady : function (results) { var instance = this, model = results.model[0]; setTimeout(function () { model.__title(_patterns.get({ url : '/patterns/buttons/flat/pattern.html', hooks : [{ title: 'title button 0', id: 'title_button_0' }, { title: 'title button 1', id: 'title_button_1' }], })); model._content_[0].__controls('Title'); }, 2000); } }).render();