Giter Site home page Giter Site logo

kriszyp / put-selector Goto Github PK

View Code? Open in Web Editor NEW
290.0 20.0 39.0 107 KB

A high-performance, lightweight function for creating and manipulating DOM elements with succinct, elegant, familiar CSS selector-based syntax

License: Other

JavaScript 99.13% HTML 0.87%

put-selector's Introduction

This put-selector/put module/package provides a high-performance, lightweight (~2KB minified, ~1KB gzipped with other code) function for creating and manipulating DOM elements with succinct, elegant, familiar CSS selector-based syntax across all browsers and platforms (including HTML generation on NodeJS). The single function from the module creates or updates DOM elements by providing a series of arguments that can include reference elements, selector strings, properties, and text content. The put() function utilizes the proven techniques for optimal performance on modern browsers to ensure maximum speed.

Installation/Usage

The put.js module can be simply downloaded and used a plain script (creates a global put() function), as an AMD module (exports the put() function), or as a NodeJS (or any server side JS environment) module. It can also be installed with CPM:

cpm install put-selector

and then reference the "put-selector" module as a dependency. or installed for Node with NPM:

npm install put-selector

and then:

put = require("put-selector");

Creating Elements

Type selector syntax (no prefix) can be used to indicate the type of element to be created. For example:

newDiv = put("div");

will create a new <div> element. We can put a reference element in front of the selector string and the <div> will be appended as a child to the provided element:

put(parent, "div"); 

The selector .class-name can be used to assign the class name. For example:

put("div.my-class") 

would create an element <div class="my-class"> (an element with a class of "my-class").

The selector #id can be used to assign an id and [name=value] can be used to assign additional attributes to the element. For example:

newInput = put(parent, "input.my-input#address[type=checkbox]");

Would create an input element with a class name of "my-input", an id of "address", and the type attribute set to "checkbox". The attribute assignment will always use setAttribute to assign the attribute to the element. Multiple attributes and classes can be assigned to a single element.

The put function returns the last top level element created or referenced from a selector. In the examples above, the newly create element would be returned. Note that passing in an existing node will not change the return value (as it is assumed you already have a reference to it). Also note that if you only pass existing nodes reference, the first passed reference will be returned.

Modifying Elements

One can also modify elements with selectors. If the tag name is omitted (and no combinators have been used), the reference element will be modified by the selector. For example, to add the class "foo" to element, we could write:

put(element, ".foo"); 

Likewise, we could set attributes, here we set the "role" attribute to "presentation":

put(element, "[role=presentation]");

And these can be combined also. For example, we could set the id and an attribute in one statement:

put(element, "#id[tabIndex=2]");

One can also remove classes from elements by using the "!" operator in place of a ".". To remove the "foo" class from an element, we could write:

put(element, "!foo");

We can also use the "!" operator to remove attributes as well. Prepending an attribute name with "!" within brackets will remove it. To remove the "role" attribute, we could write:

put(element, "[!role]");

Deleting Elements

To delete an element, we can simply use the "!" operator by itself as the entire selector:

put(elementToDelete, "!");

This will destroy the element from the DOM, using either parent innerHTML destruction (IE only, that reduces memory leaks in IE), or removeChild (for all other browsers).

Creating/Modifying Elements with XML Namespaces

To work with elements and attributes that are XML namespaced, start by adding the namespace using addNamespace:

put.addNamespace("svg", "http://www.w3.org/2000/svg");
put.addNamespace("xlink", "http://www.w3.org/1999/xlink");

From there, you can use the CSS3 selector syntax to work with elements and attributes:

var surface = put("svg|svg[width='100'][height='100']");
var img = put(surface, "svg|image[xlink|href='path/to/my/image.png']");

Text Content

The put() arguments may also include a subsequent string (or any primitive value including boolean and numbers) argument immediately following a selector, in which case it is used as the text inside of the new/referenced element. For example, here we could create a new <div> with the text "Hello, World" inside.

newDiv = put(parent, "div", "Hello, World");

The text is escaped, so any string will show up as is, and will not be parsed as HTML.

Children and Combinators

CSS combinators can be used to create child elements and sibling elements. For example, we can use the child operator (or the descendant operator, it acts the same here) to create nested elements:

spanInsideOfDiv = put(reference, "div.outer span.inner");

This would create a new span element (with a class name of "inner") as a child of a new div element (with a class name of "outer") as a child of the reference element. The span element would be returned. We can also use the sibling operator to reference the last created element or the reference element. In the example we indicate that we want to create sibling of the reference element:

newSpan = put(reference, "+span");

Would create a new span element directly after the reference element (reference and newSpan would be siblings.) We can also use the "-" operator to indicate that the new element should go before:

newSpan = put(reference, "-span");

This new span element will be inserted before the reference element in the DOM order. Note that "-" is valid character in tags and classes, so it will only be interpreted as a combinator if it is the first character or if it is preceded by a space.

The sibling operator can reference the last created element as well. For example to add two td element to a table row:

put(tableRow, "td+td");

The last created td will be returned.

The parent operator, "<" can be used to reference the parent of the last created element or reference element. In this example, we go crazy, and create a full table, using the parent operator (applied twice) to traverse back up the DOM to create another table row after creating a td element:

newTable = put(referenceElement, "table.class-name#id tr td[colSpan=2]<<tr td+td<<");

We also use a parent operator twice at the end, so that we move back up two parents to return the table element (instead of the td element).

Finally, we can use the comma operator to create multiple elements, each basing their selector scope on the reference element. For example we could add two more rows to our table without having to use the double parent operator:

put(newTable, "tr td,tr td+td");

Appending/Inserting Existing Elements

Existing elements may be referenced in the arguments after selectors as well as before. If an existing element is included in the arguments after a selector, the existing element will be appended to the last create/referenced element or it will be inserted according to a trailing combinator. For example, we could create a <div> and then append the "child" element to the new <div>:

put("div", child);

Or we can do a simple append of an existing element to another element:

put(parent, child);

We could also do this more explicitly by using a child descendant, '>' (which has the same meaning as a space operator, and is the default action between arguments in put-selector):

put(parent, ">", child);

We could also use sibling combinators to place the referenced element. We could place the "second" element after (as the next sibling) the "first" element (which needs a parent in order to have a sibling):

put(first, "+", second);

Or we could create a <div> and place "first" before it using the previous sibling combinator:

put(parent, "div.second -", first);

The put() function takes an unlimited number of arguments, so we could combine as many selectors and elements as we want:

put(parent, "div.child", grandchild, "div.great-grandchild", gggrandchild);

Variable Substitution

The put() function also supports variable substitution, by using the "$" symbol in selectors. The "$" can be used for attribute values and to represent text content. When a "$" is encountered in a selector, the next argument value is consumed and used in it's place. To create an element with a title that comes from the variable "title", we could write:

put("div[title=$]", title);

The value of title may have any characters (including ']'), no escaping is needed. This approach can simplify selector string construction and avoids the need for complicated escaping mechanisms.

The "$" may be used as a child entity to indicate text content. For example, we could create a set of <span> element that each have content to be substituted:

put("span.first-name $, span.last-name $, span.age $", firstName, lastName, age);

Assigning Properties

The put() function can also take an object with properties to be set on the new/referenced element. For example, we could write:

newDiv = put(parent, "div", {
	tabIndex: 1,
	innerHTML: "Hello, World"
});

Which is identical to writing (all the properties are set using direct property access, not setAttribute):

newDiv = put(parent, "div");
newDiv.tabIndex = 1;
newDiv.innerHTML = "Hello, World";

NodeJS/Server Side HTML Generation

While the put() function directly creates DOM elements in the browser, the put() function can be used to generate HTML on the server, in NodeJS. When no DOM is available, a fast lightweight pseudo-DOM is created that can generate HTML as a string or into a stream. The API is still the same, but the put() function returns pseudo-elements with a toString() method that can be called to return the HTML and sendTo method to direct generated elements to a stream on the fly. For example:

put("div.test").toString() -> '<div class="test"></div>' 

To use put() streaming, we create and element and call sendTo with a target stream. In streaming mode, the elements are written to the stream as they are added to the parent DOM structure. This approach is much more efficient because very little needs to be kept in memory, the HTML can be immediately flushed to the network as it is created. Once an element is added to the streamed DOM structure, it is immediately sent to the stream, and it's attributes and classes can no longer be altered. There are two methods on elements available for streaming purposes:

element.sendTo(stream)

The sendTo(stream) method will begin streaming the element to the target stream, and any children that are added to the element will be streamed as well.

element.end(leaveOpen) 

The end(leaveOpen) method will end the current streaming, closing all the necessary tags and closing the stream (unless the argument is true).

The returned elements also include a put() method so you can directly add to or apply CSS selector-based additions to elements, for example:

element.put('div.test'); // create a &lt;div class="test">&lt;/div> as a child of element

Here is an example of how we could create a full page in NodeJS that is streamed to the response:

var http = require('http');
var put = require('put-selector');
http.createServer(function (req, res) {
	res.writeHead(200, {'Content-Type': 'text/html'});
	var page = put('html').sendTo(res); // create an HTML page, and pipe to the response 
	page.put('head script[src=app.js]'); // each element is sent immediately
	page.put('body div.content', 'Hello, World');
	page.end(); // close all the tags, and end the stream
}).listen(80);

On the server, there are some limitations to put(). The server side DOM emulation is designed to be very fast and light and therefore omits much of the standard DOM functionality, and only what is needed for put() is implemented. Elements can not be moved or removed. DOM creation and updating is still supported in string generation mode, but only creation is supported in streaming mode. Also, setting object properties is mostly ignored (because only attributes are part of HTML), except you can set the innerHTML of an element.

Proper Creation of Inputs

Older versions of Internet Explorer have a bug in assigning a "name" attribute to input after it has been created, and requires a special creation technique. The put() function handles this for you as long as you specify the name of the input in the property assignment object after the selector string. For example, this input creation will properly work on all browsers, including IE:

newInput = put("input[type=checkbox]", {name: "works"});

Using on Different document

If you are using multiple frames in your web page, you may encounter a situation where you want to use put-selector to make DOM changes on a different HTML document. You can create a separate instance of the put() function for a separate document by calling the put.forDocument(document) function. For example:

put2 = put.forDocument(frames[1].document);
put2("div") <- creates a div element that belongs to the document in the second frame.
put("div") <- the original put still functions on the main document for this window/context 

License

put-selector is freely available under either the terms of the modified BSD license or the Academic Free License version 2.1. More details can be found in the LICENSE. The put-selector project follows the IP guidelines of Dojo foundation packages and all contributions require a Dojo CLA. If you feel compelled to make a monetary contribution, consider some of the author's favorite charities like Innovations for Poverty Action.

put-selector's People

Contributors

csnover avatar eduardo-matos avatar kfranqueiro avatar kriszyp avatar mbulman avatar neonstalwart avatar ttrenka avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

put-selector's Issues

Adding element attributes that contain single quotes

So far, put-selector has been great. I haven't found anything it couldn't do... until now.

I have an application where I create text boxes for a search, and I inject data-attributes into those text boxes containing bits of tSQL statements. When the application collects the contents of those text boxes, it also collects the name attribute and the data-attribute that contains the tSQL statement template. I won't bore you with the rest.

When I use put() to create the text box inputs, I've had several errors when I use it to inject data-attribute strings containing single quotes. Here is a jsFiddle containing a test sample. When I try to assign element properties through an object following the "input", it silently fails to add the attribute (see cases 3 & 4). If I try to add element attributes within the put selector string (i.e. "input[type=text]"), it throws an error. I've tried escaping the single quotes within the text, and it throws an error (see case 6).

I did find a workaround, where I create the element without the possibly offending data-attribute, then assign the data-attribute afterward (see case 5). It's an extra line or two of code to safely implement a library that has saved me hundreds of lines already.

Create a way of using XML namespaces

One thing that would be incredibly beneficial would be a way of defining XML namespaces for put, and then letting people create nodes that would map to createElementNS/createAttributeNS via CSS selector syntax. This kind of thing would allow put to create DOM fragments (not literal) for XML-based DOM structures such as SVG, XSL, and MathML.

A suggestion would be to allow for a user to add an XML namespace to put, and then use it in creation, like so:

put.add("svg", "http://www.w3.org/2000/svg");
put.add("xlink", "http://www.w3.org/1999/xlink");
var surface = put("svg:svg[width='400'][height='400'][xlink:foo='bar']");

Alternatively, a set of predefined XML namespaces can be added that users can reference directly...but I don't think that would supersede the ability to add a namespace.

npm publish

I don't see any versions after 0.2.0 on npm.

Thanks!

AMD definition ignored when using RequireJS optimizer

I ran into some problems using the RequireJS optimizer on this module, it inserts the line define("put", function(){}); after the put.js source code in the optimized output. Somehow the very non-standard UMD footer is causing the optimizer to think that it is not an AMD module.

I got it working with the optimizer using this header template, but I couldn't figure out how to get it working in node, since it has to load node-html.js.

This may be related to #19, not sure.

Node version ignores IDs and style descriptors

Using put-selector 0.2.0 with Node, it generates tagname, class, and most attributes. However, it fails silently on #ids (simply doesn't include them), and throws a TypeError when setting style:

TypeError: Cannot set property 'cssText' of undefined
    at node_modules/put-selector/put.js:172:30

Question about siblings and returned values

Hi,

In the doc, I read:

We could also use sibling combinators to place the referenced element. We could place the "second" element after (as the next sibling) the "first" element:

put(first, "+", second);

It also says:

The put function returns the last top level element created or referenced. In the examples above, the newly create element would be returned.

Now... look at this code:

      var first = put('div.first');
      var second = put('div.second');

      var t = put( first, "+", second );
      console.log(first);
      console.log(second);
      console.log('--------');
      console.log(t);

Now... this is meant to modify first so that it adds "second" as a sibling (note that I am using the same example given in the docs). But that's not what's happening:

<div class=​"first">​</div>​
<div class=​"second">​</div>​
--------
null 

So:

  • The last top element created or referenced should be "second", but that's not returned
  • If you write put( first, "+", second ), it implies that the result is an element where first and second are siblings, but... that element doesn't even exist, nor we asked put() to create it!

Something that would make more sense is:

      var first = put('div.first');
      var second = put('div.second');

      var t = put( 'div', first, "+", second );
      console.log(first);
      console.log(second);
      console.log('--------');
      console.log(t);

Result:

<div class=​"first">​</div>
<div class=​"second">​</div>​
--------
<div>​
  <div class=​"first">​</div>​
  <div class=​"second">​</div>​
</div>​

So, "div" is created, it's appended first, and then "second" as a sibling of first. Now it makes sense... except, why is the newly created div returned, rather than second (which is the last element referenced!)

Help? :D

Merc.

-span-span Issues

I was playing around with put-selector and found some weird "-" issues.

  • put(existingNode, "-span-span") creates one "span-span" tag
  • put(existingNode, "-span -span") creates only one SPAN tag
  • put(existingNode, "+div+div") creates two DIVs

The third case makes sense, but the first two cases seem errant.

Latest put.js breaks row height in dgrid

I tried going through the dgrid simple tutorial using the latest put-selector and xtype. But I kept getting row height 362px instead of 20px that the online example got. After trying various versions and doing diff on all css files I eventually switched to the put.js used in the tutorial (http://dojofoundation.org/packages/dgrid/tutorials/hello_dgrid/demo/events.html) and suddenly I got the correct row height. It's not obvious which of the code changes in put.js that causes this, especially since I don't know which version is used in the tutorial.

I'd be a happy user if put-selector and xtype had tagged, stable versions. Would that be possible? Thanks!

Regards, John

put "sometimes" fails on mobile Safari (iPad)

I'm using 2 dgrids where the second one is rebuild in the select event on the first. Everything works as expected on Chrome, FF and Safari on OSX, but when running on Safari on iPad I've got a problem. Since this problem was reproduce able I started debugging it, but when I attached the debugger on my Mac the problem disappeared; So by adding debug Output to the page, and going thru the whole dgrid create code (!!), finally I could "isolate" the problem.
I surrounded a call to insertLastElement with try catch, so that the code in put looks like:
...
if(leftoverCharacters){
throw new SyntaxError("Unexpected char " + leftoverCharacters + " in " + argument);
}
try{
insertLastElement();
}catch(err){
document.getElementById('debug').innerHTML+="
ERROROOORR!!:"+err+"
";

};
referenceElement = returnValue = current || referenceElement;
}
}
if(topReferenceElement && fragment){
// we now insert the top level elements for the fragment if it exists
topReferenceElement.appendChild(fragment);
}
return returnValue;
}

and every time when I was able to reproduce the problem I got for err:
TypeError: 'undefined' is not a function

trying to further debug insertLastElement is not possible since, as soon as I add the try catch to the insertBefore it looks like this changes timing in a way that the error disappears...

So the outcome is that insertBefore sometimes fails with the above exception, but I don't know why...
Any Ideas?
Ognian

PS: the put in dgrid which caused the error was:
tr = put(tbody, "tr"); //Grid.js line 97

Text content siblings

Hello, is it possible to generate the following tree using put syntax?

<div>Hello <b>text</b> world</div>

The issue is that the div element has two text children, "Hello " and " world", but the dollar sign syntax $ seems to be designed to accept only one.

Wrong CSS Selector definition for XML namespaced elements/attributes

Apparently I was slightly wrong with the syntax of XML namespaces using CSS selectors...instead of using a ":", it's supposed to use a pipe ("|"), like so:

put(parent, "svg|svg[width=100][height=100]");

Think we should change the syntax to meet that? The other issue I'm seeing is that QSA is supposed to support XML namespaces but doesn't seem to, at least in Safari...

Otherwise quick testing is showing that the commit made today works just fine for inserting XML-based nodes; I noticed that you have to include the namespace for all elements, but it works very well. Example:

put.addNamespace("svg", "http://www.w3.org/2000/svg");
var surface = put(document.body, "svg:svg[width=100][height=100]");
var rect = put(surface, "svg:rect[width=50][height=50][x=25][y=25][fill=red][stroke=blue]");

You'll see a small square set near the middle of the SVG element. Nice work!

package.json

The package.json file lists the wrong version number.

put.getDocument

this is low priority - mostly looking for feedback since its just for a POC.

after looking at node-html.js i came to the realization that i could use put to build an ast if i give it an appropriate object as a document. my idea is that i want to parse something like div#{{id}} into an ast and then render that with a context like { id: 'identity' } to build and manipulate DOM nodes. for example

var context = new Stateful({ id: 'identity' });
var node = magic.render('div#{{id}}', context); // creates a node with id="identity" and watches context for changes
put(parent, node); // node is added to a parent
context.set('id', 'changed'); // has the same effect as put(node, '#changed);

the idea is to parse a whole tree like this but only update each node based on changes to the context.

during magic.render i'll set the document to be something that will build an AST for me. then after that i want to set it back to the proper document. of course, i know that it should be document but the right thing to do would be to get whatever the document is before i change it, remember that and then change it back when i'm done. so for that, i'm suggesting put.getDocument.

as an aside, in case you try this, 'div#{{id}}' fails to parse but i'm able to trick put into parsing 'div#$id' (maybe that's a bug). also, if you're interested in exploring this idea with me i'd be glad to get your input.

XML Namespaced nodes not fully removed with "!"

Noticing one thing with some quick testing of the new XML namespaces addition...this:

var test = put(document.body, "svg:svg#myTest");
put(test, "!");

...seems to remove the element from the browser renderer but doesn't actually remove the node from the document. If you were to follow that block above (yes, I didn't define the namespace for brevity) with this:

var nl = document.querySelectorAll("#myTest");

...you'll end up with a single node in the nodelist, at least with my quick testing. Maybe this line in put:

put("div", current, '<').innerHTML = "";

...is what needs to be addressed?

Fail when putting something different to a string

Since last commit (01bd3a4) there is a problem when you pass a content with a type different of string (number, as example).

The problem is at line 174 from put.js. Maybe the arg type could be checked before or converted to string first.

Anonymous define causes requireJS r.js to fail!

For SitePen/dgrid#1171

When trying to build dgrid stable r.js simply fails to build it citing an "Anonymous Define".

From the dgrid code I tracked it to https://github.com/kriszyp/put-selector/blob/master/put.js#L3

Why wrap this around a function closure? It may work for Dojo but failing for requirejs. After weeks and weeks of agony 😞 dgrid master is getting built as there is no put-selector. If you can change this, I will be able to build dgrid stable too!

Issues with put-selector and Opera

Not entirely sure how much of an issue this actually is...but I tried to run an application that makes heavy use of dgrid on Opera (OSX, 11.5) and it threw a number of errors within put-selector. It would seem it doesn't like accessing the arguments object in a way that was done in put-selector...

Unfortunately I tried to upgrade Opera (to 11.62) and it promptly barfed on Dojo's AMD implementation, so I can't give you more information than this at the moment. I will try to cycle back and give you a better report ASAP.

Cheers!

Custom build fails with v0.3.3

error(307) Failed to evaluate module tagged as pure AMD (fell back to processing with regular expressions). module: put-selector/put; error: TypeError: org.mozilla.javascript.Undefined@6602e323 is not a function, it is undefined.

SVG Support

I have encountered the following error:

Uncaught TypeError: Cannot set property className of #<SVGElement> which has only a getter
(anonymous function)    @   put.js:145
put @   put.js:79

userAgent: "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.130 Safari/537.36"

var put = require("put-selector");
put.addNamespace('svg', "http://www.w3.org/2000/svg");
put('svg|g.oops');

For SVG elements you should be using the classList property.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.