Chapter 2. Practical Dart

This chapter will focus on the most common features of Dart that you'll use every day for your next Dart project. In this chapter, we'll look at:

  • Manipulating DOM and HTML elements
  • Future-Based API, Dart's built-in library for working with asynchronous calls
  • Creating Ajax requests in Dart
  • How packages work in Dart
  • Calling JavaScript from Dart
  • Calling Dart from JavaScript

The whole chapter is intended to be very practical. We'll create a small app that reads a JSON dictionary and lets you search among all the terms in it. To make it more complicated, we'll implement a so-called fuzzy search algorithm, which doesn't search exact matches but the same order of characters instead.

Optimizing DOM tree manipulations

We've already seen some basic manipulation with HTML elements in Chapter 1, Getting Started with Dart, and we'll make a few notes about it here.

The Dart library wraps standard JavaScript element classes and methods with more unified names. Classes that represent HTML elements don't start with the HTML prefix, so for example, HTMLDivElement is called DivElement in Dart. Take a look at this JavaScript snippet:

elm.addEventListener('client', function() { /* ... */ });

Instead of binding event listeners with the preceding code, Dart uses the so-called Stream API, where you don't bind listeners directly to the element you subscribe but to a Stream object instead, which emits all events. Each HTML element has all the default event emitters, such as onClick or onKeyPress:

elm.onClick.listen((e) { /* ... */ });

The listen()method returns a StreamSubscription object that can be used to unsubscribe the listener.

Streams have many methods that you probably won't use so often, but take a look at Dart's API for what's available. One of these methods is the where() method, which is declared as:

Stream<T> where(Function bool test(T event));

Typical usage could be, for example:

document.onClick.where((Event e) => e.target.id == 'myButton'),

This method creates a new Stream object that only emits events that pass the test function. This is useful, for example, when you only want to listen to a small subset of events that can be emitted by this listener. For example, you want to use onClick on the entire document and create special streams when the user clicks on a <div> element and another stream for <p> elements. Without where(), you would have to use one callback for all onClick events. With where(), you can make separate streams that let you or other developers bind specifically to them.

Another useful feature of the DOM is DocumentFragment. Manipulating with a lot of HTML elements, such as appending, removing, resizing, changing text content, and basically, any operation that visually changes the page, causes the browser to create "reflow". This is a browser procedure that recalculates the position of all the elements on the page in order to re-render the page.

In practice, you usually don't need to deal with this, but pages that contain thousands, or tens of thousands, of elements can be unresponsive for a short time. A good way to optimize this is to make changes to the DOM in bulks. For example, it's not recommended that you do this:

UlistElement cont = querySelector('#hello-ul'),
cont.children.clear();
list.forEach((String key) {
    LIElement li = new LIElement();
    li.text = key;
    cont.append(li);
});

Each call to append() will make the browser run the reflow. However, browsers are well optimized these days and they would probably run just one reflow at the end because none of the statements in the forEach() loop need to work with positions or sizes. Try to add just a single innocent statement that does nothing right after cont.append(li); such as cont.scrollTop;. We know that appending <li> elements shouldn't change its scrollTop property but the browser doesn't know this and it has to do a reflow for every element in the list. We can analyze this in Dartium with Developer Tools in the Timeline tab.

Optimizing DOM tree manipulations

This is a sneak peak from the app we'll create in this chapter. Appending 346 elements took about 150 ms. This applies even when the parent element is hidden.

There's a better way to do this with DocumentFragments:

UlistElement cont = querySelector('#hello-ul'),
var frag = new DocumentFragment();
list.forEach((String key) {
    LIElement li = new LIElement();
    li.text = key;
    frag.append(li);
});
cont.children.clear();
cont.append(frag.clone(true));

This code has the same functionality as the previous but this time, we create DocumentFragment, which is a DOM tree itself but is not placed in the document. We add multiple elements to it and then append its entire content to the <ul> element. Take a look at the following screenshot:

Optimizing DOM tree manipulations

With DocumentFragment, creating and appending 346 elements took about 50 ms because it took just one reflow and spent most of the time by creating new instances of LIElement. Note that the Dartium browser works with the Dart code and events just like Chrome works with JavaScript. At first sight, you wouldn't even notice that the browser wasn't interpreting JavaScript.

The caveat here is that most of the time, we use third-party libraries and we have no idea what's going on inside them, so you can't rely on the browser's prediction whether or not it should run the reflow. DocumentFragments aren't new to HTML but at the same time, they aren't used by many developers even when their usage is very simple.

A common usage is also when you already have an existing DOM subtree that you want to modify multiple times. You can clone just the small subtree to DocumentFragment, modify it, and replace the original subtree.

Rules for minimizing reflows apply to everything that needs to work with position or size. Another example could be:

innerElement.style.width = '100px';
print(cont.clientWidth);
innerElement.style.height = '200px';
print(cont.clientHeight);

This calls two reflows because you set the width first and then access some elements' size, which causes the first reflow, and then the same for height, which causes the second reflow. We can rewrite this as the following, which creates just one reflow:

innerElement.style.width = '100px';
innerElement.style.height = '200px';
print(cont.clientWidth);
print(cont.clientHeight);

We'll see both the Stream API and document fragments in action right now.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.165.62