Skip to content

Instantly share code, notes, and snippets.

@senica
Last active September 28, 2016 17:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save senica/b1266ee774e4357e39825acd367034cb to your computer and use it in GitHub Desktop.
Save senica/b1266ee774e4357e39825acd367034cb to your computer and use it in GitHub Desktop.
Example to prevent unmount of tags using e.preventDefault
/* 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) {
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)
var e = { unmount: unmount }
this.trigger('before-unmount', e)
if (e.preventDefault) { return }
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('&amp;', /&/g)
.replace('&lt;', /</g)
.replace('&gt;', />/g)
.replace('&quot;', /"/g)
.replace('&#039;', /'/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