angular-ui-rails 0.3.2 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -3,6 +3,12 @@ require "angular-ui-rails/version"
3
3
  module AngularUI
4
4
  module Rails
5
5
  class Engine < ::Rails::Engine
6
+ # Enabling assets precompiling under rails 3.1
7
+ if Rails.version >= '3.1'
8
+ initializer :assets do |config|
9
+ Rails.application.config.assets.precompile += %w( angular-ui-ieshiv.js )
10
+ end
11
+ end
6
12
  end
7
13
  end
8
- end
14
+ end
@@ -1,5 +1,5 @@
1
1
  module AngularUI
2
2
  module Rails
3
- VERSION = "0.3.2"
3
+ VERSION = "0.4.0"
4
4
  end
5
5
  end
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * AngularUI - The companion suite for AngularJS
3
- * @version v0.3.2 - 2012-12-04
3
+ * @version v0.4.0 - 2013-02-15
4
4
  * @link http://angular-ui.github.com
5
5
  * @license MIT License, http://www.opensource.org/licenses/MIT
6
6
  */
@@ -31,21 +31,7 @@
31
31
  window.myCustomTags = window.myCustomTags || []; // externally defined by developer using angular-ui directives
32
32
  tags.push.apply(tags, window.myCustomTags);
33
33
 
34
- var getIE = function () {
35
- // Returns the version of Internet Explorer or a -1
36
- // (indicating the use of another browser).
37
- var rv = -1; // Return value assumes failure.
38
- if (navigator.appName == 'Microsoft Internet Explorer') {
39
- var ua = navigator.userAgent;
40
- var re = new RegExp("MSIE ([0-9]{1,}[\\.0-9]{0,})");
41
- if (re.exec(ua) !== null) {
42
- rv = parseFloat(RegExp.$1);
43
- }
44
- }
45
- return rv;
46
- };
47
-
48
- var toCustomElements = function (str, delim) {
34
+ var toCustomElements = function (str) {
49
35
  var result = [];
50
36
  var dashed = str.replace(/([A-Z])/g, function ($1) {
51
37
  return " " + $1.toLowerCase();
@@ -62,20 +48,12 @@
62
48
  return result;
63
49
  };
64
50
 
65
- var shiv = function () {
66
- for (var i = 0, tlen = tags.length; i < tlen; i++) {
67
- var customElements = toCustomElements(tags[i], ':');
68
- for (var j = 0, clen = customElements.length; j < clen; j++) {
69
- var customElement = customElements[j];
70
- document.createElement(customElement);
71
- }
51
+ for (var i = 0, tlen = tags.length; i < tlen; i++) {
52
+ var customElements = toCustomElements(tags[i]);
53
+ for (var j = 0, clen = customElements.length; j < clen; j++) {
54
+ var customElement = customElements[j];
55
+ document.createElement(customElement);
72
56
  }
73
- };
74
-
75
- var ieVersion = getIE();
76
-
77
- if ((ieVersion > -1 && ieVersion < 9) || debug) {
78
- shiv();
79
57
  }
80
58
 
81
59
  })(window);
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * AngularUI - The companion suite for AngularJS
3
- * @version v0.3.2 - 2012-12-04
3
+ * @version v0.4.0 - 2013-02-15
4
4
  * @link http://angular-ui.github.com
5
5
  * @license MIT License, http://www.opensource.org/licenses/MIT
6
6
  */
@@ -11,106 +11,343 @@ angular.module('ui.filters', ['ui.config']);
11
11
  angular.module('ui.directives', ['ui.config']);
12
12
  angular.module('ui', ['ui.filters', 'ui.directives', 'ui.config']);
13
13
 
14
- /*
15
- jQuery UI Sortable plugin wrapper
14
+ /**
15
+ * Animates the injection of new DOM elements by simply creating the DOM with a class and then immediately removing it
16
+ * Animations must be done using CSS3 transitions, but provide excellent flexibility
17
+ *
18
+ * @todo Add proper support for animating out
19
+ * @param [options] {mixed} Can be an object with multiple options, or a string with the animation class
20
+ * class {string} the CSS class(es) to use. For example, 'ui-hide' might be an excellent alternative class.
21
+ * @example <li ng-repeat="item in items" ui-animate=" 'ui-hide' ">{{item}}</li>
22
+ */
23
+ angular.module('ui.directives').directive('uiAnimate', ['ui.config', '$timeout', function (uiConfig, $timeout) {
24
+ var options = {};
25
+ if (angular.isString(uiConfig.animate)) {
26
+ options['class'] = uiConfig.animate;
27
+ } else if (uiConfig.animate) {
28
+ options = uiConfig.animate;
29
+ }
30
+ return {
31
+ restrict: 'A', // supports using directive as element, attribute and class
32
+ link: function ($scope, element, attrs) {
33
+ var opts = {};
34
+ if (attrs.uiAnimate) {
35
+ opts = $scope.$eval(attrs.uiAnimate);
36
+ if (angular.isString(opts)) {
37
+ opts = {'class': opts};
38
+ }
39
+ }
40
+ opts = angular.extend({'class': 'ui-animate'}, options, opts);
16
41
 
17
- @param [ui-sortable] {object} Options to pass to $.fn.sortable() merged onto ui.config
42
+ element.addClass(opts['class']);
43
+ $timeout(function () {
44
+ element.removeClass(opts['class']);
45
+ }, 20, false);
46
+ }
47
+ };
48
+ }]);
49
+
50
+
51
+ /*
52
+ * AngularJs Fullcalendar Wrapper for the JQuery FullCalendar
53
+ * API @ http://arshaw.com/fullcalendar/
54
+ *
55
+ * Angular Calendar Directive that takes in the [eventSources] nested array object as the ng-model and watches (eventSources.length + eventSources[i].length) for changes.
56
+ * Can also take in multiple event urls as a source object(s) and feed the events per view.
57
+ * The calendar will watch any eventSource array and update itself when a delta is created
58
+ * An equalsTracker attrs has been added for use cases that would render the overall length tracker the same even though the events have changed to force updates.
59
+ *
18
60
  */
19
61
 
20
- angular.module('ui.directives').directive('uiSortable', [
21
- 'ui.config', function(uiConfig) {
22
- var options;
23
- options = {};
24
- if (uiConfig.sortable != null) {
25
- angular.extend(options, uiConfig.sortable);
26
- }
27
- return {
28
- require: '?ngModel',
29
- link: function(scope, element, attrs, ngModel) {
30
- var onStart, onUpdate, opts, _start, _update;
31
- opts = angular.extend({}, options, scope.$eval(attrs.uiOptions));
32
- if (ngModel != null) {
33
- onStart = function(e, ui) {
34
- return ui.item.data('ui-sortable-start', ui.item.index());
35
- };
36
- onUpdate = function(e, ui) {
37
- var end, start;
38
- start = ui.item.data('ui-sortable-start');
39
- end = ui.item.index();
40
- ngModel.$modelValue.splice(end, 0, ngModel.$modelValue.splice(start, 1)[0]);
41
- return scope.$apply();
42
- };
43
- _start = opts.start;
44
- opts.start = function(e, ui) {
45
- onStart(e, ui);
46
- if (typeof _start === "function") {
47
- _start(e, ui);
48
- }
49
- return scope.$apply();
50
- };
51
- _update = opts.update;
52
- opts.update = function(e, ui) {
53
- onUpdate(e, ui);
54
- if (typeof _update === "function") {
55
- _update(e, ui);
62
+ angular.module('ui.directives').directive('uiCalendar',['ui.config', '$parse', function (uiConfig,$parse) {
63
+ uiConfig.uiCalendar = uiConfig.uiCalendar || {};
64
+ //returns calendar
65
+ return {
66
+ require: 'ngModel',
67
+ restrict: 'A',
68
+ link: function(scope, elm, attrs, $timeout) {
69
+ var sources = scope.$eval(attrs.ngModel);
70
+ var tracker = 0;
71
+ /* returns the length of all source arrays plus the length of eventSource itself */
72
+ var getSources = function () {
73
+ var equalsTracker = scope.$eval(attrs.equalsTracker);
74
+ tracker = 0;
75
+ angular.forEach(sources,function(value,key){
76
+ if(angular.isArray(value)){
77
+ tracker += value.length;
78
+ }
79
+ });
80
+ if(angular.isNumber(equalsTracker)){
81
+ return tracker + sources.length + equalsTracker;
82
+ }else{
83
+ return tracker + sources.length;
84
+ }
85
+ };
86
+ /* update the calendar with the correct options */
87
+ function update() {
88
+ //calendar object exposed on scope
89
+ scope.calendar = elm.html('');
90
+ var view = scope.calendar.fullCalendar('getView');
91
+ if(view){
92
+ view = view.name; //setting the default view to be whatever the current view is. This can be overwritten.
93
+ }
94
+ /* If the calendar has options added then render them */
95
+ var expression,
96
+ options = {
97
+ defaultView : view,
98
+ eventSources: sources
99
+ };
100
+ if (attrs.uiCalendar) {
101
+ expression = scope.$eval(attrs.uiCalendar);
102
+ } else {
103
+ expression = {};
104
+ }
105
+ angular.extend(options, uiConfig.uiCalendar, expression);
106
+ scope.calendar.fullCalendar(options);
56
107
  }
57
- return scope.$apply();
58
- };
59
- }
60
- return element.sortable(opts);
61
- }
108
+ update();
109
+ /* watches all eventSources */
110
+ scope.$watch(getSources, function( newVal, oldVal )
111
+ {
112
+ update();
113
+ });
114
+ }
62
115
  };
63
- }
64
- ]);
65
-
116
+ }]);
117
+ /*global angular, CodeMirror, Error*/
66
118
  /**
67
- * General-purpose jQuery wrapper. Simply pass the plugin name as the expression.
68
- *
69
- * It is possible to specify a default set of parameters for each jQuery plugin.
70
- * Under the jq key, namespace each plugin by that which will be passed to ui-jq.
71
- * Unfortunately, at this time you can only pre-define the first parameter.
72
- * @example { jq : { datepicker : { showOn:'click' } } }
73
- *
74
- * @param ui-jq {string} The $elm.[pluginName]() to call.
75
- * @param [ui-options] {mixed} Expression to be evaluated and passed as options to the function
76
- * Multiple parameters can be separated by commas
77
- * Set {ngChange:false} to disable passthrough support for change events ( since angular watches 'input' events, not 'change' events )
78
- *
79
- * @example <input ui-jq="datepicker" ui-options="{showOn:'click'},secondParameter,thirdParameter">
119
+ * Binds a CodeMirror widget to a <textarea> element.
120
+ */
121
+ angular.module('ui.directives').directive('uiCodemirror', ['ui.config', '$timeout', function (uiConfig, $timeout) {
122
+ 'use strict';
123
+
124
+ var events = ["cursorActivity", "viewportChange", "gutterClick", "focus", "blur", "scroll", "update"];
125
+ return {
126
+ restrict:'A',
127
+ require:'ngModel',
128
+ link:function (scope, elm, attrs, ngModel) {
129
+ var options, opts, onChange, deferCodeMirror, codeMirror;
130
+
131
+ if (elm[0].type !== 'textarea') {
132
+ throw new Error('uiCodemirror3 can only be applied to a textarea element');
133
+ }
134
+
135
+ options = uiConfig.codemirror || {};
136
+ opts = angular.extend({}, options, scope.$eval(attrs.uiCodemirror));
137
+
138
+ onChange = function (aEvent) {
139
+ return function (instance, changeObj) {
140
+ var newValue = instance.getValue();
141
+ if (newValue !== ngModel.$viewValue) {
142
+ ngModel.$setViewValue(newValue);
143
+ scope.$apply();
144
+ }
145
+ if (typeof aEvent === "function")
146
+ aEvent(instance, changeObj);
147
+ };
148
+ };
149
+
150
+ deferCodeMirror = function () {
151
+ codeMirror = CodeMirror.fromTextArea(elm[0], opts);
152
+ codeMirror.on("change", onChange(opts.onChange));
153
+
154
+ for (var i = 0, n = events.length, aEvent; i < n; ++i) {
155
+ aEvent = opts["on" + events[i].charAt(0).toUpperCase() + events[i].slice(1)];
156
+ if (aEvent === void 0) continue;
157
+ if (typeof aEvent !== "function") continue;
158
+ codeMirror.on(events[i], aEvent);
159
+ }
160
+
161
+ // CodeMirror expects a string, so make sure it gets one.
162
+ // This does not change the model.
163
+ ngModel.$formatters.push(function (value) {
164
+ if (angular.isUndefined(value) || value === null) {
165
+ return '';
166
+ }
167
+ else if (angular.isObject(value) || angular.isArray(value)) {
168
+ throw new Error('ui-codemirror cannot use an object or an array as a model');
169
+ }
170
+ return value;
171
+ });
172
+
173
+ // Override the ngModelController $render method, which is what gets called when the model is updated.
174
+ // This takes care of the synchronizing the codeMirror element with the underlying model, in the case that it is changed by something else.
175
+ ngModel.$render = function () {
176
+ codeMirror.setValue(ngModel.$viewValue);
177
+ };
178
+
179
+ // Watch ui-refresh and refresh the directive
180
+ if (attrs.uiRefresh) {
181
+ scope.$watch(attrs.uiRefresh, function(newVal, oldVal){
182
+ // Skip the initial watch firing
183
+ if (newVal !== oldVal)
184
+ $timeout(codeMirror.refresh);
185
+ });
186
+ }
187
+ };
188
+
189
+ $timeout(deferCodeMirror);
190
+
191
+ }
192
+ };
193
+ }]);
194
+
195
+ /*
196
+ Gives the ability to style currency based on its sign.
80
197
  */
81
- angular.module('ui.directives').directive('uiJq', ['ui.config', function (uiConfig) {
198
+ angular.module('ui.directives').directive('uiCurrency', ['ui.config', 'currencyFilter' , function (uiConfig, currencyFilter) {
199
+ var options = {
200
+ pos: 'ui-currency-pos',
201
+ neg: 'ui-currency-neg',
202
+ zero: 'ui-currency-zero'
203
+ };
204
+ if (uiConfig.currency) {
205
+ angular.extend(options, uiConfig.currency);
206
+ }
82
207
  return {
83
- restrict: 'A',
84
- compile: function (tElm, tAttrs) {
85
- if (!angular.isFunction(tElm[tAttrs.uiJq])) {
86
- throw new Error('ui-jq: The "' + tAttrs.uiJq + '" function does not exist');
87
- }
88
- var options = uiConfig.jq && uiConfig.jq[tAttrs.uiJq];
89
- return function (scope, elm, attrs) {
90
- var linkOptions = [], ngChange = 'change';
208
+ restrict: 'EAC',
209
+ require: 'ngModel',
210
+ link: function (scope, element, attrs, controller) {
211
+ var opts, // instance-specific options
212
+ renderview,
213
+ value;
91
214
 
92
- if (attrs.uiOptions) {
93
- linkOptions = scope.$eval('[' + attrs.uiOptions + ']');
94
- if (angular.isObject(options) && angular.isObject(linkOptions[0])) {
95
- linkOptions[0] = angular.extend(options, linkOptions[0]);
96
- }
97
- } else if (options) {
98
- linkOptions = [options];
215
+ opts = angular.extend({}, options, scope.$eval(attrs.uiCurrency));
216
+
217
+ renderview = function (viewvalue) {
218
+ var num;
219
+ num = viewvalue * 1;
220
+ element.toggleClass(opts.pos, (num > 0) );
221
+ element.toggleClass(opts.neg, (num < 0) );
222
+ element.toggleClass(opts.zero, (num === 0) );
223
+ if (viewvalue === '') {
224
+ element.text('');
225
+ } else {
226
+ element.text(currencyFilter(num, opts.symbol));
99
227
  }
100
- if (attrs.ngModel && elm.is('select,input,textarea')) {
101
- if (linkOptions && angular.isObject(linkOptions[0]) && linkOptions[0].ngChange !== undefined) {
102
- ngChange = linkOptions[0].ngChange;
103
- }
104
- if (ngChange) {
105
- elm.on(ngChange, function () {
106
- elm.trigger('input');
228
+ return true;
229
+ };
230
+
231
+ controller.$render = function () {
232
+ value = controller.$viewValue;
233
+ element.val(value);
234
+ renderview(value);
235
+ };
236
+
237
+ }
238
+ };
239
+ }]);
240
+
241
+ /*global angular */
242
+ /*
243
+ jQuery UI Datepicker plugin wrapper
244
+
245
+ @note If ≤ IE8 make sure you have a polyfill for Date.toISOString()
246
+ @param [ui-date] {object} Options to pass to $.fn.datepicker() merged onto ui.config
247
+ */
248
+
249
+ angular.module('ui.directives')
250
+
251
+ .directive('uiDate', ['ui.config', function (uiConfig) {
252
+ 'use strict';
253
+ var options;
254
+ options = {};
255
+ if (angular.isObject(uiConfig.date)) {
256
+ angular.extend(options, uiConfig.date);
257
+ }
258
+ return {
259
+ require:'?ngModel',
260
+ link:function (scope, element, attrs, controller) {
261
+ var getOptions = function () {
262
+ return angular.extend({}, uiConfig.date, scope.$eval(attrs.uiDate));
263
+ };
264
+ var initDateWidget = function () {
265
+ var opts = getOptions();
266
+
267
+ // If we have a controller (i.e. ngModelController) then wire it up
268
+ if (controller) {
269
+ var updateModel = function () {
270
+ scope.$apply(function () {
271
+ var date = element.datepicker("getDate");
272
+ element.datepicker("setDate", element.val());
273
+ controller.$setViewValue(date);
274
+ element.blur();
107
275
  });
276
+ };
277
+ if (opts.onSelect) {
278
+ // Caller has specified onSelect, so call this as well as updating the model
279
+ var userHandler = opts.onSelect;
280
+ opts.onSelect = function (value, picker) {
281
+ updateModel();
282
+ scope.$apply(function() {
283
+ userHandler(value, picker);
284
+ });
285
+ };
286
+ } else {
287
+ // No onSelect already specified so just update the model
288
+ opts.onSelect = updateModel;
108
289
  }
290
+ // In case the user changes the text directly in the input box
291
+ element.bind('change', updateModel);
292
+
293
+ // Update the date picker when the model changes
294
+ controller.$render = function () {
295
+ var date = controller.$viewValue;
296
+ if ( angular.isDefined(date) && date !== null && !angular.isDate(date) ) {
297
+ throw new Error('ng-Model value must be a Date object - currently it is a ' + typeof date + ' - use ui-date-format to convert it from a string');
298
+ }
299
+ element.datepicker("setDate", date);
300
+ };
301
+ }
302
+ // If we don't destroy the old one it doesn't update properly when the config changes
303
+ element.datepicker('destroy');
304
+ // Create the new datepicker widget
305
+ element.datepicker(opts);
306
+ if ( controller ) {
307
+ // Force a render to override whatever is in the input text box
308
+ controller.$render();
109
309
  }
110
- elm[attrs.uiJq].apply(elm, linkOptions);
111
310
  };
311
+ // Watch for changes to the directives options
312
+ scope.$watch(getOptions, initDateWidget, true);
313
+ }
314
+ };
315
+ }
316
+ ])
317
+
318
+ .directive('uiDateFormat', ['ui.config', function(uiConfig) {
319
+ var directive = {
320
+ require:'ngModel',
321
+ link: function(scope, element, attrs, modelCtrl) {
322
+ var dateFormat = attrs.uiDateFormat || uiConfig.dateFormat;
323
+ if ( dateFormat ) {
324
+ // Use the datepicker with the attribute value as the dateFormat string to convert to and from a string
325
+ modelCtrl.$formatters.push(function(value) {
326
+ if (angular.isString(value) ) {
327
+ return $.datepicker.parseDate(dateFormat, value);
328
+ }
329
+ });
330
+ modelCtrl.$parsers.push(function(value){
331
+ if (value) {
332
+ return $.datepicker.formatDate(dateFormat, value);
333
+ }
334
+ });
335
+ } else {
336
+ // Default to ISO formatting
337
+ modelCtrl.$formatters.push(function(value) {
338
+ if (angular.isString(value) ) {
339
+ return new Date(value);
340
+ }
341
+ });
342
+ modelCtrl.$parsers.push(function(value){
343
+ if (value) {
344
+ return value.toISOString();
345
+ }
346
+ });
347
+ }
112
348
  }
113
349
  };
350
+ return directive;
114
351
  }]);
115
352
 
116
353
  /**
@@ -142,99 +379,219 @@ angular.module('ui.directives').directive('uiEvent', ['$parse',
142
379
  }]);
143
380
 
144
381
  /*
145
- Attaches jquery-ui input mask onto input element
382
+ * Defines the ui-if tag. This removes/adds an element from the dom depending on a condition
383
+ * Originally created by @tigbro, for the @jquery-mobile-angular-adapter
384
+ * https://github.com/tigbro/jquery-mobile-angular-adapter
146
385
  */
147
- angular.module('ui.directives').directive('uiMask', [
148
- function () {
149
- return {
150
- require:'ngModel',
151
- link:function ($scope, element, attrs, controller) {
152
-
153
- /* We override the render method to run the jQuery mask plugin
154
- */
155
- controller.$render = function () {
156
- var value = controller.$viewValue || '';
157
- element.val(value);
158
- element.mask($scope.$eval(attrs.uiMask));
159
- };
386
+ angular.module('ui.directives').directive('uiIf', [function () {
387
+ return {
388
+ transclude: 'element',
389
+ priority: 1000,
390
+ terminal: true,
391
+ restrict: 'A',
392
+ compile: function (element, attr, transclude) {
393
+ return function (scope, element, attr) {
394
+
395
+ var childElement;
396
+ var childScope;
397
+
398
+ scope.$watch(attr['uiIf'], function (newValue) {
399
+ if (childElement) {
400
+ childElement.remove();
401
+ childElement = undefined;
402
+ }
403
+ if (childScope) {
404
+ childScope.$destroy();
405
+ childScope = undefined;
406
+ }
160
407
 
161
- /* Add a parser that extracts the masked value into the model but only if the mask is valid
162
- */
163
- controller.$parsers.push(function (value) {
164
- //the second check (or) is only needed due to the fact that element.isMaskValid() will keep returning undefined
165
- //until there was at least one key event
166
- var isValid = element.isMaskValid() || angular.isUndefined(element.isMaskValid()) && element.val().length>0;
167
- controller.$setValidity('mask', isValid);
168
- return isValid ? value : undefined;
408
+ if (newValue) {
409
+ childScope = scope.$new();
410
+ transclude(childScope, function (clone) {
411
+ childElement = clone;
412
+ element.after(clone);
413
+ });
414
+ }
169
415
  });
416
+ };
417
+ }
418
+ };
419
+ }]);
420
+ /**
421
+ * General-purpose jQuery wrapper. Simply pass the plugin name as the expression.
422
+ *
423
+ * It is possible to specify a default set of parameters for each jQuery plugin.
424
+ * Under the jq key, namespace each plugin by that which will be passed to ui-jq.
425
+ * Unfortunately, at this time you can only pre-define the first parameter.
426
+ * @example { jq : { datepicker : { showOn:'click' } } }
427
+ *
428
+ * @param ui-jq {string} The $elm.[pluginName]() to call.
429
+ * @param [ui-options] {mixed} Expression to be evaluated and passed as options to the function
430
+ * Multiple parameters can be separated by commas
431
+ * @param [ui-refresh] {expression} Watch expression and refire plugin on changes
432
+ *
433
+ * @example <input ui-jq="datepicker" ui-options="{showOn:'click'},secondParameter,thirdParameter" ui-refresh="iChange">
434
+ */
435
+ angular.module('ui.directives').directive('uiJq', ['ui.config', '$timeout', function uiJqInjectingFunction(uiConfig, $timeout) {
170
436
 
171
- /* When keyup, update the view value
172
- */
173
- element.bind('keyup', function () {
174
- $scope.$apply(function () {
175
- controller.$setViewValue(element.mask());
176
- });
177
- });
437
+ return {
438
+ restrict: 'A',
439
+ compile: function uiJqCompilingFunction(tElm, tAttrs) {
440
+
441
+ if (!angular.isFunction(tElm[tAttrs.uiJq])) {
442
+ throw new Error('ui-jq: The "' + tAttrs.uiJq + '" function does not exist');
178
443
  }
179
- };
180
- }
181
- ]);
444
+ var options = uiConfig.jq && uiConfig.jq[tAttrs.uiJq];
182
445
 
183
- angular.module('ui.directives')
184
- .directive('uiModal', ['$timeout', function($timeout) {
185
- return {
186
- restrict: 'EAC',
187
- require: 'ngModel',
188
- link: function(scope, elm, attrs, model) {
189
- //helper so you don't have to type class="modal hide"
190
- elm.addClass('modal hide');
191
- elm.on( 'shown', function() {
192
- elm.find( "[autofocus]" ).focus();
193
- });
194
- scope.$watch(attrs.ngModel, function(value) {
195
- elm.modal(value && 'show' || 'hide');
196
- });
197
- //If bootstrap animations are enabled, listen to 'shown' and 'hidden' events
198
- elm.on(jQuery.support.transition && 'shown' || 'show', function() {
199
- $timeout(function() {
200
- model.$setViewValue(true);
446
+ return function uiJqLinkingFunction(scope, elm, attrs) {
447
+
448
+ var linkOptions = [];
449
+
450
+ // If ui-options are passed, merge (or override) them onto global defaults and pass to the jQuery method
451
+ if (attrs.uiOptions) {
452
+ linkOptions = scope.$eval('[' + attrs.uiOptions + ']');
453
+ if (angular.isObject(options) && angular.isObject(linkOptions[0])) {
454
+ linkOptions[0] = angular.extend({}, options, linkOptions[0]);
455
+ }
456
+ } else if (options) {
457
+ linkOptions = [options];
458
+ }
459
+ // If change compatibility is enabled, the form input's "change" event will trigger an "input" event
460
+ if (attrs.ngModel && elm.is('select,input,textarea')) {
461
+ elm.on('change', function() {
462
+ elm.trigger('input');
463
+ });
464
+ }
465
+
466
+ // Call jQuery method and pass relevant options
467
+ function callPlugin() {
468
+ $timeout(function() {
469
+ elm[attrs.uiJq].apply(elm, linkOptions);
470
+ }, 0, false);
471
+ }
472
+
473
+ // If ui-refresh is used, re-fire the the method upon every change
474
+ if (attrs.uiRefresh) {
475
+ scope.$watch(attrs.uiRefresh, function(newVal) {
476
+ callPlugin();
477
+ });
478
+ }
479
+ callPlugin();
480
+ };
481
+ }
482
+ };
483
+ }]);
484
+
485
+ angular.module('ui.directives').factory('keypressHelper', ['$parse', function keypress($parse){
486
+ var keysByCode = {
487
+ 8: 'backspace',
488
+ 9: 'tab',
489
+ 13: 'enter',
490
+ 27: 'esc',
491
+ 32: 'space',
492
+ 33: 'pageup',
493
+ 34: 'pagedown',
494
+ 35: 'end',
495
+ 36: 'home',
496
+ 37: 'left',
497
+ 38: 'up',
498
+ 39: 'right',
499
+ 40: 'down',
500
+ 45: 'insert',
501
+ 46: 'delete'
502
+ };
503
+
504
+ var capitaliseFirstLetter = function (string) {
505
+ return string.charAt(0).toUpperCase() + string.slice(1);
506
+ };
507
+
508
+ return function(mode, scope, elm, attrs) {
509
+ var params, combinations = [];
510
+ params = scope.$eval(attrs['ui'+capitaliseFirstLetter(mode)]);
511
+
512
+ // Prepare combinations for simple checking
513
+ angular.forEach(params, function (v, k) {
514
+ var combination, expression;
515
+ expression = $parse(v);
516
+
517
+ angular.forEach(k.split(' '), function(variation) {
518
+ combination = {
519
+ expression: expression,
520
+ keys: {}
521
+ };
522
+ angular.forEach(variation.split('-'), function (value) {
523
+ combination.keys[value] = true;
201
524
  });
525
+ combinations.push(combination);
202
526
  });
203
- elm.on(jQuery.support.transition && 'hidden' || 'hide', function() {
204
- $timeout(function() {
205
- model.$setViewValue(false);
206
- });
527
+ });
528
+
529
+ // Check only matching of pressed keys one of the conditions
530
+ elm.bind(mode, function (event) {
531
+ // No need to do that inside the cycle
532
+ var altPressed = event.metaKey || event.altKey;
533
+ var ctrlPressed = event.ctrlKey;
534
+ var shiftPressed = event.shiftKey;
535
+ var keyCode = event.keyCode;
536
+
537
+ // normalize keycodes
538
+ if (mode === 'keypress' && !shiftPressed && keyCode >= 97 && keyCode <= 122) {
539
+ keyCode = keyCode - 32;
540
+ }
541
+
542
+ // Iterate over prepared combinations
543
+ angular.forEach(combinations, function (combination) {
544
+
545
+ var mainKeyPressed = (combination.keys[keysByCode[event.keyCode]] || combination.keys[event.keyCode.toString()]) || false;
546
+
547
+ var altRequired = combination.keys.alt || false;
548
+ var ctrlRequired = combination.keys.ctrl || false;
549
+ var shiftRequired = combination.keys.shift || false;
550
+
551
+ if (
552
+ mainKeyPressed &&
553
+ ( altRequired == altPressed ) &&
554
+ ( ctrlRequired == ctrlPressed ) &&
555
+ ( shiftRequired == shiftPressed )
556
+ ) {
557
+ // Run the function
558
+ scope.$apply(function () {
559
+ combination.expression(scope, { '$event': event });
560
+ });
561
+ }
207
562
  });
208
- }
563
+ });
209
564
  };
210
565
  }]);
566
+
211
567
  /**
212
- * Add a clear button to form inputs to reset their value
213
- */
214
- angular.module('ui.directives').directive('uiReset', ['ui.config', function (uiConfig) {
215
- var resetValue = null;
216
- if (uiConfig.reset !== undefined)
217
- resetValue = uiConfig.reset;
568
+ * Bind one or more handlers to particular keys or their combination
569
+ * @param hash {mixed} keyBindings Can be an object or string where keybinding expression of keys or keys combinations and AngularJS Exspressions are set. Object syntax: "{ keys1: expression1 [, keys2: expression2 [ , ... ]]}". String syntax: ""expression1 on keys1 [ and expression2 on keys2 [ and ... ]]"". Expression is an AngularJS Expression, and key(s) are dash-separated combinations of keys and modifiers (one or many, if any. Order does not matter). Supported modifiers are 'ctrl', 'shift', 'alt' and key can be used either via its keyCode (13 for Return) or name. Named keys are 'backspace', 'tab', 'enter', 'esc', 'space', 'pageup', 'pagedown', 'end', 'home', 'left', 'up', 'right', 'down', 'insert', 'delete'.
570
+ * @example <input ui-keypress="{enter:'x = 1', 'ctrl-shift-space':'foo()', 'shift-13':'bar()'}" /> <input ui-keypress="foo = 2 on ctrl-13 and bar('hello') on shift-esc" />
571
+ **/
572
+ angular.module('ui.directives').directive('uiKeydown', ['keypressHelper', function(keypressHelper){
218
573
  return {
219
- require: 'ngModel',
220
- link: function (scope, elm, attrs, ctrl) {
221
- var aElement;
222
- aElement = angular.element('<a class="ui-reset" />');
223
- elm.wrap('<span class="ui-resetwrap" />').after(aElement);
224
- aElement.bind('click', function (e) {
225
- e.preventDefault();
226
- scope.$apply(function () {
227
- if (attrs.uiReset)
228
- ctrl.$setViewValue(scope.$eval(attrs.uiReset));
229
- else
230
- ctrl.$setViewValue(resetValue);
231
- ctrl.$render();
232
- });
233
- });
574
+ link: function (scope, elm, attrs) {
575
+ keypressHelper('keydown', scope, elm, attrs);
576
+ }
577
+ };
578
+ }]);
579
+
580
+ angular.module('ui.directives').directive('uiKeypress', ['keypressHelper', function(keypressHelper){
581
+ return {
582
+ link: function (scope, elm, attrs) {
583
+ keypressHelper('keypress', scope, elm, attrs);
234
584
  }
235
585
  };
236
586
  }]);
237
587
 
588
+ angular.module('ui.directives').directive('uiKeyup', ['keypressHelper', function(keypressHelper){
589
+ return {
590
+ link: function (scope, elm, attrs) {
591
+ keypressHelper('keyup', scope, elm, attrs);
592
+ }
593
+ };
594
+ }]);
238
595
  (function () {
239
596
  var app = angular.module('ui.directives');
240
597
 
@@ -246,7 +603,7 @@ angular.module('ui.directives').directive('uiReset', ['ui.config', function (uiC
246
603
  //for the googlemap doesn't interfere with a normal 'click' event
247
604
  var $event = { type: 'map-' + eventName };
248
605
  google.maps.event.addListener(googleObject, eventName, function (evt) {
249
- element.trigger(angular.extend({}, $event, evt));
606
+ element.triggerHandler(angular.extend({}, $event, evt));
250
607
  //We create an $apply if it isn't happening. we need better support for this
251
608
  //We don't want to use timeout because tons of these events fire at once,
252
609
  //and we only need one $apply
@@ -360,485 +717,141 @@ angular.module('ui.directives').directive('uiReset', ['ui.config', function (uiC
360
717
  'click dblclick');
361
718
 
362
719
  })();
363
- angular.module('ui.directives').factory('keypressHelper', ['$parse', function keypress($parse){
364
- var keysByCode = {
365
- 8: 'backspace',
366
- 9: 'tab',
367
- 13: 'enter',
368
- 27: 'esc',
369
- 32: 'space',
370
- 33: 'pageup',
371
- 34: 'pagedown',
372
- 35: 'end',
373
- 36: 'home',
374
- 37: 'left',
375
- 38: 'up',
376
- 39: 'right',
377
- 40: 'down',
378
- 45: 'insert',
379
- 46: 'delete'
380
- };
381
-
382
- var capitaliseFirstLetter = function (string) {
383
- return string.charAt(0).toUpperCase() + string.slice(1);
384
- };
385
-
386
- return function(mode, scope, elm, attrs) {
387
- var params, combinations = [];
388
- params = scope.$eval(attrs['ui'+capitaliseFirstLetter(mode)]);
720
+ /*
721
+ Attaches jquery-ui input mask onto input element
722
+ */
723
+ angular.module('ui.directives').directive('uiMask', [
724
+ function () {
725
+ return {
726
+ require:'ngModel',
727
+ link:function ($scope, element, attrs, controller) {
389
728
 
390
- // Prepare combinations for simple checking
391
- angular.forEach(params, function (v, k) {
392
- var combination, expression;
393
- expression = $parse(v);
729
+ /* We override the render method to run the jQuery mask plugin
730
+ */
731
+ controller.$render = function () {
732
+ var value = controller.$viewValue || '';
733
+ element.val(value);
734
+ element.mask($scope.$eval(attrs.uiMask));
735
+ };
394
736
 
395
- angular.forEach(k.split(' '), function(variation) {
396
- combination = {
397
- expression: expression,
398
- keys: {}
399
- };
400
- angular.forEach(variation.split('-'), function (value) {
401
- combination.keys[value] = true;
737
+ /* Add a parser that extracts the masked value into the model but only if the mask is valid
738
+ */
739
+ controller.$parsers.push(function (value) {
740
+ //the second check (or) is only needed due to the fact that element.isMaskValid() will keep returning undefined
741
+ //until there was at least one key event
742
+ var isValid = element.isMaskValid() || angular.isUndefined(element.isMaskValid()) && element.val().length>0;
743
+ controller.$setValidity('mask', isValid);
744
+ return isValid ? value : undefined;
402
745
  });
403
- combinations.push(combination);
404
- });
405
- });
406
-
407
- // Check only matching of pressed keys one of the conditions
408
- elm.bind(mode, function (event) {
409
- // No need to do that inside the cycle
410
- var altPressed = event.metaKey || event.altKey;
411
- var ctrlPressed = event.ctrlKey;
412
- var shiftPressed = event.shiftKey;
413
- var keyCode = event.keyCode;
414
-
415
- // normalize keycodes
416
- if (mode === 'keypress' && !shiftPressed && keyCode >= 97 && keyCode <= 122) {
417
- keyCode = keyCode - 32;
418
- }
419
746
 
420
- // Iterate over prepared combinations
421
- angular.forEach(combinations, function (combination) {
422
-
423
- var mainKeyPressed = (combination.keys[keysByCode[event.keyCode]] || combination.keys[event.keyCode.toString()]) || false;
424
-
425
- var altRequired = combination.keys.alt || false;
426
- var ctrlRequired = combination.keys.ctrl || false;
427
- var shiftRequired = combination.keys.shift || false;
428
-
429
- if (
430
- mainKeyPressed &&
431
- ( altRequired == altPressed ) &&
432
- ( ctrlRequired == ctrlPressed ) &&
433
- ( shiftRequired == shiftPressed )
434
- ) {
435
- // Run the function
436
- scope.$apply(function () {
437
- combination.expression(scope, { '$event': event });
747
+ /* When keyup, update the view value
748
+ */
749
+ element.bind('keyup', function () {
750
+ $scope.$apply(function () {
751
+ controller.$setViewValue(element.mask());
438
752
  });
439
- }
440
- });
441
- });
442
- };
443
- }]);
444
-
445
- /**
446
- * Bind one or more handlers to particular keys or their combination
447
- * @param hash {mixed} keyBindings Can be an object or string where keybinding expression of keys or keys combinations and AngularJS Exspressions are set. Object syntax: "{ keys1: expression1 [, keys2: expression2 [ , ... ]]}". String syntax: ""expression1 on keys1 [ and expression2 on keys2 [ and ... ]]"". Expression is an AngularJS Expression, and key(s) are dash-separated combinations of keys and modifiers (one or many, if any. Order does not matter). Supported modifiers are 'ctrl', 'shift', 'alt' and key can be used either via its keyCode (13 for Return) or name. Named keys are 'backspace', 'tab', 'enter', 'esc', 'space', 'pageup', 'pagedown', 'end', 'home', 'left', 'up', 'right', 'down', 'insert', 'delete'.
448
- * @example <input ui-keypress="{enter:'x = 1', 'ctrl-shift-space':'foo()', 'shift-13':'bar()'}" /> <input ui-keypress="foo = 2 on ctrl-13 and bar('hello') on shift-esc" />
449
- **/
450
- angular.module('ui.directives').directive('uiKeydown', ['keypressHelper', function(keypressHelper){
451
- return {
452
- link: function (scope, elm, attrs) {
453
- keypressHelper('keydown', scope, elm, attrs);
454
- }
455
- };
456
- }]);
457
-
458
- angular.module('ui.directives').directive('uiKeypress', ['keypressHelper', function(keypressHelper){
459
- return {
460
- link: function (scope, elm, attrs) {
461
- keypressHelper('keypress', scope, elm, attrs);
462
- }
463
- };
464
- }]);
465
-
466
- angular.module('ui.directives').directive('uiKeyup', ['keypressHelper', function(keypressHelper){
467
- return {
468
- link: function (scope, elm, attrs) {
469
- keypressHelper('keyup', scope, elm, attrs);
470
- }
471
- };
472
- }]);
473
- /**
474
- * General-purpose validator for ngModel.
475
- * angular.js comes with several built-in validation mechanism for input fields (ngRequired, ngPattern etc.) but using
476
- * an arbitrary validation function requires creation of a custom formatters and / or parsers.
477
- * The ui-validate directive makes it easy to use any function(s) defined in scope as a validator function(s).
478
- * A validator function will trigger validation on both model and input changes.
479
- *
480
- * @example <input ui-validate="myValidatorFunction">
481
- * @example <input ui-validate="{foo : validateFoo, bar : validateBar}">
482
- *
483
- * @param ui-validate {string|object literal} If strings is passed it should be a scope's function to be used as a validator.
484
- * If an object literal is passed a key denotes a validation error key while a value should be a validator function.
485
- * In both cases validator function should take a value to validate as its argument and should return true/false indicating a validation result.
486
- */
487
- angular.module('ui.directives').directive('uiValidate', function () {
488
-
489
- return {
490
- restrict: 'A',
491
- require: 'ngModel',
492
- link: function (scope, elm, attrs, ctrl) {
493
-
494
- var validateFn, validateExpr = attrs.uiValidate;
495
-
496
- validateExpr = scope.$eval(validateExpr);
497
- if (!validateExpr) {
498
- return;
499
- }
500
-
501
- if (angular.isFunction(validateExpr)) {
502
- validateExpr = { validator: validateExpr };
503
- }
504
-
505
- angular.forEach(validateExpr, function (validatorFn, key) {
506
- validateFn = function (valueToValidate) {
507
- if (validatorFn(valueToValidate)) {
508
- ctrl.$setValidity(key, true);
509
- return valueToValidate;
510
- } else {
511
- ctrl.$setValidity(key, false);
512
- return undefined;
513
- }
514
- };
515
- ctrl.$formatters.push(validateFn);
516
- ctrl.$parsers.push(validateFn);
517
- });
518
- }
519
- };
520
- });
521
- /**
522
- * Animates the injection of new DOM elements by simply creating the DOM with a class and then immediately removing it
523
- * Animations must be done using CSS3 transitions, but provide excellent flexibility
524
- *
525
- * @todo Add proper support for animating out
526
- * @param [options] {mixed} Can be an object with multiple options, or a string with the animation class
527
- * class {string} the CSS class(es) to use. For example, 'ui-hide' might be an excellent alternative class.
528
- * @example <li ng-repeat="item in items" ui-animate=" 'ui-hide' ">{{item}}</li>
529
- */
530
- angular.module('ui.directives').directive('uiAnimate', ['ui.config', '$timeout', function (uiConfig, $timeout) {
531
- var options = {};
532
- if (angular.isString(uiConfig.animate)) {
533
- options['class'] = uiConfig.animate;
534
- } else if (uiConfig.animate) {
535
- options = uiConfig.animate;
536
- }
537
- return {
538
- restrict: 'A', // supports using directive as element, attribute and class
539
- link: function ($scope, element, attrs) {
540
- var opts = {};
541
- if (attrs.uiAnimate) {
542
- opts = $scope.$eval(attrs.uiAnimate);
543
- if (angular.isString(opts)) {
544
- opts = {'class': opts};
545
- }
546
- }
547
- opts = angular.extend({'class': 'ui-animate'}, options, opts);
548
-
549
- element.addClass(opts['class']);
550
- $timeout(function () {
551
- element.removeClass(opts['class']);
552
- }, 20, false);
553
- }
554
- };
555
- }]);
556
-
557
-
558
- /**
559
- * Enhanced Select2 Dropmenus
560
- *
561
- * @AJAX Mode - When in this mode, your value will be an object (or array of objects) of the data used by Select2
562
- * This change is so that you do not have to do an additional query yourself on top of Select2's own query
563
- * @params [options] {object} The configuration options passed to $.fn.select2(). Refer to the documentation
564
- */
565
- angular.module('ui.directives').directive('uiSelect2', ['ui.config', '$http', function (uiConfig, $http) {
566
- var options = {};
567
- if (uiConfig.select2) {
568
- angular.extend(options, uiConfig.select2);
569
- }
570
- return {
571
- require: '?ngModel',
572
- compile: function (tElm, tAttrs) {
573
- var watch,
574
- repeatOption,
575
- repeatAttr,
576
- isSelect = tElm.is('select'),
577
- isMultiple = (tAttrs.multiple !== undefined);
578
-
579
- // Enable watching of the options dataset if in use
580
- if (tElm.is('select')) {
581
- repeatOption = tElm.find('option[ng-repeat], option[data-ng-repeat]');
582
-
583
- if (repeatOption.length) {
584
- repeatAttr = repeatOption.attr('ng-repeat') || repeatOption.attr('data-ng-repeat');
585
- watch = jQuery.trim(repeatAttr.split('|')[0]).split(' ').pop();
586
- }
587
- }
588
-
589
- return function (scope, elm, attrs, controller) {
590
- // instance-specific options
591
- var opts = angular.extend({}, options, scope.$eval(attrs.uiSelect2));
592
-
593
- if (isSelect) {
594
- // Use <select multiple> instead
595
- delete opts.multiple;
596
- delete opts.initSelection;
597
- } else if (isMultiple) {
598
- opts.multiple = true;
599
- }
600
-
601
- if (controller) {
602
- // Watch the model for programmatic changes
603
- controller.$render = function () {
604
- if (isSelect) {
605
- elm.select2('val', controller.$modelValue);
606
- } else {
607
- if (isMultiple && !controller.$modelValue) {
608
- elm.select2('data', []);
609
- } else {
610
- elm.select2('data', controller.$modelValue);
611
- }
612
- }
613
- };
614
-
615
-
616
- // Watch the options dataset for changes
617
- if (watch) {
618
- scope.$watch(watch, function (newVal, oldVal, scope) {
619
- if (!newVal) return;
620
- // Delayed so that the options have time to be rendered
621
- setTimeout(function () {
622
- elm.select2('val', controller.$viewValue);
623
- // Refresh angular to remove the superfluous option
624
- elm.trigger('change');
625
- });
626
- });
627
- }
628
-
629
- if (!isSelect) {
630
- // Set the view and model value and update the angular template manually for the ajax/multiple select2.
631
- elm.bind("change", function () {
632
- scope.$apply(function () {
633
- controller.$setViewValue(elm.select2('data'));
634
- });
635
- });
636
-
637
- if (opts.initSelection) {
638
- var initSelection = opts.initSelection;
639
- opts.initSelection = function (element, callback) {
640
- initSelection(element, function (value) {
641
- controller.$setViewValue(value);
642
- callback(value);
643
- });
644
- };
645
- }
646
- }
647
- }
648
-
649
- attrs.$observe('disabled', function (value) {
650
- elm.select2(value && 'disable' || 'enable');
651
- });
652
-
653
- scope.$watch(attrs.ngMultiple, function(newVal) {
654
- elm.select2(opts);
655
753
  });
656
-
657
- // Set initial value since Angular doesn't
658
- elm.val(scope.$eval(attrs.ngModel));
659
-
660
- // Initialize the plugin late so that the injected DOM does not disrupt the template compiler
661
- setTimeout(function () {
662
- elm.select2(opts);
663
- });
664
- };
665
- }
666
- };
667
- }]);
668
-
669
- /*global angular, CodeMirror, Error*/
670
- /**
671
- * Binds a CodeMirror widget to a <textarea> element.
672
- */
673
- angular.module('ui.directives').directive('uiCodemirror', ['ui.config', '$parse', function (uiConfig, $parse) {
674
- 'use strict';
675
-
676
- uiConfig.codemirror = uiConfig.codemirror || {};
677
- return {
678
- require: 'ngModel',
679
- link: function (scope, elm, attrs, ngModel) {
680
- // Only works on textareas
681
- if (!elm.is('textarea')) {
682
- throw new Error('ui-codemirror can only be applied to a textarea element');
683
754
  }
755
+ };
756
+ }
757
+ ]);
684
758
 
685
- var codemirror;
686
- // This is the method that we use to get the value of the ui-codemirror attribute expression.
687
- var uiCodemirrorGet = $parse(attrs.uiCodemirror);
688
- // This method will be called whenever the code mirror widget content changes
689
- var onChangeHandler = function (ed) {
690
- // We only update the model if the value has changed - this helps get around a little problem where $render triggers a change despite already being inside a $apply loop.
691
- var newValue = ed.getValue();
692
- if (newValue !== ngModel.$viewValue) {
693
- ngModel.$setViewValue(newValue);
694
- scope.$apply();
695
- }
696
- };
697
- // Create and wire up a new code mirror widget (unwiring a previous one if necessary)
698
- var updateCodeMirror = function (options) {
699
- // Merge together the options from the uiConfig and the attribute itself with the onChange event above.
700
- options = angular.extend({}, options, uiConfig.codemirror);
701
-
702
- // We actually want to run both handlers if the user has provided their own onChange handler.
703
- var userOnChange = options.onChange;
704
- if (userOnChange) {
705
- options.onChange = function (ed) {
706
- onChangeHandler(ed);
707
- userOnChange(ed);
708
- };
709
- } else {
710
- options.onChange = onChangeHandler;
711
- }
712
-
713
- // If there is a codemirror widget for this element already then we need to unwire if first
714
- if (codemirror) {
715
- codemirror.toTextArea();
716
- }
717
- // Create the new codemirror widget
718
- codemirror = CodeMirror.fromTextArea(elm[0], options);
719
- };
720
-
721
- // Initialize the code mirror widget
722
- updateCodeMirror(uiCodemirrorGet());
723
-
724
- // Now watch to see if the codemirror attribute gets updated
725
- scope.$watch(uiCodemirrorGet, updateCodeMirror, true);
726
-
727
- // CodeMirror expects a string, so make sure it gets one.
728
- // This does not change the model.
729
- ngModel.$formatters.push(function (value) {
730
- if (angular.isUndefined(value) || value === null) {
731
- return '';
732
- }
733
- else if (angular.isObject(value) || angular.isArray(value)) {
734
- throw new Error('ui-codemirror cannot use an object or an array as a model');
735
- }
736
- return value;
737
- });
738
-
739
- // Override the ngModelController $render method, which is what gets called when the model is updated.
740
- // This takes care of the synchronizing the codeMirror element with the underlying model, in the case that it is changed by something else.
741
- ngModel.$render = function () {
742
- codemirror.setValue(ngModel.$viewValue);
743
- };
744
- }
745
- };
746
- }]);
747
759
  /**
748
- * Binds a TinyMCE widget to <textarea> elements.
760
+ * Add a clear button to form inputs to reset their value
749
761
  */
750
- angular.module('ui.directives').directive('uiTinymce', ['ui.config', function (uiConfig) {
751
- uiConfig.tinymce = uiConfig.tinymce || {};
762
+ angular.module('ui.directives').directive('uiReset', ['ui.config', function (uiConfig) {
763
+ var resetValue = null;
764
+ if (uiConfig.reset !== undefined)
765
+ resetValue = uiConfig.reset;
752
766
  return {
753
767
  require: 'ngModel',
754
- link: function (scope, elm, attrs, ngModel) {
755
- var expression,
756
- options = {
757
- // Update model on button click
758
- onchange_callback: function (inst) {
759
- if (inst.isDirty()) {
760
- inst.save();
761
- ngModel.$setViewValue(elm.val());
762
- if (!scope.$$phase)
763
- scope.$apply();
764
- }
765
- },
766
- // Update model on keypress
767
- handle_event_callback: function (e) {
768
- if (this.isDirty()) {
769
- this.save();
770
- ngModel.$setViewValue(elm.val());
771
- if (!scope.$$phase)
772
- scope.$apply();
773
- }
774
- return true; // Continue handling
775
- },
776
- // Update model when calling setContent (such as from the source editor popup)
777
- setup: function (ed) {
778
- ed.onSetContent.add(function (ed, o) {
779
- if (ed.isDirty()) {
780
- ed.save();
781
- ngModel.$setViewValue(elm.val());
782
- if (!scope.$$phase)
783
- scope.$apply();
784
- }
785
- });
786
- }
787
- };
788
- if (attrs.uiTinymce) {
789
- expression = scope.$eval(attrs.uiTinymce);
790
- } else {
791
- expression = {};
792
- }
793
- angular.extend(options, uiConfig.tinymce, expression);
794
- setTimeout(function () {
795
- elm.tinymce(options);
768
+ link: function (scope, elm, attrs, ctrl) {
769
+ var aElement;
770
+ aElement = angular.element('<a class="ui-reset" />');
771
+ elm.wrap('<span class="ui-resetwrap" />').after(aElement);
772
+ aElement.bind('click', function (e) {
773
+ e.preventDefault();
774
+ scope.$apply(function () {
775
+ if (attrs.uiReset)
776
+ ctrl.$setViewValue(scope.$eval(attrs.uiReset));
777
+ else
778
+ ctrl.$setViewValue(resetValue);
779
+ ctrl.$render();
780
+ });
796
781
  });
797
782
  }
798
- };
799
- }]);
800
-
801
- /*
802
- * Defines the ui-if tag. This removes/adds an element from the dom depending on a condition
803
- * Originally created by @tigbro, for the @jquery-mobile-angular-adapter
804
- * https://github.com/tigbro/jquery-mobile-angular-adapter
783
+ };
784
+ }]);
785
+
786
+ /**
787
+ * Set a $uiRoute boolean to see if the current route matches
805
788
  */
806
- angular.module('ui.directives').directive('uiIf', [function () {
789
+ angular.module('ui.directives').directive('uiRoute', ['$location', '$parse', function ($location, $parse) {
807
790
  return {
808
- transclude: 'element',
809
- priority: 1000,
810
- terminal: true,
811
- restrict: 'A',
812
- compile: function (element, attr, linker) {
813
- return function (scope, iterStartElement, attr) {
814
- iterStartElement[0].doNotMove = true;
815
- var expression = attr.uiIf;
816
- var lastElement;
817
- var lastScope;
818
- scope.$watch(expression, function (newValue) {
819
- if (lastElement) {
820
- lastElement.remove();
821
- lastElement = null;
822
- }
823
- if (lastScope) {
824
- lastScope.$destroy();
825
- lastScope = null;
826
- }
827
- if (newValue) {
828
- lastScope = scope.$new();
829
- linker(lastScope, function (clone) {
830
- lastElement = clone;
831
- iterStartElement.after(clone);
832
- });
833
- }
834
- // Note: need to be parent() as jquery cannot trigger events on comments
835
- // (angular creates a comment node when using transclusion, as ng-repeat does).
836
- iterStartElement.parent().trigger("$childrenChanged");
791
+ restrict: 'AC',
792
+ compile: function(tElement, tAttrs) {
793
+ var useProperty;
794
+ if (tAttrs.uiRoute) {
795
+ useProperty = 'uiRoute';
796
+ } else if (tAttrs.ngHref) {
797
+ useProperty = 'ngHref';
798
+ } else if (tAttrs.href) {
799
+ useProperty = 'href';
800
+ } else {
801
+ throw new Error('uiRoute missing a route or href property on ' + tElement[0]);
802
+ }
803
+ return function ($scope, elm, attrs) {
804
+ var modelSetter = $parse(attrs.ngModel || attrs.routeModel || '$uiRoute').assign;
805
+ var watcher = angular.noop;
806
+
807
+ // Used by href and ngHref
808
+ function staticWatcher(newVal) {
809
+ if ((hash = newVal.indexOf('#')) > -1)
810
+ newVal = newVal.substr(hash + 1);
811
+ watcher = function watchHref() {
812
+ modelSetter($scope, ($location.path().indexOf(newVal) > -1));
813
+ };
814
+ watcher();
815
+ }
816
+ // Used by uiRoute
817
+ function regexWatcher(newVal) {
818
+ if ((hash = newVal.indexOf('#')) > -1)
819
+ newVal = newVal.substr(hash + 1);
820
+ watcher = function watchRegex() {
821
+ var regexp = new RegExp('^' + newVal + '$', ['i']);
822
+ modelSetter($scope, regexp.test($location.path()));
823
+ };
824
+ watcher();
825
+ }
826
+
827
+ switch (useProperty) {
828
+ case 'uiRoute':
829
+ // if uiRoute={{}} this will be undefined, otherwise it will have a value and $observe() never gets triggered
830
+ if (attrs.uiRoute)
831
+ regexWatcher(attrs.uiRoute);
832
+ else
833
+ attrs.$observe('uiRoute', regexWatcher);
834
+ break;
835
+ case 'ngHref':
836
+ // Setup watcher() every time ngHref changes
837
+ if (attrs.ngHref)
838
+ staticWatcher(attrs.ngHref);
839
+ else
840
+ attrs.$observe('ngHref', staticWatcher);
841
+ break;
842
+ case 'href':
843
+ // Setup watcher()
844
+ staticWatcher(attrs.href);
845
+ }
846
+
847
+ $scope.$on('$routeChangeSuccess', function(){
848
+ watcher();
837
849
  });
838
- };
850
+ }
839
851
  }
840
852
  };
841
853
  }]);
854
+
842
855
  /*global angular, $, document*/
843
856
  /**
844
857
  * Adds a 'ui-scrollfix' class to the element when the page scrolls past it's position.
@@ -879,69 +892,131 @@ angular.module('ui.directives').directive('uiScrollfix', ['$window', function ($
879
892
  };
880
893
  }]);
881
894
 
882
- /*
883
- * AngularJs Fullcalendar Wrapper for the JQuery FullCalendar
884
- * inspired by http://arshaw.com/fullcalendar/
885
- *
886
- * Basic Angular Calendar Directive that takes in live events as the ng-model and watches that event array for changes, to update the view accordingly.
887
- * Can also take in an event url as a source object(s) and feed the events per view.
888
- *
889
- */
895
+ /**
896
+ * Enhanced Select2 Dropmenus
897
+ *
898
+ * @AJAX Mode - When in this mode, your value will be an object (or array of objects) of the data used by Select2
899
+ * This change is so that you do not have to do an additional query yourself on top of Select2's own query
900
+ * @params [options] {object} The configuration options passed to $.fn.select2(). Refer to the documentation
901
+ */
902
+ angular.module('ui.directives').directive('uiSelect2', ['ui.config', '$timeout', function (uiConfig, $timeout) {
903
+ var options = {};
904
+ if (uiConfig.select2) {
905
+ angular.extend(options, uiConfig.select2);
906
+ }
907
+ return {
908
+ require: '?ngModel',
909
+ compile: function (tElm, tAttrs) {
910
+ var watch,
911
+ repeatOption,
912
+ repeatAttr,
913
+ isSelect = tElm.is('select'),
914
+ isMultiple = (tAttrs.multiple !== undefined);
890
915
 
891
- angular.module('ui.directives').directive('uiCalendar',['ui.config', '$parse', function (uiConfig,$parse) {
892
- uiConfig.uiCalendar = uiConfig.uiCalendar || {};
893
- //returns the fullcalendar
894
- return {
895
- require: 'ngModel',
896
- restrict: 'A',
897
- scope: {
898
- events: "=ngModel"
899
- },
900
- link: function(scope, elm, $attrs) {
901
- var ngModel = $parse($attrs.ngModel);
902
- //update method that is called on load and whenever the events array is changed.
903
- function update() {
904
- //Default View Options
905
- var expression,
906
- options = {
907
- header: {
908
- left: 'prev,next today',
909
- center: 'title',
910
- right: 'month,agendaWeek,agendaDay'
911
- },
912
- // add event name to title attribute on mouseover.
913
- eventMouseover: function(event, jsEvent, view) {
914
- if (view.name !== 'agendaDay') {
915
- $(jsEvent.target).attr('title', event.title);
916
- }
917
- },
918
-
919
- // Calling the events from the scope through the ng-model binding attribute.
920
- events: scope.events
921
- };
922
- //if attrs have been entered to the directive, then create a relative expression.
923
- if ($attrs.uiCalendar){
924
- expression = scope.$eval($attrs.uiCalendar);
916
+ // Enable watching of the options dataset if in use
917
+ if (tElm.is('select')) {
918
+ repeatOption = tElm.find('option[ng-repeat], option[data-ng-repeat]');
919
+
920
+ if (repeatOption.length) {
921
+ repeatAttr = repeatOption.attr('ng-repeat') || repeatOption.attr('data-ng-repeat');
922
+ watch = jQuery.trim(repeatAttr.split('|')[0]).split(' ').pop();
923
+ }
924
+ }
925
+
926
+ return function (scope, elm, attrs, controller) {
927
+ // instance-specific options
928
+ var opts = angular.extend({}, options, scope.$eval(attrs.uiSelect2));
929
+
930
+ if (isSelect) {
931
+ // Use <select multiple> instead
932
+ delete opts.multiple;
933
+ delete opts.initSelection;
934
+ } else if (isMultiple) {
935
+ opts.multiple = true;
936
+ }
937
+
938
+ if (controller) {
939
+ // Watch the model for programmatic changes
940
+ controller.$render = function () {
941
+ if (isSelect) {
942
+ elm.select2('val', controller.$modelValue);
943
+ } else {
944
+ if (isMultiple) {
945
+ if (!controller.$modelValue) {
946
+ elm.select2('data', []);
947
+ } else if (angular.isArray(controller.$modelValue)) {
948
+ elm.select2('data', controller.$modelValue);
949
+ } else {
950
+ elm.select2('val', controller.$modelValue);
951
+ }
952
+ } else {
953
+ if (angular.isObject(controller.$modelValue)) {
954
+ elm.select2('data', controller.$modelValue);
955
+ } else {
956
+ elm.select2('val', controller.$modelValue);
957
+ }
925
958
  }
926
- else{
927
- expression = {};
928
- }
929
- //extend the options to suite the custom directive.
930
- angular.extend(options, uiConfig.uiCalendar, expression);
931
- //call fullCalendar from an empty html tag, to keep angular happy.
932
- elm.html('').fullCalendar(options);
933
959
  }
934
- //on load update call.
935
- update();
936
- //watching the length of the array to create a more efficient update process.
937
- scope.$watch( 'events.length', function( newVal, oldVal )
938
- {
939
- //update the calendar on every change to events.length
940
- update();
941
- }, true );
960
+ };
961
+
962
+ // Watch the options dataset for changes
963
+ if (watch) {
964
+ scope.$watch(watch, function (newVal, oldVal, scope) {
965
+ if (!newVal) return;
966
+ // Delayed so that the options have time to be rendered
967
+ $timeout(function () {
968
+ elm.select2('val', controller.$viewValue);
969
+ // Refresh angular to remove the superfluous option
970
+ elm.trigger('change');
971
+ });
972
+ });
973
+ }
974
+
975
+ if (!isSelect) {
976
+ // Set the view and model value and update the angular template manually for the ajax/multiple select2.
977
+ elm.bind("change", function () {
978
+ scope.$apply(function () {
979
+ controller.$setViewValue(elm.select2('data'));
980
+ });
981
+ });
982
+
983
+ if (opts.initSelection) {
984
+ var initSelection = opts.initSelection;
985
+ opts.initSelection = function (element, callback) {
986
+ initSelection(element, function (value) {
987
+ controller.$setViewValue(value);
988
+ callback(value);
989
+ });
990
+ };
991
+ }
992
+ }
942
993
  }
943
- };
994
+
995
+ attrs.$observe('disabled', function (value) {
996
+ elm.select2(value && 'disable' || 'enable');
997
+ });
998
+
999
+ if (attrs.ngMultiple) {
1000
+ scope.$watch(attrs.ngMultiple, function(newVal) {
1001
+ elm.select2(opts);
1002
+ });
1003
+ }
1004
+
1005
+ // Set initial value since Angular doesn't
1006
+ elm.val(scope.$eval(attrs.ngModel));
1007
+
1008
+ // Initialize the plugin late so that the injected DOM does not disrupt the template compiler
1009
+ $timeout(function () {
1010
+ elm.select2(opts);
1011
+ // Not sure if I should just check for !isSelect OR if I should check for 'tags' key
1012
+ if (!opts.initSelection && !isSelect)
1013
+ controller.$setViewValue(elm.select2('data'));
1014
+ });
1015
+ };
1016
+ }
1017
+ };
944
1018
  }]);
1019
+
945
1020
  /**
946
1021
  * uiShow Directive
947
1022
  *
@@ -1004,189 +1079,237 @@ angular.module('ui.directives').directive('uiShow', [function () {
1004
1079
  }]);
1005
1080
 
1006
1081
  /*
1007
- Gives the ability to style currency based on its sign.
1008
- */
1009
- angular.module('ui.directives').directive('uiCurrency', ['ui.config', 'currencyFilter' , function (uiConfig, currencyFilter) {
1010
- var options = {
1011
- pos: 'ui-currency-pos',
1012
- neg: 'ui-currency-neg',
1013
- zero: 'ui-currency-zero'
1014
- };
1015
- if (uiConfig.currency) {
1016
- angular.extend(options, uiConfig.currency);
1017
- }
1018
- return {
1019
- restrict: 'EAC',
1020
- require: 'ngModel',
1021
- link: function (scope, element, attrs, controller) {
1022
- var opts, // instance-specific options
1023
- renderview,
1024
- value;
1082
+ jQuery UI Sortable plugin wrapper
1025
1083
 
1026
- opts = angular.extend({}, options, scope.$eval(attrs.uiCurrency));
1084
+ @param [ui-sortable] {object} Options to pass to $.fn.sortable() merged onto ui.config
1085
+ */
1086
+ angular.module('ui.directives').directive('uiSortable', [
1087
+ 'ui.config', function(uiConfig) {
1088
+ return {
1089
+ require: '?ngModel',
1090
+ link: function(scope, element, attrs, ngModel) {
1091
+ var onReceive, onRemove, onStart, onUpdate, opts, _receive, _remove, _start, _update;
1027
1092
 
1028
- renderview = function (viewvalue) {
1029
- var num;
1030
- num = viewvalue * 1;
1031
- if (num > 0) {
1032
- element.addClass(opts.pos);
1033
- } else {
1034
- element.removeClass(opts.pos);
1035
- }
1036
- if (num < 0) {
1037
- element.addClass(opts.neg);
1038
- } else {
1039
- element.removeClass(opts.neg);
1040
- }
1041
- if (num === 0) {
1042
- element.addClass(opts.zero);
1043
- } else {
1044
- element.removeClass(opts.zero);
1045
- }
1046
- if (viewvalue === '') {
1047
- element.text('');
1048
- } else {
1049
- element.text(currencyFilter(num, opts.symbol));
1050
- }
1051
- return true;
1052
- };
1093
+ opts = angular.extend({}, uiConfig.sortable, scope.$eval(attrs.uiSortable));
1053
1094
 
1054
- controller.$render = function () {
1055
- value = controller.$viewValue;
1056
- element.val(value);
1057
- renderview(value);
1058
- };
1095
+ if (ngModel) {
1059
1096
 
1060
- }
1061
- };
1062
- }]);
1097
+ ngModel.$render = function() {
1098
+ element.sortable( "refresh" );
1099
+ };
1063
1100
 
1064
- /*global angular */
1065
- /*
1066
- jQuery UI Datepicker plugin wrapper
1101
+ onStart = function(e, ui) {
1102
+ // Save position of dragged item
1103
+ ui.item.sortable = { index: ui.item.index() };
1104
+ };
1067
1105
 
1068
- @param [ui-date] {object} Options to pass to $.fn.datepicker() merged onto ui.config
1069
- */
1106
+ onUpdate = function(e, ui) {
1107
+ // For some reason the reference to ngModel in stop() is wrong
1108
+ ui.item.sortable.resort = ngModel;
1109
+ };
1070
1110
 
1071
- angular.module('ui.directives')
1111
+ onReceive = function(e, ui) {
1112
+ ui.item.sortable.relocate = true;
1113
+ // added item to array into correct position and set up flag
1114
+ ngModel.$modelValue.splice(ui.item.index(), 0, ui.item.sortable.moved);
1115
+ };
1072
1116
 
1073
- .directive('uiDate', ['ui.config', function (uiConfig) {
1074
- 'use strict';
1075
- var options;
1076
- options = {};
1077
- if (angular.isObject(uiConfig.date)) {
1078
- angular.extend(options, uiConfig.date);
1079
- }
1080
- return {
1081
- require:'?ngModel',
1082
- link:function (scope, element, attrs, controller) {
1083
- var getOptions = function () {
1084
- return angular.extend({}, uiConfig.date, scope.$eval(attrs.uiDate));
1085
- };
1086
- var initDateWidget = function () {
1087
- var opts = getOptions();
1117
+ onRemove = function(e, ui) {
1118
+ // copy data into item
1119
+ if (ngModel.$modelValue.length === 1) {
1120
+ ui.item.sortable.moved = ngModel.$modelValue.splice(0, 1)[0];
1121
+ } else {
1122
+ ui.item.sortable.moved = ngModel.$modelValue.splice(ui.item.sortable.index, 1)[0];
1123
+ }
1124
+ };
1088
1125
 
1089
- // If we have a controller (i.e. ngModelController) then wire it up
1090
- if (controller) {
1091
- var updateModel = function () {
1092
- scope.$apply(function () {
1093
- var date = element.datepicker("getDate");
1094
- element.datepicker("setDate", element.val());
1095
- controller.$setViewValue(date);
1096
- });
1126
+ onStop = function(e, ui) {
1127
+ // digest all prepared changes
1128
+ if (ui.item.sortable.resort && !ui.item.sortable.relocate) {
1129
+
1130
+ // Fetch saved and current position of dropped element
1131
+ var end, start;
1132
+ start = ui.item.sortable.index;
1133
+ end = ui.item.index();
1134
+ if (start < end)
1135
+ end--;
1136
+
1137
+ // Reorder array and apply change to scope
1138
+ ui.item.sortable.resort.$modelValue.splice(end, 0, ui.item.sortable.resort.$modelValue.splice(start, 1)[0]);
1139
+
1140
+ }
1141
+ if (ui.item.sortable.resort || ui.item.sortable.relocate) {
1142
+ scope.$apply();
1143
+ }
1144
+ };
1145
+
1146
+ // If user provided 'start' callback compose it with onStart function
1147
+ _start = opts.start;
1148
+ opts.start = function(e, ui) {
1149
+ onStart(e, ui);
1150
+ if (typeof _start === "function")
1151
+ _start(e, ui);
1152
+ };
1153
+
1154
+ // If user provided 'start' callback compose it with onStart function
1155
+ _stop = opts.stop;
1156
+ opts.stop = function(e, ui) {
1157
+ onStop(e, ui);
1158
+ if (typeof _stop === "function")
1159
+ _stop(e, ui);
1160
+ };
1161
+
1162
+ // If user provided 'update' callback compose it with onUpdate function
1163
+ _update = opts.update;
1164
+ opts.update = function(e, ui) {
1165
+ onUpdate(e, ui);
1166
+ if (typeof _update === "function")
1167
+ _update(e, ui);
1097
1168
  };
1098
- if (opts.onSelect) {
1099
- // Caller has specified onSelect, so call this as well as updating the model
1100
- var userHandler = opts.onSelect;
1101
- opts.onSelect = function (value, picker) {
1102
- updateModel();
1103
- return userHandler(value, picker);
1104
- };
1105
- } else {
1106
- // No onSelect already specified so just update the model
1107
- opts.onSelect = updateModel;
1108
- }
1109
- // In case the user changes the text directly in the input box
1110
- element.bind('change', updateModel);
1111
1169
 
1112
- // Update the date picker when the model changes
1113
- controller.$render = function () {
1114
- var date = controller.$viewValue;
1115
- if ( angular.isDefined(date) && date !== null && !angular.isDate(date) ) {
1116
- throw new Error('ng-Model value must be a Date object - currently it is a ' + typeof date + ' - use ui-date-format to convert it from a string');
1117
- }
1118
- element.datepicker("setDate", date);
1170
+ // If user provided 'receive' callback compose it with onReceive function
1171
+ _receive = opts.receive;
1172
+ opts.receive = function(e, ui) {
1173
+ onReceive(e, ui);
1174
+ if (typeof _receive === "function")
1175
+ _receive(e, ui);
1176
+ };
1177
+
1178
+ // If user provided 'remove' callback compose it with onRemove function
1179
+ _remove = opts.remove;
1180
+ opts.remove = function(e, ui) {
1181
+ onRemove(e, ui);
1182
+ if (typeof _remove === "function")
1183
+ _remove(e, ui);
1119
1184
  };
1120
1185
  }
1121
- // If we don't destroy the old one it doesn't update properly when the config changes
1122
- element.datepicker('destroy');
1123
- // Create the new datepicker widget
1124
- element.datepicker(opts);
1125
- // Force a render to override whatever is in the input text box
1126
- controller.$render();
1127
- };
1128
- // Watch for changes to the directives options
1129
- scope.$watch(getOptions, initDateWidget, true);
1130
- }
1131
- };
1132
- }
1133
- ])
1134
1186
 
1135
- .directive('uiDateFormat', [function() {
1136
- var directive = {
1137
- require:'ngModel',
1138
- link: function(scope, element, attrs, modelCtrl) {
1139
- if ( attrs.uiDateFormat === '' ) {
1140
- // Default to ISO formatting
1141
- modelCtrl.$formatters.push(function(value) {
1142
- if (angular.isString(value) ) {
1143
- return new Date(value);
1144
- }
1145
- });
1146
- modelCtrl.$parsers.push(function(value){
1147
- if (value) {
1148
- return value.toISOString();
1149
- }
1150
- });
1151
- } else {
1152
- var format = attrs.uiDateFormat;
1153
- // Use the datepicker with the attribute value as the format string to convert to and from a string
1154
- modelCtrl.$formatters.push(function(value) {
1155
- if (angular.isString(value) ) {
1156
- return $.datepicker.parseDate(format, value);
1157
- }
1158
- });
1159
- modelCtrl.$parsers.push(function(value){
1160
- if (value) {
1161
- return $.datepicker.formatDate(format, value);
1162
- }
1163
- });
1187
+ // Create sortable
1188
+ element.sortable(opts);
1164
1189
  }
1165
- }
1166
- };
1167
- return directive;
1168
- }]);
1190
+ };
1191
+ }
1192
+ ]);
1169
1193
 
1170
1194
  /**
1171
- * Wraps the
1172
- * @param text {string} haystack to search through
1173
- * @param search {string} needle to search for
1174
- * @param [caseSensitive] {boolean} optional boolean to use case-sensitive searching
1195
+ * Binds a TinyMCE widget to <textarea> elements.
1175
1196
  */
1176
- angular.module('ui.filters').filter('highlight', function () {
1177
- return function (text, search, caseSensitive) {
1178
- if (search || angular.isNumber(search)) {
1179
- text = text.toString();
1180
- search = search.toString();
1181
- if (caseSensitive) {
1182
- return text.split(search).join('<span class="ui-match">' + search + '</span>');
1197
+ angular.module('ui.directives').directive('uiTinymce', ['ui.config', function (uiConfig) {
1198
+ uiConfig.tinymce = uiConfig.tinymce || {};
1199
+ return {
1200
+ require: 'ngModel',
1201
+ link: function (scope, elm, attrs, ngModel) {
1202
+ var expression,
1203
+ options = {
1204
+ // Update model on button click
1205
+ onchange_callback: function (inst) {
1206
+ if (inst.isDirty()) {
1207
+ inst.save();
1208
+ ngModel.$setViewValue(elm.val());
1209
+ if (!scope.$$phase)
1210
+ scope.$apply();
1211
+ }
1212
+ },
1213
+ // Update model on keypress
1214
+ handle_event_callback: function (e) {
1215
+ if (this.isDirty()) {
1216
+ this.save();
1217
+ ngModel.$setViewValue(elm.val());
1218
+ if (!scope.$$phase)
1219
+ scope.$apply();
1220
+ }
1221
+ return true; // Continue handling
1222
+ },
1223
+ // Update model when calling setContent (such as from the source editor popup)
1224
+ setup: function (ed) {
1225
+ ed.onSetContent.add(function (ed, o) {
1226
+ if (ed.isDirty()) {
1227
+ ed.save();
1228
+ ngModel.$setViewValue(elm.val());
1229
+ if (!scope.$$phase)
1230
+ scope.$apply();
1231
+ }
1232
+ });
1233
+ }
1234
+ };
1235
+ if (attrs.uiTinymce) {
1236
+ expression = scope.$eval(attrs.uiTinymce);
1183
1237
  } else {
1184
- return text.replace(new RegExp(search, 'gi'), '<span class="ui-match">$&</span>');
1238
+ expression = {};
1185
1239
  }
1186
- } else {
1187
- return text;
1240
+ angular.extend(options, uiConfig.tinymce, expression);
1241
+ setTimeout(function () {
1242
+ elm.tinymce(options);
1243
+ });
1188
1244
  }
1189
1245
  };
1246
+ }]);
1247
+
1248
+ /**
1249
+ * General-purpose validator for ngModel.
1250
+ * angular.js comes with several built-in validation mechanism for input fields (ngRequired, ngPattern etc.) but using
1251
+ * an arbitrary validation function requires creation of a custom formatters and / or parsers.
1252
+ * The ui-validate directive makes it easy to use any function(s) defined in scope as a validator function(s).
1253
+ * A validator function will trigger validation on both model and input changes.
1254
+ *
1255
+ * @example <input ui-validate=" 'myValidatorFunction($value)' ">
1256
+ * @example <input ui-validate="{ foo : '$value > anotherModel', bar : 'validateFoo($value)' }">
1257
+ * @example <input ui-validate="{ foo : '$value > anotherModel' }" ui-validate-watch=" 'anotherModel' ">
1258
+ * @example <input ui-validate="{ foo : '$value > anotherModel', bar : 'validateFoo($value)' }" ui-validate-watch=" { foo : 'anotherModel' } ">
1259
+ *
1260
+ * @param ui-validate {string|object literal} If strings is passed it should be a scope's function to be used as a validator.
1261
+ * If an object literal is passed a key denotes a validation error key while a value should be a validator function.
1262
+ * In both cases validator function should take a value to validate as its argument and should return true/false indicating a validation result.
1263
+ */
1264
+ angular.module('ui.directives').directive('uiValidate', function () {
1265
+
1266
+ return {
1267
+ restrict: 'A',
1268
+ require: 'ngModel',
1269
+ link: function (scope, elm, attrs, ctrl) {
1270
+ var validateFn, watch, validators = {},
1271
+ validateExpr = scope.$eval(attrs.uiValidate);
1272
+
1273
+ if (!validateExpr) return;
1274
+
1275
+ if (angular.isString(validateExpr)) {
1276
+ validateExpr = { validator: validateExpr };
1277
+ }
1278
+
1279
+ angular.forEach(validateExpr, function (expression, key) {
1280
+ validateFn = function (valueToValidate) {
1281
+ if (scope.$eval(expression, { '$value' : valueToValidate })) {
1282
+ ctrl.$setValidity(key, true);
1283
+ return valueToValidate;
1284
+ } else {
1285
+ ctrl.$setValidity(key, false);
1286
+ return undefined;
1287
+ }
1288
+ };
1289
+ validators[key] = validateFn;
1290
+ ctrl.$formatters.push(validateFn);
1291
+ ctrl.$parsers.push(validateFn);
1292
+ });
1293
+
1294
+ // Support for ui-validate-watch
1295
+ if (attrs.uiValidateWatch) {
1296
+ watch = scope.$eval(attrs.uiValidateWatch);
1297
+ if (angular.isString(watch)) {
1298
+ scope.$watch(watch, function(){
1299
+ angular.forEach(validators, function(validatorFn, key){
1300
+ validatorFn(ctrl.$modelValue);
1301
+ });
1302
+ });
1303
+ } else {
1304
+ angular.forEach(watch, function(expression, key){
1305
+ scope.$watch(expression, function(){
1306
+ validators[key](ctrl.$modelValue);
1307
+ });
1308
+ });
1309
+ }
1310
+ }
1311
+ }
1312
+ };
1190
1313
  });
1191
1314
 
1192
1315
 
@@ -1225,48 +1348,24 @@ angular.module('ui.filters').filter('format', function(){
1225
1348
  });
1226
1349
 
1227
1350
  /**
1228
- * Filters out all duplicate items from an array by checking the specified key
1229
- * @param [key] {string} the name of the attribute of each object to compare for uniqueness
1230
- if the key is empty, the entire object will be compared
1231
- if the key === false then no filtering will be performed
1232
- * @return {array}
1351
+ * Wraps the
1352
+ * @param text {string} haystack to search through
1353
+ * @param search {string} needle to search for
1354
+ * @param [caseSensitive] {boolean} optional boolean to use case-sensitive searching
1233
1355
  */
1234
- angular.module('ui.filters').filter('unique', function () {
1235
-
1236
- return function (items, filterOn) {
1237
-
1238
- if (filterOn === false) {
1239
- return items;
1240
- }
1241
-
1242
- if ((filterOn || angular.isUndefined(filterOn)) && angular.isArray(items)) {
1243
- var hashCheck = {}, newItems = [];
1244
-
1245
- var extractValueToCompare = function (item) {
1246
- if (angular.isObject(item) && angular.isString(filterOn)) {
1247
- return item[filterOn];
1248
- } else {
1249
- return item;
1250
- }
1251
- };
1252
-
1253
- angular.forEach(items, function (item) {
1254
- var valueToCheck, isDuplicate = false;
1255
-
1256
- for (var i = 0; i < newItems.length; i++) {
1257
- if (angular.equals(extractValueToCompare(newItems[i]), extractValueToCompare(item))) {
1258
- isDuplicate = true;
1259
- break;
1260
- }
1261
- }
1262
- if (!isDuplicate) {
1263
- newItems.push(item);
1264
- }
1265
-
1266
- });
1267
- items = newItems;
1356
+ angular.module('ui.filters').filter('highlight', function () {
1357
+ return function (text, search, caseSensitive) {
1358
+ if (search || angular.isNumber(search)) {
1359
+ text = text.toString();
1360
+ search = search.toString();
1361
+ if (caseSensitive) {
1362
+ return text.split(search).join('<span class="ui-match">' + search + '</span>');
1363
+ } else {
1364
+ return text.replace(new RegExp(search, 'gi'), '<span class="ui-match">$&</span>');
1365
+ }
1366
+ } else {
1367
+ return text;
1268
1368
  }
1269
- return items;
1270
1369
  };
1271
1370
  });
1272
1371
 
@@ -1314,3 +1413,49 @@ angular.module('ui.filters').filter('inflector', function () {
1314
1413
  }
1315
1414
  };
1316
1415
  });
1416
+
1417
+ /**
1418
+ * Filters out all duplicate items from an array by checking the specified key
1419
+ * @param [key] {string} the name of the attribute of each object to compare for uniqueness
1420
+ if the key is empty, the entire object will be compared
1421
+ if the key === false then no filtering will be performed
1422
+ * @return {array}
1423
+ */
1424
+ angular.module('ui.filters').filter('unique', function () {
1425
+
1426
+ return function (items, filterOn) {
1427
+
1428
+ if (filterOn === false) {
1429
+ return items;
1430
+ }
1431
+
1432
+ if ((filterOn || angular.isUndefined(filterOn)) && angular.isArray(items)) {
1433
+ var hashCheck = {}, newItems = [];
1434
+
1435
+ var extractValueToCompare = function (item) {
1436
+ if (angular.isObject(item) && angular.isString(filterOn)) {
1437
+ return item[filterOn];
1438
+ } else {
1439
+ return item;
1440
+ }
1441
+ };
1442
+
1443
+ angular.forEach(items, function (item) {
1444
+ var valueToCheck, isDuplicate = false;
1445
+
1446
+ for (var i = 0; i < newItems.length; i++) {
1447
+ if (angular.equals(extractValueToCompare(newItems[i]), extractValueToCompare(item))) {
1448
+ isDuplicate = true;
1449
+ break;
1450
+ }
1451
+ }
1452
+ if (!isDuplicate) {
1453
+ newItems.push(item);
1454
+ }
1455
+
1456
+ });
1457
+ items = newItems;
1458
+ }
1459
+ return items;
1460
+ };
1461
+ });