UNPKG

silk-gui

Version:

GUI for developers and Node OS

260 lines (235 loc) 6.1 kB
var _ = require('../util') var Cache = require('../cache') var templateCache = new Cache(1000) var idSelectorCache = new Cache(1000) var map = { _default : [0, '', ''], legend : [1, '<fieldset>', '</fieldset>'], tr : [2, '<table><tbody>', '</tbody></table>'], col : [ 2, '<table><tbody></tbody><colgroup>', '</colgroup></table>' ] } map.td = map.th = [ 3, '<table><tbody><tr>', '</tr></tbody></table>' ] map.option = map.optgroup = [ 1, '<select multiple="multiple">', '</select>' ] map.thead = map.tbody = map.colgroup = map.caption = map.tfoot = [1, '<table>', '</table>'] map.g = map.defs = map.symbol = map.use = map.image = map.text = map.circle = map.ellipse = map.line = map.path = map.polygon = map.polyline = map.rect = [ 1, '<svg ' + 'xmlns="http://www.w3.org/2000/svg" ' + 'xmlns:xlink="http://www.w3.org/1999/xlink" ' + 'xmlns:ev="http://www.w3.org/2001/xml-events"' + 'version="1.1">', '</svg>' ] var tagRE = /<([\w:]+)/ var entityRE = /&\w+;/ /** * Convert a string template to a DocumentFragment. * Determines correct wrapping by tag types. Wrapping * strategy found in jQuery & component/domify. * * @param {String} templateString * @return {DocumentFragment} */ function stringToFragment (templateString) { // try a cache hit first var hit = templateCache.get(templateString) if (hit) { return hit } var frag = document.createDocumentFragment() var tagMatch = templateString.match(tagRE) var entityMatch = entityRE.test(templateString) if (!tagMatch && !entityMatch) { // text only, return a single text node. frag.appendChild( document.createTextNode(templateString) ) } else { var tag = tagMatch && tagMatch[1] var wrap = map[tag] || map._default var depth = wrap[0] var prefix = wrap[1] var suffix = wrap[2] var node = document.createElement('div') node.innerHTML = prefix + templateString.trim() + suffix while (depth--) { node = node.lastChild } var child /* jshint boss:true */ while (child = node.firstChild) { frag.appendChild(child) } } templateCache.put(templateString, frag) return frag } /** * Convert a template node to a DocumentFragment. * * @param {Node} node * @return {DocumentFragment} */ function nodeToFragment (node) { var tag = node.tagName // if its a template tag and the browser supports it, // its content is already a document fragment. if ( tag === 'TEMPLATE' && node.content instanceof DocumentFragment ) { return node.content } // script template if (tag === 'SCRIPT') { return stringToFragment(node.textContent) } // normal node, clone it to avoid mutating the original var clone = exports.clone(node) var frag = document.createDocumentFragment() var child /* jshint boss:true */ while (child = clone.firstChild) { frag.appendChild(child) } return frag } // Test for the presence of the Safari template cloning bug // https://bugs.webkit.org/show_bug.cgi?id=137755 var hasBrokenTemplate = _.inBrowser ? (function () { var a = document.createElement('div') a.innerHTML = '<template>1</template>' return !a.cloneNode(true).firstChild.innerHTML })() : false // Test for IE10/11 textarea placeholder clone bug var hasTextareaCloneBug = _.inBrowser ? (function () { var t = document.createElement('textarea') t.placeholder = 't' return t.cloneNode(true).value === 't' })() : false /** * 1. Deal with Safari cloning nested <template> bug by * manually cloning all template instances. * 2. Deal with IE10/11 textarea placeholder bug by setting * the correct value after cloning. * * @param {Element|DocumentFragment} node * @return {Element|DocumentFragment} */ exports.clone = function (node) { var res = node.cloneNode(true) var i, original, cloned /* istanbul ignore if */ if (hasBrokenTemplate) { original = node.querySelectorAll('template') if (original.length) { cloned = res.querySelectorAll('template') i = cloned.length while (i--) { cloned[i].parentNode.replaceChild( original[i].cloneNode(true), cloned[i] ) } } } /* istanbul ignore if */ if (hasTextareaCloneBug) { if (node.tagName === 'TEXTAREA') { res.value = node.value } else { original = node.querySelectorAll('textarea') if (original.length) { cloned = res.querySelectorAll('textarea') i = cloned.length while (i--) { cloned[i].value = original[i].value } } } } return res } /** * Process the template option and normalizes it into a * a DocumentFragment that can be used as a partial or a * instance template. * * @param {*} template * Possible values include: * - DocumentFragment object * - Node object of type Template * - id selector: '#some-template-id' * - template string: '<div><span>{{msg}}</span></div>' * @param {Boolean} clone * @param {Boolean} noSelector * @return {DocumentFragment|undefined} */ exports.parse = function (template, clone, noSelector) { var node, frag // if the template is already a document fragment, // do nothing if (template instanceof DocumentFragment) { return clone ? template.cloneNode(true) : template } if (typeof template === 'string') { // id selector if (!noSelector && template.charAt(0) === '#') { // id selector can be cached too frag = idSelectorCache.get(template) if (!frag) { node = document.getElementById(template.slice(1)) if (node) { frag = nodeToFragment(node) // save selector to cache idSelectorCache.put(template, frag) } } } else { // normal string template frag = stringToFragment(template) } } else if (template.nodeType) { // a direct node frag = nodeToFragment(template) } return frag && clone ? exports.clone(frag) : frag }