\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 ? '' : ('' + tag + '>'));
32 |
33 | currentIndentation = lastIndentation;
34 | return html;
35 | }
36 | return (this.tag == 'html' ? '\n' +
39 | (this.children ? this.children.join('') : '') +
40 | ((noClose || emptyElement) ? '' : ('' + tag + '>'));
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) + '' + tag + '>';
81 | }
82 | }else if(!emptyElement){
83 | output += '' + tag + '>';
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' + this.tag + '>');
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/).
--------------------------------------------------------------------------------