This repository was archived by the owner on May 25, 2019. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 191
/
Copy pathui-codemirror.js
172 lines (141 loc) · 4.96 KB
/
ui-codemirror.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
'use strict';
/**
* Binds a CodeMirror widget to a <textarea> element.
*/
angular.module('ui.codemirror', [])
.constant('uiCodemirrorConfig', {})
.directive('uiCodemirror', uiCodemirrorDirective);
/**
* @ngInject
*/
function uiCodemirrorDirective($timeout, $rootScope, uiCodemirrorConfig) {
return {
restrict: 'EA',
require: '?ngModel',
compile: function compile() {
// Require CodeMirror
if (angular.isUndefined(window.CodeMirror)) {
throw new Error('ui-codemirror needs CodeMirror to work... (o rly?)');
}
return postLink;
}
};
function postLink(scope, iElement, iAttrs, ngModel) {
var codemirrorOptions = angular.extend(
{ value: iElement.text() },
uiCodemirrorConfig.codemirror || {},
scope.$eval(iAttrs.uiCodemirror),
scope.$eval(iAttrs.uiCodemirrorOpts)
);
var codemirror = newCodemirrorEditor(iElement, codemirrorOptions);
configOptionsWatcher(
codemirror,
iAttrs.uiCodemirror || iAttrs.uiCodemirrorOpts,
scope
);
configNgModelLink(codemirror, ngModel, scope);
configUiRefreshAttribute(codemirror, iAttrs.uiRefresh, scope);
// Allow access to the CodeMirror instance through a broadcasted event
// eg: $broadcast('CodeMirror', function(cm){...});
scope.$on('CodeMirror', function(event, callback) {
if (angular.isFunction(callback)) {
callback(codemirror);
} else {
throw new Error('the CodeMirror event requires a callback function');
}
});
// onLoad callback
if (angular.isFunction(codemirrorOptions.onLoad)) {
codemirrorOptions.onLoad(codemirror);
}
}
function newCodemirrorEditor(iElement, codemirrorOptions) {
var codemirrot;
if (iElement[0].tagName === 'TEXTAREA') {
// Might bug but still ...
codemirrot = window.CodeMirror.fromTextArea(iElement[0], codemirrorOptions);
} else {
iElement.html('');
codemirrot = new window.CodeMirror(function(cm_el) {
iElement.append(cm_el);
}, codemirrorOptions);
}
return codemirrot;
}
function configOptionsWatcher(codemirrot, uiCodemirrorAttr, scope) {
if (!uiCodemirrorAttr) { return; }
var codemirrorDefaultsKeys = Object.keys(window.CodeMirror.defaults);
scope.$watch(uiCodemirrorAttr, updateOptions, true);
function updateOptions(newValues, oldValue) {
if (!angular.isObject(newValues)) { return; }
codemirrorDefaultsKeys.forEach(function(key) {
if (newValues.hasOwnProperty(key)) {
if (oldValue && newValues[key] === oldValue[key]) {
return;
}
codemirrot.setOption(key, newValues[key]);
}
});
}
}
function configNgModelLink(codemirror, ngModel, scope) {
if (!ngModel) { return; }
// CodeMirror expects a string, so make sure it gets one.
// This does not change the model.
ngModel.$formatters.push(function(value) {
if (angular.isUndefined(value) || value === null) {
return '';
} else if (angular.isObject(value) || angular.isArray(value)) {
throw new Error('ui-codemirror cannot use an object or an array as a model');
}
return value;
});
// Override the ngModelController $render method, which is what gets called when the model is updated.
// This takes care of the synchronizing the codeMirror element with the underlying model, in the case that it is changed by something else.
ngModel.$render = function() {
//Code mirror expects a string so make sure it gets one
//Although the formatter have already done this, it can be possible that another formatter returns undefined (for example the required directive)
var safeViewValue = ngModel.$viewValue || '';
codemirror.setValue(safeViewValue);
};
// Keep the ngModel in sync with changes from CodeMirror
codemirror.on('change', function(instance) {
var newValue = instance.getValue();
if (newValue !== ngModel.$viewValue) {
scope.$evalAsync(function() {
ngModel.$setViewValue(newValue);
});
}
});
// Commit as with NgModelController
codemirror.on('blur', function() {
if (ngModel.$touched) {
return;
}
if ($rootScope.$$phase) {
scope.$evalAsync(ngModel.$setTouched);
} else {
scope.$apply(ngModel.$setTouched);
}
});
// Debounce as with NgModelController
if (ngModel.$options && ngModel.$options.updateOn) {
ngModel.$options.updateOn.split(/\s+/).forEach(function(trigger) {
codemirror.on(trigger, function() {
ngModel.$$debounceViewValueCommit(trigger);
});
});
}
}
function configUiRefreshAttribute(codeMirror, uiRefreshAttr, scope) {
if (!uiRefreshAttr) { return; }
scope.$watch(uiRefreshAttr, function(newVal, oldVal) {
// Skip the initial watch firing
if (newVal !== oldVal) {
$timeout(function() {
codeMirror.refresh();
});
}
});
}
}