Matteo Scandolo | 686547a | 2017-08-08 13:05:25 -0700 | [diff] [blame] | 1 | |
| 2 | /* |
| 3 | * Copyright 2017-present Open Networking Foundation |
| 4 | |
| 5 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 6 | * you may not use this file except in compliance with the License. |
| 7 | * You may obtain a copy of the License at |
| 8 | |
| 9 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 10 | |
| 11 | * Unless required by applicable law or agreed to in writing, software |
| 12 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 14 | * See the License for the specific language governing permissions and |
| 15 | * limitations under the License. |
| 16 | */ |
| 17 | |
| 18 | |
Matteo Scandolo | a5d03d5 | 2016-07-21 11:35:46 -0700 | [diff] [blame] | 19 | /** |
| 20 | * © OpenCORD |
| 21 | * |
| 22 | * Visit http://guide.xosproject.org/devguide/addview/ for more information |
| 23 | * |
| 24 | * Created by teone on 5/25/16. |
| 25 | */ |
| 26 | |
| 27 | (function () { |
| 28 | 'use strict'; |
| 29 | |
| 30 | angular.module('xos.uiComponents') |
| 31 | /** |
| 32 | * @ngdoc directive |
| 33 | * @name xos.uiComponents.directive:xosField |
| 34 | * @restrict E |
| 35 | * @description The xos-field directive. |
| 36 | * This component decide, give a field wich kind of input it need to print. |
| 37 | * @param {string} name The field name |
| 38 | * @param {object} field The field configuration: |
| 39 | * ``` |
| 40 | * { |
| 41 | * label: 'Label', |
| 42 | * type: 'number', //typeof field |
| 43 | * validators: {} // see xosForm for more details |
| 44 | * } |
| 45 | * ``` |
| 46 | * @param {mixed} ngModel The field value |
| 47 | * |
| 48 | * @example |
| 49 | |
| 50 | # Basic Example |
| 51 | |
| 52 | <example module="sampleField1"> |
| 53 | <file name="script.js"> |
| 54 | angular.module('sampleField1', ['xos.uiComponents']) |
| 55 | .factory('_', function($window){ |
| 56 | return $window._; |
| 57 | }) |
| 58 | .controller('SampleCtrl', function(){ |
| 59 | this.name = 'input-name'; |
| 60 | this.field = {label: 'My String Value:', type: 'string'}; |
| 61 | this.model = 'my string'; |
| 62 | }); |
| 63 | </file> |
| 64 | <file name="index.html"> |
| 65 | <div ng-controller="SampleCtrl as vm"> |
| 66 | <xos-field ng-model="vm.model" name="vm.name" field="vm.field"></xos-field> |
| 67 | </div> |
| 68 | </file> |
| 69 | </example> |
| 70 | |
| 71 | # Possible Values |
| 72 | |
| 73 | <example module="sampleField2"> |
| 74 | <file name="script.js"> |
| 75 | angular.module('sampleField2', ['xos.uiComponents']) |
| 76 | .factory('_', function($window){ |
| 77 | return $window._; |
| 78 | }) |
| 79 | .controller('SampleCtrl', function(){ |
| 80 | this.field1 = { |
| 81 | name: 'number-field', |
| 82 | field: {label: 'My Number Value:', type: 'number'}, |
| 83 | model: 2 |
| 84 | }; |
| 85 | |
| 86 | this.field2 = { |
| 87 | name: 'date-field', |
| 88 | field: {label: 'My Date Value:', type: 'date'}, |
| 89 | model: new Date() |
| 90 | }; |
| 91 | |
| 92 | this.field3 = { |
| 93 | name: 'boolean-field', |
| 94 | field: {label: 'My Boolean Value:', type: 'boolean'}, |
| 95 | model: true |
| 96 | }; |
| 97 | |
| 98 | this.field4 = { |
| 99 | name: 'email-field', |
| 100 | field: {label: 'My Email Value:', type: 'email'}, |
| 101 | model: 'sample@domain.us' |
| 102 | }; |
Matteo Scandolo | 65116c4 | 2016-09-21 17:06:23 -0700 | [diff] [blame] | 103 | |
| 104 | this.field5 = { |
| 105 | name: 'select', |
Matteo Scandolo | bfb8ebd | 2016-10-28 17:53:05 +0200 | [diff] [blame] | 106 | field: { |
| 107 | label: 'Select field:', |
| 108 | type: 'select', |
| 109 | options: [ |
| 110 | {id: 1, label: 'One'}, |
| 111 | {id: 2, label: 'Two'}, |
| 112 | {id: 3, label: 'Three'}, |
| 113 | ] |
| 114 | }, |
| 115 | model: 1 |
| 116 | }; |
| 117 | |
| 118 | this.arrayField = { |
| 119 | name: 'array', |
| 120 | field: { |
| 121 | label: 'Array field:', |
| 122 | type: 'array', |
| 123 | options: ['one', 'two', 'three', 'four'] |
| 124 | }, |
| 125 | model: ['one', 'two'], |
Matteo Scandolo | 65116c4 | 2016-09-21 17:06:23 -0700 | [diff] [blame] | 126 | }; |
Matteo Scandolo | a5d03d5 | 2016-07-21 11:35:46 -0700 | [diff] [blame] | 127 | }); |
| 128 | </file> |
| 129 | <file name="index.html"> |
| 130 | <div ng-controller="SampleCtrl as vm"> |
| 131 | <xos-field ng-model="vm.field1.model" name="vm.field1.name" field="vm.field1.field"></xos-field> |
| 132 | <xos-field ng-model="vm.field2.model" name="vm.field2.name" field="vm.field2.field"></xos-field> |
| 133 | <xos-field ng-model="vm.field3.model" name="vm.field3.name" field="vm.field3.field"></xos-field> |
| 134 | <xos-field ng-model="vm.field4.model" name="vm.field4.name" field="vm.field4.field"></xos-field> |
Matteo Scandolo | 65116c4 | 2016-09-21 17:06:23 -0700 | [diff] [blame] | 135 | <xos-field ng-model="vm.field5.model" name="vm.field5.name" field="vm.field5.field"></xos-field> |
Matteo Scandolo | bfb8ebd | 2016-10-28 17:53:05 +0200 | [diff] [blame] | 136 | <xos-field ng-model="vm.arrayField.model" name="vm.arrayField.name" field="vm.arrayField.field"></xos-field> |
Matteo Scandolo | a5d03d5 | 2016-07-21 11:35:46 -0700 | [diff] [blame] | 137 | </div> |
| 138 | </file> |
| 139 | </example> |
| 140 | |
| 141 | # This element is recursive |
| 142 | |
| 143 | <example module="sampleField3"> |
| 144 | <file name="script.js"> |
| 145 | angular.module('sampleField3', ['xos.uiComponents']) |
| 146 | .factory('_', function($window){ |
| 147 | return $window._; |
| 148 | }) |
| 149 | .controller('SampleCtrl', function(){ |
| 150 | this.name1 = 'input-name'; |
| 151 | this.field1 = {label: 'My Object Field:', type: 'object'}; |
| 152 | this.model1 = { |
| 153 | name: 'Jhon', |
| 154 | age: '25', |
| 155 | email: 'jhon@thewall.ru', |
| 156 | active: true |
| 157 | }; |
| 158 | |
| 159 | this.name2 = 'another-name'; |
| 160 | this.field2 = { |
| 161 | label: 'Empty Object Field', |
| 162 | type: 'object', |
| 163 | properties: { |
| 164 | foo: { |
| 165 | label: 'FooLabel:', |
| 166 | type: 'string', |
| 167 | validators: { |
| 168 | required: true |
| 169 | } |
| 170 | }, |
| 171 | bar: { |
| 172 | type: 'number' |
| 173 | } |
| 174 | } |
| 175 | } |
| 176 | }); |
| 177 | </file> |
| 178 | <file name="index.html"> |
| 179 | <div ng-controller="SampleCtrl as vm"> |
| 180 | <h4>Autogenerated object field</h4> |
| 181 | <xos-field ng-model="vm.model1" name="vm.name1" field="vm.field1"></xos-field> |
| 182 | |
| 183 | <h4>Configured object field</h4> |
| 184 | <xos-field ng-model="vm.model2" name="vm.name2" field="vm.field2"></xos-field> |
| 185 | </div> |
| 186 | </file> |
| 187 | </example> |
| 188 | */ |
Arpit Agarwal | 34b6383 | 2016-08-08 11:59:45 -0700 | [diff] [blame] | 189 | .component('xosField', { |
| 190 | restrict: 'E', |
| 191 | bindings: { |
| 192 | name: '=', |
| 193 | field: '=', |
| 194 | ngModel: '=' |
| 195 | }, |
| 196 | template: ` |
Matteo Scandolo | 65116c4 | 2016-09-21 17:06:23 -0700 | [diff] [blame] | 197 | <label ng-if="vm.field.type !== 'object' && vm.field.type !== 'array'">{{vm.field.label}}</label> |
| 198 | <input |
| 199 | xos-custom-validator custom-validator="vm.field.validators.custom || null" |
| 200 | ng-if="vm.field.type !== 'boolean' && vm.field.type !== 'object' && vm.field.type !== 'select' && vm.field.type !== 'array'" |
| 201 | type="{{vm.field.type}}" |
| 202 | name="{{vm.name}}" |
| 203 | class="form-control" |
| 204 | ng-model="vm.ngModel" |
| 205 | ng-minlength="vm.field.validators.minlength || 0" |
| 206 | ng-maxlength="vm.field.validators.maxlength || 2000" |
| 207 | ng-required="vm.field.validators.required || false" /> |
| 208 | <select class="form-control" ng-if ="vm.field.type === 'select'" |
| 209 | name = "{{vm.name}}" |
| 210 | ng-options="item.id as item.label for item in vm.field.options" |
| 211 | ng-model="vm.ngModel" |
| 212 | ng-required="vm.field.validators.required || false"> |
| 213 | </select> |
| 214 | <span class="boolean-field" ng-if="vm.field.type === 'boolean'"> |
| 215 | <a href="#" |
| 216 | class="btn btn-success" |
| 217 | ng-show="vm.ngModel" |
| 218 | ng-click="vm.ngModel = false"> |
| 219 | <i class="glyphicon glyphicon-ok"></i> |
| 220 | </a> |
| 221 | <a href="#" |
| 222 | class="btn btn-danger" |
| 223 | ng-show="!vm.ngModel" |
| 224 | ng-click="vm.ngModel = true"> |
| 225 | <i class="glyphicon glyphicon-remove"></i> |
| 226 | </a> |
| 227 | </span> |
| 228 | <div |
| 229 | class="panel panel-default object-field" |
| 230 | ng-if="vm.field.type == 'object' && (!vm.isEmptyObject(vm.ngModel) || !vm.isEmptyObject(vm.field.properties))" |
| 231 | > |
| 232 | <div class="panel-heading">{{vm.field.label}}</div> |
| 233 | <div class="panel-body"> |
| 234 | <div ng-if="!vm.field.properties" ng-repeat="(k, v) in vm.ngModel"> |
| 235 | <xos-field |
| 236 | name="k" |
| 237 | field="{label: vm.formatLabel(k), type: vm.getType(v)}" |
| 238 | ng-model="v"> |
| 239 | </xos-field> |
Arpit Agarwal | 34b6383 | 2016-08-08 11:59:45 -0700 | [diff] [blame] | 240 | </div> |
Matteo Scandolo | 65116c4 | 2016-09-21 17:06:23 -0700 | [diff] [blame] | 241 | <div ng-if="vm.field.properties" ng-repeat="(k, v) in vm.field.properties"> |
| 242 | <xos-field |
| 243 | name="k" |
| 244 | field="{ |
| 245 | label: v.label || vm.formatLabel(k), |
| 246 | type: v.type, |
| 247 | validators: v.validators |
| 248 | }" |
| 249 | ng-model="vm.ngModel[k]"> |
| 250 | </xos-field> |
| 251 | </div> |
| 252 | </div> |
| 253 | </div> |
| 254 | <div |
| 255 | class="panel panel-default array-field" |
| 256 | ng-if="vm.field.type == 'array'"> |
| 257 | <div class="panel-heading">{{vm.field.label}}</div> |
| 258 | <div class="panel-body selected"> |
| 259 | <ul class="draggable" dnd-list="vm.ngModel"> |
| 260 | <li |
| 261 | class="array-element" |
| 262 | ng-repeat="item in vm.ngModel" |
| 263 | dnd-draggable="item" |
| 264 | dnd-moved="vm.ngModel.splice($index, 1)" |
| 265 | dnd-effect-allowed="move" |
| 266 | dnd-selected="models.selected = item" |
| 267 | > |
| 268 | <div class="well well-sm text-center"> |
| 269 | {{item}} |
| 270 | </div> |
| 271 | </li> |
| 272 | <div class="clearfix"></div> |
| 273 | </ul> |
| 274 | </div> |
| 275 | <div class="panel-body unselected"> |
| 276 | <ul class="draggable" dnd-list="vm.field.availableOptions"> |
| 277 | <li |
| 278 | class="array-element" |
| 279 | ng-repeat="item in vm.field.availableOptions" |
| 280 | dnd-draggable="item" |
| 281 | dnd-moved="vm.field.availableOptions.splice($index, 1)" |
| 282 | dnd-effect-allowed="move" |
| 283 | dnd-selected="models.selected = item" |
| 284 | > |
| 285 | <div class="well well-sm text-center"> |
| 286 | {{item}} |
| 287 | </div> |
| 288 | </li> |
| 289 | <div class="clearfix"></div> |
| 290 | </ul> |
| 291 | </div> |
| 292 | </div> |
Arpit Agarwal | 34b6383 | 2016-08-08 11:59:45 -0700 | [diff] [blame] | 293 | `, |
| 294 | bindToController: true, |
| 295 | controllerAs: 'vm', |
Matteo Scandolo | 65116c4 | 2016-09-21 17:06:23 -0700 | [diff] [blame] | 296 | controller: function($attrs, $scope, XosFormHelpers, LabelFormatter, _){ |
Matteo Scandolo | a5d03d5 | 2016-07-21 11:35:46 -0700 | [diff] [blame] | 297 | |
Arpit Agarwal | 34b6383 | 2016-08-08 11:59:45 -0700 | [diff] [blame] | 298 | if(!this.name){ |
| 299 | throw new Error('[xosField] Please provide a field name'); |
Matteo Scandolo | a5d03d5 | 2016-07-21 11:35:46 -0700 | [diff] [blame] | 300 | } |
Arpit Agarwal | 34b6383 | 2016-08-08 11:59:45 -0700 | [diff] [blame] | 301 | if(!this.field){ |
| 302 | throw new Error('[xosField] Please provide a field definition'); |
| 303 | } |
| 304 | if(!this.field.type){ |
| 305 | throw new Error('[xosField] Please provide a type in the field definition'); |
| 306 | } |
| 307 | if(!$attrs.ngModel){ |
| 308 | throw new Error('[xosField] Please provide an ng-model'); |
| 309 | } |
| 310 | this.getType = XosFormHelpers._getFieldFormat; |
| 311 | this.formatLabel = LabelFormatter.format; |
| 312 | |
| 313 | this.isEmptyObject = o => o ? Object.keys(o).length === 0 : true; |
Matteo Scandolo | 65116c4 | 2016-09-21 17:06:23 -0700 | [diff] [blame] | 314 | |
| 315 | if(this.field.type === 'array'){ |
| 316 | $scope.$watch(() => this.ngModel.length, () => { |
| 317 | this.field.availableOptions = _.difference(this.field.options, this.ngModel); |
| 318 | }); |
| 319 | } |
| 320 | |
Matteo Scandolo | a5d03d5 | 2016-07-21 11:35:46 -0700 | [diff] [blame] | 321 | } |
| 322 | }) |
| 323 | |
| 324 | /** |
| 325 | * @ngdoc directive |
| 326 | * @name xos.uiComponents.directive:xosCustomValidator |
| 327 | * @restrict A |
| 328 | * @description The xosCustomValidator directive. |
| 329 | * This component apply a custom validation function |
| 330 | * @param {function} customValidator The function that execute the validation. |
| 331 | * |
| 332 | * You should do your validation here and return true | false, |
| 333 | * or alternatively you can return an array [errorName, true|false] |
| 334 | */ |
| 335 | .directive('xosCustomValidator', function(){ |
| 336 | return { |
| 337 | restrict: 'A', |
| 338 | scope: { |
| 339 | fn: '=customValidator' |
| 340 | }, |
| 341 | require: 'ngModel', |
| 342 | link: function(scope, element, attr, ctrl){ |
| 343 | if(!angular.isFunction(scope.fn)){ |
| 344 | return; |
| 345 | } |
| 346 | |
| 347 | function customValidatorWrapper(ngModelValue) { |
| 348 | const valid = scope.fn(ngModelValue); |
| 349 | if(angular.isArray(valid)){ |
| 350 | // ES6 spread rocks over fn.apply() |
| 351 | ctrl.$setValidity(...valid); |
| 352 | } |
| 353 | else{ |
| 354 | ctrl.$setValidity('custom', valid); |
| 355 | } |
| 356 | return ngModelValue; |
| 357 | } |
| 358 | |
| 359 | ctrl.$parsers.push(customValidatorWrapper); |
| 360 | } |
| 361 | }; |
| 362 | }); |
| 363 | })(); |