/* * Copyright © 2016-2019 The Thingsboard Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import './json-form.scss'; import tinycolor from 'tinycolor2'; import ObjectPath from 'objectpath'; import inspector from 'schema-inspector'; import ReactSchemaForm from './react/json-form-react.jsx'; import jsonFormTemplate from './json-form.tpl.html'; import { utils } from 'react-schema-form'; import MaterialIconsDialogController from './material-icons-dialog.controller'; import materialIconsDialogTemplate from './material-icons-dialog.tpl.html'; export default angular.module('thingsboard.directives.jsonForm', []) .directive('tbJsonForm', JsonForm) .controller('MaterialIconsDialogController', MaterialIconsDialogController) .value('ReactSchemaForm', ReactSchemaForm) .name; /*@ngInject*/ function JsonForm($compile, $templateCache, $mdColorPicker, $mdDialog, $document) { var linker = function (scope, element) { var template = $templateCache.get(jsonFormTemplate); element.html(template); var childScope; var destroyModelChangeWatches = function() { if (scope.modelWatchHandle) { scope.modelWatchHandle(); } if (scope.modelRefWatchHandle) { scope.modelRefWatchHandle(); } } var initModelChangeWatches = function() { scope.modelWatchHandle = scope.$watch('model',function(newValue, prevValue) { if (newValue && prevValue && !angular.equals(newValue,prevValue)) { scope.validate(); if (scope.formControl) { scope.formControl.$setDirty(); } } }, true); scope.modelRefWatchHandle = scope.$watch('model',function(newValue, prevValue) { if (newValue && newValue != prevValue) { scope.updateValues(); } }); }; var recompile = function() { if (childScope) { childScope.$destroy(); } childScope = scope.$new(); $compile(element.contents())(childScope); } scope.isFullscreen = false; scope.formProps = { isFullscreen: false, option: { formDefaults: { startEmpty: true } }, onModelChange: function(key, val) { if (angular.isString(val) && val === '') { val = undefined; } selectOrSet(key, scope.model, val); scope.formProps.model = scope.model; }, onColorClick: function(event, key, val) { scope.showColorPicker(event, val); }, onIconClick: function(event) { scope.openIconDialog(event); }, onToggleFullscreen: function() { scope.isFullscreen = !scope.isFullscreen; scope.formProps.isFullscreen = scope.isFullscreen; } }; scope.showColorPicker = function (event, color) { $mdColorPicker.show({ value: tinycolor(color).toRgbString(), defaultValue: '#fff', random: tinycolor.random(), clickOutsideToClose: false, hasBackdrop: false, multiple: true, preserveScope: false, mdColorAlphaChannel: true, mdColorSpectrum: true, mdColorSliders: true, mdColorGenericPalette: false, mdColorMaterialPalette: true, mdColorHistory: false, mdColorDefaultTab: 2, $event: event }).then(function (color) { if (event.data && event.data.onValueChanged) { event.data.onValueChanged(tinycolor(color).toRgb()); } }); } scope.openIconDialog = function(event) { $mdDialog.show({ controller: 'MaterialIconsDialogController', controllerAs: 'vm', templateUrl: materialIconsDialogTemplate, parent: angular.element($document[0].body), locals: {icon: scope.icon}, multiple: true, fullscreen: true, targetEvent: event }).then(function (icon) { if (event.data && event.data.onValueChanged) { event.data.onValueChanged(icon); } }); } scope.onFullscreenChanged = function() {} scope.validate = function(){ if (scope.schema && scope.model) { var result = utils.validateBySchema(scope.schema, scope.model); if (scope.formControl) { scope.formControl.$setValidity('jsonForm', result.valid); } } } scope.updateValues = function(skipRerender) { destroyModelChangeWatches(); if (!skipRerender) { element.html(template); } var readonly = (scope.readonly && scope.readonly === true) ? true : false; var schema = scope.schema ? angular.copy(scope.schema) : { type: 'object' }; schema.strict = true; var form = scope.form ? angular.copy(scope.form) : [ "*" ]; var groupInfoes = scope.groupInfoes ? angular.copy(scope.groupInfoes) : []; var model = scope.model || {}; scope.model = inspector.sanitize(schema, model).data; scope.formProps.option.formDefaults.readonly = readonly; scope.formProps.schema = schema; scope.formProps.form = form; scope.formProps.groupInfoes = groupInfoes; scope.formProps.model = angular.copy(scope.model); if (!skipRerender) { recompile(); } initModelChangeWatches(); } scope.updateValues(true); scope.$watch('readonly',function() { scope.updateValues(); }); scope.$watch('schema',function(newValue, prevValue) { if (newValue && newValue != prevValue) { scope.updateValues(); scope.validate(); } }); scope.$watch('form',function(newValue, prevValue) { if (newValue && newValue != prevValue) { scope.updateValues(); } }); scope.$watch('groupInfoes',function(newValue, prevValue) { if (newValue && newValue != prevValue) { scope.updateValues(); } }); scope.validate(); recompile(); } return { restrict: "E", scope: { schema: '=', form: '=', model: '=', formControl: '=', groupInfoes: '=', readonly: '=' }, link: linker }; } function setValue(obj, key, val) { var changed = false; if (obj) { if (angular.isUndefined(val)) { if (angular.isDefined(obj[key])) { delete obj[key]; changed = true; } } else { changed = !angular.equals(obj[key], val); obj[key] = val; } } return changed; } function selectOrSet(projection, obj, valueToSet) { var numRe = /^\d+$/; if (!obj) { obj = this; } if (!obj) { return false; } var parts = angular.isString(projection) ? ObjectPath.parse(projection) : projection; if (parts.length === 1) { return setValue(obj, parts[0], valueToSet); } if (angular.isUndefined(obj[parts[0]])) { obj[parts[0]] = parts.length > 2 && numRe.test(parts[1]) ? [] : {}; } var value = obj[parts[0]]; for (var i = 1; i < parts.length; i++) { if (parts[i] === '') { return false; } if (i === parts.length - 1) { return setValue(value, parts[i], valueToSet); } else { var tmp = value[parts[i]]; if (angular.isUndefined(tmp) || tmp === null) { tmp = numRe.test(parts[i + 1]) ? [] : {}; value[parts[i]] = tmp; } value = tmp; } } return value; }