flex

patterns

What is it?

Flex-patterns is a front-end library (which can be extended to framework), which allows to use simple HTML files as templates. Flex-patterns doesn’t use any special syntax and any special ways for developing. Everything what you need: know HTML, CSS and a little bit JavaScript.

1Cache controller. All templates are cached automatically. It means, during each next loading of page, flex-patterns will try find cached version of templates and apply it instead loading it from server.

2Easy. Using flex-patterns is really easy. You don’t have to study any special syntax and some new logic. You just create a template as a standard HTML file. Also it gives you easy way to debug your template, because you can open it as separate HTML-file.

3Scalable. You can easy add your own solutions, your own functionality into flex-patterns. It gives you many possibilities to develop large and stable solutions. Besides you can easy refuse from JQuery (if you don’t use JQ-style).

4Repeated use. You can develop your own collection of templates. It can be controls, some element or heavy items with hard logic. In any case, each template has own scope and can be usage anywhere.

1Creating of template. You create a template as simple HTML file and mark places for content. You can create JS-controller to add some functionality of your template. You define some styles for your template and attach it as CSS file.

2Attaching. You can attach your template to the page via HTML or within JavaScript. After you will attach a template, flex-patterns loads HTML, CSS and JS of template and apply it to the page.

3Caching. Flex-patterns will automatically cache your template and sources (CSS and JS) to make possible load it from cache (instead requests to server) during next loading of page.

4Always actual. Flex-patterns automatically creates hashes for your templates and resources. If some file has to be updated (for example, it was changed), flex-pattern will do it automatically.

Installation

Just attach script of flex-patterns to your page. That’s all.

<script type="text/javascript" src="patterns.js"></script>

Creating template

To create template, you should know only three definitions:

  • Hook. {{name}}. Hook is a mark to paste some content at marked place.
  • Model. {{::name}}. By this mark you bind some property or attribute of node with model. For example, you can bind some place of some node and get access to it within something like this: model.myLable = "new HTML for this label".
  • Dom. {{$name}}. For all nodes (which was marked within {{$name}}) flex-patterns will create special wrapper to give you easy access to such functionality like: changing styles; attaching events, checking properties and etc.

The best way to explain something – show example. So, to see how it works, let’s create simple dialog for authorization. We will need several temples:

  • Popup. Template of popup window - link.
  • Textbox. Template of text area: login and password - link
  • Button. Template of button: logic and cancel - link
  • Dialog layout. Template with layout for our authorization window - link

Popup

As you can see, it’s just HTML file. There are no any special syntax or conditions of usage. You should just create simple HTML file. You can attach CSS files and JS files. All resource will be loaded with template by flex-patterns automatically and attached to page.

<!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>
    <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>
</body>
</html>

Using HTML-files as source of template allows you open your template as standalone page and debug it. It’s very effective way to correct styles or catch some bug. And sure, it’s really easy.

Dialog layout

Let’s postpone HEAD and other places of HTML and paste only BODY tag to save place and your time.

<div data-type="Pattern.Login">
    <p>Login</p>
    {{login}}
    <p>Password</p>
    {{password}}
    <div data-type="Pattern.Controls">{{controls}}</div>
</div>

Textbox

As you can see value of INPUT is assigned with paragraph. It means: if value of INPUT will be changed, content of P will be changed too. You will see it bellow.

<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>

Button

<a data-type="Buttons.Flat" id="{{id}}">{{title}}</a>

Attaching template

You can attach template by two ways:

  • via JavaScript
  • via HTML

To attach template via JavaScript you should to do this:

var id = flex.unique();
_patterns.get({
    url     : '/patterns/popup/pattern.html',
    node    : document.body,
    hooks   : {
        id      : id,
        title   : 'Test dialog window',
        content : _patterns.get({
            url     : '/patterns/patterns/login/pattern.html',
            hooks   : {
                login   : _patterns.get({
                    url     : '/patterns/controls/textinput/pattern.html',
                    hooks   : {
                        type: 'text',
                    }
                }),
                password: _patterns.get({
                    url     : '/patterns/controls/textinput/pattern.html',
                    hooks   : {
                        type: 'password',
                    }
                }),
                controls: _patterns.get({
                    url     : '/patterns/buttons/flat/pattern.html',
                    hooks   : [{ title: 'login', id: 'login_button' }, { title: 'cancel', id: 'cancel_button' }]
                }),
            }
        })
    },
    callbacks: {
        success: function (results) {
            var instance    = this,
                dom         = results.dom,
                model       = results.model;
            });
        }
    },
}).render();

Parameters of method get (_patterns.get(parameters)).

Name Obligatory Type Description
url yes string URL to HTML file of your template
node no node || string If you define this property your template will be mounted into defined node. You can define this property as reference to some node or as selector. For example, you can define it as: document.body (reference to node) or as ‘body’ (selector).
replace no bool Works only with property [node]. If it’s in true, your template will be mounted instead defined node.
before no node || string If you define this property your template will be mounted before defined node. You can define this property as reference to some node or as selector.
after no node || string If you define this property your template will be mounted after defined node. You can define this property as reference to some node or as selector.
id no string ID of your template.
hooks no object || array Collections of hooks.
conditions no object Collections of conditions.
callbacks no object You can define two callbacks.
callbacks.success no function This function will be called if rendering of your template was successful.
callbacks.fail no function This function will be called if rendering of your template was failed.
resources no any Property resources is used for exchanging of data between your application and controllers.
remove_missing_hooks no bool If flex-patterns cannot file hook, mark of hook ({{name}}) will be removed from markup. You can prevent such behavior by setting this property to false.

Let’s see to hooks closer. As you can see names of properties of object hooks are same as were defined in HTML of templates. Just back to HTML of popup template and you will find there: {{content}}, {{id}} and other.

So method _patterns.get() will return instance of pattern’s class. To mount it according parameters and call defined callbacks you should call method render().

Flex-patterns works asynchrony, because it gets sources of your template (HTML, JS and CSS) within HTTP(s) requests. That’s why method render() will not return any results of operation.

Here you can see result of discovered example – link.

"From box" you will get such functionality like: dragging window, resize window, maximize and restore window and focus control. That’s why in example by link you can do it. Read about it below.

To attach template within HTML, you should define in your page next HTML fragment:

<pattern src="/patterns/popup/pattern.html" style="display:none;">
    <id>0</id>
    <title>Test dialog window</title>
    <content src="/patterns/patterns/login/pattern.html">
        <login src="/patterns/controls/textinput/pattern.html">
            <type>text</type>
        </login>
        <password src="/patterns/controls/textinput/pattern.html">
            <type>password</type>
        </password>
        <controls src="/patterns/buttons/flat/pattern.html">
            <id>login_button</id><title>login</title>
            <id>cancel_button</id><title>cancel</title>
        </controls>
    </content>
</pattern>

You see tag PATTERN. Attribute SRC is used for definition of url of your template. Inside tag PATTERN you will find other tags, like ID, TITILE and CONTENT – it’s hooks. If hook consists other template you should just define attribute SRC, like it is done for CONTENT or LOGIN and others.

In this case you should not call any JavaScript code – flex-patterns automatically find template, build it and mount instead tag PATTERN.

Also you can define callbacks via attributes of tag PATTERN.

<pattern src="/patterns/popup/pattern.html" style="display:none;" success="callback_success" error="callback_error">
    ...
</pattern>

But in this case your template will not be rendered automatically to prevent situation, when callback functions aren’t ready (defined), but flex-pattern is ready to render. So, to render template from HTML with defined callbacks, you should do this (after your callback-functions will be ready):

_patterns.layout();

Let’s back again to our example link. If you type something in login field or password, you will see, that content is appeared ahead inputs. Just remember, how looks template of textbox:

<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>

It happens, because we bind value of INPUT and paragraph ahead of INPUT. Also you have access and to value of INPUT and to P of paragraph via model, but about it will be below.

Multiply values

To repeat some template within different data several times you should define value of hook as an array of values. Let’s see how it works on an example of table. We will need two templates.

Template of table

<table data-type="Demo.Table">
    <tr>
        <th>{{titles.column_0}}</th>
        <th>{{titles.column_1}}</th>
        <th>{{titles.column_2}}</th>
        <th>{{titles.column_3}}</th>
    </tr>
    {{rows}}
</table>

And template of row.

<tr>
    <td>{{column_0}}</td>
    <td>{{column_1}}</td>
    <td>{{column_2}}</td>
    <td>{{column_3}}</td>
</tr>

Let’s render our table. Here is an example of this template – link.

var data_source = [];
for (var i = 0; i < 100; 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/pattern.html',
            hooks: data_source,
        })
    }
}).render();

Pay your attention, that we can define name of hook as a chain of properties – titles.column_x. It allows us to use an understandable structure of a hook’s object and group properties by their meaning.

Also, you have seen, that in previous example (authorization window) we used multiple values for buttons, but pay your attention, in case of JavaScript attaching to define multiply value of hook we used an array to define several instances of template:

controls: _patterns.get({
    url     : '/patterns/buttons/flat/pattern.html',
    hooks   : [
        { title: 'login', id: 'login_button' }, 
        { title: 'cancel', id: 'cancel_button' }
    ]
})

But in HTML to do same we have to define hooks just several times:

<controls src="/patterns/buttons/flat/pattern.html">
    <id>login_button</id><title>login</title>
    <id>cancel_button</id><title>cancel</title>
</controls>

Controllers and callbacks

Controller and callback (success case) are absolutely same things. It’s a function, which called after render is finished and template is mounted. Let’s see on it.

/*Example of callback (success case) */
_patterns.get({
    url     : 'some_url',
    callbacks: {
        success: function (results) {
            var instance    = this,
                dom         = results.dom,
                model       = results.model,
                binds       = results.binds,
                map         = results.map,
                resources   = results.resources;
            ...
            }
    },
}).render();

/*Example of controller */
_controller(function (results) {
    var instance    = this,
        dom         = results.dom,
        model       = results.model,
        binds       = results.binds,
        map         = results.map,
        resources   = results.resources;
    ...
    });

As you can see controller and callback are equal. You can define callback during rendering template. To create a controller, you should create JS-file and place inside a definition of controller (like in example before).

If you want create a controller for our popup window (previous example of authorization popup), you should add only link to JS-file with controller.

<!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" />

    <!-- Attach controller of template -->
    <script type="text/javascript" src="conroller.js"></script>

</head>
<body>
    <div data-style="Popup">
        <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>
</body>
</html>

Now let’s see closer on object results, there are many important things inside.

Models and binds

Let’s again see on argument of callback / controller function.

function (results) {
    var instance    = this,
        dom         = results.dom,
        model       = results.model,
        binds       = results.binds,
        map         = results.map,
        resources   = results.resources;
    ...
}

As you can see a results-object has two properties (which are interesting for us at current moment): model and binds.

Everything, what will be marked by you in HTML of your template by model-mark {{::name}} will be in model and binds. Let’s back to our example of table and change a bit template of row.

<tr>
    <td style="background:{{::background_0}};">{{::column_0}}</td>
    <td style="background:{{::background_1}};">{{::column_1}}</td>
    <td style="background:{{::background_2}};">{{::column_2}}</td>
    <td style="background:{{::background_3}};">{{::column_3}}</td>
</tr>

As you can see we have added model-references to each cell ({{::column_x}} and style of each cell ({{::background_x}}). Also, pay your attention, in cases, when we do not need hook, because we have model reference, we should not define hook ({{hook}}) in layout of template. Now in callback or controller we have direct access to it. You can try this example by this link.

_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,
        })
    },
    callbacks: {
        success: function (results) {
            (function (model) {
                var fun = function () {
                    var r = Math.round(19 * Math.random()),
                        c = Math.round(3 * Math.random());
                    model.__rows__[r]['column_' + c] = (Math.random() * 1000).toFixed(4);
                    model.__rows__[r]['background_' + 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 you attention on property of model __rows__, by this way you can find all sub-models. In our example of login-window we have model-reference in template of textbox.

<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>

We can get access to this model-reference in callback.

_patterns.get({
    url: '/patterns/popup/pattern.html',
    node: document.body,
    hooks: {
        id: id,
        title: 'Test dialog window',
        content: _patterns.get({
            url: '/patterns/patterns/login/pattern.html',
            hooks: {
                login: _patterns.get({
                    url: '/patterns/controls/textinput/pattern.html',
                    hooks: {
                        type: 'text',
                    }
                }),
                password: _patterns.get({
                    url: '/patterns/controls/textinput/pattern.html',
                    hooks: {
                        type: 'password',
                    }
                }),
                controls: _patterns.get({
                    url: '/patterns/buttons/flat/pattern.html',
                    hooks: [{ title: 'login', id: 'login_button' }, { title: 'cancel', id: 'cancel_button' }]
                }),
            }
        })
    },
    callbacks: {
        success: function (results) {
            var instance = this,
                model = results.model;
            model.__content__.__login__.value = 'this new login';
        }
    },
}).render();

So, by __property__ you can repeat nesting in hooks.

If model allows you change values of assigned properties (or attributes), binds allows you attach your handle (for change event), or remove it.

All data, which you will place into model-reference will be sterilized. It means you cannot place some HTML into model-reference.

success: function (results) {
    var instance    = this,
        dom         = results.dom,
        binds       = results.binds,
        id          = null;
    //Add handle
    id = binds.__content__.__login__.value.addHandle(function (name, value) {
        var obj = this;
    });
    //Remove handle
    binds.__content__.__login__.value.removeHandle(id);
}

As you can see structure of binds-object is absolutely same as model-object has. You have two methods:

  • addHandle
  • removeHandle

Object this can be different. For example, if model-reference assigned with attribute or property of node – it will be reference to node. If model-reference assigned with style property – it will be style-object of node.

DOM and map

Other two important objects in callback / controller is: collection of nodes (object – dom) and map of template (object – map).

function (results) {
    var instance    = this,
        dom         = results.dom,
        model       = results.model,
        binds       = results.binds,
        map         = results.map,
        resources   = results.resources;
    ...
}

Map – is a map of all root-nodes, which was created by scheme of hooks. Each segment of map has property __context – this is an instance of MAP class and it gives you only one method – select(selector), which you can use to quickly find some node inside your template.

success: function (results) {
    var instance    = this,
        map         = results.map,
        nodes       = null;
    //Will find all P in whole pupup
    nodes = map.__context.select('p');
    //Will find all P inside popup in content area
    nodes = map.content.__context.select('p');
    //Will find all P in textbox-control of login
    nodes = map.content.login.__context.select('p');
}

You can use map-object to make searching of nodes faster and effective.

If map-object is generated automatically for your template always, dom-object is generated only if you had marked some nodes by this {{$name}}. Let’s change a bit template of button and add reference to dom.

<a data-type="Buttons.Flat" id="{{id}}" {{$button}}>{{title}}</a>

Pay your attention, reference {{$name}} should be added into attributes area of target node.

Now we can change a bit callback / controller of popup window.

success: function (results) {
    var instance    = this,
        dom         = results.dom;
    dom.listed.__content__.__controls__[0].button.on('click', function () {
        alert('You cannot login. It\'s just test. Login is "' + model.__content__.__login__.value + '", and password is "' + model.__content__.__password__.value + '"');
    });
    dom.listed.__content__.__controls__[1].button.on('click', function () {
        alert('Do not close me, please.');
    });
    dom.grouped.__content__.__controls__.button.on('click', function () {
        alert('This is common handle for both buttons');
    });
}

First of all, pay your attention, that dom-object has two lists: listed and grouped. We have two buttons in our template. In listed it will be represented as Array and you can get access to each button; but in grouped you will get access to both buttons in one time. So, what do use – depends only on your purposes.

You can try this example by this link. As you can see, we attached event-handles and now by clicking on buttons, we see alerts.

A final dom-object is an instance of DOM-class, which has next 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.

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-object.

success: function (results) {
    var instance    = this,
        dom         = results.dom;
    dom.grouped.__content__.__controls__.button.setBackground('rgb(0,200,0)');
}

Resources / exchanging of data

And last object in callback / controller is resources. Let’s see on next example, and everything will be clear.

_patterns.get({
    url     : '/patterns/popup/pattern.html',
    node    : document.body,
    hooks   : {
        id      : id,
        title   : 'Test dialog window',
        content : _patterns.get({
            url     : '/patterns/login/pattern.html',
            hooks   : {
                login   : _patterns.get({
                    url     : '/patterns/controls/textinput/pattern.html',
                    hooks   : {
                        type: 'text',
                    }
                }),
                password: _patterns.get({
                    url     : '/patterns/controls/textinput/pattern.html',
                    hooks   : {
                        type: 'password',
                    }
                }),
                controls: _patterns.get({
                    url     : '/patterns/buttons/flat/pattern.html',
                    hooks   : [{ title: 'login', id: 'login_button' }, { title: 'cancel', id: 'cancel_button' }]
                }),
            },
        })
    },
    resources: {
        field   : 'one',
        field2  : 'two'
    },
    callbacks: {
        success: function (results) {
            var instance    = this,
                resources   = results.resources;
            window.console.log(resources.field);
            window.console.log(resources.field2);
            
            //Result in console:
            //one
            //two
        }
    },
}).render();

As you can see, resources it’s just object, which was defined during rendering of template. You can use this object to exchange data between your application and template’s callback.

Conditions

In most cases we need more specific situation with template, which should be changed according data (hooks). For example, if we have our textbox is used for password – will be cool show some message to user with information about allowed symbols.

Let’s back to our example of authorization dialog and change a bit template of textbox.

<!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" />
    <!--Attach JS file with condition-handle-->
    <script type="text/javascript" src="conditions.js"></script>
</head>
<body>
    <p>{{::value}}</p>
    <div data-type="TextInput.Wrapper">
        <div data-type="TextInput.Container">
            <input data-type="TextInput" type="{{type}}" value="{{::value}}" name="TestInput" {{$input}} />
        </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>
</body>
</html>

As you can see we added new block of HTML-code. Format of condition is very easy: you define in HTML-comment name and value of condition, and close condition by name of it in HTML-comment.

<!--[condition_name]=[condition_value]-->
<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>
<!--[condition_name]-->

Next step – create a condition handle. Pay your attention, we have attached file "condition.js" to our template. Inside this file we have:

_conditions({
    type: function (data) {
        return data.type;
    }
});

Let’s discover this code:

  • Function _conditions is used to define conditions for template (where JS file was attached)
  • Function _conditions can have only one parameter – object with description of conditions.
  • Name of condition should be same as in HTML of template was defined. As you can see here we see condition "type" and same in HTML.
  • Condition-function (in our case "type") will get from flex.patterns one parameter – data. Data is an object with values of defined hooks. Our template has only one hook – "type". So, object data will have only one property "type". In condition function we just return it and that’s all.

How it works? During rendering, flex-patterns will find in HTML conditions, detect their names and will try to find condition-functions. If function is found, flex-patterns executes it within actual data to get result of it. And last step, flex-patterns will try to find condition for gotten result and will leave part of HTML code, which was defined for gotten value of condition. All not-actual conditions will be just removed from result.

So, if condition value will be "password" our template will add into layout addition HTML-code and we will see popup, like you can see by this link.

Let’s modify other our example, example of table and to template of row a several conditions.

<tr>
    <td>{{::column_0}}</td>
    <td>{{::column_1}}</td>
    <td>{{::column_2}}</td>
    <td>
        <div>
            <p>{{::column_3}}</p>
            <!--value_sets=0-->
                <!--sub_value_sets=0-->
                <p>This value is less than 111</p>
                <!--sub_value_sets-->
                <!--sub_value_sets=0.5-->
                <p>This value is more than 111 and less than 222</p>
                <!--sub_value_sets-->
                <!--sub_value_sets=1-->
                <p>This value is more than 222 and less than 333</p>
                <!--sub_value_sets-->
            <!--value_sets-->
            <!--value_sets=0.5-->
            <p>This value is more than 333 and less than 666</p>
            <!--value_sets-->
            <!--value_sets=1-->
            <p>This value is more than 666 and less than 1000</p>
            <!--value_sets-->
        </div>
    </td>
</tr>

As you can see we’ve created two conditions (value_sets, sub_values_sets) with three possible values for each of them. Let’s create file with conditions. And do not forget to attach it to template.

_conditions({
    value_sets: function (data) {
        if (data.column_3 <= 333                        ) { return '0';     }
        if (data.column_3 > 333 && data.column_3 <= 666 ) { return '0.5';   }
        if (data.column_3 > 666                         ) { return '1';     }
    },
    sub_value_sets: function (data) {
        if (data.column_3 <= 111                        ) { return '0';     }
        if (data.column_3 > 111 && data.column_3 <= 222 ) { return '0.5';   }
        if (data.column_3 > 222                         ) { return '1';     }
    },
});

So, now before render each row, flex.patterns will check two conditions and, according its values, build final HTML. You can see, how it looks by this link.

You can ask: why flex.patterns uses such way (via condition-function) instead popular method, when some syntax is used inside template. For example, like EJS-templates.

<ul>
    <% for(var i=0; i
    <supplies.length; i++) {%>
        <li><%= supplies[i] %></li>
        <% } %>
</ul>

We have several answers on such question.

  • Any not-standard syntax quite hard debug. At least you cannot just put somewhere break-point using standard tools of browser.
  • You cannot simply open your template as standalone page.
  • Templates with not-standard syntax much harder "move" from project to project, because it depend on template-engine.
  • And last reason – such templates cannot be updated in run-time.

Let’s stay on last point a bit longer. Flex-template can be updated in run-time mode. To see, that does it mean, take a look on next example. We will update our conditions for row.

var conditions = {
    value_sets: function (data) {
        if (data.column_3 <= 333                        ) { return '0';     }
        if (data.column_3 > 333 && data.column_3 <= 666 ) { return '0.5';   }
        if (data.column_3 > 666                         ) { return '1';     }
    },
    sub_value_sets: function (data) {
        if (data.column_3 <= 111                        ) { return '0';     }
        if (data.column_3 > 111 && data.column_3 <= 222 ) { return '0.5';   }
        if (data.column_3 > 222                         ) { return '1';     }
    },
};
conditions.value_sets.      tracking = ['column_3'];
conditions.sub_value_sets.  tracking = ['column_0'];
_conditions(conditions);

As can you see, we’ve added property tracking to each condition-functions. It means, that if value of model-reference column_3 will be changed, condition-function value_sets will be recalculated and whole template will be build (without losing any event-handles, binds and etc.). And if model-reference column_0, condition-function sub_value_sets will be recalculated too.

So, let’s modify callback of render our table to see results.

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,
        })
    },
    callbacks: {
        success: function (results) {
            (function (model) {
                var fun = function () {
                    var r = Math.round(19 * Math.random()),
                        c = Math.round(3 * Math.random());
                    model.__rows__[r]['column_' + c] = (Math.random() * 1000).toFixed(4);
                    setTimeout(fun, Math.ceil(50 * Math.random()));
                };
                fun();
            }(results.model));
        }
    }
}).render();

You can see results by this link. As you see, content of last column is changed each time, when value of model-reference is changed.

So, such way of definition of conditions allows you modify your template dynamically and do not loss such data like event-handles or binds, or something similar.

Notice, you can define condition-functions in JS-file with template or you can define it during render of template (in such case it will have higher priority).

_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,
        })
    },
    conditions : {
        value_sets: function (data) {
            if (data.column_3 <= 333                        ) { return '0';     }
            if (data.column_3 > 333 && data.column_3 <= 666 ) { return '0.5';   }
            if (data.column_3 > 666                         ) { return '1';     }
        },
        sub_value_sets: function (data) {
            if (data.column_3 <= 111                        ) { return '0';     }
            if (data.column_3 > 111 && data.column_3 <= 222 ) { return '0.5';   }
            if (data.column_3 > 222                         ) { return '1';     }
        }
    },
    callbacks: {
        success: function (results) {
        }
    }
}).render();

Hidden model’s reference

Sometimes we can have case, when we need reference to model, but without any link to some node in template. In such cases you can create hidden models, which can be used as triggers for conditions. Let’s see it within example and change textbox for password.

<p>{{::value}}</p>
<div data-type="TextInput.Wrapper">
<div data-type="TextInput.Container">
<input data-type="TextInput" type="{{type}}" value="{{::value}}" name="TestInput" {{$input}} />
        </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">{{not_valid_message}}</p>
    <!--showinfo-->

As you can see, we added addition paragraph, which should be shown, if user tries to use short login or password. Sure it isn’t real validation, but simple example.

In addition, we have to add new condition.

var conditions = {
    type        : function (data) {
        return data.type;
    },
    showinfo    : function (data) {
        return data.not_valid === true ? 'show' : 'null';
    }
};
conditions.showinfo.tracking = ['not_valid'];
_conditions(conditions);

Also we assigned condition showinfo with model-reference not_valid. But our template doesn’t have such model-reference ({{::not_valid}}). It isn’t a problem, everything what we need – define it during rendering.

var id = flex.unique();
        _patterns.get({
            url     : '/patterns/popup/pattern.html',
            node    : document.body,
            hooks   : {
                id      : id,
                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' }]
                        }),
                    },
                })
            },
            callbacks: {
                success: function (results) {
                    var instance    = this,
                        dom         = results.dom,
                        model       = results.model;
                    dom.listed.__content__.__controls__[0].button.on('click', function () {
                        var data = model.__content__;
                        if (data.__login__.value.length < 2) {
                            data.__login__.not_valid = true;
                            setTimeout(function () {
                                data.__login__.not_valid = false;
                            }, 2000);
                        }
                        if (data.__password__.value.length < 6) {
                            data.__password__.not_valid = true;
                            setTimeout(function () {
                                data.__password__.not_valid = false;
                            }, 2000);
                        }
                    });
                }
            },
        }).render();

Here you can try by yourself – click. Just try to click on login with empty fields.

Creating components

It’s cool for sure, use some ready HTML file as part of page, but it will be much better to render for example, authorization dialog (from previous example) like this:

_patterns.get({
    url     : '/components/login/component.html',
    node    : document.body,
    hooks   : {
        id  : id,
    },
}).render();

Or like this (within layout).

<pattern src="/components/login/component.html" style="display:none;">
    <id>0</id>
</pattern>

As you can see, we’ve postponed most part of hooks (leaved only ID of popup) and didn’t define any map of template (references to other templates). In scope of flex.patterns such things named – components.

To create component, we have to do two things:

  • Create template of component
  • Setup default values of hooks (this step isn’t obligatory, but if you don’t need define same hooks each time – it will be useful).

Let’s create component for our authorization popup. And code of component’s template will be next:

<pattern src="/patterns/popup/pattern.html">
    <content src="/patterns/login/pattern.html">
        <login src="/patterns/controls/textinput_hidden_model/pattern.html"></login>
        <password src="/patterns/controls/textinput_hidden_model/pattern.html"></password>
        <controls src="/patterns/buttons/flat/pattern.html"></controls>
    </content>
</pattern>

As you can see we used same algorithm as for rendering templates in layout (it was described here – goto. Only one difference – we didn’t define here any hooks – just map of template (it means, we defined references to sub-templates). Now, calling this component, you should not define sub-templates – just values of hooks:

_patterns.get({
    url     : '/components/login/component.html',
    node    : document.body,
    hooks   : {
        id      : id,
        title   : 'Test dialog window',
        content : {
            login       : {
                type                : 'text',
                not_valid_message   : 'Sorry, but your login should not be shorter than 2 symbols. Please, try again.',
                not_valid           : false
            },
            password    : {
                type                : 'password',
                not_valid_message   : 'Sorry, but your password should not be shorter than 6 symbols. Please, try again.',
                not_valid           : false
            },
            controls    : [
                { title: 'login',   id: 'login_button'  },
                { title: 'cancel',  id: 'cancel_button' }
            ],
        }
    },
}).render();

Default hooks

But, we don’t want each time define captions of buttons and error messages and etc. In most cases for us will be enough to define only ID of our popup. So, it’s really easy to do – we should define default hooks for our component.

Let’s create JS file in folder, where our component is:

_hooks({
    title   : 'Test dialog window',
    content : {
        login       : {
            type                : 'text',
            not_valid_message   : 'Sorry, but your login should not be shorter than 2 symbols. Please, try again.',
            not_valid           : false
        },
        password    : {
            type                : 'password',
            not_valid_message   : 'Sorry, but your password should not be shorter than 6 symbols. Please, try again.',
            not_valid           : false
        },
        controls    : [
            { title: 'login',   id: 'login_button'  },
            { title: 'cancel',  id: 'cancel_button' }
        ],
    }
});

Caller _hooks() allows you define default values of hooks for component (or template). And now you can call authorization popup maximally shortly.

_patterns.get({
    url     : '/components/login/component.html',
    node    : document.body,
    hooks   : {
        id  : id,
    },
}).render();

To overwritten default value of hook you should only define it during rendering, and that’s all.

Here is working example for rendering from JS – click and rendering from layout – click.

Also in these examples all logic was placed into controller. That’s why you can see error messages, if you try login with empty fields.

Collections

Let’s back to our example with table – click. It’s very nice, but in many cases in addition we have to have some way to add and to remove rows. Flex.patterns gives developer such possibility via collections. Let’s change a bit our table.

Add buttons: “remove” and “insert” into template of row (to see such buttons for each rows). As base, let’s take template of rows with conditions.

<tr>
    <td>{{::column_0}}</td>
    <td>{{::column_1}}</td>
    <td>{{::column_2}}</td>
    <td>
        <div>
            <p>{{::column_3}}</p>
            <!--value_sets=0-->
                <!--sub_value_sets=0-->
                <p>This value is less than 111</p>
                <!--sub_value_sets-->
                <!--sub_value_sets=0.5-->
                    <p>This value is more than 111 and less than 222</p>
                <!--sub_value_sets-->
                <!--sub_value_sets=1-->
                    <p>This value is more than 222 and less than 333</p>
                <!--sub_value_sets-->
            <!--value_sets-->
            <!--value_sets=0.5-->
                <p>This value is more than 333 and less than 666</p>
            <!--value_sets-->
            <!--value_sets=1-->
                <p>This value is more than 666 and less than 1000</p>
            <!--value_sets-->
        </div>
        <div data-style="Buttons">
            <a {{$remove}}>Remove</a>
            <a {{$add}}>Insert</a>
        </div>
    </td>
</tr>

Let’s add button “add new row” on page, where our table will be rendered.

<a data-style="Button" id="add_row">Add new row</a>

And now we can render our table.

_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   : [],
        })
    },
    callbacks: {
        success: function (collapsed, uncollapsed) {
            function add(event, indexes) {
                var index = indexes !== void 0 ? indexes[0] : rows.length - 1;
                rows.splice(index + 1, 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),
                });
                uncollapsed.dom.update();
                uncollapsed.dom.listed[0].__rows__[index + 1].add.on('click', add);
                uncollapsed.dom.listed[0].__rows__[index + 1].remove.on('click', remove);
            };
            function remove(event, indexes) {
                var index = indexes[0];
                rows.splice(index, 1);
            };
            var rows = uncollapsed.model[0].__rows__;
            _node('a[id="add_row"]').events().add('click', add);
        }
    }
}).render();

Here is working example of it – click. But let’s see closer on what happens inside callback. You can find there the second argument – uncollapsed. This object has same structure as the first argument (which we know already by previous chapters and examples). There are only one different – all sub levels are lists (array). That’s why we have to use uncollapsed.model[0].__rows__ to get reference to rows.

If you try add some new item to list of rows (__rows__ flex.patterns automatically will render new row in our table according defined hooks. Same situation with removing existing items in list.

Here is important question – how we can figure out position of some row (index). It’s very easy – use second argument indexes (this is array[number] with all indexes (including sublevels). First index (indexes[0] will be root index, second – sublevel index (indexes[1] and etc. according number of sublevels.

And last important moment here. To get access to dom functionality of created row you have to update dom map uncollapsed.dom.update() Why? This operation is quite heavy and in case if you add several rows at one time – it isn’t good idea updated map each time, much betted do it once after all data will be added. That’s why dom object should be updated manually.

Here is an example with sublevels – click.

Settings and cache

All your templates will be cached (within localStorage) include all resource like CSS and JS files. To prevent such behavior, you can turn on debug mode (all resource will be taken from server).

_patterns.debug();

Or you can change settings of flex.patterns.

_patterns.setup({
    USE_STORAGE_CSS : false,
    USE_STORAGE_JS  : false,
    USE_STORAGE_HTML: false
});

Here is a full list of available settings.

Setting Default Description
USE_STORAGE_CSS true Cache or not CSS files
USE_STORAGE_JS true Cache or not JS files
USE_STORAGE_HTML true Cache or not HTML files
PATTERN_NODE PATTERN Name of tag, which is used to define template in HTML of page.

Preloading

In some cases, developer need preload templates before render. In this case time for request will be used during preload procedure, but render will be done quickly.

_patterns.preload(
    //List of template
    [
        '/patterns/popup/pattern.html',
        '/patterns/login/pattern.html',
        '/patterns/controls/textinput/pattern.html',
        '/patterns/buttons/flat/pattern.html'
    ],
    //Success handle
    function(){
    },
    //Fail handle
    function(){
    }
);

Cloning

After template was rendered, you have possibility clone it – create addition instance of template. Let’s update callback for authorization window and add procedure of creating clone by click on button.

    url     : '/patterns/popup/pattern.html',
    node    : document.body,
    hooks   : {
        id      : id,
        title   : 'Test dialog window',
        content : _patterns.get({
            url     : '/patterns/login/pattern.html',
            hooks   : {
                login   : _patterns.get({
                    url     : '/patterns/controls/textinput/pattern.html',
                    hooks   : {
                        type: 'text',
                    }
                }),
                password: _patterns.get({
                    url     : '/patterns/controls/textinput/pattern.html',
                    hooks   : {
                        type: 'password',
                    }
                }),
                controls: _patterns.get({
                    url     : '/patterns/buttons/flat/pattern.html',
                    hooks   : [{ title: 'login', id: 'login_button' }, { title: 'cancel', id: 'cancel_button' }]
                }),
            },
        })
    },
    callbacks: {
        success: function (results) {
            var instance    = this,
                dom         = results.dom,
                model       = results.model;
            //Attach event to buttons
            dom.listed.__content__.__controls__[0].button.on('click', function () {
                alert('You cannot login. It\'s just test. Login is "' + model.__content__.__login__.value + '", and password is "' + model.__content__.__password__.value + '"');
            });
            dom.listed.__content__.__controls__[1].button.on('click', function () {
                var id      = flex.unique(),
                    clone   = instance.clone({
                        id      : id,
                        title   : 'Clonned dialog window',
                        content : {
                            login   : {
                                type: 'text',
                            },
                            password: {
                                type: 'password',
                            },
                            controls: [{ title: 'login', id: 'login_button' }, { title: 'cancel', id: 'cancel_button' }],
                        }
                });
                clone.append(document.body);
                flex.libraries.ui.window.focus.     init();
                flex.libraries.ui.window.move.      init();
                flex.libraries.ui.window.maximize.  init();
                flex.libraries.ui.window.resize.    init();
            });
        }
    },
}).render();

Pay your attention to method instance.clone. Because template was rendered before and is ready for usage, we define only values of hooks, without data about urls, conditions and etc.

Method clone will return instance of class NodeList (which has minimal functionality). This class was described here - goto.

You can try this example by this link. Just click on "cancel" button to create clone of template.

From box

In addition to basic functionality (as template engine) flex.patterns gives you several very useful and powerful features.

Dialogs / windows UI

Flex.templates has inbuilt controllers for:

  • Drag windows (nodes);
  • Change size of windows;
  • Maximize / restore size of windows;
  • Control focus of window.

Let’s see again on template of our popup.

<div data-style="Popup" data-flex-ui-window-move-container="{{id}}" data-flex-ui-window-resize-position-parent="{{id}}" data-flex-ui-window-focus="{{id}}">
    <div data-style="Popup.Container" data-flex-ui-window-resize-container="{{id}}" data-flex-ui-window-maximize="{{id}}">
        <div data-style="Popup.Title" data-flex-ui-window-move-hook="{{id}}">
            <p data-style="Popup.Title">{{title}}</p>
            <div data-style="Popup.Title.Switcher" data-state="max" data-flex-window-maximize-hook="{{id}}"></div>
        </div>
        <div data-style="Popup.Content">{{content}}</div>
        <div data-style="Popup.Bottom">
            <p data-style="Popup.Bottom" id="test_bottom_id">{{bottom}}{{::bottom}}</p>
            <div data-style="Window.Resize.Coner"></div>
        </div>
    </div>
</div>

You see there several not-standard attributes. Within such attributes you can setup necessary UI.

Attribute Description
data-flex-ui-window-move-container Node, which will be dragging
data-flex-ui-window-move-hook Node, which is used as hook for dragging. By click on this node user can drag window
data-flex-ui-window-resize-container Node, which will be resizing
data-flex-ui-window-resize-position-parent Node, which is needed for correction of position of resizing node. In most cases this attribute can be postponed, but if you are using quite hard nesting, it can be useful to define normal behavior during resize.
data-flex-ui-window-focus Node, which should be placed ahead all other nodes if it’s focused. By this attribute, we are defining node, whose property z-index will be changed
data-flex-ui-window-maximize Node, which will be maximized / restored
data-flex-window-maximize-hook Node, which is used as button to maximize / restore window

During rendering of template these UIs will be applied automatically. But you can do it manually (for example for cases with cloning of templates) by calling init-functions.

//Focus controller
flex.libraries.ui.window.focus.     init();
//Dragging controller
flex.libraries.ui.window.move.      init();
//Maximize / restore controller
flex.libraries.ui.window.maximize.  init();
//Resize controller
flex.libraries.ui.window.resize.    init();

Accessors

Flex.patterns is built based on flex. It gives developer several short accessors.

  • _node(string || node, useCache [default: false], context [default: document])
  • _nodes(string || array[node] , useCache [default: false], context [default: document])
  • _object(object)
  • _array(object)
  • _string(string)

As you’ve guessed, such wrappers open access to some functionality. Let’s discover default functionality (from box).

All methods for _node are actual and for _nodes.

Caller Returing Description
_node and _nodes wrappers
Html module. For work with DOM
_node(sel).html().size().get() { height: number, width: number } Collection of methods, which calculate size of node by different ways. Method get() is universal. Other methods can be useful in specific situations.
_node(sel).html().size().getWithMargin()
_node(sel).html().size().getByClientRectSize()
_node(sel).html().size().getByOffset()
_node(sel).html().position().byPage() { top: number, left: number } These methods calculate position of node on the page.
_node(sel).html().position().byWindow()
_node(sel).html().styles().apply(styles) void Applies styles to defined node
_node(sel).html().styles().redraw() void Force redrawing of node on the page
_node(sel).html().styles().addClass(className) void Add CSS class to node
_node(sel).html().styles().removeClass(className) void Remove CSS class from node
_node(sel).html().find().childByAttr(node_name, attribute) node Find child of defined node with defined attribute. Parameters attribute is an object: { name: string, value : any}
_node(sel).html().find().childByType(node_name) node Find child of defined node with defined type.
_node(sel).html().find().parentByAttr(attribute) node Find parent of defined node with defined attribute. Parameters attribute is an object: { name: string, value : any}
_node(sel).html().scroll().position() { top: number, left: number, height: number, width: number } Calculate parameters of scroll for defined node
Binds module. Controls bindings functionality
_node(sel).bindingAttrs().bind(attr, handle) id Binds attribute with handle
_node(sel).bindingAttrs().unbind(attr, id) void Remove handle for defined attribute by ID
_node(sel).bindingAttrs().kill(attr) void Remove all handle for defined attribute
_node(sel).bindingProps().bind(prop, handle) id Binds property with handle
_node(sel).bindingProps().unbind(prop, id) void Remove handle for defined property by ID
_node(sel).bindingProps().kill(prop) void Remove all handle for defined property
Events module. Extended controller of events

_node(sel).events().add(type, handle)

_node(sel).events().add(type, handle, id)

_node(sel).events().add(type, handle, id, touch)

id Attach handle to defined type of event. Parameters type and handle are obligatory. Parameters id and touch can be postponed. If touch = true will be attached analogue events for touch-devices.

_node(sel).events().remove(type, handle)

_node(sel).events().remove(type, null, id)

void Remove handle by id or by handle.
_node(sel).events().call(type) void Fires defined event for defined node
_object wrapper
_object(obj).forEach(function(key, value){ }) void Applies callback to each property of defined object

_object(obj).extend()

_object(obj).extend(target)

_object(obj).extend(target, exclusion)

object Copies all properties of defined object to target object (if it’s defined). If in array exclusion is defined some names of properties, such properties will be postponed

_object(obj).copy()

_object(obj).copy(target)

object Makes copy of defined object. If target-object is defined, will place copies of properties into it.

_object(obj).isValueIn(value)

_object(obj).isValueIn(value, nesting)

bool Try to find value inside object. If nesting = true – will make search include nested objects.
_object(obj).getByPath(path) value of found object Try find value of property by string path, like: "prop_0.prop_1.prop_2"
_object(obj).createInstanceClass() Instance of class Create instance of class.
_object(obj).binding().bind(property, handle) id Binds property with handle
_object(obj).binding().unbind(property, id) void Remove handle for defined property by ID
_object(obj).binding().kill(property) void Remove all handle for defined property

Okay you have some functionality from box, but the most important thing is – you can add your own functionality via next interface.

//For single node
flex.callers.define.node   (parameters);
//For several nodes
flex.callers.define.nodes  (parameters);
//For single array  
flex.callers.define.array  (parameters);
//For single string 
flex.callers.define.string (parameters);
//For single object 
flex.callers.define.object (parameters);

For all callers syntax is same.

flex.callers.define.nodes(
   //Define namespace of chain
   //You can use any number of parts as you want, for example: 
   // > nodes.attr
   // > nodes.attrs.set
   // > nodes.attrs.get and etc.
   'attr',
   //Define handle
   function (name, value) {
       //Object [this] will have only one property - [target].
       //[this.target] can be some value (or object) if it's single call (node, string, object and etc) or it will be array for: nodes
       Array.prototype.forEach.call(this.target, function (target) {
           result.push(methods.attr(target, name, value));
       });
       return results;
   }
);
//All parts of chain will be a functions, so to call method of chain you should use next:
_node('selector').attrs(arg);
_node('selector').nodes().attrs(arg);
_node('selector').nodes().attrs().get();
_node('selector').nodes().attrs().set(arg);

As you can see you can easily extend functionality of your application and add necessary addition methods for nodes, for objects or for any other type.

GitHub

You can discover a code and take a last version of flex.patterns on GitHub. Also you can contact with author of flex.

License

The MIT License (MIT) Copyright (c) 2016 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.