blob: 5294229e5c7721105df85432461e79df3e567fd3 [file] [log] [blame]
Matteo Scandolo7bc39c42016-04-20 11:38:42 -07001/**
2 * © OpenCORD
3 *
4 * Visit http://guide.xosproject.org/devguide/addview/ for more information
5 *
6 * Created by teone on 4/18/16.
7 */
8
9(function () {
10 'use strict';
11
12 angular.module('xos.uiComponents')
13
14 /**
15 * @ngdoc directive
16 * @name xos.uiComponents.directive:xosForm
17 * @restrict E
Matteo Scandolo71378f92016-04-28 14:16:45 -070018 * @description The xos-form directive.
19 * This components have two usage, given a model it is able to autogenerate a form or it can be configured to create a custom form.
Matteo Scandolo7bc39c42016-04-20 11:38:42 -070020 * @param {Object} config The configuration object
21 * ```
22 * {
23 * exclude: ['id', 'validators', 'created', 'updated', 'deleted'], //field to be skipped in the form, the provide values are concatenated
24 * actions: [ // define the form buttons with related callback
25 * {
26 label: 'save',
27 icon: 'ok', // refers to bootstraps glyphicon
28 cb: (user) => { // receive the model
29 console.log(user);
30 },
31 class: 'success'
32 }
Matteo Scandolo71378f92016-04-28 14:16:45 -070033 * ],
34 * fields: {
35 * field_name: {
36 * label: 'Field Label',
37 * type: 'string' // options are: [date, boolean, number, email, string],
38 * validators: {
39 * minlength: number,
40 maxlength: number,
41 required: boolean,
42 min: number,
43 max: number
44 * }
45 * }
46 * }
Matteo Scandolo7bc39c42016-04-20 11:38:42 -070047 * }
48 * ```
49 * @element ANY
50 * @scope
51 * @example
Matteo Scandolo71378f92016-04-28 14:16:45 -070052
53 Autogenerated form
54
Matteo Scandolo840260d2016-04-22 09:56:48 -070055 <example module="sampleForm">
Matteo Scandolo7bc39c42016-04-20 11:38:42 -070056 <file name="script.js">
Matteo Scandolo840260d2016-04-22 09:56:48 -070057 angular.module('sampleForm', ['xos.uiComponents'])
Matteo Scandolo199ec002016-04-22 10:53:49 -070058 .factory('_', function($window){
59 return $window._;
60 })
Matteo Scandolo840260d2016-04-22 09:56:48 -070061 .controller('SampleCtrl', function(){
62 this.model = {
Matteo Scandolo199ec002016-04-22 10:53:49 -070063 first_name: 'Jhon',
64 last_name: 'Doe',
65 email: 'jhon.doe@sample.com',
66 active: true,
67 birthDate: '2015-02-17T22:06:38.059000Z'
Matteo Scandolo840260d2016-04-22 09:56:48 -070068 }
69 this.config = {
70 exclude: ['password', 'last_login'],
71 formName: 'sampleForm',
72 actions: [
73 {
74 label: 'Save',
75 icon: 'ok', // refers to bootstraps glyphicon
76 cb: (user) => { // receive the model
77 console.log(user);
78 },
79 class: 'success'
80 }
81 ]
Matteo Scandolo7bc39c42016-04-20 11:38:42 -070082 };
83 });
84 </file>
Matteo Scandolo71378f92016-04-28 14:16:45 -070085 <file name="index.html">
86 <div ng-controller="SampleCtrl as vm">
87 <xos-form ng-model="vm.model" config="vm.config"></xos-form>
88 </div>
89 </file>
90 </example>
91
92 Configuration defined form
93
94 <example module="sampleForm1">
95 <file name="script.js">
96 angular.module('sampleForm1', ['xos.uiComponents'])
97 .factory('_', function($window){
98 return $window._;
99 })
100 .controller('SampleCtrl1', function(){
101 this.model = {
102 };
103
104 this.config = {
105 exclude: ['password', 'last_login'],
106 formName: 'sampleForm1',
107 actions: [
108 {
109 label: 'Save',
110 icon: 'ok', // refers to bootstraps glyphicon
111 cb: (user) => { // receive the model
112 console.log(user);
113 },
114 class: 'success'
115 }
116 ],
117 fields: {
118 first_name: {
119 type: 'string',
120 validators: {
121 required: true
122 }
123 },
124 last_name: {
125 label: 'Surname',
126 type: 'string',
127 validators: {
128 required: true,
129 minlength: 10
130 }
131 },
132 age: {
133 type: 'number',
134 validators: {
135 required: true,
136 min: 21
137 }
138 },
139 }
140 };
141 });
142 </file>
143 <file name="index.html">
144 <div ng-controller="SampleCtrl1 as vm">
145 <xos-form ng-model="vm.model" config="vm.config"></xos-form>
146 </div>
147 </file>
Matteo Scandolo7bc39c42016-04-20 11:38:42 -0700148 </example>
149
150 **/
151
152 .directive('xosForm', function(){
153 return {
154 restrict: 'E',
155 scope: {
156 config: '=',
157 ngModel: '='
158 },
159 template: `
Matteo Scandolo4ba4cf12016-04-20 16:36:17 -0700160 <ng-form name="vm.{{vm.config.formName || 'form'}}">
Matteo Scandolo6e2e6ff2016-04-20 14:59:39 -0700161 <div class="form-group" ng-repeat="(name, field) in vm.formField">
Matteo Scandolo9f0e5ae2016-04-20 12:24:52 -0700162 <label>{{field.label}}</label>
Matteo Scandolob3d686f2016-04-22 14:14:03 -0700163 <input
164 ng-if="field.type !== 'boolean'"
165 type="{{field.type}}"
166 name="{{name}}"
167 class="form-control"
168 ng-model="vm.ngModel[name]"
169 ng-minlength="field.validators.minlength || 0"
170 ng-maxlength="field.validators.maxlength || 2000"
Matteo Scandolo01c87572016-04-25 08:15:24 -0700171 ng-required="field.validators.required || false" />
Matteo Scandolo4ba4cf12016-04-20 16:36:17 -0700172 <span class="boolean-field" ng-if="field.type === 'boolean'">
173 <button
174 class="btn btn-success"
175 ng-show="vm.ngModel[name]"
176 ng-click="vm.ngModel[name] = false">
177 <i class="glyphicon glyphicon-ok"></i>
178 </button>
179 <button
180 class="btn btn-danger"
181 ng-show="!vm.ngModel[name]"
182 ng-click="vm.ngModel[name] = true">
183 <i class="glyphicon glyphicon-remove"></i>
184 </button>
185 </span>
Matteo Scandolob3d686f2016-04-22 14:14:03 -0700186 <!-- <pre>{{vm[vm.config.formName][name].$error | json}}</pre> -->
Matteo Scandolo4ba4cf12016-04-20 16:36:17 -0700187 <xos-validation errors="vm[vm.config.formName || 'form'][name].$error"></xos-validation>
Matteo Scandolo7bc39c42016-04-20 11:38:42 -0700188 </div>
189 <div class="form-group" ng-if="vm.config.actions">
Matteo Scandolo4ba4cf12016-04-20 16:36:17 -0700190 <button role="button" href=""
Matteo Scandolo7bc39c42016-04-20 11:38:42 -0700191 ng-repeat="action in vm.config.actions"
192 ng-click="action.cb(vm.ngModel)"
193 class="btn btn-{{action.class}}"
194 title="{{action.label}}">
195 <i class="glyphicon glyphicon-{{action.icon}}"></i>
196 {{action.label}}
197 </button>
198 </div>
199 </ng-form>
200 `,
201 bindToController: true,
202 controllerAs: 'vm',
Matteo Scandolo9f0e5ae2016-04-20 12:24:52 -0700203 controller: function($scope, $log, _, XosFormHelpers){
Matteo Scandolo7bc39c42016-04-20 11:38:42 -0700204
205 if(!this.config){
206 throw new Error('[xosForm] Please provide a configuration via the "config" attribute');
207 }
208
209 if(!this.config.actions){
210 throw new Error('[xosForm] Please provide an action list in the configuration');
211 }
212
Matteo Scandolo7bc39c42016-04-20 11:38:42 -0700213 this.excludedField = ['id', 'validators', 'created', 'updated', 'deleted', 'backend_status'];
214 if(this.config && this.config.exclude){
215 this.excludedField = this.excludedField.concat(this.config.exclude);
216 }
217
218
219 this.formField = [];
220 $scope.$watch(() => this.ngModel, (model) => {
Matteo Scandoloe2ee2d92016-04-27 15:58:16 -0700221
222 // empty from old stuff
223 this.formField = {};
224
Matteo Scandolo7bc39c42016-04-20 11:38:42 -0700225 if(!model){
226 return;
227 }
Matteo Scandoloe2ee2d92016-04-27 15:58:16 -0700228
229 let diff = _.difference(Object.keys(model), this.excludedField);
230 let modelField = XosFormHelpers.parseModelField(diff);
231 this.formField = XosFormHelpers.buildFormStructure(modelField, this.config.fields, model);
232 });
Matteo Scandolo7bc39c42016-04-20 11:38:42 -0700233
234 }
235 }
236 })
237 .service('XosFormHelpers', function(_, LabelFormatter){
238
Matteo Scandolo6e2e6ff2016-04-20 14:59:39 -0700239 this._isEmail = (text) => {
240 var re = /(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))/;
241 return re.test(text);
242 };
243
Matteo Scandolo7bc39c42016-04-20 11:38:42 -0700244 this._getFieldFormat = (value) => {
245
246 // check if is date
Matteo Scandoloa9524672016-04-26 16:34:56 -0700247 if (_.isDate(value) || (!Number.isNaN(Date.parse(value)) && new Date(value).getTime() > 631180800000)){
Matteo Scandolo7bc39c42016-04-20 11:38:42 -0700248 return 'date';
249 }
250
251 // check if is boolean
252 // isNaN(false) = false, false is a number (0), true is a number (1)
253 if(typeof value === 'boolean'){
254 return 'boolean';
255 }
256
257 // check if a string is a number
Matteo Scandolob3d686f2016-04-22 14:14:03 -0700258 if(!isNaN(value) && value !== null){
Matteo Scandolo7bc39c42016-04-20 11:38:42 -0700259 return 'number';
260 }
261
Matteo Scandolo6e2e6ff2016-04-20 14:59:39 -0700262 // check if a string is an email
263 if(this._isEmail(value)){
264 return 'email';
265 }
266
Matteo Scandolob3d686f2016-04-22 14:14:03 -0700267 // if null return string
268 if(value === null){
269 return 'string';
270 }
271
Matteo Scandolo7bc39c42016-04-20 11:38:42 -0700272 return typeof value;
273 };
274
275 this.buildFormStructure = (modelField, customField, model) => {
Matteo Scandolo199ec002016-04-22 10:53:49 -0700276
Matteo Scandoloe2ee2d92016-04-27 15:58:16 -0700277 modelField = Object.keys(modelField).length > 0 ? modelField : customField; //if no model field are provided, check custom
Matteo Scandolo199ec002016-04-22 10:53:49 -0700278 customField = customField || {};
279
Matteo Scandolo7bc39c42016-04-20 11:38:42 -0700280 return _.reduce(Object.keys(modelField), (form, f) => {
Matteo Scandolob3d686f2016-04-22 14:14:03 -0700281
Matteo Scandolo7bc39c42016-04-20 11:38:42 -0700282 form[f] = {
283 label: (customField[f] && customField[f].label) ? `${customField[f].label}:` : LabelFormatter.format(f),
284 type: (customField[f] && customField[f].type) ? customField[f].type : this._getFieldFormat(model[f]),
Matteo Scandolob3d686f2016-04-22 14:14:03 -0700285 validators: (customField[f] && customField[f].validators) ? customField[f].validators : {}
Matteo Scandolo7bc39c42016-04-20 11:38:42 -0700286 };
Matteo Scandolo6e2e6ff2016-04-20 14:59:39 -0700287
288 if(form[f].type === 'date'){
289 model[f] = new Date(model[f]);
290 }
291
Matteo Scandolo4ba4cf12016-04-20 16:36:17 -0700292 if(form[f].type === 'number'){
293 model[f] = parseInt(model[f], 10);
294 }
295
Matteo Scandolo7bc39c42016-04-20 11:38:42 -0700296 return form;
297 }, {});
298 };
299
300 this.parseModelField = (fields) => {
301 return _.reduce(fields, (form, f) => {
302 form[f] = {};
303 return form;
304 }, {});
305 }
306
307 })
308})();