Last active
September 28, 2016 16:09
-
-
Save senica/bdfce3528b74aa429c6bcf9a1e9d2aa7 to your computer and use it in GitHub Desktop.
Example using deferred unmount for riotjs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* Riot WIP, @license MIT */ | |
(function (global, factory) { | |
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : | |
typeof define === 'function' && define.amd ? define(factory) : | |
(global.riot = factory()); | |
}(this, (function () { 'use strict'; | |
var __VIRTUAL_DOM = []; | |
var __TAG_IMPL = {}; | |
var GLOBAL_MIXIN = '__global_mixin'; | |
var RIOT_PREFIX = 'riot-'; | |
var RIOT_TAG_IS = 'data-is'; | |
var T_STRING = 'string'; | |
var T_OBJECT = 'object'; | |
var T_UNDEF = 'undefined'; | |
var T_FUNCTION = 'function'; | |
var XLINK_NS = 'http://www.w3.org/1999/xlink'; | |
var XLINK_REGEX = /^xlink:(\w+)/; | |
var WIN = typeof window === T_UNDEF ? undefined : window; | |
var RE_SPECIAL_TAGS = /^(?:t(?:body|head|foot|[rhd])|caption|col(?:group)?|opt(?:ion|group))$/; | |
var RE_SPECIAL_TAGS_NO_OPTION = /^(?:t(?:body|head|foot|[rhd])|caption|col(?:group)?)$/; | |
var RE_RESERVED_NAMES = /^(?:_(?:item|id|parent)|update|root|(?:un)?mount|mixin|is(?:Mounted|Loop)|tags|parent|opts|trigger|o(?:n|ff|ne))$/; | |
var RE_SVG_TAGS = /^(altGlyph|animate(?:Color)?|circle|clipPath|defs|ellipse|fe(?:Blend|ColorMatrix|ComponentTransfer|Composite|ConvolveMatrix|DiffuseLighting|DisplacementMap|Flood|GaussianBlur|Image|Merge|Morphology|Offset|SpecularLighting|Tile|Turbulence)|filter|font|foreignObject|g(?:lyph)?(?:Ref)?|image|line(?:arGradient)?|ma(?:rker|sk)|missing-glyph|path|pattern|poly(?:gon|line)|radialGradient|rect|stop|svg|switch|symbol|text(?:Path)?|tref|tspan|use)$/; | |
var RE_HTML_ATTRS = /([-\w]+) ?= ?(?:"([^"]*)|'([^']*)|({[^}]*}))/g; | |
var RE_BOOL_ATTRS = /^(?:disabled|checked|readonly|required|allowfullscreen|auto(?:focus|play)|compact|controls|default|formnovalidate|hidden|ismap|itemscope|loop|multiple|muted|no(?:resize|shade|validate|wrap)?|open|reversed|seamless|selected|sortable|truespeed|typemustmatch)$/; | |
var IE_VERSION = (WIN && WIN.document || {}).documentMode | 0; | |
var FIREFOX = WIN && !!WIN.InstallTrigger | |
/** | |
* Check whether a DOM node must be considered a part of an svg document | |
* @param { String } name - | |
* @returns { Boolean } - | |
*/ | |
function isSVGTag(name) { | |
return RE_SVG_TAGS.test(name) | |
} | |
/** | |
* Check Check if the passed argument is undefined | |
* @param { String } value - | |
* @returns { Boolean } - | |
*/ | |
function isBoolAttr(value) { | |
return RE_BOOL_ATTRS.test(value) | |
} | |
/** | |
* Check if passed argument is a function | |
* @param { * } value - | |
* @returns { Boolean } - | |
*/ | |
function isFunction(value) { | |
return typeof value === T_FUNCTION || false // avoid IE problems | |
} | |
/** | |
* Check if passed argument is an object, exclude null | |
* NOTE: use isObject(x) && !isArray(x) to excludes arrays. | |
* @param { * } value - | |
* @returns { Boolean } - | |
*/ | |
function isObject(value) { | |
return value && typeof value === T_OBJECT // typeof null is 'object' | |
} | |
/** | |
* Check if passed argument is undefined | |
* @param { * } value - | |
* @returns { Boolean } - | |
*/ | |
function isUndefined(value) { | |
return typeof value === T_UNDEF | |
} | |
/** | |
* Check if passed argument is a string | |
* @param { * } value - | |
* @returns { Boolean } - | |
*/ | |
function isString(value) { | |
return typeof value === T_STRING | |
} | |
/** | |
* Check if passed argument is empty. Different from falsy, because we dont consider 0 or false to be blank | |
* @param { * } value - | |
* @returns { Boolean } - | |
*/ | |
function isBlank(value) { | |
return isUndefined(value) || value === null || value === '' | |
} | |
/** | |
* Check if passed argument is a kind of array | |
* @param { * } value - | |
* @returns { Boolean } - | |
*/ | |
function isArray(value) { | |
return Array.isArray(value) || value instanceof Array | |
} | |
/** | |
* Check whether object's property could be overridden | |
* @param { Object } obj - source object | |
* @param { String } key - object property | |
* @returns { Boolean } - | |
*/ | |
function isWritable(obj, key) { | |
var descriptor = Object.getOwnPropertyDescriptor(obj, key) | |
return isUndefined(obj[key]) || descriptor && descriptor.writable | |
} | |
/** | |
* Check if passed argument is a reserved name | |
* @param { String } value - | |
* @returns { Boolean } - | |
*/ | |
function isReservedName(value) { | |
return RE_RESERVED_NAMES.test(value) | |
} | |
var check = Object.freeze({ | |
isSVGTag: isSVGTag, | |
isBoolAttr: isBoolAttr, | |
isFunction: isFunction, | |
isObject: isObject, | |
isUndefined: isUndefined, | |
isString: isString, | |
isBlank: isBlank, | |
isArray: isArray, | |
isWritable: isWritable, | |
isReservedName: isReservedName | |
}); | |
/** | |
* 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) | |
} | |
/** | |
* Create a document fragment | |
* @returns { Object } document fragment | |
*/ | |
function createFrag() { | |
return document.createDocumentFragment() | |
} | |
/** | |
* Create a document text node | |
* @returns { Object } create a text node to use as placeholder | |
*/ | |
function createDOMPlaceholder() { | |
return document.createTextNode('') | |
} | |
/** | |
* 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) | |
} | |
/** | |
* 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'll inject new html | |
* @param { String } html - html to inject | |
*/ | |
function setInnerHTML(container, html) { | |
if (!isUndefined(container.innerHTML)) | |
{ container.innerHTML = html } | |
// some browsers do not support innerHTML on the SVGs tags | |
else { | |
var doc = new DOMParser().parseFromString(html, 'application/xml') | |
var node = container.ownerDocument.importNode(doc.documentElement, true) | |
container.appendChild(node) | |
} | |
} | |
/** | |
* 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) | |
} | |
/** | |
* 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 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) } | |
} | |
/** | |
* Insert safely a tag to fix #1962 #1649 | |
* @param { HTMLElement } root - children container | |
* @param { HTMLElement } curr - node to insert | |
* @param { HTMLElement } next - node that should preceed the current node inserted | |
*/ | |
function safeInsert(root, curr, next) { | |
root.insertBefore(curr, next.parentNode && next) | |
} | |
/** | |
* 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 walkAttrs(html, fn) { | |
if (!html) | |
{ return } | |
var m | |
while (m = RE_HTML_ATTRS.exec(html)) | |
{ fn(m[1].toLowerCase(), m[2] || m[3] || m[4]) } | |
} | |
/** | |
* 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 | |
* @param { Object } context - fn can optionally return an object, which is passed to children | |
*/ | |
function walkNodes(dom, fn, context) { | |
if (dom) { | |
var res = fn(dom, context) | |
var next | |
// stop the recursion | |
if (res === false) { return } | |
dom = dom.firstChild | |
while (dom) { | |
next = dom.nextSibling | |
walkNodes(dom, fn, res) | |
dom = next | |
} | |
} | |
} | |
var dom = Object.freeze({ | |
$$: $$, | |
$: $, | |
createFrag: createFrag, | |
createDOMPlaceholder: createDOMPlaceholder, | |
mkEl: mkEl, | |
getOuterHTML: getOuterHTML, | |
setInnerHTML: setInnerHTML, | |
remAttr: remAttr, | |
getAttr: getAttr, | |
setAttr: setAttr, | |
safeInsert: safeInsert, | |
walkAttrs: walkAttrs, | |
walkNodes: walkNodes | |
}); | |
var styleNode; | |
var cssTextProp; | |
var byName = {}; | |
var remainder = [] | |
// skip the following code on the server | |
if (WIN) { | |
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 | |
})() | |
cssTextProp = styleNode.styleSheet | |
} | |
/** | |
* Object that will be used to inject and manage the css of every tag instance | |
*/ | |
var styleManager = { | |
styleNode: styleNode, | |
/** | |
* Save a tag style to be later injected into DOM | |
* @param { String } css - css string | |
* @param { String } name - if it's passed we will map the css to a tagname | |
*/ | |
add: function add(css, name) { | |
if (name) { byName[name] = css } | |
else { remainder.push(css) } | |
}, | |
/** | |
* Inject all previously saved tag styles into DOM | |
* innerHTML seems slow: http://jsperf.com/riot-insert-style | |
*/ | |
inject: function inject() { | |
if (!WIN) { return } | |
var style = Object.keys(byName) | |
.map(function(k) { return byName[k] }) | |
.concat(remainder).join('\n') | |
if (cssTextProp) { cssTextProp.cssText = style } | |
else { styleNode.innerHTML = style } | |
} | |
} | |
/** | |
* 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 | |
*/ | |
/* global riot */ | |
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 | |
})() | |
/** | |
* Simple object prototypal inheritance | |
* @param { Object } parent - parent object | |
* @returns { Object } child instance | |
*/ | |
function inherit(parent) { | |
return Object.assign ? Object.assign({}, parent) : extend({}, parent) | |
} | |
/** | |
* Specialized function for looping an array-like collection with `each={}` | |
* @param { Array } list - collection of items | |
* @param {Function} fn - callback function | |
* @returns { Array } the array looped | |
*/ | |
function each(list, fn) { | |
var len = list ? list.length : 0 | |
for (var i = 0, el; i < len; ++i) { | |
el = list[i] | |
// return false -> current item was removed by fn during the loop | |
if (fn(el, i) === false) | |
{ i-- } | |
} | |
return list | |
} | |
/** | |
* Check whether an array contains an item | |
* @param { Array } array - target array | |
* @param { * } item - item to test | |
* @returns { Boolean } - | |
*/ | |
function contains(array, item) { | |
return ~array.indexOf(item) | |
} | |
/** | |
* Convert a string containing dashes to camel case | |
* @param { String } str - input string | |
* @returns { String } my-string -> myString | |
*/ | |
function toCamel(str) { | |
return str.replace(/-(\w)/g, function (_, c) { return c.toUpperCase(); }) | |
} | |
/** | |
* Faster String startsWith alternative | |
* @param { String } str - source string | |
* @param { String } value - test string | |
* @returns { Boolean } - | |
*/ | |
function startsWith(str, value) { | |
return str.slice(0, value.length) === value | |
} | |
/** | |
* 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 | |
} | |
/** | |
* 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 | |
} | |
var misc = Object.freeze({ | |
inherit: inherit, | |
each: each, | |
contains: contains, | |
toCamel: toCamel, | |
startsWith: startsWith, | |
defineProperty: defineProperty, | |
extend: extend | |
}); | |
var 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 | |
/** | |
* Public Api | |
*/ | |
// extend the el object adding the observable methods | |
Object.defineProperties(el, { | |
/** | |
* Listen to the given `event` ands | |
* execute the `callback` each time an event is triggered. | |
* @param { String } event - event id | |
* @param { Function } fn - callback function | |
* @returns { Object } el | |
*/ | |
on: { | |
value: function(event, fn) { | |
if (typeof fn == 'function') | |
{ (callbacks[event] = callbacks[event] || []).push(fn) } | |
return el | |
}, | |
enumerable: false, | |
writable: false, | |
configurable: false | |
}, | |
/** | |
* Removes the given `event` listeners | |
* @param { String } event - event id | |
* @param { Function } fn - callback function | |
* @returns { Object } el | |
*/ | |
off: { | |
value: function(event, fn) { | |
if (event == '*' && !fn) { callbacks = {} } | |
else { | |
if (fn) { | |
var arr = callbacks[event] | |
for (var i = 0, cb; cb = arr && arr[i]; ++i) { | |
if (cb == fn) { arr.splice(i--, 1) } | |
} | |
} else { delete callbacks[event] } | |
} | |
return el | |
}, | |
enumerable: false, | |
writable: false, | |
configurable: false | |
}, | |
/** | |
* Listen to the given `event` and | |
* execute the `callback` at most once | |
* @param { String } event - event id | |
* @param { Function } fn - callback function | |
* @returns { Object } el | |
*/ | |
one: { | |
value: function(event, fn) { | |
function on() { | |
el.off(event, on) | |
fn.apply(el, arguments) | |
} | |
return el.on(event, on) | |
}, | |
enumerable: false, | |
writable: false, | |
configurable: false | |
}, | |
/** | |
* Execute all callback functions that listen to | |
* the given `event` | |
* @param { String } event - event id | |
* @returns { Object } el | |
*/ | |
trigger: { | |
value: function(event) { | |
var arguments$1 = arguments; | |
// getting the arguments | |
var arglen = arguments.length - 1, | |
args = new Array(arglen), | |
fns, | |
fn, | |
i | |
for (i = 0; i < arglen; i++) { | |
args[i] = arguments$1[i + 1] // skip first argument | |
} | |
fns = slice.call(callbacks[event] || [], 0) | |
for (i = 0; fn = fns[i]; ++i) { | |
fn.apply(el, args) | |
if (fns[i] !== fn) { i-- } | |
} | |
if (callbacks['*'] && event != '*') | |
{ el.trigger.apply(el, ['*', event].concat(args)) } | |
return el | |
}, | |
enumerable: false, | |
writable: false, | |
configurable: false | |
} | |
}) | |
return el | |
} | |
var EVENTS_PREFIX_REGEX = /^on/ | |
/** | |
* Trigger DOM events | |
* @param { HTMLElement } dom - dom element target of the event | |
* @param { Function } handler - user function | |
* @param { Object } e - event object | |
*/ | |
function handleEvent(dom, handler, e) { | |
var ptag = this._parent, | |
item = this._item | |
if (!item) | |
{ while (ptag && !item) { | |
item = ptag._item | |
ptag = ptag._parent | |
} } | |
// 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 | |
handler.call(this, e) | |
if (!e.preventUpdate) { | |
getImmediateCustomParentTag(this).update() | |
} | |
} | |
/** | |
* 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) { | |
var eventName, | |
cb = handleEvent.bind(tag, dom, handler) | |
if (!dom.addEventListener) { | |
dom[name] = cb | |
return | |
} | |
// normalize event name | |
eventName = name.replace(EVENTS_PREFIX_REGEX, '') | |
// cache the callback directly on the DOM node | |
if (!dom._riotEvents) { dom._riotEvents = {} } | |
if (dom._riotEvents[name]) | |
{ dom.removeEventListener(eventName, dom._riotEvents[name]) } | |
dom._riotEvents[name] = cb | |
dom.addEventListener(eventName, cb, false) | |
} | |
/** | |
* Update dynamically created riot-tag with changing expressions | |
* @param { Object } expr - expression tag and expression info | |
* @param { Tag } parent - parent for tag creation | |
*/ | |
function updateRtag(expr, parent) { | |
var tagName = tmpl(expr.value, parent), | |
conf | |
if (expr.tag && expr.tagName === tagName) { | |
expr.tag.update() | |
return | |
} | |
// sync _parent to accommodate changing tagnames | |
if (expr.tag) { | |
var delName = expr.tag.opts.dataIs, | |
tags = expr.tag._parent.tags | |
arrayishRemove(tags, delName, expr.tag) | |
} | |
expr.impl = __TAG_IMPL[tagName] | |
conf = {root: expr.dom, parent: parent, hasImpl: true, tagName: tagName} | |
expr.tag = initChildTag(expr.impl, conf, expr.dom.innerHTML, parent) | |
expr.tagName = tagName | |
expr.tag.mount() | |
expr.tag.update() | |
// parent is the placeholder tag, not the dynamic tag so clean up | |
parent.on('unmount', function () { | |
var delName = expr.tag.opts.dataIs, | |
tags = expr.tag.parent.tags, | |
_tags = expr.tag._parent.tags | |
arrayishRemove(tags, delName, expr.tag) | |
arrayishRemove(_tags, delName, expr.tag) | |
expr.tag.unmount() | |
}) | |
} | |
/** | |
* Update on single tag expression | |
* @this Tag | |
* @param { Object } expr - expression logic | |
* @returns { undefined } | |
*/ | |
function updateExpression(expr) { | |
var dom = expr.dom, | |
attrName = expr.attr, | |
value = tmpl(expr.expr, this), | |
isValueAttr = attrName === 'value', | |
isVirtual = expr.root && expr.root.tagName === 'VIRTUAL', | |
parent = dom && (expr.parent || dom.parentNode), | |
old | |
if (expr.bool) | |
{ value = value ? attrName : false } | |
else if (isUndefined(value) || value === null) | |
{ value = '' } | |
if (expr._riot_id) { // if it's a tag | |
if (expr.isMounted) { | |
expr.update() | |
// if it hasn't been mounted yet, do that now. | |
} else { | |
expr.mount() | |
if (isVirtual) { | |
var frag = document.createDocumentFragment() | |
makeVirtual.call(expr, frag) | |
expr.root.parentElement.replaceChild(frag, expr.root) | |
} | |
} | |
return | |
} | |
old = expr.value | |
expr.value = value | |
if (expr.update) { | |
expr.update() | |
return | |
} | |
if (old === value) { return } | |
if (expr.isRtag && value) { return updateRtag(expr, this) } | |
// no change, so nothing more to do | |
if (isValueAttr && dom.value === value) { return } | |
// textarea and text nodes have 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 | |
} | |
// remove original attribute | |
remAttr(dom, attrName) | |
// event handler | |
if (isFunction(value)) { | |
setEventHandler(attrName, value, dom, this) | |
// show / hide | |
} else if (/^(show|hide)$/.test(attrName)) { | |
if (attrName === 'hide') { value = !value } | |
dom.style.display = value ? '' : 'none' | |
// field value | |
} else if (isValueAttr) { | |
dom.value = value | |
// <img src="{ expr }"> | |
} else if (startsWith(attrName, RIOT_PREFIX) && attrName !== RIOT_TAG_IS) { | |
if (value) | |
{ setAttr(dom, attrName.slice(RIOT_PREFIX.length), value) } | |
} else { | |
// <select> <option selected={true}> </select> | |
if (attrName === 'selected' && parent && /^(SELECT|OPTGROUP)$/.test(parent.nodeName) && value) | |
{ parent.value = dom.value } | |
if (expr.bool) { | |
dom[attrName] = value | |
if (!value) { return } | |
} | |
if (value === 0 || value && typeof value !== T_OBJECT) | |
{ setAttr(dom, attrName, value) } | |
} | |
} | |
/** | |
* Update all the expressions in a Tag instance | |
* @this Tag | |
* @param { Array } expressions - expression that must be re evaluated | |
*/ | |
function update$1$1(expressions) { | |
each(expressions, updateExpression.bind(this)) | |
} | |
var IfExpr = { | |
init: function init(dom, parentTag, expr) { | |
remAttr(dom, 'if') | |
this.parentTag = parentTag | |
this.expr = expr | |
this.stub = document.createTextNode('') | |
this.pristine = dom | |
var p = dom.parentNode | |
p.insertBefore(this.stub, dom) | |
p.removeChild(dom) | |
return this | |
}, | |
update: function update$1() { | |
var newValue = tmpl(this.expr, this.parentTag) | |
if (newValue && !this.current) { // insert | |
this.current = this.pristine.cloneNode(true) | |
this.stub.parentNode.insertBefore(this.current, this.stub) | |
this.expressions = [] | |
parseExpressions.apply(this.parentTag, [this.current, this.expressions, true]) | |
} | |
else if (!newValue && this.current) { // remove | |
unmountAll(this.expressions) | |
this.current.parentNode.removeChild(this.current) | |
this.current = null | |
this.expressions = [] | |
} | |
if (newValue) { update$1$1.call(this.parentTag, this.expressions) } | |
}, | |
unmount: function unmount() { | |
unmountAll(this.expressions || []) | |
delete this.pristine | |
delete this.parentNode | |
delete this.stub | |
} | |
} | |
var RefExpr = { | |
init: function init(dom, attrName, attrValue, parent) { | |
this.dom = dom | |
this.attr = attrName | |
this.rawValue = attrValue | |
this.parent = parent | |
this.hasExp = tmpl.hasExpr(attrValue) | |
this.firstRun = true | |
return this | |
}, | |
update: function update() { | |
var value = this.rawValue | |
if (this.hasExp) | |
{ value = tmpl(this.rawValue, this.parent) } | |
// if nothing changed, we're done | |
if (!this.firstRun && value === this.value) { return } | |
var customParent = this.parent && getImmediateCustomParentTag(this.parent) | |
// if the referenced element is a custom tag, then we set the tag itself, rather than DOM | |
var tagOrDom = this.tag || this.dom | |
// the name changed, so we need to remove it from the old key (if present) | |
if (!isBlank(this.value) && customParent) | |
{ arrayishRemove(customParent.refs, this.value, tagOrDom) } | |
if (isBlank(value)) { | |
// if the value is blank, we remove it | |
remAttr(this.dom, this.attr) | |
} else { | |
// add it to the refs of parent tag (this behavior was changed >=3.0) | |
if (customParent) { arrayishAdd(customParent.refs, value, tagOrDom) } | |
// set the actual DOM attr | |
setAttr(this.dom, this.attr, value) | |
} | |
this.value = value | |
this.firstRun = false | |
}, | |
unmount: function unmount() { | |
var tagOrDom = this.tag || this.dom | |
var customParent = this.parent && getImmediateCustomParentTag(this.parent) | |
if (!isBlank(this.value) && customParent) | |
{ arrayishRemove(customParent.refs, this.value, tagOrDom) } | |
delete this.dom | |
delete this.parent | |
} | |
} | |
/** | |
* 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 | |
* @param { Object } base - prototype object for the new item | |
* @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, base) { | |
var item = base ? Object.create(base) : {} | |
item[expr.key] = key | |
if (expr.pos) { item[expr.pos] = val } | |
return item | |
} | |
/** | |
* Unmount the redundant tags | |
* @param { Array } items - see maintainOrder param | |
* @param { Array } tags - array containing all the children tags | |
* @param { String } tagName - key used to identify the type of tag | |
* @param { Object } parent - parent tag to remove the child from | |
* @param { Boolean } maintainOrder - if true, param items contains items | |
* to keep. If false (default), param items contains current items to loop | |
* through. | |
*/ | |
function unmountRedundant(items, tags, tagName, parent, maintainOrder) { | |
if (maintainOrder) { | |
each(tags, function(tag, i) { | |
if (tag === undefined) { return } // fix for each function not reevaluating the length | |
if (!~items.indexOf(tag)) { | |
var t = tags.splice(i, 1)[0] | |
t.unmount() | |
arrayishRemove(parent.tags, tagName, t, true) | |
return false | |
} | |
}) | |
} else { | |
var i = tags.length, | |
j = items.length, | |
t | |
while (i > j) { | |
t = tags[--i] | |
tags.splice(i, 1) | |
t.unmount() | |
arrayishRemove(parent.tags, tagName, t, true) | |
} | |
} | |
} | |
/** | |
* Move the nested custom tags in non custom loop tags | |
* @this Tag | |
* @param { Number } i - current position of the loop tag | |
*/ | |
function moveNestedTags(i) { | |
var this$1 = this; | |
each(Object.keys(this.tags), function (tagName) { | |
var tag = this$1.tags[tagName] | |
if (isArray(tag)) | |
{ each(tag, function (t) { | |
moveChildTag.apply(t, [tagName, i]) | |
}) } | |
else | |
{ moveChildTag.apply(tag, [tagName, i]) } | |
}) | |
} | |
/** | |
* Move a child tag | |
* @this Tag | |
* @param { HTMLElement } root - dom node containing all the loop children | |
* @param { Tag } nextTag - instance of the next tag preceding the one we want to move | |
* @param { Boolean } isVirtual - is it a virtual tag? | |
*/ | |
function move(root, nextTag, isVirtual) { | |
if (isVirtual) | |
{ moveVirtual.apply(this, [root, nextTag]) } | |
else | |
{ safeInsert(root, this.root, nextTag.root) } | |
} | |
/** | |
* Insert and mount a child tag | |
* @this Tag | |
* @param { HTMLElement } root - dom node containing all the loop children | |
* @param { Tag } nextTag - instance of the next tag preceding the one we want to insert | |
* @param { Boolean } isVirtual - is it a virtual tag? | |
*/ | |
function insert(root, nextTag, isVirtual) { | |
if (isVirtual) | |
{ makeVirtual.apply(this, [root, nextTag]) } | |
else | |
{ safeInsert(root, this.root, nextTag.root) } | |
} | |
/** | |
* Append a new tag into the DOM | |
* @this Tag | |
* @param { HTMLElement } root - dom node containing all the loop children | |
* @param { Boolean } isVirtual - is it a virtual tag? | |
*/ | |
function append(root, isVirtual) { | |
if (isVirtual) | |
{ makeVirtual.call(this, root) } | |
else | |
{ root.appendChild(this.root) } | |
} | |
/** | |
* Update option nodes that seem to be buggy on Firefox see also #1374 | |
* @param { HTMLElement } root - <select> tag | |
*/ | |
function updateSelect(root) { | |
for (var n = 0; n < root.length; n++) { | |
if (root[n].__riot1374) { | |
root.selectedIndex = n // clear other options | |
delete root[n].__riot1374 | |
break | |
} | |
} | |
} | |
/** | |
* Manage tags having the 'each' | |
* @param { HTMLElement } 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 | |
* @returns { Object } expression object for this each loop | |
*/ | |
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 = __TAG_IMPL[tagName] || { tmpl: getOuterHTML(dom) }, | |
useRoot = RE_SPECIAL_TAGS.test(tagName), | |
root = dom.parentNode, | |
ref = createDOMPlaceholder(), | |
child = getTag(dom), | |
ifExpr = getAttr(dom, 'if'), | |
isOption = tagName.toLowerCase() === 'option', // the option tags must be treated differently | |
tags = [], | |
oldItems = [], | |
hasKeys, | |
isLoop = true, | |
isAnonymous = !__TAG_IMPL[tagName], | |
isVirtual = dom.tagName === 'VIRTUAL', | |
toKeep = [] | |
// parse the each expression | |
expr = tmpl.loopKeys(expr) | |
expr.isLoop = true | |
if (ifExpr) { remAttr(dom, 'if') } | |
// insert a marked where the loop tags will be injected | |
root.insertBefore(ref, dom) | |
root.removeChild(dom) | |
expr.update = function updateEach() { | |
// get the new items collection | |
var items = tmpl(expr.val, parent), | |
parentNode, | |
frag, | |
placeholder | |
root = ref.parentNode | |
if (parentNode) { | |
placeholder = createDOMPlaceholder('') | |
parentNode.insertBefore(placeholder, root) | |
parentNode.removeChild(root) | |
} else { | |
frag = createFrag() | |
} | |
// 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]) | |
}) : [] | |
} | |
if (ifExpr) { | |
items = items.filter(function(item, i) { | |
var context = mkitem(expr, item, i, parent) | |
return !!tmpl(ifExpr, context) | |
}) | |
} | |
// loop all the new items | |
each(items, function(item, i) { | |
// reorder only if the items are objects | |
var | |
_mustReorder = mustReorder && typeof item === T_OBJECT && !hasKeys, | |
oldPos = oldItems.indexOf(item), | |
pos = ~oldPos && (_mustReorder || isFunction(tags[oldPos].beforeUnmount) ) ? 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 | |
) { | |
var mustAppend = i === tags.length | |
tag = new Tag$$1(impl, { | |
parent: parent, | |
isLoop: isLoop, | |
isAnonymous: isAnonymous, | |
root: useRoot ? root : dom.cloneNode(), | |
item: item | |
}, dom.innerHTML) | |
// mount the tag | |
tag.mount() | |
if (mustAppend) | |
{ append.apply(tag, [frag || root, isVirtual]) } | |
else | |
{ insert.apply(tag, [root, tags[i], isVirtual]) } | |
if (!mustAppend) { oldItems.splice(i, 0, item) } | |
tags.splice(i, 0, tag) | |
if (child) { arrayishAdd(parent.tags, tagName, tag, true) } | |
pos = i // handled here so no move | |
} else { tag.update(item) } | |
// reorder the tag if it's not located in its previous position | |
if (pos !== i && _mustReorder) { | |
move.apply(tag, [root, tags[i], isVirtual]) | |
// 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.call(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) | |
if (isFunction(tag.beforeUnmount)) | |
{ toKeep.push(tag) } | |
}) | |
// remove the redundant tags | |
unmountRedundant(toKeep.length ? toKeep : items, tags, tagName, parent, toKeep.length) | |
// #1374 FireFox bug in <option selected={expression}> | |
if (isOption && FIREFOX && !root.multiple) { updateSelect(root) } | |
// clone the items array | |
oldItems = items.slice() | |
// empty the items we are keeping | |
toKeep = [] | |
if (frag) { | |
root.insertBefore(frag, ref) | |
} else { | |
parentNode.insertBefore(root, placeholder) | |
parentNode.removeChild(placeholder) | |
} | |
} | |
expr.unmount = function() { | |
each(tags, function(t) { t.unmount() }) | |
} | |
return expr | |
} | |
/** | |
* Walk the tag DOM to detect the expressions to evaluate | |
* @this Tag | |
* @param { HTMLElement } root - root tag where we will start digging the expressions | |
* @param { Array } expressions - empty array where the expressions will be added | |
* @param { Boolean } mustIncludeRoot - flag to decide whether the root must be parsed as well | |
*/ | |
function parseExpressions(root, expressions, mustIncludeRoot) { | |
var this$1 = this; | |
var base = {parent: {children: expressions}} | |
walkNodes(root, function (dom, ctx) { | |
var type = dom.nodeType, parent = ctx.parent, attr, expr, tagImpl | |
if (!mustIncludeRoot && dom === root) { return {parent: parent} } | |
// text node | |
if (type === 3 && dom.parentNode.tagName !== 'STYLE' && tmpl.hasExpr(dom.nodeValue)) | |
{ parent.children.push({dom: dom, expr: dom.nodeValue}) } | |
if (type !== 1) { return ctx } // not an element | |
// loop. each does it's own thing (for now) | |
if (attr = getAttr(dom, 'each')) { | |
parent.children.push(_each(dom, this$1, attr)) | |
return false | |
} | |
// if-attrs become the new parent. Any following expressions (either on the current | |
// element, or below it) become children of this expression. | |
if (attr = getAttr(dom, 'if')) { | |
parent.children.push(Object.create(IfExpr).init(dom, this$1, attr)) | |
return false | |
} | |
if (expr = getAttr(dom, RIOT_TAG_IS)) { | |
if (tmpl.hasExpr(expr)) { | |
parent.children.push({isRtag: true, expr: expr, dom: dom}) | |
return false | |
} | |
} | |
// if this is a tag, stop traversing here. | |
// we ignore the root, since parseExpressions is called while we're mounting that root | |
tagImpl = getTag(dom) | |
if (tagImpl && (dom !== root || mustIncludeRoot)) { | |
var conf = {root: dom, parent: this$1, hasImpl: true} | |
parent.children.push(initChildTag(tagImpl, conf, dom.innerHTML, this$1)) | |
return false | |
} | |
// attribute expressions | |
parseAttributes.apply(this$1, [dom, dom.attributes, function(attr, expr) { | |
if (!expr) { return } | |
parent.children.push(expr) | |
}]) | |
// whatever the parent is, all child elements get the same parent. | |
// If this element had an if-attr, that's the parent for all child elements | |
return {parent: parent} | |
}, base) | |
} | |
/** | |
* Calls `fn` for every attribute on an element. If that attr has an expression, | |
* it is also passed to fn. | |
* @this Tag | |
* @param { HTMLElement } dom - dom node to parse | |
* @param { Array } attrs - array of attributes | |
* @param { Function } fn - callback to exec on any iteration | |
*/ | |
function parseAttributes(dom, attrs, fn) { | |
var this$1 = this; | |
each(attrs, function (attr) { | |
var name = attr.name, bool = isBoolAttr(name), expr | |
if (~['ref', 'data-ref'].indexOf(name)) { | |
expr = Object.create(RefExpr).init(dom, name, attr.value, this$1) | |
} else if (tmpl.hasExpr(attr.value)) { | |
expr = {dom: dom, expr: attr.value, attr: attr.name, bool: bool} | |
} | |
fn(attr, expr) | |
}) | |
} | |
/* | |
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 reHasYield = /<yield\b/i; | |
var reYieldAll = /<yield\s*(?:\/>|>([\S\s]*?)<\/yield\s*>|>)/ig; | |
var reYieldSrc = /<yield\s+to=['"]([^'">]*)['"]\s*>([\S\s]*?)<\/yield\s*>/ig; | |
var reYieldDest = /<yield\s+from=['"]?([-\w]+)['"]?\s*(?:\/>|>([\S\s]*?)<\/yield\s*>)/ig; | |
var rootEls = { tr: 'tbody', th: 'tr', td: 'tr', col: 'colgroup' }; | |
var tblTags = IE_VERSION && IE_VERSION < 10 ? RE_SPECIAL_TAGS : RE_SPECIAL_TAGS_NO_OPTION; | |
var GENERIC = 'div' | |
/* | |
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 || '' | |
}) | |
} | |
/** | |
* 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(GENERIC, 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 | |
} | |
/** | |
* Another way to create a riot tag a bit more es6 friendly | |
* @param { HTMLElement } el - tag DOM selector or DOM node/s | |
* @param { Object } opts - tag logic | |
* @returns { Tag } new riot tag instance | |
*/ | |
function Tag$1(el, opts) { | |
// get the tag properties from the class constructor | |
var ref = this; | |
var name = ref.name; | |
var tmpl = ref.tmpl; | |
var css = ref.css; | |
var attrs = ref.attrs; | |
var onCreate = ref.onCreate; | |
// register a new tag and cache the class prototype | |
if (!__TAG_IMPL[name]) { | |
tag(name, tmpl, css, attrs, onCreate) | |
// cache the class constructor | |
__TAG_IMPL[name].class = this.constructor | |
} | |
// mount the tag using the class instance | |
mountTo(el, name, opts, this) | |
return this | |
} | |
/** | |
* Create a new riot tag implementation | |
* @param { String } name - name/id of the new riot tag | |
* @param { String } tmpl - 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 | |
*/ | |
function tag(name, tmpl, 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() | |
__TAG_IMPL[name] = { name: name, tmpl: tmpl, 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 } tmpl - 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 | |
*/ | |
function tag2(name, tmpl, css, attrs, fn) { | |
if (css) | |
{ styleManager.add(css, name) } | |
var exists = !!__TAG_IMPL[name] | |
__TAG_IMPL[name] = { name: name, tmpl: tmpl, attrs: attrs, fn: fn } | |
if (exists && riot.util.hotReloader) | |
{ riot.util.hotReloader(name) } | |
return name | |
} | |
/** | |
* Mount a tag using a specific tag implementation | |
* @param { * } selector - tag DOM selector or DOM node/s | |
* @param { String } tagName - tag implementation name | |
* @param { Object } opts - tag logic | |
* @returns { Array } new tags instances | |
*/ | |
function mount$1(selector, tagName, opts) { | |
var tags = [] | |
function pushTagsTo(root) { | |
if (root.tagName) { | |
var riotTag = getAttr(root, RIOT_TAG_IS) | |
// have tagName? force riot-tag to be the same | |
if (tagName && riotTag !== tagName) { | |
riotTag = tagName | |
setAttr(root, RIOT_TAG_IS, tagName) | |
} | |
var tag = mountTo(root, riotTag || root.tagName.toLowerCase(), opts) | |
if (tag) | |
{ tags.push(tag) } | |
} else if (root.length) | |
{ each(root, pushTagsTo) } // assume nodeList | |
} | |
// inject styles into DOM | |
styleManager.inject() | |
if (isObject(tagName)) { | |
opts = tagName | |
tagName = 0 | |
} | |
var elem | |
var allTags | |
// crawl the DOM to find the tag | |
if (isString(selector)) { | |
selector = selector === '*' ? | |
// select all registered tags | |
// & tags found with the riot-tag attribute set | |
allTags = selectTags() : | |
// or just the ones named like the selector | |
selector + selectTags(selector.split(/, */)) | |
// make sure to pass always a selector | |
// to the querySelectorAll function | |
elem = selector ? $$(selector) : [] | |
} | |
else | |
// probably you have passed already a tag or a NodeList | |
{ elem = selector } | |
// select all the registered and mount them inside their root elements | |
if (tagName === '*') { | |
// get all custom tags | |
tagName = allTags || selectTags() | |
// if the root els it's just a single tag | |
if (elem.tagName) | |
{ elem = $$(tagName, elem) } | |
else { | |
// select all the children for all the different root elements | |
var nodeList = [] | |
each(elem, function (_el) { return nodeList.push($$(tagName, _el)); }) | |
elem = nodeList | |
} | |
// get rid of the tagName | |
tagName = 0 | |
} | |
pushTagsTo(elem) | |
return tags | |
} | |
// Create a mixin that could be globally shared across all the tags | |
var mixins = {} | |
var globals = mixins[GLOBAL_MIXIN] = {} | |
var _id = 0 | |
/** | |
* Create/Return a mixin by its name | |
* @param { String } name - mixin name (global mixin if object) | |
* @param { Object } mix - mixin logic | |
* @param { Boolean } g - is global? | |
* @returns { Object } the mixin logic | |
*/ | |
function mixin(name, mix, g) { | |
// Unnamed global | |
if (isObject(name)) { | |
mixin(("__unnamed_" + (_id++)), name, true) | |
return | |
} | |
var store = g ? globals : mixins | |
// Getter | |
if (!mix) { | |
if (isUndefined(store[name])) | |
{ throw new Error('Unregistered mixin: ' + name) } | |
return store[name] | |
} | |
// Setter | |
store[name] = isFunction(mix) ? | |
extend(mix.prototype, store[name] || {}) && mix : | |
extend(store[name] || {}, mix) | |
} | |
/** | |
* Update all the tags instances created | |
* @returns { Array } all the tags instances | |
*/ | |
function update$2() { | |
return each(__VIRTUAL_DOM, function (tag) { return tag.update(); }) | |
} | |
function unregister(name) { | |
delete __TAG_IMPL[name] | |
} | |
// counter to give a unique id to all the Tag instances | |
var __uid = 0 | |
/** | |
* We need to update opts for this tag. That requires updating the expressions | |
* in any attributes on the tag, and then copying the result onto opts. | |
* @this Tag | |
* @param {Boolean} isLoop - is it a loop tag? | |
* @param { Tag } parent - parent tag node | |
* @param { Boolean } isAnonymous - is it a tag without any impl? (a tag not registered) | |
* @param { Object } opts - tag options | |
* @param { Array } instAttrs - tag attributes array | |
*/ | |
function updateOpts(isLoop, parent, isAnonymous, opts, instAttrs) { | |
// isAnonymous `each` tags treat `dom` and `root` differently. In this case | |
// (and only this case) we don't need to do updateOpts, because the regular parse | |
// will update those attrs. Plus, isAnonymous tags don't need opts anyway | |
if (isLoop && isAnonymous) { return } | |
var ctx = !isAnonymous && isLoop ? this : parent || this | |
each(instAttrs, function (attr) { | |
if (attr.expr) { update$1$1.call(ctx, [attr.expr]) } | |
opts[toCamel(attr.name)] = attr.expr ? attr.expr.value : attr.value | |
}) | |
} | |
/** | |
* Tag class | |
* @constructor | |
* @param { Object } impl - it contains the tag template, and logic | |
* @param { Object } conf - tag options | |
* @param { String } innerHTML - html that eventually we need to inject in the tag | |
*/ | |
function Tag$$1(impl, conf, innerHTML) { | |
var opts = inherit(conf.opts), | |
parent = conf.parent, | |
isLoop = conf.isLoop, | |
isAnonymous = conf.isAnonymous, | |
item = cleanUpData(conf.item), | |
instAttrs = [], // All attributes on the Tag when it's first parsed | |
implAttrs = [], // expressions on this type of Tag | |
expressions = [], | |
root = conf.root, | |
tagName = conf.tagName || root.tagName.toLowerCase(), | |
propsInSyncWithParent = [], | |
dom | |
// make this tag observable | |
observable(this) | |
// only call unmount if we have a valid __TAG_IMPL (has name property) | |
if (impl.name && root._tag) { root._tag.unmount(true) } | |
// not yet mounted | |
this.isMounted = false | |
root.isLoop = isLoop | |
defineProperty(this, '_internal', { | |
isAnonymous: isAnonymous, | |
instAttrs: instAttrs, | |
innerHTML: innerHTML, | |
// these vars will be needed only for the virtual tags | |
virts: [], | |
tail: null, | |
head: null | |
}) | |
// 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" and "refs" property from being overridden | |
defineProperty(this, 'tags', {}) | |
defineProperty(this, 'refs', {}) | |
dom = mkdom(impl.tmpl, innerHTML) | |
/** | |
* Update the tag expressions and options | |
* @param { * } data - data we want to use to extend the tag properties | |
* @returns { Tag } | |
*/ | |
defineProperty(this, 'update', function tagUpdate(data) { | |
if (isFunction(this.shouldUpdate) && !this.shouldUpdate()) { return } | |
// make sure the data passed will not override | |
// the component core methods | |
data = cleanUpData(data) | |
// inherit properties from the parent, but only for isAnonymous tags | |
if (isLoop && isAnonymous) { inheritFrom.apply(this, [this.parent, propsInSyncWithParent]) } | |
extend(this, data) | |
updateOpts.apply(this, [isLoop, parent, isAnonymous, opts, instAttrs]) | |
if (this.isMounted) { this.trigger('update', data) } | |
update$1$1.call(this, expressions) | |
if (this.isMounted) { this.trigger('updated') } | |
return this | |
}) | |
/** | |
* Add a mixin to this tag | |
* @returns { Tag } | |
*/ | |
defineProperty(this, 'mixin', function tagMixin() { | |
var this$1 = this; | |
each(arguments, function (mix) { | |
var instance, | |
props = [], | |
obj | |
mix = isString(mix) ? mixin(mix) : mix | |
// check if the mixin is a function | |
if (isFunction(mix)) { | |
// create the new mixin instance | |
instance = new mix() | |
} else { instance = mix } | |
var proto = Object.getPrototypeOf(instance) | |
// 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 this | |
// allow mixins to override other properties/parent mixins | |
if (key !== 'init') { | |
// check for getters/setters | |
var descriptor = Object.getOwnPropertyDescriptor(instance, key) || Object.getOwnPropertyDescriptor(proto, key) | |
var hasGetterSetter = descriptor && (descriptor.get || descriptor.set) | |
// apply method only if it does not already exist on the instance | |
if (!this$1.hasOwnProperty(key) && hasGetterSetter) { | |
Object.defineProperty(this$1, key, descriptor) | |
} else { | |
this$1[key] = isFunction(instance[key]) ? | |
instance[key].bind(this$1) : | |
instance[key] | |
} | |
} | |
}) | |
// init method will be called automatically | |
if (instance.init) | |
{ instance.init.bind(this$1)() } | |
}) | |
return this | |
}) | |
/** | |
* Mount the current tag instance | |
* @returns { Tag } | |
*/ | |
defineProperty(this, 'mount', function tagMount() { | |
var this$1 = this; | |
root._tag = this // keep a reference to the tag just created | |
// add global mixins | |
var globalMixin = mixin(GLOBAL_MIXIN) | |
if (globalMixin) | |
{ for (var i in globalMixin) | |
{ if (globalMixin.hasOwnProperty(i)) | |
{ this$1.mixin(globalMixin[i]) } } } | |
// Read all the attrs on this instance. This give us the info we need for updateOpts | |
parseAttributes.apply(parent, [root, root.attributes, function (attr, expr) { | |
if (!isAnonymous && RefExpr.isPrototypeOf(expr)) { expr.tag = this$1 } | |
attr.expr = expr | |
instAttrs.push(attr) | |
}]) | |
// children in loop should inherit from true parent | |
if (this._parent && isAnonymous) { inheritFrom.apply(this, [this._parent, propsInSyncWithParent]) } | |
// initialiation | |
updateOpts.apply(this, [isLoop, parent, isAnonymous, opts, instAttrs]) | |
if (impl.fn) { impl.fn.call(this, opts) } | |
this.trigger('before-mount') | |
// update the root adding custom attributes coming from the compiler | |
implAttrs = [] | |
walkAttrs(impl.attrs, function (k, v) { implAttrs.push({name: k, value: v}) }) | |
parseAttributes.apply(this, [root, implAttrs, function (attr, expr) { | |
if (expr) { expressions.push(expr) } | |
else { setAttr(root, attr.name, attr.value) } | |
}]) | |
// parse layout after init. fn may calculate args for nested custom tags | |
parseExpressions.apply(this, [dom, expressions, false]) | |
this.update(item) | |
if (isLoop && isAnonymous) { | |
// update the root attribute for the looped elements | |
this.root = root = dom.firstChild | |
} else { | |
while (dom.firstChild) { root.appendChild(dom.firstChild) } | |
if (root.stub) { root = parent.root } | |
} | |
defineProperty(this, 'root', root) | |
this.isMounted = true | |
// if it's not a child tag we can trigger its mount event | |
if (!this.parent || this.parent.isMounted) { | |
this.trigger('mount') | |
} | |
// otherwise we need to wait that the parent event gets triggered | |
else { this.parent.one('mount', function () { | |
this$1.trigger('mount') | |
}) } | |
}) | |
/** | |
* Unmount the tag instance | |
* @param { Boolean } mustKeepRoot - if it's true the root node will not be removed | |
*/ | |
defineProperty(this, 'unmount', function tagUnmount(mustKeepRoot) { | |
this.trigger('before-unmount') | |
var unmount = function(mustKeepRoot) { | |
var el = this.root, | |
p = el.parentNode, | |
ptag, | |
tagIndex = __VIRTUAL_DOM.indexOf(this) | |
// remove this tag instance from the global virtualDom variable | |
if (~tagIndex) | |
{ __VIRTUAL_DOM.splice(tagIndex, 1) } | |
if (p) { | |
if (parent) { | |
ptag = getImmediateCustomParentTag(parent) | |
arrayishRemove(ptag.tags, tagName, this) | |
} | |
else | |
{ while (el.firstChild) { el.removeChild(el.firstChild) } } | |
if (!mustKeepRoot) | |
{ p.removeChild(el) } | |
else | |
// the riot-tag and the data-is attributes aren't needed anymore, remove them | |
{ remAttr(p, RIOT_TAG_IS) } | |
} | |
if (this._internal.virts) { | |
each(this._internal.virts, function (v) { | |
if (v.parentNode) { v.parentNode.removeChild(v) } | |
}) | |
} | |
// allow expressions to unmount themselves | |
unmountAll(expressions) | |
each(instAttrs, function (a) { return a.expr && a.expr.unmount && a.expr.unmount(); }) | |
this.trigger('unmount') | |
this.off('*') | |
this.isMounted = false | |
delete this.root._tag | |
}.bind(this, mustKeepRoot) | |
if (isFunction(this.beforeUnmount)) { | |
this.beforeUnmount.call(this, unmount) | |
} else { | |
unmount() | |
} | |
}) | |
} | |
/** | |
* 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 && __TAG_IMPL[getAttr(dom, RIOT_TAG_IS) || | |
getAttr(dom, RIOT_TAG_IS) || dom.tagName.toLowerCase()] | |
} | |
/** | |
* Inherit properties from a target tag instance | |
* @this Tag | |
* @param { Tag } target - tag where we will inherit properties | |
* @param { Array } propsInSyncWithParent - array of properties to sync with the target | |
*/ | |
function inheritFrom(target, propsInSyncWithParent) { | |
var this$1 = this; | |
each(Object.keys(target), function (k) { | |
// some properties must be always in sync with the parent tag | |
var mustSync = !isReservedName(k) && contains(propsInSyncWithParent, k) | |
if (isUndefined(this$1[k]) || mustSync) { | |
// track the property to keep in sync | |
// so we can keep it updated | |
if (!mustSync) { propsInSyncWithParent.push(k) } | |
this$1[k] = target[k] | |
} | |
}) | |
} | |
/** | |
* Move the position of a custom tag in its parent tag | |
* @this Tag | |
* @param { String } tagName - key where the tag was stored | |
* @param { Number } newPos - index where the new tag will be stored | |
*/ | |
function moveChildTag(tagName, newPos) { | |
var parent = this.parent, | |
tags | |
// no parent no move | |
if (!parent) { return } | |
tags = parent.tags[tagName] | |
if (isArray(tags)) | |
{ tags.splice(newPos, 0, tags.splice(tags.indexOf(this), 1)[0]) } | |
else { arrayishAdd(parent.tags, tagName, this) } | |
} | |
/** | |
* 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 | |
* @param { Boolean } skipName - hack to ignore the name attribute when attaching to parent | |
* @returns { Object } instance of the new child tag just created | |
*/ | |
function initChildTag(child, opts, innerHTML, parent) { | |
var tag = new Tag$$1(child, opts, innerHTML), | |
tagName = opts.tagName || getTagName(opts.root, true), | |
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 | |
arrayishAdd(ptag.tags, tagName, tag) | |
// and also to the real parent tag | |
if (ptag !== parent) | |
{ arrayishAdd(parent.tags, tagName, tag) } | |
// 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 (ptag._internal.isAnonymous) { | |
if (!ptag.parent) { break } | |
ptag = ptag.parent | |
} | |
return ptag | |
} | |
/** | |
* Trigger the unmount method on all the expressions | |
* @param { Array } expressions - DOM expressions | |
*/ | |
function unmountAll(expressions) { | |
each(expressions, function(expr) { | |
if (expr instanceof Tag$$1) { expr.unmount(true) } | |
else if (expr.unmount) { expr.unmount() } | |
}) | |
} | |
/** | |
* Get the tag name of any DOM node | |
* @param { Object } dom - DOM node we want to parse | |
* @param { Boolean } skipName - hack to ignore the name attribute when attaching to parent | |
* @returns { String } name to identify this dom node in riot | |
*/ | |
function getTagName(dom, skipName) { | |
var child = getTag(dom), | |
namedTag = !skipName && getAttr(dom, 'name'), | |
tagName = namedTag && !tmpl.hasExpr(namedTag) ? | |
namedTag : | |
child ? child.name : dom.tagName.toLowerCase() | |
return tagName | |
} | |
/** | |
* 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$$1) && !(data && typeof data.trigger === T_FUNCTION)) | |
{ return data } | |
var o = {} | |
for (var key in data) { | |
if (!RE_RESERVED_NAMES.test(key)) { o[key] = data[key] } | |
} | |
return o | |
} | |
/** | |
* Set the property of an object for a given key. If something already | |
* exists there, then it becomes an array containing both the old and new value. | |
* @param { Object } obj - object on which to set the property | |
* @param { String } key - property name | |
* @param { Object } value - the value of the property to be set | |
* @param { Boolean } ensureArray - ensure that the property remains an array | |
*/ | |
function arrayishAdd(obj, key, value, ensureArray) { | |
var dest = obj[key] | |
var isArr = isArray(dest) | |
if (dest && dest === value) { return } | |
// if the key was never set, set it once | |
if (!dest && ensureArray) { obj[key] = [value] } | |
else if (!dest) { obj[key] = value } | |
// if it was an array and not yet set | |
else if (!isArr || isArr && !contains(dest, value)) { | |
if (isArr) { dest.push(value) } | |
else { obj[key] = [dest, value] } | |
} | |
} | |
/** | |
* Removes an item from an object at a given key. If the key points to an array, | |
* then the item is just removed from the array. | |
* @param { Object } obj - object on which to remove the property | |
* @param { String } key - property name | |
* @param { Object } value - the value of the property to be removed | |
* @param { Boolean } ensureArray - ensure that the property remains an array | |
*/ | |
function arrayishRemove(obj, key, value, ensureArray) { | |
if (isArray(obj[key])) { | |
each(obj[key], function(item, i) { | |
if (item === value) { obj[key].splice(i, 1) } | |
}) | |
if (!obj[key].length) { delete obj[key] } | |
else if (obj[key].length === 1 && !ensureArray) { obj[key] = obj[key][0] } | |
} else | |
{ delete obj[key] } // otherwise just delete the key | |
} | |
/** | |
* 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 | |
} | |
/** | |
* 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 | |
* @param { Object } ctx - optional context that will be used to extend an existing class ( used in riot.Tag ) | |
* @returns { Tag } a new Tag instance | |
*/ | |
function mountTo(root, tagName, opts, ctx) { | |
var impl = __TAG_IMPL[tagName], | |
implClass = __TAG_IMPL[tagName].class, | |
tag = ctx || (implClass ? Object.create(implClass.prototype) : {}), | |
// cache the inner HTML to fix #855 | |
innerHTML = root._innerHTML = root._innerHTML || root.innerHTML | |
// clear the inner html | |
root.innerHTML = '' | |
var conf = { root: root, opts: opts } | |
if (opts && opts.parent) { conf.parent = opts.parent } | |
if (impl && root) { Tag$$1.apply(tag, [impl, conf, innerHTML]) } | |
if (tag && tag.mount) { | |
tag.mount(true) | |
// add this tag to the virtualDom variable | |
if (!contains(__VIRTUAL_DOM, tag)) { __VIRTUAL_DOM.push(tag) } | |
} | |
return tag | |
} | |
/** | |
* Adds the elements for a virtual tag | |
* @this Tag | |
* @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 makeVirtual(src, target) { | |
var this$1 = this; | |
var head = createDOMPlaceholder(), | |
tail = createDOMPlaceholder(), | |
frag = createFrag(), | |
sib, el | |
this._internal.head = this.root.insertBefore(head, this.root.firstChild) | |
this._internal.tail = this.root.appendChild(tail) | |
el = this._internal.head | |
while (el) { | |
sib = el.nextSibling | |
frag.appendChild(el) | |
this$1._internal.virts.push(el) // hold for unmounting | |
el = sib | |
} | |
if (target) | |
{ src.insertBefore(frag, target._internal.head) } | |
else | |
{ src.appendChild(frag) } | |
} | |
/** | |
* Move virtual tag and all child nodes | |
* @this Tag | |
* @param { Node } src - the node that will do the inserting | |
* @param { Tag } target - insert before this tag's first child | |
*/ | |
function moveVirtual(src, target) { | |
var this$1 = this; | |
var el = this._internal.head, | |
frag = createFrag(), | |
sib | |
while (el) { | |
sib = el.nextSibling | |
frag.appendChild(el) | |
el = sib | |
if (el === this$1._internal.tail) { | |
frag.appendChild(el) | |
src.insertBefore(frag, target._internal.head) | |
break | |
} | |
} | |
} | |
/** | |
* Get selectors for tags | |
* @param { Array } tags - tag names to select | |
* @returns { String } selector | |
*/ | |
function selectTags(tags) { | |
// select all tags | |
if (!tags) { | |
var keys = Object.keys(__TAG_IMPL) | |
return keys + selectTags(keys) | |
} | |
return tags | |
.filter(function (t) { return !/[^-\w]/.test(t); }) | |
.reduce(function (list, t) { | |
var name = t.trim().toLowerCase() | |
return list + ",[" + RIOT_TAG_IS + "=\"" + name + "\"]" | |
}, '') | |
} | |
var tags = Object.freeze({ | |
getTag: getTag, | |
inheritFrom: inheritFrom, | |
moveChildTag: moveChildTag, | |
initChildTag: initChildTag, | |
getImmediateCustomParentTag: getImmediateCustomParentTag, | |
unmountAll: unmountAll, | |
getTagName: getTagName, | |
cleanUpData: cleanUpData, | |
arrayishAdd: arrayishAdd, | |
arrayishRemove: arrayishRemove, | |
isInStub: isInStub, | |
mountTo: mountTo, | |
makeVirtual: makeVirtual, | |
moveVirtual: moveVirtual, | |
selectTags: selectTags | |
}); | |
/** | |
* Riot public api | |
*/ | |
var settings = Object.create(brackets.settings) | |
var util = { | |
tmpl: tmpl, | |
brackets: brackets, | |
styleManager: styleManager, | |
vdom: __VIRTUAL_DOM, | |
styleNode: styleManager.styleNode, | |
// export the riot internal utils as well | |
dom: dom, | |
check: check, | |
misc: misc, | |
tags: tags | |
} | |
var riot$1 = Object.freeze({ | |
settings: settings, | |
util: util, | |
observable: observable, | |
Tag: Tag$1, | |
tag: tag, | |
tag2: tag2, | |
mount: mount$1, | |
mixin: mixin, | |
update: update$2, | |
unregister: unregister | |
}); | |
/** | |
* Compiler for riot custom tags | |
* @version v3.0.0-alpha.1 | |
*/ | |
/** | |
* @module parsers | |
*/ | |
var parsers$1 = (function () { | |
function _req (name) { | |
var parser = window[name] | |
if (parser) { return parser } | |
throw new Error(name + ' parser not found.') | |
} | |
function extend (obj, props) { | |
if (props) { | |
for (var prop in props) { | |
/* istanbul ignore next */ | |
if (props.hasOwnProperty(prop)) { | |
obj[prop] = props[prop] | |
} | |
} | |
} | |
return obj | |
} | |
var _p = { | |
html: { | |
jade: function (html, opts, url) { | |
opts = extend({ | |
pretty: true, | |
filename: url, | |
doctype: 'html' | |
}, opts) | |
return _req('jade').render(html, opts) | |
} | |
}, | |
css: { | |
less: function (tag, css, opts, url) { | |
var ret | |
opts = extend({ | |
sync: true, | |
syncImport: true, | |
filename: url | |
}, opts) | |
_req('less').render(css, opts, function (err, result) { | |
// istanbul ignore next | |
if (err) { throw err } | |
ret = result.css | |
}) | |
return ret | |
} | |
}, | |
js: { | |
es6: function (js, opts) { | |
opts = extend({ | |
blacklist: ['useStrict', 'strict', 'react'], | |
sourceMaps: false, | |
comments: false | |
}, opts) | |
return _req('babel').transform(js, opts).code | |
}, | |
babel: function (js, opts, url) { | |
return _req('babel').transform(js, extend({ filename: url }, opts)).code | |
}, | |
coffee: function (js, opts) { | |
return _req('CoffeeScript').compile(js, extend({ bare: true }, opts)) | |
}, | |
livescript: function (js, opts) { | |
return _req('livescript').compile(js, extend({ bare: true, header: false }, opts)) | |
}, | |
typescript: function (js, opts) { | |
return _req('typescript')(js, opts) | |
}, | |
none: function (js) { | |
return js | |
} | |
} | |
} | |
_p.js.javascript = _p.js.none | |
_p.js.coffeescript = _p.js.coffee | |
return _p | |
})() | |
/** | |
* @module compiler | |
*/ | |
var S_LINESTR = /"[^"\n\\]*(?:\\[\S\s][^"\n\\]*)*"|'[^'\n\\]*(?:\\[\S\s][^'\n\\]*)*'/.source | |
var S_STRINGS = brackets.R_STRINGS.source | |
var HTML_ATTRS = / *([-\w:\xA0-\xFF]+) ?(?:= ?('[^']*'|"[^"]*"|\S+))?/g | |
var HTML_COMMS = RegExp(/<!--(?!>)[\S\s]*?-->/.source + '|' + S_LINESTR, 'g') | |
var HTML_TAGS = /<(-?[A-Za-z][-\w\xA0-\xFF]*)(?:\s+([^"'\/>]*(?:(?:"[^"]*"|'[^']*'|\/[^>])[^'"\/>]*)*)|\s*)(\/?)>/g | |
var HTML_PACK = />[ \t]+<(-?[A-Za-z]|\/[-A-Za-z])/g | |
var RIOT_ATTRS = ['style', 'src', 'd'] | |
var VOID_TAGS = /^(?:input|img|br|wbr|hr|area|base|col|embed|keygen|link|meta|param|source|track)$/ | |
var PRE_TAGS = /<pre(?:\s+(?:[^">]*|"[^"]*")*)?>([\S\s]+?)<\/pre\s*>/gi | |
var SPEC_TYPES = /^"(?:number|date(?:time)?|time|month|email|color)\b/i | |
var TRIM_TRAIL = /[ \t]+$/gm | |
var RE_HASEXPR = /\x01#\d/; | |
var RE_REPEXPR = /\x01#(\d+)/g; | |
var CH_IDEXPR = '\x01#'; | |
var CH_DQCODE = '\u2057'; | |
var DQ = '"'; | |
var SQ = "'" | |
function cleanSource (src) { | |
var | |
mm, | |
re = HTML_COMMS | |
if (~src.indexOf('\r')) { | |
src = src.replace(/\r\n?/g, '\n') | |
} | |
re.lastIndex = 0 | |
while ((mm = re.exec(src))) { | |
if (mm[0][0] === '<') { | |
src = RegExp.leftContext + RegExp.rightContext | |
re.lastIndex = mm[3] + 1 | |
} | |
} | |
return src | |
} | |
function parseAttribs (str, pcex) { | |
var | |
list = [], | |
match, | |
type, vexp | |
HTML_ATTRS.lastIndex = 0 | |
str = str.replace(/\s+/g, ' ') | |
while ((match = HTML_ATTRS.exec(str))) { | |
var | |
k = match[1].toLowerCase(), | |
v = match[2] | |
if (!v) { | |
list.push(k) | |
} else { | |
if (v[0] !== DQ) { | |
v = DQ + (v[0] === SQ ? v.slice(1, -1) : v) + DQ | |
} | |
if (k === 'type' && SPEC_TYPES.test(v)) { | |
type = v | |
} else { | |
if (RE_HASEXPR.test(v)) { | |
if (k === 'value') { vexp = 1 } | |
else if (~RIOT_ATTRS.indexOf(k)) { k = 'riot-' + k } | |
} | |
list.push(k + '=' + v) | |
} | |
} | |
} | |
if (type) { | |
if (vexp) { type = DQ + pcex._bp[0] + SQ + type.slice(1, -1) + SQ + pcex._bp[1] + DQ } | |
list.push('type=' + type) | |
} | |
return list.join(' ') | |
} | |
function splitHtml (html, opts, pcex) { | |
var _bp = pcex._bp | |
if (html && _bp[4].test(html)) { | |
var | |
jsfn = opts.expr && (opts.parser || opts.type) ? _compileJS : 0, | |
list = brackets.split(html, 0, _bp), | |
expr | |
for (var i = 1; i < list.length; i += 2) { | |
expr = list[i] | |
if (expr[0] === '^') { | |
expr = expr.slice(1) | |
} else if (jsfn) { | |
expr = jsfn(expr, opts).trim() | |
if (expr.slice(-1) === ';') { expr = expr.slice(0, -1) } | |
} | |
list[i] = CH_IDEXPR + (pcex.push(expr) - 1) + _bp[1] | |
} | |
html = list.join('') | |
} | |
return html | |
} | |
function restoreExpr (html, pcex) { | |
if (pcex.length) { | |
html = html.replace(RE_REPEXPR, function (_, d) { | |
return pcex._bp[0] + pcex[d].trim().replace(/[\r\n]+/g, ' ').replace(/"/g, CH_DQCODE) | |
}) | |
} | |
return html | |
} | |
function _compileHTML (html, opts, pcex) { | |
html = splitHtml(html, opts, pcex) | |
.replace(HTML_TAGS, function (_, name, attr, ends) { | |
name = name.toLowerCase() | |
ends = ends && !VOID_TAGS.test(name) ? '></' + name : '' | |
if (attr) { name += ' ' + parseAttribs(attr, pcex) } | |
return '<' + name + ends + '>' | |
}) | |
if (!opts.whitespace) { | |
var p = [] | |
if (/<pre[\s>]/.test(html)) { | |
html = html.replace(PRE_TAGS, function (q) { | |
p.push(q) | |
return '\u0002' | |
}) | |
} | |
html = html.trim().replace(/\s+/g, ' ') | |
if (p.length) { html = html.replace(/\u0002/g, function () { return p.shift() }) } | |
} | |
if (opts.compact) { html = html.replace(HTML_PACK, '><$1') } | |
return restoreExpr(html, pcex).replace(TRIM_TRAIL, '') | |
} | |
function compileHTML (html, opts, pcex) { | |
if (Array.isArray(opts)) { | |
pcex = opts | |
opts = {} | |
} else { | |
if (!pcex) { pcex = [] } | |
if (!opts) { opts = {} } | |
} | |
pcex._bp = brackets.array(opts.brackets) | |
return _compileHTML(cleanSource(html), opts, pcex) | |
} | |
var JS_ES6SIGN = /^[ \t]*([$_A-Za-z][$\w]*)\s*\([^()]*\)\s*{/m | |
var JS_ES6END = RegExp('[{}]|' + brackets.S_QBLOCKS, 'g') | |
var JS_COMMS = RegExp(brackets.R_MLCOMMS.source + '|//[^\r\n]*|' + brackets.S_QBLOCKS, 'g') | |
function riotjs (js) { | |
var | |
parts = [], | |
match, | |
toes5, | |
pos, | |
name, | |
RE = RegExp | |
if (~js.indexOf('/')) { js = rmComms(js, JS_COMMS) } | |
while ((match = js.match(JS_ES6SIGN))) { | |
parts.push(RE.leftContext) | |
js = RE.rightContext | |
pos = skipBody(js, JS_ES6END) | |
name = match[1] | |
toes5 = !/^(?:if|while|for|switch|catch|function)$/.test(name) | |
name = toes5 ? match[0].replace(name, 'this.' + name + ' = function') : match[0] | |
parts.push(name, js.slice(0, pos)) | |
js = js.slice(pos) | |
if (toes5 && !/^\s*.\s*bind\b/.test(js)) { parts.push('.bind(this)') } | |
} | |
return parts.length ? parts.join('') + js : js | |
function rmComms (s, r, m) { | |
r.lastIndex = 0 | |
while ((m = r.exec(s))) { | |
if (m[0][0] === '/' && !m[1] && !m[2]) { | |
s = RE.leftContext + ' ' + RE.rightContext | |
r.lastIndex = m[3] + 1 | |
} | |
} | |
return s | |
} | |
function skipBody (s, r) { | |
var m, i = 1 | |
r.lastIndex = 0 | |
while (i && (m = r.exec(s))) { | |
if (m[0] === '{') { ++i } | |
else if (m[0] === '}') { --i } | |
} | |
return i ? s.length : r.lastIndex | |
} | |
} | |
function _compileJS (js, opts, type, parserOpts, url) { | |
if (!/\S/.test(js)) { return '' } | |
if (!type) { type = opts.type } | |
var parser = opts.parser || (type ? parsers$1.js[type] : riotjs) | |
if (!parser) { | |
throw new Error('JS parser not found: "' + type + '"') | |
} | |
return parser(js, parserOpts, url).replace(/\r\n?/g, '\n').replace(TRIM_TRAIL, '') | |
} | |
function compileJS (js, opts, type, userOpts) { | |
if (typeof opts === 'string') { | |
userOpts = type | |
type = opts | |
opts = {} | |
} | |
if (type && typeof type === 'object') { | |
userOpts = type | |
type = '' | |
} | |
if (!userOpts) { userOpts = {} } | |
return _compileJS(js, opts || {}, type, userOpts.parserOptions, userOpts.url) | |
} | |
var CSS_SELECTOR = RegExp('([{}]|^)[ ;]*([^@ ;{}][^{}]*)(?={)|' + S_LINESTR, 'g') | |
function scopedCSS (tag, css) { | |
var scope = ':scope' | |
return css.replace(CSS_SELECTOR, function (m, p1, p2) { | |
if (!p2) { return m } | |
p2 = p2.replace(/[^,]+/g, function (sel) { | |
var s = sel.trim() | |
if (!s || s === 'from' || s === 'to' || s.slice(-1) === '%') { | |
return sel | |
} | |
if (s.indexOf(scope) < 0) { | |
s = tag + ' ' + s + ',[riot-tag="' + tag + '"] ' + s + | |
',[data-is="' + tag + '"] ' + s | |
} else { | |
s = s.replace(scope, tag) + ',' + | |
s.replace(scope, '[riot-tag="' + tag + '"]') + ',' + | |
s.replace(scope, '[data-is="' + tag + '"]') | |
} | |
return s | |
}) | |
return p1 ? p1 + ' ' + p2 : p2 | |
}) | |
} | |
function _compileCSS (css, tag, type, opts) { | |
var scoped = (opts || (opts = {})).scoped | |
if (type) { | |
if (type === 'scoped-css') { | |
scoped = true | |
} else if (parsers$1.css[type]) { | |
css = parsers$1.css[type](tag, css, opts.parserOpts || {}, opts.url) | |
} else if (type !== 'css') { | |
throw new Error('CSS parser not found: "' + type + '"') | |
} | |
} | |
css = css.replace(brackets.R_MLCOMMS, '').replace(/\s+/g, ' ').trim() | |
if (scoped) { | |
if (!tag) { | |
throw new Error('Can not parse scoped CSS without a tagName') | |
} | |
css = scopedCSS(tag, css) | |
} | |
return css | |
} | |
function compileCSS (css, type, opts) { | |
if (type && typeof type === 'object') { | |
opts = type | |
type = '' | |
} else if (!opts) { opts = {} } | |
return _compileCSS(css, opts.tagName, type, opts) | |
} | |
var TYPE_ATTR = /\stype\s*=\s*(?:(['"])(.+?)\1|(\S+))/i | |
var MISC_ATTR = '\\s*=\\s*(' + S_STRINGS + '|{[^}]+}|\\S+)' | |
var END_TAGS = /\/>\n|^<(?:\/?-?[A-Za-z][-\w\xA0-\xFF]*\s*|-?[A-Za-z][-\w\xA0-\xFF]*\s+[-\w:\xA0-\xFF][\S\s]*?)>\n/ | |
function _q (s, r) { | |
if (!s) { return "''" } | |
s = SQ + s.replace(/\\/g, '\\\\').replace(/'/g, "\\'") + SQ | |
return r && ~s.indexOf('\n') ? s.replace(/\n/g, '\\n') : s | |
} | |
function mktag (name, html, css, attr, js, opts) { | |
var | |
c = opts.debug ? ',\n ' : ', ', | |
s = '});' | |
if (js && js.slice(-1) !== '\n') { s = '\n' + s } | |
return 'riot.tag2(\'' + name + SQ + | |
c + _q(html, 1) + | |
c + _q(css) + | |
c + _q(attr) + ', function(opts) {\n' + js + s | |
} | |
function splitBlocks (str) { | |
if (/<[-\w]/.test(str)) { | |
var | |
m, | |
k = str.lastIndexOf('<'), | |
n = str.length | |
while (~k) { | |
m = str.slice(k, n).match(END_TAGS) | |
if (m) { | |
k += m.index + m[0].length | |
return [str.slice(0, k), str.slice(k)] | |
} | |
n = k | |
k = str.lastIndexOf('<', k - 1) | |
} | |
} | |
return ['', str] | |
} | |
function getType (attribs) { | |
if (attribs) { | |
var match = attribs.match(TYPE_ATTR) | |
match = match && (match[2] || match[3]) | |
if (match) { | |
return match.replace('text/', '') | |
} | |
} | |
return '' | |
} | |
function getAttrib (attribs, name) { | |
if (attribs) { | |
var match = attribs.match(RegExp('\\s' + name + MISC_ATTR, 'i')) | |
match = match && match[1] | |
if (match) { | |
return (/^['"]/).test(match) ? match.slice(1, -1) : match | |
} | |
} | |
return '' | |
} | |
function unescapeHTML (str) { | |
return str | |
.replace('&', /&/g) | |
.replace('<', /</g) | |
.replace('>', />/g) | |
.replace('"', /"/g) | |
.replace(''', /'/g) | |
} | |
function getParserOptions (attribs) { | |
var opts = unescapeHTML(getAttrib(attribs, 'options')) | |
return opts ? JSON.parse(opts) : null | |
} | |
function getCode (code, opts, attribs, base) { | |
var | |
type = getType(attribs), | |
src = getAttrib(attribs, 'src') | |
if (src) { return false } | |
return _compileJS(code, opts, type, getParserOptions(attribs), base) | |
} | |
function cssCode (code, opts, attribs, url, tag) { | |
var extraOpts = { | |
parserOpts: getParserOptions(attribs), | |
scoped: attribs && /\sscoped(\s|=|$)/i.test(attribs), | |
url: url | |
} | |
return _compileCSS(code, tag, getType(attribs) || opts.style, extraOpts) | |
} | |
function compileTemplate (html, url, lang, opts) { | |
var parser = parsers$1.html[lang] | |
if (!parser) { | |
throw new Error('Template parser not found: "' + lang + '"') | |
} | |
return parser(html, opts, url) | |
} | |
var CUST_TAG = RegExp(/^([ \t]*)<(-?[A-Za-z][-\w\xA0-\xFF]*)(?:\s+([^'"\/>]+(?:(?:@|\/[^>])[^'"\/>]*)*)|\s*)?(?:\/>|>[ \t]*\n?([\S\s]*)^\1<\/\2\s*>|>(.*)<\/\2\s*>)/ | |
.source.replace('@', S_STRINGS), 'gim'); | |
var SCRIPTS = /<script(\s+[^>]*)?>\n?([\S\s]*?)<\/script\s*>/gi; | |
var STYLES = /<style(\s+[^>]*)?>\n?([\S\s]*?)<\/style\s*>/gi | |
function compile$1 (src, opts, url) { | |
var | |
parts = [], | |
included | |
if (!opts) { opts = {} } | |
included = opts.exclude | |
? function (s) { return opts.exclude.indexOf(s) < 0 } : function () { return 1 } | |
if (!url) { url = '' } | |
var _bp = brackets.array(opts.brackets) | |
if (opts.template) { | |
src = compileTemplate(src, url, opts.template, opts.templateOptions) | |
} | |
src = cleanSource(src) | |
.replace(CUST_TAG, function (_, indent, tagName, attribs, body, body2) { | |
var | |
jscode = '', | |
styles = '', | |
html = '', | |
pcex = [] | |
pcex._bp = _bp | |
tagName = tagName.toLowerCase() | |
attribs = attribs && included('attribs') | |
? restoreExpr( | |
parseAttribs( | |
splitHtml(attribs, opts, pcex), | |
pcex), | |
pcex) : '' | |
if ((body || (body = body2)) && /\S/.test(body)) { | |
if (body2) { | |
if (included('html')) { html = _compileHTML(body2, opts, pcex) } | |
} else { | |
body = body.replace(RegExp('^' + indent, 'gm'), '') | |
body = body.replace(STYLES, function (_m, _attrs, _style) { | |
if (included('css')) { | |
styles += (styles ? ' ' : '') + cssCode(_style, opts, _attrs, url, tagName) | |
} | |
return '' | |
}) | |
body = body.replace(SCRIPTS, function (_m, _attrs, _script) { | |
if (included('js')) { | |
var code = getCode(_script, opts, _attrs, url) | |
if (code) { jscode += (jscode ? '\n' : '') + code } | |
} | |
return '' | |
}) | |
var blocks = splitBlocks(body.replace(TRIM_TRAIL, '')) | |
if (included('html')) { | |
html = _compileHTML(blocks[0], opts, pcex) | |
} | |
if (included('js')) { | |
body = _compileJS(blocks[1], opts, null, null, url) | |
if (body) { jscode += (jscode ? '\n' : '') + body } | |
} | |
} | |
} | |
jscode = /\S/.test(jscode) ? jscode.replace(/\n{3,}/g, '\n\n') : '' | |
if (opts.entities) { | |
parts.push({ | |
tagName: tagName, | |
html: html, | |
css: styles, | |
attribs: attribs, | |
js: jscode | |
}) | |
return '' | |
} | |
return mktag(tagName, html, styles, attribs, jscode, opts) | |
}) | |
if (opts.entities) { return parts } | |
return src | |
} | |
var version = 'v3.0.0-alpha.1' | |
var compiler = { | |
compile: compile$1, | |
compileHTML: compileHTML, | |
compileCSS: compileCSS, | |
compileJS: compileJS, | |
parsers: parsers$1, | |
version: version | |
} | |
var promise; | |
var ready // all the scripts were compiled? | |
// gets the source of an external tag with an async call | |
function GET (url, fn, opts) { | |
var req = new XMLHttpRequest() | |
req.onreadystatechange = function () { | |
if (req.readyState === 4 && | |
(req.status === 200 || !req.status && req.responseText.length)) { | |
fn(req.responseText, opts, url) | |
} | |
} | |
req.open('GET', url, true) | |
req.send('') | |
} | |
// evaluates a compiled tag within the global context | |
function globalEval (js, url) { | |
if (typeof js === T_STRING) { | |
var | |
node = mkEl('script'), | |
root = document.documentElement | |
// make the source available in the "(no domain)" tab | |
// of Chrome DevTools, with a .js extension | |
if (url) { js += '\n//# sourceURL=' + url + '.js' } | |
node.text = js | |
root.appendChild(node) | |
root.removeChild(node) | |
} | |
} | |
// compiles all the internal and external tags on the page | |
function compileScripts (fn, xopt) { | |
var | |
scripts = $$('script[type="riot/tag"]'), | |
scriptsAmount = scripts.length | |
function done() { | |
promise.trigger('ready') | |
ready = true | |
if (fn) { fn() } | |
} | |
function compileTag (src, opts, url) { | |
var code = compiler.compile(src, opts, url) | |
globalEval(code, url) | |
if (!--scriptsAmount) { done() } | |
} | |
if (!scriptsAmount) { done() } | |
else { | |
for (var i = 0; i < scripts.length; ++i) { | |
var | |
script = scripts[i], | |
opts = extend({template: getAttr(script, 'template')}, xopt), | |
url = getAttr(script, 'src') | |
url ? GET(url, compileTag, opts) : compileTag(script.innerHTML, opts) | |
} | |
} | |
} | |
var parsers = compiler.parsers | |
/* | |
Compilation for the browser | |
*/ | |
var compile = function (arg, fn, opts) { | |
if (typeof arg === T_STRING) { | |
// 2nd parameter is optional, but can be null | |
if (isObject(fn)) { | |
opts = fn | |
fn = false | |
} | |
// `riot.compile(tag [, callback | true][, options])` | |
if (/^\s*</m.test(arg)) { | |
var js = compiler.compile(arg, opts) | |
if (fn !== true) { globalEval(js) } | |
if (isFunction(fn)) { fn(js, arg, opts) } | |
return js | |
} | |
// `riot.compile(url [, callback][, options])` | |
GET(arg, function (str, opts, url) { | |
var js = compiler.compile(str, opts, url) | |
globalEval(js, url) | |
if (fn) { fn(js, str, opts) } | |
}, opts) | |
} else if (isArray(arg)) { | |
var i = arg.length | |
// `riot.compile([urlsList] [, callback][, options])` | |
arg.forEach(function(str) { | |
GET(str, function (str, opts, url) { | |
var js = compiler.compile(str, opts, url) | |
globalEval(js, url) | |
i -- | |
if (!i && fn) { fn(js, str, opts) } | |
}, opts) | |
}) | |
} else { | |
// `riot.compile([callback][, options])` | |
if (isFunction(arg)) { | |
opts = fn | |
fn = arg | |
} else { | |
opts = arg | |
fn = undefined | |
} | |
if (ready) { | |
return fn && fn() | |
} | |
if (promise) { | |
if (fn) { promise.on('ready', fn) } | |
} else { | |
promise = observable() | |
compileScripts(fn, opts) | |
} | |
} | |
} | |
function mount$$1(a, b, c) { | |
var ret | |
compile(function () { ret = mount$1(a, b, c) }) | |
return ret | |
} | |
var riot_compiler = extend({}, riot$1, { | |
mount: mount$$1, | |
compile: compile, | |
parsers: parsers | |
}) | |
return riot_compiler; | |
}))); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment