Last modified by Vincent Massol on 2024/11/19 16:14

Hide last authors
Vincent Massol 1.1 1 Currently XWiki is still using prototype as its JS framework of choice. Extensions can be written using jquery.
2
3 Prototype has 2 limitations for us:
Vincent Massol 2.1 4
Vincent Massol 1.1 5 * It's now dead
6 * It's not enough to build modern web UIs
7
8 Possible list of JS MVC frameworks to check out:
Vincent Massol 2.1 9
Vincent Massol 7.1 10 * AngularJS (also check [[Angular 2.0>>http://blog.angularjs.org/2014/03/angular-20.html]] which is mobile first)
Vincent Massol 1.1 11 * EmberJS
12 * DurandalJS
13 * BackboneJS
Vincent Massol 1.2 14 * KnockoutJS
Vincent Massol 1.1 15
Vincent Massol 14.1 16 Also note that ShadowDOM (part of "Web Components") which allows creating new HTML elements) is a competitor to AngularJS's directives but [[they are actually compatible>>http://www.binpress.com/blog/2014/06/26/polymer-vs-angular/]]. Thus in a near future we'll be able to use AngularJS which Web Components which is very nice.
17
Vincent Massol 1.1 18 Discussions found on the web comparing them:
Vincent Massol 2.1 19
Vincent Massol 1.1 20 * http://eviltrout.com/2013/06/15/ember-vs-angular.html
21 * http://www.quora.com/Client-side-MVC/Is-Angular-js-or-Ember-js-the-better-choice-for-Javascript-frameworks
Guillaume Delhumeau 8.1 22 * http://readwrite.com/2014/02/06/angular-backbone-ember-best-javascript-framework-for-you
Vincent Massol 1.1 23
Vincent Massol 2.1 24 Experiments inside XWiki:
Ecaterina Moraru (Valica) 4.1 25
Ecaterina Moraru (Valica) 15.1 26 * With EmberJS: [[http:~~/~~/extensions.xwiki.org/xwiki/bin/view/Extension/AngularJSDemo>>doc:extensions:Extension.AngularJSDemo]] (source: https://github.com/xwiki-contrib/application-emberjs-todolist and mail thread with explanations: http://markmail.org/message/ihg5t5qtbp5yfe6f)
Vincent Massol 2.1 27 * With AngularJS:
28 ** https://github.com/xwiki-contrib/angular-rendering
29 ** https://github.com/xwiki-contrib/application-angular-todo
30 ** https://github.com/xwiki-contrib/application-angularjsdemo
Ecaterina Moraru (Valica) 5.1 31
Caleb James DeLisle 13.1 32 == Github Stats ==
33
34 Angular comes out here as the clear winner, although backbone and ember should not be considered "risky" choices.
35
36 |=Framework|=Forks|=Contributors
37 |Angular|9627|916
38 |Backbone|4200|230
39 |Ember|2335|380
40 |Knockout|882|40
41 |Durandel|321|61
42
Marius Dumitru Florea 9.1 43 == AngularJS (1.x) ==
44
45 Can be loaded with RequireJS.
46
47 {{code language="JavaScript"}}
48 require.config({
49 paths: {
50 angular: '//ajax.googleapis.com/ajax/libs/angularjs/1.2.16/angular.min',
51 'angular-route': '//ajax.googleapis.com/ajax/libs/angularjs/1.2.16/angular-route.min',
52 'angular-resource': '//ajax.googleapis.com/ajax/libs/angularjs/1.2.16/angular-resource.min',
53 'angular-animate': '//ajax.googleapis.com/ajax/libs/angularjs/1.2.16/angular-animate.min'
54 },
55 shim: {
56 angular: {
57 exports: 'angular'
58 },
59 'angular-route': {
60 deps: ['angular'],
61 exports: 'angular'
62 },
63 'angular-resource': {
64 deps: ['angular'],
65 exports: 'angular'
66 },
67 'angular-animate': {
68 deps: ['angular'],
69 exports: 'angular'
70 }
71 }
72 });
73
74 require(['angular', 'angular-route', 'angular-resource', 'angular-animate'], function(angular) {
75 // Define a new module and specify its dependencies.
76 var myApp = angular.module('myApp', [
77 'ngRoute',
78 'myAppControllers',
79 'myAppFilters',
80 'myAppServices',
81 'myAppAnimations']);
82 ...
83 });
84 {{/code}}
85
86 We can load it also from a WebJar.
87
88 {{code language="JavaScript"}}
89 require(["$services.webjars.url('angularjs/1.2.16/angular.js')"], function() {
90 var myApp = angular.module('myApp', []);
91 ...
92 });
93 {{/code}}
94
95 It can initialize itself automatically on page load if it finds an element marked with the ##ng-app## attribute (the element that wraps the application UI):
96
97 {{code language="html"}}
98 <div ng-app="myApp">
99 ...
100 </div>
101 {{/code}}
102
103 but it can also be lazy loaded:
104
105 {{code language="JavaScript"}}
106 angular.bootstrap($('#myAppContainer')[0], ['myApp']);
107 {{/code}}
108
109 It has its own **Dependency Injection** mechanism but it injects dependencies that have already been loaded on the client. So it doesn't fetch scripts dynamically. This means we can use RequireJS to fetch Angular modules from the server dynamically and then use Angular's DI to inject them in our application.
110
111 Angular was designed for single-page applications. You have a layout template with an area (the view) that changes with the URL. Since the page is never reloaded, when the view is changed only the document fragment part of the URL is changed. This means the views are still bookmarkable (and Back/Forward buttons work). We can map URLs (actually document fragments) to views.
112
113 {{code language="JavaScript"}}
114 phoneApp.config(['$routeProvider', function($routeProvider) {
115 $routeProvider.when('/phones', {
116 templateUrl: new XWiki.Document('ListView', 'Phone').getURL('get'),
117 controller: 'PhoneListCtrl'
118 }).when('/phones/:phoneId', {
119 templateUrl: new XWiki.Document('DetailView', 'Phone').getURL('get'),
120 controller: 'PhoneDetailCtrl'
121 }).otherwise({
122 redirectTo: '/phones'
123 });
124 }]);
125 {{/code}}
126
127 The '/phone' part actually translates to ##xwiki/bin/view/Space/Page#/phones##. A small issue is that the usage of '$' (in ##$routeProvider##) can lead to problems if the JavaScript code is parsed for Velocity. An important limitation is that you can have only one view currently displayed (this may change in Angular 2.x). So we can't have sibling or nested views. The view is marked with the ##ng-view## empty attribute:
128
129 {{code language="html"}}
130 <!-- Here you have UI elements common to all views (this is the layout template). -->
131 <div ng-view>
132 <!-- The content displayed here depends on the current route (URL) -->
133 <!-- We can't have a view nested here! -->
134 </div>
135 <!-- We can't have a sibling view here! -->
136 {{/code}}
137
Vincent Massol 11.1 138 Views are defined by 'templates' that are evaluated on the client. The syntax used is HTML with custom ##ng*## attributes and a [[handle-bars ##~~{~~{value}}##>>http://handlebarsjs.com/]]-like syntax. We can write the Angular templates in wiki pages but the handle-bars notation clearly prevents us from using the wiki syntax. We have to use the HTML macro:
Marius Dumitru Florea 9.1 139
140 {{code language="none"}}
141 {{html}}
142 <ul class="phones">
143 <li ng-repeat="phone in phones | filter:query | orderBy:fieldToOrderBy:reverse"
144 class="thumbnail phone-listing">
145 <a href="#/phones/{{phone.id}}" class="thumb">
146 <img ng-src="{{phone.imageUrl}}"/>
147 </a>
148 <a href="#/phones/{{phone.id}}">{{phone.name}}</a>
149 <p>{{phone.snippet}}</p>
150 </li>
151 </ul>
152 {{/html}}
153 {{/code}}
154
Marius Dumitru Florea 10.1 155 Note that we can ensure strict (X)HTML5 validity by prefixing the custom Angular attributes with ##data-## (e.g. ##data-ng-src##). In a template we can display or interact with data from the **scope**. The scope is very similar to the Velocity context. Think how we can put all kinds of objects on the Velocity context and then access them from the Velocity templates. One difference is that in Angular we can have nested scopes which inherit from one another. Everything that is put on the scope is considered to be the **model**. So the model can be any type of data, from primitive types to complex objects. The JavaScript code that puts data on the scope is the **controller**. Each view has a controller for instance that decides what data is displayed by that view.
Marius Dumitru Florea 9.1 156
157 {{code}}
158 myApp.controller('todoCtrl', ['$scope', function($scope) {
159 $scope.todos = [
160 {
161 text: 'Learn the XWiki API',
162 done: true
163 }, {
164 text: 'Create an XWiki application',
165 done: false
166 }
167 ];
168 }]);
169 {{/code}}
170
171 There is a two-way binding between the model and the view. Angular listens to DOM events and updates the model whenever needed. Changes to the model done from within the Angular code (e.g. from the controller) are reflected automatically in the view. Angular detects which objects have been modified using what is called 'dirty-checking'. This aproach has some performance limitations when the model is big (lots of complex objects) but the Angular team has some fixes planed for 2.x.
172
Marius Dumitru Florea 10.1 173 There is a simple way to store the view state in the URL. In other words we can have bookmarkable views. This is needed for instance if we want to implement the live table using Angular. The live table state (filter values, sort column, sort order) is currently saved in the URL hash (document fragment). We can achieve the same with Angular, without interfering with the route path.
Marius Dumitru Florea 9.1 174
175 {{code language="html"}}
Marius Dumitru Florea 10.1 176 <label>Sort by:</label>
177 <select ng-model="sortColumn" ng-change="saveSortColumn()">
178 <option value="name">Alphabetical</option>
179 <option value="age">Newest</option>
180 </select>
181 {{/code}}
182
183 {{code language="JavaScript"}}
184 myApp.controller('MyAppCtrl', ['$scope', '$location', function ($scope, $location) {
185 $scope.sortColumn = $location.search().sortColumn || 'age';
186 $scope.saveSortColumn = function() {
187 $location.search('sortColumn', $scope.sortColumn);
188 };
189 }]);
190 {{/code}}
191
192 {{code language="none"}}
193 /xwiki/bin/view/Space/Page#/phones?sortColumn=name
194 {{/code}}
195
196 The declaration of behaviour directly in HTML (##ng-click##, ##ng-change##) doesn't look very good though. Our practice has been to avoid in-line JavaScript code and to use behavioural CSS classes (e.g. ##withTip##). Fortunately we can implement them using directives (e.g. a custom attribute that adds behaviour to an element).
197
198 Angular offers a nice mechanism to control how data is displayed, called **filters**. This is in some ways similar to our property displayers.
199
200 {{code language="html"}}
Marius Dumitru Florea 9.1 201 <dd>{{phone.connectivity.gps | checkmark}}</dd>
202 {{/code}}
203
204 {{code language="JavaScript"}}
205 myApp.filter('checkmark', function() {
206 return function(input) {
207 return input ? '\u2713' : '\u2718';
208 };
209 });
210 {{/code}}
211
Marius Dumitru Florea 10.1 212 We can package and expose business logic as **services**. Angular comes with a bunch of useful services, like ##$http## which can be used to make AJAX requests. We can use the ##$resource## service to wrap (query/post) REST resources or anything that generates JSON on the server (a wiki page for instance).
Marius Dumitru Florea 9.1 213
Marius Dumitru Florea 10.1 214 {{code language="JavaScript"}}
215 myApp.factory('Page', ['$resource', function($resource) {
216 // The colon ':' is URL-encoded so we must decode it otherwise Angular won't find the parameters.
217 var url = new XWiki.Document(':page', ':space', ':wiki').getRestURL().replace('/%3A', '/:');
218 return $resource(url);
219 }]);
220 ...
221 myApp.controller('MyAppCtrl', ['$scope', 'Page', function ($scope, Page) {
222 $scope.page = Page.get({
223 wiki: 'xwiki',
224 space: 'Sandbox',
225 page: 'WebHome'
226 });
227 }]);
228 {{/code}}
229
Marius Dumitru Florea 10.2 230 Angular offers a way to reuse UI elements (widgets) or to enhance DOM elements with reusable behaviour by packaging them as **directives**. Directives can be used as custom HTML elements, custom HTML attributes, CSS class as well as comments.
Marius Dumitru Florea 10.1 231
232 {{code language="html"}}
Marius Dumitru Florea 10.2 233 <my-dir></my-dir>
234 <span my-dir="exp"></span>
235 <!-- directive: my-dir exp -->
236 <span class="my-dir: exp;"></span>
237 {{/code}}
238
239 We can create for instance a custom ##livetable## HTML element using directives.
240
241 {{code language="html"}}
Marius Dumitru Florea 10.1 242 <livetable xclass="Blog.BlogPostClass" />
243 {{/code}}
244
245 {{code language="JavaScript"}}
246 myApp.directive('livetable', ['liveTableResource', function(liveTableResource) {
247 return {
248 restrict: 'E',
249 templateUrl: new XWiki.Document('LiveTable', 'XWiki').getURL('get'),
250 link: function(scope, element, attributes) {
251 scope.rows = liveTableResource.query({
252 'class': attributes.xclass,
253 ...
254 });
255 }
256 };
257 }]);
258 {{/code}}
259
Marius Dumitru Florea 10.3 260 One of the strong points of Angular is the extensive support for **testing** the application.
Marius Dumitru Florea 10.2 261
Ecaterina Moraru (Valica) 5.1 262 == Related Proposals ==
263
264 * [[Twitter Bootstrap Integration>>Proposal.BootstrapIntegration]]
265 * [[Javascript Frameworks in XWiki (2007)>>Design.RationalizetheuseofjavascriptframeworksinXWiki]]

Get Connected