Writing A Better JavaScript Library For The DOM

Advertisement

At present, jQuery is the de facto library for working with the document object model (DOM). It can be used with popular client-side MV* frameworks (such as Backbone), and it has a ton of plugins and a very large community. As developers’ interest in JavaScript increases by the minute, a lot of people are becoming curious about how native APIs really work and about when we can just use them instead of including an extra library.

Lately, I have started to see more and more problems with jQuery, at least my use of it. Most of the problems are with jQuery’s core and can’t be fixed without breaking backwards compatibility — which is very important. I, like many others, continued using the library for a while, navigating all of the pesky quirks every day.

Then, Daniel Buchner1 created SelectorListener2, and the idea of “live extensions” manifested. I started to think about creating a set of functions that would enable us to build unobtrusive DOM components using a better approach than what we have used so far. The objective was to review existing APIs and solutions and to build a clearer, testable and lightweight library.

Adding Useful Features To The Library

The idea of live extensions encouraged me to develop the better-dom project3, although other interesting features make the library unique. Let’s review them quickly:

  • live extensions
  • native animations
  • embedded microtemplating
  • internationalization support

Live Extensions

jQuery has a concept called “live events.” Drawing on the idea of event delegation, it enables developers to handle existing and future elements. But more flexibility is required in a lot of cases. For example, delegated events fall short when the DOM needs to be mutated in order to initialize a widget. Hence, live extensions.

The goal is to define an extension once and have any future elements run through the initialization function, regardless of the widget’s complexity. This is important because it enables us to write Web pages declaratively; so, it works great with AJAX applications.

Live extensions make is easier to handle any future elements.4
Live extensions enable you to handle any future elements without the need to invoke the initialization function. (Image credits5)

Let’s look at a simple example. Let’s say our task is to implement a fully customizable tooltip. The :hover pseudo-selector won’t help us here because the position of the tooltip changes with the mouse cursor. Event delegation doesn’t fit well either; listening to mouseover and mouseleave for all elements in the document tree is very expensive. Live extensions to the rescue!

DOM.extend("[title]", {
  constructor: function() {
    var tooltip = DOM.create("span.custom-title");

    // set the title's textContent and hide it initially
    tooltip.set("textContent", this.get("title")).hide();

    this
      // remove legacy title
      .set("title", null)
      // store reference for quicker access
      .data("tooltip", tooltip)
      // register event handlers
      .on("mouseenter", this.onMouseEnter, ["clientX", "clientY"])
      .on("mouseleave", this.onMouseLeave)
      // insert the title element into DOM
      .append(tooltip);
  },
  onMouseEnter: function(x, y) {
    this.data("tooltip").style({left: x, top: y}).show();
  },
  onMouseLeave: function() {
    this.data("tooltip").hide();
  }
});

We can style the .custom-title element in CSS:

.custom-title {
  position: fixed; /* required */
  border: 1px solid #faebcc;
  background: #faf8f0;
}

The most interesting part happens when you insert a new element with a title attribute in the page. The custom tooltip will work without any initialization call.

Live extensions are self-contained; thus, they don’t require you to invoke an initialization function in order to work with future content. So, they can be combined with any DOM library and will simplify your application logic by separating the UI code into many small independent pieces.

Last but not least, a few words on Web components6. A section of the specification, “Decorators7,” aims to solve a similar problem. Currently, it uses a markup-based implementation with a special syntax for attaching event listeners to child elements. But it’s still an early draft:

“Decorators, unlike other parts of Web Components, do not have a specification yet.”

Native Animations

Thanks to Apple8, CSS has good animation support9 now. In the past, animations were usually implemented in JavaScript via setInterval and setTimeout. It was a cool feature — but now it’s more like a bad practice. Native animations will always be smoother: They are usually faster, take less energy and degrade well if not supported by the browser.

In better-dom, there is no animate method: just show, hide and toggle. To capture a hidden element state in CSS, the library uses the standards-based aria-hidden attribute.

To illustrate how it works, let’s add a simple animation effect to the custom tooltip that we introduced earlier:

.custom-title {
  position: fixed; /* required */
  border: 1px solid #faebcc;
  background: #faf8f0;
  /* animation code */
  opacity: 1;
  -webkit-transition: opacity 0.5s;
  transition: opacity 0.5s;
}

.custom-title[aria-hidden=true] {
  opacity: 0;
}

Internally, show() and hide() set the aria-hidden attribute value to be false and true. It enables the CSS to handle the animations and transitions.

You can see a demo with more animation examples that use better-dom10.

Embedded Microtemplating

HTML strings are annoyingly verbose. Looking for a replacement, I found the excellent Emmet11. Today, Emmet is quite a popular plugin for text editors, and it has a nice and compact syntax. Take this HTML:

body.append("<ul><li class='list-item'></li><li class='list-item'></li><li class='list-item'></li></ul>");

And compare it to the equivalent microtemplate:

body.append("ul>li.list-item*3");

In better-dom, any method that accepts HTML may use Emmet expressions as well. The abbreviation parser is fast12, so no need to worry about a performance penalty. A template precompilation function13 also exists to be used on demand.

Internationalization Support

Developing a UI widget often requires localization — not an easy task. Over the years, many have tackled this in different ways. With better-dom, I believe that changing the state of a CSS selector is like switching languages.

Conceptually speaking, switching a language is like changing the “representation” of content. In CSS2, several pseudo-selectors help to describe such a model: :lang and :before. Take the code below:

[data-i18n="hello"]:before {
  content: "Hello Maksim!";
}

[data-i18n="hello"]:lang(ru):before {
  content: "Привет Максим!";
}

The trick is simple: The value of the content property changes according to the current language, which is determined by the lang attribute of the html element. By using data attributes such as data-i18n, we can maintain the textual content in HTML:

[data-i18n]:before {
  content: attr(data-i18n);
}

[data-i18n="Hello Maksim!"]:lang(ru):before {
  content: "Привет Максим!";
}

Of course, such CSS isn’t exactly attractive, so better-dom has two helpers: i18n and DOM.importStrings. The first is used to update the data-i18n attribute with the appropriate value, and the second localizes strings for a particular language.

label.i18n("Hello Maksim!");
// the label displays "Hello Maksim!"
DOM.importStrings("ru",  "Hello Maksim!", "Привет Максим!");
// now if the page is set to ru language,
// the label will display "Привет Максим!"
label.set("lang", "ru");
// now the label will display "Привет Максим!"
// despite the web page's language

Parameterized strings can be used as well. Just add ${param} variables to a key string:

label.i18n("Hello ${user}!", {user: "Maksim"});
// the label will display "Hello Maksim!"

Making Native APIs More Elegant

Generally, we want to stick to standards. But sometimes the standards aren’t exactly user-friendly. The DOM is a total mess, and to make it bearable, we have to wrap it in a convenient API. Despite all of the improvements made by open-source libraries, some parts could still be done better:

  • getter and setter,
  • event handling,
  • functional methods support.

Getter and Setter

The native DOM has the concept of attributes and properties of elements that could behave differently. Assume we have the markup below on a Web page:

<a href="/chemerisuk/better-dom" id="foo" data-test="test">better-dom</a>

To explain why “the DOM is a total mess,” let’s look at this:

var link = document.getElementById("foo");

link.href; // => "https://github.com/chemerisuk/better-dom"
link.getAttribute("href"); // => "/chemerisuk/better-dom"
link["data-test"]; // => undefined
link.getAttribute("data-test"); // => "test"

link.href = "abc";
link.href; // => "https://github.com/abc"
link.getAttribute("href"); // => "abc"

An attribute value is equal to the appropriate string in HTML, while the element property with the same name could have some special behavior, such as generating the fully qualified URL in the listing above. These differences can be confusing.

In practice, it’s hard to imagine a practical situation in which such a distinction would be useful. Moreover, the developer should always keep in mind which value (attribute or property) is being used that introduces unnecessary complexity.

In better-dom, things are clearer. Every element has only smart getters and setters.

var link = DOM.find("#foo");

link.get("href"); // => "https://github.com/chemerisuk/better-dom"
link.set("href", "abc");
link.get("href"); // => "https://github.com/abc"
link.get("data-attr"); // => "test"

In the first step, it does a property lookup, and if it’s defined, then it’s used for manipulation. Otherwise, getter and setter work with the appropriate attribute of the element. For booleans (checked, selected, etc.), you could just use true or false to update the value: Changing such a property on an element would trigger the appropriate attribute (native behavior) to be updated.

Improved Event Handling

Event handling is a big part of the DOM, however, I’ve discovered one fundamental problem: Having an event object in element listeners forces a developer who cares about testability to mock the first argument, or to create an extra function that passes only event properties used in the handler.

var button = document.getElementById("foo");

button.addEventListener("click", function(e) {
  handleButtonClick(e.button);
}, false);

This is really annoying. What if we extracted the changing part as an argument? This would allow us to get rid of the extra function:

var button = DOM.find("#foo");

button.on("click", handleButtonClick, ["button"]);

By default, the event handler passes the ["target", "defaultPrevented"] array, so no need to add the last argument to get access to these properties:

button.on("click", function(target, canceled) {
  // handle button click here
});

Late binding is supported as well (I’d recommend reading Peter Michaux’s review of the topic14). It’s a more flexible alternative to the regular event handlers that exist in the W3C’s standard15. It could be useful when you need frequent on and off method calls.

button._handleButtonClick = function() { alert("click!"); };

button.on("click", "_handleButtonClick");
button.fire("click"); // shows "clicked" message
button._handleButtonClick = null;
button.fire("click"); // shows nothing

Last but not least, better-dom has none of the shortcuts that exist in legacy APIs and that behave inconsistently across browsers, like click(), focus() and submit(). The only way to call them is to use the fire method, which executes the default action when no listener has returned false:

link.fire("click"); // clicks on the link
link.on("click", function() { return false; });
link.fire("click"); // triggers the handler above but doesn't do a click

Functional Methods Support

ES5 standardized a couple of useful methods for arrays, including map, filter and some. They allow us to use common collection operations in a standards-based way. As a result, today we have projects like Underscore16 and Lo-Dash17, which polyfill these methods for old browsers.

Each element (or collection) in better-dom has the methods below built in:

  • each (which differs from forEach by returning this instead of undefined)
  • some
  • every
  • map
  • filter
  • reduce[Right]
var urls, activeLi, linkText; 

urls = menu.findAll("a").map(function(el) {
  return el.get("href");
});
activeLi = menu.children().filter(function(el) {
  return el.hasClass("active");
});
linkText = menu.children().reduce(function(memo, el) {
  return memo || el.hasClass("active") && el.find("a").get()
}, false);

Avoiding jQuery Problems

Most of the following issues can’t be fixed in jQuery without breaking backwards compatibility. That’s why creating a new library seemed like the logical way out.

  • the “magical” $ function
  • the value of the [] operator
  • issues with return false
  • find and findAll

The “Magical” $ Function

Everyone has heard at some point that the $ (dollar) function is kind of like magic. A single-character name is not very descriptive, so it looks like a built-in language operator. That’s why inexperienced developers call it inline everywhere.

Behind the scenes, the dollar is quite a complex function. Executing it too often, especially in frequent events such as mousemove and scroll, could cause poor UI performance.

Despite so many articles recommending jQuery objects to be cached, developers continue to insert the dollar function inline, because the library’s syntax encourages them to use this coding style.

Another issue with the dollar function is that it allows us to do two completely different things. People have gotten used to such a syntax, but it’s a bad practice of a function design in general:

$("a"); // => searches all elements that match “a” selector
$("<a>"); // => creates a <a> element with jQuery wrapper

In better-dom, several methods cover the responsibilities of the dollar function in jQuery: find[All] and DOM.create. find[All] is used to search element(s) according to the CSS selector. DOM.create makes a new elements tree in memory. Their names make it very clear what they are responsible for.

Value of the [] Operator

Another reason for the problem of frequent dollar function calls is the brackets operator. When a new jQuery object is created, all associated nodes are stored in numeric properties. But note that the value of such a property contains a native element instance (not a jQuery wrapper):

var links = $("a");

links[0].on("click", function() { ... }); // throws an error
$(links[0]).on("click", function() { ... }); // works fine

Because of such a feature, every functional method in jQuery or another library (like Underscore) requires the current element to be wrapped with $() inside of a callback function. Therefore, developers must always keep in mind the type of object they are working with — a native element or a wrapper — despite the fact that they are using a library to work with the DOM.

In better-dom, the brackets operator returns a library’s object, so developers can forget about native elements. There is only one acceptable way to access them: by using a special legacy method.

var foo = DOM.find("#foo");

foo.legacy(function(node) {
  // use Hammer library to bind a swipe listener
  Hammer(node).on("swipe", function(e) {
    // handle swipe gesture here
  }); 
});

In reality, this method is required in very rare cases, such as to be compatible with a native function or with another DOM library (like Hammer18 in the example above).

Issues With return false

One thing that really blows my mind is the strange return false interception in jQuery’s event handlers. According to the W3C’s standards, it should in most cases19 cancel the default behavior. In jQuery, return false also stops event delegation20.

Such interception creates problems:

  1. Invoking stopPropagation() by itself could lead to compatibility problems, because it prevents listeners that are related to some other task from doing their work.
  2. Most developers (even experienced ones) are not aware of such behavior.

It’s unclear why the jQuery community decided to go cross-standards. But better-dom is not going to repeat the same mistake. Thus, return false in an event handler only prevents the browser’s default action, without messing with event propagation, as everyone would expect.

find and findAll

Element search is one of the most expensive operations in the browser. Two native methods could be used to implement it: querySelector and querySelectorAll. The difference is that the first one stops searching on the first match.

This feature enables us to decrease the iterations count dramatically in certain cases. In my tests, the speed was up to 20 times faster21! Also, you can expect that the improvement will grow according to the size of the document tree.

jQuery has a find method that uses querySelectorAll for general cases. Currently, no function uses querySelector to fetch only the first matched element.

The better-dom library has two separate methods: find and findAll. They allow us to use querySelector optimization. To estimate the potential improvement in performance, I searched for the usage of these methods in all of the source code of my last commercial project:

  • find
    103 matches across 11 files
  • findAll
    14 matches across 4 files

The find method is definitely much more popular. It means that querySelector optimization makes sense in most use cases and could give a major performance boost.

Conclusion

Live extensions really make solving front-end problems much easier. Splitting the UI in many small pieces leads to more independent and maintainable solutions. But as we’ve shown, a framework is not only about them (although it is the main goal).

One thing I’ve learned in the development process is that if you don’t like a standard or you have a different opinion of how things should work, then just implement it and prove that your approach works. It’s really fun, too!

More information about the better-dom project can be found on GitHub22.

(al, il, ea)

↑ Back to topShare on Twitter

Maksim is a freelance front-end and back-end Web developer who lives in Minsk, Belarus. In his spare time, he likes to learn Web standards and to contribute to open source projects that could accelerate publishing new ideas via the Internet.

  1. 1

    “Lately, I have started to see more and more problems with jQuery, at least my use of it. Most of the problems are with jQuery’s core and can’t be fixed without breaking backwards compatibility — which is very important. I, like many others, continued using the library for a while, navigating all of the pesky quirks every day.”
    LOL
    give me a break

    0
    • 2

      Brandon Carrol McKimmons

      January 13, 2014 10:24 am

      Do you care to enlighten us all on just what’s so ridiculously funny to you? The statement you quoted of his could’ve come from the mouth of myself or countless other web devs out there… Are you saying jQuery is flawless and to question it’s uber-DOM-manipulations is folly, borderline blasphemy??

      … the guy is trying to spark a conversation on OTHER solutions than the almighty jQuery because, in fact, jQuery is not perfect and there are many out there who’d absolutely welcome the addition of any viable options.

      1
      • 3

        Yes, jQuery IS as close to flawless as it can get. if you encounter issues I would say you ate doing things wrong. Extremely capable people are working hard on the lib. Just a random guy from the internet can’t possibly do any better, it’s ridiculous. Please. You could use presto. And what’s the deals with saying CSS animations are better than js?? That is an outright lie. JS can render crazy things CSS isn’t close to handle. So please stop hating jQuery just cause you’re a web hipster who wants to do things differently.

        1
        • 4

          While i agree with you for the most part, CSS animations are better than JS. Everything you can do with CSS, you should do with CSS. Animations you can’t do in CSS – you use JS. Why? Performance. Especially on mobile.

          Nobody said CSS can replace JS for animations. But CSS animations (the ones you can do with CSS) ARE better than the same animations in JS.

          -1
          • 5

            @Sumit. I would say CSS are good , but are not a panacea and I would do a bit more research JS animation can in fact be more performant and significantly more flexible than CSS. Have a look at http://css-tricks.com/myth-busting-css-animations-vs-javascript/. This is written by one of the developers of GSAP but he does do a very good analysis and very informative.

            Just as jQuery has been a boon to interactive web, doesn’t mean jQuery does things perfectly. it was quite some time before jQuery has easing that matched Moo tools.

            Everyone has different requirements so blanket assertions by any developer are not always valid. In the case of Maksim’s work I could see this being useful in many Web applications where the front-end js goes into the thousands of lines. It I can get the functionality I need by reducing the written code great. Isn’t that just one aspect a developer wants.

            0
          • 6

            Maksim Chemerisuk

            January 15, 2014 8:05 pm

            @Kevin, thanks for the interesting link. But that article is focused on performance, although you should also keep in mind other things:

            1) power consumption.
            Having all animation logic in CSS is much simpler to optimize on hardware-level, because it’s easier to predict a next frame. I believe CSS animations will always win in this field.

            2) separation of concerns.
            I remember the times when people used mouseenter and mouseleave to change color of links. Then :hover pseudoselector was included into CSS and currently I do not know a lot of developers who still uses JS for such simple effects.

            When CSS3 animations spec was proposed I think the world started to move animations (at least simple ones) into CSS. So accepting this point of view earlier is future-proof as well.

            3) file size.
            Implementing animations in JavaScript will definitely require more code.

            0
      • 7

        Relax Brandon. When you use jQuery you vanquish every foe with a couple of seconds thought and a couple of lines of code. It can be hard switching out of that mode when entering back into the real world.

        0
      • 8

        He probably uses jQuery for a few days now, give him a break :)

        0
  2. 9

    I think there are a lot of good ideas and fixes in your library. Some problems you mentioned with jQuery like the $ function and the [] operator are for the most part only problems if you don’t know how to use them. I’m not saying it’s good, but I won’t call it a problem either as developing code shouldn’t be for dummies anyway.

    The biggest advantage I see in your library is “find” vs “findAll” and internationalization support.
    Currently I have only big projects I’m working on but as soon as a smaller one comes up, I’ll give your library a try.

    Thanks for the nice write-up.

    0
    • 10

      Hardly jQuery would be abandoned in the near future, hence it’s viable to suggest findOne method for them.

      0
  3. 11

    I do not understand why do you overwrite the default behaviour of aria-hidden=true and set it to display block. Why do you not toggle the other way round and set the css transform property on aria-hidden=false?

    0
    • 12

      Maksim Chemerisuk

      January 13, 2014 9:20 am

      The problem is that CSS3 animations do not work if an element changes the “display” property from “none” to something else. In better-dom before 1.6.6, that was published recently, I added display:none by default for each element with [aria-hidden=true], so I had to overwrite it manually. But good news is that starting from 1.6.6 the library uses visibility property with a completely different approach (compare http://jsfiddle.net/C3WeM/4/ and http://jsfiddle.net/C3WeM/5/), so no need to define the extra display property.

      Using aria-hidden=false is also an interesting idea. The only downsides I see is that it adds an extra aria-hidden=false rule and in theory by spec an element with aria-hidden=false and without aria-hidden attribute should have equal meaning… Anyway please submit an issue at github and let’s discuss your idea there.

      0
  4. 13

    So I like the article a lot. The only thing that nags at me is calling it the “native” DOM. So the inference is using JQuery is not the “native” DOM but at the end of the day JQuery is using the native DOM. It’s just a lib on top of the same exact language. Everyone these days is saying “native” to define whatever they want. They aren’t native API’s. They’re the JavaScript API’s. Native API’s would be the stuff that the JS runtime is calling from C.

    0
    • 14

      Maksim Chemerisuk

      January 13, 2014 9:27 am

      Hey Rick, I’m not calling jQuery “native” :) By “native” I mean APIs that available in browser without any library. The last part of the article is focused on jQuery only because it’s the most popular solution for working with the DOM.

      0
  5. 15

    Interesting work, Maksim.

    It reminds me a little of Dean Edwards’ “Javascript Behaviors”:
    http://dean.edwards.name/jsb/behavior.html
    Especially the “live extensions” feature.

    I also have similar goals with my “DOM Sprockets” project:
    https://github.com/meekostuff/DOMSprockets
    Its alpha quality, but you can see some demos at:
    http://devel.meekostuff.net/DOMSprockets/0.2-devel/demos/
    I’m always keen for feedback.

    One thing I liked in JSB is that “behaviors” encourage inheritance. Can I do that with “live extensions”?

    I didn’t like that JSB passed the proxied element as the first argument of all method calls – I’m glad to see better-dom *does not*.

    I *do* think better-dom should provide lower-level access to objects. For instance:
    - in the constructor or event handler `this` will be a proxy for an element, but can I get the proxied element? (I’m guessing `this._node` does this?)
    - DOM.find() will return a default proxy for an element. Can I get the proxy for any “live extensions” that have been applied to the element.

    How do you handle the situation where two “live extension” registrations might target the same element? I only permit one per element which *should be* based on selector specificity (but is currently the last one registered). I suspect this is the least surprise for a page-author since it matches CSS properties. But it does create more work for my library.

    Is there a use-case for late-binding of event handlers that can’t be achieved by changing the state of the proxy object or proxied element and having the handler respond to that state? e.g.

    button._handleClick = function() { if (!this._disabled) alert(“click!”); };
    button.on(“click”, “_handleClick”);
    button.fire(“click”); // shows “clicked” message
    button._disabled = null;
    button.fire(“click”); // shows nothing

    1
    • 16

      Maksim Chemerisuk

      January 14, 2014 4:23 am

      1. You can always access native elements via the `legacy` method (take a look at the end of the VALUE OF THE [] OPERATOR section)

      2. I was thinking about inheritance in the beginning as well, but ended with multiple extensions applied to the same element. I didn’t have any real life use cases when need to inherit and modify an extension declaration itself.

      3. DOM.find() will usually return an element wrapper with ALL extensions applied. There is no possibility to apply an extension manually, frankly I do not see any value for doing this.

      4. Of course it’s possible to cover all late binding use cases via simple event listeners (you got the idea how according to your example). It’s just a syntax sugar for a specific type of event handlers.

      0
      • 17

        Hi Maksim,

        Thanks for the reply. I completely missed the `legacy()` method.
        I’m surprised that you have no real life use cases for inheritance with “live extensions” – I’m using it all the time. Can you link to some examples for “live extensions”?

        0
        • 18

          Maksim Chemerisuk

          January 14, 2014 6:34 am

          Hey Sean, you can find some examples of live extensions here: https://github.com/chemerisuk/better-dom#projects-that-use-better-dom.

          As for inheritance I probably should clarify why. The reason is that `DOM.extend` from better-dom is close by goals to “Decorators” from Web Components or HTC behaviors from Microsoft. Originally I wanted to make a “library for creating polyfills”.

          UI widget hierarchies look cool but they are usually too dependent. It’s hard just to grab a single component and use it in your particular project, you had to add tons of extra stuff. Therefore I like to develop small independent widgets or polyfills that do not rely on each other.

          `DOM.extend` respects declaration order, so it’s possible to override public methods and event handlers (late binding could help here) by defining a new live extension that matches the same selector after a parent. But as I noted above I’d not recommend to develop widgets that rely on each other. It’s not a bugs-free strategy as well.

          0
        • 19

          Sean,

          Maybe you should pass some examples to Maksim. Even better maybe you should both talk more, maybe there is ways to integrate or tie the projects together (Sean, haven’t looked at your work yet.)

          One thing I find is that some many people create “different” wheels that at some point there are too many. I think that is why jQuery became so popular.

          0
  6. 20

    We’ve been accustomed to think in jQuery. (IE 11′s spell checking dictionary has even adopted this word.) And the SelectorListener you gave us pollutes the document and DOM objects, so I don’t think it a good choice.

    And to Smashing Magazine, when I wanted to submit my reply, I tabbed from the “Your message*” (let me call it so) textarea and I expected a submit button to hold the focus. But what was really focused is the logo link on the header. I felt confused.

    0
  7. 23

    I enjoyed this article and would like to read more about this library. Wrapping all the elements returned by findAll in the equivalent of $() would save me a lot of grief. A new approach to attaching event handlers would be nice too. I basically never want to attach an event handler to a specific element, I almost always want it attached “live” to a selector rooted at the document. Anyway, I’m not dropping jQuery just quite yet ;) But thanks for explaining where you’re coming from and talking through the issues you have. *Something* will eventually replace jQuery, we might as well be thinking about it.

    0
  8. 24

    Thanks Maksim -Nice article, but why is it thanks to Apple that css has good support for animation now?

    Safari, Firefox and Chrome are all fine browsers and have good support for animations and transitions. Safari and Chrome both introduced these features early and relied on Webkit to do so.

    If anything the market share of Chrome has done more than Safari to make using css animations and transitions practical. Although no web browser, layout engine or company deserves full credit for this.

    0
    • 25

      Before Chrome … there was SAFARI with web-kit :) He is talking a for a time about 5-6 years back before we had the mighty Chrome (that no longer uses web-kit).

      0
    • 26

      Maksim Chemerisuk

      January 14, 2014 3:55 am

      Apple proposed the CSS animations specification.

      0
  9. 27

    Quite an extensive blog! I really appreciate your insight and knowledge. I will bookmark this blog, and return for references. I am searching for topics, tips, and tricks to create articles on http://www.diywebpages.com We are in the beginning stages of creating a “on-stop-shop” for DiY “do it yourself” website owners.

    Any ideas, suggestions are always welcome! Thank you for your time!

    James W. – Opulentworx

    0
  10. 29

    Definitely good stuff. Haven’t seen that here in a while. Maksim, very fresh and nice idea and I would most certainly test it with some new features I am doing. Thanks for sharing your ideas!

    0
  11. 30

    “how native APIs really work and [how] we can just use them instead of including an extra library.”

    I assume by “native APIs” you are referring to vanilla JS, not requiring additional libraries to function.

    Using your better-dom would ironically mean to depend on an extra library rather than the “native API”. What was the point again? – Using native APIs instead of extra libraries?

    0
    • 31

      Maksim Chemerisuk

      January 15, 2014 8:21 am

      Yes, we still need a library for working with the DOM. The point of the introduction is that by learning native APIs we could better understand what features browsers have at present. Having these knowledge in mind we could discover new cool ides and make our libraries lighter when possible (for instance by dropping JavaScript animations and using CSS3 capabilities instead).

      0
  12. 32

    Dem pesky quirks

    0
  13. 33

    button.on(“click”, function(target, canceled) {
    // handle button click here
    });

    Wouldn’t this make more sense:

    button.on(“click”, function(e, target, canceled) {
    // handle button click here
    });

    or is there another way to gain access to the event object?

    Allowing access to the original event object means that the user can easily check control combinations (shift, ctrl, alt, right/left button, etc). Has this been considered?

    Great article – thanks!

    0
    • 34

      Maksim Chemerisuk

      January 15, 2014 8:33 am

      I want to drop the event object from arguments to improve code testability. You can still access all properties by providing an extra array argument with property names that you want to use in your event handler:

      var handleKeyDown = function(which, shiftKey) {
      // use which to determine key code, or shiftKey to check if shift was pressed
      };

      input.on(“keydown”, handleKeyDown, ["which", "shiftKey"]);

      Such approach could take some time to adopt but you’ll understand the advantage when start to write tests for your widget/component.

      0
  14. 35

    This article is testament to the need to build a ten wheeled bicycle.

    1
  15. 36

    There are ways to handle multi lingual interfaces, it belongs to content layer (resources) that is drvien by presentation layer… Css is not the right place to manage language.

    0
    • 37

      Maksim Chemerisuk

      January 18, 2014 8:13 am

      I’d like to make an important note here: the i18n solution doesn’t aim to solve the multilanguage problem for a whole web page. It should be used in small independent widgets or components. For example, common validity messages for a form validation polyfill, date picker labels etc.

      0
  16. 38

    It sounds like you have some misconceptions about properties and attributes. It seems pretty clear to me why it would be useful to represent characteristics of DOM elements as objects other than strings (which is all you get with attributes). For example:

    - “checked” and other Boolean attributes. The property could not be more simple: it is true or false. There exists much confusion about the correct attribute equivalent for a true value: is it “true” (no), “” (no), “checked” (yes, but not in old IE). Also, the “checked” property and “checked” attribute are not equivalent: the property that corresponds to the “checked” attribute is “defaultChecked”. Same for “value” and “defaultValue”, and “selected” and “defaultSelected”.

    - “style”. An element’s style property is an object with individual properties corresponding to CSS style properties. This allows direct querying and setting of an individual style property without having to parse or assemble a string containing all the element’s style properties, which is clearly superior.

    - Numeric properties such as size, width, height. It’s convenient to deal with these as numbers rather than strings.

    The distinction between properties and attributes is really important. I touched on reasons why above in the paragraph on Boolean attributes. jQuery used to think as you do and had a single attr() method that provided an unholy mixture of property and attribute access, but eventually in 1.6 introduced prop() that went some way towards acknowledging their initial mistake.

    0
    • 39

      Maksim Chemerisuk

      January 20, 2014 7:56 am

      I think you misunderstood the idea of getter and setter: they use *properties* at first and then fallback to attributes. Therefore:

      var input = DOM.create(“input[type=checkbox]“);

      typeof input.get(“checked”); // => “boolean”
      typeof input.get(“style”); // => “object”
      typeof input.get(“tabIndex”); // => “number”

      The mistake of jQuery was that they chosen a confusing method name (attr). I do believe that in typical situations you need properties, not attributes (they are faster too).

      Anyway you can always access attributes via input.get(“attributes”) which returns a native array of Attr objects.

      0
  17. 40

    VALUE OF THE [] OPERATOR

    You dont have to wrap with $(), jQuery has .eq function. Or is it the same under the hood?
    http://api.jquery.com/eq/

    0
    • 41

      Maksim Chemerisuk

      January 20, 2014 8:10 am

      Yeah, I’m aware of the eq method. The problem of jQuery’s approach that the value of the [] operator forces you to call $() when you use a functional method. Compare:
      jqLinks.each(function(i, el) { console.log( $(el).html() ) });
      domLinks.each(function(el) { console.log( el.get() ) });

      This is also annoying when you use a 3d party library:
      _.find(jqLinks, function(el) { return $(el).prop(“title”) === “stop” });
      _.find(domLinks, function(el) { return el.get(“title”) === “stop” });

      Notice that in case of better-dom I’m just using the *el* variable without wrapping it.

      0
  18. 42

    Sylvain Pollet-Villard

    January 18, 2014 10:55 am

    This library looks like a fantastic alternative to jQuery. I’ll try it on my next project. There are plenty of good ideas, but two of them seemed wrong in my opinion :

    - This legacy method is even uglier than jQuery.get. Why pass a function as an argument ? I know that extending native DOM Objects is a bad idea and the biggest mistake Prototype.js has done. But what about checking native Element properties and methods when these do not match anything in your better-dom-object ? This does not fix the Hammer issue though. I hope you’l find a safe way to reduce this abstraction gap between native and better DOM objects. This is what gave me many troubles with jQuery too.

    - I don’t see the point of your internationalization helpers. Why would anyone want to change the language of a small portion of a document, or even a single element? That does not make any sense. Also, I can not imagine declaring all the labels in all the languages supported on the client side. Without JS templating the whole document and outsourcing language files, it seems to me inconceivable to put internationalization on the client. And :before pseudo-elements everywhere, really ? Doubling number of nodes ? What if I already use :before on my label ? I suggest you separate these helpers from the rest of the project, since they solve too few cases.

    0
    • 43

      Maksim Chemerisuk

      January 20, 2014 8:23 am

      Hey Sylvain!

      1. the legacy method accepts a function because of safety: you may call it on a single element, collection and an empty node. No need to do a null or length checks. In better-dom you should use native elements in a very rare cases, like described in the article. Why? They are unsafe and buggy.

      2. Changing language of a part on page could be useful, for instance to see a preview of something in a different language.
      Also I agree that i18n should not be used for a whole page, take a look at my comment: http://coding.smashingmagazine.com/2014/01/13/better-javascript-library-for-the-dom/#comment-998565

      0
      • 44

        Sylvain Pollet-Villard

        January 23, 2014 5:04 am

        1. I though separating find and findAll would have prevented the developer to ask if it is an element or a collection. If I want to work with potentially multiple or non-existent native elements, my first instinct would be to use findAll and .each so that we retrieve the function wrapper safety ; without imposing another closure for single elements *that we know they’re here*.

        2. Do you plan to make a custom build script so that we can select which modules we want to include ?

        0
        • 45

          Maksim Chemerisuk

          January 23, 2014 9:35 am

          1. Correct, just use `legacy` instead of `each` if you need to access native elements. `legacy` is the same as `each` except it has one extra argument:
          DOM.findAll(“a”).legacy(function(native, el, index, collection) { … });

          2. With browserify it’s very easy to compile a custom build. I just need to investigate what kind of requirements do people have and verify dependencies.

          0
  19. 46

    sebastian kerckhof

    January 20, 2014 2:21 am

    How does the concept of live extensions compare to angularjs fixtures?

    0
    • 47

      Maksim Chemerisuk

      January 20, 2014 8:25 am

      not sure what you mean

      0
      • 48

        sebastian kerckhof

        January 20, 2014 2:21 pm

        I’m sorry, I meant to say angularjs directives, which try to solve the same problem.

        0
        • 49

          Maksim Chemerisuk

          January 21, 2014 3:49 am

          A live extension encapsulates a widget or component on your web page. It’s not a MV* framework, although better-dom APIs could be used to create it. I’m big fan of rendering HTML on server-side, so created better-ajaxify (https://github.com/chemerisuk/better-ajaxify) which is just a simple PAJAX solution (again, not a MV* framework).

          The biggest advantage of live extensions is that they could be combined with any existing framework in market – plain jQuery, Backbone, Angular, Ember etc.

          0
  20. 50

    JQuery is very great. But I use dojo for most everything. I use JQuery less than i use dojotoolkit, though. Dojo is full blown. I use Jquery to cover any gaps if or when necessary.

    0
  21. 51

    I loved this article and I congratulate you on trying to do things in a different way.
    I am currently investigating moving to this lib for my project which has a need to lots of micro components.

    One thing I am struggling with is “defaults”.
    For example: I’d like to build a currency input component, this component will have a different regions / locale / formatting based on the installation so I’d like to set the component’s region globally at page load from a single place.
    When calling DOM.extend the passed “mixins” object is lost unless there is an element on the page to retrieve it.
    What I’d like is a way to retrieve the static object declared as the components class definition, or the “mixins”.

    There are loads of ways I could solve this problem using standard javascript but that makes using the component, for other developers, non-standard. The reason I like this lib so much is there is a structured way of doing things that is easy to understand and clear but falls short at my “defaults” requirement.

    Please let me know if there is already a way of solving this or if you have any thoughts on my requirement.

    Thanks :)

    0
    • 52

      Is there a way to find a “DOM” element using a a native dom element?
      i.e. something like: DOM.find($(“#div1″)[0]);

      0
    • 53

      Maksim Chemerisuk

      March 31, 2014 12:31 pm

      Thanks, Jonathan.

      I see several options to solve your problem. They depend on reqs, so

      1) Define a global settings in the `DOM` namespace, e.g. `DOM.currencyFormat` or smth similar. This approach makes sense if you have several different extensions that use this global settings object.

      2) Otherwise I’d recommend to do the second `DOM.extend` call and pass settings into a element’s private property:

      DOM.extend(“.currency-input”, {
      // implementation of the currency control
      // you should read “_currencyFormat” to access
      // the currency settings object
      });

      DOM.extend(“.currency-input”, {
      constructor: function() {
      this.set(“_currencyFormat”, …);
      }
      });

      Let me know if it solves your problem.

      NOTE: I use the latest better-dom syntax here (method `data` is deprecated, so `set` is used instead).

      > Is there a way to find a “DOM” element using a a native dom element?
      Just call `DOM.create($(“#div1″)[0])`

      0

Leave a Comment

Yay! You've decided to leave a comment. That's fantastic! Please keep in mind that comments are moderated and rel="nofollow" is in use. So, please do not use a spammy keyword or a domain as your name, or else it will be deleted. Let's have a personal and meaningful conversation instead. Thanks for dropping by!

↑ Back to top