├── test ├── testPut.html ├── example-server.js ├── node-put.js └── put.js ├── package.js ├── package.json ├── node-html.js ├── put.js ├── LICENSE └── README.md /test/testPut.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Test put-selector/put 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /test/example-server.js: -------------------------------------------------------------------------------- 1 | var http = require('http'); 2 | var put = require('put-selector'); 3 | http.createServer(function (req, res) { 4 | res.writeHead(200, {'Content-Type': 'text/html'}); 5 | var page = put('html').sendTo(res); // create an HTML page, and pipe to the response 6 | put(page, 'head script[src=app.js]'); // each are sent immediately 7 | put(page, 'body div.content', 'Hello, World'); 8 | page.end(); // close all the tags, and end the stream 9 | }).listen(81); -------------------------------------------------------------------------------- /package.js: -------------------------------------------------------------------------------- 1 | var miniExcludes = { 2 | "put-selector/README.md": 1, 3 | "put-selector/package": 1 4 | }, 5 | amdExcludes = { 6 | }, 7 | copyOnly = { 8 | "put-selector/node-html": 1 9 | }, 10 | isJsRe = /\.js$/, 11 | isTestRe = /\/test\//; 12 | 13 | var profile = { 14 | resourceTags: { 15 | test: function(filename, mid){ 16 | return isTestRe.test(filename); 17 | }, 18 | 19 | miniExclude: function(filename, mid){ 20 | return isTestRe.test(filename) || mid in miniExcludes; 21 | }, 22 | 23 | amd: function(filename, mid){ 24 | return isJsRe.test(filename) && !(mid in amdExcludes); 25 | }, 26 | 27 | copyOnly: function(filename, mid){ 28 | return mid in copyOnly; 29 | } 30 | } 31 | }; 32 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "put-selector", 3 | "author": "Kris Zyp", 4 | "version": "0.3.6", 5 | "description": "A high-performance, lightweight function for creating and manipulating DOM elements with succinct, elegant, familiar CSS selector-based syntax", 6 | "licenses": [ 7 | { 8 | "type": "AFLv2.1", 9 | "url": "http://trac.dojotoolkit.org/browser/dojo/trunk/LICENSE#L43" 10 | }, 11 | { 12 | "type": "BSD", 13 | "url": "http://trac.dojotoolkit.org/browser/dojo/trunk/LICENSE#L13" 14 | } 15 | ], 16 | "maintainers": [ 17 | { 18 | "name": "Kris Zyp", 19 | "email": "kriszyp@gmail.com" 20 | } 21 | ], 22 | "repository": { 23 | "type":"git", 24 | "url":"http://github.com/kriszyp/put-selector" 25 | }, 26 | "directories": { 27 | "lib": "." 28 | }, 29 | "main": "./put", 30 | "icon": "http://packages.dojofoundation.org/images/xstyle.jpg", 31 | "dojoBuild": "package.js" 32 | } 33 | -------------------------------------------------------------------------------- /test/node-put.js: -------------------------------------------------------------------------------- 1 | var assert = require("assert"), 2 | put = require("../put"); 3 | exports.testSimple = function() { 4 | assert.equal(put('div span.test<').toString(), '\n
\n \n
'); 5 | assert.equal(put('div', ['header', 'section']).toString(), '\n
\n
\n
\n
'); 6 | }; 7 | exports.testPage = function() { 8 | put.indentation = false; 9 | var page = put('html'); 10 | put(page, 'head script[src=test.js]+link[href=test.css]+link[href=test2.css]'); 11 | var content = put(page, 'body div.header $\ 12 | +div.content', 'Hello World'); 13 | put(content, 'div.left', 'Left'); 14 | put.addNamespace('foo', 'http://foo.com/foo'); 15 | put(content, 'foo|bar'); 16 | put(content, 'div.right', {innerHTML: 'Right text'}); 17 | assert.equal(page.toString(), '\n
Hello World
Left
Right text
'); 18 | }; 19 | exports.testStream = function() { 20 | //put.indentation = ' '; 21 | var output = ''; 22 | var stream = { 23 | write: function(str){ 24 | output += str; 25 | }, 26 | end: function(str){ 27 | output += str; 28 | } 29 | } 30 | var page = put('html').sendTo(stream); 31 | put(page, 'head script[src=test.js]+link[href=test.css]+link[href=test2.css]'); 32 | var content = put(page, 'body div.header $\ 33 | +div.content', 'Hello World'); 34 | content.put('div.left', 'Left'); 35 | content.put('div.right', {innerHTML: 'Right text'}); 36 | page.end(); 37 | assert.equal(output, '\n
Hello World
Left
Right text
\n'); 38 | }; 39 | if (require.main === module) 40 | require("patr/runner").run(exports); -------------------------------------------------------------------------------- /test/put.js: -------------------------------------------------------------------------------- 1 | var div = put("div"); 2 | console.assert(div.tagName.toLowerCase() == "div"); 3 | console.assert(put(div) === div); 4 | 5 | var body = document.body; 6 | put(body, "h1 $", "Running put() tests"); 7 | 8 | var parentDiv = div; 9 | 10 | var span1 = put(parentDiv, "span.class-name-1.class-name-2[name=span1]"); 11 | console.assert(span1.className == "class-name-1 class-name-2"); 12 | console.assert(span1.getAttribute("name") == "span1"); 13 | console.assert(span1.parentNode == div); 14 | put(span1, "!class-name-1.class-name-3[!name]"); 15 | console.assert(span1.className == "class-name-2 class-name-3"); 16 | put(span1, "!.class-name-3"); 17 | console.assert(span1.className == "class-name-2"); 18 | console.assert(span1.getAttribute("name") == null); 19 | put(span1, "[name=span1]"); // readd the attribute 20 | 21 | var defaultTag = put(parentDiv, " .class"); 22 | console.assert(defaultTag.tagName.toLowerCase() == "div"); 23 | var span2, span3 = put(span1, "+span[name=span2] + span[name=span3]"); 24 | console.assert(span3.getAttribute("name") == "span3"); 25 | console.assert((span2 = span3.previousSibling).getAttribute("name") == "span2"); 26 | console.assert(span3.previousSibling.previousSibling.getAttribute("name") == "span1"); 27 | var span4 = put(span2, ">", span3, "span.$[name=$]", "span3-child", "span4"); 28 | console.assert(span3.parentNode == span2); 29 | console.assert(span4.parentNode == span3); 30 | console.assert(span4.className == "span3-child"); 31 | console.assert(span4.getAttribute('name') == "span4"); 32 | put(span2, "+", span3, "+", span4); 33 | console.assert(span2.nextSibling == span3); 34 | console.assert(span3.nextSibling == span4); 35 | 36 | var div = put('div[title=$]', 4) 37 | console.assert(div.title, '4') 38 | 39 | var parentDiv = put("div.parent span.first $ + span.second $<", "inside first", "inside second"); 40 | console.assert(parentDiv.firstChild.innerHTML, "inside first"); 41 | console.assert(parentDiv.lastChild.innerHTML, "inside second"); 42 | 43 | put(span3, "!"); // destroy span3 44 | console.assert(span2.nextSibling != span3); // make sure span3 is gone 45 | 46 | var span0 = put(span1, "-span[name=span0]"); 47 | console.assert(span0.getAttribute("name") == "span0"); 48 | 49 | var spanMinusTwo = put(span0, "-span -span"); 50 | console.assert(spanMinusTwo.nextSibling.nextSibling == span0); 51 | 52 | 53 | var spanWithId = put(parentDiv, "span#with-id"); 54 | console.assert(spanWithId.id == "with-id"); 55 | 56 | var table = put(parentDiv, "table.class-name#id tr.class-name td[colSpan=2]<td,tr>td+td"); 65 | console.assert(table.childNodes.length == 4); 66 | console.assert(table.lastChild.childNodes.length == 2); 67 | 68 | var checkbox = put(div, "input[type=checkbox][checked]"); 69 | console.assert(checkbox.type == "checkbox"); 70 | console.assert(checkbox.hasAttribute("checked")); 71 | 72 | div = put("div"); 73 | var arrayFrag = put(div, ["span.c1", "span.c2", "span.c3"]); 74 | console.assert(arrayFrag.tagName.toLowerCase() == "div"); 75 | console.assert(div.firstChild.className == "c1"); 76 | console.assert(div.lastChild.className == "c3"); 77 | 78 | put(div, "#encode%3A%20d"); 79 | console.assert(div.id == "encode%3A%20d"); 80 | 81 | put(div, "[title='with single \\' quote']"); 82 | console.assert(div.title, "with single ' quote"); 83 | 84 | put(div, "[title='[brackets]']"); 85 | console.assert(div.title, "[brackets]"); 86 | 87 | var styled = put("div.someClass[style=color:green;margin-left:10px]"); 88 | console.assert(styled.style.marginLeft.slice(0,2) == "10"); 89 | 90 | 91 | put.addNamespace("put", "http://github.com/kriszyp/dgrid"); 92 | var namespaced = put("put|foo[bar=test1][put|bar=test2]"); 93 | console.assert((namespaced.namespaceURI || namespaced.tagUrn) == "http://github.com/kriszyp/dgrid"); 94 | console.assert(namespaced.tagName == "foo"); 95 | console.assert(namespaced.getAttribute("bar") == "test1"); 96 | if(document.createElementNS){ 97 | console.assert(namespaced.getAttributeNS("http://github.com/kriszyp/dgrid","bar") == "test2"); 98 | } 99 | 100 | put.addNamespace("svg", "http://www.w3.org/2000/svg"); 101 | var svg = put(document.body, "svg|svg#svg-test"); 102 | put(svg, "!"); 103 | console.assert(document.getElementById("svg-test") == null); 104 | 105 | var unicode = put("div.unicode-你好"); 106 | console.assert(unicode.className === "unicode-你好"); 107 | 108 | put(body, "div", {innerHTML: "finished tests, check console for errors"}); 109 | 110 | var unicodeId = put(body, "div#ÅÄÖ"); 111 | console.assert(unicodeId.id === "ÅÄÖ"); 112 | -------------------------------------------------------------------------------- /node-html.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var put; 3 | function Element(tag){ 4 | this.tag = tag; 5 | } 6 | // create set of elements with an empty content model, so we now to skip their closing tag 7 | var emptyElements = {}; 8 | ["base", "link", "meta", "hr", "br", "wbr", "img", "embed", "param", "source", "track", "area", "col", "input", "keygen", "command"].forEach(function(tag){ 9 | emptyElements[tag] = true; 10 | }); 11 | var prototype = Element.prototype; 12 | var currentIndentation = ''; 13 | prototype.nodeType = 1; 14 | prototype.put = function(){ 15 | var args = [this]; 16 | args.push.apply(args, arguments); 17 | return put.apply(null, args); 18 | } 19 | prototype.toString = function(noClose){ 20 | var tag = this.tag; 21 | var emptyElement = emptyElements[tag]; 22 | if(put.indentation && !noClose){ 23 | // using pretty printing with indentation 24 | var lastIndentation = currentIndentation; 25 | currentIndentation += put.indentation; 26 | var html = (tag == 'html' ? '\n' + 29 | (this.children ? this.children.join('') : '') + 30 | (!this.mixed && !emptyElement && this.children ? '\n' +lastIndentation : '') + 31 | (emptyElement ? '' : ('')); 32 | 33 | currentIndentation = lastIndentation; 34 | return html; 35 | } 36 | return (this.tag == 'html' ? '\n' + 39 | (this.children ? this.children.join('') : '') + 40 | ((noClose || emptyElement) ? '' : ('')); 41 | }; 42 | prototype.sendTo = function(stream){ 43 | if(typeof stream == 'function'){ // write function itself 44 | stream = {write: stream, end: stream}; 45 | } 46 | var active = this; 47 | var streamIndentation = ''; 48 | function pipe(element){ 49 | // TODO: Perhaps consider buffering if it is any faster and having a non-indentation version that is faster 50 | var closing = returnTo(this); 51 | if(closing){ 52 | stream.write(closing); 53 | } 54 | var tag = element.tag; 55 | if(element.tag){ 56 | if(put.indentation){ 57 | stream.write('\n' + streamIndentation + element.toString(true)); 58 | streamIndentation += put.indentation; 59 | }else{ 60 | stream.write(element.toString(true)); 61 | } 62 | this.children = true; 63 | active = element; 64 | element.pipe = pipe; 65 | }else{ 66 | stream.write(element.toString()); 67 | } 68 | } 69 | function returnTo(element){ 70 | var output = ''; 71 | while(active != element){ 72 | if(!active){ 73 | throw new Error("Can not add to an element that has already been streamed"); 74 | } 75 | var tag = active.tag; 76 | var emptyElement = emptyElements[tag]; 77 | if(put.indentation){ 78 | streamIndentation = streamIndentation.slice(put.indentation.length); 79 | if(!emptyElement){ 80 | output += ((active.mixed || !active.children) ? '' : '\n' + streamIndentation) + ''; 81 | } 82 | }else if(!emptyElement){ 83 | output += ''; 84 | } 85 | active = active.parentNode; 86 | } 87 | return output; 88 | } 89 | pipe.call(this, this); 90 | // add on end() function to close all the tags and close the stream 91 | this.end = function(leaveStreamOpen){ 92 | stream[leaveStreamOpen ? 'write' : 'end'](returnTo(this) + '\n'); 93 | } 94 | return this; 95 | }; 96 | prototype.children = false; 97 | prototype.attributes = false; 98 | prototype.insertBefore = function(child, reference){ 99 | child.parentNode = this; 100 | if(this.pipe){ 101 | return this.pipe(child); 102 | //return this.s(child); 103 | } 104 | var children = this.children; 105 | if(!children){ 106 | children = this.children = []; 107 | } 108 | if(reference){ 109 | for(var i = 0, l = children.length; i < l; i++){ 110 | if(reference == children[i]){ 111 | child.nextSibling = reference; 112 | if(i > 0){ 113 | children[i-1].nextSibling = child; 114 | } 115 | return children.splice(i, 0, child); 116 | } 117 | } 118 | } 119 | if(children.length > 0){ 120 | children[children.length-1].nextSibling = child; 121 | } 122 | children.push(child); 123 | }; 124 | prototype.appendChild = function(child){ 125 | if(typeof child == "string"){ 126 | this.mixed = true; 127 | } 128 | if(this.pipe){ 129 | return this.pipe(child); 130 | } 131 | var children = this.children; 132 | if(!children){ 133 | children = this.children = []; 134 | } 135 | children.push(child); 136 | }; 137 | prototype.setAttribute = function(name, value, escape){ 138 | var attributes = this.attributes; 139 | if(!attributes){ 140 | attributes = this.attributes = []; 141 | } 142 | attributes.push(' ' + name + '="' + value + '"'); 143 | }; 144 | prototype.removeAttribute = function(name, value){ 145 | var attributes = this.attributes; 146 | if(!attributes){ 147 | return; 148 | } 149 | var match = ' ' + name + '=', matchLength = match.length; 150 | for(var i = 0, l = attributes.length; i < l; i++){ 151 | if(attributes[i].slice(0, matchLength) == match){ 152 | return attributes.splice(i, 1); 153 | } 154 | } 155 | }; 156 | Object.defineProperties(prototype, { 157 | innerHTML: { 158 | get: function(){ 159 | return this.children.join(''); 160 | }, 161 | set: function(value){ 162 | this.mixed = true; 163 | if(this.pipe){ 164 | return this.pipe(value); 165 | } 166 | this.children = [value]; 167 | } 168 | } 169 | }); 170 | function DocumentFragment(){ 171 | } 172 | DocumentFragment.prototype = new Element(); 173 | DocumentFragment.prototype.toString = function(){ 174 | return this.children ? this.children.join('') : ''; 175 | }; 176 | 177 | var lessThanRegex = / ]/; // if it has any of these combinators, it is probably going to be faster with a document fragment 3 | localDefine([], forDocument = function(doc, newFragmentFasterHeuristic){ 4 | "use strict"; 5 | // module: 6 | // put-selector/put 7 | // summary: 8 | // This module defines a fast lightweight function for updating and creating new elements 9 | // terse, CSS selector-based syntax. The single function from this module creates 10 | // new DOM elements and updates existing elements. See README.md for more information. 11 | // examples: 12 | // To create a simple div with a class name of "foo": 13 | // | put("div.foo"); 14 | fragmentFasterHeuristic = newFragmentFasterHeuristic || fragmentFasterHeuristic; 15 | var selectorParse = /(?:\s*([-+ ,<>]))?\s*(\.|!\.?|#)?([-\w\u00A0-\uFFFF%$|]+)?(?:\[([^\]=]+)=?('(?:\\.|[^'])*'|"(?:\\.|[^"])*"|[^\]]*)\])?/g, 16 | undefined, namespaceIndex, namespaces = false, 17 | doc = doc || document, 18 | ieCreateElement = typeof doc.createElement == "object"; // telltale sign of the old IE behavior with createElement that does not support later addition of name 19 | function insertTextNode(element, text){ 20 | element.appendChild(doc.createTextNode(text)); 21 | } 22 | function put(topReferenceElement){ 23 | var fragment, lastSelectorArg, nextSibling, referenceElement, current, 24 | args = arguments, 25 | returnValue = args[0]; // use the first argument as the default return value in case only an element is passed in 26 | function insertLastElement(){ 27 | // we perform insertBefore actions after the element is fully created to work properly with 28 | // tags in older versions of IE that require type attributes 29 | // to be set before it is attached to a parent. 30 | // We also handle top level as a document fragment actions in a complex creation 31 | // are done on a detached DOM which is much faster 32 | // Also if there is a parse error, we generally error out before doing any DOM operations (more atomic) 33 | if(current && referenceElement && current != referenceElement){ 34 | (referenceElement == topReferenceElement && 35 | // top level, may use fragment for faster access 36 | (fragment || 37 | // fragment doesn't exist yet, check to see if we really want to create it 38 | (fragment = fragmentFasterHeuristic.test(argument) && doc.createDocumentFragment())) 39 | // any of the above fails just use the referenceElement 40 | ? fragment : referenceElement). 41 | insertBefore(current, nextSibling || null); // do the actual insertion 42 | } 43 | } 44 | for(var i = 0; i < args.length; i++){ 45 | var argument = args[i]; 46 | if(typeof argument == "object"){ 47 | lastSelectorArg = false; 48 | if(argument instanceof Array){ 49 | // an array 50 | current = doc.createDocumentFragment(); 51 | for(var key = 0; key < argument.length; key++){ 52 | current.appendChild(put(argument[key])); 53 | } 54 | argument = current; 55 | } 56 | if(argument.nodeType){ 57 | current = argument; 58 | insertLastElement(); 59 | referenceElement = argument; 60 | nextSibling = 0; 61 | }else{ 62 | // an object hash 63 | for(var key in argument){ 64 | current[key] = argument[key]; 65 | } 66 | } 67 | }else if(lastSelectorArg){ 68 | // a text node should be created 69 | // take a scalar value, use createTextNode so it is properly escaped 70 | // createTextNode is generally several times faster than doing an escaped innerHTML insertion: http://jsperf.com/createtextnode-vs-innerhtml/2 71 | lastSelectorArg = false; 72 | insertTextNode(current, argument); 73 | }else{ 74 | if(i < 1){ 75 | // if we are starting with a selector, there is no top element 76 | topReferenceElement = null; 77 | } 78 | lastSelectorArg = true; 79 | var leftoverCharacters = argument.replace(selectorParse, function(t, combinator, prefix, value, attrName, attrValue){ 80 | if(combinator){ 81 | // insert the last current object 82 | insertLastElement(); 83 | if(combinator == '-' || combinator == '+'){ 84 | // + or - combinator, 85 | // TODO: add support for >- as a means of indicating before the first child? 86 | referenceElement = (nextSibling = (current || referenceElement)).parentNode; 87 | current = null; 88 | if(combinator == "+"){ 89 | nextSibling = nextSibling.nextSibling; 90 | }// else a - operator, again not in CSS, but obvious in it's meaning (create next element before the current/referenceElement) 91 | }else{ 92 | if(combinator == "<"){ 93 | // parent combinator (not really in CSS, but theorized, and obvious in it's meaning) 94 | referenceElement = current = (current || referenceElement).parentNode; 95 | }else{ 96 | if(combinator == ","){ 97 | // comma combinator, start a new selector 98 | referenceElement = topReferenceElement; 99 | }else if(current){ 100 | // else descendent or child selector (doesn't matter, treated the same), 101 | referenceElement = current; 102 | } 103 | current = null; 104 | } 105 | nextSibling = 0; 106 | } 107 | if(current){ 108 | referenceElement = current; 109 | } 110 | } 111 | var tag = !prefix && value; 112 | if(tag || (!current && (prefix || attrName))){ 113 | if(tag == "$"){ 114 | // this is a variable to be replaced with a text node 115 | insertTextNode(referenceElement, args[++i]); 116 | }else{ 117 | // Need to create an element 118 | tag = tag || put.defaultTag; 119 | var ieInputName = ieCreateElement && args[i +1] && args[i +1].name; 120 | if(ieInputName){ 121 | // in IE, we have to use the crazy non-standard createElement to create input's that have a name 122 | tag = '<' + tag + ' name="' + ieInputName + '">'; 123 | } 124 | // we swtich between creation methods based on namespace usage 125 | current = namespaces && ~(namespaceIndex = tag.indexOf('|')) ? 126 | doc.createElementNS(namespaces[tag.slice(0, namespaceIndex)], tag.slice(namespaceIndex + 1)) : 127 | doc.createElement(tag); 128 | } 129 | } 130 | if(prefix){ 131 | if(value == "$"){ 132 | value = args[++i]; 133 | } 134 | if(prefix == "#"){ 135 | // #id was specified 136 | current.id = value; 137 | }else{ 138 | // we are in the className addition and removal branch 139 | var currentClassName = current.className; 140 | // remove the className (needed for addition or removal) 141 | // see http://jsperf.com/remove-class-name-algorithm/2 for some tests on this 142 | var removed = currentClassName && (" " + currentClassName + " ").replace(" " + value + " ", " "); 143 | if(prefix == "."){ 144 | // addition, add the className 145 | current.className = currentClassName ? (removed + value).substring(1) : value; 146 | }else{ 147 | // else a '!' class removal 148 | if(argument == "!"){ 149 | var parentNode; 150 | // special signal to delete this element 151 | if(ieCreateElement){ 152 | // use the ol' innerHTML trick to get IE to do some cleanup 153 | put("div", current, '<').innerHTML = ""; 154 | }else if(parentNode = current.parentNode){ // intentional assigment 155 | // use a faster, and more correct (for namespaced elements) removal (http://jsperf.com/removechild-innerhtml) 156 | parentNode.removeChild(current); 157 | } 158 | }else{ 159 | // we already have removed the class, just need to trim 160 | removed = removed.substring(1, removed.length - 1); 161 | // only assign if it changed, this can save a lot of time 162 | if(removed != currentClassName){ 163 | current.className = removed; 164 | } 165 | } 166 | } 167 | // CSS class removal 168 | } 169 | } 170 | if(attrName){ 171 | if(attrValue && (attrValue.charAt(0) === '"' || attrValue.charAt(0) === "'")) { 172 | // quoted string 173 | attrValue = attrValue.slice(1, -1).replace(/\\/g, '') 174 | } 175 | if(attrValue == "$"){ 176 | attrValue = args[++i]; 177 | } 178 | // [name=value] 179 | if(attrName == "style"){ 180 | // handle the special case of setAttribute not working in old IE 181 | current.style.cssText = attrValue; 182 | }else{ 183 | var method = attrName.charAt(0) == "!" ? (attrName = attrName.substring(1)) && 'removeAttribute' : 'setAttribute'; 184 | // determine if we need to use a namespace 185 | namespaces && ~(namespaceIndex = attrName.indexOf('|')) ? 186 | current[method + "NS"](namespaces[attrName.slice(0, namespaceIndex)], attrName.slice(namespaceIndex + 1), attrValue) : 187 | current[method](attrName, attrValue); 188 | } 189 | } 190 | return ''; 191 | }); 192 | if(leftoverCharacters){ 193 | throw new SyntaxError("Unexpected char " + leftoverCharacters + " in " + argument); 194 | } 195 | insertLastElement(); 196 | referenceElement = returnValue = current || referenceElement; 197 | } 198 | } 199 | if(topReferenceElement && fragment){ 200 | // we now insert the top level elements for the fragment if it exists 201 | topReferenceElement.appendChild(fragment); 202 | } 203 | return returnValue; 204 | } 205 | put.addNamespace = function(name, uri){ 206 | if(doc.createElementNS){ 207 | (namespaces || (namespaces = {}))[name] = uri; 208 | }else{ 209 | // for old IE 210 | doc.namespaces.add(name, uri); 211 | } 212 | }; 213 | put.defaultTag = "div"; 214 | put.forDocument = forDocument; 215 | return put; 216 | }); 217 | })(function(id, deps, factory){ 218 | factory = factory || deps; 219 | if(typeof define === "function"){ 220 | // AMD loader 221 | define([], function(){ 222 | return factory(); 223 | }); 224 | }else if(typeof window == "undefined"){ 225 | // server side JavaScript, probably (hopefully) NodeJS 226 | require("./node-html")(module, factory); 227 | }else{ 228 | // plain script in a browser 229 | put = factory(); 230 | } 231 | }); 232 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | put-selector is available under *either* the terms of the modified BSD license *or* the 2 | Academic Free License version 2.1. As a recipient of put-selector, you may choose which 3 | license to receive this code under. 4 | 5 | The text of the AFL and BSD licenses is reproduced below. 6 | 7 | ------------------------------------------------------------------------------- 8 | The "New" BSD License: 9 | ********************** 10 | 11 | Copyright (c) 2010-2011, The Dojo Foundation 12 | All rights reserved. 13 | 14 | Redistribution and use in source and binary forms, with or without 15 | modification, are permitted provided that the following conditions are met: 16 | 17 | * Redistributions of source code must retain the above copyright notice, this 18 | list of conditions and the following disclaimer. 19 | * Redistributions in binary form must reproduce the above copyright notice, 20 | this list of conditions and the following disclaimer in the documentation 21 | and/or other materials provided with the distribution. 22 | * Neither the name of the Dojo Foundation nor the names of its contributors 23 | may be used to endorse or promote products derived from this software 24 | without specific prior written permission. 25 | 26 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 27 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 28 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 29 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 30 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 31 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 32 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 33 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 34 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 35 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 36 | 37 | ------------------------------------------------------------------------------- 38 | The Academic Free License, v. 2.1: 39 | ********************************** 40 | 41 | This Academic Free License (the "License") applies to any original work of 42 | authorship (the "Original Work") whose owner (the "Licensor") has placed the 43 | following notice immediately following the copyright notice for the Original 44 | Work: 45 | 46 | Licensed under the Academic Free License version 2.1 47 | 48 | 1) Grant of Copyright License. Licensor hereby grants You a world-wide, 49 | royalty-free, non-exclusive, perpetual, sublicenseable license to do the 50 | following: 51 | 52 | a) to reproduce the Original Work in copies; 53 | 54 | b) to prepare derivative works ("Derivative Works") based upon the Original 55 | Work; 56 | 57 | c) to distribute copies of the Original Work and Derivative Works to the 58 | public; 59 | 60 | d) to perform the Original Work publicly; and 61 | 62 | e) to display the Original Work publicly. 63 | 64 | 2) Grant of Patent License. Licensor hereby grants You a world-wide, 65 | royalty-free, non-exclusive, perpetual, sublicenseable license, under patent 66 | claims owned or controlled by the Licensor that are embodied in the Original 67 | Work as furnished by the Licensor, to make, use, sell and offer for sale the 68 | Original Work and Derivative Works. 69 | 70 | 3) Grant of Source Code License. The term "Source Code" means the preferred 71 | form of the Original Work for making modifications to it and all available 72 | documentation describing how to modify the Original Work. Licensor hereby 73 | agrees to provide a machine-readable copy of the Source Code of the Original 74 | Work along with each copy of the Original Work that Licensor distributes. 75 | Licensor reserves the right to satisfy this obligation by placing a 76 | machine-readable copy of the Source Code in an information repository 77 | reasonably calculated to permit inexpensive and convenient access by You for as 78 | long as Licensor continues to distribute the Original Work, and by publishing 79 | the address of that information repository in a notice immediately following 80 | the copyright notice that applies to the Original Work. 81 | 82 | 4) Exclusions From License Grant. Neither the names of Licensor, nor the names 83 | of any contributors to the Original Work, nor any of their trademarks or 84 | service marks, may be used to endorse or promote products derived from this 85 | Original Work without express prior written permission of the Licensor. Nothing 86 | in this License shall be deemed to grant any rights to trademarks, copyrights, 87 | patents, trade secrets or any other intellectual property of Licensor except as 88 | expressly stated herein. No patent license is granted to make, use, sell or 89 | offer to sell embodiments of any patent claims other than the licensed claims 90 | defined in Section 2. No right is granted to the trademarks of Licensor even if 91 | such marks are included in the Original Work. Nothing in this License shall be 92 | interpreted to prohibit Licensor from licensing under different terms from this 93 | License any Original Work that Licensor otherwise would have a right to 94 | license. 95 | 96 | 5) This section intentionally omitted. 97 | 98 | 6) Attribution Rights. You must retain, in the Source Code of any Derivative 99 | Works that You create, all copyright, patent or trademark notices from the 100 | Source Code of the Original Work, as well as any notices of licensing and any 101 | descriptive text identified therein as an "Attribution Notice." You must cause 102 | the Source Code for any Derivative Works that You create to carry a prominent 103 | Attribution Notice reasonably calculated to inform recipients that You have 104 | modified the Original Work. 105 | 106 | 7) Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that 107 | the copyright in and to the Original Work and the patent rights granted herein 108 | by Licensor are owned by the Licensor or are sublicensed to You under the terms 109 | of this License with the permission of the contributor(s) of those copyrights 110 | and patent rights. Except as expressly stated in the immediately proceeding 111 | sentence, the Original Work is provided under this License on an "AS IS" BASIS 112 | and WITHOUT WARRANTY, either express or implied, including, without limitation, 113 | the warranties of NON-INFRINGEMENT, MERCHANTABILITY or FITNESS FOR A PARTICULAR 114 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. 115 | This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No 116 | license to Original Work is granted hereunder except under this disclaimer. 117 | 118 | 8) Limitation of Liability. Under no circumstances and under no legal theory, 119 | whether in tort (including negligence), contract, or otherwise, shall the 120 | Licensor be liable to any person for any direct, indirect, special, incidental, 121 | or consequential damages of any character arising as a result of this License 122 | or the use of the Original Work including, without limitation, damages for loss 123 | of goodwill, work stoppage, computer failure or malfunction, or any and all 124 | other commercial damages or losses. This limitation of liability shall not 125 | apply to liability for death or personal injury resulting from Licensor's 126 | negligence to the extent applicable law prohibits such limitation. Some 127 | jurisdictions do not allow the exclusion or limitation of incidental or 128 | consequential damages, so this exclusion and limitation may not apply to You. 129 | 130 | 9) Acceptance and Termination. If You distribute copies of the Original Work or 131 | a Derivative Work, You must make a reasonable effort under the circumstances to 132 | obtain the express assent of recipients to the terms of this License. Nothing 133 | else but this License (or another written agreement between Licensor and You) 134 | grants You permission to create Derivative Works based upon the Original Work 135 | or to exercise any of the rights granted in Section 1 herein, and any attempt 136 | to do so except under the terms of this License (or another written agreement 137 | between Licensor and You) is expressly prohibited by U.S. copyright law, the 138 | equivalent laws of other countries, and by international treaty. Therefore, by 139 | exercising any of the rights granted to You in Section 1 herein, You indicate 140 | Your acceptance of this License and all of its terms and conditions. 141 | 142 | 10) Termination for Patent Action. This License shall terminate automatically 143 | and You may no longer exercise any of the rights granted to You by this License 144 | as of the date You commence an action, including a cross-claim or counterclaim, 145 | against Licensor or any licensee alleging that the Original Work infringes a 146 | patent. This termination provision shall not apply for an action alleging 147 | patent infringement by combinations of the Original Work with other software or 148 | hardware. 149 | 150 | 11) Jurisdiction, Venue and Governing Law. Any action or suit relating to this 151 | License may be brought only in the courts of a jurisdiction wherein the 152 | Licensor resides or in which Licensor conducts its primary business, and under 153 | the laws of that jurisdiction excluding its conflict-of-law provisions. The 154 | application of the United Nations Convention on Contracts for the International 155 | Sale of Goods is expressly excluded. Any use of the Original Work outside the 156 | scope of this License or after its termination shall be subject to the 157 | requirements and penalties of the U.S. Copyright Act, 17 U.S.C. § 101 et 158 | seq., the equivalent laws of other countries, and international treaty. This 159 | section shall survive the termination of this License. 160 | 161 | 12) Attorneys Fees. In any action to enforce the terms of this License or 162 | seeking damages relating thereto, the prevailing party shall be entitled to 163 | recover its costs and expenses, including, without limitation, reasonable 164 | attorneys' fees and costs incurred in connection with such action, including 165 | any appeal of such action. This section shall survive the termination of this 166 | License. 167 | 168 | 13) Miscellaneous. This License represents the complete agreement concerning 169 | the subject matter hereof. If any provision of this License is held to be 170 | unenforceable, such provision shall be reformed only to the extent necessary to 171 | make it enforceable. 172 | 173 | 14) Definition of "You" in This License. "You" throughout this License, whether 174 | in upper or lower case, means an individual or a legal entity exercising rights 175 | under, and complying with all of the terms of, this License. For legal 176 | entities, "You" includes any entity that controls, is controlled by, or is 177 | under common control with you. For purposes of this definition, "control" means 178 | (i) the power, direct or indirect, to cause the direction or management of such 179 | entity, whether by contract or otherwise, or (ii) ownership of fifty percent 180 | (50%) or more of the outstanding shares, or (iii) beneficial ownership of such 181 | entity. 182 | 183 | 15) Right to Use. You may use the Original Work in all ways not otherwise 184 | restricted or conditioned by this License or by law, and Licensor promises not 185 | to interfere with or be responsible for such uses by You. 186 | 187 | This license is Copyright (C) 2003-2004 Lawrence E. Rosen. All rights reserved. 188 | Permission is hereby granted to copy and distribute this license without 189 | modification. This license may not be modified without the express written 190 | permission of its copyright owner. 191 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This put-selector/put module/package provides a high-performance, lightweight 2 | (~2KB minified, ~1KB gzipped with other code) function for creating 3 | and manipulating DOM elements with succinct, elegant, familiar CSS selector-based 4 | syntax across all browsers and platforms (including HTML generation on NodeJS). 5 | The single function from the module creates or updates DOM elements by providing 6 | a series of arguments that can include reference elements, selector strings, properties, 7 | and text content. The put() function utilizes the proven techniques for optimal performance 8 | on modern browsers to ensure maximum speed. 9 | 10 | Installation/Usage 11 | ---------------- 12 | 13 | The put.js module can be simply downloaded and used a plain script (creates a global 14 | put() function), as an AMD module (exports the put() function), or as a NodeJS (or any 15 | server side JS environment) module. 16 | It can also be installed with CPM: 17 | 18 | cpm install put-selector 19 | 20 | and then reference the "put-selector" module as a dependency. 21 | or installed for Node with NPM: 22 | 23 | npm install put-selector 24 | 25 | and then: 26 | 27 | put = require("put-selector"); 28 | 29 | Creating Elements 30 | ---------------- 31 | 32 | Type selector syntax (no prefix) can be used to indicate the type of element to be created. For example: 33 | 34 | newDiv = put("div"); 35 | 36 | will create a new <div> element. We can put a reference element in front of the selector 37 | string and the <div> will be appended as a child to the provided element: 38 | 39 | put(parent, "div"); 40 | 41 | The selector .class-name can be used to assign the class name. For example: 42 | 43 | put("div.my-class") 44 | 45 | would create an element <div class="my-class"> (an element with a class of "my-class"). 46 | 47 | The selector #id can be used to assign an id and [name=value] can be used to 48 | assign additional attributes to the element. For example: 49 | 50 | newInput = put(parent, "input.my-input#address[type=checkbox]"); 51 | 52 | Would create an input element with a class name of "my-input", an id of "address", 53 | and the type attribute set to "checkbox". The attribute assignment will always use 54 | setAttribute to assign the attribute to the element. Multiple attributes and classes 55 | can be assigned to a single element. 56 | 57 | The put function returns the last top level element created or referenced from a selector. 58 | In the examples above, the newly create element would be returned. Note that passing 59 | in an existing node will not change the return value (as it is assumed you already have 60 | a reference to it). Also note that if you only pass existing nodes reference, the first 61 | passed reference will be returned. 62 | 63 | Modifying Elements 64 | ---------------- 65 | 66 | One can also modify elements with selectors. If the tag name is omitted (and no 67 | combinators have been used), the reference element will be modified by the selector. 68 | For example, to add the class "foo" to element, we could write: 69 | 70 | put(element, ".foo"); 71 | 72 | Likewise, we could set attributes, here we set the "role" attribute to "presentation": 73 | 74 | put(element, "[role=presentation]"); 75 | 76 | And these can be combined also. For example, we could set the id and an attribute in 77 | one statement: 78 | 79 | put(element, "#id[tabIndex=2]"); 80 | 81 | One can also remove classes from elements by using the "!" operator in place of a ".". 82 | To remove the "foo" class from an element, we could write: 83 | 84 | put(element, "!foo"); 85 | 86 | We can also use the "!" operator to remove attributes as well. Prepending an attribute name 87 | with "!" within brackets will remove it. To remove the "role" attribute, we could write: 88 | 89 | put(element, "[!role]"); 90 | 91 | Deleting Elements 92 | -------------- 93 | 94 | To delete an element, we can simply use the "!" operator by itself as the entire selector: 95 | 96 | put(elementToDelete, "!"); 97 | 98 | This will destroy the element from the DOM, using either parent innerHTML destruction (IE only, that 99 | reduces memory leaks in IE), or removeChild (for all other browsers). 100 | 101 | Creating/Modifying Elements with XML Namespaces 102 | ----------- 103 | 104 | To work with elements and attributes that are XML namespaced, start by adding the namespace using addNamespace: 105 | 106 | put.addNamespace("svg", "http://www.w3.org/2000/svg"); 107 | put.addNamespace("xlink", "http://www.w3.org/1999/xlink"); 108 | 109 | From there, you can use the CSS3 selector syntax to work with elements and attributes: 110 | 111 | var surface = put("svg|svg[width='100'][height='100']"); 112 | var img = put(surface, "svg|image[xlink|href='path/to/my/image.png']"); 113 | 114 | Text Content 115 | ----------- 116 | 117 | The put() arguments may also include a subsequent string (or any primitive value including 118 | boolean and numbers) argument immediately 119 | following a selector, in which case it is used as the text inside of the new/referenced element. 120 | For example, here we could create a new <div> with the text "Hello, World" inside. 121 | 122 | newDiv = put(parent, "div", "Hello, World"); 123 | 124 | The text is escaped, so any string will show up as is, and will not be parsed as HTML. 125 | 126 | Children and Combinators 127 | ----------------------- 128 | 129 | CSS combinators can be used to create child elements and sibling elements. For example, 130 | we can use the child operator (or the descendant operator, it acts the same here) to 131 | create nested elements: 132 | 133 | spanInsideOfDiv = put(reference, "div.outer span.inner"); 134 | 135 | This would create a new span element (with a class name of "inner") as a child of a 136 | new div element (with a class name of "outer") as a child of the reference element. The 137 | span element would be returned. We can also use the sibling operator to reference 138 | the last created element or the reference element. In the example we indicate that 139 | we want to create sibling of the reference element: 140 | 141 | newSpan = put(reference, "+span"); 142 | 143 | Would create a new span element directly after the reference element (reference and 144 | newSpan would be siblings.) We can also use the "-" operator to indicate that the new element 145 | should go before: 146 | 147 | newSpan = put(reference, "-span"); 148 | 149 | This new span element will be inserted before the reference element in the DOM order. 150 | Note that "-" is valid character in tags and classes, so it will only be interpreted as a 151 | combinator if it is the first character or if it is preceded by a space. 152 | 153 | The sibling operator can reference the last created element as well. For example 154 | to add two td element to a table row: 155 | 156 | put(tableRow, "td+td"); 157 | 158 | The last created td will be returned. 159 | 160 | The parent operator, "<" can be used to reference the parent of the last created 161 | element or reference element. In this example, we go crazy, and create a full table, 162 | using the parent operator (applied twice) to traverse back up the DOM to create another table row 163 | after creating a td element: 164 | 165 | newTable = put(referenceElement, "table.class-name#id tr td[colSpan=2]< and then append 183 | the "child" element to the new <div>: 184 | 185 | put("div", child); 186 | 187 | Or we can do a simple append of an existing element to another element: 188 | 189 | put(parent, child); 190 | 191 | We could also do this more explicitly by using a child descendant, '>' (which has the 192 | same meaning as a space operator, and is the default action between arguments in put-selector): 193 | 194 | put(parent, ">", child); 195 | 196 | We could also use sibling combinators to place the referenced element. We could place 197 | the "second" element after (as the next sibling) the "first" element (which needs a parent 198 | in order to have a sibling): 199 | 200 | put(first, "+", second); 201 | 202 | Or we could create a <div> and place "first" before it using the previous sibling combinator: 203 | 204 | put(parent, "div.second -", first); 205 | 206 | The put() function takes an unlimited number of arguments, so we could combine as 207 | many selectors and elements as we want: 208 | 209 | put(parent, "div.child", grandchild, "div.great-grandchild", gggrandchild); 210 | 211 | Variable Substitution 212 | ------------------- 213 | 214 | The put() function also supports variable substitution, by using the "$" symbol in selectors. 215 | The "$" can be used for attribute values and to represent text content. When a "$" 216 | is encountered in a selector, the next argument value is consumed and used in it's 217 | place. To create an element with a title that comes from the variable "title", we could write: 218 | 219 | put("div[title=$]", title); 220 | 221 | The value of title may have any characters (including ']'), no escaping is needed. 222 | This approach can simplify selector string construction and avoids the need for complicated 223 | escaping mechanisms. 224 | 225 | The "$" may be used as a child entity to indicate text content. For example, we could 226 | create a set of <span> element that each have content to be substituted: 227 | 228 | put("span.first-name $, span.last-name $, span.age $", firstName, lastName, age); 229 | 230 | Assigning Properties 231 | ------------------ 232 | 233 | The put() function can also take an object with properties to be set on the new/referenced 234 | element. For example, we could write: 235 | 236 | newDiv = put(parent, "div", { 237 | tabIndex: 1, 238 | innerHTML: "Hello, World" 239 | }); 240 | 241 | Which is identical to writing (all the properties are set using direct property access, not setAttribute): 242 | 243 | newDiv = put(parent, "div"); 244 | newDiv.tabIndex = 1; 245 | newDiv.innerHTML = "Hello, World"; 246 | 247 | NodeJS/Server Side HTML Generation 248 | ---------------------------- 249 | 250 | While the put() function directly creates DOM elements in the browser, the put() function 251 | can be used to generate HTML on the server, in NodeJS. When no DOM is available, 252 | a fast lightweight pseudo-DOM is created that can generate HTML as a string or into a stream. 253 | The API is still the same, but the put() function returns pseudo-elements with a 254 | toString() method that can be called to return the HTML and sendTo method to direct 255 | generated elements to a stream on the fly. For example: 256 | 257 | put("div.test").toString() -> '
' 258 | 259 | To use put() streaming, we create and element and call sendTo with a target stream. 260 | In streaming mode, the elements are written to the stream as they are added to the 261 | parent DOM structure. This approach is much more efficient because very little 262 | needs to be kept in memory, the HTML can be immediately flushed to the network as it is created. 263 | Once an element is added to the streamed DOM structure, 264 | it is immediately sent to the stream, and it's attributes and classes can no longer be 265 | altered. There are two methods on elements available for streaming purposes: 266 | 267 | element.sendTo(stream) 268 | 269 | The sendTo(stream) method will begin streaming the element to the target stream, 270 | and any children that are added to the element will be streamed as well. 271 | 272 | element.end(leaveOpen) 273 | 274 | The end(leaveOpen) method will end the current streaming, closing all the necessary 275 | tags and closing the stream (unless the argument is true). 276 | 277 | The returned elements also include a put() method so you can directly add to or apply 278 | CSS selector-based additions to elements, for example: 279 | 280 | element.put('div.test'); // create a <div class="test"></div> as a child of element 281 | 282 | Here is an example of how we could create a full page in NodeJS that is streamed to 283 | the response: 284 | 285 | var http = require('http'); 286 | var put = require('put-selector'); 287 | http.createServer(function (req, res) { 288 | res.writeHead(200, {'Content-Type': 'text/html'}); 289 | var page = put('html').sendTo(res); // create an HTML page, and pipe to the response 290 | page.put('head script[src=app.js]'); // each element is sent immediately 291 | page.put('body div.content', 'Hello, World'); 292 | page.end(); // close all the tags, and end the stream 293 | }).listen(80); 294 | 295 | On the server, there are some limitations to put(). The server side DOM emulation 296 | is designed to be very fast and light and therefore omits much of the standard DOM 297 | functionality, and only what is needed for put() is implemented. Elements can 298 | not be moved or removed. DOM creation and updating is still supported in string 299 | generation mode, but only creation is supported in streaming mode. Also, setting 300 | object properties is mostly ignored (because only attributes are part of HTML), except 301 | you can set the innerHTML of an element. 302 | 303 | Proper Creation of Inputs 304 | ------------------------- 305 | 306 | Older versions of Internet Explorer have a bug in assigning a "name" attribute to input after it 307 | has been created, and requires a special creation technique. The put() function handles 308 | this for you as long as you specify the name of the input in the property assignment 309 | object after the selector string. For example, this input creation will properly work 310 | on all browsers, including IE: 311 | 312 | newInput = put("input[type=checkbox]", {name: "works"}); 313 | 314 | Using on Different document 315 | ------------------------- 316 | 317 | If you are using multiple frames in your web page, you may encounter a situation where 318 | you want to use put-selector to make DOM changes on a different HTML document. 319 | You can create a separate instance of the put() function for a separate document by 320 | calling the put.forDocument(document) function. For example: 321 | 322 | put2 = put.forDocument(frames[1].document); 323 | put2("div") <- creates a div element that belongs to the document in the second frame. 324 | put("div") <- the original put still functions on the main document for this window/context 325 | 326 | # License 327 | 328 | put-selector is freely available under *either* the terms of the modified BSD license *or* the 329 | Academic Free License version 2.1. More details can be found in the [LICENSE](LICENSE). 330 | The put-selector project follows the IP guidelines of Dojo foundation packages and all contributions require a Dojo CLA. 331 | If you feel compelled to make a monetary contribution, consider some of the author's [favorite 332 | charities](http://thezyps.com/2012-giving-guide/) like [Innovations for Poverty Action](http://www.poverty-action.org/). --------------------------------------------------------------------------------