|
| 1 | +angular.module('schemaForm').directive('sfField', |
| 2 | + ['$parse', '$compile', '$http', '$templateCache', '$interpolate', '$q', 'sfErrorMessage', |
| 3 | + 'sfPath','sfSelect', |
| 4 | + function($parse, $compile, $http, $templateCache, $interpolate, $q, sfErrorMessage, |
| 5 | + sfPath, sfSelect) { |
| 6 | + |
| 7 | + return { |
| 8 | + restrict: 'AE', |
| 9 | + replace: false, |
| 10 | + transclude: false, |
| 11 | + scope: true, |
| 12 | + require: '?^sfSchema', |
| 13 | + link: function(scope, element, attrs, sfSchema) { |
| 14 | + |
| 15 | + //The ngModelController is used in some templates and |
| 16 | + //is needed for error messages, |
| 17 | + scope.$on('schemaFormPropagateNgModelController', function(event, ngModel) { |
| 18 | + event.stopPropagation(); |
| 19 | + event.preventDefault(); |
| 20 | + scope.ngModel = ngModel; |
| 21 | + }); |
| 22 | + |
| 23 | + //Keep error prone logic from the template |
| 24 | + scope.showTitle = function() { |
| 25 | + return scope.form && scope.form.notitle !== true && scope.form.title; |
| 26 | + }; |
| 27 | + |
| 28 | + scope.listToCheckboxValues = function(list) { |
| 29 | + var values = {}; |
| 30 | + angular.forEach(list, function(v) { |
| 31 | + values[v] = true; |
| 32 | + }); |
| 33 | + return values; |
| 34 | + }; |
| 35 | + |
| 36 | + scope.checkboxValuesToList = function(values) { |
| 37 | + var lst = []; |
| 38 | + angular.forEach(values, function(v, k) { |
| 39 | + if (v) { |
| 40 | + lst.push(k); |
| 41 | + } |
| 42 | + }); |
| 43 | + return lst; |
| 44 | + }; |
| 45 | + |
| 46 | + scope.buttonClick = function($event, form) { |
| 47 | + if (angular.isFunction(form.onClick)) { |
| 48 | + form.onClick($event, form); |
| 49 | + } else if (angular.isString(form.onClick)) { |
| 50 | + if (sfSchema) { |
| 51 | + //evaluating in scope outside of sfSchemas isolated scope |
| 52 | + sfSchema.evalInParentScope(form.onClick, {'$event': $event, form: form}); |
| 53 | + } else { |
| 54 | + scope.$eval(form.onClick, {'$event': $event, form: form}); |
| 55 | + } |
| 56 | + } |
| 57 | + }; |
| 58 | + |
| 59 | + /** |
| 60 | + * Evaluate an expression, i.e. scope.$eval |
| 61 | + * but do it in sfSchemas parent scope sf-schema directive is used |
| 62 | + * @param {string} expression |
| 63 | + * @param {Object} locals (optional) |
| 64 | + * @return {Any} the result of the expression |
| 65 | + */ |
| 66 | + scope.evalExpr = function(expression, locals) { |
| 67 | + if (sfSchema) { |
| 68 | + //evaluating in scope outside of sfSchemas isolated scope |
| 69 | + return sfSchema.evalInParentScope(expression, locals); |
| 70 | + } |
| 71 | + |
| 72 | + return scope.$eval(expression, locals); |
| 73 | + }; |
| 74 | + |
| 75 | + /** |
| 76 | + * Evaluate an expression, i.e. scope.$eval |
| 77 | + * in this decorators scope |
| 78 | + * @param {string} expression |
| 79 | + * @param {Object} locals (optional) |
| 80 | + * @return {Any} the result of the expression |
| 81 | + */ |
| 82 | + scope.evalInScope = function(expression, locals) { |
| 83 | + if (expression) { |
| 84 | + return scope.$eval(expression, locals); |
| 85 | + } |
| 86 | + }; |
| 87 | + |
| 88 | + /** |
| 89 | + * Interpolate the expression. |
| 90 | + * Similar to `evalExpr()` and `evalInScope()` |
| 91 | + * but will not fail if the expression is |
| 92 | + * text that contains spaces. |
| 93 | + * |
| 94 | + * Use the Angular `{{ interpolation }}` |
| 95 | + * braces to access properties on `locals`. |
| 96 | + * |
| 97 | + * @param {string} content The string to interpolate. |
| 98 | + * @param {Object} locals (optional) Properties that may be accessed in the |
| 99 | + * `expression` string. |
| 100 | + * @return {Any} The result of the expression or `undefined`. |
| 101 | + */ |
| 102 | + scope.interp = function(expression, locals) { |
| 103 | + return (expression && $interpolate(expression)(locals)); |
| 104 | + }; |
| 105 | + |
| 106 | + //This works since we ot the ngModel from the array or the schema-validate directive. |
| 107 | + scope.hasSuccess = function() { |
| 108 | + if (!scope.ngModel) { |
| 109 | + return false; |
| 110 | + } |
| 111 | + return scope.ngModel.$valid && |
| 112 | + (!scope.ngModel.$pristine || !scope.ngModel.$isEmpty(scope.ngModel.$modelValue)); |
| 113 | + }; |
| 114 | + |
| 115 | + scope.hasError = function() { |
| 116 | + if (!scope.ngModel) { |
| 117 | + return false; |
| 118 | + } |
| 119 | + return scope.ngModel.$invalid && !scope.ngModel.$pristine; |
| 120 | + }; |
| 121 | + |
| 122 | + /** |
| 123 | + * DEPRECATED: use sf-messages instead. |
| 124 | + * Error message handler |
| 125 | + * An error can either be a schema validation message or a angular js validtion |
| 126 | + * error (i.e. required) |
| 127 | + */ |
| 128 | + scope.errorMessage = function(schemaError) { |
| 129 | + return sfErrorMessage.interpolate( |
| 130 | + (schemaError && schemaError.code + '') || 'default', |
| 131 | + (scope.ngModel && scope.ngModel.$modelValue) || '', |
| 132 | + (scope.ngModel && scope.ngModel.$viewValue) || '', |
| 133 | + scope.form, |
| 134 | + scope.options && scope.options.validationMessage |
| 135 | + ); |
| 136 | + }; |
| 137 | + |
| 138 | + // Rebind our part of the form to the scope. |
| 139 | + var once = scope.$watch(attrs.sfField, function(form) { |
| 140 | + if (form) { |
| 141 | + console.warn('got form!!!!', form) |
| 142 | + // Workaround for 'updateOn' error from ngModelOptions |
| 143 | + // see https://github.com/Textalk/angular-schema-form/issues/255 |
| 144 | + // and https://github.com/Textalk/angular-schema-form/issues/206 |
| 145 | + form.ngModelOptions = form.ngModelOptions || {}; |
| 146 | + scope.form = form; |
| 147 | + |
| 148 | +/* |
| 149 | + //ok let's replace that template! |
| 150 | + //We do this manually since we need to bind ng-model properly and also |
| 151 | + //for fieldsets to recurse properly. |
| 152 | + var templatePromise; |
| 153 | +
|
| 154 | + // type: "template" is a special case. It can contain a template inline or an url. |
| 155 | + // otherwise we find out the url to the template and load them. |
| 156 | + if (form.type === 'template' && form.template) { |
| 157 | + templatePromise = $q.when(form.template); |
| 158 | + } else { |
| 159 | + var url = form.type === 'template' ? form.templateUrl : templateUrl(name, form); |
| 160 | + templatePromise = $http.get(url, {cache: $templateCache}).then(function(res) { |
| 161 | + return res.data; |
| 162 | + }); |
| 163 | + } |
| 164 | +*/ |
| 165 | +/* |
| 166 | + templatePromise.then(function(template) { |
| 167 | + if (form.key) { |
| 168 | + var key = form.key ? |
| 169 | + sfPathProvider.stringify(form.key).replace(/"/g, '"') : ''; |
| 170 | + template = template.replace( |
| 171 | + /\$\$value\$\$/g, |
| 172 | + 'model' + (key[0] !== '[' ? '.' : '') + key |
| 173 | + ); |
| 174 | + } |
| 175 | + element.html(template); |
| 176 | +*/ |
| 177 | + // Do we have a condition? Then we slap on an ng-if on all children, |
| 178 | + // but be nice to existing ng-if. |
| 179 | + /*if (form.condition) { |
| 180 | +
|
| 181 | + var evalExpr = 'evalExpr(form.condition,{ model: model, "arrayIndex": arrayIndex})'; |
| 182 | + if (form.key) { |
| 183 | + evalExpr = 'evalExpr(form.condition,{ model: model, "arrayIndex": arrayIndex, "modelValue": model' + sfPath.stringify(form.key) + '})'; |
| 184 | + } |
| 185 | +
|
| 186 | + angular.forEach(element.children(), function(child) { |
| 187 | + var ngIf = child.getAttribute('ng-if'); |
| 188 | + child.setAttribute( |
| 189 | + 'ng-if', |
| 190 | + ngIf ? |
| 191 | + '(' + ngIf + |
| 192 | + ') || (' + evalExpr +')' |
| 193 | + : evalExpr |
| 194 | + ); |
| 195 | + }); |
| 196 | + }*/ |
| 197 | + //$compile(element.contents())(scope); |
| 198 | + //}); |
| 199 | + |
| 200 | + // Where there is a key there is probably a ngModel |
| 201 | + if (form.key) { |
| 202 | + // It looks better with dot notation. |
| 203 | + scope.$on( |
| 204 | + 'schemaForm.error.' + form.key.join('.'), |
| 205 | + function(event, error, validationMessage, validity) { |
| 206 | + if (validationMessage === true || validationMessage === false) { |
| 207 | + validity = validationMessage; |
| 208 | + validationMessage = undefined; |
| 209 | + } |
| 210 | + |
| 211 | + if (scope.ngModel && error) { |
| 212 | + if (scope.ngModel.$setDirty()) { |
| 213 | + scope.ngModel.$setDirty(); |
| 214 | + } else { |
| 215 | + // FIXME: Check that this actually works on 1.2 |
| 216 | + scope.ngModel.$dirty = true; |
| 217 | + scope.ngModel.$pristine = false; |
| 218 | + } |
| 219 | + |
| 220 | + // Set the new validation message if one is supplied |
| 221 | + // Does not work when validationMessage is just a string. |
| 222 | + if (validationMessage) { |
| 223 | + if (!form.validationMessage) { |
| 224 | + form.validationMessage = {}; |
| 225 | + } |
| 226 | + form.validationMessage[error] = validationMessage; |
| 227 | + } |
| 228 | + |
| 229 | + scope.ngModel.$setValidity(error, validity === true); |
| 230 | + |
| 231 | + if (validity === true) { |
| 232 | + // Setting or removing a validity can change the field to believe its valid |
| 233 | + // but its not. So lets trigger its validation as well. |
| 234 | + scope.$broadcast('schemaFormValidate'); |
| 235 | + } |
| 236 | + } |
| 237 | + }); |
| 238 | + |
| 239 | + // Clean up the model when the corresponding form field is $destroy-ed. |
| 240 | + // Default behavior can be supplied as a globalOption, and behavior can be overridden in the form definition. |
| 241 | + scope.$on('$destroy', function() { |
| 242 | + // If the entire schema form is destroyed we don't touch the model |
| 243 | + if (!scope.externalDestructionInProgress) { |
| 244 | + var destroyStrategy = form.destroyStrategy || |
| 245 | + (scope.options && scope.options.destroyStrategy) || 'remove'; |
| 246 | + // No key no model, and we might have strategy 'retain' |
| 247 | + if (form.key && destroyStrategy !== 'retain') { |
| 248 | + |
| 249 | + // Get the object that has the property we wan't to clear. |
| 250 | + var obj = scope.model; |
| 251 | + if (form.key.length > 1) { |
| 252 | + obj = sfSelect(form.key.slice(0, form.key.length - 1), obj); |
| 253 | + } |
| 254 | + |
| 255 | + // We can get undefined here if the form hasn't been filled out entirely |
| 256 | + if (obj === undefined) { |
| 257 | + return; |
| 258 | + } |
| 259 | + |
| 260 | + // Type can also be a list in JSON Schema |
| 261 | + var type = (form.schema && form.schema.type) || ''; |
| 262 | + |
| 263 | + // Empty means '',{} and [] for appropriate types and undefined for the rest |
| 264 | + //console.log('destroy', destroyStrategy, form.key, type, obj); |
| 265 | + if (destroyStrategy === 'empty' && type.indexOf('string') !== -1) { |
| 266 | + obj[form.key.slice(-1)] = ''; |
| 267 | + } else if (destroyStrategy === 'empty' && type.indexOf('object') !== -1) { |
| 268 | + obj[form.key.slice(-1)] = {}; |
| 269 | + } else if (destroyStrategy === 'empty' && type.indexOf('array') !== -1) { |
| 270 | + obj[form.key.slice(-1)] = []; |
| 271 | + } else if (destroyStrategy === 'null') { |
| 272 | + obj[form.key.slice(-1)] = null; |
| 273 | + } else { |
| 274 | + delete obj[form.key.slice(-1)]; |
| 275 | + } |
| 276 | + } |
| 277 | + } |
| 278 | + }); |
| 279 | + } |
| 280 | + |
| 281 | + once(); |
| 282 | + } |
| 283 | + }); |
| 284 | + } |
| 285 | + }; |
| 286 | + } |
| 287 | + ]); |
0 commit comments