Dart with jQuery

There is no doubt that jQuery has become very popular among developers because of its simplicity. Let's try to combine the simplicity of jQuery and the power of Dart in a real example. For demonstration purposes, we created the js_proxy package to help the Dart code to communicate with jQuery. It is available on the pub manager at https://pub.dartlang.org/packages/js_proxy. This package is layered on dart:js and has a library of the same name and sole class JProxy. An instance of the JProxy class can be created via the generative constructor where we can specify the optional reference on the proxied JsObject:

JProxy([this._object]);

We can create an instance of JProxy with a named constructor and provide the name of the JavaScript object accessible through the dart:js context as follows:

JProxy.fromContext(String name) {
  _object = js.context[name];
  }

The JProxy instance keeps the reference on the proxied JsObject class and makes all the manipulation on it, as shown in the following code:

js.JsObject _object;
js.JsObject get object => _object;

How to create a shortcut to jQuery

We can use JProxy to create a reference to jQuery via the context from the dart:js library as follows:

var jquery = new JProxy.fromContext('jQuery'),

Another very popular way is to use the dollar sign as a shortcut to the jQuery variable as shown in the following code:

var $ = new JProxy.fromContext('jQuery'),

Bear in mind that the original jQuery and $ variables from JavaScript are functions, so our variables reference to the JsFunction class. From now, jQuery lovers who moved to Dart have a chance to use both the syntax to work with selectors via parentheses.

Why does JProxy need a method call?

Usually, jQuery sends a request to select HTML elements based on IDs, classes, types, attributes, and values of their attributes or their combination, and then performs some action on the results. We can use the basic syntax to pass the search criteria in the jQuery or $ function to select the HTML elements:

$(selector)

As mentioned in Chapter 3, Object Creation, Dart has a syntactic sugar method, call, that helps us to emulate a function and we can use the call method in the jQuery syntax. Dart knows nothing about the number of arguments passing through the function, so we use the fixed number of optional arguments in the call method. Through this method, we invoke the proxied function (because jquery and $ are functions) and returns results within JProxy:

dynamic call([arg0 = null, arg1 = null, arg2 = null, 
    arg3 = null, arg4 = null, arg5 = null, arg6 = null, 
    arg7 = null, arg8 = null, arg9 = null]) {
  var args = [];
  if (arg0 != null) args.add(arg0);
  if (arg1 != null) args.add(arg1);
  if (arg2 != null) args.add(arg2);
  if (arg3 != null) args.add(arg3);
  if (arg4 != null) args.add(arg4);
  if (arg5 != null) args.add(arg5);
  if (arg6 != null) args.add(arg6);
  if (arg7 != null) args.add(arg7);
  if (arg8 != null) args.add(arg8);
  if (arg9 != null) args.add(arg9);
  return _proxify((_object as js.JsFunction).apply(args));
}

How does JProxy invoke jQuery?

The JProxy class is a proxy to other classes, so it marks with the @proxy annotation. We override noSuchMethod intentionally to call the proxied methods and properties of jQuery when the methods or properties of the proxy are invoked. The logic flow in noSuchMethod is pretty straightforward. It invokes callMethod of the proxied JsObject when we invoke the method on proxy, or returns a value of property of the proxied object if we call the corresponding operation on proxy. The code is as follows:

@override
dynamic noSuchMethod(Invocation invocation) {
  if (invocation.isMethod) {
    return _proxify(_object.callMethod(
      symbolAsString(invocation.memberName), 
      _jsify(invocation.positionalArguments)));
  } else if (invocation.isGetter) {
    return 
      _proxify(_object[symbolAsString(invocation.memberName)]);
  } else if (invocation.isSetter) {
    throw new Exception('The setter feature was not implemented 
      yet.'),
  }
  return super.noSuchMethod(invocation);
}

As you might remember, all Map or Iterable arguments must be converted to JsObject with the help of the jsify method. In our case, we call the _jsify method to check and convert passed arguments aligned with a called function, as shown in the following code:

List _jsify(List params) {
  List res = [];
  params.forEach((item) {
    if (item is Map || item is List) {
      res.add(new js.JsObject.jsify(item));
    } else {
      res.add(item);
    }
  });
  return res;
  }

Before return, the result must be passed through the _proxify function as follows:

dynamic _proxify(value) {
    return value is js.JsObject ? new JProxy(value) : value;
}

This function wraps all JsObject classes within a JProxy class and passes other values as it is.

An example project

Now create the jquery project, open the pubspec.yaml file, and add js_proxy to the dependencies. Open the jquery.html file and make the following changes:

<!DOCTYPE html>

<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" 
      content="width=device-width, initial-scale=1">
    <title>jQuery</title>
    
    <link rel="stylesheet" href="jquery.css">
  </head>
  <body>
    <h1>Jquery</h1>
    
    <p>I'm a paragraph</p>
   	<p>Click on me to hide</p>
   	<button>Click me</button>
   	<div class="container">
          <div class="box"></div>
   </div>

  </body>

  <script src="//code.jquery.com/jquery-1.11.0.min.js"></script>
    
  <script type="application/dart" src="jquery.dart"></script>
  <script src="packages/browser/dart.js"></script>
</html>

This project aims to demonstrate that:

  • Communication is easy between Dart and JavaScript
  • The syntax of the Dart code could be similar to the jQuery code

In general, you can copy the JavaScript code, paste it in the Dart code, and probably make slightly small changes.

How to get the jQuery version

It's time to add js_proxy in our code. Open jquery.dart and make the following changes:

import 'dart:html';
import 'package:js_proxy/js_proxy.dart';

/**
 * Shortcut for jQuery.
 */
var $ = new JProxy.fromContext('jQuery'),

/**
 * Shortcut for browser console object.
 */
var console = window.console;

main() {
  printVersion();
}

/**
 * jQuery code:
 * 
 *   var ver = $().jquery;
 *   console.log("jQuery version is " + ver);
 * 
 * JS_Proxy based analog:
 */
printVersion() {
  var ver = $().jquery;
  console.log("jQuery version is " + ver);
}

You should be familiar with jQuery and console shortcuts by now. The call to jQuery with empty parentheses returns JProxy and contains JsObject with reference to jQuery from JavaScript. The jQuery object has a jquery property that contains the current version number, so we reach this one via noSuchMethod of JProxy. Run the application, and you will see the following result in the console:

jQuery version is 1.11.1

Let's move on and perform some actions on the selected HTML elements.

How to perform actions in jQuery

The syntax of jQuery is based on selecting the HTML elements and it also performs some actions on them:

$(selector).action();

Let's select a button on the HTML page and fire the click event as shown in the following code:

/**
 * jQuery code:
 * 
 *   $("button").click(function(){
 *     alert('You click on button'),
 *   });
 * 
 * JS_Proxy based analog:
 */
events() {
  // We remove 'function' and add 'event' here
  $("button").click((event) {
    // Call method 'alert' of 'window'
    window.alert('You click on button'),
  });
}

All we need to do here is just remove the function keyword, because anonymous functions on Dart do not use it, and then add the event parameter. This is because this argument is required in the Dart version of the event listener. The code calls jQuery to find all the HTML button elements to add the click event listener to each of them. So when we click on any button, a specified alert message will be displayed. On running the application, you will see the following message:

How to perform actions in jQuery

How to use effects in jQuery

The jQuery supports animation out of the box, so it sounds very tempting to use it in Dart. Let's take a look at the following code snippet:

/**
 * jQuery code:
 * 
 *   $("p").click(function() {
 *     this.hide("slow",function(){
 *       alert("The paragraph is now hidden");
 *     });
 *   });
 *   $(".box").click(function(){
 *     var box = this;
 *     startAnimation();
 *     function startAnimation(){
 *       box.animate({height:300},"slow");
 *       box.animate({width:300},"slow");
 *       box.css("background-color","blue");  
 *       box.animate({height:100},"slow");
 *       box.animate({width:100},"slow",startAnimation);
 *     }
 *   }); 
 * 
 * JS_Proxy based analog:
 */
effects() {
  $("p").click((event) {
    $(event['target']).hide("slow",(){
      window.alert("The paragraph is now hidden");
    });
  });
  $(".box").click((event) {
    var box = $(event['target']); 
    startAnimation() {
      box.animate({'height':300},"slow");
      box.animate({'width':300},"slow");
      box.css("background-color","blue");  
      box.animate({'height':100},"slow");
      box.animate({'width':100},"slow",startAnimation);
    };
    startAnimation();
  });
}

This code finds all the paragraphs on the web page to add a click event listener to each one. The JavaScript code uses the this keyword as a reference to the selected paragraph to start the hiding animation. The this keyword has a different notion on JavaScript and Dart, so we cannot use it directly in anonymous functions on Dart. The target property of event keeps the reference to the clicked element and presents JsObject in Dart. We wrap the clicked element to return a JProxy instance and use it to call the hide method.

The jQuery is big enough and we have no space in this book to discover all its features, but you can find more examples at https://github.com/akserg/js_proxy.

What is the impact on performance?

Now we should talk about the performance impact of using different approaches across several modern web browsers. The algorithm must perform all the following actions:

  • It should create 10000 DIV elements
  • Each element should be added into the same DIV container
  • Each element should be updated with one style
  • All elements must be removed one by one

This algorithm must be implemented in the following solutions:

  • The clear jQuery solution on JavaScript
  • The jQuery solution calling via JProxy and dart:js from Dart
  • The clear Dart solution based on dart:html

We implemented this algorithm on all of them, so we have a chance to compare the results and choose the champion. The following HTML code has three buttons to run independent tests, three paragraph elements to show the results of the tests, and one DIV element used as a container. The code is as follows:

<div>    
  <button id="run_js" onclick="run_js_test()">Run JS</button>
  <button id="run_jproxy">Run JProxy</button>
  <button id="run_dart">Run Dart</button>
</div>
    
<p id="result_js"></p>
<p id="result_jproxy"></p>
<p id="result_dart"></p>

<div id="container"></div>

The JavaScript code based on jQuery is as follows:

function run_js_test() {
  var startTime = new Date();
  process_js();
  var diff = new Date(new Date().getTime() – 
    startTime.getTime()).getTime();
  $('#result_js').text('jQuery tooks ' + diff + 
    ' ms to process 10000 HTML elements.'),
}
function process_js() {
  var container = $('#container'),
  // Create 10000 DIV elements
  for (var i = 0; i < 10000; i++) {
    $('<div>Test</div>').appendTo(container);
  }
  // Find and update classes of all DIV elements
  $('#container > div').css("color","red");
  // Remove all DIV elements
  $('#container > div').remove();
}

The main code registers the click event listeners and the call function run_dart_js_test. The first parameter of the run_dart_js_test function must be a function which we will investigate. The second and third parameters are used to pass the selector of the result element and test the title:

void main() {
  querySelector('#run_jproxy').onClick.listen((event) {
    run_dart_js_test(process_jproxy, '#result_jproxy', 'JProxy'),
  });
  querySelector('#run_dart').onClick.listen((event) {
    run_dart_js_test(process_dart, '#result_dart', 'Dart'),
  });
}

run_dart_js_test(Function fun, String el, String title) {
  var startTime = new DateTime.now();
  fun();
  var  diff = new DateTime.now().difference(startTime);
  querySelector(el).text = '$title tooks ${diff.inMilliseconds} ms 
    to process 10000 HTML elements.';
}

Here is the Dart solution based on JProxy and dart:js:

process_jproxy() {
  var container = $('#container'),
  // Create 10000 DIV elements
  for (var i = 0; i < 10000; i++) {
    $('<div>Test</div>').appendTo(container.object);
  }
  // Find and update classes of all DIV elements
  $('#container > div').css("color","red");
  // Remove all DIV elements
  $('#container > div').remove();
}

Finally, a clear Dart solution based on dart:html is as follows:

process_dart() {
  // Create 10000 DIV elements
  var container = querySelector('#container'),
  for (var i = 0; i < 10000; i++) {
    container.appendHtml('<div>Test</div>'),
  }
  // Find and update classes of all DIV elements
  querySelectorAll('#container > div').forEach((Element el) {
    el.style.color = 'red';
  });
  // Remove all DIV elements
  querySelectorAll('#container > div').forEach((Element el) {
    el.remove();
  });
}

All the results are in milliseconds. Run the application and wait until the web page is fully loaded. Run each test by clicking on the appropriate button. My result of the tests on Dartium, Chrome, Firefox, and Internet Explorer are shown in the following table:

Web browser

jQuery framework

jQuery via JProxy

Library dart:html

Dartium

2173

3156

714

Chrome

2935

6512

795

Firefox

2485

5787

582

Internet Explorer

12262

17748

2956

Now, we have the absolute champion—the Dart-based solution. Even the Dart code compiled in the JavaScript code to be executed in Chrome, Firefox, and Internet Explorer works quicker than jQuery (four to five times) and much quicker than dart:js and JProxy class-based solutions (four to ten times).

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

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