riot_js-rails 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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