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