forked from dounine/yes-ui
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
9 changed files
with
999 additions
and
144 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
Large diffs are not rendered by default.
Oops, something went wrong.
Large diffs are not rendered by default.
Oops, something went wrong.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,326 @@ | ||
'use strict'; | ||
/** | ||
* Binds a ACE Editor widget | ||
*/ | ||
angular.module('ui.ace', []) | ||
.constant('uiAceConfig', {}) | ||
.directive('uiAce', ['uiAceConfig', function (uiAceConfig) { | ||
if (angular.isUndefined(window.ace)) { | ||
throw new Error('ui-ace need ace to work... (o rly?)'); | ||
} | ||
|
||
/** | ||
* Sets editor options such as the wrapping mode or the syntax checker. | ||
* | ||
* The supported options are: | ||
* | ||
* <ul> | ||
* <li>showGutter</li> | ||
* <li>useWrapMode</li> | ||
* <li>onLoad</li> | ||
* <li>theme</li> | ||
* <li>mode</li> | ||
* </ul> | ||
* | ||
* @param acee | ||
* @param session ACE editor session | ||
* @param {object} opts Options to be set | ||
*/ | ||
var setOptions = function(acee, session, opts) { | ||
|
||
// sets the ace worker path, if running from concatenated | ||
// or minified source | ||
if (angular.isDefined(opts.workerPath)) { | ||
var config = window.ace.require('ace/config'); | ||
config.set('workerPath', opts.workerPath); | ||
} | ||
// ace requires loading | ||
if (angular.isDefined(opts.require)) { | ||
opts.require.forEach(function (n) { | ||
window.ace.require(n); | ||
}); | ||
} | ||
// Boolean options | ||
if (angular.isDefined(opts.showGutter)) { | ||
acee.renderer.setShowGutter(opts.showGutter); | ||
} | ||
if (angular.isDefined(opts.useWrapMode)) { | ||
session.setUseWrapMode(opts.useWrapMode); | ||
} | ||
if (angular.isDefined(opts.showInvisibles)) { | ||
acee.renderer.setShowInvisibles(opts.showInvisibles); | ||
} | ||
if (angular.isDefined(opts.showIndentGuides)) { | ||
acee.renderer.setDisplayIndentGuides(opts.showIndentGuides); | ||
} | ||
if (angular.isDefined(opts.useSoftTabs)) { | ||
session.setUseSoftTabs(opts.useSoftTabs); | ||
} | ||
if (angular.isDefined(opts.showPrintMargin)) { | ||
acee.setShowPrintMargin(opts.showPrintMargin); | ||
} | ||
|
||
// commands | ||
if (angular.isDefined(opts.disableSearch) && opts.disableSearch) { | ||
acee.commands.addCommands([ | ||
{ | ||
name: 'unfind', | ||
bindKey: { | ||
win: 'Ctrl-F', | ||
mac: 'Command-F' | ||
}, | ||
exec: function () { | ||
return false; | ||
}, | ||
readOnly: true | ||
} | ||
]); | ||
} | ||
|
||
// Basic options | ||
if (angular.isString(opts.theme)) { | ||
acee.setTheme('ace/theme/' + opts.theme); | ||
} | ||
if (angular.isString(opts.mode)) { | ||
session.setMode('ace/mode/' + opts.mode); | ||
} | ||
// Advanced options | ||
if (angular.isDefined(opts.firstLineNumber)) { | ||
if (angular.isNumber(opts.firstLineNumber)) { | ||
session.setOption('firstLineNumber', opts.firstLineNumber); | ||
} else if (angular.isFunction(opts.firstLineNumber)) { | ||
session.setOption('firstLineNumber', opts.firstLineNumber()); | ||
} | ||
} | ||
|
||
// advanced options | ||
var key, obj; | ||
if (angular.isDefined(opts.advanced)) { | ||
for (key in opts.advanced) { | ||
// create a javascript object with the key and value | ||
obj = { name: key, value: opts.advanced[key] }; | ||
// try to assign the option to the ace editor | ||
acee.setOption(obj.name, obj.value); | ||
} | ||
} | ||
|
||
// advanced options for the renderer | ||
if (angular.isDefined(opts.rendererOptions)) { | ||
for (key in opts.rendererOptions) { | ||
// create a javascript object with the key and value | ||
obj = { name: key, value: opts.rendererOptions[key] }; | ||
// try to assign the option to the ace editor | ||
acee.renderer.setOption(obj.name, obj.value); | ||
} | ||
} | ||
|
||
// onLoad callbacks | ||
angular.forEach(opts.callbacks, function (cb) { | ||
if (angular.isFunction(cb)) { | ||
cb(acee); | ||
} | ||
}); | ||
}; | ||
|
||
return { | ||
restrict: 'EA', | ||
require: '?ngModel', | ||
link: function (scope, elm, attrs, ngModel) { | ||
|
||
/** | ||
* Corresponds the uiAceConfig ACE configuration. | ||
* @type object | ||
*/ | ||
var options = uiAceConfig.ace || {}; | ||
|
||
/** | ||
* uiAceConfig merged with user options via json in attribute or data binding | ||
* @type object | ||
*/ | ||
var opts = angular.extend({}, options, scope.$eval(attrs.uiAce)); | ||
|
||
/** | ||
* ACE editor | ||
* @type object | ||
*/ | ||
var acee = window.ace.edit(elm[0]); | ||
|
||
/** | ||
* ACE editor session. | ||
* @type object | ||
* @see [EditSession]{@link http://ace.c9.io/#nav=api&api=edit_session} | ||
*/ | ||
var session = acee.getSession(); | ||
|
||
/** | ||
* Reference to a change listener created by the listener factory. | ||
* @function | ||
* @see listenerFactory.onChange | ||
*/ | ||
var onChangeListener; | ||
|
||
/** | ||
* Reference to a blur listener created by the listener factory. | ||
* @function | ||
* @see listenerFactory.onBlur | ||
*/ | ||
var onBlurListener; | ||
|
||
/** | ||
* Calls a callback by checking its existing. The argument list | ||
* is variable and thus this function is relying on the arguments | ||
* object. | ||
* @throws {Error} If the callback isn't a function | ||
*/ | ||
var executeUserCallback = function () { | ||
|
||
/** | ||
* The callback function grabbed from the array-like arguments | ||
* object. The first argument should always be the callback. | ||
* | ||
* @see [arguments]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions_and_function_scope/arguments} | ||
* @type {*} | ||
*/ | ||
var callback = arguments[0]; | ||
|
||
/** | ||
* Arguments to be passed to the callback. These are taken | ||
* from the array-like arguments object. The first argument | ||
* is stripped because that should be the callback function. | ||
* | ||
* @see [arguments]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions_and_function_scope/arguments} | ||
* @type {Array} | ||
*/ | ||
var args = Array.prototype.slice.call(arguments, 1); | ||
|
||
if (angular.isDefined(callback)) { | ||
scope.$evalAsync(function () { | ||
if (angular.isFunction(callback)) { | ||
callback(args); | ||
} else { | ||
throw new Error('ui-ace use a function as callback.'); | ||
} | ||
}); | ||
} | ||
}; | ||
|
||
/** | ||
* Listener factory. Until now only change listeners can be created. | ||
* @type object | ||
*/ | ||
var listenerFactory = { | ||
/** | ||
* Creates a change listener which propagates the change event | ||
* and the editor session to the callback from the user option | ||
* onChange. It might be exchanged during runtime, if this | ||
* happens the old listener will be unbound. | ||
* | ||
* @param callback callback function defined in the user options | ||
* @see onChangeListener | ||
*/ | ||
onChange: function (callback) { | ||
return function (e) { | ||
var newValue = session.getValue(); | ||
|
||
if (ngModel && newValue !== ngModel.$viewValue && | ||
// HACK make sure to only trigger the apply outside of the | ||
// digest loop 'cause ACE is actually using this callback | ||
// for any text transformation ! | ||
!scope.$$phase && !scope.$root.$$phase) { | ||
scope.$evalAsync(function () { | ||
ngModel.$setViewValue(newValue); | ||
}); | ||
} | ||
|
||
executeUserCallback(callback, e, acee); | ||
}; | ||
}, | ||
/** | ||
* Creates a blur listener which propagates the editor session | ||
* to the callback from the user option onBlur. It might be | ||
* exchanged during runtime, if this happens the old listener | ||
* will be unbound. | ||
* | ||
* @param callback callback function defined in the user options | ||
* @see onBlurListener | ||
*/ | ||
onBlur: function (callback) { | ||
return function () { | ||
executeUserCallback(callback, acee); | ||
}; | ||
} | ||
}; | ||
|
||
attrs.$observe('readonly', function (value) { | ||
acee.setReadOnly(!!value || value === ''); | ||
}); | ||
|
||
// Value Blind | ||
if (ngModel) { | ||
ngModel.$formatters.push(function (value) { | ||
if (angular.isUndefined(value) || value === null) { | ||
return ''; | ||
} | ||
else if (angular.isObject(value) || angular.isArray(value)) { | ||
throw new Error('ui-ace cannot use an object or an array as a model'); | ||
} | ||
return value; | ||
}); | ||
|
||
ngModel.$render = function () { | ||
session.setValue(ngModel.$viewValue); | ||
}; | ||
} | ||
|
||
// Listen for option updates | ||
var updateOptions = function (current, previous) { | ||
if (current === previous) return; | ||
opts = angular.extend({}, options, scope.$eval(attrs.uiAce)); | ||
|
||
opts.callbacks = [ opts.onLoad ]; | ||
if (opts.onLoad !== options.onLoad) { | ||
// also call the global onLoad handler | ||
opts.callbacks.unshift(options.onLoad); | ||
} | ||
|
||
// EVENTS | ||
|
||
// unbind old change listener | ||
session.removeListener('change', onChangeListener); | ||
|
||
// bind new change listener | ||
onChangeListener = listenerFactory.onChange(opts.onChange); | ||
session.on('change', onChangeListener); | ||
|
||
// unbind old blur listener | ||
//session.removeListener('blur', onBlurListener); | ||
acee.removeListener('blur', onBlurListener); | ||
|
||
// bind new blur listener | ||
onBlurListener = listenerFactory.onBlur(opts.onBlur); | ||
acee.on('blur', onBlurListener); | ||
|
||
setOptions(acee, session, opts); | ||
}; | ||
|
||
scope.$watch(attrs.uiAce, updateOptions, /* deep watch */ true); | ||
|
||
// set the options here, even if we try to watch later, if this | ||
// line is missing things go wrong (and the tests will also fail) | ||
updateOptions(options); | ||
|
||
elm.on('$destroy', function () { | ||
acee.session.$stopWorker(); | ||
acee.destroy(); | ||
}); | ||
|
||
scope.$watch(function() { | ||
return [elm[0].offsetWidth, elm[0].offsetHeight]; | ||
}, function() { | ||
acee.resize(); | ||
acee.renderer.updateFull(); | ||
}, true); | ||
|
||
} | ||
}; | ||
}]); |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Large diffs are not rendered by default.
Oops, something went wrong.