





















































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.)
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;
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.
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)); }
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.
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:
In general, you may copy the JavaScript code, paste it in the Dart code, and probably make slightly small changes.
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.
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:
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.
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:
This algorithm must be implemented in the following solutions:
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).
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: