11'use strict' ;
22
33/**
4- * Binds a ACE Ediitor widget
4+ * Binds a ACE Editor widget
55 */
66angular . 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