12 min read

In this article by Sergey Akopkokhyants, author of Mastering Dart, we will combine the simplicity of jQuery and the power of Dart in a real example.

(For more resources related to this topic, see here.)

Integrating Dart with jQuery

For demonstration purposes, we have 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 JProxy needs a method call?

Usually, jQuery send 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)

Dart has 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 JProxy invokes 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 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 may 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 yet. 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 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:

Mastering Dart

How to use effects in jQuery?

The jQuery supports animation out of the box, so it sounds very tempting to use it from Dart. Let’s take an example of 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 article to discover all its features, but you can find more examples at https://github.com/akserg/js_proxy.

What are the performance impacts?

Now, we should talk about the performance impacts 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 this function must be investigated. 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 solution (four to ten times).

Summary

This article showed you how to use Dart and JavaScript together to build web applications. It listed problems and solutions you can use to communicate between Dart and JavaScript and the existing JavaScript program. We compared jQuery, JProxy, and dart:js and cleared the Dart code based on the dart:html solutions to identify who is quicker than others.

Resources for Article:


Further resources on this subject:


LEAVE A REPLY

Please enter your comment!
Please enter your name here