Skip to content
This repository was archived by the owner on Sep 8, 2020. It is now read-only.

Commit 07234ae

Browse files
committed
Merge pull request #36 from slopjong/master
Data binding support added for ace options and JSDoc blocks added
2 parents b2024c1 + 1fea228 commit 07234ae

File tree

2 files changed

+220
-55
lines changed

2 files changed

+220
-55
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ Although, _ui-ace_ automatically handles some handy options :
5656
+ _mode_ : to set the mode to use.
5757
+ _onLoad_ : callback when the editor has finished loading (see [below](#ace-instance-direct-access)).
5858
+ _onChange_ : callback when the editor content is changed ().
59+
+ _onBlur_ : callback when the editor is blurred ().
5960

6061
```html
6162
<div ui-ace="{

src/ui-ace.js

Lines changed: 219 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,74 +1,207 @@
11
'use strict';
22

33
/**
4-
* Binds a ACE Ediitor widget
4+
* Binds a ACE Editor widget
55
*/
66
angular.module('ui.ace', [])
77
.constant('uiAceConfig', {})
88
.directive('uiAce', ['uiAceConfig', function (uiAceConfig) {
9+
910
if (angular.isUndefined(window.ace)) {
1011
throw new Error('ui-ace need ace to work... (o rly?)');
1112
}
13+
14+
/**
15+
* Sets editor options such as the wrapping mode or the syntax checker.
16+
*
17+
* The supported options are:
18+
*
19+
* <ul>
20+
* <li>showGutter</li>
21+
* <li>useWrapMode</li>
22+
* <li>onLoad</li>
23+
* <li>theme</li>
24+
* <li>mode</li>
25+
* </ul>
26+
*
27+
* @param acee
28+
* @param session ACE editor session
29+
* @param {object} opts Options to be set
30+
*/
31+
var setOptions = function(acee, session, opts) {
32+
33+
// Boolean options
34+
if (angular.isDefined(opts.showGutter)) {
35+
acee.renderer.setShowGutter(opts.showGutter);
36+
}
37+
if (angular.isDefined(opts.useWrapMode)) {
38+
session.setUseWrapMode(opts.useWrapMode);
39+
}
40+
if (angular.isDefined(opts.showInvisibles)) {
41+
acee.renderer.setShowInvisibles(opts.showInvisibles);
42+
}
43+
if (angular.isDefined(opts.showIndentGuides)) {
44+
acee.renderer.setDisplayIndentGuides(opts.showIndentGuides);
45+
}
46+
if (angular.isDefined(opts.useSoftTabs)) {
47+
session.setUseSoftTabs(opts.useSoftTabs);
48+
}
49+
50+
// commands
51+
if (angular.isDefined(opts.disableSearch) && opts.disableSearch) {
52+
acee.commands.addCommands([
53+
{
54+
name: 'unfind',
55+
bindKey: {
56+
win: 'Ctrl-F',
57+
mac: 'Command-F'
58+
},
59+
exec: function () {
60+
return false;
61+
},
62+
readOnly: true
63+
}
64+
]);
65+
}
66+
67+
// onLoad callback
68+
if (angular.isFunction(opts.onLoad)) {
69+
opts.onLoad(acee);
70+
}
71+
72+
// Basic options
73+
if (angular.isString(opts.theme)) {
74+
acee.setTheme('ace/theme/' + opts.theme);
75+
}
76+
if (angular.isString(opts.mode)) {
77+
session.setMode('ace/mode/' + opts.mode);
78+
}
79+
};
80+
1281
return {
1382
restrict: 'EA',
1483
require: '?ngModel',
1584
link: function (scope, elm, attrs, ngModel) {
16-
var options, opts, acee, session, onChange;
17-
18-
options = uiAceConfig.ace || {};
19-
opts = angular.extend({}, options, scope.$eval(attrs.uiAce));
20-
21-
acee = window.ace.edit(elm[0]);
22-
session = acee.getSession();
23-
24-
onChange = function (callback) {
25-
return function (e) {
26-
var newValue = session.getValue();
27-
if (newValue !== scope.$eval(attrs.value) && !scope.$$phase && !scope.$root.$$phase) {
28-
if (angular.isDefined(ngModel)) {
29-
scope.$apply(function () {
30-
ngModel.$setViewValue(newValue);
31-
});
32-
}
3385

34-
/**
35-
* Call the user onChange function.
36-
*/
37-
if (angular.isDefined(callback)) {
38-
scope.$apply(function () {
39-
if (angular.isFunction(callback)) {
40-
callback(e, acee);
41-
}
42-
else {
43-
throw new Error('ui-ace use a function as callback.');
44-
}
45-
});
46-
}
47-
}
48-
};
49-
};
86+
/**
87+
* Corresponds the uiAceConfig ACE configuration.
88+
* @type object
89+
*/
90+
var options = uiAceConfig.ace || {};
5091

92+
/**
93+
* uiAceConfig merged with user options via json in attribute or data binding
94+
* @type object
95+
*/
96+
var opts = angular.extend({}, options, scope.$eval(attrs.uiAce));
5197

52-
// Boolean options
53-
if (angular.isDefined(opts.showGutter)) {
54-
acee.renderer.setShowGutter(opts.showGutter);
55-
}
56-
if (angular.isDefined(opts.useWrapMode)) {
57-
session.setUseWrapMode(opts.useWrapMode);
58-
}
98+
/**
99+
* ACE editor
100+
* @type object
101+
*/
102+
var acee = window.ace.edit(elm[0]);
59103

60-
// onLoad callback
61-
if (angular.isFunction(opts.onLoad)) {
62-
opts.onLoad(acee);
63-
}
104+
/**
105+
* ACE editor session.
106+
* @type object
107+
* @see [EditSession]{@link http://ace.c9.io/#nav=api&api=edit_session}
108+
*/
109+
var session = acee.getSession();
64110

65-
// Basic options
66-
if (angular.isString(opts.theme)) {
67-
acee.setTheme('ace/theme/' + opts.theme);
68-
}
69-
if (angular.isString(opts.mode)) {
70-
session.setMode('ace/mode/' + opts.mode);
71-
}
111+
/**
112+
* Reference to a change listener created by the listener factory.
113+
* @function
114+
* @see listenerFactory.onChange
115+
*/
116+
var onChangeListener;
117+
118+
/**
119+
* Reference to a blur listener created by the listener factory.
120+
* @function
121+
* @see listenerFactory.onBlur
122+
*/
123+
var onBlurListener;
124+
125+
/**
126+
* Calls a callback by checking its existing. The argument list
127+
* is variable and thus this function is relying on the arguments
128+
* object.
129+
* @throws {Error} If the callback isn't a function
130+
*/
131+
var executeUserCallback = function () {
132+
133+
/**
134+
* The callback function grabbed from the array-like arguments
135+
* object. The first argument should always be the callback.
136+
*
137+
* @see [arguments]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions_and_function_scope/arguments}
138+
* @type {*}
139+
*/
140+
var callback = arguments[0];
141+
142+
/**
143+
* Arguments to be passed to the callback. These are taken
144+
* from the array-like arguments object. The first argument
145+
* is stripped because that should be the callback function.
146+
*
147+
* @see [arguments]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions_and_function_scope/arguments}
148+
* @type {Array}
149+
*/
150+
var args = Array.prototype.slice.call(arguments, 1);
151+
152+
if (angular.isDefined(callback)) {
153+
scope.$apply(function () {
154+
if (angular.isFunction(callback)) {
155+
callback(args);
156+
} else {
157+
throw new Error('ui-ace use a function as callback.');
158+
}
159+
});
160+
}
161+
};
162+
163+
/**
164+
* Listener factory. Until now only change listeners can be created.
165+
* @type object
166+
*/
167+
var listenerFactory = {
168+
/**
169+
* Creates a change listener which propagates the change event
170+
* and the editor session to the callback from the user option
171+
* onChange. It might be exchanged during runtime, if this
172+
* happens the old listener will be unbound.
173+
*
174+
* @param callback callback function defined in the user options
175+
* @see onChangeListener
176+
*/
177+
onChange: function (callback) {
178+
return function (e) {
179+
var newValue = session.getValue();
180+
if (newValue !== scope.$eval(attrs.value) && !scope.$$phase && !scope.$root.$$phase) {
181+
if (angular.isDefined(ngModel)) {
182+
scope.$apply(function () {
183+
ngModel.$setViewValue(newValue);
184+
});
185+
}
186+
executeUserCallback(callback, e, acee);
187+
}
188+
};
189+
},
190+
/**
191+
* Creates a blur listener which propagates the editor session
192+
* to the callback from the user option onBlur. It might be
193+
* exchanged during runtime, if this happens the old listener
194+
* will be unbound.
195+
*
196+
* @param callback callback function defined in the user options
197+
* @see onBlurListener
198+
*/
199+
onBlur: function (callback) {
200+
return function () {
201+
executeUserCallback(callback, acee);
202+
};
203+
}
204+
};
72205

73206
attrs.$observe('readonly', function (value) {
74207
acee.setReadOnly(value === 'true');
@@ -91,20 +224,51 @@ angular.module('ui.ace', [])
91224
};
92225
}
93226

227+
// set the options here, even if we try to watch later, if this
228+
// line is missing things go wrong (and the tests will also fail)
229+
setOptions(acee, session, opts);
230+
231+
// Listen for option updates
232+
scope.$watch( attrs.uiAce, function() {
233+
opts = angular.extend({}, options, scope.$eval(attrs.uiAce));
234+
235+
// unbind old change listener
236+
session.removeListener('change', onChangeListener);
237+
238+
// bind new change listener
239+
onChangeListener = listenerFactory.onChange(opts.onChange);
240+
session.on('change', onChangeListener);
241+
242+
// unbind old blur listener
243+
//session.removeListener('blur', onBlurListener);
244+
acee.removeListener('blur', onBlurListener);
245+
246+
// bind new blur listener
247+
onBlurListener = listenerFactory.onBlur(opts.onBlur);
248+
acee.on('blur', onBlurListener);
249+
250+
setOptions(acee, session, opts);
251+
}, /* deep watch */ true );
252+
94253
// EVENTS
95-
session.on('change', onChange(opts.onChange));
254+
onChangeListener = listenerFactory.onChange(opts.onChange);
255+
session.on('change', onChangeListener);
256+
257+
onBlurListener = listenerFactory.onBlur(opts.onBlur);
258+
acee.on('blur', onBlurListener);
96259

97-
elm.on('$destroy', function() {
260+
elm.on('$destroy', function () {
98261
acee.session.$stopWorker();
99262
acee.destroy();
100263
});
101-
264+
102265
scope.$watch(function() {
103266
return [elm[0].offsetWidth, elm[0].offsetHeight];
104267
}, function() {
105268
acee.resize();
106269
acee.renderer.updateFull();
107270
}, true);
271+
108272
}
109273
};
110274
}]);

0 commit comments

Comments
 (0)