muu

DEPRECATED lightweight JS framework
git clone https://git.ce9e.org/muu.git

commit
5de21ab882f0789d7d0914b215da4896f112c486
parent
e783e09691135b41033ce70d377c2fabc2364afd
Author
Tobias Bengfort <tobias.bengfort@gmx.net>
Date
2015-08-28 10:55
build

Diffstat

C dist/muu.js -> dist/muu-core.js 1627 +++++++++++++++++++------------------------------------------
A dist/muu-core.min.js 10 ++++++++++
M dist/muu.js 2102 +++++++++++++++++++++++++++++++------------------------------
M dist/muu.min.js 36 ++++++++++++++++++------------------

4 files changed, 1592 insertions, 2183 deletions


diff --git a/dist/muu.js b/dist/muu-core.js

@@ -1,13 +1,19 @@
    1     1 (function(window, document, undefined) {
   -1     2     var name = 'muu';
   -1     3 
    2     4     (function(factory) {
    3     5         if (typeof define === 'function' && define.amd) {
    4    -1             define('muu', [], factory);
   -1     6             define(name, ['lodash'], factory);
    5     7         } else {
    6    -1             window['muu'] = factory();
   -1     8             window[name] = factory(window._);
    7     9         }
    8    -1     })(function() {
   -1    10     })(function(lodash) {
    9    11         var modules = {};
   10    12 
   -1    13         modules['muu-js-helpers'] = {
   -1    14             instance: lodash
   -1    15         };
   -1    16 
   11    17         var map = function(a, fn) {
   12    18             var b = [];
   13    19             for (var i = 0; i < a.length; i++) {
@@ -38,1207 +44,594 @@
   38    44             return modules[name].instance;
   39    45         };
   40    46 
   41    -1     /**
   42    -1      * Exports the {@link Directive} class.
   43    -1      * @module muu-directive
   44    -1      * @ignore
   45    -1      */
   46    -1     _define('muu-directive', ['muu-dom-helpers', 'muu-js-helpers', 'muu-update-dom'], function($, _, updateDOM) {
   47    -1         "use strict";
   48    -1 
   49    47         /**
   50    -1          * A directive is linked to a Element and manages the DOM tree below
   51    -1          * that element (excluding any isolated subtrees, e.g. those managed by
   52    -1          * subdirectives).
   53    -1          *
   54    -1          * It provides a set of methods to interact with the managed part of the
   55    -1          * DOM. This is separated into three distinct parts:
   56    -1          *
   57    -1          * - You can push data to the DOM using the {@link Directive#update}
   58    -1          *   method. The DOM will than be updated using the template that was
   59    -1          *   provided at construction.
   60    -1          * - You can get data from the DOM using the {@link Directive#getModel}
   61    -1          *   method. This is however restricted to form field by design.
   62    -1          * - You can react to DOM events by specifying an alias for them. In the
   63    -1          *   template, you might for example add the attribute
   64    -1          *   `data-onclick="custom"` to an element. When there is `click` event on
   65    -1          *   that element, a `muu-custom` event will be triggered on the
   66    -1          *   directive's root element.
   67    -1          *
   68    -1          * Directives are typically not created directly but via {@link
   69    -1          * Registry#link}.
   70    -1          *
   71    -1          * @constructs Directive
   72    -1          * @param {Element} root
   73    -1          * @param {string} template
   74    -1          * @param {Registry} registry
   -1    48          * Exports the {@link Directive} class.
   -1    49          * @module muu-directive
   -1    50          * @ignore
   75    51          */
   76    -1         var Directive = function(root, template, registry) {
   77    -1             var self = this;
   78    -1 
   79    -1             root.innerHTML = '';
   80    -1 
   81    -1             var eventCallback = function(originalEvent) {
   82    -1                 var attrName = 'data-on' + originalEvent.type;
   83    -1                 if (originalEvent.target.hasAttribute(attrName)) {
   84    -1                     var eventName = originalEvent.target.getAttribute(attrName);
   85    -1                     var event = $.createEvent('muu-' + eventName, originalEvent);
   86    -1                     root.dispatchEvent(event);
   87    -1                 }
   88    -1             };
   -1    52         _define('muu-directive', ['muu-dom-helpers', 'muu-js-helpers', 'muu-update-dom'], function($, _, updateDOM) {
   -1    53             "use strict";
   89    54 
   90    55             /**
   91    -1              * Rerender `template` with `data` and push the changes to the DOM.
   -1    56              * A directive is linked to a Element and manages the DOM tree below
   -1    57              * that element (excluding any isolated subtrees, e.g. those managed by
   -1    58              * subdirectives).
   92    59              *
   93    -1              * @param {Object.<string, *>} data
   94    -1              * @see {@link module:muu-update-dom} for details.
   95    -1              * @see The templating system can be defined in the {@link Registry}.
   96    -1              */
   97    -1             this.update = function(data) {
   98    -1                 var tmp = document.createElement('div');
   99    -1                 tmp.innerHTML = registry.renderer(template, data);
  100    -1 
  101    -1                 updateDOM(root, tmp);
  102    -1 
  103    -1                 _.forEach(['keydown', 'keyup', 'click', 'change', 'search'], function(eventType) {
  104    -1                     var selector = '[data-on' + eventType + ']';
  105    -1                     _.forEach(self.querySelectorAll(selector), function(element) {
  106    -1                         element.addEventListener(eventType, eventCallback, false);
  107    -1                     });
  108    -1                 });
  109    -1 
  110    -1                 var updateEvent = $.createEvent('muu-parent-update');
  111    -1                 var subDirectives = this.querySelectorAll('muu.muu-initialised');
  112    -1                 _.forEach(subDirectives, function(element) {
  113    -1                     element.dispatchEvent(updateEvent);
  114    -1                 });
  115    -1 
  116    -1                 registry.linkAll(self);
  117    -1             };
  118    -1 
  119    -1             /**
  120    -1              * A variant of `querySelectorAll` that returns only elements from
  121    -1              * the managed part of the DOM.
   -1    60              * It provides a set of methods to interact with the managed part of the
   -1    61              * DOM. This is separated into three distinct parts:
  122    62              *
  123    -1              * @private
  124    -1              * @param {string} selector
  125    -1              * @return {Array.<Element>} All child elements that match the given
  126    -1              *     selector and are not isolated.
  127    -1              */
  128    -1             this.querySelectorAll = function(selector) {
  129    -1                 var hits = root.querySelectorAll(selector);
  130    -1 
  131    -1                 // NOTE: querySelectorAll returns all elements in the tree that
  132    -1                 // match the given selector.  findAll does the same with *relative
  133    -1                 // selectors* but does not seem to be available yet.
  134    -1                 var isolations = root.querySelectorAll('.muu-isolate');
  135    -1                 var isolated = _.union(_.map(isolations, function(isolation) {
  136    -1                     return isolation.querySelectorAll(selector);
  137    -1                 }));
  138    -1 
  139    -1                 return _.difference(hits, isolated);
  140    -1             };
  141    -1 
  142    -1             /**
  143    -1              * A variant of `querySelector` that returns only elements from the
  144    -1              * managed part of the DOM.
   -1    63              * - You can push data to the DOM using the {@link Directive#update}
   -1    64              *   method. The DOM will than be updated using the template that was
   -1    65              *   provided at construction.
   -1    66              * - You can get data from the DOM using the {@link Directive#getModel}
   -1    67              *   method. This is however restricted to form field by design.
   -1    68              * - You can react to DOM events by specifying an alias for them. In the
   -1    69              *   template, you might for example add the attribute
   -1    70              *   `data-onclick="custom"` to an element. When there is `click` event on
   -1    71              *   that element, a `muu-custom` event will be triggered on the
   -1    72              *   directive's root element.
  145    73              *
  146    -1              * @private
  147    -1              * @param {String} selector
  148    -1              * @return {Element} First child element that matches the given
  149    -1              *     selector and is not isolated.
  150    -1              * @suppress {missingReturn}
  151    -1              */
  152    -1             this.querySelector = function(selector) {
  153    -1                 var all = self.querySelectorAll(selector);
  154    -1                 if (all.length > 0) {
  155    -1                     return all[0];
  156    -1                 }
  157    -1             };
  158    -1 
  159    -1             /**
  160    -1              * Get all model data as a flat object.
   -1    74              * Directives are typically not created directly but via {@link
   -1    75              * Registry#link}.
  161    76              *
  162    -1              * @return {Object.<string, string|number|boolean>}
  163    -1              *//**
  164    -1              * Get the value of a form input by name.
  165    -1              *
  166    -1              * In case of a checkbox, returns `boolean`.
  167    -1              * In case of radioboxes, returns the value of the selected box.
  168    -1              *
  169    -1              * @param {string} name
  170    -1              * @param {*} [_default]
  171    -1              * @return {string|number|boolean|*}
   -1    77              * @constructs Directive
   -1    78              * @param {Element} root
   -1    79              * @param {string} template
   -1    80              * @param {Registry} registry
  172    81              */
  173    -1             this.getModel = function(name, _default) {
  174    -1                 if (name === undefined) {
  175    -1                     var model = {};
  176    -1                     _.forEach(self.querySelectorAll('[name]'), function(element) {
  177    -1                         model[element.name] = self.getModel(element.name);
  178    -1                     });
  179    -1                     return model;
  180    -1                 } else {
  181    -1                     var element = self.querySelector('[name=' + name + ']');
  182    -1                     if (element === undefined) {
  183    -1                         return _default;
  184    -1                     } else if (element.type === 'checkbox') {
  185    -1                         return element.checked;
  186    -1                     } else if (element.type === 'radio') {
  187    -1                         var options = self.querySelectorAll('[name=' + name + ']');
  188    -1                         return $.getRadio(options) || _default;
  189    -1                     } else {
  190    -1                         return element.value;
  191    -1                     }
  192    -1                 }
  193    -1             };
   -1    82             var Directive = function(root, template, registry) {
   -1    83                 var self = this;
  194    84 
  195    -1             /**
  196    -1              * Set the value of a form input by name.
  197    -1              *
  198    -1              * In case of a checkbox, sets `element.checked`.
  199    -1              * In case of radioboxes, selects the box with matching value.
  200    -1              *
  201    -1              * @param {string} name
  202    -1              * @param {string|number|boolean} value
  203    -1              */
  204    -1             this.setModel = function(name, value) {
  205    -1                 var element = self.querySelector('[name=' + name + ']');
  206    -1                 if (element.type === 'checkbox') {
  207    -1                     element.checked = value;
  208    -1                 } else if (element.type === 'radio') {
  209    -1                     var options = self.querySelectorAll('[name=' + name + ']');
  210    -1                     $.setRadio(options, value);
  211    -1                 } else {
  212    -1                     element.value = value;
  213    -1                 }
  214    -1             };
  215    -1         };
  216    -1 
  217    -1         return Directive;
  218    -1     });
  219    -1     /**
  220    -1      * DOM related helper functions
  221    -1      * @module muu-dom-helpers
  222    -1      */
  223    -1     _define("muu-dom-helpers", ['muu-js-helpers'], function(_) {
  224    -1         "use strict";
  225    -1 
  226    -1         var entityMap = {
  227    -1             '&': '&amp;',
  228    -1             '<': '&lt;',
  229    -1             '>': '&gt;',
  230    -1             '"': '&quot;',
  231    -1             "'": '&#39;',
  232    -1             '/': '&#x2F;'
  233    -1         };
  234    -1 
  235    -1         /** @lends module:muu-dom-helpers */
  236    -1         var $ = {};
   -1    85                 root.innerHTML = '';
  237    86 
  238    -1         $.DELAY = 1000;
  239    -1 
  240    -1         /**
  241    -1          * @param {string} string
  242    -1          * @return {string} - escaped HTML
  243    -1          */
  244    -1         $.escapeHtml = function(string) {
  245    -1             return String(string).replace(/[&<>"'\/]/g, function(s) {
  246    -1                 return entityMap[s];
  247    -1             });
  248    -1         };
   -1    87                 var eventCallback = function(originalEvent) {
   -1    88                     var attrName = 'data-on' + originalEvent.type;
   -1    89                     if (originalEvent.target.hasAttribute(attrName)) {
   -1    90                         var eventName = originalEvent.target.getAttribute(attrName);
   -1    91                         var event = $.createEvent('muu-' + eventName, originalEvent);
   -1    92                         root.dispatchEvent(event);
   -1    93                     }
   -1    94                 };
  249    95 
  250    -1         /**
  251    -1          * Cross browser custom events.
  252    -1          *
  253    -1          * *Note*: IE does not seem to like it when you use existing event names
  254    -1          * with this.
  255    -1          *
  256    -1          * @param {string} type
  257    -1          * @param {*} detail
  258    -1          * @return {Event}
  259    -1          * @see https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Creating_and_triggering_events
  260    -1          */
  261    -1         $.createEvent = function(type, detail) {
  262    -1             if (typeof CustomEvent === 'function') {
  263    -1                 return new CustomEvent(type, {
  264    -1                     detail: detail
  265    -1                 });
  266    -1             } else {
  267    -1                 var event = document.createEvent('CustomEvent');
  268    -1                 event.initCustomEvent(type, false, true, detail);
  269    -1                 return event;
  270    -1             }
  271    -1         };
   -1    96                 /**
   -1    97                  * Rerender `template` with `data` and push the changes to the DOM.
   -1    98                  *
   -1    99                  * @param {Object.<string, *>} data
   -1   100                  * @see {@link module:muu-update-dom} for details.
   -1   101                  * @see The templating system can be defined in the {@link Registry}.
   -1   102                  */
   -1   103                 this.update = function(data) {
   -1   104                     var tmp = document.createElement('div');
   -1   105                     tmp.innerHTML = registry.renderer(template, data);
   -1   106 
   -1   107                     updateDOM(root, tmp);
   -1   108 
   -1   109                     _.forEach(['keydown', 'keyup', 'click', 'change', 'search'], function(eventType) {
   -1   110                         var selector = '[data-on' + eventType + ']';
   -1   111                         _.forEach(self.querySelectorAll(selector), function(element) {
   -1   112                             element.addEventListener(eventType, eventCallback, false);
   -1   113                         });
   -1   114                     });
  272   115 
  273    -1         /**
  274    -1          * @param {EventTarget} element
  275    -1          * @param {string} eventName
  276    -1          * @param {Function} callback
  277    -1          * @return {function()} An unregister function
  278    -1          */
  279    -1         $.on = function(element, eventName, callback) {
  280    -1             element.addEventListener(eventName, callback, false);
  281    -1             return function() {
  282    -1                 element.removeEventListener(eventName, callback, false);
  283    -1             };
  284    -1         };
   -1   116                     var updateEvent = $.createEvent('muu-parent-update');
   -1   117                     var subDirectives = this.querySelectorAll('muu.muu-initialised');
   -1   118                     _.forEach(subDirectives, function(element) {
   -1   119                         element.dispatchEvent(updateEvent);
   -1   120                     });
  285   121 
  286    -1         /**
  287    -1          * @param {Function} fn
  288    -1          * @return {function()} An unregister function
  289    -1          */
  290    -1         $.ready = function(fn) {
  291    -1             var _fn = _.once(fn);
  292    -1             if (document.readyState === 'complete') {
  293    -1                 _fn();
  294    -1                 return function() {};
  295    -1             } else {
  296    -1                 var u1 = $.on(document, 'DOMContentLoaded', _fn);
  297    -1                 var u2 = $.on(window, 'load', _fn);
  298    -1                 return function() {
  299    -1                     u1();
  300    -1                     u2();
   -1   122                     registry.linkAll(self);
  301   123                 };
  302    -1             }
  303    -1         };
  304   124 
  305    -1         /**
  306    -1          * @param {Node} desc
  307    -1          * @param {Node} root
  308    -1          * @return {boolean}
  309    -1          */
  310    -1         $.isDescendant = function(desc, root) {
  311    -1              return !!desc && (desc === root || $.isDescendant(desc.parentNode, root));
  312    -1         };
   -1   125                 /**
   -1   126                  * A variant of `querySelectorAll` that returns only elements from
   -1   127                  * the managed part of the DOM.
   -1   128                  *
   -1   129                  * @private
   -1   130                  * @param {string} selector
   -1   131                  * @return {Array.<Element>} All child elements that match the given
   -1   132                  *     selector and are not isolated.
   -1   133                  */
   -1   134                 this.querySelectorAll = function(selector) {
   -1   135                     var hits = root.querySelectorAll(selector);
   -1   136 
   -1   137                     // NOTE: querySelectorAll returns all elements in the tree that
   -1   138                     // match the given selector.  findAll does the same with *relative
   -1   139                     // selectors* but does not seem to be available yet.
   -1   140                     var isolations = root.querySelectorAll('.muu-isolate');
   -1   141                     var isolated = _.union(_.map(isolations, function(isolation) {
   -1   142                         return isolation.querySelectorAll(selector);
   -1   143                     }));
   -1   144 
   -1   145                     return _.difference(hits, isolated);
   -1   146                 };
  313   147 
  314    -1         /**
  315    -1          * Execute a function when `element` is removed from the DOM.
  316    -1          *
  317    -1          * *Note*: The callback is not executed directly when (or even before) the
  318    -1          * element is removed but with a slight delay. So the only way to test this
  319    -1          * is to use a timeout in the test.
  320    -1          *
  321    -1          * @param {Element} element
  322    -1          * @param {Function} fn
  323    -1          * @return {function()} An unregister function
  324    -1          */
  325    -1         $.destroy = function(element, fn) {
  326    -1             var unregister;
  327    -1 
  328    -1             if (!!window.MutationObserver) {
  329    -1                 var observer = new MutationObserver(function() {
  330    -1                     if (!$.isDescendant(element, document)) {
  331    -1                         fn();
  332    -1                         unregister();
   -1   148                 /**
   -1   149                  * A variant of `querySelector` that returns only elements from the
   -1   150                  * managed part of the DOM.
   -1   151                  *
   -1   152                  * @private
   -1   153                  * @param {String} selector
   -1   154                  * @return {Element} First child element that matches the given
   -1   155                  *     selector and is not isolated.
   -1   156                  * @suppress {missingReturn}
   -1   157                  */
   -1   158                 this.querySelector = function(selector) {
   -1   159                     var all = self.querySelectorAll(selector);
   -1   160                     if (all.length > 0) {
   -1   161                         return all[0];
  333   162                     }
  334    -1                 });
  335    -1 
  336    -1                 observer.observe(document, {
  337    -1                      childList: true,
  338    -1                      subtree: true
  339    -1                 });
   -1   163                 };
  340   164 
  341    -1                 unregister = _.once(function() {
  342    -1                     observer.disconnect();
  343    -1                     observer = undefined;
  344    -1                 });
  345    -1             } else {
  346    -1                 var intervalID = setInterval(function() {
  347    -1                     if (!$.isDescendant(element, document)) {
  348    -1                         fn();
  349    -1                         unregister();
   -1   165                 /**
   -1   166                  * Get all model data as a flat object.
   -1   167                  *
   -1   168                  * @return {Object.<string, string|number|boolean>}
   -1   169                  *//**
   -1   170                  * Get the value of a form input by name.
   -1   171                  *
   -1   172                  * In case of a checkbox, returns `boolean`.
   -1   173                  * In case of radioboxes, returns the value of the selected box.
   -1   174                  *
   -1   175                  * @param {string} name
   -1   176                  * @param {*} [_default]
   -1   177                  * @return {string|number|boolean|*}
   -1   178                  */
   -1   179                 this.getModel = function(name, _default) {
   -1   180                     if (name === undefined) {
   -1   181                         var model = {};
   -1   182                         _.forEach(self.querySelectorAll('[name]'), function(element) {
   -1   183                             model[element.name] = self.getModel(element.name);
   -1   184                         });
   -1   185                         return model;
   -1   186                     } else {
   -1   187                         var element = self.querySelector('[name=' + name + ']');
   -1   188                         if (element === undefined) {
   -1   189                             return _default;
   -1   190                         } else if (element.type === 'checkbox') {
   -1   191                             return element.checked;
   -1   192                         } else if (element.type === 'radio') {
   -1   193                             var options = self.querySelectorAll('[name=' + name + ']');
   -1   194                             return $.getRadio(options) || _default;
   -1   195                         } else {
   -1   196                             return element.value;
   -1   197                         }
  350   198                     }
  351    -1                 }, $.DELAY);
  352    -1 
  353    -1                 unregister = function() {
  354    -1                     clearInterval(intervalID);
  355   199                 };
  356    -1             }
  357   200 
  358    -1             return unregister;
  359    -1         };
  360    -1 
  361    -1         /**
  362    -1          * @param {Array.<Element>} options
  363    -1          * @return {string}
  364    -1          * @suppress {missingReturn}
  365    -1          */
  366    -1         $.getRadio = function(options) {
  367    -1             for (var i = 0; i < options.length; i++) {
  368    -1                 if (options[i].checked) {
  369    -1                     return options[i].value;
  370    -1                 }
  371    -1             }
  372    -1         };
  373    -1 
  374    -1         /**
  375    -1          * @param {Array.<Element>} options
  376    -1          * @param {string} value
  377    -1          */
  378    -1         $.setRadio = function(options, value) {
  379    -1             for (var i = 0; i < options.length; i++) {
  380    -1                 if (options[i].value === value) {
  381    -1                     options[i].checked = true;
  382    -1                 } else {
  383    -1                     options[i].checked = false;
  384    -1                 }
  385    -1             }
  386    -1         };
  387    -1 
  388    -1         return $;
  389    -1     });
  390    -1     /**
  391    -1      * Minimal implementation of an underscore/lodash subset.
  392    -1      * @module muu-js-helpers
  393    -1      */
  394    -1     _define('muu-js-helpers', [], function() {
  395    -1         "use strict";
  396    -1 
  397    -1         /** @lends module:muu-js-helpers */
  398    -1         var _ = {};
  399    -1 
  400    -1         /**
  401    -1          * @param {Object} value
  402    -1          * @return {string}
  403    -1          */
  404    -1         var objToString = function(value) {
  405    -1             return Object.prototype.toString.call(value);
  406    -1         };
  407    -1 
  408    -1         /**
  409    -1          * @param {*} value
  410    -1          * @return {boolean}
  411    -1          */
  412    -1         _.isString = function(value) {
  413    -1             return typeof value === 'string' || objToString(value) === '[object String]';
  414    -1         };
  415    -1 
  416    -1         /**
  417    -1          * @function
  418    -1          * @param {*} value
  419    -1          * @return {boolean}
  420    -1          */
  421    -1         _.isArray = Array.isArray;
  422    -1 
  423    -1         /**
  424    -1          * @param {*} value
  425    -1          * @return {boolean}
  426    -1          */
  427    -1         _.isFunction = function(value) {
  428    -1             return typeof value === 'function';
  429    -1         };
  430    -1 
  431    -1         /**
  432    -1          * @param {Function} fn
  433    -1          * @return {Function}
  434    -1          */
  435    -1         _.once = function(fn) {
  436    -1             var result;
  437    -1             var called = false;
  438    -1 
  439    -1             return function() {
  440    -1                 if (!called) {
  441    -1                     result = fn.apply(this, arguments);
  442    -1                     called = true;
  443    -1                 }
  444    -1                 return result;
  445    -1             };
  446    -1         };
  447    -1 
  448    -1         /**
  449    -1          * @param {Array} array
  450    -1          * @param {*} value
  451    -1          * @return {number}
  452    -1          */
  453    -1         _.indexOf = function(array, value) {
  454    -1             if ('indexOf' in array) {
  455    -1                 return array.indexOf(value);
  456    -1             }
  457    -1 
  458    -1             for (var i = 0; i < array.length; i++) {
  459    -1                 if (array[i] === value) {
  460    -1                     return i;
  461    -1                 }
  462    -1             }
  463    -1             return -1;
  464    -1         };
  465    -1 
  466    -1         /**
  467    -1          * @param {Array} array
  468    -1          * @param {Function} fn
  469    -1          */
  470    -1         _.forEach = function(array, fn) {
  471    -1             if ('forEach' in array) {
  472    -1                 return array.forEach(fn);
  473    -1             }
  474    -1 
  475    -1             for (var i = 0; i < array.length; i++) {
  476    -1                 fn(array[i]);
  477    -1             }
  478    -1         };
  479    -1 
  480    -1         /**
  481    -1          * @param {Array} array
  482    -1          * @param {Function} fn
  483    -1          * @return {Array}
  484    -1          */
  485    -1         _.map = function(array, fn) {
  486    -1             if ('map' in array) {
  487    -1                 return array.map(fn);
  488    -1             }
  489    -1 
  490    -1             var results = [];
  491    -1             for (var i = 0; i < array.length; i++) {
  492    -1                 results.push(fn(array[i]));
  493    -1             }
  494    -1             return results;
  495    -1         };
  496    -1 
  497    -1         /**
  498    -1          * @param {Array} array
  499    -1          * @param {Function} fn
  500    -1          * @return {Array}
  501    -1          */
  502    -1         _.filter = function(array, fn) {
  503    -1             if ('filter' in array) {
  504    -1                 return array.filter(fn);
  505    -1             }
  506    -1 
  507    -1             var results = [];
  508    -1             for (var i = 0; i < array.length; i++) {
  509    -1                 if (fn(array[i])) {
  510    -1                     results.push(array[i]);
  511    -1                 }
  512    -1             }
  513    -1             return results;
  514    -1         };
  515    -1 
  516    -1         /**
  517    -1          * @param {Array.<Array>} arrays
  518    -1          * @return {Array}
  519    -1          */
  520    -1         _.union = function(arrays) {
  521    -1             var results = [];
  522    -1             for (var i = 0; i < arrays.length; i++) {
  523    -1                 for (var j = 0; j < arrays[i].length; j++) {
  524    -1                     if (_.indexOf(results, arrays[i][j]) === -1) {
  525    -1                         results.push(arrays[i][j]);
   -1   201                 /**
   -1   202                  * Set the value of a form input by name.
   -1   203                  *
   -1   204                  * In case of a checkbox, sets `element.checked`.
   -1   205                  * In case of radioboxes, selects the box with matching value.
   -1   206                  *
   -1   207                  * @param {string} name
   -1   208                  * @param {string|number|boolean} value
   -1   209                  */
   -1   210                 this.setModel = function(name, value) {
   -1   211                     var element = self.querySelector('[name=' + name + ']');
   -1   212                     if (element.type === 'checkbox') {
   -1   213                         element.checked = value;
   -1   214                     } else if (element.type === 'radio') {
   -1   215                         var options = self.querySelectorAll('[name=' + name + ']');
   -1   216                         $.setRadio(options, value);
   -1   217                     } else {
   -1   218                         element.value = value;
  526   219                     }
  527    -1                 }
  528    -1             }
  529    -1             return results;
  530    -1         };
  531    -1 
  532    -1         /**
  533    -1          * @param {Array} a
  534    -1          * @param {Array} b
  535    -1          * @return {Array}
  536    -1          */
  537    -1         _.difference = function(a, b) {
  538    -1             var results = [];
  539    -1             for (var i = 0; i < a.length; i++) {
  540    -1                 if (_.indexOf(b, a[i]) === -1) {
  541    -1                     results.push(a[i]);
  542    -1                 }
  543    -1             }
  544    -1             return results;
  545    -1         };
  546    -1 
  547    -1         /**
  548    -1          * @param {Array} a
  549    -1          * @return {Array}
  550    -1          */
  551    -1         _.flatten = function(a) {
  552    -1             var o = [];
  553    -1             _.forEach(a, function(item) {
  554    -1                 if (_.isArray(item)) {
  555    -1                     o = o.concat(_.flatten(item));
  556    -1                 } else {
  557    -1                     o.push(item);
  558    -1                 }
  559    -1             });
  560    -1             return o;
  561    -1         };
  562    -1 
  563    -1         return _;
  564    -1     });
  565    -1     /**
  566    -1      * angular inspired location service.
  567    -1      * @module muu-location
  568    -1      */
  569    -1     _define('muu-location', ['muu-search'], function(q) {
  570    -1         "use strict";
  571    -1 
  572    -1         /** @lends module:muu-location */
  573    -1         var loc = {};
  574    -1 
  575    -1         /**
  576    -1          * @return {string}
  577    -1          */
  578    -1         loc.absUrl = function() {
  579    -1             return location.href;
  580    -1         };
  581    -1 
  582    -1         /**
  583    -1          * @return {string}
  584    -1          *//**
  585    -1          * @param {string} value
  586    -1          * @param {boolean} [replace]
  587    -1          * @return {muu-location}
  588    -1          */
  589    -1         loc.url = function(value, replace) {
  590    -1             if (value === undefined) {
  591    -1                 return location.pathname + location.search + location.hash;
  592    -1             } else if (replace) {
  593    -1                 history.replaceState(null, null, value);
  594    -1             } else {
  595    -1                 history.pushState(null, null, value);
  596    -1             }
  597    -1             return loc;
  598    -1         };
  599    -1 
  600    -1         /**
  601    -1          * @return {string}
  602    -1          */
  603    -1         loc.protocol = function() {
  604    -1             return location.protocol;
  605    -1         };
   -1   220                 };
   -1   221             };
  606   222 
   -1   223             return Directive;
   -1   224         });
  607   225         /**
  608    -1          * @return {string}
   -1   226          * DOM related helper functions
   -1   227          * @module muu-dom-helpers
  609   228          */
  610    -1         loc.host = function() {
  611    -1             return location.host;
  612    -1         };
   -1   229         _define("muu-dom-helpers", ['muu-js-helpers'], function(_) {
   -1   230             "use strict";
  613   231 
  614    -1         /**
  615    -1          * @return {string}
  616    -1          */
  617    -1         loc.port = function() {
  618    -1             return location.port;
  619    -1         };
  620    -1 
  621    -1         /**
  622    -1          * @return {string}
  623    -1          *//**
  624    -1          * @param {string} value
  625    -1          * @param {boolean} [replace]
  626    -1          * @return {muu-location}
  627    -1          */
  628    -1         loc.path = function(value, replace) {
  629    -1             if (value === undefined) {
  630    -1                 return location.pathname;
  631    -1             } else {
  632    -1                 var url = value + location.search + location.hash;
  633    -1                 loc.url(url, replace);
  634    -1                 return loc;
  635    -1             }
  636    -1         };
   -1   232             var entityMap = {
   -1   233                 '&': '&amp;',
   -1   234                 '<': '&lt;',
   -1   235                 '>': '&gt;',
   -1   236                 '"': '&quot;',
   -1   237                 "'": '&#39;',
   -1   238                 '/': '&#x2F;'
   -1   239             };
  637   240 
  638    -1         var _search = function(value, replace) {
  639    -1             if (value === undefined) {
  640    -1                 return location.search;
  641    -1             } else {
  642    -1                 if (value && value[0] !== '?') {
  643    -1                     value = '?' + value;
  644    -1                 }
  645    -1                 if (value.length === 1) {
  646    -1                     value = '';
  647    -1                 }
   -1   241             /** @lends module:muu-dom-helpers */
   -1   242             var $ = {};
  648   243 
  649    -1                 var url = location.pathname + value + location.hash;
  650    -1                 loc.url(url, replace);
  651    -1                 return loc;
  652    -1             }
  653    -1         };
   -1   244             $.DELAY = 1000;
  654   245 
  655    -1         /**
  656    -1          * @return {Object}
  657    -1          *//**
  658    -1          * @param {string|object} value
  659    -1          * @return {muu-location}
  660    -1          *//**
  661    -1          * @param {string} key
  662    -1          * @param {*} value
  663    -1          * @param {boolean} [replace]
  664    -1          * @return {muu-location}
  665    -1          */
  666    -1         loc.search = function(key, value, replace) {
  667    -1             if (key !== undefined) {
  668    -1                 if (value !== undefined) {
  669    -1                     var search = q.parse(_search());
  670    -1                     search[key] = value;
  671    -1                     return _search(q.unparse(search), replace);
  672    -1                 } else {
  673    -1                     return _search(q.unparse(key), replace);
  674    -1                 }
  675    -1             } else {
  676    -1                 return q.parse(_search());
  677    -1             }
  678    -1         };
   -1   246             /**
   -1   247              * @param {string} string
   -1   248              * @return {string} - escaped HTML
   -1   249              */
   -1   250             $.escapeHtml = function(string) {
   -1   251                 return String(string).replace(/[&<>"'\/]/g, function(s) {
   -1   252                     return entityMap[s];
   -1   253                 });
   -1   254             };
  679   255 
  680    -1         /**
  681    -1          * @return {string}
  682    -1          *//**
  683    -1          * @param {string} value
  684    -1          * @param {boolean} [replace]
  685    -1          * @return {muu-location}
  686    -1          */
  687    -1         loc.hash = function(value, replace) {
  688    -1             if (value === undefined) {
  689    -1                 if (location.hash) {
  690    -1                     return location.hash.slice(1);
   -1   256             /**
   -1   257              * Cross browser custom events.
   -1   258              *
   -1   259              * *Note*: IE does not seem to like it when you use existing event names
   -1   260              * with this.
   -1   261              *
   -1   262              * @param {string} type
   -1   263              * @param {*} detail
   -1   264              * @return {Event}
   -1   265              * @see https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Creating_and_triggering_events
   -1   266              */
   -1   267             $.createEvent = function(type, detail) {
   -1   268                 if (typeof CustomEvent === 'function') {
   -1   269                     return new CustomEvent(type, {
   -1   270                         detail: detail
   -1   271                     });
  691   272                 } else {
  692    -1                     return '';
   -1   273                     var event = document.createEvent('CustomEvent');
   -1   274                     event.initCustomEvent(type, false, true, detail);
   -1   275                     return event;
  693   276                 }
  694    -1             } else {
  695    -1                 var url = location.pathname + location.search + '#' + value;
  696    -1                 loc.url(url, replace);
  697    -1                 return loc;
  698    -1             }
  699    -1         };
  700    -1 
  701    -1         /**
  702    -1          * @param {string} eventName
  703    -1          * @param {Function} fn
  704    -1          * @return {muu-location}
  705    -1          */
  706    -1         loc.addEventListener = function(eventName, fn) {
  707    -1             if (eventName === 'change') {
  708    -1                 window.addEventListener('popstate', fn, false);
  709    -1             }
  710    -1             return loc;
  711    -1         };
  712    -1 
  713    -1         /**
  714    -1          * @param {string} eventName
  715    -1          * @param {Function} fn
  716    -1          * @return {muu-location}
  717    -1          */
  718    -1         loc.removeEventListener = function(eventName, fn) {
  719    -1             if (eventName === 'change') {
  720    -1                 window.removeEventListener('popstate', fn, false);
  721    -1             }
  722    -1             return loc;
  723    -1         };
  724    -1 
  725    -1         return loc;
  726    -1     });
  727    -1     /**
  728    -1      * Exports the {@link Registry} class.
  729    -1      * @module muu-registry
  730    -1      * @ignore
  731    -1      */
  732    -1     _define('muu-registry', ['muu-template', 'muu-directive', 'muu-js-helpers', 'muu-dom-helpers'], function(muuTemplate, Directive, _, $) {
  733    -1         "use strict";
  734    -1 
  735    -1         /**
  736    -1          * @constructs Registry
  737    -1          * @param {Object} config The config object may have following properties:
  738    -1          *
  739    -1          * - **debug** - `{boolean}` - Enable debug mode. In debug mode,
  740    -1          *   directive objects are available as properties from the DOM as
  741    -1          *   `element.directive`.
  742    -1          * - **renderer** - `{function(string, Object)}` - The template renderer
  743    -1          *   to be used. Defaults to {@link module:muu-template}.
  744    -1          */
  745    -1         var Registry = function(config) {
  746    -1             var self = this;
  747    -1             var directives = {};
  748    -1 
  749    -1             this.config = config || {};
  750    -1             this.renderer = self.config.renderer || muuTemplate;
   -1   277             };
  751   278 
  752   279             /**
  753    -1              * Register a new type of {@link Directive}
  754    -1              *
  755    -1              * @param {string} type
  756    -1              * @param {string} template
  757    -1              * @param {function(Directive, Element): Function} link The link
  758    -1              *   function is called with an instance of {@link Directive} and a
  759    -1              *   Element when {@link Registry#link} is executed.
  760    -1              *
  761    -1              *   It is the only place where you can access a directive and
  762    -1              *   therefore the place where you define its behavior.
  763    -1              *
  764    -1              *   This typically means to make an initial call to {@link
  765    -1              *   Directive#update} and to add some event listeners. You should also
  766    -1              *   return an *unlink* function that clears all external references in
  767    -1              *   order to avoid memory leaks.
  768    -1              * @return {Registry} this
   -1   280              * @param {EventTarget} element
   -1   281              * @param {string} eventName
   -1   282              * @param {Function} callback
   -1   283              * @return {function()} An unregister function
  769   284              */
  770    -1             this.registerDirective = function(type, template, link) {
  771    -1                 directives[type] = {
  772    -1                     template: template,
  773    -1                     link: link
   -1   285             $.on = function(element, eventName, callback) {
   -1   286                 element.addEventListener(eventName, callback, false);
   -1   287                 return function() {
   -1   288                     element.removeEventListener(eventName, callback, false);
  774   289                 };
  775    -1                 return self;
  776   290             };
  777   291 
  778   292             /**
  779    -1              * Shortcut for wrapping calls to {@link Registry} in a function.
  780    -1              *
  781    -1              * This can be esepcially helpful if that function is defined in a
  782    -1              * different module.
  783    -1              *
  784    -1              * ```.js
  785    -1              * _define('foobar', [], function() {
  786    -1              *   return function(registry) {
  787    -1              *     registry
  788    -1              *        .registerDirective('foo', '...', function() {...})
  789    -1              *        .registerDirective('bar', '...', function() {...});
  790    -1              *   };
  791    -1              * });
  792    -1              *
  793    -1              * require(['foobar'], function(foobar) {
  794    -1              *   var registry = new Registry();
  795    -1              *   registry.registerModule(foobar);
  796    -1              * });
  797    -1              * ```
  798    -1              *
  799    -1              * @param {function(Registry)} module
  800    -1              * @return {Registry} this
   -1   293              * @param {Function} fn
   -1   294              * @return {function()} An unregister function
   -1   295              */
   -1   296             $.ready = function(fn) {
   -1   297                 var _fn = _.once(fn);
   -1   298                 if (document.readyState === 'complete') {
   -1   299                     _fn();
   -1   300                     return function() {};
   -1   301                 } else {
   -1   302                     var u1 = $.on(document, 'DOMContentLoaded', _fn);
   -1   303                     var u2 = $.on(window, 'load', _fn);
   -1   304                     return function() {
   -1   305                         u1();
   -1   306                         u2();
   -1   307                     };
   -1   308                 }
   -1   309             };
   -1   310 
   -1   311             /**
   -1   312              * @param {Node} desc
   -1   313              * @param {Node} root
   -1   314              * @return {boolean}
  801   315              */
  802    -1             this.registerModule = function(module) {
  803    -1                 module(self);
  804    -1                 return self;
   -1   316             $.isDescendant = function(desc, root) {
   -1   317                  return !!desc && (desc === root || $.isDescendant(desc.parentNode, root));
  805   318             };
  806   319 
  807   320             /**
  808    -1              * Create and initialise a {@link Directive} for `element`.
   -1   321              * Execute a function when `element` is removed from the DOM.
   -1   322              *
   -1   323              * *Note*: The callback is not executed directly when (or even before) the
   -1   324              * element is removed but with a slight delay. So the only way to test this
   -1   325              * is to use a timeout in the test.
  809   326              *
  810   327              * @param {Element} element
  811    -1              * @param {string} type
  812    -1              * @return {Directive}
   -1   328              * @param {Function} fn
   -1   329              * @return {function()} An unregister function
  813   330              */
  814    -1             this.link = function(element, type) {
  815    -1                 if (type === undefined) {
  816    -1                     type = element.getAttribute('type');
  817    -1                 }
  818    -1 
  819    -1                 if (!directives.hasOwnProperty(type)) {
  820    -1                     throw new Error('Unknown directive type: ' + type);
  821    -1                 }
  822    -1 
  823    -1                 var template = directives[type].template;
  824    -1                 var link = directives[type].link;
   -1   331             $.destroy = function(element, fn) {
   -1   332                 var unregister;
   -1   333 
   -1   334                 if (!!window.MutationObserver) {
   -1   335                     var observer = new MutationObserver(function() {
   -1   336                         if (!$.isDescendant(element, document)) {
   -1   337                             fn();
   -1   338                             unregister();
   -1   339                         }
   -1   340                     });
  825   341 
  826    -1                 var directive = new Directive(element, template, self);
  827    -1                 var unlink = link(directive, element);
  828    -1                 element.classList.add('muu-isolate');
  829    -1                 element.classList.add('muu-initialised');
   -1   342                     observer.observe(document, {
   -1   343                          childList: true,
   -1   344                          subtree: true
   -1   345                     });
  830   346 
  831    -1                 if (self.config.debug) {
  832    -1                     element.directive = directive;
  833    -1                 }
   -1   347                     unregister = _.once(function() {
   -1   348                         observer.disconnect();
   -1   349                         observer = undefined;
   -1   350                     });
   -1   351                 } else {
   -1   352                     var intervalID = setInterval(function() {
   -1   353                         if (!$.isDescendant(element, document)) {
   -1   354                             fn();
   -1   355                             unregister();
   -1   356                         }
   -1   357                     }, $.DELAY);
  834   358 
  835    -1                 if (unlink !== undefined) {
  836    -1                     $.destroy(element, unlink);
   -1   359                     unregister = function() {
   -1   360                         clearInterval(intervalID);
   -1   361                     };
  837   362                 }
  838   363 
  839    -1                 return directive;
   -1   364                 return unregister;
  840   365             };
  841   366 
  842   367             /**
  843    -1              * Link all directives that can be found inside `root`.
  844    -1              *
  845    -1              * @param {Element} root
  846    -1              * @return {Array.<Directive>}
   -1   368              * @param {Array.<Element>} options
   -1   369              * @return {string}
   -1   370              * @suppress {missingReturn}
  847   371              */
  848    -1             this.linkAll = function(root) {
  849    -1                 // NOTE: root may be a DOM Node or a directive
  850    -1                 var elements = _.filter(root.querySelectorAll('muu'), function(element) {
  851    -1                     return !element.classList.contains('muu-initialised');
  852    -1                 });
  853    -1                 return _.map(elements, function(element) {
  854    -1                     return self.link(element);
  855    -1                 });
  856    -1             };
  857    -1         };
  858    -1 
  859    -1         return Registry;
  860    -1     });
  861    -1     _define('muu-search', ['muu-js-helpers'], function(_) {
  862    -1         "use strict";
  863    -1 
  864    -1         var q = {};
  865    -1 
  866    -1         q.parse = function(s) {
  867    -1             var q = {};
  868    -1 
  869    -1             var set = function(key, value) {
  870    -1                 if (!q.hasOwnProperty(key)) {
  871    -1                     q[key] = value;
  872    -1                 } else if (_.isArray(q[key])) {
  873    -1                     q[key].push(value);
  874    -1                 } else {
  875    -1                     q[key] = [q[key], value];
   -1   372             $.getRadio = function(options) {
   -1   373                 for (var i = 0; i < options.length; i++) {
   -1   374                     if (options[i].checked) {
   -1   375                         return options[i].value;
   -1   376                     }
  876   377                 }
  877   378             };
  878   379 
  879    -1             _.forEach(s.substring(1).split('&'), function(item) {
  880    -1                 var i = _.map(item.split('='), decodeURIComponent);
  881    -1                 if (i.length === 2) {
  882    -1                     set(i[0], i[1]);
  883    -1                 } else if (i[0]) {
  884    -1                     set(i[0], true);
   -1   380             /**
   -1   381              * @param {Array.<Element>} options
   -1   382              * @param {string} value
   -1   383              */
   -1   384             $.setRadio = function(options, value) {
   -1   385                 for (var i = 0; i < options.length; i++) {
   -1   386                     if (options[i].value === value) {
   -1   387                         options[i].checked = true;
   -1   388                     } else {
   -1   389                         options[i].checked = false;
   -1   390                     }
  885   391                 }
  886    -1             });
  887    -1             return q;
  888    -1         };
  889    -1 
  890    -1         var unparseItem = function(key, value) {
  891    -1             if (value === undefined || value === null || value === false) {
  892    -1                 return [];
  893    -1             } else if (_.isArray(value)) {
  894    -1                 return _.flatten(_.map(value, function(v) {
  895    -1                     return unparseItem(key, v);
  896    -1                 }));
  897    -1             } else if (value === true) {
  898    -1                 return [encodeURIComponent(key)];
  899    -1             } else {
  900    -1                 return [encodeURIComponent(key) + '=' + encodeURIComponent(value)];
  901    -1             }
  902    -1         };
   -1   392             };
  903   393 
  904    -1         q.unparse = function(q) {
  905    -1             if (_.isString(q)) {
  906    -1                 return q;
  907    -1             }
   -1   394             return $;
   -1   395         });
   -1   396         /**
   -1   397          * This module gives access to the following objects:
   -1   398          *
   -1   399          * -   `Registry` - {@link Registry}
   -1   400          * -   `$` - {@link module:muu-dom-helpers}
   -1   401          * -   `$location` - {@link module:muu-location}
   -1   402          *
   -1   403          * @module muu
   -1   404          */
   -1   405         _define('muu', ['muu-registry', 'muu-dom-helpers', 'muu-location'], function(Registry, $, $location) {
   -1   406             "use strict";
  908   407 
  909    -1             var a = []
  910    -1             for (var key in q) {
  911    -1                 if (q.hasOwnProperty(key)) {
  912    -1                     a = a.concat(unparseItem(key, q[key]));
  913    -1                 }
  914    -1             }
  915    -1             if (a.length > 0) {
  916    -1                 return '?' + a.join('&');
  917    -1             } else {
  918    -1                 return '';
  919    -1             }
  920    -1         };
   -1   408             var module = {};
  921   409 
  922    -1         return q;
  923    -1     });
  924    -1     /**
  925    -1      * minimal mustache insipred templating
  926    -1      *
  927    -1      * ## Variables
  928    -1      *
  929    -1      * Variables are created with a `{{name}}` tag. These are always escaped.
  930    -1      *
  931    -1      * ## Loops
  932    -1      *
  933    -1      * Loops render blocks of text a number of times, depending on the value of
  934    -1      * the key in the current context.
  935    -1      *
  936    -1      * A loop begins with a pound and ends with a slash. That is, `{{#person}}`
  937    -1      * begins a "person" section while `{{/person}}` ends it.
  938    -1      *
  939    -1      * If the value is an array, the block is repeated for each item in that array.
  940    -1      * In any other case, the block is rendered with the outer scope, but only if
  941    -1      * the value is truthy.
  942    -1      *
  943    -1      * ## Inverted loops
  944    -1      *
  945    -1      * Inverted loops render blocks of test if the value of the key is falsy. They
  946    -1      * begin with a caret.
  947    -1      *
  948    -1      * ## Comments
  949    -1      *
  950    -1      * Comments begin with a bang and are ignored.
  951    -1      *
  952    -1      * ## Pairs
  953    -1      *
  954    -1      * Pairs look like JSON objects. The result is a space separated list of all
  955    -1      * keys with truthy values.
  956    -1      *
  957    -1      * ```
  958    -1      * muuTemplate('{{foo: var1, bar: var2, baz: var3}}', {
  959    -1      *   var1: true,
  960    -1      *   var2: false,
  961    -1      *   var3: true
  962    -1      * });  // 'foo baz'
  963    -1      * ```
  964    -1      *
  965    -1      * ## Special variable `this`
  966    -1      *
  967    -1      * `this` always refers to the current context. So the following expressions
  968    -1      * are equivalent:
  969    -1      *
  970    -1      * ```
  971    -1      * muuTemplate('{{#items}}{{content}}{{/items}}', {
  972    -1      *   item: [{
  973    -1      *     content: 1
  974    -1      *   }, {
  975    -1      *     content: 2
  976    -1      *   }]
  977    -1      * });
  978    -1      *
  979    -1      * muuTemplate('{{#this}}{{this}}{{/this}}', [1, 2]);
  980    -1      * ```
  981    -1      *
  982    -1      * @module muu-template
  983    -1      * @param {string} template
  984    -1      * @param {Object} data
  985    -1      * @return {string}
  986    -1      */
  987    -1     _define('muu-template', ['muu-js-helpers', 'muu-dom-helpers'], function(_, $) {
  988    -1         "use strict";
  989    -1 
  990    -1         var openTag = '{{';
  991    -1         var closeTag = '}}';
  992    -1 
  993    -1         var getValue = function(key, data) {
  994    -1             return key === 'this' ? data : data[key];
  995    -1         };
   -1   410             module.Registry = Registry;
   -1   411             module.$ = $;
   -1   412             module.$location = $location;
  996   413 
  997    -1         var parseVariableTemplate = function(template) {
  998    -1             var content = template.slice(2, -2);
   -1   414             return module;
   -1   415         });
   -1   416         /**
   -1   417          * Exports the {@link Registry} class.
   -1   418          * @module muu-registry
   -1   419          * @ignore
   -1   420          */
   -1   421         _define('muu-registry', ['muu-template', 'muu-directive', 'muu-js-helpers', 'muu-dom-helpers'], function(muuTemplate, Directive, _, $) {
   -1   422             "use strict";
  999   423 
 1000    -1             if (template.indexOf(':') === -1) {
 1001    -1                 return function(data) {
 1002    -1                     return $.escapeHtml(getValue(content, data) || '');
   -1   424             /**
   -1   425              * @constructs Registry
   -1   426              * @param {Object} config The config object may have following properties:
   -1   427              *
   -1   428              * - **debug** - `{boolean}` - Enable debug mode. In debug mode,
   -1   429              *   directive objects are available as properties from the DOM as
   -1   430              *   `element.directive`.
   -1   431              * - **renderer** - `{function(string, Object)}` - The template renderer
   -1   432              *   to be used. Defaults to {@link module:muu-template}.
   -1   433              */
   -1   434             var Registry = function(config) {
   -1   435                 var self = this;
   -1   436                 var directives = {};
   -1   437 
   -1   438                 this.config = config || {};
   -1   439                 this.renderer = self.config.renderer || muuTemplate;
   -1   440 
   -1   441                 /**
   -1   442                  * Register a new type of {@link Directive}
   -1   443                  *
   -1   444                  * @param {string} type
   -1   445                  * @param {string} template
   -1   446                  * @param {function(Directive, Element): Function} link The link
   -1   447                  *   function is called with an instance of {@link Directive} and a
   -1   448                  *   Element when {@link Registry#link} is executed.
   -1   449                  *
   -1   450                  *   It is the only place where you can access a directive and
   -1   451                  *   therefore the place where you define its behavior.
   -1   452                  *
   -1   453                  *   This typically means to make an initial call to {@link
   -1   454                  *   Directive#update} and to add some event listeners. You should also
   -1   455                  *   return an *unlink* function that clears all external references in
   -1   456                  *   order to avoid memory leaks.
   -1   457                  * @return {Registry} this
   -1   458                  */
   -1   459                 this.registerDirective = function(type, template, link) {
   -1   460                     directives[type] = {
   -1   461                         template: template,
   -1   462                         link: link
   -1   463                     };
   -1   464                     return self;
 1003   465                 };
 1004    -1             } else {
 1005    -1                 var pairs = content.split(',').map(function(pair) {
 1006    -1                     var v = pair.split(':');
 1007    -1                     var key = v[0].trim();
 1008    -1                     var value = v.slice(1).join(':').trim();
 1009    -1                     return [key, value];
 1010    -1                 });
 1011    -1 
 1012    -1                 return function(data) {
 1013    -1                     var results = [];
 1014   466 
 1015    -1                     for (var i = 0; i < pairs.length; i++) {
 1016    -1                         var key = pairs[i][0];
 1017    -1                         var value = pairs[i][1];
   -1   467                 /**
   -1   468                  * Shortcut for wrapping calls to {@link Registry} in a function.
   -1   469                  *
   -1   470                  * This can be esepcially helpful if that function is defined in a
   -1   471                  * different module.
   -1   472                  *
   -1   473                  * ```.js
   -1   474                  * _define('foobar', [], function() {
   -1   475                  *   return function(registry) {
   -1   476                  *     registry
   -1   477                  *        .registerDirective('foo', '...', function() {...})
   -1   478                  *        .registerDirective('bar', '...', function() {...});
   -1   479                  *   };
   -1   480                  * });
   -1   481                  *
   -1   482                  * require(['foobar'], function(foobar) {
   -1   483                  *   var registry = new Registry();
   -1   484                  *   registry.registerModule(foobar);
   -1   485                  * });
   -1   486                  * ```
   -1   487                  *
   -1   488                  * @param {function(Registry)} module
   -1   489                  * @return {Registry} this
   -1   490                  */
   -1   491                 this.registerModule = function(module) {
   -1   492                     module(self);
   -1   493                     return self;
   -1   494                 };
 1018   495 
 1019    -1                         if (getValue(value, data)) {
 1020    -1                             results.push(key);
 1021    -1                         }
   -1   496                 /**
   -1   497                  * Create and initialise a {@link Directive} for `element`.
   -1   498                  *
   -1   499                  * @param {Element} element
   -1   500                  * @param {string} type
   -1   501                  * @return {Directive}
   -1   502                  */
   -1   503                 this.link = function(element, type) {
   -1   504                     if (type === undefined) {
   -1   505                         type = element.getAttribute('type');
 1022   506                     }
 1023   507 
 1024    -1                     return $.escapeHtml(results.join(' '));
 1025    -1                 };
 1026    -1             }
 1027    -1         };
   -1   508                     if (!directives.hasOwnProperty(type)) {
   -1   509                         throw new Error('Unknown directive type: ' + type);
   -1   510                     }
 1028   511 
 1029    -1         var parseLoopTemplate = function(tag, afterTag, inverted) {
 1030    -1             var tagName = tag.slice(3, -2);
   -1   512                     var template = directives[type].template;
   -1   513                     var link = directives[type].link;
 1031   514 
 1032    -1             var v = parseTemplate(afterTag, tagName);
 1033    -1             var inner = v[0];
 1034    -1             var afterLoop = v[1];
   -1   515                     var directive = new Directive(element, template, self);
   -1   516                     var unlink = link(directive, element);
   -1   517                     element.classList.add('muu-isolate');
   -1   518                     element.classList.add('muu-initialised');
 1035   519 
 1036    -1             var render = function(data) {
 1037    -1                 if (inverted) {
 1038    -1                     if (getValue(tagName, data)) {
 1039    -1                         return '';
 1040    -1                     } else {
 1041    -1                         return inner(data);
   -1   520                     if (self.config.debug) {
   -1   521                         element.directive = directive;
 1042   522                     }
 1043    -1                 } else {
 1044    -1                     if (_.isArray(getValue(tagName, data))) {
 1045    -1                         var result = '';
 1046    -1                         for (var i = 0; i < getValue(tagName, data).length; i++) {
 1047    -1                             result += inner(getValue(tagName, data)[i]);
 1048    -1                         }
 1049    -1                         return result;
 1050    -1                     } else if (getValue(tagName, data)) {
 1051    -1                         return inner(data);
 1052    -1                     } else {
 1053    -1                         return '';
   -1   523 
   -1   524                     if (unlink !== undefined) {
   -1   525                         $.destroy(element, unlink);
 1054   526                     }
 1055    -1                 }
 1056    -1             };
 1057   527 
 1058    -1             return [render, afterLoop];
 1059    -1         };
   -1   528                     return directive;
   -1   529                 };
 1060   530 
 1061    -1         var concat = function(a) {
 1062    -1             var last = a.pop();
 1063    -1 
 1064    -1             if (_.isArray(last)) {
 1065    -1                 a.push(last[0]);
 1066    -1                 return [concat(a), last[1]];
 1067    -1             } else {
 1068    -1                 a.push(last);
 1069    -1 
 1070    -1                 return function(data) {
 1071    -1                     return a.map(function(item) {
 1072    -1                         if (_.isString(item)) {
 1073    -1                             return item;
 1074    -1                         } else if (_.isFunction(item)) {
 1075    -1                             return item(data);
 1076    -1                         }
 1077    -1                     }).join('');
   -1   531                 /**
   -1   532                  * Link all directives that can be found inside `root`.
   -1   533                  *
   -1   534                  * @param {Element} root
   -1   535                  * @return {Array.<Directive>}
   -1   536                  */
   -1   537                 this.linkAll = function(root) {
   -1   538                     // NOTE: root may be a DOM Node or a directive
   -1   539                     var elements = _.filter(root.querySelectorAll('muu'), function(element) {
   -1   540                         return !element.classList.contains('muu-initialised');
   -1   541                     });
   -1   542                     return _.map(elements, function(element) {
   -1   543                         return self.link(element);
   -1   544                     });
 1078   545                 };
 1079    -1             }
 1080    -1         };
   -1   546             };
 1081   547 
 1082    -1         var parseTemplate = function(template, loopName) {
 1083    -1             var openIndex = template.indexOf(openTag);
 1084    -1             if (openIndex === -1) {
 1085    -1                 if (loopName === undefined) {
 1086    -1                     return function() {
 1087    -1                         return template;
 1088    -1                     };
 1089    -1                 } else {
 1090    -1                     throw new Error('unclosed loop: ' + loopName);
 1091    -1                 }
 1092    -1             } else {
 1093    -1                 var beforeTag = template.slice(0, openIndex);
 1094    -1                 var tmp = template.slice(openIndex);
   -1   548             return Registry;
   -1   549         });
   -1   550         /**
   -1   551          * Recreate children of `source` in `target` by making only small adjustments.
   -1   552          *
   -1   553          * *The following section explains details about the current implementation.
   -1   554          * These are likely to change in the future.*
   -1   555          *
   -1   556          * The algorithms is relatively simple. It just iterates through all top level
   -1   557          * nodes. If a node has a different `nodeType` (e.g. text or element) or a
   -1   558          * different `nodeName` (e.g. div or ul) it is replaced completely and the
   -1   559          * algorithm proceeds with the node's children recursively.  Otherwise, only
   -1   560          * the nodes's attributes are updated.
   -1   561          *
   -1   562          * Note that non-attribute properties (e.g. value) are lost in the first case
   -1   563          * and preserved in the second.
   -1   564          *
   -1   565          * If the algorithm encounters an element with the class `muu-isolate` it does
   -1   566          * not recurse into its children. This way, you can protect dynamically
   -1   567          * generated content from being overwritten.
   -1   568          *
   -1   569          * @module muu-update-dom
   -1   570          * @param {Element} target
   -1   571          * @param {Element} source
   -1   572          */
   -1   573         _define('muu-update-dom', ['muu-js-helpers'], function(_) {
   -1   574             "use strict";
 1095   575 
 1096    -1                 var closeIndex = tmp.indexOf(closeTag) + 2;
 1097    -1                 if (closeIndex === 1) {
 1098    -1                     throw new Error('unclosed tag: ' + tmp);
 1099    -1                 }
 1100    -1                 var tag = tmp.slice(0, closeIndex);
 1101    -1                 var afterTag = tmp.slice(closeIndex);
 1102    -1 
 1103    -1                 if (tag.lastIndexOf('{{#', 0) === 0) {
 1104    -1                     var v = parseLoopTemplate(tag, afterTag);
 1105    -1                     var loop = v[0];
 1106    -1                     var after = parseTemplate(v[1], loopName);
 1107    -1                     return concat([beforeTag, loop, after]);
 1108    -1                 } else if (tag.lastIndexOf('{{^', 0) === 0) {
 1109    -1                     var v = parseLoopTemplate(tag, afterTag, true);
 1110    -1                     var loop = v[0];
 1111    -1                     var after = parseTemplate(v[1], loopName);
 1112    -1                     return concat([beforeTag, loop, after]);
 1113    -1                 } else if (tag.lastIndexOf('{{!', 0) === 0) {
 1114    -1                     var after = parseTemplate(afterTag, loopName);
 1115    -1                     return concat([beforeTag, after]);
 1116    -1                 } else if (tag.lastIndexOf('{{/', 0) === 0) {
 1117    -1                     if (tag.slice(3, -2) === loopName) {
 1118    -1                         var render = function() {
 1119    -1                             return beforeTag;
 1120    -1                         };
 1121    -1                         return [render, afterTag];
 1122    -1                     } else {
 1123    -1                         throw new Error('unexpected closing loop: ' + tag);
   -1   576             var updateAttributes = function(target, source) {
   -1   577                 var targetAttrNames = _.map(target.attributes, function(item) {
   -1   578                     return item.name;
   -1   579                 });
   -1   580                 var sourceAttrNames = _.map(source.attributes, function(item) {
   -1   581                     return item.name;
   -1   582                 });
   -1   583 
   -1   584                 _.forEach(targetAttrNames, function(name) {
   -1   585                     // NOTE: ie8.js creates some attribute
   -1   586                     if (!source.hasAttribute(name) && name.substr(0, 7) !== '__IE8__') {
   -1   587                         target.removeAttribute(name);
 1124   588                     }
 1125    -1                 } else {
 1126    -1                     var render = parseVariableTemplate(tag);
 1127    -1                     var after = parseTemplate(afterTag, loopName);
 1128    -1                     return concat([beforeTag, render, after]);
 1129    -1                 }
 1130    -1             }
 1131    -1         };
   -1   589                 });
   -1   590                 _.forEach(sourceAttrNames, function(name) {
   -1   591                     if (target.getAttribute(name) !== source.getAttribute(name)) {
   -1   592                         target.setAttribute(name, source.getAttribute(name));
   -1   593                     }
   -1   594                 });
   -1   595             };
 1132   596 
 1133    -1         return function(template, data) {
 1134    -1             return parseTemplate(template)(data);
 1135    -1         };
 1136    -1     });
 1137    -1     /**
 1138    -1      * Recreate children of `source` in `target` by making only small adjustments.
 1139    -1      *
 1140    -1      * *The following section explains details about the current implementation.
 1141    -1      * These are likely to change in the future.*
 1142    -1      *
 1143    -1      * The algorithms is relatively simple. It just iterates through all top level
 1144    -1      * nodes. If a node has a different `nodeType` (e.g. text or element) or a
 1145    -1      * different `nodeName` (e.g. div or ul) it is replaced completely and the
 1146    -1      * algorithm proceeds with the node's children recursively.  Otherwise, only
 1147    -1      * the nodes's attributes are updated.
 1148    -1      *
 1149    -1      * Note that non-attribute properties (e.g. value) are lost in the first case
 1150    -1      * and preserved in the second.
 1151    -1      *
 1152    -1      * If the algorithm encounters an element with the class `muu-isolate` it does
 1153    -1      * not recurse into its children. This way, you can protect dynamically
 1154    -1      * generated content from being overwritten.
 1155    -1      *
 1156    -1      * @module muu-update-dom
 1157    -1      * @param {Element} target
 1158    -1      * @param {Element} source
 1159    -1      */
 1160    -1     _define('muu-update-dom', ['muu-js-helpers'], function(_) {
 1161    -1         "use strict";
 1162    -1 
 1163    -1         var updateAttributes = function(target, source) {
 1164    -1             var targetAttrNames = _.map(target.attributes, function(item) {
 1165    -1                 return item.name;
 1166    -1             });
 1167    -1             var sourceAttrNames = _.map(source.attributes, function(item) {
 1168    -1                 return item.name;
 1169    -1             });
 1170    -1 
 1171    -1             _.forEach(targetAttrNames, function(name) {
 1172    -1                 // NOTE: ie8.js creates some attribute
 1173    -1                 if (!source.hasAttribute(name) && name.substr(0, 7) !== '__IE8__') {
 1174    -1                     target.removeAttribute(name);
   -1   597             var updateDOM = function(target, source) {
   -1   598                 var nt = target.childNodes.length;
   -1   599                 var ns = source.childNodes.length;
   -1   600 
   -1   601                 for (var i = ns; i < nt; i++) {
   -1   602                     target.removeChild(target.childNodes[ns]);
 1175   603                 }
 1176    -1             });
 1177    -1             _.forEach(sourceAttrNames, function(name) {
 1178    -1                 if (target.getAttribute(name) !== source.getAttribute(name)) {
 1179    -1                     target.setAttribute(name, source.getAttribute(name));
   -1   604                 for (i = nt; i < ns; i++) {
   -1   605                     target.appendChild(source.childNodes[nt]);
 1180   606                 }
 1181    -1             });
 1182    -1         };
 1183    -1 
 1184    -1         var updateDOM = function(target, source) {
 1185    -1             var nt = target.childNodes.length;
 1186    -1             var ns = source.childNodes.length;
 1187    -1 
 1188    -1             for (var i = ns; i < nt; i++) {
 1189    -1                 target.removeChild(target.childNodes[ns]);
 1190    -1             }
 1191    -1             for (i = nt; i < ns; i++) {
 1192    -1                 target.appendChild(source.childNodes[nt]);
 1193    -1             }
 1194    -1             for (i = 0; i < nt && i < ns; i++) {
 1195    -1                 var tchild = target.childNodes[i];
 1196    -1                 var schild = source.childNodes[i];
 1197    -1 
 1198    -1                 if (tchild.nodeType === schild.nodeType && tchild.nodeName === schild.nodeName && tchild.type === schild.type) {
 1199    -1                     if (tchild.nodeType === 1) {
 1200    -1                         var muuClasses = _.filter(tchild.classList, function(cls) {
 1201    -1                             return cls.lastIndexOf('muu-', 0) === 0;
 1202    -1                         });
 1203    -1                         updateAttributes(tchild, schild);
 1204    -1                         _.forEach(muuClasses, function(cls) {
 1205    -1                             tchild.classList.add(cls);
 1206    -1                         });
 1207    -1                     } else if (tchild.nodeType === 3) {
 1208    -1                         tchild.nodeValue = schild.nodeValue;
 1209    -1                     }
 1210    -1                     if (tchild.nodeType !== 3 && !tchild.classList.contains('muu-isolate')) {
 1211    -1                         updateDOM(tchild, schild);
   -1   607                 for (i = 0; i < nt && i < ns; i++) {
   -1   608                     var tchild = target.childNodes[i];
   -1   609                     var schild = source.childNodes[i];
   -1   610 
   -1   611                     if (tchild.nodeType === schild.nodeType && tchild.nodeName === schild.nodeName && tchild.type === schild.type) {
   -1   612                         if (tchild.nodeType === 1) {
   -1   613                             var muuClasses = _.filter(tchild.classList, function(cls) {
   -1   614                                 return cls.lastIndexOf('muu-', 0) === 0;
   -1   615                             });
   -1   616                             updateAttributes(tchild, schild);
   -1   617                             _.forEach(muuClasses, function(cls) {
   -1   618                                 tchild.classList.add(cls);
   -1   619                             });
   -1   620                         } else if (tchild.nodeType === 3) {
   -1   621                             tchild.nodeValue = schild.nodeValue;
   -1   622                         }
   -1   623                         if (tchild.nodeType !== 3 && !tchild.classList.contains('muu-isolate')) {
   -1   624                             updateDOM(tchild, schild);
   -1   625                         }
   -1   626                     } else {
   -1   627                         tchild.parentNode.replaceChild(schild, tchild);
 1212   628                     }
 1213    -1                 } else {
 1214    -1                     tchild.parentNode.replaceChild(schild, tchild);
 1215   629                 }
 1216    -1             }
 1217    -1         };
   -1   630             };
 1218   631 
 1219    -1         return updateDOM;
 1220    -1     });
 1221    -1     /**
 1222    -1      * This module gives access to the following objects:
 1223    -1      *
 1224    -1      * -   `Registry` - {@link Registry}
 1225    -1      * -   `$` - {@link module:muu-dom-helpers}
 1226    -1      * -   `$location` - {@link module:muu-location}
 1227    -1      *
 1228    -1      * @module muu
 1229    -1      */
 1230    -1     _define('muu', ['muu-registry', 'muu-dom-helpers', 'muu-location'], function(Registry, $, $location) {
 1231    -1         "use strict";
 1232    -1 
 1233    -1         var module = {};
 1234    -1 
 1235    -1         module.Registry = Registry;
 1236    -1         module.$ = $;
 1237    -1         module.$location = $location;
 1238    -1 
 1239    -1         return module;
 1240    -1     });
   -1   632             return updateDOM;
   -1   633         });
 1241   634 
 1242    -1         return _require('muu');
   -1   635         return _require(name);
 1243   636     });
 1244   637 })(window, document, void 0);

diff --git a/dist/muu-core.min.js b/dist/muu-core.min.js

@@ -0,0 +1,10 @@
   -1     1 (function(q,n,p){(function(l){"function"===typeof define&&define.amd?define("muu",["lodash"],l):q.muu=l(q._)})(function(l){var m={};m["muu-js-helpers"]={instance:l};var t=function(e,k){for(var g=[],a=0;a<e.length;a++)g.push(k(e[a]));return g};l=function(e,k,g){m[e]={deps:k,factory:g}};var r=function(e){if(!m[e])return p;m[e].instance||(m[e].instance=m[e].factory.apply(p,t(m[e].deps,r)));return m[e].instance};l("muu-directive",["muu-dom-helpers","muu-js-helpers","muu-update-dom"],function(e,k,g){return function(a,
   -1     2 b,d){var h=this;a.innerHTML="";var c=function(f){var c="data-on"+f.type;f.target.hasAttribute(c)&&(c=f.target.getAttribute(c),f=e.createEvent("muu-"+c,f),a.dispatchEvent(f))};this.update=function(f){var s=n.createElement("div");s.innerHTML=d.renderer(b,f);g(a,s);k.forEach(["keydown","keyup","click","change","search"],function(a){k.forEach(h.querySelectorAll("[data-on"+a+"]"),function(f){f.addEventListener(a,c,!1)})});var u=e.createEvent("muu-parent-update");f=this.querySelectorAll("muu.muu-initialised");
   -1     3 k.forEach(f,function(a){a.dispatchEvent(u)});d.linkAll(h)};this.querySelectorAll=function(f){var c=a.querySelectorAll(f),d=a.querySelectorAll(".muu-isolate"),d=k.union(k.map(d,function(a){return a.querySelectorAll(f)}));return k.difference(c,d)};this.querySelector=function(a){a=h.querySelectorAll(a);if(0<a.length)return a[0]};this.getModel=function(a,c){if(a===p){var d={};k.forEach(h.querySelectorAll("[name]"),function(a){d[a.name]=h.getModel(a.name)});return d}var b=h.querySelector("[name="+a+"]");
   -1     4 return b===p?c:"checkbox"===b.type?b.checked:"radio"===b.type?(b=h.querySelectorAll("[name="+a+"]"),e.getRadio(b)||c):b.value};this.setModel=function(a,c){var b=h.querySelector("[name="+a+"]");"checkbox"===b.type?b.checked=c:"radio"===b.type?(b=h.querySelectorAll("[name="+a+"]"),e.setRadio(b,c)):b.value=c}}});l("muu-dom-helpers",["muu-js-helpers"],function(e){var k={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;","/":"&#x2F;"},g={DELAY:1E3,escapeHtml:function(a){return String(a).replace(/[&<>"'\/]/g,
   -1     5 function(a){return k[a]})},createEvent:function(a,b){if("function"===typeof CustomEvent)return new CustomEvent(a,{detail:b});var d=n.createEvent("CustomEvent");d.initCustomEvent(a,!1,!0,b);return d},on:function(a,b,d){a.addEventListener(b,d,!1);return function(){a.removeEventListener(b,d,!1)}},ready:function(a){a=e.once(a);if("complete"===n.readyState)return a(),function(){};var b=g.on(n,"DOMContentLoaded",a),d=g.on(q,"load",a);return function(){b();d()}},isDescendant:function(a,b){return!!a&&(a===
   -1     6 b||g.isDescendant(a.parentNode,b))},destroy:function(a,b){var d;if(q.MutationObserver){var h=new MutationObserver(function(){g.isDescendant(a,n)||(b(),d())});h.observe(n,{childList:!0,subtree:!0});d=e.once(function(){h.disconnect();h=p})}else{var c=setInterval(function(){g.isDescendant(a,n)||(b(),d())},g.DELAY);d=function(){clearInterval(c)}}return d},getRadio:function(a){for(var b=0;b<a.length;b++)if(a[b].checked)return a[b].value},setRadio:function(a,b){for(var d=0;d<a.length;d++)a[d].checked=a[d].value===
   -1     7 b?!0:!1}};return g});l("muu",["muu-registry","muu-dom-helpers","muu-location"],function(e,k,g){var a={};a.Registry=e;a.$=k;a.$location=g;return a});l("muu-registry",["muu-template","muu-directive","muu-js-helpers","muu-dom-helpers"],function(e,k,g,a){return function(b){var d=this,h={};this.config=b||{};this.renderer=d.config.renderer||e;this.registerDirective=function(a,b,e){h[a]={template:b,link:e};return d};this.registerModule=function(a){a(d);return d};this.link=function(c,b){b===p&&(b=c.getAttribute("type"));
   -1     8 if(!h.hasOwnProperty(b))throw Error("Unknown directive type: "+b);var e=h[b].link,g=new k(c,h[b].template,d),e=e(g,c);c.classList.add("muu-isolate");c.classList.add("muu-initialised");d.config.debug&&(c.directive=g);e!==p&&a.destroy(c,e);return g};this.linkAll=function(a){a=g.filter(a.querySelectorAll("muu"),function(a){return!a.classList.contains("muu-initialised")});return g.map(a,function(a){return d.link(a)})}}});l("muu-update-dom",["muu-js-helpers"],function(e){var k=function(a,b){var d=e.map(a.attributes,
   -1     9 function(a){return a.name}),g=e.map(b.attributes,function(a){return a.name});e.forEach(d,function(c){b.hasAttribute(c)||"__IE8__"===c.substr(0,7)||a.removeAttribute(c)});e.forEach(g,function(c){a.getAttribute(c)!==b.getAttribute(c)&&a.setAttribute(c,b.getAttribute(c))})},g=function(a,b){for(var d=a.childNodes.length,h=b.childNodes.length,c=h;c<d;c++)a.removeChild(a.childNodes[h]);for(c=d;c<h;c++)a.appendChild(b.childNodes[d]);for(c=0;c<d&&c<h;c++){var f=a.childNodes[c],l=b.childNodes[c];if(f.nodeType===
   -1    10 l.nodeType&&f.nodeName===l.nodeName&&f.type===l.type){if(1===f.nodeType){var m=e.filter(f.classList,function(a){return 0===a.lastIndexOf("muu-",0)});k(f,l);e.forEach(m,function(a){f.classList.add(a)})}else 3===f.nodeType&&(f.nodeValue=l.nodeValue);3===f.nodeType||f.classList.contains("muu-isolate")||g(f,l)}else f.parentNode.replaceChild(l,f)}};return g});return r("muu")})})(window,document,void 0);

diff --git a/dist/muu.js b/dist/muu.js

@@ -1,13 +1,19 @@
    1     1 (function(window, document, undefined) {
   -1     2     var name = 'muu';
   -1     3 
    2     4     (function(factory) {
    3     5         if (typeof define === 'function' && define.amd) {
    4    -1             define('muu', [], factory);
   -1     6             define(name, [], factory);
    5     7         } else {
    6    -1             window['muu'] = factory();
   -1     8             window[name] = factory(window._);
    7     9         }
    8    -1     })(function() {
   -1    10     })(function(lodash) {
    9    11         var modules = {};
   10    12 
   -1    13         modules['muu-js-helpers'] = {
   -1    14             instance: lodash
   -1    15         };
   -1    16 
   11    17         var map = function(a, fn) {
   12    18             var b = [];
   13    19             for (var i = 0; i < a.length; i++) {
@@ -38,1207 +44,1207 @@
   38    44             return modules[name].instance;
   39    45         };
   40    46 
   41    -1     /**
   42    -1      * Exports the {@link Directive} class.
   43    -1      * @module muu-directive
   44    -1      * @ignore
   45    -1      */
   46    -1     _define('muu-directive', ['muu-dom-helpers', 'muu-js-helpers', 'muu-update-dom'], function($, _, updateDOM) {
   47    -1         "use strict";
   48    -1 
   49    47         /**
   50    -1          * A directive is linked to a Element and manages the DOM tree below
   51    -1          * that element (excluding any isolated subtrees, e.g. those managed by
   52    -1          * subdirectives).
   53    -1          *
   54    -1          * It provides a set of methods to interact with the managed part of the
   55    -1          * DOM. This is separated into three distinct parts:
   56    -1          *
   57    -1          * - You can push data to the DOM using the {@link Directive#update}
   58    -1          *   method. The DOM will than be updated using the template that was
   59    -1          *   provided at construction.
   60    -1          * - You can get data from the DOM using the {@link Directive#getModel}
   61    -1          *   method. This is however restricted to form field by design.
   62    -1          * - You can react to DOM events by specifying an alias for them. In the
   63    -1          *   template, you might for example add the attribute
   64    -1          *   `data-onclick="custom"` to an element. When there is `click` event on
   65    -1          *   that element, a `muu-custom` event will be triggered on the
   66    -1          *   directive's root element.
   67    -1          *
   68    -1          * Directives are typically not created directly but via {@link
   69    -1          * Registry#link}.
   70    -1          *
   71    -1          * @constructs Directive
   72    -1          * @param {Element} root
   73    -1          * @param {string} template
   74    -1          * @param {Registry} registry
   -1    48          * Exports the {@link Directive} class.
   -1    49          * @module muu-directive
   -1    50          * @ignore
   75    51          */
   76    -1         var Directive = function(root, template, registry) {
   77    -1             var self = this;
   78    -1 
   79    -1             root.innerHTML = '';
   80    -1 
   81    -1             var eventCallback = function(originalEvent) {
   82    -1                 var attrName = 'data-on' + originalEvent.type;
   83    -1                 if (originalEvent.target.hasAttribute(attrName)) {
   84    -1                     var eventName = originalEvent.target.getAttribute(attrName);
   85    -1                     var event = $.createEvent('muu-' + eventName, originalEvent);
   86    -1                     root.dispatchEvent(event);
   87    -1                 }
   88    -1             };
   -1    52         _define('muu-directive', ['muu-dom-helpers', 'muu-js-helpers', 'muu-update-dom'], function($, _, updateDOM) {
   -1    53             "use strict";
   89    54 
   90    55             /**
   91    -1              * Rerender `template` with `data` and push the changes to the DOM.
   -1    56              * A directive is linked to a Element and manages the DOM tree below
   -1    57              * that element (excluding any isolated subtrees, e.g. those managed by
   -1    58              * subdirectives).
   -1    59              *
   -1    60              * It provides a set of methods to interact with the managed part of the
   -1    61              * DOM. This is separated into three distinct parts:
   -1    62              *
   -1    63              * - You can push data to the DOM using the {@link Directive#update}
   -1    64              *   method. The DOM will than be updated using the template that was
   -1    65              *   provided at construction.
   -1    66              * - You can get data from the DOM using the {@link Directive#getModel}
   -1    67              *   method. This is however restricted to form field by design.
   -1    68              * - You can react to DOM events by specifying an alias for them. In the
   -1    69              *   template, you might for example add the attribute
   -1    70              *   `data-onclick="custom"` to an element. When there is `click` event on
   -1    71              *   that element, a `muu-custom` event will be triggered on the
   -1    72              *   directive's root element.
   -1    73              *
   -1    74              * Directives are typically not created directly but via {@link
   -1    75              * Registry#link}.
   92    76              *
   93    -1              * @param {Object.<string, *>} data
   94    -1              * @see {@link module:muu-update-dom} for details.
   95    -1              * @see The templating system can be defined in the {@link Registry}.
   -1    77              * @constructs Directive
   -1    78              * @param {Element} root
   -1    79              * @param {string} template
   -1    80              * @param {Registry} registry
   96    81              */
   97    -1             this.update = function(data) {
   98    -1                 var tmp = document.createElement('div');
   99    -1                 tmp.innerHTML = registry.renderer(template, data);
   -1    82             var Directive = function(root, template, registry) {
   -1    83                 var self = this;
  100    84 
  101    -1                 updateDOM(root, tmp);
   -1    85                 root.innerHTML = '';
   -1    86 
   -1    87                 var eventCallback = function(originalEvent) {
   -1    88                     var attrName = 'data-on' + originalEvent.type;
   -1    89                     if (originalEvent.target.hasAttribute(attrName)) {
   -1    90                         var eventName = originalEvent.target.getAttribute(attrName);
   -1    91                         var event = $.createEvent('muu-' + eventName, originalEvent);
   -1    92                         root.dispatchEvent(event);
   -1    93                     }
   -1    94                 };
  102    95 
  103    -1                 _.forEach(['keydown', 'keyup', 'click', 'change', 'search'], function(eventType) {
  104    -1                     var selector = '[data-on' + eventType + ']';
  105    -1                     _.forEach(self.querySelectorAll(selector), function(element) {
  106    -1                         element.addEventListener(eventType, eventCallback, false);
   -1    96                 /**
   -1    97                  * Rerender `template` with `data` and push the changes to the DOM.
   -1    98                  *
   -1    99                  * @param {Object.<string, *>} data
   -1   100                  * @see {@link module:muu-update-dom} for details.
   -1   101                  * @see The templating system can be defined in the {@link Registry}.
   -1   102                  */
   -1   103                 this.update = function(data) {
   -1   104                     var tmp = document.createElement('div');
   -1   105                     tmp.innerHTML = registry.renderer(template, data);
   -1   106 
   -1   107                     updateDOM(root, tmp);
   -1   108 
   -1   109                     _.forEach(['keydown', 'keyup', 'click', 'change', 'search'], function(eventType) {
   -1   110                         var selector = '[data-on' + eventType + ']';
   -1   111                         _.forEach(self.querySelectorAll(selector), function(element) {
   -1   112                             element.addEventListener(eventType, eventCallback, false);
   -1   113                         });
  107   114                     });
  108    -1                 });
  109   115 
  110    -1                 var updateEvent = $.createEvent('muu-parent-update');
  111    -1                 var subDirectives = this.querySelectorAll('muu.muu-initialised');
  112    -1                 _.forEach(subDirectives, function(element) {
  113    -1                     element.dispatchEvent(updateEvent);
  114    -1                 });
   -1   116                     var updateEvent = $.createEvent('muu-parent-update');
   -1   117                     var subDirectives = this.querySelectorAll('muu.muu-initialised');
   -1   118                     _.forEach(subDirectives, function(element) {
   -1   119                         element.dispatchEvent(updateEvent);
   -1   120                     });
   -1   121 
   -1   122                     registry.linkAll(self);
   -1   123                 };
   -1   124 
   -1   125                 /**
   -1   126                  * A variant of `querySelectorAll` that returns only elements from
   -1   127                  * the managed part of the DOM.
   -1   128                  *
   -1   129                  * @private
   -1   130                  * @param {string} selector
   -1   131                  * @return {Array.<Element>} All child elements that match the given
   -1   132                  *     selector and are not isolated.
   -1   133                  */
   -1   134                 this.querySelectorAll = function(selector) {
   -1   135                     var hits = root.querySelectorAll(selector);
   -1   136 
   -1   137                     // NOTE: querySelectorAll returns all elements in the tree that
   -1   138                     // match the given selector.  findAll does the same with *relative
   -1   139                     // selectors* but does not seem to be available yet.
   -1   140                     var isolations = root.querySelectorAll('.muu-isolate');
   -1   141                     var isolated = _.union(_.map(isolations, function(isolation) {
   -1   142                         return isolation.querySelectorAll(selector);
   -1   143                     }));
   -1   144 
   -1   145                     return _.difference(hits, isolated);
   -1   146                 };
   -1   147 
   -1   148                 /**
   -1   149                  * A variant of `querySelector` that returns only elements from the
   -1   150                  * managed part of the DOM.
   -1   151                  *
   -1   152                  * @private
   -1   153                  * @param {String} selector
   -1   154                  * @return {Element} First child element that matches the given
   -1   155                  *     selector and is not isolated.
   -1   156                  * @suppress {missingReturn}
   -1   157                  */
   -1   158                 this.querySelector = function(selector) {
   -1   159                     var all = self.querySelectorAll(selector);
   -1   160                     if (all.length > 0) {
   -1   161                         return all[0];
   -1   162                     }
   -1   163                 };
  115   164 
  116    -1                 registry.linkAll(self);
   -1   165                 /**
   -1   166                  * Get all model data as a flat object.
   -1   167                  *
   -1   168                  * @return {Object.<string, string|number|boolean>}
   -1   169                  *//**
   -1   170                  * Get the value of a form input by name.
   -1   171                  *
   -1   172                  * In case of a checkbox, returns `boolean`.
   -1   173                  * In case of radioboxes, returns the value of the selected box.
   -1   174                  *
   -1   175                  * @param {string} name
   -1   176                  * @param {*} [_default]
   -1   177                  * @return {string|number|boolean|*}
   -1   178                  */
   -1   179                 this.getModel = function(name, _default) {
   -1   180                     if (name === undefined) {
   -1   181                         var model = {};
   -1   182                         _.forEach(self.querySelectorAll('[name]'), function(element) {
   -1   183                             model[element.name] = self.getModel(element.name);
   -1   184                         });
   -1   185                         return model;
   -1   186                     } else {
   -1   187                         var element = self.querySelector('[name=' + name + ']');
   -1   188                         if (element === undefined) {
   -1   189                             return _default;
   -1   190                         } else if (element.type === 'checkbox') {
   -1   191                             return element.checked;
   -1   192                         } else if (element.type === 'radio') {
   -1   193                             var options = self.querySelectorAll('[name=' + name + ']');
   -1   194                             return $.getRadio(options) || _default;
   -1   195                         } else {
   -1   196                             return element.value;
   -1   197                         }
   -1   198                     }
   -1   199                 };
   -1   200 
   -1   201                 /**
   -1   202                  * Set the value of a form input by name.
   -1   203                  *
   -1   204                  * In case of a checkbox, sets `element.checked`.
   -1   205                  * In case of radioboxes, selects the box with matching value.
   -1   206                  *
   -1   207                  * @param {string} name
   -1   208                  * @param {string|number|boolean} value
   -1   209                  */
   -1   210                 this.setModel = function(name, value) {
   -1   211                     var element = self.querySelector('[name=' + name + ']');
   -1   212                     if (element.type === 'checkbox') {
   -1   213                         element.checked = value;
   -1   214                     } else if (element.type === 'radio') {
   -1   215                         var options = self.querySelectorAll('[name=' + name + ']');
   -1   216                         $.setRadio(options, value);
   -1   217                     } else {
   -1   218                         element.value = value;
   -1   219                     }
   -1   220                 };
  117   221             };
  118   222 
  119    -1             /**
  120    -1              * A variant of `querySelectorAll` that returns only elements from
  121    -1              * the managed part of the DOM.
  122    -1              *
  123    -1              * @private
  124    -1              * @param {string} selector
  125    -1              * @return {Array.<Element>} All child elements that match the given
  126    -1              *     selector and are not isolated.
  127    -1              */
  128    -1             this.querySelectorAll = function(selector) {
  129    -1                 var hits = root.querySelectorAll(selector);
  130    -1 
  131    -1                 // NOTE: querySelectorAll returns all elements in the tree that
  132    -1                 // match the given selector.  findAll does the same with *relative
  133    -1                 // selectors* but does not seem to be available yet.
  134    -1                 var isolations = root.querySelectorAll('.muu-isolate');
  135    -1                 var isolated = _.union(_.map(isolations, function(isolation) {
  136    -1                     return isolation.querySelectorAll(selector);
  137    -1                 }));
  138    -1 
  139    -1                 return _.difference(hits, isolated);
   -1   223             return Directive;
   -1   224         });
   -1   225         /**
   -1   226          * DOM related helper functions
   -1   227          * @module muu-dom-helpers
   -1   228          */
   -1   229         _define("muu-dom-helpers", ['muu-js-helpers'], function(_) {
   -1   230             "use strict";
   -1   231 
   -1   232             var entityMap = {
   -1   233                 '&': '&amp;',
   -1   234                 '<': '&lt;',
   -1   235                 '>': '&gt;',
   -1   236                 '"': '&quot;',
   -1   237                 "'": '&#39;',
   -1   238                 '/': '&#x2F;'
  140   239             };
  141   240 
   -1   241             /** @lends module:muu-dom-helpers */
   -1   242             var $ = {};
   -1   243 
   -1   244             $.DELAY = 1000;
   -1   245 
  142   246             /**
  143    -1              * A variant of `querySelector` that returns only elements from the
  144    -1              * managed part of the DOM.
  145    -1              *
  146    -1              * @private
  147    -1              * @param {String} selector
  148    -1              * @return {Element} First child element that matches the given
  149    -1              *     selector and is not isolated.
  150    -1              * @suppress {missingReturn}
   -1   247              * @param {string} string
   -1   248              * @return {string} - escaped HTML
  151   249              */
  152    -1             this.querySelector = function(selector) {
  153    -1                 var all = self.querySelectorAll(selector);
  154    -1                 if (all.length > 0) {
  155    -1                     return all[0];
  156    -1                 }
   -1   250             $.escapeHtml = function(string) {
   -1   251                 return String(string).replace(/[&<>"'\/]/g, function(s) {
   -1   252                     return entityMap[s];
   -1   253                 });
  157   254             };
  158   255 
  159   256             /**
  160    -1              * Get all model data as a flat object.
  161    -1              *
  162    -1              * @return {Object.<string, string|number|boolean>}
  163    -1              *//**
  164    -1              * Get the value of a form input by name.
   -1   257              * Cross browser custom events.
  165   258              *
  166    -1              * In case of a checkbox, returns `boolean`.
  167    -1              * In case of radioboxes, returns the value of the selected box.
   -1   259              * *Note*: IE does not seem to like it when you use existing event names
   -1   260              * with this.
  168   261              *
  169    -1              * @param {string} name
  170    -1              * @param {*} [_default]
  171    -1              * @return {string|number|boolean|*}
   -1   262              * @param {string} type
   -1   263              * @param {*} detail
   -1   264              * @return {Event}
   -1   265              * @see https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Creating_and_triggering_events
  172   266              */
  173    -1             this.getModel = function(name, _default) {
  174    -1                 if (name === undefined) {
  175    -1                     var model = {};
  176    -1                     _.forEach(self.querySelectorAll('[name]'), function(element) {
  177    -1                         model[element.name] = self.getModel(element.name);
   -1   267             $.createEvent = function(type, detail) {
   -1   268                 if (typeof CustomEvent === 'function') {
   -1   269                     return new CustomEvent(type, {
   -1   270                         detail: detail
  178   271                     });
  179    -1                     return model;
  180   272                 } else {
  181    -1                     var element = self.querySelector('[name=' + name + ']');
  182    -1                     if (element === undefined) {
  183    -1                         return _default;
  184    -1                     } else if (element.type === 'checkbox') {
  185    -1                         return element.checked;
  186    -1                     } else if (element.type === 'radio') {
  187    -1                         var options = self.querySelectorAll('[name=' + name + ']');
  188    -1                         return $.getRadio(options) || _default;
  189    -1                     } else {
  190    -1                         return element.value;
  191    -1                     }
   -1   273                     var event = document.createEvent('CustomEvent');
   -1   274                     event.initCustomEvent(type, false, true, detail);
   -1   275                     return event;
  192   276                 }
  193   277             };
  194   278 
  195   279             /**
  196    -1              * Set the value of a form input by name.
  197    -1              *
  198    -1              * In case of a checkbox, sets `element.checked`.
  199    -1              * In case of radioboxes, selects the box with matching value.
  200    -1              *
  201    -1              * @param {string} name
  202    -1              * @param {string|number|boolean} value
   -1   280              * @param {EventTarget} element
   -1   281              * @param {string} eventName
   -1   282              * @param {Function} callback
   -1   283              * @return {function()} An unregister function
   -1   284              */
   -1   285             $.on = function(element, eventName, callback) {
   -1   286                 element.addEventListener(eventName, callback, false);
   -1   287                 return function() {
   -1   288                     element.removeEventListener(eventName, callback, false);
   -1   289                 };
   -1   290             };
   -1   291 
   -1   292             /**
   -1   293              * @param {Function} fn
   -1   294              * @return {function()} An unregister function
  203   295              */
  204    -1             this.setModel = function(name, value) {
  205    -1                 var element = self.querySelector('[name=' + name + ']');
  206    -1                 if (element.type === 'checkbox') {
  207    -1                     element.checked = value;
  208    -1                 } else if (element.type === 'radio') {
  209    -1                     var options = self.querySelectorAll('[name=' + name + ']');
  210    -1                     $.setRadio(options, value);
   -1   296             $.ready = function(fn) {
   -1   297                 var _fn = _.once(fn);
   -1   298                 if (document.readyState === 'complete') {
   -1   299                     _fn();
   -1   300                     return function() {};
  211   301                 } else {
  212    -1                     element.value = value;
   -1   302                     var u1 = $.on(document, 'DOMContentLoaded', _fn);
   -1   303                     var u2 = $.on(window, 'load', _fn);
   -1   304                     return function() {
   -1   305                         u1();
   -1   306                         u2();
   -1   307                     };
  213   308                 }
  214   309             };
  215    -1         };
  216   310 
  217    -1         return Directive;
  218    -1     });
  219    -1     /**
  220    -1      * DOM related helper functions
  221    -1      * @module muu-dom-helpers
  222    -1      */
  223    -1     _define("muu-dom-helpers", ['muu-js-helpers'], function(_) {
  224    -1         "use strict";
  225    -1 
  226    -1         var entityMap = {
  227    -1             '&': '&amp;',
  228    -1             '<': '&lt;',
  229    -1             '>': '&gt;',
  230    -1             '"': '&quot;',
  231    -1             "'": '&#39;',
  232    -1             '/': '&#x2F;'
  233    -1         };
   -1   311             /**
   -1   312              * @param {Node} desc
   -1   313              * @param {Node} root
   -1   314              * @return {boolean}
   -1   315              */
   -1   316             $.isDescendant = function(desc, root) {
   -1   317                  return !!desc && (desc === root || $.isDescendant(desc.parentNode, root));
   -1   318             };
  234   319 
  235    -1         /** @lends module:muu-dom-helpers */
  236    -1         var $ = {};
   -1   320             /**
   -1   321              * Execute a function when `element` is removed from the DOM.
   -1   322              *
   -1   323              * *Note*: The callback is not executed directly when (or even before) the
   -1   324              * element is removed but with a slight delay. So the only way to test this
   -1   325              * is to use a timeout in the test.
   -1   326              *
   -1   327              * @param {Element} element
   -1   328              * @param {Function} fn
   -1   329              * @return {function()} An unregister function
   -1   330              */
   -1   331             $.destroy = function(element, fn) {
   -1   332                 var unregister;
   -1   333 
   -1   334                 if (!!window.MutationObserver) {
   -1   335                     var observer = new MutationObserver(function() {
   -1   336                         if (!$.isDescendant(element, document)) {
   -1   337                             fn();
   -1   338                             unregister();
   -1   339                         }
   -1   340                     });
  237   341 
  238    -1         $.DELAY = 1000;
   -1   342                     observer.observe(document, {
   -1   343                          childList: true,
   -1   344                          subtree: true
   -1   345                     });
  239   346 
  240    -1         /**
  241    -1          * @param {string} string
  242    -1          * @return {string} - escaped HTML
  243    -1          */
  244    -1         $.escapeHtml = function(string) {
  245    -1             return String(string).replace(/[&<>"'\/]/g, function(s) {
  246    -1                 return entityMap[s];
  247    -1             });
  248    -1         };
   -1   347                     unregister = _.once(function() {
   -1   348                         observer.disconnect();
   -1   349                         observer = undefined;
   -1   350                     });
   -1   351                 } else {
   -1   352                     var intervalID = setInterval(function() {
   -1   353                         if (!$.isDescendant(element, document)) {
   -1   354                             fn();
   -1   355                             unregister();
   -1   356                         }
   -1   357                     }, $.DELAY);
  249   358 
  250    -1         /**
  251    -1          * Cross browser custom events.
  252    -1          *
  253    -1          * *Note*: IE does not seem to like it when you use existing event names
  254    -1          * with this.
  255    -1          *
  256    -1          * @param {string} type
  257    -1          * @param {*} detail
  258    -1          * @return {Event}
  259    -1          * @see https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Creating_and_triggering_events
  260    -1          */
  261    -1         $.createEvent = function(type, detail) {
  262    -1             if (typeof CustomEvent === 'function') {
  263    -1                 return new CustomEvent(type, {
  264    -1                     detail: detail
  265    -1                 });
  266    -1             } else {
  267    -1                 var event = document.createEvent('CustomEvent');
  268    -1                 event.initCustomEvent(type, false, true, detail);
  269    -1                 return event;
  270    -1             }
  271    -1         };
   -1   359                     unregister = function() {
   -1   360                         clearInterval(intervalID);
   -1   361                     };
   -1   362                 }
  272   363 
  273    -1         /**
  274    -1          * @param {EventTarget} element
  275    -1          * @param {string} eventName
  276    -1          * @param {Function} callback
  277    -1          * @return {function()} An unregister function
  278    -1          */
  279    -1         $.on = function(element, eventName, callback) {
  280    -1             element.addEventListener(eventName, callback, false);
  281    -1             return function() {
  282    -1                 element.removeEventListener(eventName, callback, false);
   -1   364                 return unregister;
  283   365             };
  284    -1         };
  285    -1 
  286    -1         /**
  287    -1          * @param {Function} fn
  288    -1          * @return {function()} An unregister function
  289    -1          */
  290    -1         $.ready = function(fn) {
  291    -1             var _fn = _.once(fn);
  292    -1             if (document.readyState === 'complete') {
  293    -1                 _fn();
  294    -1                 return function() {};
  295    -1             } else {
  296    -1                 var u1 = $.on(document, 'DOMContentLoaded', _fn);
  297    -1                 var u2 = $.on(window, 'load', _fn);
  298    -1                 return function() {
  299    -1                     u1();
  300    -1                     u2();
  301    -1                 };
  302    -1             }
  303    -1         };
  304   366 
  305    -1         /**
  306    -1          * @param {Node} desc
  307    -1          * @param {Node} root
  308    -1          * @return {boolean}
  309    -1          */
  310    -1         $.isDescendant = function(desc, root) {
  311    -1              return !!desc && (desc === root || $.isDescendant(desc.parentNode, root));
  312    -1         };
  313    -1 
  314    -1         /**
  315    -1          * Execute a function when `element` is removed from the DOM.
  316    -1          *
  317    -1          * *Note*: The callback is not executed directly when (or even before) the
  318    -1          * element is removed but with a slight delay. So the only way to test this
  319    -1          * is to use a timeout in the test.
  320    -1          *
  321    -1          * @param {Element} element
  322    -1          * @param {Function} fn
  323    -1          * @return {function()} An unregister function
  324    -1          */
  325    -1         $.destroy = function(element, fn) {
  326    -1             var unregister;
  327    -1 
  328    -1             if (!!window.MutationObserver) {
  329    -1                 var observer = new MutationObserver(function() {
  330    -1                     if (!$.isDescendant(element, document)) {
  331    -1                         fn();
  332    -1                         unregister();
   -1   367             /**
   -1   368              * @param {Array.<Element>} options
   -1   369              * @return {string}
   -1   370              * @suppress {missingReturn}
   -1   371              */
   -1   372             $.getRadio = function(options) {
   -1   373                 for (var i = 0; i < options.length; i++) {
   -1   374                     if (options[i].checked) {
   -1   375                         return options[i].value;
  333   376                     }
  334    -1                 });
  335    -1 
  336    -1                 observer.observe(document, {
  337    -1                      childList: true,
  338    -1                      subtree: true
  339    -1                 });
   -1   377                 }
   -1   378             };
  340   379 
  341    -1                 unregister = _.once(function() {
  342    -1                     observer.disconnect();
  343    -1                     observer = undefined;
  344    -1                 });
  345    -1             } else {
  346    -1                 var intervalID = setInterval(function() {
  347    -1                     if (!$.isDescendant(element, document)) {
  348    -1                         fn();
  349    -1                         unregister();
   -1   380             /**
   -1   381              * @param {Array.<Element>} options
   -1   382              * @param {string} value
   -1   383              */
   -1   384             $.setRadio = function(options, value) {
   -1   385                 for (var i = 0; i < options.length; i++) {
   -1   386                     if (options[i].value === value) {
   -1   387                         options[i].checked = true;
   -1   388                     } else {
   -1   389                         options[i].checked = false;
  350   390                     }
  351    -1                 }, $.DELAY);
  352    -1 
  353    -1                 unregister = function() {
  354    -1                     clearInterval(intervalID);
  355    -1                 };
  356    -1             }
  357    -1 
  358    -1             return unregister;
  359    -1         };
  360    -1 
  361    -1         /**
  362    -1          * @param {Array.<Element>} options
  363    -1          * @return {string}
  364    -1          * @suppress {missingReturn}
  365    -1          */
  366    -1         $.getRadio = function(options) {
  367    -1             for (var i = 0; i < options.length; i++) {
  368    -1                 if (options[i].checked) {
  369    -1                     return options[i].value;
  370   391                 }
  371    -1             }
  372    -1         };
   -1   392             };
  373   393 
   -1   394             return $;
   -1   395         });
  374   396         /**
  375    -1          * @param {Array.<Element>} options
  376    -1          * @param {string} value
   -1   397          * Minimal implementation of an underscore/lodash subset.
   -1   398          * @module muu-js-helpers
  377   399          */
  378    -1         $.setRadio = function(options, value) {
  379    -1             for (var i = 0; i < options.length; i++) {
  380    -1                 if (options[i].value === value) {
  381    -1                     options[i].checked = true;
  382    -1                 } else {
  383    -1                     options[i].checked = false;
  384    -1                 }
  385    -1             }
  386    -1         };
   -1   400         _define('muu-js-helpers', [], function() {
   -1   401             "use strict";
  387   402 
  388    -1         return $;
  389    -1     });
  390    -1     /**
  391    -1      * Minimal implementation of an underscore/lodash subset.
  392    -1      * @module muu-js-helpers
  393    -1      */
  394    -1     _define('muu-js-helpers', [], function() {
  395    -1         "use strict";
   -1   403             /** @lends module:muu-js-helpers */
   -1   404             var _ = {};
  396   405 
  397    -1         /** @lends module:muu-js-helpers */
  398    -1         var _ = {};
   -1   406             /**
   -1   407              * @param {Object} value
   -1   408              * @return {string}
   -1   409              */
   -1   410             var objToString = function(value) {
   -1   411                 return Object.prototype.toString.call(value);
   -1   412             };
  399   413 
  400    -1         /**
  401    -1          * @param {Object} value
  402    -1          * @return {string}
  403    -1          */
  404    -1         var objToString = function(value) {
  405    -1             return Object.prototype.toString.call(value);
  406    -1         };
   -1   414             /**
   -1   415              * @param {*} value
   -1   416              * @return {boolean}
   -1   417              */
   -1   418             _.isString = function(value) {
   -1   419                 return typeof value === 'string' || objToString(value) === '[object String]';
   -1   420             };
  407   421 
  408    -1         /**
  409    -1          * @param {*} value
  410    -1          * @return {boolean}
  411    -1          */
  412    -1         _.isString = function(value) {
  413    -1             return typeof value === 'string' || objToString(value) === '[object String]';
  414    -1         };
   -1   422             /**
   -1   423              * @function
   -1   424              * @param {*} value
   -1   425              * @return {boolean}
   -1   426              */
   -1   427             _.isArray = Array.isArray;
  415   428 
  416    -1         /**
  417    -1          * @function
  418    -1          * @param {*} value
  419    -1          * @return {boolean}
  420    -1          */
  421    -1         _.isArray = Array.isArray;
   -1   429             /**
   -1   430              * @param {*} value
   -1   431              * @return {boolean}
   -1   432              */
   -1   433             _.isFunction = function(value) {
   -1   434                 return typeof value === 'function';
   -1   435             };
  422   436 
  423    -1         /**
  424    -1          * @param {*} value
  425    -1          * @return {boolean}
  426    -1          */
  427    -1         _.isFunction = function(value) {
  428    -1             return typeof value === 'function';
  429    -1         };
   -1   437             /**
   -1   438              * @param {Function} fn
   -1   439              * @return {Function}
   -1   440              */
   -1   441             _.once = function(fn) {
   -1   442                 var result;
   -1   443                 var called = false;
  430   444 
  431    -1         /**
  432    -1          * @param {Function} fn
  433    -1          * @return {Function}
  434    -1          */
  435    -1         _.once = function(fn) {
  436    -1             var result;
  437    -1             var called = false;
  438    -1 
  439    -1             return function() {
  440    -1                 if (!called) {
  441    -1                     result = fn.apply(this, arguments);
  442    -1                     called = true;
  443    -1                 }
  444    -1                 return result;
   -1   445                 return function() {
   -1   446                     if (!called) {
   -1   447                         result = fn.apply(this, arguments);
   -1   448                         called = true;
   -1   449                     }
   -1   450                     return result;
   -1   451                 };
  445   452             };
  446    -1         };
  447    -1 
  448    -1         /**
  449    -1          * @param {Array} array
  450    -1          * @param {*} value
  451    -1          * @return {number}
  452    -1          */
  453    -1         _.indexOf = function(array, value) {
  454    -1             if ('indexOf' in array) {
  455    -1                 return array.indexOf(value);
  456    -1             }
  457   453 
  458    -1             for (var i = 0; i < array.length; i++) {
  459    -1                 if (array[i] === value) {
  460    -1                     return i;
   -1   454             /**
   -1   455              * @param {Array} array
   -1   456              * @param {*} value
   -1   457              * @return {number}
   -1   458              */
   -1   459             _.indexOf = function(array, value) {
   -1   460                 if ('indexOf' in array) {
   -1   461                     return array.indexOf(value);
  461   462                 }
  462    -1             }
  463    -1             return -1;
  464    -1         };
  465   463 
  466    -1         /**
  467    -1          * @param {Array} array
  468    -1          * @param {Function} fn
  469    -1          */
  470    -1         _.forEach = function(array, fn) {
  471    -1             if ('forEach' in array) {
  472    -1                 return array.forEach(fn);
  473    -1             }
   -1   464                 for (var i = 0; i < array.length; i++) {
   -1   465                     if (array[i] === value) {
   -1   466                         return i;
   -1   467                     }
   -1   468                 }
   -1   469                 return -1;
   -1   470             };
  474   471 
  475    -1             for (var i = 0; i < array.length; i++) {
  476    -1                 fn(array[i]);
  477    -1             }
  478    -1         };
   -1   472             /**
   -1   473              * @param {Array} array
   -1   474              * @param {Function} fn
   -1   475              */
   -1   476             _.forEach = function(array, fn) {
   -1   477                 if ('forEach' in array) {
   -1   478                     return array.forEach(fn);
   -1   479                 }
  479   480 
  480    -1         /**
  481    -1          * @param {Array} array
  482    -1          * @param {Function} fn
  483    -1          * @return {Array}
  484    -1          */
  485    -1         _.map = function(array, fn) {
  486    -1             if ('map' in array) {
  487    -1                 return array.map(fn);
  488    -1             }
   -1   481                 for (var i = 0; i < array.length; i++) {
   -1   482                     fn(array[i]);
   -1   483                 }
   -1   484             };
  489   485 
  490    -1             var results = [];
  491    -1             for (var i = 0; i < array.length; i++) {
  492    -1                 results.push(fn(array[i]));
  493    -1             }
  494    -1             return results;
  495    -1         };
   -1   486             /**
   -1   487              * @param {Array} array
   -1   488              * @param {Function} fn
   -1   489              * @return {Array}
   -1   490              */
   -1   491             _.map = function(array, fn) {
   -1   492                 if ('map' in array) {
   -1   493                     return array.map(fn);
   -1   494                 }
  496   495 
  497    -1         /**
  498    -1          * @param {Array} array
  499    -1          * @param {Function} fn
  500    -1          * @return {Array}
  501    -1          */
  502    -1         _.filter = function(array, fn) {
  503    -1             if ('filter' in array) {
  504    -1                 return array.filter(fn);
  505    -1             }
   -1   496                 var results = [];
   -1   497                 for (var i = 0; i < array.length; i++) {
   -1   498                     results.push(fn(array[i]));
   -1   499                 }
   -1   500                 return results;
   -1   501             };
  506   502 
  507    -1             var results = [];
  508    -1             for (var i = 0; i < array.length; i++) {
  509    -1                 if (fn(array[i])) {
  510    -1                     results.push(array[i]);
   -1   503             /**
   -1   504              * @param {Array} array
   -1   505              * @param {Function} fn
   -1   506              * @return {Array}
   -1   507              */
   -1   508             _.filter = function(array, fn) {
   -1   509                 if ('filter' in array) {
   -1   510                     return array.filter(fn);
  511   511                 }
  512    -1             }
  513    -1             return results;
  514    -1         };
  515   512 
  516    -1         /**
  517    -1          * @param {Array.<Array>} arrays
  518    -1          * @return {Array}
  519    -1          */
  520    -1         _.union = function(arrays) {
  521    -1             var results = [];
  522    -1             for (var i = 0; i < arrays.length; i++) {
  523    -1                 for (var j = 0; j < arrays[i].length; j++) {
  524    -1                     if (_.indexOf(results, arrays[i][j]) === -1) {
  525    -1                         results.push(arrays[i][j]);
   -1   513                 var results = [];
   -1   514                 for (var i = 0; i < array.length; i++) {
   -1   515                     if (fn(array[i])) {
   -1   516                         results.push(array[i]);
  526   517                     }
  527   518                 }
  528    -1             }
  529    -1             return results;
  530    -1         };
   -1   519                 return results;
   -1   520             };
  531   521 
  532    -1         /**
  533    -1          * @param {Array} a
  534    -1          * @param {Array} b
  535    -1          * @return {Array}
  536    -1          */
  537    -1         _.difference = function(a, b) {
  538    -1             var results = [];
  539    -1             for (var i = 0; i < a.length; i++) {
  540    -1                 if (_.indexOf(b, a[i]) === -1) {
  541    -1                     results.push(a[i]);
   -1   522             /**
   -1   523              * @param {Array.<Array>} arrays
   -1   524              * @return {Array}
   -1   525              */
   -1   526             _.union = function(arrays) {
   -1   527                 var results = [];
   -1   528                 for (var i = 0; i < arrays.length; i++) {
   -1   529                     for (var j = 0; j < arrays[i].length; j++) {
   -1   530                         if (_.indexOf(results, arrays[i][j]) === -1) {
   -1   531                             results.push(arrays[i][j]);
   -1   532                         }
   -1   533                     }
  542   534                 }
  543    -1             }
  544    -1             return results;
  545    -1         };
   -1   535                 return results;
   -1   536             };
  546   537 
  547    -1         /**
  548    -1          * @param {Array} a
  549    -1          * @return {Array}
  550    -1          */
  551    -1         _.flatten = function(a) {
  552    -1             var o = [];
  553    -1             _.forEach(a, function(item) {
  554    -1                 if (_.isArray(item)) {
  555    -1                     o = o.concat(_.flatten(item));
  556    -1                 } else {
  557    -1                     o.push(item);
   -1   538             /**
   -1   539              * @param {Array} a
   -1   540              * @param {Array} b
   -1   541              * @return {Array}
   -1   542              */
   -1   543             _.difference = function(a, b) {
   -1   544                 var results = [];
   -1   545                 for (var i = 0; i < a.length; i++) {
   -1   546                     if (_.indexOf(b, a[i]) === -1) {
   -1   547                         results.push(a[i]);
   -1   548                     }
  558   549                 }
  559    -1             });
  560    -1             return o;
  561    -1         };
  562    -1 
  563    -1         return _;
  564    -1     });
  565    -1     /**
  566    -1      * angular inspired location service.
  567    -1      * @module muu-location
  568    -1      */
  569    -1     _define('muu-location', ['muu-search'], function(q) {
  570    -1         "use strict";
   -1   550                 return results;
   -1   551             };
  571   552 
  572    -1         /** @lends module:muu-location */
  573    -1         var loc = {};
   -1   553             /**
   -1   554              * @param {Array} a
   -1   555              * @return {Array}
   -1   556              */
   -1   557             _.flatten = function(a) {
   -1   558                 var o = [];
   -1   559                 _.forEach(a, function(item) {
   -1   560                     if (_.isArray(item)) {
   -1   561                         o = o.concat(_.flatten(item));
   -1   562                     } else {
   -1   563                         o.push(item);
   -1   564                     }
   -1   565                 });
   -1   566                 return o;
   -1   567             };
  574   568 
   -1   569             return _;
   -1   570         });
  575   571         /**
  576    -1          * @return {string}
   -1   572          * angular inspired location service.
   -1   573          * @module muu-location
  577   574          */
  578    -1         loc.absUrl = function() {
  579    -1             return location.href;
  580    -1         };
   -1   575         _define('muu-location', ['muu-search'], function(q) {
   -1   576             "use strict";
  581   577 
  582    -1         /**
  583    -1          * @return {string}
  584    -1          *//**
  585    -1          * @param {string} value
  586    -1          * @param {boolean} [replace]
  587    -1          * @return {muu-location}
  588    -1          */
  589    -1         loc.url = function(value, replace) {
  590    -1             if (value === undefined) {
  591    -1                 return location.pathname + location.search + location.hash;
  592    -1             } else if (replace) {
  593    -1                 history.replaceState(null, null, value);
  594    -1             } else {
  595    -1                 history.pushState(null, null, value);
  596    -1             }
  597    -1             return loc;
  598    -1         };
   -1   578             /** @lends module:muu-location */
   -1   579             var loc = {};
  599   580 
  600    -1         /**
  601    -1          * @return {string}
  602    -1          */
  603    -1         loc.protocol = function() {
  604    -1             return location.protocol;
  605    -1         };
   -1   581             /**
   -1   582              * @return {string}
   -1   583              */
   -1   584             loc.absUrl = function() {
   -1   585                 return location.href;
   -1   586             };
  606   587 
  607    -1         /**
  608    -1          * @return {string}
  609    -1          */
  610    -1         loc.host = function() {
  611    -1             return location.host;
  612    -1         };
   -1   588             /**
   -1   589              * @return {string}
   -1   590              *//**
   -1   591              * @param {string} value
   -1   592              * @param {boolean} [replace]
   -1   593              * @return {muu-location}
   -1   594              */
   -1   595             loc.url = function(value, replace) {
   -1   596                 if (value === undefined) {
   -1   597                     return location.pathname + location.search + location.hash;
   -1   598                 } else if (replace) {
   -1   599                     history.replaceState(null, null, value);
   -1   600                 } else {
   -1   601                     history.pushState(null, null, value);
   -1   602                 }
   -1   603                 return loc;
   -1   604             };
  613   605 
  614    -1         /**
  615    -1          * @return {string}
  616    -1          */
  617    -1         loc.port = function() {
  618    -1             return location.port;
  619    -1         };
   -1   606             /**
   -1   607              * @return {string}
   -1   608              */
   -1   609             loc.protocol = function() {
   -1   610                 return location.protocol;
   -1   611             };
  620   612 
  621    -1         /**
  622    -1          * @return {string}
  623    -1          *//**
  624    -1          * @param {string} value
  625    -1          * @param {boolean} [replace]
  626    -1          * @return {muu-location}
  627    -1          */
  628    -1         loc.path = function(value, replace) {
  629    -1             if (value === undefined) {
  630    -1                 return location.pathname;
  631    -1             } else {
  632    -1                 var url = value + location.search + location.hash;
  633    -1                 loc.url(url, replace);
  634    -1                 return loc;
  635    -1             }
  636    -1         };
   -1   613             /**
   -1   614              * @return {string}
   -1   615              */
   -1   616             loc.host = function() {
   -1   617                 return location.host;
   -1   618             };
  637   619 
  638    -1         var _search = function(value, replace) {
  639    -1             if (value === undefined) {
  640    -1                 return location.search;
  641    -1             } else {
  642    -1                 if (value && value[0] !== '?') {
  643    -1                     value = '?' + value;
  644    -1                 }
  645    -1                 if (value.length === 1) {
  646    -1                     value = '';
   -1   620             /**
   -1   621              * @return {string}
   -1   622              */
   -1   623             loc.port = function() {
   -1   624                 return location.port;
   -1   625             };
   -1   626 
   -1   627             /**
   -1   628              * @return {string}
   -1   629              *//**
   -1   630              * @param {string} value
   -1   631              * @param {boolean} [replace]
   -1   632              * @return {muu-location}
   -1   633              */
   -1   634             loc.path = function(value, replace) {
   -1   635                 if (value === undefined) {
   -1   636                     return location.pathname;
   -1   637                 } else {
   -1   638                     var url = value + location.search + location.hash;
   -1   639                     loc.url(url, replace);
   -1   640                     return loc;
  647   641                 }
   -1   642             };
  648   643 
  649    -1                 var url = location.pathname + value + location.hash;
  650    -1                 loc.url(url, replace);
  651    -1                 return loc;
  652    -1             }
  653    -1         };
   -1   644             var _search = function(value, replace) {
   -1   645                 if (value === undefined) {
   -1   646                     return location.search;
   -1   647                 } else {
   -1   648                     if (value && value[0] !== '?') {
   -1   649                         value = '?' + value;
   -1   650                     }
   -1   651                     if (value.length === 1) {
   -1   652                         value = '';
   -1   653                     }
  654   654 
  655    -1         /**
  656    -1          * @return {Object}
  657    -1          *//**
  658    -1          * @param {string|object} value
  659    -1          * @return {muu-location}
  660    -1          *//**
  661    -1          * @param {string} key
  662    -1          * @param {*} value
  663    -1          * @param {boolean} [replace]
  664    -1          * @return {muu-location}
  665    -1          */
  666    -1         loc.search = function(key, value, replace) {
  667    -1             if (key !== undefined) {
  668    -1                 if (value !== undefined) {
  669    -1                     var search = q.parse(_search());
  670    -1                     search[key] = value;
  671    -1                     return _search(q.unparse(search), replace);
   -1   655                     var url = location.pathname + value + location.hash;
   -1   656                     loc.url(url, replace);
   -1   657                     return loc;
   -1   658                 }
   -1   659             };
   -1   660 
   -1   661             /**
   -1   662              * @return {Object}
   -1   663              *//**
   -1   664              * @param {string|object} value
   -1   665              * @return {muu-location}
   -1   666              *//**
   -1   667              * @param {string} key
   -1   668              * @param {*} value
   -1   669              * @param {boolean} [replace]
   -1   670              * @return {muu-location}
   -1   671              */
   -1   672             loc.search = function(key, value, replace) {
   -1   673                 if (key !== undefined) {
   -1   674                     if (value !== undefined) {
   -1   675                         var search = q.parse(_search());
   -1   676                         search[key] = value;
   -1   677                         return _search(q.unparse(search), replace);
   -1   678                     } else {
   -1   679                         return _search(q.unparse(key), replace);
   -1   680                     }
  672   681                 } else {
  673    -1                     return _search(q.unparse(key), replace);
   -1   682                     return q.parse(_search());
  674   683                 }
  675    -1             } else {
  676    -1                 return q.parse(_search());
  677    -1             }
  678    -1         };
   -1   684             };
  679   685 
  680    -1         /**
  681    -1          * @return {string}
  682    -1          *//**
  683    -1          * @param {string} value
  684    -1          * @param {boolean} [replace]
  685    -1          * @return {muu-location}
  686    -1          */
  687    -1         loc.hash = function(value, replace) {
  688    -1             if (value === undefined) {
  689    -1                 if (location.hash) {
  690    -1                     return location.hash.slice(1);
   -1   686             /**
   -1   687              * @return {string}
   -1   688              *//**
   -1   689              * @param {string} value
   -1   690              * @param {boolean} [replace]
   -1   691              * @return {muu-location}
   -1   692              */
   -1   693             loc.hash = function(value, replace) {
   -1   694                 if (value === undefined) {
   -1   695                     if (location.hash) {
   -1   696                         return location.hash.slice(1);
   -1   697                     } else {
   -1   698                         return '';
   -1   699                     }
  691   700                 } else {
  692    -1                     return '';
   -1   701                     var url = location.pathname + location.search + '#' + value;
   -1   702                     loc.url(url, replace);
   -1   703                     return loc;
   -1   704                 }
   -1   705             };
   -1   706 
   -1   707             /**
   -1   708              * @param {string} eventName
   -1   709              * @param {Function} fn
   -1   710              * @return {muu-location}
   -1   711              */
   -1   712             loc.addEventListener = function(eventName, fn) {
   -1   713                 if (eventName === 'change') {
   -1   714                     window.addEventListener('popstate', fn, false);
  693   715                 }
  694    -1             } else {
  695    -1                 var url = location.pathname + location.search + '#' + value;
  696    -1                 loc.url(url, replace);
  697   716                 return loc;
  698    -1             }
  699    -1         };
   -1   717             };
  700   718 
  701    -1         /**
  702    -1          * @param {string} eventName
  703    -1          * @param {Function} fn
  704    -1          * @return {muu-location}
  705    -1          */
  706    -1         loc.addEventListener = function(eventName, fn) {
  707    -1             if (eventName === 'change') {
  708    -1                 window.addEventListener('popstate', fn, false);
  709    -1             }
  710    -1             return loc;
  711    -1         };
   -1   719             /**
   -1   720              * @param {string} eventName
   -1   721              * @param {Function} fn
   -1   722              * @return {muu-location}
   -1   723              */
   -1   724             loc.removeEventListener = function(eventName, fn) {
   -1   725                 if (eventName === 'change') {
   -1   726                     window.removeEventListener('popstate', fn, false);
   -1   727                 }
   -1   728                 return loc;
   -1   729             };
  712   730 
  713    -1         /**
  714    -1          * @param {string} eventName
  715    -1          * @param {Function} fn
  716    -1          * @return {muu-location}
  717    -1          */
  718    -1         loc.removeEventListener = function(eventName, fn) {
  719    -1             if (eventName === 'change') {
  720    -1                 window.removeEventListener('popstate', fn, false);
  721    -1             }
  722   731             return loc;
  723    -1         };
  724    -1 
  725    -1         return loc;
  726    -1     });
  727    -1     /**
  728    -1      * Exports the {@link Registry} class.
  729    -1      * @module muu-registry
  730    -1      * @ignore
  731    -1      */
  732    -1     _define('muu-registry', ['muu-template', 'muu-directive', 'muu-js-helpers', 'muu-dom-helpers'], function(muuTemplate, Directive, _, $) {
  733    -1         "use strict";
  734    -1 
   -1   732         });
  735   733         /**
  736    -1          * @constructs Registry
  737    -1          * @param {Object} config The config object may have following properties:
  738    -1          *
  739    -1          * - **debug** - `{boolean}` - Enable debug mode. In debug mode,
  740    -1          *   directive objects are available as properties from the DOM as
  741    -1          *   `element.directive`.
  742    -1          * - **renderer** - `{function(string, Object)}` - The template renderer
  743    -1          *   to be used. Defaults to {@link module:muu-template}.
   -1   734          * Exports the {@link Registry} class.
   -1   735          * @module muu-registry
   -1   736          * @ignore
  744   737          */
  745    -1         var Registry = function(config) {
  746    -1             var self = this;
  747    -1             var directives = {};
  748    -1 
  749    -1             this.config = config || {};
  750    -1             this.renderer = self.config.renderer || muuTemplate;
   -1   738         _define('muu-registry', ['muu-template', 'muu-directive', 'muu-js-helpers', 'muu-dom-helpers'], function(muuTemplate, Directive, _, $) {
   -1   739             "use strict";
  751   740 
  752   741             /**
  753    -1              * Register a new type of {@link Directive}
   -1   742              * @constructs Registry
   -1   743              * @param {Object} config The config object may have following properties:
  754   744              *
  755    -1              * @param {string} type
  756    -1              * @param {string} template
  757    -1              * @param {function(Directive, Element): Function} link The link
  758    -1              *   function is called with an instance of {@link Directive} and a
  759    -1              *   Element when {@link Registry#link} is executed.
  760    -1              *
  761    -1              *   It is the only place where you can access a directive and
  762    -1              *   therefore the place where you define its behavior.
  763    -1              *
  764    -1              *   This typically means to make an initial call to {@link
  765    -1              *   Directive#update} and to add some event listeners. You should also
  766    -1              *   return an *unlink* function that clears all external references in
  767    -1              *   order to avoid memory leaks.
  768    -1              * @return {Registry} this
   -1   745              * - **debug** - `{boolean}` - Enable debug mode. In debug mode,
   -1   746              *   directive objects are available as properties from the DOM as
   -1   747              *   `element.directive`.
   -1   748              * - **renderer** - `{function(string, Object)}` - The template renderer
   -1   749              *   to be used. Defaults to {@link module:muu-template}.
  769   750              */
  770    -1             this.registerDirective = function(type, template, link) {
  771    -1                 directives[type] = {
  772    -1                     template: template,
  773    -1                     link: link
   -1   751             var Registry = function(config) {
   -1   752                 var self = this;
   -1   753                 var directives = {};
   -1   754 
   -1   755                 this.config = config || {};
   -1   756                 this.renderer = self.config.renderer || muuTemplate;
   -1   757 
   -1   758                 /**
   -1   759                  * Register a new type of {@link Directive}
   -1   760                  *
   -1   761                  * @param {string} type
   -1   762                  * @param {string} template
   -1   763                  * @param {function(Directive, Element): Function} link The link
   -1   764                  *   function is called with an instance of {@link Directive} and a
   -1   765                  *   Element when {@link Registry#link} is executed.
   -1   766                  *
   -1   767                  *   It is the only place where you can access a directive and
   -1   768                  *   therefore the place where you define its behavior.
   -1   769                  *
   -1   770                  *   This typically means to make an initial call to {@link
   -1   771                  *   Directive#update} and to add some event listeners. You should also
   -1   772                  *   return an *unlink* function that clears all external references in
   -1   773                  *   order to avoid memory leaks.
   -1   774                  * @return {Registry} this
   -1   775                  */
   -1   776                 this.registerDirective = function(type, template, link) {
   -1   777                     directives[type] = {
   -1   778                         template: template,
   -1   779                         link: link
   -1   780                     };
   -1   781                     return self;
  774   782                 };
  775    -1                 return self;
  776    -1             };
  777   783 
  778    -1             /**
  779    -1              * Shortcut for wrapping calls to {@link Registry} in a function.
  780    -1              *
  781    -1              * This can be esepcially helpful if that function is defined in a
  782    -1              * different module.
  783    -1              *
  784    -1              * ```.js
  785    -1              * _define('foobar', [], function() {
  786    -1              *   return function(registry) {
  787    -1              *     registry
  788    -1              *        .registerDirective('foo', '...', function() {...})
  789    -1              *        .registerDirective('bar', '...', function() {...});
  790    -1              *   };
  791    -1              * });
  792    -1              *
  793    -1              * require(['foobar'], function(foobar) {
  794    -1              *   var registry = new Registry();
  795    -1              *   registry.registerModule(foobar);
  796    -1              * });
  797    -1              * ```
  798    -1              *
  799    -1              * @param {function(Registry)} module
  800    -1              * @return {Registry} this
  801    -1              */
  802    -1             this.registerModule = function(module) {
  803    -1                 module(self);
  804    -1                 return self;
  805    -1             };
   -1   784                 /**
   -1   785                  * Shortcut for wrapping calls to {@link Registry} in a function.
   -1   786                  *
   -1   787                  * This can be esepcially helpful if that function is defined in a
   -1   788                  * different module.
   -1   789                  *
   -1   790                  * ```.js
   -1   791                  * _define('foobar', [], function() {
   -1   792                  *   return function(registry) {
   -1   793                  *     registry
   -1   794                  *        .registerDirective('foo', '...', function() {...})
   -1   795                  *        .registerDirective('bar', '...', function() {...});
   -1   796                  *   };
   -1   797                  * });
   -1   798                  *
   -1   799                  * require(['foobar'], function(foobar) {
   -1   800                  *   var registry = new Registry();
   -1   801                  *   registry.registerModule(foobar);
   -1   802                  * });
   -1   803                  * ```
   -1   804                  *
   -1   805                  * @param {function(Registry)} module
   -1   806                  * @return {Registry} this
   -1   807                  */
   -1   808                 this.registerModule = function(module) {
   -1   809                     module(self);
   -1   810                     return self;
   -1   811                 };
  806   812 
  807    -1             /**
  808    -1              * Create and initialise a {@link Directive} for `element`.
  809    -1              *
  810    -1              * @param {Element} element
  811    -1              * @param {string} type
  812    -1              * @return {Directive}
  813    -1              */
  814    -1             this.link = function(element, type) {
  815    -1                 if (type === undefined) {
  816    -1                     type = element.getAttribute('type');
  817    -1                 }
   -1   813                 /**
   -1   814                  * Create and initialise a {@link Directive} for `element`.
   -1   815                  *
   -1   816                  * @param {Element} element
   -1   817                  * @param {string} type
   -1   818                  * @return {Directive}
   -1   819                  */
   -1   820                 this.link = function(element, type) {
   -1   821                     if (type === undefined) {
   -1   822                         type = element.getAttribute('type');
   -1   823                     }
  818   824 
  819    -1                 if (!directives.hasOwnProperty(type)) {
  820    -1                     throw new Error('Unknown directive type: ' + type);
  821    -1                 }
   -1   825                     if (!directives.hasOwnProperty(type)) {
   -1   826                         throw new Error('Unknown directive type: ' + type);
   -1   827                     }
  822   828 
  823    -1                 var template = directives[type].template;
  824    -1                 var link = directives[type].link;
   -1   829                     var template = directives[type].template;
   -1   830                     var link = directives[type].link;
  825   831 
  826    -1                 var directive = new Directive(element, template, self);
  827    -1                 var unlink = link(directive, element);
  828    -1                 element.classList.add('muu-isolate');
  829    -1                 element.classList.add('muu-initialised');
   -1   832                     var directive = new Directive(element, template, self);
   -1   833                     var unlink = link(directive, element);
   -1   834                     element.classList.add('muu-isolate');
   -1   835                     element.classList.add('muu-initialised');
  830   836 
  831    -1                 if (self.config.debug) {
  832    -1                     element.directive = directive;
  833    -1                 }
   -1   837                     if (self.config.debug) {
   -1   838                         element.directive = directive;
   -1   839                     }
  834   840 
  835    -1                 if (unlink !== undefined) {
  836    -1                     $.destroy(element, unlink);
  837    -1                 }
   -1   841                     if (unlink !== undefined) {
   -1   842                         $.destroy(element, unlink);
   -1   843                     }
  838   844 
  839    -1                 return directive;
  840    -1             };
   -1   845                     return directive;
   -1   846                 };
  841   847 
  842    -1             /**
  843    -1              * Link all directives that can be found inside `root`.
  844    -1              *
  845    -1              * @param {Element} root
  846    -1              * @return {Array.<Directive>}
  847    -1              */
  848    -1             this.linkAll = function(root) {
  849    -1                 // NOTE: root may be a DOM Node or a directive
  850    -1                 var elements = _.filter(root.querySelectorAll('muu'), function(element) {
  851    -1                     return !element.classList.contains('muu-initialised');
  852    -1                 });
  853    -1                 return _.map(elements, function(element) {
  854    -1                     return self.link(element);
  855    -1                 });
   -1   848                 /**
   -1   849                  * Link all directives that can be found inside `root`.
   -1   850                  *
   -1   851                  * @param {Element} root
   -1   852                  * @return {Array.<Directive>}
   -1   853                  */
   -1   854                 this.linkAll = function(root) {
   -1   855                     // NOTE: root may be a DOM Node or a directive
   -1   856                     var elements = _.filter(root.querySelectorAll('muu'), function(element) {
   -1   857                         return !element.classList.contains('muu-initialised');
   -1   858                     });
   -1   859                     return _.map(elements, function(element) {
   -1   860                         return self.link(element);
   -1   861                     });
   -1   862                 };
  856   863             };
  857    -1         };
  858    -1 
  859    -1         return Registry;
  860    -1     });
  861    -1     _define('muu-search', ['muu-js-helpers'], function(_) {
  862    -1         "use strict";
  863   864 
  864    -1         var q = {};
   -1   865             return Registry;
   -1   866         });
   -1   867         _define('muu-search', ['muu-js-helpers'], function(_) {
   -1   868             "use strict";
  865   869 
  866    -1         q.parse = function(s) {
  867   870             var q = {};
  868   871 
  869    -1             var set = function(key, value) {
  870    -1                 if (!q.hasOwnProperty(key)) {
  871    -1                     q[key] = value;
  872    -1                 } else if (_.isArray(q[key])) {
  873    -1                     q[key].push(value);
   -1   872             q.parse = function(s) {
   -1   873                 var q = {};
   -1   874 
   -1   875                 var set = function(key, value) {
   -1   876                     if (!q.hasOwnProperty(key)) {
   -1   877                         q[key] = value;
   -1   878                     } else if (_.isArray(q[key])) {
   -1   879                         q[key].push(value);
   -1   880                     } else {
   -1   881                         q[key] = [q[key], value];
   -1   882                     }
   -1   883                 };
   -1   884 
   -1   885                 _.forEach(s.substring(1).split('&'), function(item) {
   -1   886                     var i = _.map(item.split('='), decodeURIComponent);
   -1   887                     if (i.length === 2) {
   -1   888                         set(i[0], i[1]);
   -1   889                     } else if (i[0]) {
   -1   890                         set(i[0], true);
   -1   891                     }
   -1   892                 });
   -1   893                 return q;
   -1   894             };
   -1   895 
   -1   896             var unparseItem = function(key, value) {
   -1   897                 if (value === undefined || value === null || value === false) {
   -1   898                     return [];
   -1   899                 } else if (_.isArray(value)) {
   -1   900                     return _.flatten(_.map(value, function(v) {
   -1   901                         return unparseItem(key, v);
   -1   902                     }));
   -1   903                 } else if (value === true) {
   -1   904                     return [encodeURIComponent(key)];
  874   905                 } else {
  875    -1                     q[key] = [q[key], value];
   -1   906                     return [encodeURIComponent(key) + '=' + encodeURIComponent(value)];
  876   907                 }
  877   908             };
  878   909 
  879    -1             _.forEach(s.substring(1).split('&'), function(item) {
  880    -1                 var i = _.map(item.split('='), decodeURIComponent);
  881    -1                 if (i.length === 2) {
  882    -1                     set(i[0], i[1]);
  883    -1                 } else if (i[0]) {
  884    -1                     set(i[0], true);
   -1   910             q.unparse = function(q) {
   -1   911                 if (_.isString(q)) {
   -1   912                     return q;
   -1   913                 }
   -1   914 
   -1   915                 var a = []
   -1   916                 for (var key in q) {
   -1   917                     if (q.hasOwnProperty(key)) {
   -1   918                         a = a.concat(unparseItem(key, q[key]));
   -1   919                     }
  885   920                 }
  886    -1             });
   -1   921                 if (a.length > 0) {
   -1   922                     return '?' + a.join('&');
   -1   923                 } else {
   -1   924                     return '';
   -1   925                 }
   -1   926             };
   -1   927 
  887   928             return q;
  888    -1         };
   -1   929         });
   -1   930         /**
   -1   931          * minimal mustache insipred templating
   -1   932          *
   -1   933          * ## Variables
   -1   934          *
   -1   935          * Variables are created with a `{{name}}` tag. These are always escaped.
   -1   936          *
   -1   937          * ## Loops
   -1   938          *
   -1   939          * Loops render blocks of text a number of times, depending on the value of
   -1   940          * the key in the current context.
   -1   941          *
   -1   942          * A loop begins with a pound and ends with a slash. That is, `{{#person}}`
   -1   943          * begins a "person" section while `{{/person}}` ends it.
   -1   944          *
   -1   945          * If the value is an array, the block is repeated for each item in that array.
   -1   946          * In any other case, the block is rendered with the outer scope, but only if
   -1   947          * the value is truthy.
   -1   948          *
   -1   949          * ## Inverted loops
   -1   950          *
   -1   951          * Inverted loops render blocks of test if the value of the key is falsy. They
   -1   952          * begin with a caret.
   -1   953          *
   -1   954          * ## Comments
   -1   955          *
   -1   956          * Comments begin with a bang and are ignored.
   -1   957          *
   -1   958          * ## Pairs
   -1   959          *
   -1   960          * Pairs look like JSON objects. The result is a space separated list of all
   -1   961          * keys with truthy values.
   -1   962          *
   -1   963          * ```
   -1   964          * muuTemplate('{{foo: var1, bar: var2, baz: var3}}', {
   -1   965          *   var1: true,
   -1   966          *   var2: false,
   -1   967          *   var3: true
   -1   968          * });  // 'foo baz'
   -1   969          * ```
   -1   970          *
   -1   971          * ## Special variable `this`
   -1   972          *
   -1   973          * `this` always refers to the current context. So the following expressions
   -1   974          * are equivalent:
   -1   975          *
   -1   976          * ```
   -1   977          * muuTemplate('{{#items}}{{content}}{{/items}}', {
   -1   978          *   item: [{
   -1   979          *     content: 1
   -1   980          *   }, {
   -1   981          *     content: 2
   -1   982          *   }]
   -1   983          * });
   -1   984          *
   -1   985          * muuTemplate('{{#this}}{{this}}{{/this}}', [1, 2]);
   -1   986          * ```
   -1   987          *
   -1   988          * @module muu-template
   -1   989          * @param {string} template
   -1   990          * @param {Object} data
   -1   991          * @return {string}
   -1   992          */
   -1   993         _define('muu-template', ['muu-js-helpers', 'muu-dom-helpers'], function(_, $) {
   -1   994             "use strict";
  889   995 
  890    -1         var unparseItem = function(key, value) {
  891    -1             if (value === undefined || value === null || value === false) {
  892    -1                 return [];
  893    -1             } else if (_.isArray(value)) {
  894    -1                 return _.flatten(_.map(value, function(v) {
  895    -1                     return unparseItem(key, v);
  896    -1                 }));
  897    -1             } else if (value === true) {
  898    -1                 return [encodeURIComponent(key)];
  899    -1             } else {
  900    -1                 return [encodeURIComponent(key) + '=' + encodeURIComponent(value)];
  901    -1             }
  902    -1         };
   -1   996             var openTag = '{{';
   -1   997             var closeTag = '}}';
  903   998 
  904    -1         q.unparse = function(q) {
  905    -1             if (_.isString(q)) {
  906    -1                 return q;
  907    -1             }
   -1   999             var getValue = function(key, data) {
   -1  1000                 return key === 'this' ? data : data[key];
   -1  1001             };
  908  1002 
  909    -1             var a = []
  910    -1             for (var key in q) {
  911    -1                 if (q.hasOwnProperty(key)) {
  912    -1                     a = a.concat(unparseItem(key, q[key]));
  913    -1                 }
  914    -1             }
  915    -1             if (a.length > 0) {
  916    -1                 return '?' + a.join('&');
  917    -1             } else {
  918    -1                 return '';
  919    -1             }
  920    -1         };
   -1  1003             var parseVariableTemplate = function(template) {
   -1  1004                 var content = template.slice(2, -2);
  921  1005 
  922    -1         return q;
  923    -1     });
  924    -1     /**
  925    -1      * minimal mustache insipred templating
  926    -1      *
  927    -1      * ## Variables
  928    -1      *
  929    -1      * Variables are created with a `{{name}}` tag. These are always escaped.
  930    -1      *
  931    -1      * ## Loops
  932    -1      *
  933    -1      * Loops render blocks of text a number of times, depending on the value of
  934    -1      * the key in the current context.
  935    -1      *
  936    -1      * A loop begins with a pound and ends with a slash. That is, `{{#person}}`
  937    -1      * begins a "person" section while `{{/person}}` ends it.
  938    -1      *
  939    -1      * If the value is an array, the block is repeated for each item in that array.
  940    -1      * In any other case, the block is rendered with the outer scope, but only if
  941    -1      * the value is truthy.
  942    -1      *
  943    -1      * ## Inverted loops
  944    -1      *
  945    -1      * Inverted loops render blocks of test if the value of the key is falsy. They
  946    -1      * begin with a caret.
  947    -1      *
  948    -1      * ## Comments
  949    -1      *
  950    -1      * Comments begin with a bang and are ignored.
  951    -1      *
  952    -1      * ## Pairs
  953    -1      *
  954    -1      * Pairs look like JSON objects. The result is a space separated list of all
  955    -1      * keys with truthy values.
  956    -1      *
  957    -1      * ```
  958    -1      * muuTemplate('{{foo: var1, bar: var2, baz: var3}}', {
  959    -1      *   var1: true,
  960    -1      *   var2: false,
  961    -1      *   var3: true
  962    -1      * });  // 'foo baz'
  963    -1      * ```
  964    -1      *
  965    -1      * ## Special variable `this`
  966    -1      *
  967    -1      * `this` always refers to the current context. So the following expressions
  968    -1      * are equivalent:
  969    -1      *
  970    -1      * ```
  971    -1      * muuTemplate('{{#items}}{{content}}{{/items}}', {
  972    -1      *   item: [{
  973    -1      *     content: 1
  974    -1      *   }, {
  975    -1      *     content: 2
  976    -1      *   }]
  977    -1      * });
  978    -1      *
  979    -1      * muuTemplate('{{#this}}{{this}}{{/this}}', [1, 2]);
  980    -1      * ```
  981    -1      *
  982    -1      * @module muu-template
  983    -1      * @param {string} template
  984    -1      * @param {Object} data
  985    -1      * @return {string}
  986    -1      */
  987    -1     _define('muu-template', ['muu-js-helpers', 'muu-dom-helpers'], function(_, $) {
  988    -1         "use strict";
  989    -1 
  990    -1         var openTag = '{{';
  991    -1         var closeTag = '}}';
  992    -1 
  993    -1         var getValue = function(key, data) {
  994    -1             return key === 'this' ? data : data[key];
  995    -1         };
   -1  1006                 if (template.indexOf(':') === -1) {
   -1  1007                     return function(data) {
   -1  1008                         return $.escapeHtml(getValue(content, data) || '');
   -1  1009                     };
   -1  1010                 } else {
   -1  1011                     var pairs = content.split(',').map(function(pair) {
   -1  1012                         var v = pair.split(':');
   -1  1013                         var key = v[0].trim();
   -1  1014                         var value = v.slice(1).join(':').trim();
   -1  1015                         return [key, value];
   -1  1016                     });
  996  1017 
  997    -1         var parseVariableTemplate = function(template) {
  998    -1             var content = template.slice(2, -2);
   -1  1018                     return function(data) {
   -1  1019                         var results = [];
  999  1020 
 1000    -1             if (template.indexOf(':') === -1) {
 1001    -1                 return function(data) {
 1002    -1                     return $.escapeHtml(getValue(content, data) || '');
 1003    -1                 };
 1004    -1             } else {
 1005    -1                 var pairs = content.split(',').map(function(pair) {
 1006    -1                     var v = pair.split(':');
 1007    -1                     var key = v[0].trim();
 1008    -1                     var value = v.slice(1).join(':').trim();
 1009    -1                     return [key, value];
 1010    -1                 });
   -1  1021                         for (var i = 0; i < pairs.length; i++) {
   -1  1022                             var key = pairs[i][0];
   -1  1023                             var value = pairs[i][1];
   -1  1024 
   -1  1025                             if (getValue(value, data)) {
   -1  1026                                 results.push(key);
   -1  1027                             }
   -1  1028                         }
   -1  1029 
   -1  1030                         return $.escapeHtml(results.join(' '));
   -1  1031                     };
   -1  1032                 }
   -1  1033             };
 1011  1034 
 1012    -1                 return function(data) {
 1013    -1                     var results = [];
   -1  1035             var parseLoopTemplate = function(tag, afterTag, inverted) {
   -1  1036                 var tagName = tag.slice(3, -2);
 1014  1037 
 1015    -1                     for (var i = 0; i < pairs.length; i++) {
 1016    -1                         var key = pairs[i][0];
 1017    -1                         var value = pairs[i][1];
   -1  1038                 var v = parseTemplate(afterTag, tagName);
   -1  1039                 var inner = v[0];
   -1  1040                 var afterLoop = v[1];
 1018  1041 
 1019    -1                         if (getValue(value, data)) {
 1020    -1                             results.push(key);
   -1  1042                 var render = function(data) {
   -1  1043                     if (inverted) {
   -1  1044                         if (getValue(tagName, data)) {
   -1  1045                             return '';
   -1  1046                         } else {
   -1  1047                             return inner(data);
   -1  1048                         }
   -1  1049                     } else {
   -1  1050                         if (_.isArray(getValue(tagName, data))) {
   -1  1051                             var result = '';
   -1  1052                             for (var i = 0; i < getValue(tagName, data).length; i++) {
   -1  1053                                 result += inner(getValue(tagName, data)[i]);
   -1  1054                             }
   -1  1055                             return result;
   -1  1056                         } else if (getValue(tagName, data)) {
   -1  1057                             return inner(data);
   -1  1058                         } else {
   -1  1059                             return '';
 1021  1060                         }
 1022  1061                     }
 1023    -1 
 1024    -1                     return $.escapeHtml(results.join(' '));
 1025  1062                 };
 1026    -1             }
 1027    -1         };
 1028  1063 
 1029    -1         var parseLoopTemplate = function(tag, afterTag, inverted) {
 1030    -1             var tagName = tag.slice(3, -2);
   -1  1064                 return [render, afterLoop];
   -1  1065             };
 1031  1066 
 1032    -1             var v = parseTemplate(afterTag, tagName);
 1033    -1             var inner = v[0];
 1034    -1             var afterLoop = v[1];
   -1  1067             var concat = function(a) {
   -1  1068                 var last = a.pop();
 1035  1069 
 1036    -1             var render = function(data) {
 1037    -1                 if (inverted) {
 1038    -1                     if (getValue(tagName, data)) {
 1039    -1                         return '';
   -1  1070                 if (_.isArray(last)) {
   -1  1071                     a.push(last[0]);
   -1  1072                     return [concat(a), last[1]];
   -1  1073                 } else {
   -1  1074                     a.push(last);
   -1  1075 
   -1  1076                     return function(data) {
   -1  1077                         return a.map(function(item) {
   -1  1078                             if (_.isString(item)) {
   -1  1079                                 return item;
   -1  1080                             } else if (_.isFunction(item)) {
   -1  1081                                 return item(data);
   -1  1082                             }
   -1  1083                         }).join('');
   -1  1084                     };
   -1  1085                 }
   -1  1086             };
   -1  1087 
   -1  1088             var parseTemplate = function(template, loopName) {
   -1  1089                 var openIndex = template.indexOf(openTag);
   -1  1090                 if (openIndex === -1) {
   -1  1091                     if (loopName === undefined) {
   -1  1092                         return function() {
   -1  1093                             return template;
   -1  1094                         };
 1040  1095                     } else {
 1041    -1                         return inner(data);
   -1  1096                         throw new Error('unclosed loop: ' + loopName);
 1042  1097                     }
 1043  1098                 } else {
 1044    -1                     if (_.isArray(getValue(tagName, data))) {
 1045    -1                         var result = '';
 1046    -1                         for (var i = 0; i < getValue(tagName, data).length; i++) {
 1047    -1                             result += inner(getValue(tagName, data)[i]);
   -1  1099                     var beforeTag = template.slice(0, openIndex);
   -1  1100                     var tmp = template.slice(openIndex);
   -1  1101 
   -1  1102                     var closeIndex = tmp.indexOf(closeTag) + 2;
   -1  1103                     if (closeIndex === 1) {
   -1  1104                         throw new Error('unclosed tag: ' + tmp);
   -1  1105                     }
   -1  1106                     var tag = tmp.slice(0, closeIndex);
   -1  1107                     var afterTag = tmp.slice(closeIndex);
   -1  1108 
   -1  1109                     if (tag.lastIndexOf('{{#', 0) === 0) {
   -1  1110                         var v = parseLoopTemplate(tag, afterTag);
   -1  1111                         var loop = v[0];
   -1  1112                         var after = parseTemplate(v[1], loopName);
   -1  1113                         return concat([beforeTag, loop, after]);
   -1  1114                     } else if (tag.lastIndexOf('{{^', 0) === 0) {
   -1  1115                         var v = parseLoopTemplate(tag, afterTag, true);
   -1  1116                         var loop = v[0];
   -1  1117                         var after = parseTemplate(v[1], loopName);
   -1  1118                         return concat([beforeTag, loop, after]);
   -1  1119                     } else if (tag.lastIndexOf('{{!', 0) === 0) {
   -1  1120                         var after = parseTemplate(afterTag, loopName);
   -1  1121                         return concat([beforeTag, after]);
   -1  1122                     } else if (tag.lastIndexOf('{{/', 0) === 0) {
   -1  1123                         if (tag.slice(3, -2) === loopName) {
   -1  1124                             var render = function() {
   -1  1125                                 return beforeTag;
   -1  1126                             };
   -1  1127                             return [render, afterTag];
   -1  1128                         } else {
   -1  1129                             throw new Error('unexpected closing loop: ' + tag);
 1048  1130                         }
 1049    -1                         return result;
 1050    -1                     } else if (getValue(tagName, data)) {
 1051    -1                         return inner(data);
 1052  1131                     } else {
 1053    -1                         return '';
   -1  1132                         var render = parseVariableTemplate(tag);
   -1  1133                         var after = parseTemplate(afterTag, loopName);
   -1  1134                         return concat([beforeTag, render, after]);
 1054  1135                     }
 1055  1136                 }
 1056  1137             };
 1057  1138 
 1058    -1             return [render, afterLoop];
 1059    -1         };
   -1  1139             return function(template, data) {
   -1  1140                 return parseTemplate(template)(data);
   -1  1141             };
   -1  1142         });
   -1  1143         /**
   -1  1144          * Recreate children of `source` in `target` by making only small adjustments.
   -1  1145          *
   -1  1146          * *The following section explains details about the current implementation.
   -1  1147          * These are likely to change in the future.*
   -1  1148          *
   -1  1149          * The algorithms is relatively simple. It just iterates through all top level
   -1  1150          * nodes. If a node has a different `nodeType` (e.g. text or element) or a
   -1  1151          * different `nodeName` (e.g. div or ul) it is replaced completely and the
   -1  1152          * algorithm proceeds with the node's children recursively.  Otherwise, only
   -1  1153          * the nodes's attributes are updated.
   -1  1154          *
   -1  1155          * Note that non-attribute properties (e.g. value) are lost in the first case
   -1  1156          * and preserved in the second.
   -1  1157          *
   -1  1158          * If the algorithm encounters an element with the class `muu-isolate` it does
   -1  1159          * not recurse into its children. This way, you can protect dynamically
   -1  1160          * generated content from being overwritten.
   -1  1161          *
   -1  1162          * @module muu-update-dom
   -1  1163          * @param {Element} target
   -1  1164          * @param {Element} source
   -1  1165          */
   -1  1166         _define('muu-update-dom', ['muu-js-helpers'], function(_) {
   -1  1167             "use strict";
 1060  1168 
 1061    -1         var concat = function(a) {
 1062    -1             var last = a.pop();
 1063    -1 
 1064    -1             if (_.isArray(last)) {
 1065    -1                 a.push(last[0]);
 1066    -1                 return [concat(a), last[1]];
 1067    -1             } else {
 1068    -1                 a.push(last);
 1069    -1 
 1070    -1                 return function(data) {
 1071    -1                     return a.map(function(item) {
 1072    -1                         if (_.isString(item)) {
 1073    -1                             return item;
 1074    -1                         } else if (_.isFunction(item)) {
 1075    -1                             return item(data);
 1076    -1                         }
 1077    -1                     }).join('');
 1078    -1                 };
 1079    -1             }
 1080    -1         };
   -1  1169             var updateAttributes = function(target, source) {
   -1  1170                 var targetAttrNames = _.map(target.attributes, function(item) {
   -1  1171                     return item.name;
   -1  1172                 });
   -1  1173                 var sourceAttrNames = _.map(source.attributes, function(item) {
   -1  1174                     return item.name;
   -1  1175                 });
 1081  1176 
 1082    -1         var parseTemplate = function(template, loopName) {
 1083    -1             var openIndex = template.indexOf(openTag);
 1084    -1             if (openIndex === -1) {
 1085    -1                 if (loopName === undefined) {
 1086    -1                     return function() {
 1087    -1                         return template;
 1088    -1                     };
 1089    -1                 } else {
 1090    -1                     throw new Error('unclosed loop: ' + loopName);
 1091    -1                 }
 1092    -1             } else {
 1093    -1                 var beforeTag = template.slice(0, openIndex);
 1094    -1                 var tmp = template.slice(openIndex);
   -1  1177                 _.forEach(targetAttrNames, function(name) {
   -1  1178                     // NOTE: ie8.js creates some attribute
   -1  1179                     if (!source.hasAttribute(name) && name.substr(0, 7) !== '__IE8__') {
   -1  1180                         target.removeAttribute(name);
   -1  1181                     }
   -1  1182                 });
   -1  1183                 _.forEach(sourceAttrNames, function(name) {
   -1  1184                     if (target.getAttribute(name) !== source.getAttribute(name)) {
   -1  1185                         target.setAttribute(name, source.getAttribute(name));
   -1  1186                     }
   -1  1187                 });
   -1  1188             };
   -1  1189 
   -1  1190             var updateDOM = function(target, source) {
   -1  1191                 var nt = target.childNodes.length;
   -1  1192                 var ns = source.childNodes.length;
 1095  1193 
 1096    -1                 var closeIndex = tmp.indexOf(closeTag) + 2;
 1097    -1                 if (closeIndex === 1) {
 1098    -1                     throw new Error('unclosed tag: ' + tmp);
   -1  1194                 for (var i = ns; i < nt; i++) {
   -1  1195                     target.removeChild(target.childNodes[ns]);
 1099  1196                 }
 1100    -1                 var tag = tmp.slice(0, closeIndex);
 1101    -1                 var afterTag = tmp.slice(closeIndex);
 1102    -1 
 1103    -1                 if (tag.lastIndexOf('{{#', 0) === 0) {
 1104    -1                     var v = parseLoopTemplate(tag, afterTag);
 1105    -1                     var loop = v[0];
 1106    -1                     var after = parseTemplate(v[1], loopName);
 1107    -1                     return concat([beforeTag, loop, after]);
 1108    -1                 } else if (tag.lastIndexOf('{{^', 0) === 0) {
 1109    -1                     var v = parseLoopTemplate(tag, afterTag, true);
 1110    -1                     var loop = v[0];
 1111    -1                     var after = parseTemplate(v[1], loopName);
 1112    -1                     return concat([beforeTag, loop, after]);
 1113    -1                 } else if (tag.lastIndexOf('{{!', 0) === 0) {
 1114    -1                     var after = parseTemplate(afterTag, loopName);
 1115    -1                     return concat([beforeTag, after]);
 1116    -1                 } else if (tag.lastIndexOf('{{/', 0) === 0) {
 1117    -1                     if (tag.slice(3, -2) === loopName) {
 1118    -1                         var render = function() {
 1119    -1                             return beforeTag;
 1120    -1                         };
 1121    -1                         return [render, afterTag];
   -1  1197                 for (i = nt; i < ns; i++) {
   -1  1198                     target.appendChild(source.childNodes[nt]);
   -1  1199                 }
   -1  1200                 for (i = 0; i < nt && i < ns; i++) {
   -1  1201                     var tchild = target.childNodes[i];
   -1  1202                     var schild = source.childNodes[i];
   -1  1203 
   -1  1204                     if (tchild.nodeType === schild.nodeType && tchild.nodeName === schild.nodeName && tchild.type === schild.type) {
   -1  1205                         if (tchild.nodeType === 1) {
   -1  1206                             var muuClasses = _.filter(tchild.classList, function(cls) {
   -1  1207                                 return cls.lastIndexOf('muu-', 0) === 0;
   -1  1208                             });
   -1  1209                             updateAttributes(tchild, schild);
   -1  1210                             _.forEach(muuClasses, function(cls) {
   -1  1211                                 tchild.classList.add(cls);
   -1  1212                             });
   -1  1213                         } else if (tchild.nodeType === 3) {
   -1  1214                             tchild.nodeValue = schild.nodeValue;
   -1  1215                         }
   -1  1216                         if (tchild.nodeType !== 3 && !tchild.classList.contains('muu-isolate')) {
   -1  1217                             updateDOM(tchild, schild);
   -1  1218                         }
 1122  1219                     } else {
 1123    -1                         throw new Error('unexpected closing loop: ' + tag);
   -1  1220                         tchild.parentNode.replaceChild(schild, tchild);
 1124  1221                     }
 1125    -1                 } else {
 1126    -1                     var render = parseVariableTemplate(tag);
 1127    -1                     var after = parseTemplate(afterTag, loopName);
 1128    -1                     return concat([beforeTag, render, after]);
 1129  1222                 }
 1130    -1             }
 1131    -1         };
   -1  1223             };
 1132  1224 
 1133    -1         return function(template, data) {
 1134    -1             return parseTemplate(template)(data);
 1135    -1         };
 1136    -1     });
 1137    -1     /**
 1138    -1      * Recreate children of `source` in `target` by making only small adjustments.
 1139    -1      *
 1140    -1      * *The following section explains details about the current implementation.
 1141    -1      * These are likely to change in the future.*
 1142    -1      *
 1143    -1      * The algorithms is relatively simple. It just iterates through all top level
 1144    -1      * nodes. If a node has a different `nodeType` (e.g. text or element) or a
 1145    -1      * different `nodeName` (e.g. div or ul) it is replaced completely and the
 1146    -1      * algorithm proceeds with the node's children recursively.  Otherwise, only
 1147    -1      * the nodes's attributes are updated.
 1148    -1      *
 1149    -1      * Note that non-attribute properties (e.g. value) are lost in the first case
 1150    -1      * and preserved in the second.
 1151    -1      *
 1152    -1      * If the algorithm encounters an element with the class `muu-isolate` it does
 1153    -1      * not recurse into its children. This way, you can protect dynamically
 1154    -1      * generated content from being overwritten.
 1155    -1      *
 1156    -1      * @module muu-update-dom
 1157    -1      * @param {Element} target
 1158    -1      * @param {Element} source
 1159    -1      */
 1160    -1     _define('muu-update-dom', ['muu-js-helpers'], function(_) {
 1161    -1         "use strict";
 1162    -1 
 1163    -1         var updateAttributes = function(target, source) {
 1164    -1             var targetAttrNames = _.map(target.attributes, function(item) {
 1165    -1                 return item.name;
 1166    -1             });
 1167    -1             var sourceAttrNames = _.map(source.attributes, function(item) {
 1168    -1                 return item.name;
 1169    -1             });
 1170    -1 
 1171    -1             _.forEach(targetAttrNames, function(name) {
 1172    -1                 // NOTE: ie8.js creates some attribute
 1173    -1                 if (!source.hasAttribute(name) && name.substr(0, 7) !== '__IE8__') {
 1174    -1                     target.removeAttribute(name);
 1175    -1                 }
 1176    -1             });
 1177    -1             _.forEach(sourceAttrNames, function(name) {
 1178    -1                 if (target.getAttribute(name) !== source.getAttribute(name)) {
 1179    -1                     target.setAttribute(name, source.getAttribute(name));
 1180    -1                 }
 1181    -1             });
 1182    -1         };
   -1  1225             return updateDOM;
   -1  1226         });
   -1  1227         /**
   -1  1228          * This module gives access to the following objects:
   -1  1229          *
   -1  1230          * -   `Registry` - {@link Registry}
   -1  1231          * -   `$` - {@link module:muu-dom-helpers}
   -1  1232          * -   `$location` - {@link module:muu-location}
   -1  1233          *
   -1  1234          * @module muu
   -1  1235          */
   -1  1236         _define('muu', ['muu-registry', 'muu-dom-helpers', 'muu-location'], function(Registry, $, $location) {
   -1  1237             "use strict";
 1183  1238 
 1184    -1         var updateDOM = function(target, source) {
 1185    -1             var nt = target.childNodes.length;
 1186    -1             var ns = source.childNodes.length;
   -1  1239             var module = {};
 1187  1240 
 1188    -1             for (var i = ns; i < nt; i++) {
 1189    -1                 target.removeChild(target.childNodes[ns]);
 1190    -1             }
 1191    -1             for (i = nt; i < ns; i++) {
 1192    -1                 target.appendChild(source.childNodes[nt]);
 1193    -1             }
 1194    -1             for (i = 0; i < nt && i < ns; i++) {
 1195    -1                 var tchild = target.childNodes[i];
 1196    -1                 var schild = source.childNodes[i];
 1197    -1 
 1198    -1                 if (tchild.nodeType === schild.nodeType && tchild.nodeName === schild.nodeName && tchild.type === schild.type) {
 1199    -1                     if (tchild.nodeType === 1) {
 1200    -1                         var muuClasses = _.filter(tchild.classList, function(cls) {
 1201    -1                             return cls.lastIndexOf('muu-', 0) === 0;
 1202    -1                         });
 1203    -1                         updateAttributes(tchild, schild);
 1204    -1                         _.forEach(muuClasses, function(cls) {
 1205    -1                             tchild.classList.add(cls);
 1206    -1                         });
 1207    -1                     } else if (tchild.nodeType === 3) {
 1208    -1                         tchild.nodeValue = schild.nodeValue;
 1209    -1                     }
 1210    -1                     if (tchild.nodeType !== 3 && !tchild.classList.contains('muu-isolate')) {
 1211    -1                         updateDOM(tchild, schild);
 1212    -1                     }
 1213    -1                 } else {
 1214    -1                     tchild.parentNode.replaceChild(schild, tchild);
 1215    -1                 }
 1216    -1             }
 1217    -1         };
   -1  1241             module.Registry = Registry;
   -1  1242             module.$ = $;
   -1  1243             module.$location = $location;
 1218  1244 
 1219    -1         return updateDOM;
 1220    -1     });
 1221    -1     /**
 1222    -1      * This module gives access to the following objects:
 1223    -1      *
 1224    -1      * -   `Registry` - {@link Registry}
 1225    -1      * -   `$` - {@link module:muu-dom-helpers}
 1226    -1      * -   `$location` - {@link module:muu-location}
 1227    -1      *
 1228    -1      * @module muu
 1229    -1      */
 1230    -1     _define('muu', ['muu-registry', 'muu-dom-helpers', 'muu-location'], function(Registry, $, $location) {
 1231    -1         "use strict";
 1232    -1 
 1233    -1         var module = {};
 1234    -1 
 1235    -1         module.Registry = Registry;
 1236    -1         module.$ = $;
 1237    -1         module.$location = $location;
 1238    -1 
 1239    -1         return module;
 1240    -1     });
   -1  1245             return module;
   -1  1246         });
 1241  1247 
 1242    -1         return _require('muu');
   -1  1248         return _require(name);
 1243  1249     });
 1244  1250 })(window, document, void 0);

diff --git a/dist/muu.min.js b/dist/muu.min.js

@@ -1,19 +1,19 @@
    1    -1 (function(r,p,h){(function(h){"function"===typeof define&&define.amd?define("muu",[],h):r.muu=h()})(function(){var q={},v=function(c,b){for(var e=[],a=0;a<c.length;a++)e.push(b(c[a]));return e},m=function(c,b,e){q[c]={deps:b,factory:e}},t=function(c){if(!q[c])return h;q[c].instance||(q[c].instance=q[c].factory.apply(h,v(q[c].deps,t)));return q[c].instance};m("muu-directive",["muu-dom-helpers","muu-js-helpers","muu-update-dom"],function(c,b,e){return function(a,d,g){var f=this;a.innerHTML="";var k=
    2    -1 function(d){var b="data-on"+d.type;d.target.hasAttribute(b)&&(b=d.target.getAttribute(b),d=c.createEvent("muu-"+b,d),a.dispatchEvent(d))};this.update=function(s){var u=p.createElement("div");u.innerHTML=g.renderer(d,s);e(a,u);b.forEach(["keydown","keyup","click","change","search"],function(a){b.forEach(f.querySelectorAll("[data-on"+a+"]"),function(d){d.addEventListener(a,k,!1)})});var n=c.createEvent("muu-parent-update");s=this.querySelectorAll("muu.muu-initialised");b.forEach(s,function(a){a.dispatchEvent(n)});
    3    -1 g.linkAll(f)};this.querySelectorAll=function(d){var k=a.querySelectorAll(d),c=a.querySelectorAll(".muu-isolate"),c=b.union(b.map(c,function(a){return a.querySelectorAll(d)}));return b.difference(k,c)};this.querySelector=function(a){a=f.querySelectorAll(a);if(0<a.length)return a[0]};this.getModel=function(a,d){if(a===h){var k={};b.forEach(f.querySelectorAll("[name]"),function(a){k[a.name]=f.getModel(a.name)});return k}var e=f.querySelector("[name="+a+"]");return e===h?d:"checkbox"===e.type?e.checked:
    4    -1 "radio"===e.type?(e=f.querySelectorAll("[name="+a+"]"),c.getRadio(e)||d):e.value};this.setModel=function(a,d){var b=f.querySelector("[name="+a+"]");"checkbox"===b.type?b.checked=d:"radio"===b.type?(b=f.querySelectorAll("[name="+a+"]"),c.setRadio(b,d)):b.value=d}}});m("muu-dom-helpers",["muu-js-helpers"],function(c){var b={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;","/":"&#x2F;"},e={DELAY:1E3,escapeHtml:function(a){return String(a).replace(/[&<>"'\/]/g,function(a){return b[a]})},createEvent:function(a,
    5    -1 d){if("function"===typeof CustomEvent)return new CustomEvent(a,{detail:d});var b=p.createEvent("CustomEvent");b.initCustomEvent(a,!1,!0,d);return b},on:function(a,d,b){a.addEventListener(d,b,!1);return function(){a.removeEventListener(d,b,!1)}},ready:function(a){a=c.once(a);if("complete"===p.readyState)return a(),function(){};var d=e.on(p,"DOMContentLoaded",a),b=e.on(r,"load",a);return function(){d();b()}},isDescendant:function(a,d){return!!a&&(a===d||e.isDescendant(a.parentNode,d))},destroy:function(a,
    6    -1 d){var b;if(r.MutationObserver){var f=new MutationObserver(function(){e.isDescendant(a,p)||(d(),b())});f.observe(p,{childList:!0,subtree:!0});b=c.once(function(){f.disconnect();f=h})}else{var k=setInterval(function(){e.isDescendant(a,p)||(d(),b())},e.DELAY);b=function(){clearInterval(k)}}return b},getRadio:function(a){for(var d=0;d<a.length;d++)if(a[d].checked)return a[d].value},setRadio:function(a,d){for(var b=0;b<a.length;b++)a[b].checked=a[b].value===d?!0:!1}};return e});m("muu-js-helpers",[],
    7    -1 function(){var c={isString:function(b){return"string"===typeof b||"[object String]"===Object.prototype.toString.call(b)}};c.isArray=Array.isArray;c.isFunction=function(b){return"function"===typeof b};c.once=function(b){var e,a=!1;return function(){a||(e=b.apply(this,arguments),a=!0);return e}};c.indexOf=function(b,e){if("indexOf"in b)return b.indexOf(e);for(var a=0;a<b.length;a++)if(b[a]===e)return a;return-1};c.forEach=function(b,e){if("forEach"in b)return b.forEach(e);for(var a=0;a<b.length;a++)e(b[a])};
    8    -1 c.map=function(b,e){if("map"in b)return b.map(e);for(var a=[],d=0;d<b.length;d++)a.push(e(b[d]));return a};c.filter=function(b,e){if("filter"in b)return b.filter(e);for(var a=[],d=0;d<b.length;d++)e(b[d])&&a.push(b[d]);return a};c.union=function(b){for(var e=[],a=0;a<b.length;a++)for(var d=0;d<b[a].length;d++)-1===c.indexOf(e,b[a][d])&&e.push(b[a][d]);return e};c.difference=function(b,e){for(var a=[],d=0;d<b.length;d++)-1===c.indexOf(e,b[d])&&a.push(b[d]);return a};c.flatten=function(b){var e=[];
    9    -1 c.forEach(b,function(a){c.isArray(a)?e=e.concat(c.flatten(a)):e.push(a)});return e};return c});m("muu-location",["muu-search"],function(c){var b={absUrl:function(){return location.href},url:function(a,d){if(a===h)return location.pathname+location.search+location.hash;d?history.replaceState(null,null,a):history.pushState(null,null,a);return b},protocol:function(){return location.protocol},host:function(){return location.host},port:function(){return location.port},path:function(a,d){if(a===h)return location.pathname;
   10    -1 b.url(a+location.search+location.hash,d);return b}},e=function(a,d){if(a===h)return location.search;a&&"?"!==a[0]&&(a="?"+a);1===a.length&&(a="");b.url(location.pathname+a+location.hash,d);return b};b.search=function(a,b,g){if(a!==h){if(b!==h){var f=c.parse(e());f[a]=b;return e(c.unparse(f),g)}return e(c.unparse(a),g)}return c.parse(e())};b.hash=function(a,d){if(a===h)return location.hash?location.hash.slice(1):"";b.url(location.pathname+location.search+"#"+a,d);return b};b.addEventListener=function(a,
   11    -1 d){"change"===a&&r.addEventListener("popstate",d,!1);return b};b.removeEventListener=function(a,d){"change"===a&&r.removeEventListener("popstate",d,!1);return b};return b});m("muu-registry",["muu-template","muu-directive","muu-js-helpers","muu-dom-helpers"],function(c,b,e,a){return function(d){var g=this,f={};this.config=d||{};this.renderer=g.config.renderer||c;this.registerDirective=function(a,b,d){f[a]={template:b,link:d};return g};this.registerModule=function(a){a(g);return g};this.link=function(d,
   12    -1 e){e===h&&(e=d.getAttribute("type"));if(!f.hasOwnProperty(e))throw Error("Unknown directive type: "+e);var c=f[e].link,n=new b(d,f[e].template,g),c=c(n,d);d.classList.add("muu-isolate");d.classList.add("muu-initialised");g.config.debug&&(d.directive=n);c!==h&&a.destroy(d,c);return n};this.linkAll=function(a){a=e.filter(a.querySelectorAll("muu"),function(a){return!a.classList.contains("muu-initialised")});return e.map(a,function(a){return g.link(a)})}}});m("muu-search",["muu-js-helpers"],function(c){var b=
   13    -1 {parse:function(a){var d={},b=function(a,b){d.hasOwnProperty(a)?c.isArray(d[a])?d[a].push(b):d[a]=[d[a],b]:d[a]=b};c.forEach(a.substring(1).split("&"),function(a){a=c.map(a.split("="),decodeURIComponent);2===a.length?b(a[0],a[1]):a[0]&&b(a[0],!0)});return d}},e=function(a,b){return b===h||null===b||!1===b?[]:c.isArray(b)?c.flatten(c.map(b,function(b){return e(a,b)})):!0===b?[encodeURIComponent(a)]:[encodeURIComponent(a)+"="+encodeURIComponent(b)]};b.unparse=function(a){if(c.isString(a))return a;var b=
   14    -1 [],g;for(g in a)a.hasOwnProperty(g)&&(b=b.concat(e(g,a[g])));return 0<b.length?"?"+b.join("&"):""};return b});m("muu-template",["muu-js-helpers","muu-dom-helpers"],function(c,b){var e=function(a,b){return"this"===a?b:b[a]},a=function(a){var d=a.slice(2,-2);if(-1===a.indexOf(":"))return function(a){return b.escapeHtml(e(d,a)||"")};var c=d.split(",").map(function(a){var b=a.split(":");a=b[0].trim();b=b.slice(1).join(":").trim();return[a,b]});return function(a){for(var d=[],k=0;k<c.length;k++){var f=
   15    -1 c[k][0];e(c[k][1],a)&&d.push(f)}return b.escapeHtml(d.join(" "))}},d=function(a,b,d){var g=a.slice(3,-2);a=f(b,g);var l=a[0];return[function(a){if(d)return e(g,a)?"":l(a);if(c.isArray(e(g,a))){for(var b="",k=0;k<e(g,a).length;k++)b+=l(e(g,a)[k]);return b}return e(g,a)?l(a):""},a[1]]},g=function(a){var b=a.pop();if(c.isArray(b))return a.push(b[0]),[g(a),b[1]];a.push(b);return function(b){return a.map(function(a){if(c.isString(a))return a;if(c.isFunction(a))return a(b)}).join("")}},f=function(b,e){var c=
   16    -1 b.indexOf("{{");if(-1===c){if(e===h)return function(){return b};throw Error("unclosed loop: "+e);}var n=b.slice(0,c),l=b.slice(c),m=l.indexOf("}}")+2;if(1===m)throw Error("unclosed tag: "+l);c=l.slice(0,m);l=l.slice(m);if(0===c.lastIndexOf("{{#",0))return l=d(c,l),c=l[0],l=f(l[1],e),g([n,c,l]);if(0===c.lastIndexOf("{{^",0))return l=d(c,l,!0),c=l[0],l=f(l[1],e),g([n,c,l]);if(0===c.lastIndexOf("{{!",0))return l=f(l,e),g([n,l]);if(0===c.lastIndexOf("{{/",0)){if(c.slice(3,-2)===e)return[function(){return n},
   17    -1 l];throw Error("unexpected closing loop: "+c);}c=a(c);l=f(l,e);return g([n,c,l])};return function(a,b){return f(a)(b)}});m("muu-update-dom",["muu-js-helpers"],function(c){var b=function(a,b){var e=c.map(a.attributes,function(a){return a.name}),f=c.map(b.attributes,function(a){return a.name});c.forEach(e,function(c){b.hasAttribute(c)||"__IE8__"===c.substr(0,7)||a.removeAttribute(c)});c.forEach(f,function(c){a.getAttribute(c)!==b.getAttribute(c)&&a.setAttribute(c,b.getAttribute(c))})},e=function(a,
   18    -1 d){for(var g=a.childNodes.length,f=d.childNodes.length,k=f;k<g;k++)a.removeChild(a.childNodes[f]);for(k=g;k<f;k++)a.appendChild(d.childNodes[g]);for(k=0;k<g&&k<f;k++){var h=a.childNodes[k],m=d.childNodes[k];if(h.nodeType===m.nodeType&&h.nodeName===m.nodeName&&h.type===m.type){if(1===h.nodeType){var n=c.filter(h.classList,function(a){return 0===a.lastIndexOf("muu-",0)});b(h,m);c.forEach(n,function(a){h.classList.add(a)})}else 3===h.nodeType&&(h.nodeValue=m.nodeValue);3===h.nodeType||h.classList.contains("muu-isolate")||
   -1     1 (function(r,q,h){(function(h){"function"===typeof define&&define.amd?define("muu",[],h):r.muu=h(r._)})(function(m){var p={};p["muu-js-helpers"]={instance:m};var v=function(c,b){for(var e=[],a=0;a<c.length;a++)e.push(b(c[a]));return e};m=function(c,b,e){p[c]={deps:b,factory:e}};var t=function(c){if(!p[c])return h;p[c].instance||(p[c].instance=p[c].factory.apply(h,v(p[c].deps,t)));return p[c].instance};m("muu-directive",["muu-dom-helpers","muu-js-helpers","muu-update-dom"],function(c,b,e){return function(a,
   -1     2 d,g){var f=this;a.innerHTML="";var k=function(d){var b="data-on"+d.type;d.target.hasAttribute(b)&&(b=d.target.getAttribute(b),d=c.createEvent("muu-"+b,d),a.dispatchEvent(d))};this.update=function(s){var u=q.createElement("div");u.innerHTML=g.renderer(d,s);e(a,u);b.forEach(["keydown","keyup","click","change","search"],function(a){b.forEach(f.querySelectorAll("[data-on"+a+"]"),function(d){d.addEventListener(a,k,!1)})});var n=c.createEvent("muu-parent-update");s=this.querySelectorAll("muu.muu-initialised");
   -1     3 b.forEach(s,function(a){a.dispatchEvent(n)});g.linkAll(f)};this.querySelectorAll=function(d){var k=a.querySelectorAll(d),c=a.querySelectorAll(".muu-isolate"),c=b.union(b.map(c,function(a){return a.querySelectorAll(d)}));return b.difference(k,c)};this.querySelector=function(a){a=f.querySelectorAll(a);if(0<a.length)return a[0]};this.getModel=function(a,d){if(a===h){var k={};b.forEach(f.querySelectorAll("[name]"),function(a){k[a.name]=f.getModel(a.name)});return k}var e=f.querySelector("[name="+a+"]");
   -1     4 return e===h?d:"checkbox"===e.type?e.checked:"radio"===e.type?(e=f.querySelectorAll("[name="+a+"]"),c.getRadio(e)||d):e.value};this.setModel=function(a,d){var b=f.querySelector("[name="+a+"]");"checkbox"===b.type?b.checked=d:"radio"===b.type?(b=f.querySelectorAll("[name="+a+"]"),c.setRadio(b,d)):b.value=d}}});m("muu-dom-helpers",["muu-js-helpers"],function(c){var b={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;","/":"&#x2F;"},e={DELAY:1E3,escapeHtml:function(a){return String(a).replace(/[&<>"'\/]/g,
   -1     5 function(a){return b[a]})},createEvent:function(a,d){if("function"===typeof CustomEvent)return new CustomEvent(a,{detail:d});var b=q.createEvent("CustomEvent");b.initCustomEvent(a,!1,!0,d);return b},on:function(a,d,b){a.addEventListener(d,b,!1);return function(){a.removeEventListener(d,b,!1)}},ready:function(a){a=c.once(a);if("complete"===q.readyState)return a(),function(){};var d=e.on(q,"DOMContentLoaded",a),b=e.on(r,"load",a);return function(){d();b()}},isDescendant:function(a,d){return!!a&&(a===
   -1     6 d||e.isDescendant(a.parentNode,d))},destroy:function(a,d){var b;if(r.MutationObserver){var f=new MutationObserver(function(){e.isDescendant(a,q)||(d(),b())});f.observe(q,{childList:!0,subtree:!0});b=c.once(function(){f.disconnect();f=h})}else{var k=setInterval(function(){e.isDescendant(a,q)||(d(),b())},e.DELAY);b=function(){clearInterval(k)}}return b},getRadio:function(a){for(var d=0;d<a.length;d++)if(a[d].checked)return a[d].value},setRadio:function(a,d){for(var b=0;b<a.length;b++)a[b].checked=a[b].value===
   -1     7 d?!0:!1}};return e});m("muu-js-helpers",[],function(){var c={isString:function(b){return"string"===typeof b||"[object String]"===Object.prototype.toString.call(b)}};c.isArray=Array.isArray;c.isFunction=function(b){return"function"===typeof b};c.once=function(b){var e,a=!1;return function(){a||(e=b.apply(this,arguments),a=!0);return e}};c.indexOf=function(b,e){if("indexOf"in b)return b.indexOf(e);for(var a=0;a<b.length;a++)if(b[a]===e)return a;return-1};c.forEach=function(b,e){if("forEach"in b)return b.forEach(e);
   -1     8 for(var a=0;a<b.length;a++)e(b[a])};c.map=function(b,e){if("map"in b)return b.map(e);for(var a=[],d=0;d<b.length;d++)a.push(e(b[d]));return a};c.filter=function(b,e){if("filter"in b)return b.filter(e);for(var a=[],d=0;d<b.length;d++)e(b[d])&&a.push(b[d]);return a};c.union=function(b){for(var e=[],a=0;a<b.length;a++)for(var d=0;d<b[a].length;d++)-1===c.indexOf(e,b[a][d])&&e.push(b[a][d]);return e};c.difference=function(b,e){for(var a=[],d=0;d<b.length;d++)-1===c.indexOf(e,b[d])&&a.push(b[d]);return a};
   -1     9 c.flatten=function(b){var e=[];c.forEach(b,function(a){c.isArray(a)?e=e.concat(c.flatten(a)):e.push(a)});return e};return c});m("muu-location",["muu-search"],function(c){var b={absUrl:function(){return location.href},url:function(a,d){if(a===h)return location.pathname+location.search+location.hash;d?history.replaceState(null,null,a):history.pushState(null,null,a);return b},protocol:function(){return location.protocol},host:function(){return location.host},port:function(){return location.port},path:function(a,
   -1    10 d){if(a===h)return location.pathname;b.url(a+location.search+location.hash,d);return b}},e=function(a,d){if(a===h)return location.search;a&&"?"!==a[0]&&(a="?"+a);1===a.length&&(a="");b.url(location.pathname+a+location.hash,d);return b};b.search=function(a,b,g){if(a!==h){if(b!==h){var f=c.parse(e());f[a]=b;return e(c.unparse(f),g)}return e(c.unparse(a),g)}return c.parse(e())};b.hash=function(a,d){if(a===h)return location.hash?location.hash.slice(1):"";b.url(location.pathname+location.search+"#"+a,
   -1    11 d);return b};b.addEventListener=function(a,d){"change"===a&&r.addEventListener("popstate",d,!1);return b};b.removeEventListener=function(a,d){"change"===a&&r.removeEventListener("popstate",d,!1);return b};return b});m("muu-registry",["muu-template","muu-directive","muu-js-helpers","muu-dom-helpers"],function(c,b,e,a){return function(d){var g=this,f={};this.config=d||{};this.renderer=g.config.renderer||c;this.registerDirective=function(a,b,d){f[a]={template:b,link:d};return g};this.registerModule=
   -1    12 function(a){a(g);return g};this.link=function(d,e){e===h&&(e=d.getAttribute("type"));if(!f.hasOwnProperty(e))throw Error("Unknown directive type: "+e);var c=f[e].link,n=new b(d,f[e].template,g),c=c(n,d);d.classList.add("muu-isolate");d.classList.add("muu-initialised");g.config.debug&&(d.directive=n);c!==h&&a.destroy(d,c);return n};this.linkAll=function(a){a=e.filter(a.querySelectorAll("muu"),function(a){return!a.classList.contains("muu-initialised")});return e.map(a,function(a){return g.link(a)})}}});
   -1    13 m("muu-search",["muu-js-helpers"],function(c){var b={parse:function(a){var d={},b=function(a,b){d.hasOwnProperty(a)?c.isArray(d[a])?d[a].push(b):d[a]=[d[a],b]:d[a]=b};c.forEach(a.substring(1).split("&"),function(a){a=c.map(a.split("="),decodeURIComponent);2===a.length?b(a[0],a[1]):a[0]&&b(a[0],!0)});return d}},e=function(a,b){return b===h||null===b||!1===b?[]:c.isArray(b)?c.flatten(c.map(b,function(b){return e(a,b)})):!0===b?[encodeURIComponent(a)]:[encodeURIComponent(a)+"="+encodeURIComponent(b)]};
   -1    14 b.unparse=function(a){if(c.isString(a))return a;var b=[],g;for(g in a)a.hasOwnProperty(g)&&(b=b.concat(e(g,a[g])));return 0<b.length?"?"+b.join("&"):""};return b});m("muu-template",["muu-js-helpers","muu-dom-helpers"],function(c,b){var e=function(a,b){return"this"===a?b:b[a]},a=function(a){var d=a.slice(2,-2);if(-1===a.indexOf(":"))return function(a){return b.escapeHtml(e(d,a)||"")};var c=d.split(",").map(function(a){var b=a.split(":");a=b[0].trim();b=b.slice(1).join(":").trim();return[a,b]});return function(a){for(var d=
   -1    15 [],k=0;k<c.length;k++){var f=c[k][0];e(c[k][1],a)&&d.push(f)}return b.escapeHtml(d.join(" "))}},d=function(a,b,d){var g=a.slice(3,-2);a=f(b,g);var l=a[0];return[function(a){if(d)return e(g,a)?"":l(a);if(c.isArray(e(g,a))){for(var b="",k=0;k<e(g,a).length;k++)b+=l(e(g,a)[k]);return b}return e(g,a)?l(a):""},a[1]]},g=function(a){var b=a.pop();if(c.isArray(b))return a.push(b[0]),[g(a),b[1]];a.push(b);return function(b){return a.map(function(a){if(c.isString(a))return a;if(c.isFunction(a))return a(b)}).join("")}},
   -1    16 f=function(b,e){var c=b.indexOf("{{");if(-1===c){if(e===h)return function(){return b};throw Error("unclosed loop: "+e);}var n=b.slice(0,c),l=b.slice(c),m=l.indexOf("}}")+2;if(1===m)throw Error("unclosed tag: "+l);c=l.slice(0,m);l=l.slice(m);if(0===c.lastIndexOf("{{#",0))return l=d(c,l),c=l[0],l=f(l[1],e),g([n,c,l]);if(0===c.lastIndexOf("{{^",0))return l=d(c,l,!0),c=l[0],l=f(l[1],e),g([n,c,l]);if(0===c.lastIndexOf("{{!",0))return l=f(l,e),g([n,l]);if(0===c.lastIndexOf("{{/",0)){if(c.slice(3,-2)===
   -1    17 e)return[function(){return n},l];throw Error("unexpected closing loop: "+c);}c=a(c);l=f(l,e);return g([n,c,l])};return function(a,b){return f(a)(b)}});m("muu-update-dom",["muu-js-helpers"],function(c){var b=function(a,b){var e=c.map(a.attributes,function(a){return a.name}),f=c.map(b.attributes,function(a){return a.name});c.forEach(e,function(c){b.hasAttribute(c)||"__IE8__"===c.substr(0,7)||a.removeAttribute(c)});c.forEach(f,function(c){a.getAttribute(c)!==b.getAttribute(c)&&a.setAttribute(c,b.getAttribute(c))})},
   -1    18 e=function(a,d){for(var g=a.childNodes.length,f=d.childNodes.length,k=f;k<g;k++)a.removeChild(a.childNodes[f]);for(k=g;k<f;k++)a.appendChild(d.childNodes[g]);for(k=0;k<g&&k<f;k++){var h=a.childNodes[k],m=d.childNodes[k];if(h.nodeType===m.nodeType&&h.nodeName===m.nodeName&&h.type===m.type){if(1===h.nodeType){var n=c.filter(h.classList,function(a){return 0===a.lastIndexOf("muu-",0)});b(h,m);c.forEach(n,function(a){h.classList.add(a)})}else 3===h.nodeType&&(h.nodeValue=m.nodeValue);3===h.nodeType||h.classList.contains("muu-isolate")||
   19    19 e(h,m)}else h.parentNode.replaceChild(m,h)}};return e});m("muu",["muu-registry","muu-dom-helpers","muu-location"],function(c,b,e){var a={};a.Registry=c;a.$=b;a.$location=e;return a});return t("muu")})})(window,document,void 0);