/* Riot v2.6.1, @license MIT */
;(function(window, undefined) {
'use strict';
var riot = { version: 'v2.6.1', settings: {} },
// be aware, internal usage
// ATTENTION: prefix the global dynamic variables with `__`
// counter to give a unique id to all the Tag instances
__uid = 0,
// tags instances cache
__virtualDom = [],
// tags implementation cache
__tagImpl = {},
/**
* Const
*/
GLOBAL_MIXIN = '__global_mixin',
// riot specific prefixes
RIOT_PREFIX = 'riot-',
RIOT_TAG = RIOT_PREFIX + 'tag',
RIOT_TAG_IS = 'data-is',
// for typeof == '' comparisons
T_STRING = 'string',
T_OBJECT = 'object',
T_UNDEF = 'undefined',
T_FUNCTION = 'function',
XLINK_NS = 'http://www.w3.org/1999/xlink',
XLINK_REGEX = /^xlink:(\w+)/,
// special native tags that cannot be treated like the others
SPECIAL_TAGS_REGEX = /^(?:t(?:body|head|foot|[rhd])|caption|col(?:group)?|opt(?:ion|group))$/,
RESERVED_WORDS_BLACKLIST = /^(?:_(?:item|id|parent)|update|root|(?:un)?mount|mixin|is(?:Mounted|Loop)|tags|parent|opts|trigger|o(?:n|ff|ne))$/,
// SVG tags list https://www.w3.org/TR/SVG/attindex.html#PresentationAttributes
SVG_TAGS_LIST = ['altGlyph', 'animate', 'animateColor', 'circle', 'clipPath', 'defs', 'ellipse', 'feBlend', 'feColorMatrix', 'feComponentTransfer', 'feComposite', 'feConvolveMatrix', 'feDiffuseLighting', 'feDisplacementMap', 'feFlood', 'feGaussianBlur', 'feImage', 'feMerge', 'feMorphology', 'feOffset', 'feSpecularLighting', 'feTile', 'feTurbulence', 'filter', 'font', 'foreignObject', 'g', 'glyph', 'glyphRef', 'image', 'line', 'linearGradient', 'marker', 'mask', 'missing-glyph', 'path', 'pattern', 'polygon', 'polyline', 'radialGradient', 'rect', 'stop', 'svg', 'switch', 'symbol', 'text', 'textPath', 'tref', 'tspan', 'use'],
// version# for IE 8-11, 0 for others
IE_VERSION = (window && window.document || {}).documentMode | 0,
// detect firefox to fix #1374
FIREFOX = window && !!window.InstallTrigger
/* istanbul ignore next */
riot.observable = function(el) {
/**
* Extend the original object or create a new empty one
* @type { Object }
*/
el = el || {}
/**
* Private variables
*/
var callbacks = {},
slice = Array.prototype.slice
/**
* Private Methods
*/
/**
* Helper function needed to get and loop all the events in a string
* @param { String } e - event string
* @param {Function} fn - callback
*/
function onEachEvent(e, fn) {
var es = e.split(' '), l = es.length, i = 0
for (; i < l; i++) {
var name = es[i]
if (name) fn(name, i)
}
}
/**
* Public Api
*/
// extend the el object adding the observable methods
Object.defineProperties(el, {
/**
* Listen to the given space separated list of `events` and
* execute the `callback` each time an event is triggered.
* @param { String } events - events ids
* @param { Function } fn - callback function
* @returns { Object } el
*/
on: {
value: function(events, fn) {
if (typeof fn != 'function') return el
onEachEvent(events, function(name, pos) {
(callbacks[name] = callbacks[name] || []).push(fn)
fn.typed = pos > 0
})
return el
},
enumerable: false,
writable: false,
configurable: false
},
/**
* Removes the given space separated list of `events` listeners
* @param { String } events - events ids
* @param { Function } fn - callback function
* @returns { Object } el
*/
off: {
value: function(events, fn) {
if (events == '*' && !fn) callbacks = {}
else {
onEachEvent(events, function(name, pos) {
if (fn) {
var arr = callbacks[name]
for (var i = 0, cb; cb = arr && arr[i]; ++i) {
if (cb == fn) arr.splice(i--, 1)
}
} else delete callbacks[name]
})
}
return el
},
enumerable: false,
writable: false,
configurable: false
},
/**
* Listen to the given space separated list of `events` and
* execute the `callback` at most once
* @param { String } events - events ids
* @param { Function } fn - callback function
* @returns { Object } el
*/
one: {
value: function(events, fn) {
function on() {
el.off(events, on)
fn.apply(el, arguments)
}
return el.on(events, on)
},
enumerable: false,
writable: false,
configurable: false
},
/**
* Execute all callback functions that listen to
* the given space separated list of `events`
* @param { String } events - events ids
* @returns { Object } el
*/
trigger: {
value: function(events) {
// getting the arguments
var arglen = arguments.length - 1,
args = new Array(arglen),
fns
for (var i = 0; i < arglen; i++) {
args[i] = arguments[i + 1] // skip first argument
}
onEachEvent(events, function(name, pos) {
fns = slice.call(callbacks[name] || [], 0)
for (var i = 0, fn; fn = fns[i]; ++i) {
if (fn.busy) continue
fn.busy = 1
fn.apply(el, fn.typed ? [name].concat(args) : args)
if (fns[i] !== fn) { i-- }
fn.busy = 0
}
if (callbacks['*'] && name != '*')
el.trigger.apply(el, ['*', name].concat(args))
})
return el
},
enumerable: false,
writable: false,
configurable: false
}
})
return el
}
/* istanbul ignore next */
;(function(riot) {
/**
* Simple client-side router
* @module riot-route
*/
var RE_ORIGIN = /^.+?\/\/+[^\/]+/,
EVENT_LISTENER = 'EventListener',
REMOVE_EVENT_LISTENER = 'remove' + EVENT_LISTENER,
ADD_EVENT_LISTENER = 'add' + EVENT_LISTENER,
HAS_ATTRIBUTE = 'hasAttribute',
REPLACE = 'replace',
POPSTATE = 'popstate',
HASHCHANGE = 'hashchange',
TRIGGER = 'trigger',
MAX_EMIT_STACK_LEVEL = 3,
win = typeof window != 'undefined' && window,
doc = typeof document != 'undefined' && document,
hist = win && history,
loc = win && (hist.location || win.location), // see html5-history-api
prot = Router.prototype, // to minify more
clickEvent = doc && doc.ontouchstart ? 'touchstart' : 'click',
started = false,
central = riot.observable(),
routeFound = false,
debouncedEmit,
base, current, parser, secondParser, emitStack = [], emitStackLevel = 0
/**
* Default parser. You can replace it via router.parser method.
* @param {string} path - current path (normalized)
* @returns {array} array
*/
function DEFAULT_PARSER(path) {
return path.split(/[/?#]/)
}
/**
* Default parser (second). You can replace it via router.parser method.
* @param {string} path - current path (normalized)
* @param {string} filter - filter string (normalized)
* @returns {array} array
*/
function DEFAULT_SECOND_PARSER(path, filter) {
var re = new RegExp('^' + filter[REPLACE](/\*/g, '([^/?#]+?)')[REPLACE](/\.\./, '.*') + '$'),
args = path.match(re)
if (args) return args.slice(1)
}
/**
* Simple/cheap debounce implementation
* @param {function} fn - callback
* @param {number} delay - delay in seconds
* @returns {function} debounced function
*/
function debounce(fn, delay) {
var t
return function () {
clearTimeout(t)
t = setTimeout(fn, delay)
}
}
/**
* Set the window listeners to trigger the routes
* @param {boolean} autoExec - see route.start
*/
function start(autoExec) {
debouncedEmit = debounce(emit, 1)
win[ADD_EVENT_LISTENER](POPSTATE, debouncedEmit)
win[ADD_EVENT_LISTENER](HASHCHANGE, debouncedEmit)
doc[ADD_EVENT_LISTENER](clickEvent, click)
if (autoExec) emit(true)
}
/**
* Router class
*/
function Router() {
this.$ = []
riot.observable(this) // make it observable
central.on('stop', this.s.bind(this))
central.on('emit', this.e.bind(this))
}
function normalize(path) {
return path[REPLACE](/^\/|\/$/, '')
}
function isString(str) {
return typeof str == 'string'
}
/**
* Get the part after domain name
* @param {string} href - fullpath
* @returns {string} path from root
*/
function getPathFromRoot(href) {
return (href || loc.href)[REPLACE](RE_ORIGIN, '')
}
/**
* Get the part after base
* @param {string} href - fullpath
* @returns {string} path from base
*/
function getPathFromBase(href) {
return base[0] == '#'
? (href || loc.href || '').split(base)[1] || ''
: (loc ? getPathFromRoot(href) : href || '')[REPLACE](base, '')
}
function emit(force) {
// the stack is needed for redirections
var isRoot = emitStackLevel == 0, first
if (MAX_EMIT_STACK_LEVEL <= emitStackLevel) return
emitStackLevel++
emitStack.push(function() {
var path = getPathFromBase()
if (force || path != current) {
central[TRIGGER]('emit', path)
current = path
}
})
if (isRoot) {
while (first = emitStack.shift()) first() // stack increses within this call
emitStackLevel = 0
}
}
function click(e) {
if (
e.which != 1 // not left click
|| e.metaKey || e.ctrlKey || e.shiftKey // or meta keys
|| e.defaultPrevented // or default prevented
) return
var el = e.target
while (el && el.nodeName != 'A') el = el.parentNode
if (
!el || el.nodeName != 'A' // not A tag
|| el[HAS_ATTRIBUTE]('download') // has download attr
|| !el[HAS_ATTRIBUTE]('href') // has no href attr
|| el.target && el.target != '_self' // another window or frame
|| el.href.indexOf(loc.href.match(RE_ORIGIN)[0]) == -1 // cross origin
) return
if (el.href != loc.href
&& (
el.href.split('#')[0] == loc.href.split('#')[0] // internal jump
|| base[0] != '#' && getPathFromRoot(el.href).indexOf(base) !== 0 // outside of base
|| base[0] == '#' && el.href.split(base)[0] != loc.href.split(base)[0] // outside of #base
|| !go(getPathFromBase(el.href), el.title || doc.title) // route not found
)) return
e.preventDefault()
}
/**
* Go to the path
* @param {string} path - destination path
* @param {string} title - page title
* @param {boolean} shouldReplace - use replaceState or pushState
* @returns {boolean} - route not found flag
*/
function go(path, title, shouldReplace) {
// Server-side usage: directly execute handlers for the path
if (!hist) return central[TRIGGER]('emit', getPathFromBase(path))
path = base + normalize(path)
title = title || doc.title
// browsers ignores the second parameter `title`
shouldReplace
? hist.replaceState(null, title, path)
: hist.pushState(null, title, path)
// so we need to set it manually
doc.title = title
routeFound = false
emit()
return routeFound
}
/**
* Go to path or set action
* a single string: go there
* two strings: go there with setting a title
* two strings and boolean: replace history with setting a title
* a single function: set an action on the default route
* a string/RegExp and a function: set an action on the route
* @param {(string|function)} first - path / action / filter
* @param {(string|RegExp|function)} second - title / action
* @param {boolean} third - replace flag
*/
prot.m = function(first, second, third) {
if (isString(first) && (!second || isString(second))) go(first, second, third || false)
else if (second) this.r(first, second)
else this.r('@', first)
}
/**
* Stop routing
*/
prot.s = function() {
this.off('*')
this.$ = []
}
/**
* Emit
* @param {string} path - path
*/
prot.e = function(path) {
this.$.concat('@').some(function(filter) {
var args = (filter == '@' ? parser : secondParser)(normalize(path), normalize(filter))
if (typeof args != 'undefined') {
this[TRIGGER].apply(null, [filter].concat(args))
return routeFound = true // exit from loop
}
}, this)
}
/**
* Register route
* @param {string} filter - filter for matching to url
* @param {function} action - action to register
*/
prot.r = function(filter, action) {
if (filter != '@') {
filter = '/' + normalize(filter)
this.$.push(filter)
}
this.on(filter, action)
}
var mainRouter = new Router()
var route = mainRouter.m.bind(mainRouter)
/**
* Create a sub router
* @returns {function} the method of a new Router object
*/
route.create = function() {
var newSubRouter = new Router()
// assign sub-router's main method
var router = newSubRouter.m.bind(newSubRouter)
// stop only this sub-router
router.stop = newSubRouter.s.bind(newSubRouter)
return router
}
/**
* Set the base of url
* @param {(str|RegExp)} arg - a new base or '#' or '#!'
*/
route.base = function(arg) {
base = arg || '#'
current = getPathFromBase() // recalculate current path
}
/** Exec routing right now **/
route.exec = function() {
emit(true)
}
/**
* Replace the default router to yours
* @param {function} fn - your parser function
* @param {function} fn2 - your secondParser function
*/
route.parser = function(fn, fn2) {
if (!fn && !fn2) {
// reset parser for testing...
parser = DEFAULT_PARSER
secondParser = DEFAULT_SECOND_PARSER
}
if (fn) parser = fn
if (fn2) secondParser = fn2
}
/**
* Helper function to get url query as an object
* @returns {object} parsed query
*/
route.query = function() {
var q = {}
var href = loc.href || current
href[REPLACE](/[?&](.+?)=([^&]*)/g, function(_, k, v) { q[k] = v })
return q
}
/** Stop routing **/
route.stop = function () {
if (started) {
if (win) {
win[REMOVE_EVENT_LISTENER](POPSTATE, debouncedEmit)
win[REMOVE_EVENT_LISTENER](HASHCHANGE, debouncedEmit)
doc[REMOVE_EVENT_LISTENER](clickEvent, click)
}
central[TRIGGER]('stop')
started = false
}
}
/**
* Start routing
* @param {boolean} autoExec - automatically exec after starting if true
*/
route.start = function (autoExec) {
if (!started) {
if (win) {
if (document.readyState == 'complete') start(autoExec)
// the timeout is needed to solve
// a weird safari bug https://github.com/riot/route/issues/33
else win[ADD_EVENT_LISTENER]('load', function() {
setTimeout(function() { start(autoExec) }, 1)
})
}
started = true
}
}
/** Prepare the router **/
route.base()
route.parser()
riot.route = route
})(riot)
/* istanbul ignore next */
/**
* The riot template engine
* @version v2.4.1
*/
/**
* riot.util.brackets
*
* - `brackets ` - Returns a string or regex based on its parameter
* - `brackets.set` - Change the current riot brackets
*
* @module
*/
var brackets = (function (UNDEF) {
var
REGLOB = 'g',
R_MLCOMMS = /\/\*[^*]*\*+(?:[^*\/][^*]*\*+)*\//g,
R_STRINGS = /"[^"\\]*(?:\\[\S\s][^"\\]*)*"|'[^'\\]*(?:\\[\S\s][^'\\]*)*'/g,
S_QBLOCKS = R_STRINGS.source + '|' +
/(?:\breturn\s+|(?:[$\w\)\]]|\+\+|--)\s*(\/)(?![*\/]))/.source + '|' +
/\/(?=[^*\/])[^[\/\\]*(?:(?:\[(?:\\.|[^\]\\]*)*\]|\\.)[^[\/\\]*)*?(\/)[gim]*/.source,
UNSUPPORTED = RegExp('[\\' + 'x00-\\x1F<>a-zA-Z0-9\'",;\\\\]'),
NEED_ESCAPE = /(?=[[\]()*+?.^$|])/g,
FINDBRACES = {
'(': RegExp('([()])|' + S_QBLOCKS, REGLOB),
'[': RegExp('([[\\]])|' + S_QBLOCKS, REGLOB),
'{': RegExp('([{}])|' + S_QBLOCKS, REGLOB)
},
DEFAULT = '{ }'
var _pairs = [
'{', '}',
'{', '}',
/{[^}]*}/,
/\\([{}])/g,
/\\({)|{/g,
RegExp('\\\\(})|([[({])|(})|' + S_QBLOCKS, REGLOB),
DEFAULT,
/^\s*{\^?\s*([$\w]+)(?:\s*,\s*(\S+))?\s+in\s+(\S.*)\s*}/,
/(^|[^\\]){=[\S\s]*?}/
]
var
cachedBrackets = UNDEF,
_regex,
_cache = [],
_settings
function _loopback (re) { return re }
function _rewrite (re, bp) {
if (!bp) bp = _cache
return new RegExp(
re.source.replace(/{/g, bp[2]).replace(/}/g, bp[3]), re.global ? REGLOB : ''
)
}
function _create (pair) {
if (pair === DEFAULT) return _pairs
var arr = pair.split(' ')
if (arr.length !== 2 || UNSUPPORTED.test(pair)) {
throw new Error('Unsupported brackets "' + pair + '"')
}
arr = arr.concat(pair.replace(NEED_ESCAPE, '\\').split(' '))
arr[4] = _rewrite(arr[1].length > 1 ? /{[\S\s]*?}/ : _pairs[4], arr)
arr[5] = _rewrite(pair.length > 3 ? /\\({|})/g : _pairs[5], arr)
arr[6] = _rewrite(_pairs[6], arr)
arr[7] = RegExp('\\\\(' + arr[3] + ')|([[({])|(' + arr[3] + ')|' + S_QBLOCKS, REGLOB)
arr[8] = pair
return arr
}
function _brackets (reOrIdx) {
return reOrIdx instanceof RegExp ? _regex(reOrIdx) : _cache[reOrIdx]
}
_brackets.split = function split (str, tmpl, _bp) {
// istanbul ignore next: _bp is for the compiler
if (!_bp) _bp = _cache
var
parts = [],
match,
isexpr,
start,
pos,
re = _bp[6]
isexpr = start = re.lastIndex = 0
while ((match = re.exec(str))) {
pos = match.index
if (isexpr) {
if (match[2]) {
re.lastIndex = skipBraces(str, match[2], re.lastIndex)
continue
}
if (!match[3]) {
continue
}
}
if (!match[1]) {
unescapeStr(str.slice(start, pos))
start = re.lastIndex
re = _bp[6 + (isexpr ^= 1)]
re.lastIndex = start
}
}
if (str && start < str.length) {
unescapeStr(str.slice(start))
}
return parts
function unescapeStr (s) {
if (tmpl || isexpr) {
parts.push(s && s.replace(_bp[5], '$1'))
} else {
parts.push(s)
}
}
function skipBraces (s, ch, ix) {
var
match,
recch = FINDBRACES[ch]
recch.lastIndex = ix
ix = 1
while ((match = recch.exec(s))) {
if (match[1] &&
!(match[1] === ch ? ++ix : --ix)) break
}
return ix ? s.length : recch.lastIndex
}
}
_brackets.hasExpr = function hasExpr (str) {
return _cache[4].test(str)
}
_brackets.loopKeys = function loopKeys (expr) {
var m = expr.match(_cache[9])
return m
? { key: m[1], pos: m[2], val: _cache[0] + m[3].trim() + _cache[1] }
: { val: expr.trim() }
}
_brackets.array = function array (pair) {
return pair ? _create(pair) : _cache
}
function _reset (pair) {
if ((pair || (pair = DEFAULT)) !== _cache[8]) {
_cache = _create(pair)
_regex = pair === DEFAULT ? _loopback : _rewrite
_cache[9] = _regex(_pairs[9])
}
cachedBrackets = pair
}
function _setSettings (o) {
var b
o = o || {}
b = o.brackets
Object.defineProperty(o, 'brackets', {
set: _reset,
get: function () { return cachedBrackets },
enumerable: true
})
_settings = o
_reset(b)
}
Object.defineProperty(_brackets, 'settings', {
set: _setSettings,
get: function () { return _settings }
})
/* istanbul ignore next: in the browser riot is always in the scope */
_brackets.settings = typeof riot !== 'undefined' && riot.settings || {}
_brackets.set = _reset
_brackets.R_STRINGS = R_STRINGS
_brackets.R_MLCOMMS = R_MLCOMMS
_brackets.S_QBLOCKS = S_QBLOCKS
return _brackets
})()
/**
* @module tmpl
*
* tmpl - Root function, returns the template value, render with data
* tmpl.hasExpr - Test the existence of a expression inside a string
* tmpl.loopKeys - Get the keys for an 'each' loop (used by `_each`)
*/
var tmpl = (function () {
var _cache = {}
function _tmpl (str, data) {
if (!str) return str
return (_cache[str] || (_cache[str] = _create(str))).call(data, _logErr)
}
_tmpl.haveRaw = brackets.hasRaw
_tmpl.hasExpr = brackets.hasExpr
_tmpl.loopKeys = brackets.loopKeys
// istanbul ignore next
_tmpl.clearCache = function () { _cache = {} }
_tmpl.errorHandler = null
function _logErr (err, ctx) {
if (_tmpl.errorHandler) {
err.riotData = {
tagName: ctx && ctx.root && ctx.root.tagName,
_riot_id: ctx && ctx._riot_id //eslint-disable-line camelcase
}
_tmpl.errorHandler(err)
}
}
function _create (str) {
var expr = _getTmpl(str)
if (expr.slice(0, 11) !== 'try{return ') expr = 'return ' + expr
return new Function('E', expr + ';') // eslint-disable-line no-new-func
}
var
CH_IDEXPR = '\u2057',
RE_CSNAME = /^(?:(-?[_A-Za-z\xA0-\xFF][-\w\xA0-\xFF]*)|\u2057(\d+)~):/,
RE_QBLOCK = RegExp(brackets.S_QBLOCKS, 'g'),
RE_DQUOTE = /\u2057/g,
RE_QBMARK = /\u2057(\d+)~/g
function _getTmpl (str) {
var
qstr = [],
expr,
parts = brackets.split(str.replace(RE_DQUOTE, '"'), 1)
if (parts.length > 2 || parts[0]) {
var i, j, list = []
for (i = j = 0; i < parts.length; ++i) {
expr = parts[i]
if (expr && (expr = i & 1
? _parseExpr(expr, 1, qstr)
: '"' + expr
.replace(/\\/g, '\\\\')
.replace(/\r\n?|\n/g, '\\n')
.replace(/"/g, '\\"') +
'"'
)) list[j++] = expr
}
expr = j < 2 ? list[0]
: '[' + list.join(',') + '].join("")'
} else {
expr = _parseExpr(parts[1], 0, qstr)
}
if (qstr[0]) {
expr = expr.replace(RE_QBMARK, function (_, pos) {
return qstr[pos]
.replace(/\r/g, '\\r')
.replace(/\n/g, '\\n')
})
}
return expr
}
var
RE_BREND = {
'(': /[()]/g,
'[': /[[\]]/g,
'{': /[{}]/g
}
function _parseExpr (expr, asText, qstr) {
expr = expr
.replace(RE_QBLOCK, function (s, div) {
return s.length > 2 && !div ? CH_IDEXPR + (qstr.push(s) - 1) + '~' : s
})
.replace(/\s+/g, ' ').trim()
.replace(/\ ?([[\({},?\.:])\ ?/g, '$1')
if (expr) {
var
list = [],
cnt = 0,
match
while (expr &&
(match = expr.match(RE_CSNAME)) &&
!match.index
) {
var
key,
jsb,
re = /,|([[{(])|$/g
expr = RegExp.rightContext
key = match[2] ? qstr[match[2]].slice(1, -1).trim().replace(/\s+/g, ' ') : match[1]
while (jsb = (match = re.exec(expr))[1]) skipBraces(jsb, re)
jsb = expr.slice(0, match.index)
expr = RegExp.rightContext
list[cnt++] = _wrapExpr(jsb, 1, key)
}
expr = !cnt ? _wrapExpr(expr, asText)
: cnt > 1 ? '[' + list.join(',') + '].join(" ").trim()' : list[0]
}
return expr
function skipBraces (ch, re) {
var
mm,
lv = 1,
ir = RE_BREND[ch]
ir.lastIndex = re.lastIndex
while (mm = ir.exec(expr)) {
if (mm[0] === ch) ++lv
else if (!--lv) break
}
re.lastIndex = lv ? expr.length : ir.lastIndex
}
}
// istanbul ignore next: not both
var // eslint-disable-next-line max-len
JS_CONTEXT = '"in this?this:' + (typeof window !== 'object' ? 'global' : 'window') + ').',
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,
JS_NOPROPS = /^(?=(\.[$\w]+))\1(?:[^.[(]|$)/
function _wrapExpr (expr, asText, key) {
var tb
expr = expr.replace(JS_VARNAME, function (match, p, mvar, pos, s) {
if (mvar) {
pos = tb ? 0 : pos + match.length
if (mvar !== 'this' && mvar !== 'global' && mvar !== 'window') {
match = p + '("' + mvar + JS_CONTEXT + mvar
if (pos) tb = (s = s[pos]) === '.' || s === '(' || s === '['
} else if (pos) {
tb = !JS_NOPROPS.test(s.slice(pos))
}
}
return match
})
if (tb) {
expr = 'try{return ' + expr + '}catch(e){E(e,this)}'
}
if (key) {
expr = (tb
? 'function(){' + expr + '}.call(this)' : '(' + expr + ')'
) + '?"' + key + '":""'
} else if (asText) {
expr = 'function(v){' + (tb
? expr.replace('return ', 'v=') : 'v=(' + expr + ')'
) + ';return v||v===0?v:""}.call(this)'
}
return expr
}
_tmpl.version = brackets.version = 'v2.4.1'
return _tmpl
})()
/*
lib/browser/tag/mkdom.js
Includes hacks needed for the Internet Explorer version 9 and below
See: http://kangax.github.io/compat-table/es5/#ie8
http://codeplanet.io/dropping-ie8/
*/
var mkdom = (function _mkdom() {
var
reHasYield = /<yield\b/i,
reYieldAll = /<yield\s*(?:\/>|>([\S\s]*?)<\/yield\s*>|>)/ig,
reYieldSrc = /<yield\s+to=['"]([^'">]*)['"]\s*>([\S\s]*?)<\/yield\s*>/ig,
reYieldDest = /<yield\s+from=['"]?([-\w]+)['"]?\s*(?:\/>|>([\S\s]*?)<\/yield\s*>)/ig
var
rootEls = { tr: 'tbody', th: 'tr', td: 'tr', col: 'colgroup' },
tblTags = IE_VERSION && IE_VERSION < 10
? SPECIAL_TAGS_REGEX : /^(?:t(?:body|head|foot|[rhd])|caption|col(?:group)?)$/
/**
* Creates a DOM element to wrap the given content. Normally an `DIV`, but can be
* also a `TABLE`, `SELECT`, `TBODY`, `TR`, or `COLGROUP` element.
*
* @param {string} templ - The template coming from the custom tag definition
* @param {string} [html] - HTML content that comes from the DOM element where you
* will mount the tag, mostly the original tag in the page
* @returns {HTMLElement} DOM element with _templ_ merged through `YIELD` with the _html_.
*/
function _mkdom(templ, html) {
var
match = templ && templ.match(/^\s*<([-\w]+)/),
tagName = match && match[1].toLowerCase(),
el = mkEl('div', isSVGTag(tagName))
// replace all the yield tags with the tag inner html
templ = replaceYield(templ, html)
/* istanbul ignore next */
if (tblTags.test(tagName))
el = specialTags(el, templ, tagName)
else
setInnerHTML(el, templ)
el.stub = true
return el
}
/*
Creates the root element for table or select child elements:
tr/th/td/thead/tfoot/tbody/caption/col/colgroup/option/optgroup
*/
function specialTags(el, templ, tagName) {
var
select = tagName[0] === 'o',
parent = select ? 'select>' : 'table>'
// trim() is important here, this ensures we don't have artifacts,
// so we can check if we have only one element inside the parent
el.innerHTML = '<' + parent + templ.trim() + '</' + parent
parent = el.firstChild
// returns the immediate parent if tr/th/td/col is the only element, if not
// returns the whole tree, as this can include additional elements
if (select) {
parent.selectedIndex = -1 // for IE9, compatible w/current riot behavior
} else {
// avoids insertion of cointainer inside container (ex: tbody inside tbody)
var tname = rootEls[tagName]
if (tname && parent.childElementCount === 1) parent = $(tname, parent)
}
return parent
}
/*
Replace the yield tag from any tag template with the innerHTML of the
original tag in the page
*/
function replaceYield(templ, html) {
// do nothing if no yield
if (!reHasYield.test(templ)) return templ
// be careful with #1343 - string on the source having `$1`
var src = {}
html = html && html.replace(reYieldSrc, function (_, ref, text) {
src[ref] = src[ref] || text // preserve first definition
return ''
}).trim()
return templ
.replace(reYieldDest, function (_, ref, def) { // yield with from - to attrs
return src[ref] || def || ''
})
.replace(reYieldAll, function (_, def) { // yield without any "from"
return html || def || ''
})
}
return _mkdom
})()
/**
* Convert the item looped into an object used to extend the child tag properties
* @param { Object } expr - object containing the keys used to extend the children tags
* @param { * } key - value to assign to the new object returned
* @param { * } val - value containing the position of the item in the array
* @returns { Object } - new object containing the values of the original item
*
* The variables 'key' and 'val' are arbitrary.
* They depend on the collection type looped (Array, Object)
* and on the expression used on the each tag
*
*/
function mkitem(expr, key, val) {
var item = {}
item[expr.key] = key
if (expr.pos) item[expr.pos] = val
return item
}
/**
* Unmount the redundant tags
* @param { Array } items - array containing the current items to loop
* @param { Array } tags - array containing all the children tags
*/
function unmountRedundant(items, tags) {
var i = tags.length,
j = items.length,
t
while (i > j) {
t = tags[--i]
tags.splice(i, 1)
t.unmount()
}
}
/**
* Move the nested custom tags in non custom loop tags
* @param { Object } child - non custom loop tag
* @param { Number } i - current position of the loop tag
*/
function moveNestedTags(child, i) {
Object.keys(child.tags).forEach(function(tagName) {
var tag = child.tags[tagName]
if (isArray(tag))
each(tag, function (t) {
moveChildTag(t, tagName, i)
})
else
moveChildTag(tag, tagName, i)
})
}
/**
* Adds the elements for a virtual tag
* @param { Tag } tag - the tag whose root's children will be inserted or appended
* @param { Node } src - the node that will do the inserting or appending
* @param { Tag } target - only if inserting, insert before this tag's first child
*/
function addVirtual(tag, src, target) {
var el = tag._root, sib
tag._virts = []
while (el) {
sib = el.nextSibling
if (target)
src.insertBefore(el, target._root)
else
src.appendChild(el)
tag._virts.push(el) // hold for unmounting
el = sib
}
}
/**
* Move virtual tag and all child nodes
* @param { Tag } tag - first child reference used to start move
* @param { Node } src - the node that will do the inserting
* @param { Tag } target - insert before this tag's first child
* @param { Number } len - how many child nodes to move
*/
function moveVirtual(tag, src, target, len) {
var el = tag._root, sib, i = 0
for (; i < len; i++) {
sib = el.nextSibling
src.insertBefore(el, target._root)
el = sib
}
}
/**
* Manage tags having the 'each'
* @param { Object } dom - DOM node we need to loop
* @param { Tag } parent - parent tag instance where the dom node is contained
* @param { String } expr - string contained in the 'each' attribute
*/
function _each(dom, parent, expr) {
// remove the each property from the original tag
remAttr(dom, 'each')
var mustReorder = typeof getAttr(dom, 'no-reorder') !== T_STRING || remAttr(dom, 'no-reorder'),
tagName = getTagName(dom),
impl = __tagImpl[tagName] || { tmpl: getOuterHTML(dom) },
useRoot = SPECIAL_TAGS_REGEX.test(tagName),
root = dom.parentNode,
ref = document.createTextNode(''),
child = getTag(dom),
isOption = tagName.toLowerCase() === 'option', // the option tags must be treated differently
tags = [],
oldItems = [],
hasKeys,
isVirtual = dom.tagName == 'VIRTUAL'
// parse the each expression
expr = tmpl.loopKeys(expr)
// insert a marked where the loop tags will be injected
root.insertBefore(ref, dom)
// clean template code
parent.one('before-mount', function () {
// remove the original DOM node
dom.parentNode.removeChild(dom)
if (root.stub) root = parent.root
}).on('update', function () {
// get the new items collection
var items = tmpl(expr.val, parent),
// create a fragment to hold the new DOM nodes to inject in the parent tag
frag = document.createDocumentFragment()
// object loop. any changes cause full redraw
if (!isArray(items)) {
hasKeys = items || false
items = hasKeys ?
Object.keys(items).map(function (key) {
return mkitem(expr, key, items[key])
}) : []
}
// loop all the new items
var i = 0,
itemsLength = items.length
for (; i < itemsLength; i++) {
// reorder only if the items are objects
var
item = items[i],
_mustReorder = mustReorder && typeof item == T_OBJECT && !hasKeys,
oldPos = oldItems.indexOf(item),
pos = ~oldPos && _mustReorder ? oldPos : i,
// does a tag exist in this position?
tag = tags[pos]
item = !hasKeys && expr.key ? mkitem(expr, item, i) : item
// new tag
if (
!_mustReorder && !tag // with no-reorder we just update the old tags
||
_mustReorder && !~oldPos || !tag // by default we always try to reorder the DOM elements
) {
tag = new Tag(impl, {
parent: parent,
isLoop: true,
hasImpl: !!__tagImpl[tagName],
root: useRoot ? root : dom.cloneNode(),
item: item
}, dom.innerHTML)
tag.mount()
if (isVirtual) tag._root = tag.root.firstChild // save reference for further moves or inserts
// this tag must be appended
if (i == tags.length || !tags[i]) { // fix 1581
if (isVirtual)
addVirtual(tag, frag)
else frag.appendChild(tag.root)
}
// this tag must be insert
else {
if (isVirtual)
addVirtual(tag, root, tags[i])
else root.insertBefore(tag.root, tags[i].root) // #1374 some browsers reset selected here
oldItems.splice(i, 0, item)
}
tags.splice(i, 0, tag)
pos = i // handled here so no move
} else tag.update(item, true)
// reorder the tag if it's not located in its previous position
if (
pos !== i && _mustReorder &&
tags[i] // fix 1581 unable to reproduce it in a test!
) {
// update the DOM
if (isVirtual)
moveVirtual(tag, root, tags[i], dom.childNodes.length)
else if (tags[i].root.parentNode) root.insertBefore(tag.root, tags[i].root)
// update the position attribute if it exists
if (expr.pos)
tag[expr.pos] = i
// move the old tag instance
tags.splice(i, 0, tags.splice(pos, 1)[0])
// move the old item
oldItems.splice(i, 0, oldItems.splice(pos, 1)[0])
// if the loop tags are not custom
// we need to move all their custom tags into the right position
if (!child && tag.tags) moveNestedTags(tag, i)
}
// cache the original item to use it in the events bound to this node
// and its children
tag._item = item
// cache the real parent tag internally
defineProperty(tag, '_parent', parent)
}
// remove the redundant tags
unmountRedundant(items, tags)
// insert the new nodes
root.insertBefore(frag, ref)
if (isOption) {
// #1374 FireFox bug in <option selected={expression}>
if (FIREFOX && !root.multiple) {
for (var n = 0; n < root.length; n++) {
if (root[n].__riot1374) {
root.selectedIndex = n // clear other options
delete root[n].__riot1374
break
}
}
}
}
// set the 'tags' property of the parent tag
// if child is 'undefined' it means that we don't need to set this property
// for example:
// we don't need store the `myTag.tags['div']` property if we are looping a div tag
// but we need to track the `myTag.tags['child']` property looping a custom child node named `child`
if (child) parent.tags[tagName] = tags
// clone the items array
oldItems = items.slice()
})
}
/**
* Object that will be used to inject and manage the css of every tag instance
*/
var styleManager = (function(_riot) {
if (!window) return { // skip injection on the server
add: function () {},
inject: function () {}
}
var styleNode = (function () {
// create a new style element with the correct type
var newNode = mkEl('style')
setAttr(newNode, 'type', 'text/css')
// replace any user node or insert the new one into the head
var userNode = $('style[type=riot]')
if (userNode) {
if (userNode.id) newNode.id = userNode.id
userNode.parentNode.replaceChild(newNode, userNode)
}
else document.getElementsByTagName('head')[0].appendChild(newNode)
return newNode
})()
// Create cache and shortcut to the correct property
var cssTextProp = styleNode.styleSheet,
stylesToInject = ''
// Expose the style node in a non-modificable property
Object.defineProperty(_riot, 'styleNode', {
value: styleNode,
writable: true
})
/**
* Public api
*/
return {
/**
* Save a tag style to be later injected into DOM
* @param { String } css [description]
*/
add: function(css) {
stylesToInject += css
},
/**
* Inject all previously saved tag styles into DOM
* innerHTML seems slow: http://jsperf.com/riot-insert-style
*/
inject: function() {
if (stylesToInject) {
if (cssTextProp) cssTextProp.cssText += stylesToInject
else styleNode.innerHTML += stylesToInject
stylesToInject = ''
}
}
}
})(riot)
function parseNamedElements(root, tag, childTags, forceParsingNamed) {
walk(root, function(dom) {
if (dom.nodeType == 1) {
dom.isLoop = dom.isLoop ||
(dom.parentNode && dom.parentNode.isLoop || getAttr(dom, 'each'))
? 1 : 0
// custom child tag
if (childTags) {
var child = getTag(dom)
if (child && !dom.isLoop)
childTags.push(initChildTag(child, {root: dom, parent: tag}, dom.innerHTML, tag))
}
if (!dom.isLoop || forceParsingNamed)
setNamed(dom, tag, [])
}
})
}
function parseExpressions(root, tag, expressions) {
function addExpr(dom, val, extra) {
if (tmpl.hasExpr(val)) {
expressions.push(extend({ dom: dom, expr: val }, extra))
}
}
walk(root, function(dom) {
var type = dom.nodeType,
attr
// text node
if (type == 3 && dom.parentNode.tagName != 'STYLE') addExpr(dom, dom.nodeValue)
if (type != 1) return
/* element */
// loop
attr = getAttr(dom, 'each')
if (attr) { _each(dom, tag, attr); return false }
// attribute expressions
each(dom.attributes, function(attr) {
var name = attr.name,
bool = name.split('__')[1]
addExpr(dom, attr.value, { attr: bool || name, bool: bool })
if (bool) { remAttr(dom, name); return false }
})
// skip custom tags
if (getTag(dom)) return false
})
}
function Tag(impl, conf, innerHTML) {
var self = riot.observable(this),
opts = inherit(conf.opts) || {},
parent = conf.parent,
isLoop = conf.isLoop,
hasImpl = conf.hasImpl,
item = cleanUpData(conf.item),
expressions = [],
childTags = [],
root = conf.root,
tagName = root.tagName.toLowerCase(),
attr = {},
propsInSyncWithParent = [],
dom
// only call unmount if we have a valid __tagImpl (has name property)
if (impl.name && root._tag) root._tag.unmount(true)
// not yet mounted
this.isMounted = false
root.isLoop = isLoop
// keep a reference to the tag just created
// so we will be able to mount this tag multiple times
root._tag = this
// create a unique id to this tag
// it could be handy to use it also to improve the virtual dom rendering speed
defineProperty(this, '_riot_id', ++__uid) // base 1 allows test !t._riot_id
extend(this, { parent: parent, root: root, opts: opts}, item)
// protect the "tags" property from being overridden
defineProperty(this, 'tags', {})
// grab attributes
each(root.attributes, function(el) {
var val = el.value
// remember attributes with expressions only
if (tmpl.hasExpr(val)) attr[el.name] = val
})
dom = mkdom(impl.tmpl, innerHTML)
// options
function updateOpts() {
var ctx = hasImpl && isLoop ? self : parent || self
// update opts from current DOM attributes
each(root.attributes, function(el) {
var val = el.value
opts[toCamel(el.name)] = tmpl.hasExpr(val) ? tmpl(val, ctx) : val
})
// recover those with expressions
each(Object.keys(attr), function(name) {
opts[toCamel(name)] = tmpl(attr[name], ctx)
})
}
function normalizeData(data) {
for (var key in item) {
if (typeof self[key] !== T_UNDEF && isWritable(self, key))
self[key] = data[key]
}
}
function inheritFrom(target) {
each(Object.keys(target), function(k) {
// some properties must be always in sync with the parent tag
var mustSync = !RESERVED_WORDS_BLACKLIST.test(k) && contains(propsInSyncWithParent, k)
if (typeof self[k] === T_UNDEF || mustSync) {
// track the property to keep in sync
// so we can keep it updated
if (!mustSync) propsInSyncWithParent.push(k)
self[k] = target[k]
}
})
}
/**
* Update the tag expressions and options
* @param { * } data - data we want to use to extend the tag properties
* @param { Boolean } isInherited - is this update coming from a parent tag?
* @returns { self }
*/
defineProperty(this, 'update', function(data, isInherited) {
// make sure the data passed will not override
// the component core methods
data = cleanUpData(data)
// inherit properties from the parent in loop
if (isLoop) {
inheritFrom(self.parent)
}
// normalize the tag properties in case an item object was initially passed
if (data && isObject(item)) {
normalizeData(data)
item = data
}
extend(self, data)
updateOpts()
self.trigger('update', data)
update(expressions, self)
// the updated event will be triggered
// once the DOM will be ready and all the re-flows are completed
// this is useful if you want to get the "real" root properties
// 4 ex: root.offsetWidth ...
if (isInherited && self.parent)
// closes #1599
self.parent.one('updated', function() { self.trigger('updated') })
else rAF(function() { self.trigger('updated') })
return this
})
defineProperty(this, 'mixin', function() {
each(arguments, function(mix) {
var instance,
props = [],
obj
mix = typeof mix === T_STRING ? riot.mixin(mix) : mix
// check if the mixin is a function
if (isFunction(mix)) {
// create the new mixin instance
instance = new mix()
} else instance = mix
// build multilevel prototype inheritance chain property list
do props = props.concat(Object.getOwnPropertyNames(obj || instance))
while (obj = Object.getPrototypeOf(obj || instance))
// loop the keys in the function prototype or the all object keys
each(props, function(key) {
// bind methods to self
// allow mixins to override other properties/parent mixins
if (key != 'init') {
// check for getters/setters
var descriptor = Object.getOwnPropertyDescriptor(instance, key)
var hasGetterSetter = descriptor && (descriptor.get || descriptor.set)
// apply method only if it does not already exist on the instance
if (!self.hasOwnProperty(key) && hasGetterSetter) {
Object.defineProperty(self, key, descriptor)
} else {
self[key] = isFunction(instance[key]) ?
instance[key].bind(self) :
instance[key]
}
}
})
// init method will be called automatically
if (instance.init) instance.init.bind(self)()
})
return this
})
defineProperty(this, 'mount', function() {
updateOpts()
// add global mixins
var globalMixin = riot.mixin(GLOBAL_MIXIN)
if (globalMixin)
for (var i in globalMixin)
if (globalMixin.hasOwnProperty(i))
self.mixin(globalMixin[i])
// children in loop should inherit from true parent
if (self._parent) {
inheritFrom(self._parent)
}
// initialiation
if (impl.fn) impl.fn.call(self, opts)
// parse layout after init. fn may calculate args for nested custom tags
parseExpressions(dom, self, expressions)
// mount the child tags
toggle(true)
// update the root adding custom attributes coming from the compiler
// it fixes also #1087
if (impl.attrs)
walkAttributes(impl.attrs, function (k, v) { setAttr(root, k, v) })
if (impl.attrs || hasImpl)
parseExpressions(self.root, self, expressions)
if (!self.parent || isLoop) self.update(item)
// internal use only, fixes #403
self.trigger('before-mount')
if (isLoop && !hasImpl) {
// update the root attribute for the looped elements
root = dom.firstChild
} else {
while (dom.firstChild) root.appendChild(dom.firstChild)
if (root.stub) root = parent.root
}
defineProperty(self, 'root', root)
// parse the named dom nodes in the looped child
// adding them to the parent as well
if (isLoop)
parseNamedElements(self.root, self.parent, null, true)
// if it's not a child tag we can trigger its mount event
if (!self.parent || self.parent.isMounted) {
self.isMounted = true
self.trigger('mount')
}
// otherwise we need to wait that the parent event gets triggered
else self.parent.one('mount', function() {
// avoid to trigger the `mount` event for the tags
// not visible included in an if statement
if (!isInStub(self.root)) {
self.parent.isMounted = self.isMounted = true
self.trigger('mount')
}
})
})
defineProperty(this, 'unmount', function(keepRootTag) {
var el = root,
p = el.parentNode,
ptag,
tagIndex = __virtualDom.indexOf(self)
self.trigger('before-unmount')
// remove this tag instance from the global virtualDom variable
if (~tagIndex)
__virtualDom.splice(tagIndex, 1)
if (p) {
if (parent) {
ptag = getImmediateCustomParentTag(parent)
// remove this tag from the parent tags object
// if there are multiple nested tags with same name..
// remove this element form the array
if (isArray(ptag.tags[tagName]))
each(ptag.tags[tagName], function(tag, i) {
if (tag._riot_id == self._riot_id)
ptag.tags[tagName].splice(i, 1)
})
else
// otherwise just delete the tag instance
ptag.tags[tagName] = undefined
}
else
while (el.firstChild) el.removeChild(el.firstChild)
if (!keepRootTag)
p.removeChild(el)
else {
// the riot-tag and the data-is attributes aren't needed anymore, remove them
remAttr(p, RIOT_TAG_IS)
remAttr(p, RIOT_TAG) // this will be removed in riot 3.0.0
}
}
if (this._virts) {
each(this._virts, function(v) {
if (v.parentNode) v.parentNode.removeChild(v)
})
}
self.trigger('unmount')
toggle()
self.off('*')
self.isMounted = false
delete root._tag
})
// proxy function to bind updates
// dispatched from a parent tag
function onChildUpdate(data) { self.update(data, true) }
function toggle(isMount) {
// mount/unmount children
each(childTags, function(child) { child[isMount ? 'mount' : 'unmount']() })
// listen/unlisten parent (events flow one way from parent to children)
if (!parent) return
var evt = isMount ? 'on' : 'off'
// the loop tags will be always in sync with the parent automatically
if (isLoop)
parent[evt]('unmount', self.unmount)
else {
parent[evt]('update', onChildUpdate)[evt]('unmount', self.unmount)
}
}
// named elements available for fn
parseNamedElements(dom, this, childTags)
}
/**
* Attach an event to a DOM node
* @param { String } name - event name
* @param { Function } handler - event callback
* @param { Object } dom - dom node
* @param { Tag } tag - tag instance
*/
function setEventHandler(name, handler, dom, tag) {
dom[name] = function(e) {
var ptag = tag._parent,
item = tag._item,
el
if (!item)
while (ptag && !item) {
item = ptag._item
ptag = ptag._parent
}
// cross browser event fix
e = e || window.event
// override the event properties
if (isWritable(e, 'currentTarget')) e.currentTarget = dom
if (isWritable(e, 'target')) e.target = e.srcElement
if (isWritable(e, 'which')) e.which = e.charCode || e.keyCode
e.item = item
// prevent default behaviour (by default)
if (handler.call(tag, e) !== true && !/radio|check/.test(dom.type)) {
if (e.preventDefault) e.preventDefault()
e.returnValue = false
}
if (!e.preventUpdate) {
el = item ? getImmediateCustomParentTag(ptag) : tag
el.update()
}
}
}
/**
* Insert a DOM node replacing another one (used by if- attribute)
* @param { Object } root - parent node
* @param { Object } node - node replaced
* @param { Object } before - node added
*/
function insertTo(root, node, before) {
if (!root) return
root.insertBefore(before, node)
root.removeChild(node)
}
/**
* Update the expressions in a Tag instance
* @param { Array } expressions - expression that must be re evaluated
* @param { Tag } tag - tag instance
*/
function update(expressions, tag) {
each(expressions, function(expr, i) {
var dom = expr.dom,
attrName = expr.attr,
value = tmpl(expr.expr, tag),
parent = expr.parent || expr.dom.parentNode
if (expr.bool) {
value = !!value
} else if (value == null) {
value = ''
}
// #1638: regression of #1612, update the dom only if the value of the
// expression was changed
if (expr.value === value) {
return
}
expr.value = value
// textarea and text nodes has no attribute name
if (!attrName) {
// about #815 w/o replace: the browser converts the value to a string,
// the comparison by "==" does too, but not in the server
value += ''
// test for parent avoids error with invalid assignment to nodeValue
if (parent) {
// cache the parent node because somehow it will become null on IE
// on the next iteration
expr.parent = parent
if (parent.tagName === 'TEXTAREA') {
parent.value = value // #1113
if (!IE_VERSION) dom.nodeValue = value // #1625 IE throws here, nodeValue
} // will be available on 'updated'
else dom.nodeValue = value
}
return
}
// ~~#1612: look for changes in dom.value when updating the value~~
if (attrName === 'value') {
if (dom.value !== value) {
dom.value = value
setAttr(dom, attrName, value)
}
return
} else {
// remove original attribute
remAttr(dom, attrName)
}
// event handler
if (isFunction(value)) {
setEventHandler(attrName, value, dom, tag)
// if- conditional
} else if (attrName == 'if') {
var stub = expr.stub,
add = function() { insertTo(stub.parentNode, stub, dom) },
remove = function() { insertTo(dom.parentNode, dom, stub) }
// add to DOM
if (value) {
if (stub) {
add()
dom.inStub = false
// avoid to trigger the mount event if the tags is not visible yet
// maybe we can optimize this avoiding to mount the tag at all
if (!isInStub(dom)) {
walk(dom, function(el) {
if (el._tag && !el._tag.isMounted)
el._tag.isMounted = !!el._tag.trigger('mount')
})
}
}
// remove from DOM
} else {
stub = expr.stub = stub || document.createTextNode('')
// if the parentNode is defined we can easily replace the tag
if (dom.parentNode)
remove()
// otherwise we need to wait the updated event
else (tag.parent || tag).one('updated', remove)
dom.inStub = true
}
// show / hide
} else if (attrName === 'show') {
dom.style.display = value ? '' : 'none'
} else if (attrName === 'hide') {
dom.style.display = value ? 'none' : ''
} else if (expr.bool) {
dom[attrName] = value
if (value) setAttr(dom, attrName, attrName)
if (FIREFOX && attrName === 'selected' && dom.tagName === 'OPTION') {
dom.__riot1374 = value // #1374
}
} else if (value === 0 || value && typeof value !== T_OBJECT) {
// <img src="{ expr }">
if (startsWith(attrName, RIOT_PREFIX) && attrName != RIOT_TAG) {
attrName = attrName.slice(RIOT_PREFIX.length)
}
setAttr(dom, attrName, value)
}
})
}
/**
* Specialized function for looping an array-like collection with `each={}`
* @param { Array } els - collection of items
* @param {Function} fn - callback function
* @returns { Array } the array looped
*/
function each(els, fn) {
var len = els ? els.length : 0
for (var i = 0, el; i < len; i++) {
el = els[i]
// return false -> current item was removed by fn during the loop
if (el != null && fn(el, i) === false) i--
}
return els
}
/**
* Detect if the argument passed is a function
* @param { * } v - whatever you want to pass to this function
* @returns { Boolean } -
*/
function isFunction(v) {
return typeof v === T_FUNCTION || false // avoid IE problems
}
/**
* Get the outer html of any DOM node SVGs included
* @param { Object } el - DOM node to parse
* @returns { String } el.outerHTML
*/
function getOuterHTML(el) {
if (el.outerHTML) return el.outerHTML
// some browsers do not support outerHTML on the SVGs tags
else {
var container = mkEl('div')
container.appendChild(el.cloneNode(true))
return container.innerHTML
}
}
/**
* Set the inner html of any DOM node SVGs included
* @param { Object } container - DOM node where we will inject the new html
* @param { String } html - html to inject
*/
function setInnerHTML(container, html) {
if (typeof container.innerHTML != T_UNDEF) container.innerHTML = html
// some browsers do not support innerHTML on the SVGs tags
else {
var doc = new DOMParser().parseFromString(html, 'application/xml')
container.appendChild(
container.ownerDocument.importNode(doc.documentElement, true)
)
}
}
/**
* Checks wether a DOM node must be considered part of an svg document
* @param { String } name - tag name
* @returns { Boolean } -
*/
function isSVGTag(name) {
return ~SVG_TAGS_LIST.indexOf(name)
}
/**
* Detect if the argument passed is an object, exclude null.
* NOTE: Use isObject(x) && !isArray(x) to excludes arrays.
* @param { * } v - whatever you want to pass to this function
* @returns { Boolean } -
*/
function isObject(v) {
return v && typeof v === T_OBJECT // typeof null is 'object'
}
/**
* Remove any DOM attribute from a node
* @param { Object } dom - DOM node we want to update
* @param { String } name - name of the property we want to remove
*/
function remAttr(dom, name) {
dom.removeAttribute(name)
}
/**
* Convert a string containing dashes to camel case
* @param { String } string - input string
* @returns { String } my-string -> myString
*/
function toCamel(string) {
return string.replace(/-(\w)/g, function(_, c) {
return c.toUpperCase()
})
}
/**
* Get the value of any DOM attribute on a node
* @param { Object } dom - DOM node we want to parse
* @param { String } name - name of the attribute we want to get
* @returns { String | undefined } name of the node attribute whether it exists
*/
function getAttr(dom, name) {
return dom.getAttribute(name)
}
/**
* Set any DOM/SVG attribute
* @param { Object } dom - DOM node we want to update
* @param { String } name - name of the property we want to set
* @param { String } val - value of the property we want to set
*/
function setAttr(dom, name, val) {
var xlink = XLINK_REGEX.exec(name)
if (xlink && xlink[1])
dom.setAttributeNS(XLINK_NS, xlink[1], val)
else
dom.setAttribute(name, val)
}
/**
* Detect the tag implementation by a DOM node
* @param { Object } dom - DOM node we need to parse to get its tag implementation
* @returns { Object } it returns an object containing the implementation of a custom tag (template and boot function)
*/
function getTag(dom) {
return dom.tagName && __tagImpl[getAttr(dom, RIOT_TAG_IS) ||
getAttr(dom, RIOT_TAG) || dom.tagName.toLowerCase()]
}
/**
* Add a child tag to its parent into the `tags` object
* @param { Object } tag - child tag instance
* @param { String } tagName - key where the new tag will be stored
* @param { Object } parent - tag instance where the new child tag will be included
*/
function addChildTag(tag, tagName, parent) {
var cachedTag = parent.tags[tagName]
// if there are multiple children tags having the same name
if (cachedTag) {
// if the parent tags property is not yet an array
// create it adding the first cached tag
if (!isArray(cachedTag))
// don't add the same tag twice
if (cachedTag !== tag)
parent.tags[tagName] = [cachedTag]
// add the new nested tag to the array
if (!contains(parent.tags[tagName], tag))
parent.tags[tagName].push(tag)
} else {
parent.tags[tagName] = tag
}
}
/**
* Move the position of a custom tag in its parent tag
* @param { Object } tag - child tag instance
* @param { String } tagName - key where the tag was stored
* @param { Number } newPos - index where the new tag will be stored
*/
function moveChildTag(tag, tagName, newPos) {
var parent = tag.parent,
tags
// no parent no move
if (!parent) return
tags = parent.tags[tagName]
if (isArray(tags))
tags.splice(newPos, 0, tags.splice(tags.indexOf(tag), 1)[0])
else addChildTag(tag, tagName, parent)
}
/**
* Create a new child tag including it correctly into its parent
* @param { Object } child - child tag implementation
* @param { Object } opts - tag options containing the DOM node where the tag will be mounted
* @param { String } innerHTML - inner html of the child node
* @param { Object } parent - instance of the parent tag including the child custom tag
* @returns { Object } instance of the new child tag just created
*/
function initChildTag(child, opts, innerHTML, parent) {
var tag = new Tag(child, opts, innerHTML),
tagName = getTagName(opts.root),
ptag = getImmediateCustomParentTag(parent)
// fix for the parent attribute in the looped elements
tag.parent = ptag
// store the real parent tag
// in some cases this could be different from the custom parent tag
// for example in nested loops
tag._parent = parent
// add this tag to the custom parent tag
addChildTag(tag, tagName, ptag)
// and also to the real parent tag
if (ptag !== parent)
addChildTag(tag, tagName, parent)
// empty the child node once we got its template
// to avoid that its children get compiled multiple times
opts.root.innerHTML = ''
return tag
}
/**
* Loop backward all the parents tree to detect the first custom parent tag
* @param { Object } tag - a Tag instance
* @returns { Object } the instance of the first custom parent tag found
*/
function getImmediateCustomParentTag(tag) {
var ptag = tag
while (!getTag(ptag.root)) {
if (!ptag.parent) break
ptag = ptag.parent
}
return ptag
}
/**
* Helper function to set an immutable property
* @param { Object } el - object where the new property will be set
* @param { String } key - object key where the new property will be stored
* @param { * } value - value of the new property
* @param { Object } options - set the propery overriding the default options
* @returns { Object } - the initial object
*/
function defineProperty(el, key, value, options) {
Object.defineProperty(el, key, extend({
value: value,
enumerable: false,
writable: false,
configurable: true
}, options))
return el
}
/**
* Get the tag name of any DOM node
* @param { Object } dom - DOM node we want to parse
* @returns { String } name to identify this dom node in riot
*/
function getTagName(dom) {
var child = getTag(dom),
namedTag = getAttr(dom, 'name'),
tagName = namedTag && !tmpl.hasExpr(namedTag) ?
namedTag :
child ? child.name : dom.tagName.toLowerCase()
return tagName
}
/**
* Extend any object with other properties
* @param { Object } src - source object
* @returns { Object } the resulting extended object
*
* var obj = { foo: 'baz' }
* extend(obj, {bar: 'bar', foo: 'bar'})
* console.log(obj) => {bar: 'bar', foo: 'bar'}
*
*/
function extend(src) {
var obj, args = arguments
for (var i = 1; i < args.length; ++i) {
if (obj = args[i]) {
for (var key in obj) {
// check if this property of the source object could be overridden
if (isWritable(src, key))
src[key] = obj[key]
}
}
}
return src
}
/**
* Check whether an array contains an item
* @param { Array } arr - target array
* @param { * } item - item to test
* @returns { Boolean } Does 'arr' contain 'item'?
*/
function contains(arr, item) {
return ~arr.indexOf(item)
}
/**
* Check whether an object is a kind of array
* @param { * } a - anything
* @returns {Boolean} is 'a' an array?
*/
function isArray(a) { return Array.isArray(a) || a instanceof Array }
/**
* Detect whether a property of an object could be overridden
* @param { Object } obj - source object
* @param { String } key - object property
* @returns { Boolean } is this property writable?
*/
function isWritable(obj, key) {
var props = Object.getOwnPropertyDescriptor(obj, key)
return typeof obj[key] === T_UNDEF || props && props.writable
}
/**
* With this function we avoid that the internal Tag methods get overridden
* @param { Object } data - options we want to use to extend the tag instance
* @returns { Object } clean object without containing the riot internal reserved words
*/
function cleanUpData(data) {
if (!(data instanceof Tag) && !(data && typeof data.trigger == T_FUNCTION))
return data
var o = {}
for (var key in data) {
if (!RESERVED_WORDS_BLACKLIST.test(key)) o[key] = data[key]
}
return o
}
/**
* Walk down recursively all the children tags starting dom node
* @param { Object } dom - starting node where we will start the recursion
* @param { Function } fn - callback to transform the child node just found
*/
function walk(dom, fn) {
if (dom) {
// stop the recursion
if (fn(dom) === false) return
else {
dom = dom.firstChild
while (dom) {
walk(dom, fn)
dom = dom.nextSibling
}
}
}
}
/**
* Minimize risk: only zero or one _space_ between attr & value
* @param { String } html - html string we want to parse
* @param { Function } fn - callback function to apply on any attribute found
*/
function walkAttributes(html, fn) {
var m,
re = /([-\w]+) ?= ?(?:"([^"]*)|'([^']*)|({[^}]*}))/g
while (m = re.exec(html)) {
fn(m[1].toLowerCase(), m[2] || m[3] || m[4])
}
}
/**
* Check whether a DOM node is in stub mode, useful for the riot 'if' directive
* @param { Object } dom - DOM node we want to parse
* @returns { Boolean } -
*/
function isInStub(dom) {
while (dom) {
if (dom.inStub) return true
dom = dom.parentNode
}
return false
}
/**
* Create a generic DOM node
* @param { String } name - name of the DOM node we want to create
* @param { Boolean } isSvg - should we use a SVG as parent node?
* @returns { Object } DOM node just created
*/
function mkEl(name, isSvg) {
return isSvg ?
document.createElementNS('http://www.w3.org/2000/svg', 'svg') :
document.createElement(name)
}
/**
* Shorter and fast way to select multiple nodes in the DOM
* @param { String } selector - DOM selector
* @param { Object } ctx - DOM node where the targets of our search will is located
* @returns { Object } dom nodes found
*/
function $$(selector, ctx) {
return (ctx || document).querySelectorAll(selector)
}
/**
* Shorter and fast way to select a single node in the DOM
* @param { String } selector - unique dom selector
* @param { Object } ctx - DOM node where the target of our search will is located
* @returns { Object } dom node found
*/
function $(selector, ctx) {
return (ctx || document).querySelector(selector)
}
/**
* Simple object prototypal inheritance
* @param { Object } parent - parent object
* @returns { Object } child instance
*/
function inherit(parent) {
function Child() {}
Child.prototype = parent
return new Child()
}
/**
* Get the name property needed to identify a DOM node in riot
* @param { Object } dom - DOM node we need to parse
* @returns { String | undefined } give us back a string to identify this dom node
*/
function getNamedKey(dom) {
return getAttr(dom, 'id') || getAttr(dom, 'name')
}
/**
* Set the named properties of a tag element
* @param { Object } dom - DOM node we need to parse
* @param { Object } parent - tag instance where the named dom element will be eventually added
* @param { Array } keys - list of all the tag instance properties
*/
function setNamed(dom, parent, keys) {
// get the key value we want to add to the tag instance
var key = getNamedKey(dom),
isArr,
// add the node detected to a tag instance using the named property
add = function(value) {
// avoid to override the tag properties already set
if (contains(keys, key)) return
// check whether this value is an array
isArr = isArray(value)
// if the key was never set
if (!value)
// set it once on the tag instance
parent[key] = dom
// if it was an array and not yet set
else if (!isArr || isArr && !contains(value, dom)) {
// add the dom node into the array
if (isArr)
value.push(dom)
else
parent[key] = [value, dom]
}
}
// skip the elements with no named properties
if (!key) return
// check whether this key has been already evaluated
if (tmpl.hasExpr(key))
// wait the first updated event only once
parent.one('mount', function() {
key = getNamedKey(dom)
add(parent[key])
})
else
add(parent[key])
}
/**
* Faster String startsWith alternative
* @param { String } src - source string
* @param { String } str - test string
* @returns { Boolean } -
*/
function startsWith(src, str) {
return src.slice(0, str.length) === str
}
/**
* requestAnimationFrame function
* Adapted from https://gist.github.com/paulirish/1579671, license MIT
*/
var rAF = (function (w) {
var raf = w.requestAnimationFrame ||
w.mozRequestAnimationFrame || w.webkitRequestAnimationFrame
if (!raf || /iP(ad|hone|od).*OS 6/.test(w.navigator.userAgent)) { // buggy iOS6
var lastTime = 0
raf = function (cb) {
var nowtime = Date.now(), timeout = Math.max(16 - (nowtime - lastTime), 0)
setTimeout(function () { cb(lastTime = nowtime + timeout) }, timeout)
}
}
return raf
})(window || {})
/**
* Mount a tag creating new Tag instance
* @param { Object } root - dom node where the tag will be mounted
* @param { String } tagName - name of the riot tag we want to mount
* @param { Object } opts - options to pass to the Tag instance
* @returns { Tag } a new Tag instance
*/
function mountTo(root, tagName, opts) {
var tag = __tagImpl[tagName],
// cache the inner HTML to fix #855
innerHTML = root._innerHTML = root._innerHTML || root.innerHTML
// clear the inner html
root.innerHTML = ''
if (tag && root) tag = new Tag(tag, { root: root, opts: opts }, innerHTML)
if (tag && tag.mount) {
tag.mount()
// add this tag to the virtualDom variable
if (!contains(__virtualDom, tag)) __virtualDom.push(tag)
}
return tag
}
/**
* Riot public api
*/
// share methods for other riot parts, e.g. compiler
riot.util = { brackets: brackets, tmpl: tmpl }
/**
* Create a mixin that could be globally shared across all the tags
*/
riot.mixin = (function() {
var mixins = {},
globals = mixins[GLOBAL_MIXIN] = {},
_id = 0
/**
* Create/Return a mixin by its name
* @param { String } name - mixin name (global mixin if object)
* @param { Object } mixin - mixin logic
* @param { Boolean } g - is global?
* @returns { Object } the mixin logic
*/
return function(name, mixin, g) {
// Unnamed global
if (isObject(name)) {
riot.mixin('__unnamed_'+_id++, name, true)
return
}
var store = g ? globals : mixins
// Getter
if (!mixin) {
if (typeof store[name] === T_UNDEF) {
throw new Error('Unregistered mixin: ' + name)
}
return store[name]
}
// Setter
if (isFunction(mixin)) {
extend(mixin.prototype, store[name] || {})
store[name] = mixin
}
else {
store[name] = extend(store[name] || {}, mixin)
}
}
})()
/**
* Create a new riot tag implementation
* @param { String } name - name/id of the new riot tag
* @param { String } html - tag template
* @param { String } css - custom tag css
* @param { String } attrs - root tag attributes
* @param { Function } fn - user function
* @returns { String } name/id of the tag just created
*/
riot.tag = function(name, html, css, attrs, fn) {
if (isFunction(attrs)) {
fn = attrs
if (/^[\w\-]+\s?=/.test(css)) {
attrs = css
css = ''
} else attrs = ''
}
if (css) {
if (isFunction(css)) fn = css
else styleManager.add(css)
}
name = name.toLowerCase()
__tagImpl[name] = { name: name, tmpl: html, attrs: attrs, fn: fn }
return name
}
/**
* Create a new riot tag implementation (for use by the compiler)
* @param { String } name - name/id of the new riot tag
* @param { String } html - tag template
* @param { String } css - custom tag css
* @param { String } attrs - root tag attributes
* @param { Function } fn - user function
* @returns { String } name/id of the tag just created
*/
riot.tag2 = function(name, html, css, attrs, fn) {
if (css) styleManager.add(css)
//if (bpair) riot.settings.brackets = bpair
__tagImpl[name] = { name: name, tmpl: html, attrs: attrs, fn: fn }
return name
}
/**
* Mount a tag using a specific tag implementation
* @param { String } selector - tag DOM selector
* @param { String } tagName - tag implementation name
* @param { Object } opts - tag logic
* @returns { Array } new tags instances
*/
riot.mount = function(selector, tagName, opts) {
var els,
allTags,
tags = []
// helper functions
function addRiotTags(arr) {
var list = ''
each(arr, function (e) {
if (!/[^-\w]/.test(e)) {
e = e.trim().toLowerCase()
list += ',[' + RIOT_TAG_IS + '="' + e + '"],[' + RIOT_TAG + '="' + e + '"]'
}
})
return list
}
function selectAllTags() {
var keys = Object.keys(__tagImpl)
return keys + addRiotTags(keys)
}
function pushTags(root) {
if (root.tagName) {
var riotTag = getAttr(root, RIOT_TAG_IS) || getAttr(root, RIOT_TAG)
// have tagName? force riot-tag to be the same
if (tagName && riotTag !== tagName) {
riotTag = tagName
setAttr(root, RIOT_TAG_IS, tagName)
setAttr(root, RIOT_TAG, tagName) // this will be removed in riot 3.0.0
}
var tag = mountTo(root, riotTag || root.tagName.toLowerCase(), opts)
if (tag) tags.push(tag)
} else if (root.length) {
each(root, pushTags) // assume nodeList
}
}
// ----- mount code -----
// inject styles into DOM
styleManager.inject()
if (isObject(tagName)) {
opts = tagName
tagName = 0
}
// crawl the DOM to find the tag
if (typeof selector === T_STRING) {
if (selector === '*')
// select all the tags registered
// and also the tags found with the riot-tag attribute set
selector = allTags = selectAllTags()
else
// or just the ones named like the selector
selector += addRiotTags(selector.split(/, */))
// make sure to pass always a selector
// to the querySelectorAll function
els = selector ? $$(selector) : []
}
else
// probably you have passed already a tag or a NodeList
els = selector
// select all the registered and mount them inside their root elements
if (tagName === '*') {
// get all custom tags
tagName = allTags || selectAllTags()
// if the root els it's just a single tag
if (els.tagName)
els = $$(tagName, els)
else {
// select all the children for all the different root elements
var nodeList = []
each(els, function (_el) {
nodeList.push($$(tagName, _el))
})
els = nodeList
}
// get rid of the tagName
tagName = 0
}
pushTags(els)
return tags
}
/**
* Update all the tags instances created
* @returns { Array } all the tags instances
*/
riot.update = function() {
return each(__virtualDom, function(tag) {
tag.update()
})
}
/**
* Export the Virtual DOM
*/
riot.vdom = __virtualDom
/**
* Export the Tag constructor
*/
riot.Tag = Tag
// support CommonJS, AMD & browser
/* istanbul ignore next */
if (typeof exports === T_OBJECT)
module.exports = riot
else if (typeof define === T_FUNCTION && typeof define.amd !== T_UNDEF)
define(function() { return riot })
else
window.riot = riot
})(typeof window != 'undefined' ? window : void 0);