Servage Magazine

Information about YOUR hosting company – where we give you a clear picture of what we think and do!

Polygonal modeling and Three.js – Part 1

Tuesday, April 1st, 2014 by Servage

javascriptNewFunction1-6

The * selector has a special behavior: all extension declaration properties will be injected directly into the element wrapper prototype except for the constructor which is totally ignored. Therefore, there is no performance penalty that is usually associated with the universal selector.

Note: Never pass more specific selectors such as .some-class * into DOM.extend because they are slow and do not have the same behavior as mentioned above.

MULTIPLE LIVE EXTENSIONS ON THE SAME ELEMENT

More often than not, it makes sense to split a large live extension into several pieces to reduce complexity. For instance, you may have such an element on your page:

<div></div>

There are two different extensions attached to it. The .infinite-scrollextension implements a well-known infinite scroll pattern, e.g. it’s responsible for loading new content. At the same time, the .chat extension shows tooltips whenever a user hovers over a userpic, adds smileys into messages, and so on. However, be accurate with multiple extensions: even though all event handlers may have been removed from the interface, you still may have public methods that intersect with each other.

INHERITANCE

Live extensions respect declaration order; you can use this to your advantage and develop your own component hierarchy. Late binding helps to declare override-able event handlers and method overloading allows to redefine a method implementation in a child extension:

DOM.extend(".my-widget", {
  constructor: function() {
    this.on("click", "_handleClick");
  },
  showMessage: function() { }
});

DOM.extend(".my-button", {
  _handleClick: function() {
    console.log("I am a button!");
  },
  showMessage: function() {
    alert("I am a button message!");
  }
});

If you take a closer look at the code above, you’ll notice that the .my-buttonextension does not attach a click listener. The registration is done with the help of late binding instead of a simple event handler in .my-widget. Late binding is a perfect choice here: even if a child does not implement _handleClick there won’t be any errors since the handler will be silently ignored.

While spreading functionality across multiple modules is possible, this is not recommended in everyday use. Double check if you really need to go in this direction, because it’s the most complex one.

WRITING TESTS WITH DOM.MOCK

One requirement for a high-quality widget is test coverage. New elements are captured by a live extension asynchronously, so it’s not that easy to simply make them in memory. To solve this problem, better-dom has the DOM.mockfunction:

var myButton = DOM.mock(“button.my-button”);

DOM.mock creates elements, just like DOM.create. Additionally, it synchronously applies the registered live extensions to the newly created elements. For even more convenience, all wrapper objects created by DOM.mock preserve event handlers (e.g. onClick), so you can test them.

From time to time, you may need to create a “fake” instance of an element. UseDOM.mock without arguments to make such an object:

console.log(DOM.mock().length); // => 0

A test for the modal dialog live extension introduced earlier could look like this (I use Jasmine):

describe(".modal-dlg", function() {
  var dlg, backdrop;

  beforeEach(function() {
    dlg = DOM.mock("div.modal-dlg");
    backdrop = DOM.mock();
  });

 

  it("should hide itself and backdrop on close", function() {
    var dlgSpy = spyOn(dlg, "hide"),
      backdropSpy = spyOn(backdrop, "hide");

    dlg.onClose(backdrop);
    expect(dlgSpy).toHaveBeenCalled();
    expect(backdropSpy).toHaveBeenCalled();
  });

  it("should show itself and backdrop on show", function() {
    var dlgSpy = spyOn(dlg, "show"),
      backdropSpy = spyOn(backdrop, "show");

    dlg.showModal(backdrop);
    expect(dlgSpy).toHaveBeenCalled();
    expect(backdropSpy).toHaveBeenCalled();
  });
});

FEATURE DETECTION (IN BETTER-DOM 1.7)

There are some cases when filtering with a CSS selector is not flexible enough. For instance, let’s say you want to declare a live extension but only for browsers that support (or do not support) a particular feature. You may need to run tests in a headless browser like PhantomJS that support the feature natively. Starting with better-dom 1.7, DOM.extend supports the optional argument condition.

Assume we need to create a polyfill for the placeholder attribute. It doesn’t make sense to implement it for browsers that have built-in support. Below is an example of how the feature detection could look like:

var supportsPlaceholder = typeof DOM.create(“input”)

      .get("placeholder") === "string";

By using just a simple “If” statement as shown in the example below, we won’t have an ability to test the widget because PhantomJS supports the placeholderattribute and the live extension will never be declared.

if (!supportsPlaceholder) {
  DOM.extend("[placeholder]", {
    // implement placeholder support
  };
}

In order to solve this problem, you can use an extra condition argument inDOM.extend that might be Boolean or a function:

DOM.extend("[placeholder]", !supportsPlaceholder, {
  constructor: function() { … },
  onFocus: function() { … },
  onBlur: function() { … }
});

DOM.mock ignores the condition argument, so you can access all methods of the[placeholder] extension even if current browser passes the check:

var input = DOM.mock(“input[placeholder=test]“);

 

typeof input.onFocus; // => “function”

Conclusion

Live extensions — and better-dom as an implementation of the concept — are a good base to build upon whenever your target is uncertain, e.g. when creating a polyfill that may or may not be used on a particular site. Or regular widgets that may or may not be needed, depending upon some AJAX call.

Live extensions aim to separate declaration and the use of widgets. They bring loose coupling (or decoupling, rather) of any DOM-based component, and allow your code to become smaller, cleaner and easier to maintain. You can even combine such independent pieces with any existing framework within the market (or with the vanilla DOM, of course).

You may now be thinking, “But wait, there are projects like Polymer or x-tags, right?” Well, live extensions cover a different area; they are not about custom tags but rather about extending existing ones instead. I prefer a standards-based way (if possible) of creating UI widgets, so making polyfills is my choice.

Better-dom also has another advantage: a carefully crafted live extension does not force you to rewrite a website’s markup using different tags. All you need is to simply include a script file on your page. Standards-based elements can potentially work without JavaScript, so they degrade well when it’s disabled. And the library’s browser support allows you to start using live extensions straight away.


Polygonal modeling and Three.js – Part 1, 5.0 out of 5 based on 1 rating
You can follow any responses to this entry through the RSS 2.0 feed. You can leave a response, or trackback from your own site.

No comments yet (leave a comment)

You are welcome to initiate a conversation about this blog entry.

Leave a comment

You must be logged in to post a comment.