riot_js-rails 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,32 +1,206 @@
1
- var parsers = {
1
+ /**
2
+ * The compiler.parsers object holds the compiler's predefined parsers
3
+ * @module
4
+ */
5
+
6
+ var path = require('path') // for sass
7
+
8
+ // dummy function for the none and javascript parsers
9
+ function _none (src) { return src }
10
+
11
+ /** Cache of required modules */
12
+ var _mods = {
13
+ none: _none,
14
+ javascript: _none
15
+ }
16
+
17
+ /**
18
+ * Returns the module name for the given parser's name.
19
+ *
20
+ * @param {string} name - one of the `html`, `css`, or `js` parsers.
21
+ * @returns {string} The module name for using with `require()`
22
+ * @static
23
+ */
24
+ function _modname (name) {
25
+ switch (name) {
26
+ case 'es6':
27
+ return 'babel'
28
+ case 'babel':
29
+ return 'babel-core'
30
+ case 'javascript':
31
+ return 'none'
32
+ case 'coffee':
33
+ case 'coffeescript':
34
+ return 'coffee-script'
35
+ case 'scss':
36
+ case 'sass':
37
+ return 'node-sass'
38
+ case 'typescript':
39
+ return 'typescript-simple'
40
+ default:
41
+ return name
42
+ }
43
+ }
44
+
45
+ /**
46
+ * Loads a parser instance via `require`, without generating error.
47
+ *
48
+ * @param {string} name - one of the `html`, `css`, or `js` parsers.
49
+ * @param {string} [req=name] - name for `require()`
50
+ * @returns {function} parser function, or null if error
51
+ */
52
+ function _try (name, req) {
53
+
54
+ function fn (r) {
55
+ try { return require(r) } catch (_) {/**/}
56
+ return null
57
+ }
58
+
59
+ var p = _mods[name] = fn(req || _modname(name))
60
+
61
+ // istanbul ignore next: babel-core v5.8.x is not loaded by CI
62
+ if (!p && name === 'es6') {
63
+ p = _mods[name] = fn('babel-core')
64
+ }
65
+ return p
66
+ }
67
+
68
+ /**
69
+ * Returns a parser instance by its name, require the module if necessary.
70
+ * Public through the `parsers._req` function.
71
+ *
72
+ * @param {string} name - The parser's name, as registered in the parsers object
73
+ * @param {string} [req] - To be used by `_try` with `require`
74
+ * @returns {function} The parser instance, null if the parser is not found
75
+ * @static
76
+ */
77
+ function _req (name, req) {
78
+ return name in _mods ? _mods[name] : _try(name, req)
79
+ }
80
+
81
+ /**
82
+ * Merge the properties of the first object with the properties of the second.
83
+ *
84
+ * @param {object} target - Target object
85
+ * @param {object} source - Source of the extra properties
86
+ * @returns {object} Target object containing the new properties
87
+ */
88
+ function extend (target, source) {
89
+ if (source) {
90
+ for (var prop in source) {
91
+ /* istanbul ignore next */
92
+ if (source.hasOwnProperty(prop)) {
93
+ target[prop] = source[prop]
94
+ }
95
+ }
96
+ }
97
+ return target
98
+ }
99
+
100
+ module.exports = {
101
+ /**
102
+ * The HTML parsers.
103
+ * @prop {function} jade - http://jade-lang.com
104
+ */
2
105
  html: {
3
- jade: function(html) {
4
- return require('jade').render(html, {pretty: true, doctype: 'html'})
106
+ jade: function (html, opts, url) {
107
+ opts = extend({
108
+ pretty: true,
109
+ filename: url,
110
+ doctype: 'html'
111
+ }, opts)
112
+ return _req('jade').render(html, opts)
113
+ }
114
+ },
115
+ /**
116
+ * Style parsers. In browsers, only less is supported.
117
+ * @prop {function} sass - http://sass-lang.com
118
+ * @prop {function} scss - http://sass-lang.com
119
+ * @prop {function} less - http://lesscss.org
120
+ * @prop {function} stylus - http://stylus-lang.com
121
+ */
122
+ css: {
123
+ sass: function (tag, css, opts, url) {
124
+ opts = extend({
125
+ data: css,
126
+ includePaths: [path.dirname(url)],
127
+ indentedSyntax: true,
128
+ omitSourceMapUrl: true,
129
+ outputStyle: 'compact'
130
+ }, opts)
131
+ return _req('sass').renderSync(opts).css + ''
132
+ },
133
+ scss: function (tag, css, opts, url) {
134
+ opts = extend({
135
+ data: css,
136
+ includePaths: [path.dirname(url)],
137
+ indentedSyntax: false,
138
+ omitSourceMapUrl: true,
139
+ outputStyle: 'compact'
140
+ }, opts)
141
+ return _req('scss').renderSync(opts).css + ''
142
+ },
143
+ less: function (tag, css, opts, url) {
144
+ var ret
145
+
146
+ opts = extend({
147
+ sync: true,
148
+ syncImport: true,
149
+ filename: url
150
+ }, opts)
151
+ _req('less').render(css, opts, function (err, result) {
152
+ /* istanbul ignore next */
153
+ if (err) throw err
154
+ ret = result.css
155
+ })
156
+ return ret
157
+ },
158
+ stylus: function (tag, css, opts, url) {
159
+ var
160
+ stylus = _req('stylus'),
161
+ nib = _req('nib') // optional nib support
162
+
163
+ opts = extend({ filename: url }, opts)
164
+ /* istanbul ignore next: can't run both */
165
+ return nib
166
+ ? stylus(css, opts).use(nib()).import('nib').render() : stylus.render(css, opts)
5
167
  }
6
168
  },
7
- css: {},
169
+ /**
170
+ * The JavaScript parsers.
171
+ * @prop {function} es6 - https://babeljs.io - babel or babel-core up to v5.8
172
+ * @prop {function} babel - https://babeljs.io - for v6.x or later
173
+ * @prop {function} coffee - http://coffeescript.org
174
+ * @prop {function} livescript - http://livescript.net
175
+ * @prop {function} typescript - http://www.typescriptlang.org
176
+ */
8
177
  js: {
9
- none: function(js) {
10
- return js
178
+ es6: function (js, opts) {
179
+ opts = extend({
180
+ blacklist: ['useStrict', 'strict', 'react'],
181
+ sourceMaps: false,
182
+ comments: false
183
+ }, opts)
184
+ return _req('es6').transform(js, opts).code
11
185
  },
12
- livescript: function(js) {
13
- return require('livescript').compile(js, { bare: true, header: false })
186
+ babel: function (js, opts, url) {
187
+ return _req('babel').transform(js, extend({ filename: url }, opts)).code
14
188
  },
15
- typescript: function(js) {
16
- return require('typescript-simple')(js)
189
+ coffee: function (js, opts) {
190
+ return _req('coffee').compile(js, extend({ bare: true }, opts))
17
191
  },
18
- es6: function(js) {
19
- return require('babel').transform(js, { blacklist: ['useStrict'] }).code
192
+ livescript: function (js, opts) {
193
+ return _req('livescript').compile(js, extend({ bare: true, header: false }, opts))
20
194
  },
21
- coffee: function(js) {
22
- return require('coffee-script').compile(js, { bare: true })
23
- }
24
- }
195
+ typescript: function (js, opts) {
196
+ return _req('typescript')(js, opts)
197
+ },
198
+ none: _none, javascript: _none
199
+ },
200
+ _modname: _modname,
201
+ _req: _req
25
202
  }
26
203
 
27
- // fix 913
28
- parsers.js.javascript = parsers.js.none
29
- // 4 the nostalgics
30
- parsers.js.coffeescript = parsers.js.coffee
204
+ exports = module.exports
205
+ exports.js.coffeescript = exports.js.coffee // 4 the nostalgics
31
206
 
32
- module.exports = parsers
@@ -1,13 +1,21 @@
1
- /* Riot v2.2.4, @license MIT, (c) 2015 Muut Inc. + contributors */
1
+ /* Riot v2.3.15, @license MIT, (c) 2015 Muut Inc. + contributors */
2
2
 
3
3
  ;(function(window, undefined) {
4
4
  'use strict';
5
- var riot = { version: 'v2.2.4', settings: {} },
6
- //// be aware, internal usage
5
+ var riot = { version: 'v2.3.15', settings: {} },
6
+ // be aware, internal usage
7
+ // ATTENTION: prefix the global dynamic variables with `__`
7
8
 
8
9
  // counter to give a unique id to all the Tag instances
9
10
  __uid = 0,
10
-
11
+ // tags instances cache
12
+ __virtualDom = [],
13
+ // tags implementation cache
14
+ __tagImpl = {},
15
+
16
+ /**
17
+ * Const
18
+ */
11
19
  // riot specific prefixes
12
20
  RIOT_PREFIX = 'riot-',
13
21
  RIOT_TAG = RIOT_PREFIX + 'tag',
@@ -18,470 +26,984 @@ var riot = { version: 'v2.2.4', settings: {} },
18
26
  T_UNDEF = 'undefined',
19
27
  T_FUNCTION = 'function',
20
28
  // special native tags that cannot be treated like the others
21
- SPECIAL_TAGS_REGEX = /^(?:opt(ion|group)|tbody|col|t[rhd])$/,
22
- RESERVED_WORDS_BLACKLIST = ['_item', '_id', 'update', 'root', 'mount', 'unmount', 'mixin', 'isMounted', 'isLoop', 'tags', 'parent', 'opts', 'trigger', 'on', 'off', 'one'],
29
+ SPECIAL_TAGS_REGEX = /^(?:t(?:body|head|foot|[rhd])|caption|col(?:group)?|opt(?:ion|group))$/,
30
+ RESERVED_WORDS_BLACKLIST = ['_item', '_id', '_parent', 'update', 'root', 'mount', 'unmount', 'mixin', 'isMounted', 'isLoop', 'tags', 'parent', 'opts', 'trigger', 'on', 'off', 'one'],
23
31
 
24
32
  // version# for IE 8-11, 0 for others
25
- IE_VERSION = (window && window.document || {}).documentMode | 0,
26
-
27
- // Array.isArray for IE8 is in the polyfills
28
- isArray = Array.isArray
29
-
33
+ IE_VERSION = (window && window.document || {}).documentMode | 0
34
+ /* istanbul ignore next */
30
35
  riot.observable = function(el) {
31
36
 
37
+ /**
38
+ * Extend the original object or create a new empty one
39
+ * @type { Object }
40
+ */
41
+
32
42
  el = el || {}
33
43
 
44
+ /**
45
+ * Private variables and methods
46
+ */
34
47
  var callbacks = {},
35
- _id = 0
36
-
37
- el.on = function(events, fn) {
38
- if (isFunction(fn)) {
39
- if (typeof fn.id === T_UNDEF) fn._id = _id++
40
-
41
- events.replace(/\S+/g, function(name, pos) {
42
- (callbacks[name] = callbacks[name] || []).push(fn)
43
- fn.typed = pos > 0
48
+ slice = Array.prototype.slice,
49
+ onEachEvent = function(e, fn) { e.replace(/\S+/g, fn) },
50
+ defineProperty = function (key, value) {
51
+ Object.defineProperty(el, key, {
52
+ value: value,
53
+ enumerable: false,
54
+ writable: false,
55
+ configurable: false
44
56
  })
45
57
  }
58
+
59
+ /**
60
+ * Listen to the given space separated list of `events` and execute the `callback` each time an event is triggered.
61
+ * @param { String } events - events ids
62
+ * @param { Function } fn - callback function
63
+ * @returns { Object } el
64
+ */
65
+ defineProperty('on', function(events, fn) {
66
+ if (typeof fn != 'function') return el
67
+
68
+ onEachEvent(events, function(name, pos) {
69
+ (callbacks[name] = callbacks[name] || []).push(fn)
70
+ fn.typed = pos > 0
71
+ })
72
+
46
73
  return el
47
- }
74
+ })
48
75
 
49
- el.off = function(events, fn) {
50
- if (events == '*') callbacks = {}
76
+ /**
77
+ * Removes the given space separated list of `events` listeners
78
+ * @param { String } events - events ids
79
+ * @param { Function } fn - callback function
80
+ * @returns { Object } el
81
+ */
82
+ defineProperty('off', function(events, fn) {
83
+ if (events == '*' && !fn) callbacks = {}
51
84
  else {
52
- events.replace(/\S+/g, function(name) {
85
+ onEachEvent(events, function(name) {
53
86
  if (fn) {
54
87
  var arr = callbacks[name]
55
- for (var i = 0, cb; (cb = arr && arr[i]); ++i) {
56
- if (cb._id == fn._id) arr.splice(i--, 1)
88
+ for (var i = 0, cb; cb = arr && arr[i]; ++i) {
89
+ if (cb == fn) arr.splice(i--, 1)
57
90
  }
58
- } else {
59
- callbacks[name] = []
60
- }
91
+ } else delete callbacks[name]
61
92
  })
62
93
  }
63
94
  return el
64
- }
95
+ })
65
96
 
66
- // only single event supported
67
- el.one = function(name, fn) {
97
+ /**
98
+ * Listen to the given space separated list of `events` and execute the `callback` at most once
99
+ * @param { String } events - events ids
100
+ * @param { Function } fn - callback function
101
+ * @returns { Object } el
102
+ */
103
+ defineProperty('one', function(events, fn) {
68
104
  function on() {
69
- el.off(name, on)
105
+ el.off(events, on)
70
106
  fn.apply(el, arguments)
71
107
  }
72
- return el.on(name, on)
73
- }
108
+ return el.on(events, on)
109
+ })
110
+
111
+ /**
112
+ * Execute all callback functions that listen to the given space separated list of `events`
113
+ * @param { String } events - events ids
114
+ * @returns { Object } el
115
+ */
116
+ defineProperty('trigger', function(events) {
117
+
118
+ // getting the arguments
119
+ // skipping the first one
120
+ var args = slice.call(arguments, 1),
121
+ fns
122
+
123
+ onEachEvent(events, function(name) {
74
124
 
75
- el.trigger = function(name) {
76
- var args = [].slice.call(arguments, 1),
77
- fns = callbacks[name] || []
125
+ fns = slice.call(callbacks[name] || [], 0)
78
126
 
79
- for (var i = 0, fn; (fn = fns[i]); ++i) {
80
- if (!fn.busy) {
127
+ for (var i = 0, fn; fn = fns[i]; ++i) {
128
+ if (fn.busy) return
81
129
  fn.busy = 1
82
130
  fn.apply(el, fn.typed ? [name].concat(args) : args)
83
131
  if (fns[i] !== fn) { i-- }
84
132
  fn.busy = 0
85
133
  }
86
- }
87
134
 
88
- if (callbacks.all && name != 'all') {
89
- el.trigger.apply(el, ['all', name].concat(args))
90
- }
135
+ if (callbacks['*'] && name != '*')
136
+ el.trigger.apply(el, ['*', name].concat(args))
137
+
138
+ })
91
139
 
92
140
  return el
93
- }
141
+ })
94
142
 
95
143
  return el
96
144
 
97
145
  }
98
- riot.mixin = (function() {
99
- var mixins = {}
146
+ /* istanbul ignore next */
147
+ ;(function(riot) {
148
+
149
+ /**
150
+ * Simple client-side router
151
+ * @module riot-route
152
+ */
153
+
154
+
155
+ var RE_ORIGIN = /^.+?\/+[^\/]+/,
156
+ EVENT_LISTENER = 'EventListener',
157
+ REMOVE_EVENT_LISTENER = 'remove' + EVENT_LISTENER,
158
+ ADD_EVENT_LISTENER = 'add' + EVENT_LISTENER,
159
+ HAS_ATTRIBUTE = 'hasAttribute',
160
+ REPLACE = 'replace',
161
+ POPSTATE = 'popstate',
162
+ HASHCHANGE = 'hashchange',
163
+ TRIGGER = 'trigger',
164
+ MAX_EMIT_STACK_LEVEL = 3,
165
+ win = typeof window != 'undefined' && window,
166
+ doc = typeof document != 'undefined' && document,
167
+ hist = win && history,
168
+ loc = win && (hist.location || win.location), // see html5-history-api
169
+ prot = Router.prototype, // to minify more
170
+ clickEvent = doc && doc.ontouchstart ? 'touchstart' : 'click',
171
+ started = false,
172
+ central = riot.observable(),
173
+ routeFound = false,
174
+ debouncedEmit,
175
+ base, current, parser, secondParser, emitStack = [], emitStackLevel = 0
176
+
177
+ /**
178
+ * Default parser. You can replace it via router.parser method.
179
+ * @param {string} path - current path (normalized)
180
+ * @returns {array} array
181
+ */
182
+ function DEFAULT_PARSER(path) {
183
+ return path.split(/[/?#]/)
184
+ }
100
185
 
101
- return function(name, mixin) {
102
- if (!mixin) return mixins[name]
103
- mixins[name] = mixin
186
+ /**
187
+ * Default parser (second). You can replace it via router.parser method.
188
+ * @param {string} path - current path (normalized)
189
+ * @param {string} filter - filter string (normalized)
190
+ * @returns {array} array
191
+ */
192
+ function DEFAULT_SECOND_PARSER(path, filter) {
193
+ var re = new RegExp('^' + filter[REPLACE](/\*/g, '([^/?#]+?)')[REPLACE](/\.\./, '.*') + '$'),
194
+ args = path.match(re)
195
+
196
+ if (args) return args.slice(1)
197
+ }
198
+
199
+ /**
200
+ * Simple/cheap debounce implementation
201
+ * @param {function} fn - callback
202
+ * @param {number} delay - delay in seconds
203
+ * @returns {function} debounced function
204
+ */
205
+ function debounce(fn, delay) {
206
+ var t
207
+ return function () {
208
+ clearTimeout(t)
209
+ t = setTimeout(fn, delay)
104
210
  }
211
+ }
105
212
 
106
- })()
213
+ /**
214
+ * Set the window listeners to trigger the routes
215
+ * @param {boolean} autoExec - see route.start
216
+ */
217
+ function start(autoExec) {
218
+ debouncedEmit = debounce(emit, 1)
219
+ win[ADD_EVENT_LISTENER](POPSTATE, debouncedEmit)
220
+ win[ADD_EVENT_LISTENER](HASHCHANGE, debouncedEmit)
221
+ doc[ADD_EVENT_LISTENER](clickEvent, click)
222
+ if (autoExec) emit(true)
223
+ }
107
224
 
108
- ;(function(riot, evt, win) {
225
+ /**
226
+ * Router class
227
+ */
228
+ function Router() {
229
+ this.$ = []
230
+ riot.observable(this) // make it observable
231
+ central.on('stop', this.s.bind(this))
232
+ central.on('emit', this.e.bind(this))
233
+ }
109
234
 
110
- // browsers only
111
- if (!win) return
235
+ function normalize(path) {
236
+ return path[REPLACE](/^\/|\/$/, '')
237
+ }
112
238
 
113
- var loc = win.location,
114
- fns = riot.observable(),
115
- started = false,
116
- current
239
+ function isString(str) {
240
+ return typeof str == 'string'
241
+ }
117
242
 
118
- function hash() {
119
- return loc.href.split('#')[1] || '' // why not loc.hash.splice(1) ?
120
- }
243
+ /**
244
+ * Get the part after domain name
245
+ * @param {string} href - fullpath
246
+ * @returns {string} path from root
247
+ */
248
+ function getPathFromRoot(href) {
249
+ return (href || loc.href || '')[REPLACE](RE_ORIGIN, '')
250
+ }
121
251
 
122
- function parser(path) {
123
- return path.split('/')
124
- }
252
+ /**
253
+ * Get the part after base
254
+ * @param {string} href - fullpath
255
+ * @returns {string} path from base
256
+ */
257
+ function getPathFromBase(href) {
258
+ return base[0] == '#'
259
+ ? (href || loc.href || '').split(base)[1] || ''
260
+ : getPathFromRoot(href)[REPLACE](base, '')
261
+ }
125
262
 
126
- function emit(path) {
127
- if (path.type) path = hash()
263
+ function emit(force) {
264
+ // the stack is needed for redirections
265
+ var isRoot = emitStackLevel == 0
266
+ if (MAX_EMIT_STACK_LEVEL <= emitStackLevel) return
128
267
 
129
- if (path != current) {
130
- fns.trigger.apply(null, ['H'].concat(parser(path)))
268
+ emitStackLevel++
269
+ emitStack.push(function() {
270
+ var path = getPathFromBase()
271
+ if (force || path != current) {
272
+ central[TRIGGER]('emit', path)
131
273
  current = path
132
274
  }
133
- }
134
-
135
- var r = riot.route = function(arg) {
136
- // string
137
- if (arg[0]) {
138
- loc.hash = arg
139
- emit(arg)
140
-
141
- // function
142
- } else {
143
- fns.on('H', arg)
275
+ })
276
+ if (isRoot) {
277
+ while (emitStack.length) {
278
+ emitStack[0]()
279
+ emitStack.shift()
144
280
  }
281
+ emitStackLevel = 0
145
282
  }
283
+ }
146
284
 
147
- r.exec = function(fn) {
148
- fn.apply(null, parser(hash()))
285
+ function click(e) {
286
+ if (
287
+ e.which != 1 // not left click
288
+ || e.metaKey || e.ctrlKey || e.shiftKey // or meta keys
289
+ || e.defaultPrevented // or default prevented
290
+ ) return
291
+
292
+ var el = e.target
293
+ while (el && el.nodeName != 'A') el = el.parentNode
294
+ if (
295
+ !el || el.nodeName != 'A' // not A tag
296
+ || el[HAS_ATTRIBUTE]('download') // has download attr
297
+ || !el[HAS_ATTRIBUTE]('href') // has no href attr
298
+ || el.target && el.target != '_self' // another window or frame
299
+ || el.href.indexOf(loc.href.match(RE_ORIGIN)[0]) == -1 // cross origin
300
+ ) return
301
+
302
+ if (el.href != loc.href) {
303
+ if (
304
+ el.href.split('#')[0] == loc.href.split('#')[0] // internal jump
305
+ || base != '#' && getPathFromRoot(el.href).indexOf(base) !== 0 // outside of base
306
+ || !go(getPathFromBase(el.href), el.title || doc.title) // route not found
307
+ ) return
149
308
  }
150
309
 
151
- r.parser = function(fn) {
152
- parser = fn
153
- }
310
+ e.preventDefault()
311
+ }
154
312
 
155
- r.stop = function () {
156
- if (started) {
157
- if (win.removeEventListener) win.removeEventListener(evt, emit, false) //@IE8 - the if()
158
- else win.detachEvent('on' + evt, emit) //@IE8
159
- fns.off('*')
160
- started = false
161
- }
313
+ /**
314
+ * Go to the path
315
+ * @param {string} path - destination path
316
+ * @param {string} title - page title
317
+ * @param {boolean} shouldReplace - use replaceState or pushState
318
+ * @returns {boolean} - route not found flag
319
+ */
320
+ function go(path, title, shouldReplace) {
321
+ if (hist) { // if a browser
322
+ path = base + normalize(path)
323
+ title = title || doc.title
324
+ // browsers ignores the second parameter `title`
325
+ shouldReplace
326
+ ? hist.replaceState(null, title, path)
327
+ : hist.pushState(null, title, path)
328
+ // so we need to set it manually
329
+ doc.title = title
330
+ routeFound = false
331
+ emit()
332
+ return routeFound
162
333
  }
163
334
 
164
- r.start = function () {
165
- if (!started) {
166
- if (win.addEventListener) win.addEventListener(evt, emit, false) //@IE8 - the if()
167
- else win.attachEvent('on' + evt, emit) //IE8
168
- started = true
335
+ // Server-side usage: directly execute handlers for the path
336
+ return central[TRIGGER]('emit', getPathFromBase(path))
337
+ }
338
+
339
+ /**
340
+ * Go to path or set action
341
+ * a single string: go there
342
+ * two strings: go there with setting a title
343
+ * two strings and boolean: replace history with setting a title
344
+ * a single function: set an action on the default route
345
+ * a string/RegExp and a function: set an action on the route
346
+ * @param {(string|function)} first - path / action / filter
347
+ * @param {(string|RegExp|function)} second - title / action
348
+ * @param {boolean} third - replace flag
349
+ */
350
+ prot.m = function(first, second, third) {
351
+ if (isString(first) && (!second || isString(second))) go(first, second, third || false)
352
+ else if (second) this.r(first, second)
353
+ else this.r('@', first)
354
+ }
355
+
356
+ /**
357
+ * Stop routing
358
+ */
359
+ prot.s = function() {
360
+ this.off('*')
361
+ this.$ = []
362
+ }
363
+
364
+ /**
365
+ * Emit
366
+ * @param {string} path - path
367
+ */
368
+ prot.e = function(path) {
369
+ this.$.concat('@').some(function(filter) {
370
+ var args = (filter == '@' ? parser : secondParser)(normalize(path), normalize(filter))
371
+ if (typeof args != 'undefined') {
372
+ this[TRIGGER].apply(null, [filter].concat(args))
373
+ return routeFound = true // exit from loop
169
374
  }
375
+ }, this)
376
+ }
377
+
378
+ /**
379
+ * Register route
380
+ * @param {string} filter - filter for matching to url
381
+ * @param {function} action - action to register
382
+ */
383
+ prot.r = function(filter, action) {
384
+ if (filter != '@') {
385
+ filter = '/' + normalize(filter)
386
+ this.$.push(filter)
170
387
  }
388
+ this.on(filter, action)
389
+ }
171
390
 
172
- // autostart the router
173
- r.start()
391
+ var mainRouter = new Router()
392
+ var route = mainRouter.m.bind(mainRouter)
393
+
394
+ /**
395
+ * Create a sub router
396
+ * @returns {function} the method of a new Router object
397
+ */
398
+ route.create = function() {
399
+ var newSubRouter = new Router()
400
+ // stop only this sub-router
401
+ newSubRouter.m.stop = newSubRouter.s.bind(newSubRouter)
402
+ // return sub-router's main method
403
+ return newSubRouter.m.bind(newSubRouter)
404
+ }
174
405
 
175
- })(riot, 'hashchange', window)
176
- /*
406
+ /**
407
+ * Set the base of url
408
+ * @param {(str|RegExp)} arg - a new base or '#' or '#!'
409
+ */
410
+ route.base = function(arg) {
411
+ base = arg || '#'
412
+ current = getPathFromBase() // recalculate current path
413
+ }
177
414
 
178
- //// How it works?
415
+ /** Exec routing right now **/
416
+ route.exec = function() {
417
+ emit(true)
418
+ }
419
+
420
+ /**
421
+ * Replace the default router to yours
422
+ * @param {function} fn - your parser function
423
+ * @param {function} fn2 - your secondParser function
424
+ */
425
+ route.parser = function(fn, fn2) {
426
+ if (!fn && !fn2) {
427
+ // reset parser for testing...
428
+ parser = DEFAULT_PARSER
429
+ secondParser = DEFAULT_SECOND_PARSER
430
+ }
431
+ if (fn) parser = fn
432
+ if (fn2) secondParser = fn2
433
+ }
434
+
435
+ /**
436
+ * Helper function to get url query as an object
437
+ * @returns {object} parsed query
438
+ */
439
+ route.query = function() {
440
+ var q = {}
441
+ var href = loc.href || current
442
+ href[REPLACE](/[?&](.+?)=([^&]*)/g, function(_, k, v) { q[k] = v })
443
+ return q
444
+ }
445
+
446
+ /** Stop routing **/
447
+ route.stop = function () {
448
+ if (started) {
449
+ if (win) {
450
+ win[REMOVE_EVENT_LISTENER](POPSTATE, debouncedEmit)
451
+ win[REMOVE_EVENT_LISTENER](HASHCHANGE, debouncedEmit)
452
+ doc[REMOVE_EVENT_LISTENER](clickEvent, click)
453
+ }
454
+ central[TRIGGER]('stop')
455
+ started = false
456
+ }
457
+ }
179
458
 
459
+ /**
460
+ * Start routing
461
+ * @param {boolean} autoExec - automatically exec after starting if true
462
+ */
463
+ route.start = function (autoExec) {
464
+ if (!started) {
465
+ if (win) {
466
+ if (document.readyState == 'complete') start(autoExec)
467
+ // the timeout is needed to solve
468
+ // a weird safari bug https://github.com/riot/route/issues/33
469
+ else win[ADD_EVENT_LISTENER]('load', function() {
470
+ setTimeout(function() { start(autoExec) }, 1)
471
+ })
472
+ }
473
+ started = true
474
+ }
475
+ }
180
476
 
181
- Three ways:
477
+ /** Prepare the router **/
478
+ route.base()
479
+ route.parser()
480
+
481
+ riot.route = route
482
+ })(riot)
483
+ /* istanbul ignore next */
484
+
485
+ /**
486
+ * The riot template engine
487
+ * @version v2.3.21
488
+ */
489
+
490
+ /**
491
+ * riot.util.brackets
492
+ *
493
+ * - `brackets ` - Returns a string or regex based on its parameter
494
+ * - `brackets.set` - Change the current riot brackets
495
+ *
496
+ * @module
497
+ */
498
+
499
+ var brackets = (function (UNDEF) {
500
+
501
+ var
502
+ REGLOB = 'g',
503
+
504
+ R_MLCOMMS = /\/\*[^*]*\*+(?:[^*\/][^*]*\*+)*\//g,
505
+
506
+ R_STRINGS = /"[^"\\]*(?:\\[\S\s][^"\\]*)*"|'[^'\\]*(?:\\[\S\s][^'\\]*)*'/g,
507
+
508
+ S_QBLOCKS = R_STRINGS.source + '|' +
509
+ /(?:\breturn\s+|(?:[$\w\)\]]|\+\+|--)\s*(\/)(?![*\/]))/.source + '|' +
510
+ /\/(?=[^*\/])[^[\/\\]*(?:(?:\[(?:\\.|[^\]\\]*)*\]|\\.)[^[\/\\]*)*?(\/)[gim]*/.source,
511
+
512
+ FINDBRACES = {
513
+ '(': RegExp('([()])|' + S_QBLOCKS, REGLOB),
514
+ '[': RegExp('([[\\]])|' + S_QBLOCKS, REGLOB),
515
+ '{': RegExp('([{}])|' + S_QBLOCKS, REGLOB)
516
+ },
517
+
518
+ DEFAULT = '{ }'
519
+
520
+ var _pairs = [
521
+ '{', '}',
522
+ '{', '}',
523
+ /{[^}]*}/,
524
+ /\\([{}])/g,
525
+ /\\({)|{/g,
526
+ RegExp('\\\\(})|([[({])|(})|' + S_QBLOCKS, REGLOB),
527
+ DEFAULT,
528
+ /^\s*{\^?\s*([$\w]+)(?:\s*,\s*(\S+))?\s+in\s+(\S.*)\s*}/,
529
+ /(^|[^\\]){=[\S\s]*?}/
530
+ ]
531
+
532
+ var
533
+ cachedBrackets = UNDEF,
534
+ _regex,
535
+ _cache = [],
536
+ _settings
537
+
538
+ function _loopback (re) { return re }
539
+
540
+ function _rewrite (re, bp) {
541
+ if (!bp) bp = _cache
542
+ return new RegExp(
543
+ re.source.replace(/{/g, bp[2]).replace(/}/g, bp[3]), re.global ? REGLOB : ''
544
+ )
545
+ }
182
546
 
183
- 1. Expressions: tmpl('{ value }', data).
184
- Returns the result of evaluated expression as a raw object.
547
+ function _create (pair) {
548
+ if (pair === DEFAULT) return _pairs
185
549
 
186
- 2. Templates: tmpl('Hi { name } { surname }', data).
187
- Returns a string with evaluated expressions.
550
+ var arr = pair.split(' ')
188
551
 
189
- 3. Filters: tmpl('{ show: !done, highlight: active }', data).
190
- Returns a space separated list of trueish keys (mainly
191
- used for setting html classes), e.g. "show highlight".
552
+ if (arr.length !== 2 || /[\x00-\x1F<>a-zA-Z0-9'",;\\]/.test(pair)) {
553
+ throw new Error('Unsupported brackets "' + pair + '"')
554
+ }
555
+ arr = arr.concat(pair.replace(/(?=[[\]()*+?.^$|])/g, '\\').split(' '))
556
+
557
+ arr[4] = _rewrite(arr[1].length > 1 ? /{[\S\s]*?}/ : _pairs[4], arr)
558
+ arr[5] = _rewrite(pair.length > 3 ? /\\({|})/g : _pairs[5], arr)
559
+ arr[6] = _rewrite(_pairs[6], arr)
560
+ arr[7] = RegExp('\\\\(' + arr[3] + ')|([[({])|(' + arr[3] + ')|' + S_QBLOCKS, REGLOB)
561
+ arr[8] = pair
562
+ return arr
563
+ }
192
564
 
565
+ function _brackets (reOrIdx) {
566
+ return reOrIdx instanceof RegExp ? _regex(reOrIdx) : _cache[reOrIdx]
567
+ }
193
568
 
194
- // Template examples
569
+ _brackets.split = function split (str, tmpl, _bp) {
570
+ // istanbul ignore next: _bp is for the compiler
571
+ if (!_bp) _bp = _cache
195
572
 
196
- tmpl('{ title || "Untitled" }', data)
197
- tmpl('Results are { results ? "ready" : "loading" }', data)
198
- tmpl('Today is { new Date() }', data)
199
- tmpl('{ message.length > 140 && "Message is too long" }', data)
200
- tmpl('This item got { Math.round(rating) } stars', data)
201
- tmpl('<h1>{ title }</h1>{ body }', data)
573
+ var
574
+ parts = [],
575
+ match,
576
+ isexpr,
577
+ start,
578
+ pos,
579
+ re = _bp[6]
202
580
 
581
+ isexpr = start = re.lastIndex = 0
203
582
 
204
- // Falsy expressions in templates
583
+ while (match = re.exec(str)) {
205
584
 
206
- In templates (as opposed to single expressions) all falsy values
207
- except zero (undefined/null/false) will default to empty string:
585
+ pos = match.index
208
586
 
209
- tmpl('{ undefined } - { false } - { null } - { 0 }', {})
210
- // will return: " - - - 0"
587
+ if (isexpr) {
211
588
 
212
- */
589
+ if (match[2]) {
590
+ re.lastIndex = skipBraces(str, match[2], re.lastIndex)
591
+ continue
592
+ }
593
+ if (!match[3])
594
+ continue
595
+ }
213
596
 
597
+ if (!match[1]) {
598
+ unescapeStr(str.slice(start, pos))
599
+ start = re.lastIndex
600
+ re = _bp[6 + (isexpr ^= 1)]
601
+ re.lastIndex = start
602
+ }
603
+ }
214
604
 
215
- var brackets = (function(orig) {
605
+ if (str && start < str.length) {
606
+ unescapeStr(str.slice(start))
607
+ }
216
608
 
217
- var cachedBrackets,
218
- r,
219
- b,
220
- re = /[{}]/g
609
+ return parts
221
610
 
222
- return function(x) {
611
+ function unescapeStr (s) {
612
+ if (tmpl || isexpr)
613
+ parts.push(s && s.replace(_bp[5], '$1'))
614
+ else
615
+ parts.push(s)
616
+ }
223
617
 
224
- // make sure we use the current setting
225
- var s = riot.settings.brackets || orig
618
+ function skipBraces (s, ch, ix) {
619
+ var
620
+ match,
621
+ recch = FINDBRACES[ch]
226
622
 
227
- // recreate cached vars if needed
228
- if (cachedBrackets !== s) {
229
- cachedBrackets = s
230
- b = s.split(' ')
231
- r = b.map(function (e) { return e.replace(/(?=.)/g, '\\') })
623
+ recch.lastIndex = ix
624
+ ix = 1
625
+ while (match = recch.exec(s)) {
626
+ if (match[1] &&
627
+ !(match[1] === ch ? ++ix : --ix)) break
628
+ }
629
+ return ix ? s.length : recch.lastIndex
232
630
  }
631
+ }
233
632
 
234
- // if regexp given, rewrite it with current brackets (only if differ from default)
235
- return x instanceof RegExp ? (
236
- s === orig ? x :
237
- new RegExp(x.source.replace(re, function(b) { return r[~~(b === '}')] }), x.global ? 'g' : '')
238
- ) :
239
- // else, get specific bracket
240
- b[x]
633
+ _brackets.hasExpr = function hasExpr (str) {
634
+ return _cache[4].test(str)
241
635
  }
242
- })('{ }')
243
636
 
637
+ _brackets.loopKeys = function loopKeys (expr) {
638
+ var m = expr.match(_cache[9])
639
+ return m
640
+ ? { key: m[1], pos: m[2], val: _cache[0] + m[3].trim() + _cache[1] }
641
+ : { val: expr.trim() }
642
+ }
244
643
 
245
- var tmpl = (function() {
644
+ _brackets.hasRaw = function (src) {
645
+ return _cache[10].test(src)
646
+ }
246
647
 
247
- var cache = {},
248
- OGLOB = '"in d?d:' + (window ? 'window).' : 'global).'),
249
- reVars =
250
- /(['"\/])(?:[^\\]*?|\\.|.)*?\1|\.\w*|\w*:|\b(?:(?:new|typeof|in|instanceof) |(?:this|true|false|null|undefined)\b|function\s*\()|([A-Za-z_$]\w*)/g
648
+ _brackets.array = function array (pair) {
649
+ return pair ? _create(pair) : _cache
650
+ }
251
651
 
252
- // build a template (or get it from cache), render with data
253
- return function(str, data) {
254
- return str && (cache[str] || (cache[str] = tmpl(str)))(data)
652
+ function _reset (pair) {
653
+ if ((pair || (pair = DEFAULT)) !== _cache[8]) {
654
+ _cache = _create(pair)
655
+ _regex = pair === DEFAULT ? _loopback : _rewrite
656
+ _cache[9] = _regex(_pairs[9])
657
+ _cache[10] = _regex(_pairs[10])
658
+ }
659
+ cachedBrackets = pair
255
660
  }
256
661
 
662
+ function _setSettings (o) {
663
+ var b
664
+ o = o || {}
665
+ b = o.brackets
666
+ Object.defineProperty(o, 'brackets', {
667
+ set: _reset,
668
+ get: function () { return cachedBrackets },
669
+ enumerable: true
670
+ })
671
+ _settings = o
672
+ _reset(b)
673
+ }
257
674
 
258
- // create a template instance
675
+ Object.defineProperty(_brackets, 'settings', {
676
+ set: _setSettings,
677
+ get: function () { return _settings }
678
+ })
259
679
 
260
- function tmpl(s, p) {
680
+ /* istanbul ignore next: in the browser riot is always in the scope */
681
+ _brackets.settings = typeof riot !== 'undefined' && riot.settings || {}
682
+ _brackets.set = _reset
261
683
 
262
- if (s.indexOf(brackets(0)) < 0) {
263
- // return raw text
264
- s = s.replace(/\n|\r\n?/g, '\n')
265
- return function () { return s }
266
- }
684
+ _brackets.R_STRINGS = R_STRINGS
685
+ _brackets.R_MLCOMMS = R_MLCOMMS
686
+ _brackets.S_QBLOCKS = S_QBLOCKS
267
687
 
268
- // temporarily convert \{ and \} to a non-character
269
- s = s
270
- .replace(brackets(/\\{/g), '\uFFF0')
271
- .replace(brackets(/\\}/g), '\uFFF1')
688
+ return _brackets
272
689
 
273
- // split string to expression and non-expresion parts
274
- p = split(s, extract(s, brackets(/{/), brackets(/}/)))
690
+ })()
275
691
 
276
- // is it a single expression or a template? i.e. {x} or <b>{x}</b>
277
- s = (p.length === 2 && !p[0]) ?
692
+ /**
693
+ * @module tmpl
694
+ *
695
+ * tmpl - Root function, returns the template value, render with data
696
+ * tmpl.hasExpr - Test the existence of a expression inside a string
697
+ * tmpl.loopKeys - Get the keys for an 'each' loop (used by `_each`)
698
+ */
278
699
 
279
- // if expression, evaluate it
280
- expr(p[1]) :
700
+ var tmpl = (function () {
281
701
 
282
- // if template, evaluate all expressions in it
283
- '[' + p.map(function(s, i) {
702
+ var _cache = {}
284
703
 
285
- // is it an expression or a string (every second part is an expression)
286
- return i % 2 ?
704
+ function _tmpl (str, data) {
705
+ if (!str) return str
287
706
 
288
- // evaluate the expressions
289
- expr(s, true) :
707
+ return (_cache[str] || (_cache[str] = _create(str))).call(data, _logErr)
708
+ }
290
709
 
291
- // process string parts of the template:
292
- '"' + s
710
+ _tmpl.haveRaw = brackets.hasRaw
293
711
 
294
- // preserve new lines
295
- .replace(/\n|\r\n?/g, '\\n')
712
+ _tmpl.hasExpr = brackets.hasExpr
296
713
 
297
- // escape quotes
298
- .replace(/"/g, '\\"') +
714
+ _tmpl.loopKeys = brackets.loopKeys
299
715
 
300
- '"'
716
+ _tmpl.errorHandler = null
301
717
 
302
- }).join(',') + '].join("")'
718
+ function _logErr (err, ctx) {
303
719
 
304
- return new Function('d', 'return ' + s
305
- // bring escaped { and } back
306
- .replace(/\uFFF0/g, brackets(0))
307
- .replace(/\uFFF1/g, brackets(1)) + ';')
720
+ if (_tmpl.errorHandler) {
308
721
 
722
+ err.riotData = {
723
+ tagName: ctx && ctx.root && ctx.root.tagName,
724
+ _riot_id: ctx && ctx._riot_id //eslint-disable-line camelcase
725
+ }
726
+ _tmpl.errorHandler(err)
727
+ }
309
728
  }
310
729
 
730
+ function _create (str) {
311
731
 
312
- // parse { ... } expression
732
+ var expr = _getTmpl(str)
733
+ if (expr.slice(0, 11) !== 'try{return ') expr = 'return ' + expr
313
734
 
314
- function expr(s, n) {
315
- s = s
735
+ return new Function('E', expr + ';')
736
+ }
316
737
 
317
- // convert new lines to spaces
318
- .replace(/\n|\r\n?/g, ' ')
738
+ var
739
+ RE_QBLOCK = RegExp(brackets.S_QBLOCKS, 'g'),
740
+ RE_QBMARK = /\x01(\d+)~/g
319
741
 
320
- // trim whitespace, brackets, strip comments
321
- .replace(brackets(/^[{ ]+|[ }]+$|\/\*.+?\*\//g), '')
742
+ function _getTmpl (str) {
743
+ var
744
+ qstr = [],
745
+ expr,
746
+ parts = brackets.split(str.replace(/\u2057/g, '"'), 1)
322
747
 
323
- // is it an object literal? i.e. { key : value }
324
- return /^\s*[\w- "']+ *:/.test(s) ?
748
+ if (parts.length > 2 || parts[0]) {
749
+ var i, j, list = []
325
750
 
326
- // if object literal, return trueish keys
327
- // e.g.: { show: isOpen(), done: item.done } -> "show done"
328
- '[' +
751
+ for (i = j = 0; i < parts.length; ++i) {
329
752
 
330
- // extract key:val pairs, ignoring any nested objects
331
- extract(s,
753
+ expr = parts[i]
332
754
 
333
- // name part: name:, "name":, 'name':, name :
334
- /["' ]*[\w- ]+["' ]*:/,
755
+ if (expr && (expr = i & 1 ?
335
756
 
336
- // expression part: everything upto a comma followed by a name (see above) or end of line
337
- /,(?=["' ]*[\w- ]+["' ]*:)|}|$/
338
- ).map(function(pair) {
757
+ _parseExpr(expr, 1, qstr) :
339
758
 
340
- // get key, val parts
341
- return pair.replace(/^[ "']*(.+?)[ "']*: *(.+?),? *$/, function(_, k, v) {
759
+ '"' + expr
760
+ .replace(/\\/g, '\\\\')
761
+ .replace(/\r\n?|\n/g, '\\n')
762
+ .replace(/"/g, '\\"') +
763
+ '"'
342
764
 
343
- // wrap all conditional parts to ignore errors
344
- return v.replace(/[^&|=!><]+/g, wrap) + '?"' + k + '":"",'
765
+ )) list[j++] = expr
345
766
 
346
- })
767
+ }
347
768
 
348
- }).join('') +
769
+ expr = j < 2 ? list[0] :
770
+ '[' + list.join(',') + '].join("")'
349
771
 
350
- '].join(" ").trim()' :
772
+ } else {
351
773
 
352
- // if js expression, evaluate as javascript
353
- wrap(s, n)
774
+ expr = _parseExpr(parts[1], 0, qstr)
775
+ }
354
776
 
777
+ if (qstr[0])
778
+ expr = expr.replace(RE_QBMARK, function (_, pos) {
779
+ return qstr[pos]
780
+ .replace(/\r/g, '\\r')
781
+ .replace(/\n/g, '\\n')
782
+ })
783
+
784
+ return expr
355
785
  }
356
786
 
787
+ var
788
+ RE_BREND = {
789
+ '(': /[()]/g,
790
+ '[': /[[\]]/g,
791
+ '{': /[{}]/g
792
+ },
793
+ CS_IDENT = /^(?:(-?[_A-Za-z\xA0-\xFF][-\w\xA0-\xFF]*)|\x01(\d+)~):/
357
794
 
358
- // execute js w/o breaking on errors or undefined vars
795
+ function _parseExpr (expr, asText, qstr) {
359
796
 
360
- function wrap(s, nonull) {
361
- s = s.trim()
362
- return !s ? '' : '(function(v){try{v=' +
797
+ if (expr[0] === '=') expr = expr.slice(1)
363
798
 
364
- // prefix vars (name => data.name)
365
- s.replace(reVars, function(s, _, v) { return v ? '(("' + v + OGLOB + v + ')' : s }) +
799
+ expr = expr
800
+ .replace(RE_QBLOCK, function (s, div) {
801
+ return s.length > 2 && !div ? '\x01' + (qstr.push(s) - 1) + '~' : s
802
+ })
803
+ .replace(/\s+/g, ' ').trim()
804
+ .replace(/\ ?([[\({},?\.:])\ ?/g, '$1')
366
805
 
367
- // default to empty string for falsy values except zero
368
- '}catch(e){}return ' + (nonull === true ? '!v&&v!==0?"":v' : 'v') + '}).call(d)'
369
- }
806
+ if (expr) {
807
+ var
808
+ list = [],
809
+ cnt = 0,
810
+ match
370
811
 
812
+ while (expr &&
813
+ (match = expr.match(CS_IDENT)) &&
814
+ !match.index
815
+ ) {
816
+ var
817
+ key,
818
+ jsb,
819
+ re = /,|([[{(])|$/g
371
820
 
372
- // split string by an array of substrings
821
+ expr = RegExp.rightContext
822
+ key = match[2] ? qstr[match[2]].slice(1, -1).trim().replace(/\s+/g, ' ') : match[1]
373
823
 
374
- function split(str, substrings) {
375
- var parts = []
376
- substrings.map(function(sub, i) {
824
+ while (jsb = (match = re.exec(expr))[1]) skipBraces(jsb, re)
377
825
 
378
- // push matched expression and part before it
379
- i = str.indexOf(sub)
380
- parts.push(str.slice(0, i), sub)
381
- str = str.slice(i + sub.length)
382
- })
383
- if (str) parts.push(str)
826
+ jsb = expr.slice(0, match.index)
827
+ expr = RegExp.rightContext
384
828
 
385
- // push the remaining part
386
- return parts
829
+ list[cnt++] = _wrapExpr(jsb, 1, key)
830
+ }
831
+
832
+ expr = !cnt ? _wrapExpr(expr, asText) :
833
+ cnt > 1 ? '[' + list.join(',') + '].join(" ").trim()' : list[0]
834
+ }
835
+ return expr
836
+
837
+ function skipBraces (ch, re) {
838
+ var
839
+ mm,
840
+ lv = 1,
841
+ ir = RE_BREND[ch]
842
+
843
+ ir.lastIndex = re.lastIndex
844
+ while (mm = ir.exec(expr)) {
845
+ if (mm[0] === ch) ++lv
846
+ else if (!--lv) break
847
+ }
848
+ re.lastIndex = lv ? expr.length : ir.lastIndex
849
+ }
387
850
  }
388
851
 
852
+ // istanbul ignore next: not both
853
+ var
854
+ JS_CONTEXT = '"in this?this:' + (typeof window !== 'object' ? 'global' : 'window') + ').',
855
+ JS_VARNAME = /[,{][$\w]+:|(^ *|[^$\w\.])(?!(?:typeof|true|false|null|undefined|in|instanceof|is(?:Finite|NaN)|void|NaN|new|Date|RegExp|Math)(?![$\w]))([$_A-Za-z][$\w]*)/g,
856
+ JS_NOPROPS = /^(?=(\.[$\w]+))\1(?:[^.[(]|$)/
389
857
 
390
- // match strings between opening and closing regexp, skipping any inner/nested matches
858
+ function _wrapExpr (expr, asText, key) {
859
+ var tb
391
860
 
392
- function extract(str, open, close) {
861
+ expr = expr.replace(JS_VARNAME, function (match, p, mvar, pos, s) {
862
+ if (mvar) {
863
+ pos = tb ? 0 : pos + match.length
393
864
 
394
- var start,
395
- level = 0,
396
- matches = [],
397
- re = new RegExp('(' + open.source + ')|(' + close.source + ')', 'g')
865
+ if (mvar !== 'this' && mvar !== 'global' && mvar !== 'window') {
866
+ match = p + '("' + mvar + JS_CONTEXT + mvar
867
+ if (pos) tb = (s = s[pos]) === '.' || s === '(' || s === '['
868
+ } else if (pos) {
869
+ tb = !JS_NOPROPS.test(s.slice(pos))
870
+ }
871
+ }
872
+ return match
873
+ })
398
874
 
399
- str.replace(re, function(_, open, close, pos) {
875
+ if (tb) {
876
+ expr = 'try{return ' + expr + '}catch(e){E(e,this)}'
877
+ }
400
878
 
401
- // if outer inner bracket, mark position
402
- if (!level && open) start = pos
879
+ if (key) {
403
880
 
404
- // in(de)crease bracket level
405
- level += open ? 1 : -1
881
+ expr = (tb ?
882
+ 'function(){' + expr + '}.call(this)' : '(' + expr + ')'
883
+ ) + '?"' + key + '":""'
406
884
 
407
- // if outer closing bracket, grab the match
408
- if (!level && close != null) matches.push(str.slice(start, pos + close.length))
885
+ } else if (asText) {
409
886
 
410
- })
887
+ expr = 'function(v){' + (tb ?
888
+ expr.replace('return ', 'v=') : 'v=(' + expr + ')'
889
+ ) + ';return v||v===0?v:""}.call(this)'
890
+ }
411
891
 
412
- return matches
892
+ return expr
413
893
  }
414
894
 
895
+ // istanbul ignore next: compatibility fix for beta versions
896
+ _tmpl.parse = function (s) { return s }
897
+
898
+ _tmpl.version = brackets.version = 'v2.3.21'
899
+
900
+ return _tmpl
901
+
415
902
  })()
416
903
 
417
904
  /*
418
905
  lib/browser/tag/mkdom.js
419
906
 
420
- Includes hacks needed for the Internet Explorer version 9 and bellow
421
-
907
+ Includes hacks needed for the Internet Explorer version 9 and below
908
+ See: http://kangax.github.io/compat-table/es5/#ie8
909
+ http://codeplanet.io/dropping-ie8/
422
910
  */
423
- // http://kangax.github.io/compat-table/es5/#ie8
424
- // http://codeplanet.io/dropping-ie8/
425
-
426
911
  var mkdom = (function (checkIE) {
427
912
 
428
- var rootEls = {
429
- 'tr': 'tbody',
430
- 'th': 'tr',
431
- 'td': 'tr',
432
- 'tbody': 'table',
433
- 'col': 'colgroup'
434
- },
435
- GENERIC = 'div'
913
+ var
914
+ reToSrc = /<yield\s+to=(['"])?@\1\s*>([\S\s]+?)<\/yield\s*>/.source,
915
+ rootEls = { tr: 'tbody', th: 'tr', td: 'tr', col: 'colgroup' },
916
+ GENERIC = 'div'
436
917
 
437
918
  checkIE = checkIE && checkIE < 10
919
+ var tblTags = checkIE
920
+ ? SPECIAL_TAGS_REGEX : /^(?:t(?:body|head|foot|[rhd])|caption|col(?:group)?)$/
438
921
 
439
922
  // creates any dom element in a div, table, or colgroup container
440
- function _mkdom(html) {
923
+ function _mkdom(templ, html) {
441
924
 
442
- var match = html && html.match(/^\s*<([-\w]+)/),
443
- tagName = match && match[1].toLowerCase(),
444
- rootTag = rootEls[tagName] || GENERIC,
445
- el = mkEl(rootTag)
925
+ var match = templ && templ.match(/^\s*<([-\w]+)/),
926
+ tagName = match && match[1].toLowerCase(),
927
+ el = mkEl(GENERIC)
446
928
 
447
- el.stub = true
929
+ // replace all the yield tags with the tag inner html
930
+ templ = replaceYield(templ, html || '')
448
931
 
449
- if (checkIE && tagName && (match = tagName.match(SPECIAL_TAGS_REGEX)))
450
- ie9elem(el, html, tagName, !!match[1])
932
+ /* istanbul ignore next */
933
+ //if ((checkIE || !startsWith(tagName, 'opt')) && SPECIAL_TAGS_REGEX.test(tagName))
934
+ if (tblTags.test(tagName))
935
+ el = specialTags(el, templ, tagName)
451
936
  else
452
- el.innerHTML = html
937
+ el.innerHTML = templ
938
+
939
+ el.stub = true
453
940
 
454
941
  return el
455
942
  }
456
943
 
457
- // creates tr, th, td, option, optgroup element for IE8-9
458
- /* istanbul ignore next */
459
- function ie9elem(el, html, tagName, select) {
460
-
461
- var div = mkEl(GENERIC),
462
- tag = select ? 'select>' : 'table>',
463
- child
464
-
465
- div.innerHTML = '<' + tag + html + '</' + tag
944
+ // creates the root element for table and select child elements
945
+ // tr/th/td/thead/tfoot/tbody/caption/col/colgroup/option/optgroup
946
+ function specialTags(el, templ, tagName) {
947
+ var
948
+ select = tagName[0] === 'o',
949
+ parent = select ? 'select>' : 'table>'
950
+
951
+ // trim() is important here, this ensures we don't have artifacts,
952
+ // so we can check if we have only one element inside the parent
953
+ el.innerHTML = '<' + parent + templ.trim() + '</' + parent
954
+ parent = el.firstChild
955
+
956
+ // returns the immediate parent if tr/th/td/col is the only element, if not
957
+ // returns the whole tree, as this can include additional elements
958
+ if (select) {
959
+ parent.selectedIndex = -1 // for IE9, compatible w/current riot behavior
960
+ } else {
961
+ var tname = rootEls[tagName]
962
+ if (tname && parent.children.length === 1) parent = $(tname, parent)
963
+ }
964
+ return parent
965
+ }
466
966
 
467
- child = div.getElementsByTagName(tagName)[0]
468
- if (child)
469
- el.appendChild(child)
967
+ /**
968
+ * Replace the yield tag from any tag template with the innerHTML of the
969
+ * original tag in the page
970
+ * @param { String } templ - tag implementation template
971
+ * @param { String } html - original content of the tag in the DOM
972
+ * @returns { String } tag template updated without the yield tag
973
+ */
974
+ function replaceYield(templ, html) {
975
+ // do nothing if no yield
976
+ if (!/<yield\b/i.test(templ)) return templ
977
+
978
+ // be careful with #1343 - string on the source having `$1`
979
+ var n = 0
980
+ templ = templ.replace(/<yield\s+from=['"]([-\w]+)['"]\s*(?:\/>|>\s*<\/yield\s*>)/ig,
981
+ function (str, ref) {
982
+ var m = html.match(RegExp(reToSrc.replace('@', ref), 'i'))
983
+ ++n
984
+ return m && m[2] || ''
985
+ })
470
986
 
987
+ // yield without any "from", replace yield in templ with the innerHTML
988
+ return n ? templ : templ.replace(/<yield\s*(?:\/>|>\s*<\/yield\s*>)/gi, html)
471
989
  }
472
- // end ie9elem()
473
990
 
474
991
  return _mkdom
475
992
 
476
993
  })(IE_VERSION)
477
994
 
478
- // { key, i in items} -> { key, i, items }
479
- function loopKeys(expr) {
480
- var b0 = brackets(0),
481
- els = expr.trim().slice(b0.length).match(/^\s*(\S+?)\s*(?:,\s*(\S+))?\s+in\s+(.+)$/)
482
- return els ? { key: els[1], pos: els[2], val: b0 + els[3] } : { val: expr }
483
- }
484
-
995
+ /**
996
+ * Convert the item looped into an object used to extend the child tag properties
997
+ * @param { Object } expr - object containing the keys used to extend the children tags
998
+ * @param { * } key - value to assign to the new object returned
999
+ * @param { * } val - value containing the position of the item in the array
1000
+ * @returns { Object } - new object containing the values of the original item
1001
+ *
1002
+ * The variables 'key' and 'val' are arbitrary.
1003
+ * They depend on the collection type looped (Array, Object)
1004
+ * and on the expression used on the each tag
1005
+ *
1006
+ */
485
1007
  function mkitem(expr, key, val) {
486
1008
  var item = {}
487
1009
  item[expr.key] = key
@@ -489,114 +1011,305 @@ function mkitem(expr, key, val) {
489
1011
  return item
490
1012
  }
491
1013
 
1014
+ /**
1015
+ * Unmount the redundant tags
1016
+ * @param { Array } items - array containing the current items to loop
1017
+ * @param { Array } tags - array containing all the children tags
1018
+ */
1019
+ function unmountRedundant(items, tags) {
1020
+
1021
+ var i = tags.length,
1022
+ j = items.length,
1023
+ t
1024
+
1025
+ while (i > j) {
1026
+ t = tags[--i]
1027
+ tags.splice(i, 1)
1028
+ t.unmount()
1029
+ }
1030
+ }
492
1031
 
493
- /* Beware: heavy stuff */
1032
+ /**
1033
+ * Move the nested custom tags in non custom loop tags
1034
+ * @param { Object } child - non custom loop tag
1035
+ * @param { Number } i - current position of the loop tag
1036
+ */
1037
+ function moveNestedTags(child, i) {
1038
+ Object.keys(child.tags).forEach(function(tagName) {
1039
+ var tag = child.tags[tagName]
1040
+ if (isArray(tag))
1041
+ each(tag, function (t) {
1042
+ moveChildTag(t, tagName, i)
1043
+ })
1044
+ else
1045
+ moveChildTag(tag, tagName, i)
1046
+ })
1047
+ }
1048
+
1049
+ /**
1050
+ * Adds the elements for a virtual tag
1051
+ * @param { Tag } tag - the tag whose root's children will be inserted or appended
1052
+ * @param { Node } src - the node that will do the inserting or appending
1053
+ * @param { Tag } target - only if inserting, insert before this tag's first child
1054
+ */
1055
+ function addVirtual(tag, src, target) {
1056
+ var el = tag._root, sib
1057
+ tag._virts = []
1058
+ while (el) {
1059
+ sib = el.nextSibling
1060
+ if (target)
1061
+ src.insertBefore(el, target._root)
1062
+ else
1063
+ src.appendChild(el)
1064
+
1065
+ tag._virts.push(el) // hold for unmounting
1066
+ el = sib
1067
+ }
1068
+ }
1069
+
1070
+ /**
1071
+ * Move virtual tag and all child nodes
1072
+ * @param { Tag } tag - first child reference used to start move
1073
+ * @param { Node } src - the node that will do the inserting
1074
+ * @param { Tag } target - insert before this tag's first child
1075
+ * @param { Number } len - how many child nodes to move
1076
+ */
1077
+ function moveVirtual(tag, src, target, len) {
1078
+ var el = tag._root, sib, i = 0
1079
+ for (; i < len; i++) {
1080
+ sib = el.nextSibling
1081
+ src.insertBefore(el, target._root)
1082
+ el = sib
1083
+ }
1084
+ }
1085
+
1086
+
1087
+ /**
1088
+ * Manage tags having the 'each'
1089
+ * @param { Object } dom - DOM node we need to loop
1090
+ * @param { Tag } parent - parent tag instance where the dom node is contained
1091
+ * @param { String } expr - string contained in the 'each' attribute
1092
+ */
494
1093
  function _each(dom, parent, expr) {
495
1094
 
1095
+ // remove the each property from the original tag
496
1096
  remAttr(dom, 'each')
497
1097
 
498
- var tagName = getTagName(dom),
499
- template = dom.outerHTML,
500
- hasImpl = !!tagImpl[tagName],
501
- impl = tagImpl[tagName] || {
502
- tmpl: template
503
- },
504
- root = dom.parentNode,
505
- placeholder = document.createComment('riot placeholder'),
506
- tags = [],
507
- child = getTag(dom),
508
- checksum
1098
+ var mustReorder = typeof getAttr(dom, 'no-reorder') !== T_STRING || remAttr(dom, 'no-reorder'),
1099
+ tagName = getTagName(dom),
1100
+ impl = __tagImpl[tagName] || { tmpl: dom.outerHTML },
1101
+ useRoot = SPECIAL_TAGS_REGEX.test(tagName),
1102
+ root = dom.parentNode,
1103
+ ref = document.createTextNode(''),
1104
+ child = getTag(dom),
1105
+ isOption = /^option$/i.test(tagName), // the option tags must be treated differently
1106
+ tags = [],
1107
+ oldItems = [],
1108
+ hasKeys,
1109
+ isVirtual = dom.tagName == 'VIRTUAL'
1110
+
1111
+ // parse the each expression
1112
+ expr = tmpl.loopKeys(expr)
1113
+
1114
+ // insert a marked where the loop tags will be injected
1115
+ root.insertBefore(ref, dom)
509
1116
 
510
- root.insertBefore(placeholder, dom)
1117
+ // clean template code
1118
+ parent.one('before-mount', function () {
511
1119
 
512
- expr = loopKeys(expr)
1120
+ // remove the original DOM node
1121
+ dom.parentNode.removeChild(dom)
1122
+ if (root.stub) root = parent.root
513
1123
 
514
- // clean template code
515
- parent
516
- .one('premount', function () {
517
- if (root.stub) root = parent.root
518
- // remove the original DOM node
519
- dom.parentNode.removeChild(dom)
520
- })
521
- .on('update', function () {
522
- var items = tmpl(expr.val, parent)
1124
+ }).on('update', function () {
1125
+ // get the new items collection
1126
+ var items = tmpl(expr.val, parent),
1127
+ // create a fragment to hold the new DOM nodes to inject in the parent tag
1128
+ frag = document.createDocumentFragment()
523
1129
 
524
- // object loop. any changes cause full redraw
525
- if (!isArray(items)) {
526
1130
 
527
- checksum = items ? JSON.stringify(items) : ''
528
1131
 
529
- items = !items ? [] :
530
- Object.keys(items).map(function (key) {
531
- return mkitem(expr, key, items[key])
532
- })
533
- }
1132
+ // object loop. any changes cause full redraw
1133
+ if (!isArray(items)) {
1134
+ hasKeys = items || false
1135
+ items = hasKeys ?
1136
+ Object.keys(items).map(function (key) {
1137
+ return mkitem(expr, key, items[key])
1138
+ }) : []
1139
+ }
534
1140
 
535
- var frag = document.createDocumentFragment(),
536
- i = tags.length,
537
- j = items.length
1141
+ // loop all the new items
1142
+ items.forEach(function(item, i) {
1143
+ // reorder only if the items are objects
1144
+ var _mustReorder = mustReorder && item instanceof Object,
1145
+ oldPos = oldItems.indexOf(item),
1146
+ pos = ~oldPos && _mustReorder ? oldPos : i,
1147
+ // does a tag exist in this position?
1148
+ tag = tags[pos]
1149
+
1150
+ item = !hasKeys && expr.key ? mkitem(expr, item, i) : item
1151
+
1152
+ // new tag
1153
+ if (
1154
+ !_mustReorder && !tag // with no-reorder we just update the old tags
1155
+ ||
1156
+ _mustReorder && !~oldPos || !tag // by default we always try to reorder the DOM elements
1157
+ ) {
1158
+
1159
+ tag = new Tag(impl, {
1160
+ parent: parent,
1161
+ isLoop: true,
1162
+ hasImpl: !!__tagImpl[tagName],
1163
+ root: useRoot ? root : dom.cloneNode(),
1164
+ item: item
1165
+ }, dom.innerHTML)
1166
+
1167
+ tag.mount()
1168
+ if (isVirtual) tag._root = tag.root.firstChild // save reference for further moves or inserts
1169
+ // this tag must be appended
1170
+ if (i == tags.length) {
1171
+ if (isVirtual)
1172
+ addVirtual(tag, frag)
1173
+ else frag.appendChild(tag.root)
1174
+ }
1175
+ // this tag must be insert
1176
+ else {
1177
+ if (isVirtual)
1178
+ addVirtual(tag, root, tags[i])
1179
+ else root.insertBefore(tag.root, tags[i].root)
1180
+ oldItems.splice(i, 0, item)
1181
+ }
538
1182
 
539
- // unmount leftover items
540
- while (i > j) {
541
- tags[--i].unmount()
542
- tags.splice(i, 1)
1183
+ tags.splice(i, 0, tag)
1184
+ pos = i // handled here so no move
1185
+ } else tag.update(item)
1186
+
1187
+ // reorder the tag if it's not located in its previous position
1188
+ if (pos !== i && _mustReorder) {
1189
+ // update the DOM
1190
+ if (isVirtual)
1191
+ moveVirtual(tag, root, tags[i], dom.childNodes.length)
1192
+ else root.insertBefore(tag.root, tags[i].root)
1193
+ // update the position attribute if it exists
1194
+ if (expr.pos)
1195
+ tag[expr.pos] = i
1196
+ // move the old tag instance
1197
+ tags.splice(i, 0, tags.splice(pos, 1)[0])
1198
+ // move the old item
1199
+ oldItems.splice(i, 0, oldItems.splice(pos, 1)[0])
1200
+ // if the loop tags are not custom
1201
+ // we need to move all their custom tags into the right position
1202
+ if (!child && tag.tags) moveNestedTags(tag, i)
543
1203
  }
544
1204
 
545
- for (i = 0; i < j; ++i) {
546
- var _item = !checksum && !!expr.key ? mkitem(expr, items[i], i) : items[i]
1205
+ // cache the original item to use it in the events bound to this node
1206
+ // and its children
1207
+ tag._item = item
1208
+ // cache the real parent tag internally
1209
+ defineProperty(tag, '_parent', parent)
547
1210
 
548
- if (!tags[i]) {
549
- // mount new
550
- (tags[i] = new Tag(impl, {
551
- parent: parent,
552
- isLoop: true,
553
- hasImpl: hasImpl,
554
- root: SPECIAL_TAGS_REGEX.test(tagName) ? root : dom.cloneNode(),
555
- item: _item
556
- }, dom.innerHTML)
557
- ).mount()
1211
+ })
558
1212
 
559
- frag.appendChild(tags[i].root)
560
- } else
561
- tags[i].update(_item)
1213
+ // remove the redundant tags
1214
+ unmountRedundant(items, tags)
562
1215
 
563
- tags[i]._item = _item
1216
+ // insert the new nodes
1217
+ if (isOption) root.appendChild(frag)
1218
+ else root.insertBefore(frag, ref)
564
1219
 
565
- }
1220
+ // set the 'tags' property of the parent tag
1221
+ // if child is 'undefined' it means that we don't need to set this property
1222
+ // for example:
1223
+ // we don't need store the `myTag.tags['div']` property if we are looping a div tag
1224
+ // but we need to track the `myTag.tags['child']` property looping a custom child node named `child`
1225
+ if (child) parent.tags[tagName] = tags
566
1226
 
567
- root.insertBefore(frag, placeholder)
1227
+ // clone the items array
1228
+ oldItems = items.slice()
568
1229
 
569
- if (child) parent.tags[tagName] = tags
570
-
571
- }).one('updated', function() {
572
- var keys = Object.keys(parent)// only set new values
573
- walk(root, function(node) {
574
- // only set element node and not isLoop
575
- if (node.nodeType == 1 && !node.isLoop && !node._looped) {
576
- node._visited = false // reset _visited for loop node
577
- node._looped = true // avoid set multiple each
578
- setNamed(node, parent, keys)
579
- }
580
- })
581
- })
1230
+ })
582
1231
 
583
1232
  }
1233
+ /**
1234
+ * Object that will be used to inject and manage the css of every tag instance
1235
+ */
1236
+ var styleManager = (function(_riot) {
1237
+
1238
+ if (!window) return { // skip injection on the server
1239
+ add: function () {},
1240
+ inject: function () {}
1241
+ }
1242
+
1243
+ var styleNode = (function () {
1244
+ // create a new style element with the correct type
1245
+ var newNode = mkEl('style')
1246
+ setAttr(newNode, 'type', 'text/css')
1247
+
1248
+ // replace any user node or insert the new one into the head
1249
+ var userNode = $('style[type=riot]')
1250
+ if (userNode) {
1251
+ if (userNode.id) newNode.id = userNode.id
1252
+ userNode.parentNode.replaceChild(newNode, userNode)
1253
+ }
1254
+ else document.getElementsByTagName('head')[0].appendChild(newNode)
584
1255
 
1256
+ return newNode
1257
+ })()
585
1258
 
586
- function parseNamedElements(root, tag, childTags) {
1259
+ // Create cache and shortcut to the correct property
1260
+ var cssTextProp = styleNode.styleSheet,
1261
+ stylesToInject = ''
1262
+
1263
+ // Expose the style node in a non-modificable property
1264
+ Object.defineProperty(_riot, 'styleNode', {
1265
+ value: styleNode,
1266
+ writable: true
1267
+ })
1268
+
1269
+ /**
1270
+ * Public api
1271
+ */
1272
+ return {
1273
+ /**
1274
+ * Save a tag style to be later injected into DOM
1275
+ * @param { String } css [description]
1276
+ */
1277
+ add: function(css) {
1278
+ stylesToInject += css
1279
+ },
1280
+ /**
1281
+ * Inject all previously saved tag styles into DOM
1282
+ * innerHTML seems slow: http://jsperf.com/riot-insert-style
1283
+ */
1284
+ inject: function() {
1285
+ if (stylesToInject) {
1286
+ if (cssTextProp) cssTextProp.cssText += stylesToInject
1287
+ else styleNode.innerHTML += stylesToInject
1288
+ stylesToInject = ''
1289
+ }
1290
+ }
1291
+ }
1292
+
1293
+ })(riot)
1294
+
1295
+
1296
+ function parseNamedElements(root, tag, childTags, forceParsingNamed) {
587
1297
 
588
1298
  walk(root, function(dom) {
589
1299
  if (dom.nodeType == 1) {
590
- dom.isLoop = dom.isLoop || (dom.parentNode && dom.parentNode.isLoop || dom.getAttribute('each')) ? 1 : 0
1300
+ dom.isLoop = dom.isLoop ||
1301
+ (dom.parentNode && dom.parentNode.isLoop || getAttr(dom, 'each'))
1302
+ ? 1 : 0
591
1303
 
592
1304
  // custom child tag
593
- var child = getTag(dom)
1305
+ if (childTags) {
1306
+ var child = getTag(dom)
594
1307
 
595
- if (child && !dom.isLoop) {
596
- childTags.push(initChildTag(child, dom, tag))
1308
+ if (child && !dom.isLoop)
1309
+ childTags.push(initChildTag(child, {root: dom, parent: tag}, dom.innerHTML, tag))
597
1310
  }
598
1311
 
599
- if (!dom.isLoop)
1312
+ if (!dom.isLoop || forceParsingNamed)
600
1313
  setNamed(dom, tag, [])
601
1314
  }
602
1315
 
@@ -607,14 +1320,14 @@ function parseNamedElements(root, tag, childTags) {
607
1320
  function parseExpressions(root, tag, expressions) {
608
1321
 
609
1322
  function addExpr(dom, val, extra) {
610
- if (val.indexOf(brackets(0)) >= 0) {
611
- var expr = { dom: dom, expr: val }
612
- expressions.push(extend(expr, extra))
1323
+ if (tmpl.hasExpr(val)) {
1324
+ expressions.push(extend({ dom: dom, expr: val }, extra))
613
1325
  }
614
1326
  }
615
1327
 
616
1328
  walk(root, function(dom) {
617
- var type = dom.nodeType
1329
+ var type = dom.nodeType,
1330
+ attr
618
1331
 
619
1332
  // text node
620
1333
  if (type == 3 && dom.parentNode.tagName != 'STYLE') addExpr(dom, dom.nodeValue)
@@ -623,7 +1336,7 @@ function parseExpressions(root, tag, expressions) {
623
1336
  /* element */
624
1337
 
625
1338
  // loop
626
- var attr = dom.getAttribute('each')
1339
+ attr = getAttr(dom, 'each')
627
1340
 
628
1341
  if (attr) { _each(dom, tag, attr); return false }
629
1342
 
@@ -646,23 +1359,21 @@ function parseExpressions(root, tag, expressions) {
646
1359
  function Tag(impl, conf, innerHTML) {
647
1360
 
648
1361
  var self = riot.observable(this),
649
- opts = inherit(conf.opts) || {},
650
- dom = mkdom(impl.tmpl),
651
- parent = conf.parent,
652
- isLoop = conf.isLoop,
653
- hasImpl = conf.hasImpl,
654
- item = cleanUpData(conf.item),
655
- expressions = [],
656
- childTags = [],
657
- root = conf.root,
658
- fn = impl.fn,
659
- tagName = root.tagName.toLowerCase(),
660
- attr = {},
661
- propsInSyncWithParent = []
662
-
663
- if (fn && root._tag) {
664
- root._tag.unmount(true)
665
- }
1362
+ opts = inherit(conf.opts) || {},
1363
+ parent = conf.parent,
1364
+ isLoop = conf.isLoop,
1365
+ hasImpl = conf.hasImpl,
1366
+ item = cleanUpData(conf.item),
1367
+ expressions = [],
1368
+ childTags = [],
1369
+ root = conf.root,
1370
+ fn = impl.fn,
1371
+ tagName = root.tagName.toLowerCase(),
1372
+ attr = {},
1373
+ propsInSyncWithParent = [],
1374
+ dom
1375
+
1376
+ if (fn && root._tag) root._tag.unmount(true)
666
1377
 
667
1378
  // not yet mounted
668
1379
  this.isMounted = false
@@ -674,7 +1385,7 @@ function Tag(impl, conf, innerHTML) {
674
1385
 
675
1386
  // create a unique id to this tag
676
1387
  // it could be handy to use it also to improve the virtual dom rendering speed
677
- this._id = __uid++
1388
+ defineProperty(this, '_riot_id', ++__uid) // base 1 allows test !t._riot_id
678
1389
 
679
1390
  extend(this, { parent: parent, root: root, opts: opts, tags: {} }, item)
680
1391
 
@@ -682,12 +1393,10 @@ function Tag(impl, conf, innerHTML) {
682
1393
  each(root.attributes, function(el) {
683
1394
  var val = el.value
684
1395
  // remember attributes with expressions only
685
- if (brackets(/{.*}/).test(val)) attr[el.name] = val
1396
+ if (tmpl.hasExpr(val)) attr[el.name] = val
686
1397
  })
687
1398
 
688
- if (dom.innerHTML && !/^(select|optgroup|table|tbody|tr|col(?:group)?)$/.test(tagName))
689
- // replace all the yield tags with the tag inner html
690
- dom.innerHTML = replaceYield(dom.innerHTML, innerHTML)
1399
+ dom = mkdom(impl.tmpl, innerHTML)
691
1400
 
692
1401
  // options
693
1402
  function updateOpts() {
@@ -695,17 +1404,18 @@ function Tag(impl, conf, innerHTML) {
695
1404
 
696
1405
  // update opts from current DOM attributes
697
1406
  each(root.attributes, function(el) {
698
- opts[el.name] = tmpl(el.value, ctx)
1407
+ var val = el.value
1408
+ opts[toCamel(el.name)] = tmpl.hasExpr(val) ? tmpl(val, ctx) : val
699
1409
  })
700
1410
  // recover those with expressions
701
1411
  each(Object.keys(attr), function(name) {
702
- opts[name] = tmpl(attr[name], ctx)
1412
+ opts[toCamel(name)] = tmpl(attr[name], ctx)
703
1413
  })
704
1414
  }
705
1415
 
706
1416
  function normalizeData(data) {
707
1417
  for (var key in item) {
708
- if (typeof self[key] !== T_UNDEF)
1418
+ if (typeof self[key] !== T_UNDEF && isWritable(self, key))
709
1419
  self[key] = data[key]
710
1420
  }
711
1421
  }
@@ -714,7 +1424,7 @@ function Tag(impl, conf, innerHTML) {
714
1424
  if (!self.parent || !isLoop) return
715
1425
  each(Object.keys(self.parent), function(k) {
716
1426
  // some properties must be always in sync with the parent tag
717
- var mustSync = !~RESERVED_WORDS_BLACKLIST.indexOf(k) && ~propsInSyncWithParent.indexOf(k)
1427
+ var mustSync = !contains(RESERVED_WORDS_BLACKLIST, k) && contains(propsInSyncWithParent, k)
718
1428
  if (typeof self[k] === T_UNDEF || mustSync) {
719
1429
  // track the property to keep in sync
720
1430
  // so we can keep it updated
@@ -724,7 +1434,8 @@ function Tag(impl, conf, innerHTML) {
724
1434
  })
725
1435
  }
726
1436
 
727
- this.update = function(data) {
1437
+ defineProperty(this, 'update', function(data) {
1438
+
728
1439
  // make sure the data passed will not override
729
1440
  // the component core methods
730
1441
  data = cleanUpData(data)
@@ -739,23 +1450,44 @@ function Tag(impl, conf, innerHTML) {
739
1450
  updateOpts()
740
1451
  self.trigger('update', data)
741
1452
  update(expressions, self)
742
- self.trigger('updated')
743
- }
1453
+ // the updated event will be triggered
1454
+ // once the DOM will be ready and all the reflows are completed
1455
+ // this is useful if you want to get the "real" root properties
1456
+ // 4 ex: root.offsetWidth ...
1457
+ rAF(function() { self.trigger('updated') })
1458
+ return this
1459
+ })
744
1460
 
745
- this.mixin = function() {
1461
+ defineProperty(this, 'mixin', function() {
746
1462
  each(arguments, function(mix) {
1463
+ var instance
1464
+
747
1465
  mix = typeof mix === T_STRING ? riot.mixin(mix) : mix
748
- each(Object.keys(mix), function(key) {
1466
+
1467
+ // check if the mixin is a function
1468
+ if (isFunction(mix)) {
1469
+ // create the new mixin instance
1470
+ instance = new mix()
1471
+ // save the prototype to loop it afterwards
1472
+ mix = mix.prototype
1473
+ } else instance = mix
1474
+
1475
+ // loop the keys in the function prototype or the all object keys
1476
+ each(Object.getOwnPropertyNames(mix), function(key) {
749
1477
  // bind methods to self
750
1478
  if (key != 'init')
751
- self[key] = isFunction(mix[key]) ? mix[key].bind(self) : mix[key]
1479
+ self[key] = isFunction(instance[key]) ?
1480
+ instance[key].bind(self) :
1481
+ instance[key]
752
1482
  })
1483
+
753
1484
  // init method will be called automatically
754
- if (mix.init) mix.init.bind(self)()
1485
+ if (instance.init) instance.init.bind(self)()
755
1486
  })
756
- }
1487
+ return this
1488
+ })
757
1489
 
758
- this.mount = function() {
1490
+ defineProperty(this, 'mount', function() {
759
1491
 
760
1492
  updateOpts()
761
1493
 
@@ -771,14 +1503,14 @@ function Tag(impl, conf, innerHTML) {
771
1503
  // update the root adding custom attributes coming from the compiler
772
1504
  // it fixes also #1087
773
1505
  if (impl.attrs || hasImpl) {
774
- walkAttributes(impl.attrs, function (k, v) { root.setAttribute(k, v) })
1506
+ walkAttributes(impl.attrs, function (k, v) { setAttr(root, k, v) })
775
1507
  parseExpressions(self.root, self, expressions)
776
1508
  }
777
1509
 
778
1510
  if (!self.parent || isLoop) self.update(item)
779
1511
 
780
1512
  // internal use only, fixes #403
781
- self.trigger('premount')
1513
+ self.trigger('before-mount')
782
1514
 
783
1515
  if (isLoop && !hasImpl) {
784
1516
  // update the root attribute for the looped elements
@@ -788,6 +1520,12 @@ function Tag(impl, conf, innerHTML) {
788
1520
  while (dom.firstChild) root.appendChild(dom.firstChild)
789
1521
  if (root.stub) self.root = root = parent.root
790
1522
  }
1523
+
1524
+ // parse the named dom nodes in the looped child
1525
+ // adding them to the parent as well
1526
+ if (isLoop)
1527
+ parseNamedElements(self.root, self.parent, null, true)
1528
+
791
1529
  // if it's not a child tag we can trigger its mount event
792
1530
  if (!self.parent || self.parent.isMounted) {
793
1531
  self.isMounted = true
@@ -802,13 +1540,26 @@ function Tag(impl, conf, innerHTML) {
802
1540
  self.trigger('mount')
803
1541
  }
804
1542
  })
805
- }
1543
+ })
806
1544
 
807
1545
 
808
- this.unmount = function(keepRootTag) {
1546
+ defineProperty(this, 'unmount', function(keepRootTag) {
809
1547
  var el = root,
810
- p = el.parentNode,
811
- ptag
1548
+ p = el.parentNode,
1549
+ ptag,
1550
+ tagIndex = __virtualDom.indexOf(self)
1551
+
1552
+ self.trigger('before-unmount')
1553
+
1554
+ // remove this tag instance from the global virtualDom variable
1555
+ if (~tagIndex)
1556
+ __virtualDom.splice(tagIndex, 1)
1557
+
1558
+ if (this._virts) {
1559
+ each(this._virts, function(v) {
1560
+ if (v.parentNode) v.parentNode.removeChild(v)
1561
+ })
1562
+ }
812
1563
 
813
1564
  if (p) {
814
1565
 
@@ -819,7 +1570,7 @@ function Tag(impl, conf, innerHTML) {
819
1570
  // remove this element form the array
820
1571
  if (isArray(ptag.tags[tagName]))
821
1572
  each(ptag.tags[tagName], function(tag, i) {
822
- if (tag._id == self._id)
1573
+ if (tag._riot_id == self._riot_id)
823
1574
  ptag.tags[tagName].splice(i, 1)
824
1575
  })
825
1576
  else
@@ -834,17 +1585,17 @@ function Tag(impl, conf, innerHTML) {
834
1585
  p.removeChild(el)
835
1586
  else
836
1587
  // the riot-tag attribute isn't needed anymore, remove it
837
- p.removeAttribute('riot-tag')
1588
+ remAttr(p, 'riot-tag')
838
1589
  }
839
1590
 
840
1591
 
841
1592
  self.trigger('unmount')
842
1593
  toggle()
843
1594
  self.off('*')
844
- // somehow ie8 does not like `delete root._tag`
845
- root._tag = null
1595
+ self.isMounted = false
1596
+ delete root._tag
846
1597
 
847
- }
1598
+ })
848
1599
 
849
1600
  function toggle(isMount) {
850
1601
 
@@ -852,46 +1603,48 @@ function Tag(impl, conf, innerHTML) {
852
1603
  each(childTags, function(child) { child[isMount ? 'mount' : 'unmount']() })
853
1604
 
854
1605
  // listen/unlisten parent (events flow one way from parent to children)
855
- if (parent) {
856
- var evt = isMount ? 'on' : 'off'
1606
+ if (!parent) return
1607
+ var evt = isMount ? 'on' : 'off'
857
1608
 
858
- // the loop tags will be always in sync with the parent automatically
859
- if (isLoop)
860
- parent[evt]('unmount', self.unmount)
861
- else
862
- parent[evt]('update', self.update)[evt]('unmount', self.unmount)
863
- }
1609
+ // the loop tags will be always in sync with the parent automatically
1610
+ if (isLoop)
1611
+ parent[evt]('unmount', self.unmount)
1612
+ else
1613
+ parent[evt]('update', self.update)[evt]('unmount', self.unmount)
864
1614
  }
865
1615
 
866
1616
  // named elements available for fn
867
1617
  parseNamedElements(dom, this, childTags)
868
1618
 
869
-
870
1619
  }
871
-
1620
+ /**
1621
+ * Attach an event to a DOM node
1622
+ * @param { String } name - event name
1623
+ * @param { Function } handler - event callback
1624
+ * @param { Object } dom - dom node
1625
+ * @param { Tag } tag - tag instance
1626
+ */
872
1627
  function setEventHandler(name, handler, dom, tag) {
873
1628
 
874
1629
  dom[name] = function(e) {
875
1630
 
876
- var item = tag._item,
877
- ptag = tag.parent,
878
- el
1631
+ var ptag = tag._parent,
1632
+ item = tag._item,
1633
+ el
879
1634
 
880
1635
  if (!item)
881
1636
  while (ptag && !item) {
882
1637
  item = ptag._item
883
- ptag = ptag.parent
1638
+ ptag = ptag._parent
884
1639
  }
885
1640
 
886
1641
  // cross browser event fix
887
1642
  e = e || window.event
888
1643
 
889
- // ignore error on some browsers
890
- try {
891
- e.currentTarget = dom
892
- if (!e.target) e.target = e.srcElement
893
- if (!e.which) e.which = e.charCode || e.keyCode
894
- } catch (ignored) { /**/ }
1644
+ // override the event properties
1645
+ if (isWritable(e, 'currentTarget')) e.currentTarget = dom
1646
+ if (isWritable(e, 'target')) e.target = e.srcElement
1647
+ if (isWritable(e, 'which')) e.which = e.charCode || e.keyCode
895
1648
 
896
1649
  e.item = item
897
1650
 
@@ -910,22 +1663,32 @@ function setEventHandler(name, handler, dom, tag) {
910
1663
 
911
1664
  }
912
1665
 
913
- // used by if- attribute
1666
+
1667
+ /**
1668
+ * Insert a DOM node replacing another one (used by if- attribute)
1669
+ * @param { Object } root - parent node
1670
+ * @param { Object } node - node replaced
1671
+ * @param { Object } before - node added
1672
+ */
914
1673
  function insertTo(root, node, before) {
915
- if (root) {
916
- root.insertBefore(before, node)
917
- root.removeChild(node)
918
- }
1674
+ if (!root) return
1675
+ root.insertBefore(before, node)
1676
+ root.removeChild(node)
919
1677
  }
920
1678
 
1679
+ /**
1680
+ * Update the expressions in a Tag instance
1681
+ * @param { Array } expressions - expression that must be re evaluated
1682
+ * @param { Tag } tag - tag instance
1683
+ */
921
1684
  function update(expressions, tag) {
922
1685
 
923
1686
  each(expressions, function(expr, i) {
924
1687
 
925
1688
  var dom = expr.dom,
926
- attrName = expr.attr,
927
- value = tmpl(expr.expr, tag),
928
- parent = expr.dom.parentNode
1689
+ attrName = expr.attr,
1690
+ value = tmpl(expr.expr, tag),
1691
+ parent = expr.dom.parentNode
929
1692
 
930
1693
  if (expr.bool)
931
1694
  value = value ? attrName : false
@@ -934,7 +1697,11 @@ function update(expressions, tag) {
934
1697
 
935
1698
  // leave out riot- prefixes from strings inside textarea
936
1699
  // fix #815: any value -> string
937
- if (parent && parent.tagName == 'TEXTAREA') value = ('' + value).replace(/riot-/g, '')
1700
+ if (parent && parent.tagName == 'TEXTAREA') {
1701
+ value = ('' + value).replace(/riot-/g, '')
1702
+ // change textarea's value
1703
+ parent.value = value
1704
+ }
938
1705
 
939
1706
  // no change
940
1707
  if (expr.value === value) return
@@ -955,8 +1722,8 @@ function update(expressions, tag) {
955
1722
  // if- conditional
956
1723
  } else if (attrName == 'if') {
957
1724
  var stub = expr.stub,
958
- add = function() { insertTo(stub.parentNode, stub, dom) },
959
- remove = function() { insertTo(dom.parentNode, dom, stub) }
1725
+ add = function() { insertTo(stub.parentNode, stub, dom) },
1726
+ remove = function() { insertTo(dom.parentNode, dom, stub) }
960
1727
 
961
1728
  // add to DOM
962
1729
  if (value) {
@@ -967,7 +1734,8 @@ function update(expressions, tag) {
967
1734
  // maybe we can optimize this avoiding to mount the tag at all
968
1735
  if (!isInStub(dom)) {
969
1736
  walk(dom, function(el) {
970
- if (el._tag && !el._tag.isMounted) el._tag.isMounted = !!el._tag.trigger('mount')
1737
+ if (el._tag && !el._tag.isMounted)
1738
+ el._tag.isMounted = !!el._tag.trigger('mount')
971
1739
  })
972
1740
  }
973
1741
  }
@@ -977,9 +1745,8 @@ function update(expressions, tag) {
977
1745
  // if the parentNode is defined we can easily replace the tag
978
1746
  if (dom.parentNode)
979
1747
  remove()
980
- else
981
1748
  // otherwise we need to wait the updated event
982
- (tag.parent || tag).one('updated', remove)
1749
+ else (tag.parent || tag).one('updated', remove)
983
1750
 
984
1751
  dom.inStub = true
985
1752
  }
@@ -995,7 +1762,7 @@ function update(expressions, tag) {
995
1762
  // <img src="{ expr }">
996
1763
  } else if (startsWith(attrName, RIOT_PREFIX) && attrName != RIOT_TAG) {
997
1764
  if (value)
998
- dom.setAttribute(attrName.slice(RIOT_PREFIX.length), value)
1765
+ setAttr(dom, attrName.slice(RIOT_PREFIX.length), value)
999
1766
 
1000
1767
  } else {
1001
1768
  if (expr.bool) {
@@ -1003,65 +1770,168 @@ function update(expressions, tag) {
1003
1770
  if (!value) return
1004
1771
  }
1005
1772
 
1006
- if (typeof value !== T_OBJECT) dom.setAttribute(attrName, value)
1773
+ if (value === 0 || value && typeof value !== T_OBJECT)
1774
+ setAttr(dom, attrName, value)
1007
1775
 
1008
1776
  }
1009
1777
 
1010
1778
  })
1011
1779
 
1012
1780
  }
1781
+ /**
1782
+ * Specialized function for looping an array-like collection with `each={}`
1783
+ * @param { Array } els - collection of items
1784
+ * @param {Function} fn - callback function
1785
+ * @returns { Array } the array looped
1786
+ */
1013
1787
  function each(els, fn) {
1014
- for (var i = 0, len = (els || []).length, el; i < len; i++) {
1788
+ var len = els ? els.length : 0
1789
+
1790
+ for (var i = 0, el; i < len; i++) {
1015
1791
  el = els[i]
1016
- // return false -> remove current item during loop
1792
+ // return false -> current item was removed by fn during the loop
1017
1793
  if (el != null && fn(el, i) === false) i--
1018
1794
  }
1019
1795
  return els
1020
1796
  }
1021
1797
 
1798
+ /**
1799
+ * Detect if the argument passed is a function
1800
+ * @param { * } v - whatever you want to pass to this function
1801
+ * @returns { Boolean } -
1802
+ */
1022
1803
  function isFunction(v) {
1023
1804
  return typeof v === T_FUNCTION || false // avoid IE problems
1024
1805
  }
1025
1806
 
1807
+ /**
1808
+ * Remove any DOM attribute from a node
1809
+ * @param { Object } dom - DOM node we want to update
1810
+ * @param { String } name - name of the property we want to remove
1811
+ */
1026
1812
  function remAttr(dom, name) {
1027
1813
  dom.removeAttribute(name)
1028
1814
  }
1029
1815
 
1030
- function getTag(dom) {
1031
- return dom.tagName && tagImpl[dom.getAttribute(RIOT_TAG) || dom.tagName.toLowerCase()]
1816
+ /**
1817
+ * Convert a string containing dashes to camel case
1818
+ * @param { String } string - input string
1819
+ * @returns { String } my-string -> myString
1820
+ */
1821
+ function toCamel(string) {
1822
+ return string.replace(/-(\w)/g, function(_, c) {
1823
+ return c.toUpperCase()
1824
+ })
1032
1825
  }
1033
1826
 
1034
- function initChildTag(child, dom, parent) {
1035
- var tag = new Tag(child, { root: dom, parent: parent }, dom.innerHTML),
1036
- tagName = getTagName(dom),
1037
- ptag = getImmediateCustomParentTag(parent),
1038
- cachedTag
1827
+ /**
1828
+ * Get the value of any DOM attribute on a node
1829
+ * @param { Object } dom - DOM node we want to parse
1830
+ * @param { String } name - name of the attribute we want to get
1831
+ * @returns { String | undefined } name of the node attribute whether it exists
1832
+ */
1833
+ function getAttr(dom, name) {
1834
+ return dom.getAttribute(name)
1835
+ }
1039
1836
 
1040
- // fix for the parent attribute in the looped elements
1041
- tag.parent = ptag
1837
+ /**
1838
+ * Set any DOM attribute
1839
+ * @param { Object } dom - DOM node we want to update
1840
+ * @param { String } name - name of the property we want to set
1841
+ * @param { String } val - value of the property we want to set
1842
+ */
1843
+ function setAttr(dom, name, val) {
1844
+ dom.setAttribute(name, val)
1845
+ }
1042
1846
 
1043
- cachedTag = ptag.tags[tagName]
1847
+ /**
1848
+ * Detect the tag implementation by a DOM node
1849
+ * @param { Object } dom - DOM node we need to parse to get its tag implementation
1850
+ * @returns { Object } it returns an object containing the implementation of a custom tag (template and boot function)
1851
+ */
1852
+ function getTag(dom) {
1853
+ return dom.tagName && __tagImpl[getAttr(dom, RIOT_TAG) || dom.tagName.toLowerCase()]
1854
+ }
1855
+ /**
1856
+ * Add a child tag to its parent into the `tags` object
1857
+ * @param { Object } tag - child tag instance
1858
+ * @param { String } tagName - key where the new tag will be stored
1859
+ * @param { Object } parent - tag instance where the new child tag will be included
1860
+ */
1861
+ function addChildTag(tag, tagName, parent) {
1862
+ var cachedTag = parent.tags[tagName]
1044
1863
 
1045
1864
  // if there are multiple children tags having the same name
1046
1865
  if (cachedTag) {
1047
1866
  // if the parent tags property is not yet an array
1048
1867
  // create it adding the first cached tag
1049
1868
  if (!isArray(cachedTag))
1050
- ptag.tags[tagName] = [cachedTag]
1869
+ // don't add the same tag twice
1870
+ if (cachedTag !== tag)
1871
+ parent.tags[tagName] = [cachedTag]
1051
1872
  // add the new nested tag to the array
1052
- if (!~ptag.tags[tagName].indexOf(tag))
1053
- ptag.tags[tagName].push(tag)
1873
+ if (!contains(parent.tags[tagName], tag))
1874
+ parent.tags[tagName].push(tag)
1054
1875
  } else {
1055
- ptag.tags[tagName] = tag
1876
+ parent.tags[tagName] = tag
1056
1877
  }
1878
+ }
1879
+
1880
+ /**
1881
+ * Move the position of a custom tag in its parent tag
1882
+ * @param { Object } tag - child tag instance
1883
+ * @param { String } tagName - key where the tag was stored
1884
+ * @param { Number } newPos - index where the new tag will be stored
1885
+ */
1886
+ function moveChildTag(tag, tagName, newPos) {
1887
+ var parent = tag.parent,
1888
+ tags
1889
+ // no parent no move
1890
+ if (!parent) return
1891
+
1892
+ tags = parent.tags[tagName]
1893
+
1894
+ if (isArray(tags))
1895
+ tags.splice(newPos, 0, tags.splice(tags.indexOf(tag), 1)[0])
1896
+ else addChildTag(tag, tagName, parent)
1897
+ }
1057
1898
 
1899
+ /**
1900
+ * Create a new child tag including it correctly into its parent
1901
+ * @param { Object } child - child tag implementation
1902
+ * @param { Object } opts - tag options containing the DOM node where the tag will be mounted
1903
+ * @param { String } innerHTML - inner html of the child node
1904
+ * @param { Object } parent - instance of the parent tag including the child custom tag
1905
+ * @returns { Object } instance of the new child tag just created
1906
+ */
1907
+ function initChildTag(child, opts, innerHTML, parent) {
1908
+ var tag = new Tag(child, opts, innerHTML),
1909
+ tagName = getTagName(opts.root),
1910
+ ptag = getImmediateCustomParentTag(parent)
1911
+ // fix for the parent attribute in the looped elements
1912
+ tag.parent = ptag
1913
+ // store the real parent tag
1914
+ // in some cases this could be different from the custom parent tag
1915
+ // for example in nested loops
1916
+ tag._parent = parent
1917
+
1918
+ // add this tag to the custom parent tag
1919
+ addChildTag(tag, tagName, ptag)
1920
+ // and also to the real parent tag
1921
+ if (ptag !== parent)
1922
+ addChildTag(tag, tagName, parent)
1058
1923
  // empty the child node once we got its template
1059
1924
  // to avoid that its children get compiled multiple times
1060
- dom.innerHTML = ''
1925
+ opts.root.innerHTML = ''
1061
1926
 
1062
1927
  return tag
1063
1928
  }
1064
1929
 
1930
+ /**
1931
+ * Loop backward all the parents tree to detect the first custom parent tag
1932
+ * @param { Object } tag - a Tag instance
1933
+ * @returns { Object } the instance of the first custom parent tag found
1934
+ */
1065
1935
  function getImmediateCustomParentTag(tag) {
1066
1936
  var ptag = tag
1067
1937
  while (!getTag(ptag.root)) {
@@ -1071,40 +1941,117 @@ function getImmediateCustomParentTag(tag) {
1071
1941
  return ptag
1072
1942
  }
1073
1943
 
1944
+ /**
1945
+ * Helper function to set an immutable property
1946
+ * @param { Object } el - object where the new property will be set
1947
+ * @param { String } key - object key where the new property will be stored
1948
+ * @param { * } value - value of the new property
1949
+ * @param { Object } options - set the propery overriding the default options
1950
+ * @returns { Object } - the initial object
1951
+ */
1952
+ function defineProperty(el, key, value, options) {
1953
+ Object.defineProperty(el, key, extend({
1954
+ value: value,
1955
+ enumerable: false,
1956
+ writable: false,
1957
+ configurable: false
1958
+ }, options))
1959
+ return el
1960
+ }
1961
+
1962
+ /**
1963
+ * Get the tag name of any DOM node
1964
+ * @param { Object } dom - DOM node we want to parse
1965
+ * @returns { String } name to identify this dom node in riot
1966
+ */
1074
1967
  function getTagName(dom) {
1075
1968
  var child = getTag(dom),
1076
- namedTag = dom.getAttribute('name'),
1077
- tagName = namedTag && namedTag.indexOf(brackets(0)) < 0 ? namedTag : child ? child.name : dom.tagName.toLowerCase()
1969
+ namedTag = getAttr(dom, 'name'),
1970
+ tagName = namedTag && !tmpl.hasExpr(namedTag) ?
1971
+ namedTag :
1972
+ child ? child.name : dom.tagName.toLowerCase()
1078
1973
 
1079
1974
  return tagName
1080
1975
  }
1081
1976
 
1977
+ /**
1978
+ * Extend any object with other properties
1979
+ * @param { Object } src - source object
1980
+ * @returns { Object } the resulting extended object
1981
+ *
1982
+ * var obj = { foo: 'baz' }
1983
+ * extend(obj, {bar: 'bar', foo: 'bar'})
1984
+ * console.log(obj) => {bar: 'bar', foo: 'bar'}
1985
+ *
1986
+ */
1082
1987
  function extend(src) {
1083
1988
  var obj, args = arguments
1084
1989
  for (var i = 1; i < args.length; ++i) {
1085
- if ((obj = args[i])) {
1086
- for (var key in obj) { // eslint-disable-line guard-for-in
1087
- src[key] = obj[key]
1990
+ if (obj = args[i]) {
1991
+ for (var key in obj) {
1992
+ // check if this property of the source object could be overridden
1993
+ if (isWritable(src, key))
1994
+ src[key] = obj[key]
1088
1995
  }
1089
1996
  }
1090
1997
  }
1091
1998
  return src
1092
1999
  }
1093
2000
 
1094
- // with this function we avoid that the current Tag methods get overridden
2001
+ /**
2002
+ * Check whether an array contains an item
2003
+ * @param { Array } arr - target array
2004
+ * @param { * } item - item to test
2005
+ * @returns { Boolean } Does 'arr' contain 'item'?
2006
+ */
2007
+ function contains(arr, item) {
2008
+ return ~arr.indexOf(item)
2009
+ }
2010
+
2011
+ /**
2012
+ * Check whether an object is a kind of array
2013
+ * @param { * } a - anything
2014
+ * @returns {Boolean} is 'a' an array?
2015
+ */
2016
+ function isArray(a) { return Array.isArray(a) || a instanceof Array }
2017
+
2018
+ /**
2019
+ * Detect whether a property of an object could be overridden
2020
+ * @param { Object } obj - source object
2021
+ * @param { String } key - object property
2022
+ * @returns { Boolean } is this property writable?
2023
+ */
2024
+ function isWritable(obj, key) {
2025
+ var props = Object.getOwnPropertyDescriptor(obj, key)
2026
+ return typeof obj[key] === T_UNDEF || props && props.writable
2027
+ }
2028
+
2029
+
2030
+ /**
2031
+ * With this function we avoid that the internal Tag methods get overridden
2032
+ * @param { Object } data - options we want to use to extend the tag instance
2033
+ * @returns { Object } clean object without containing the riot internal reserved words
2034
+ */
1095
2035
  function cleanUpData(data) {
1096
- if (!(data instanceof Tag) && !(data && typeof data.trigger == T_FUNCTION)) return data
2036
+ if (!(data instanceof Tag) && !(data && typeof data.trigger == T_FUNCTION))
2037
+ return data
1097
2038
 
1098
2039
  var o = {}
1099
2040
  for (var key in data) {
1100
- if (!~RESERVED_WORDS_BLACKLIST.indexOf(key))
2041
+ if (!contains(RESERVED_WORDS_BLACKLIST, key))
1101
2042
  o[key] = data[key]
1102
2043
  }
1103
2044
  return o
1104
2045
  }
1105
2046
 
2047
+ /**
2048
+ * Walk down recursively all the children tags starting dom node
2049
+ * @param { Object } dom - starting node where we will start the recursion
2050
+ * @param { Function } fn - callback to transform the child node just found
2051
+ */
1106
2052
  function walk(dom, fn) {
1107
2053
  if (dom) {
2054
+ // stop the recursion
1108
2055
  if (fn(dom) === false) return
1109
2056
  else {
1110
2057
  dom = dom.firstChild
@@ -1117,16 +2064,25 @@ function walk(dom, fn) {
1117
2064
  }
1118
2065
  }
1119
2066
 
1120
- // minimize risk: only zero or one _space_ between attr & value
2067
+ /**
2068
+ * Minimize risk: only zero or one _space_ between attr & value
2069
+ * @param { String } html - html string we want to parse
2070
+ * @param { Function } fn - callback function to apply on any attribute found
2071
+ */
1121
2072
  function walkAttributes(html, fn) {
1122
2073
  var m,
1123
- re = /([-\w]+) ?= ?(?:"([^"]*)|'([^']*)|({[^}]*}))/g
2074
+ re = /([-\w]+) ?= ?(?:"([^"]*)|'([^']*)|({[^}]*}))/g
1124
2075
 
1125
- while ((m = re.exec(html))) {
2076
+ while (m = re.exec(html)) {
1126
2077
  fn(m[1].toLowerCase(), m[2] || m[3] || m[4])
1127
2078
  }
1128
2079
  }
1129
2080
 
2081
+ /**
2082
+ * Check whether a DOM node is in stub mode, useful for the riot 'if' directive
2083
+ * @param { Object } dom - DOM node we want to parse
2084
+ * @returns { Boolean } -
2085
+ */
1130
2086
  function isInStub(dom) {
1131
2087
  while (dom) {
1132
2088
  if (dom.inStub) return true
@@ -1135,97 +2091,141 @@ function isInStub(dom) {
1135
2091
  return false
1136
2092
  }
1137
2093
 
2094
+ /**
2095
+ * Create a generic DOM node
2096
+ * @param { String } name - name of the DOM node we want to create
2097
+ * @returns { Object } DOM node just created
2098
+ */
1138
2099
  function mkEl(name) {
1139
2100
  return document.createElement(name)
1140
2101
  }
1141
2102
 
1142
- function replaceYield(tmpl, innerHTML) {
1143
- return tmpl.replace(/<(yield)\/?>(<\/\1>)?/gi, innerHTML || '')
1144
- }
1145
-
2103
+ /**
2104
+ * Shorter and fast way to select multiple nodes in the DOM
2105
+ * @param { String } selector - DOM selector
2106
+ * @param { Object } ctx - DOM node where the targets of our search will is located
2107
+ * @returns { Object } dom nodes found
2108
+ */
1146
2109
  function $$(selector, ctx) {
1147
2110
  return (ctx || document).querySelectorAll(selector)
1148
2111
  }
1149
2112
 
2113
+ /**
2114
+ * Shorter and fast way to select a single node in the DOM
2115
+ * @param { String } selector - unique dom selector
2116
+ * @param { Object } ctx - DOM node where the target of our search will is located
2117
+ * @returns { Object } dom node found
2118
+ */
1150
2119
  function $(selector, ctx) {
1151
2120
  return (ctx || document).querySelector(selector)
1152
2121
  }
1153
2122
 
2123
+ /**
2124
+ * Simple object prototypal inheritance
2125
+ * @param { Object } parent - parent object
2126
+ * @returns { Object } child instance
2127
+ */
1154
2128
  function inherit(parent) {
1155
2129
  function Child() {}
1156
2130
  Child.prototype = parent
1157
2131
  return new Child()
1158
2132
  }
1159
2133
 
2134
+ /**
2135
+ * Get the name property needed to identify a DOM node in riot
2136
+ * @param { Object } dom - DOM node we need to parse
2137
+ * @returns { String | undefined } give us back a string to identify this dom node
2138
+ */
2139
+ function getNamedKey(dom) {
2140
+ return getAttr(dom, 'id') || getAttr(dom, 'name')
2141
+ }
2142
+
2143
+ /**
2144
+ * Set the named properties of a tag element
2145
+ * @param { Object } dom - DOM node we need to parse
2146
+ * @param { Object } parent - tag instance where the named dom element will be eventually added
2147
+ * @param { Array } keys - list of all the tag instance properties
2148
+ */
1160
2149
  function setNamed(dom, parent, keys) {
1161
- if (dom._visited) return
1162
- var p,
1163
- v = dom.getAttribute('id') || dom.getAttribute('name')
1164
-
1165
- if (v) {
1166
- if (keys.indexOf(v) < 0) {
1167
- p = parent[v]
1168
- if (!p)
1169
- parent[v] = dom
1170
- else if (isArray(p))
1171
- p.push(dom)
1172
- else
1173
- parent[v] = [p, dom]
2150
+ // get the key value we want to add to the tag instance
2151
+ var key = getNamedKey(dom),
2152
+ isArr,
2153
+ // add the node detected to a tag instance using the named property
2154
+ add = function(value) {
2155
+ // avoid to override the tag properties already set
2156
+ if (contains(keys, key)) return
2157
+ // check whether this value is an array
2158
+ isArr = isArray(value)
2159
+ // if the key was never set
2160
+ if (!value)
2161
+ // set it once on the tag instance
2162
+ parent[key] = dom
2163
+ // if it was an array and not yet set
2164
+ else if (!isArr || isArr && !contains(value, dom)) {
2165
+ // add the dom node into the array
2166
+ if (isArr)
2167
+ value.push(dom)
2168
+ else
2169
+ parent[key] = [value, dom]
2170
+ }
1174
2171
  }
1175
- dom._visited = true
1176
- }
2172
+
2173
+ // skip the elements with no named properties
2174
+ if (!key) return
2175
+
2176
+ // check whether this key has been already evaluated
2177
+ if (tmpl.hasExpr(key))
2178
+ // wait the first updated event only once
2179
+ parent.one('mount', function() {
2180
+ key = getNamedKey(dom)
2181
+ add(parent[key])
2182
+ })
2183
+ else
2184
+ add(parent[key])
2185
+
1177
2186
  }
1178
2187
 
1179
- // faster String startsWith alternative
2188
+ /**
2189
+ * Faster String startsWith alternative
2190
+ * @param { String } src - source string
2191
+ * @param { String } str - test string
2192
+ * @returns { Boolean } -
2193
+ */
1180
2194
  function startsWith(src, str) {
1181
2195
  return src.slice(0, str.length) === str
1182
2196
  }
1183
2197
 
1184
- /*
1185
- Virtual dom is an array of custom tags on the document.
1186
- Updates and unmounts propagate downwards from parent to children.
1187
- */
1188
-
1189
- var virtualDom = [],
1190
- tagImpl = {},
1191
- styleNode
1192
-
1193
- function injectStyle(css) {
1194
-
1195
- if (riot.render) return // skip injection on the server
1196
-
1197
- if (!styleNode) {
1198
- styleNode = mkEl('style')
1199
- styleNode.setAttribute('type', 'text/css')
1200
- }
1201
-
1202
- var head = document.head || document.getElementsByTagName('head')[0]
1203
-
1204
- if (styleNode.styleSheet)
1205
- styleNode.styleSheet.cssText += css
1206
- else
1207
- styleNode.innerHTML += css
2198
+ /**
2199
+ * requestAnimationFrame function
2200
+ * Adapted from https://gist.github.com/paulirish/1579671, license MIT
2201
+ */
2202
+ var rAF = (function (w) {
2203
+ var raf = w.requestAnimationFrame ||
2204
+ w.mozRequestAnimationFrame || w.webkitRequestAnimationFrame
1208
2205
 
1209
- if (!styleNode._rendered)
1210
- if (styleNode.styleSheet) {
1211
- document.body.appendChild(styleNode)
1212
- } else {
1213
- var rs = $('style[type=riot]')
1214
- if (rs) {
1215
- rs.parentNode.insertBefore(styleNode, rs)
1216
- rs.parentNode.removeChild(rs)
1217
- } else head.appendChild(styleNode)
2206
+ if (!raf || /iP(ad|hone|od).*OS 6/.test(w.navigator.userAgent)) { // buggy iOS6
2207
+ var lastTime = 0
1218
2208
 
2209
+ raf = function (cb) {
2210
+ var nowtime = Date.now(), timeout = Math.max(16 - (nowtime - lastTime), 0)
2211
+ setTimeout(function () { cb(lastTime = nowtime + timeout) }, timeout)
1219
2212
  }
2213
+ }
2214
+ return raf
1220
2215
 
1221
- styleNode._rendered = true
1222
-
1223
- }
2216
+ })(window || {})
1224
2217
 
2218
+ /**
2219
+ * Mount a tag creating new Tag instance
2220
+ * @param { Object } root - dom node where the tag will be mounted
2221
+ * @param { String } tagName - name of the riot tag we want to mount
2222
+ * @param { Object } opts - options to pass to the Tag instance
2223
+ * @returns { Tag } a new Tag instance
2224
+ */
1225
2225
  function mountTo(root, tagName, opts) {
1226
- var tag = tagImpl[tagName],
1227
- // cache the inner HTML to fix #855
1228
- innerHTML = root._innerHTML = root._innerHTML || root.innerHTML
2226
+ var tag = __tagImpl[tagName],
2227
+ // cache the inner HTML to fix #855
2228
+ innerHTML = root._innerHTML = root._innerHTML || root.innerHTML
1229
2229
 
1230
2230
  // clear the inner html
1231
2231
  root.innerHTML = ''
@@ -1234,14 +2234,47 @@ function mountTo(root, tagName, opts) {
1234
2234
 
1235
2235
  if (tag && tag.mount) {
1236
2236
  tag.mount()
1237
- virtualDom.push(tag)
1238
- return tag.on('unmount', function() {
1239
- virtualDom.splice(virtualDom.indexOf(tag), 1)
1240
- })
2237
+ // add this tag to the virtualDom variable
2238
+ if (!contains(__virtualDom, tag)) __virtualDom.push(tag)
1241
2239
  }
1242
2240
 
2241
+ return tag
1243
2242
  }
2243
+ /**
2244
+ * Riot public api
2245
+ */
2246
+
2247
+ // share methods for other riot parts, e.g. compiler
2248
+ riot.util = { brackets: brackets, tmpl: tmpl }
2249
+
2250
+ /**
2251
+ * Create a mixin that could be globally shared across all the tags
2252
+ */
2253
+ riot.mixin = (function() {
2254
+ var mixins = {}
1244
2255
 
2256
+ /**
2257
+ * Create/Return a mixin by its name
2258
+ * @param { String } name - mixin name
2259
+ * @param { Object } mixin - mixin logic
2260
+ * @returns { Object } the mixin logic
2261
+ */
2262
+ return function(name, mixin) {
2263
+ if (!mixin) return mixins[name]
2264
+ mixins[name] = mixin
2265
+ }
2266
+
2267
+ })()
2268
+
2269
+ /**
2270
+ * Create a new riot tag implementation
2271
+ * @param { String } name - name/id of the new riot tag
2272
+ * @param { String } html - tag template
2273
+ * @param { String } css - custom tag css
2274
+ * @param { String } attrs - root tag attributes
2275
+ * @param { Function } fn - user function
2276
+ * @returns { String } name/id of the tag just created
2277
+ */
1245
2278
  riot.tag = function(name, html, css, attrs, fn) {
1246
2279
  if (isFunction(attrs)) {
1247
2280
  fn = attrs
@@ -1252,51 +2285,78 @@ riot.tag = function(name, html, css, attrs, fn) {
1252
2285
  }
1253
2286
  if (css) {
1254
2287
  if (isFunction(css)) fn = css
1255
- else injectStyle(css)
2288
+ else styleManager.add(css)
1256
2289
  }
1257
- tagImpl[name] = { name: name, tmpl: html, attrs: attrs, fn: fn }
2290
+ __tagImpl[name] = { name: name, tmpl: html, attrs: attrs, fn: fn }
2291
+ return name
2292
+ }
2293
+
2294
+ /**
2295
+ * Create a new riot tag implementation (for use by the compiler)
2296
+ * @param { String } name - name/id of the new riot tag
2297
+ * @param { String } html - tag template
2298
+ * @param { String } css - custom tag css
2299
+ * @param { String } attrs - root tag attributes
2300
+ * @param { Function } fn - user function
2301
+ * @param { string } [bpair] - brackets used in the compilation
2302
+ * @returns { String } name/id of the tag just created
2303
+ */
2304
+ riot.tag2 = function(name, html, css, attrs, fn, bpair) {
2305
+ if (css) styleManager.add(css)
2306
+ //if (bpair) riot.settings.brackets = bpair
2307
+ __tagImpl[name] = { name: name, tmpl: html, attrs: attrs, fn: fn }
1258
2308
  return name
1259
2309
  }
1260
2310
 
2311
+ /**
2312
+ * Mount a tag using a specific tag implementation
2313
+ * @param { String } selector - tag DOM selector
2314
+ * @param { String } tagName - tag implementation name
2315
+ * @param { Object } opts - tag logic
2316
+ * @returns { Array } new tags instances
2317
+ */
1261
2318
  riot.mount = function(selector, tagName, opts) {
1262
2319
 
1263
2320
  var els,
1264
- allTags,
1265
- tags = []
2321
+ allTags,
2322
+ tags = []
1266
2323
 
1267
2324
  // helper functions
1268
2325
 
1269
2326
  function addRiotTags(arr) {
1270
2327
  var list = ''
1271
2328
  each(arr, function (e) {
1272
- list += ', *[' + RIOT_TAG + '="' + e.trim() + '"]'
2329
+ if (!/[^-\w]/.test(e))
2330
+ list += ',*[' + RIOT_TAG + '=' + e.trim() + ']'
1273
2331
  })
1274
2332
  return list
1275
2333
  }
1276
2334
 
1277
2335
  function selectAllTags() {
1278
- var keys = Object.keys(tagImpl)
2336
+ var keys = Object.keys(__tagImpl)
1279
2337
  return keys + addRiotTags(keys)
1280
2338
  }
1281
2339
 
1282
2340
  function pushTags(root) {
1283
2341
  var last
2342
+
1284
2343
  if (root.tagName) {
1285
- if (tagName && (!(last = root.getAttribute(RIOT_TAG)) || last != tagName))
1286
- root.setAttribute(RIOT_TAG, tagName)
2344
+ if (tagName && (!(last = getAttr(root, RIOT_TAG)) || last != tagName))
2345
+ setAttr(root, RIOT_TAG, tagName)
1287
2346
 
1288
- var tag = mountTo(root,
1289
- tagName || root.getAttribute(RIOT_TAG) || root.tagName.toLowerCase(), opts)
2347
+ var tag = mountTo(root, tagName || root.getAttribute(RIOT_TAG) || root.tagName.toLowerCase(), opts)
1290
2348
 
1291
2349
  if (tag) tags.push(tag)
1292
- }
1293
- else if (root.length) {
2350
+ } else if (root.length)
1294
2351
  each(root, pushTags) // assume nodeList
1295
- }
2352
+
1296
2353
  }
1297
2354
 
1298
2355
  // ----- mount code -----
1299
2356
 
2357
+ // inject styles into DOM
2358
+ styleManager.inject()
2359
+
1300
2360
  if (typeof tagName === T_OBJECT) {
1301
2361
  opts = tagName
1302
2362
  tagName = 0
@@ -1312,7 +2372,9 @@ riot.mount = function(selector, tagName, opts) {
1312
2372
  // or just the ones named like the selector
1313
2373
  selector += addRiotTags(selector.split(','))
1314
2374
 
1315
- els = $$(selector)
2375
+ // make sure to pass always a selector
2376
+ // to the querySelectorAll function
2377
+ els = selector ? $$(selector) : []
1316
2378
  }
1317
2379
  else
1318
2380
  // probably you have passed already a tag or a NodeList
@@ -1345,25 +2407,26 @@ riot.mount = function(selector, tagName, opts) {
1345
2407
  return tags
1346
2408
  }
1347
2409
 
1348
- // update everything
2410
+ /**
2411
+ * Update all the tags instances created
2412
+ * @returns { Array } all the tags instances
2413
+ */
1349
2414
  riot.update = function() {
1350
- return each(virtualDom, function(tag) {
2415
+ return each(__virtualDom, function(tag) {
1351
2416
  tag.update()
1352
2417
  })
1353
2418
  }
1354
2419
 
1355
- // @deprecated
1356
- riot.mountTo = riot.mount
1357
-
1358
- // share methods for other riot parts, e.g. compiler
1359
- riot.util = { brackets: brackets, tmpl: tmpl }
1360
-
2420
+ /**
2421
+ * Export the Tag constructor
2422
+ */
2423
+ riot.Tag = Tag
1361
2424
  // support CommonJS, AMD & browser
1362
2425
  /* istanbul ignore next */
1363
2426
  if (typeof exports === T_OBJECT)
1364
2427
  module.exports = riot
1365
- else if (typeof define === 'function' && define.amd)
1366
- define(function() { return (window.riot = riot) })
2428
+ else if (typeof define === T_FUNCTION && typeof define.amd !== T_UNDEF)
2429
+ define(function() { return riot })
1367
2430
  else
1368
2431
  window.riot = riot
1369
2432