From 0fc0f0ddc3fa77d18894656a356505c178c9977f Mon Sep 17 00:00:00 2001 From: Andrew Fontana <32223252+Kharg@users.noreply.github.com> Date: Sun, 4 Sep 2022 15:47:06 +0200 Subject: [PATCH] Removed alert() and added Espo.Ui --- .../Resources/i18n/en_GB/Admin.json | 5 + .../Resources/i18n/en_US/Admin.json | 5 + .../Resources/i18n/es_ES/Admin.json | 5 + .../Resources/i18n/es_MX/Admin.json | 5 + .../Resources/metadata/app/client.json | 10 + .../metadata/fields/dynamic-checklist.json | 40 ++ .../lib/module-js-functions.js | 25 ++ .../fields/dynamic-checklist/edit.tpl | 24 ++ .../src/views/fields/dynamic-checklist.js | 402 ++++++++++++++++++ manifest.json | 10 + 10 files changed, 531 insertions(+) create mode 100644 files/application/Espo/Modules/DynamicChecklist/Resources/i18n/en_GB/Admin.json create mode 100644 files/application/Espo/Modules/DynamicChecklist/Resources/i18n/en_US/Admin.json create mode 100644 files/application/Espo/Modules/DynamicChecklist/Resources/i18n/es_ES/Admin.json create mode 100644 files/application/Espo/Modules/DynamicChecklist/Resources/i18n/es_MX/Admin.json create mode 100644 files/application/Espo/Modules/DynamicChecklist/Resources/metadata/app/client.json create mode 100644 files/application/Espo/Modules/DynamicChecklist/Resources/metadata/fields/dynamic-checklist.json create mode 100644 files/client/modules/dynamic-checklist/lib/module-js-functions.js create mode 100644 files/client/modules/dynamic-checklist/res/templates/fields/dynamic-checklist/edit.tpl create mode 100644 files/client/modules/dynamic-checklist/src/views/fields/dynamic-checklist.js create mode 100644 manifest.json diff --git a/files/application/Espo/Modules/DynamicChecklist/Resources/i18n/en_GB/Admin.json b/files/application/Espo/Modules/DynamicChecklist/Resources/i18n/en_GB/Admin.json new file mode 100644 index 0000000..6291e86 --- /dev/null +++ b/files/application/Espo/Modules/DynamicChecklist/Resources/i18n/en_GB/Admin.json @@ -0,0 +1,5 @@ +{ + "fieldTypes": { + "dynamic-checklist": "Dynamic Checklist" + } +} \ No newline at end of file diff --git a/files/application/Espo/Modules/DynamicChecklist/Resources/i18n/en_US/Admin.json b/files/application/Espo/Modules/DynamicChecklist/Resources/i18n/en_US/Admin.json new file mode 100644 index 0000000..6291e86 --- /dev/null +++ b/files/application/Espo/Modules/DynamicChecklist/Resources/i18n/en_US/Admin.json @@ -0,0 +1,5 @@ +{ + "fieldTypes": { + "dynamic-checklist": "Dynamic Checklist" + } +} \ No newline at end of file diff --git a/files/application/Espo/Modules/DynamicChecklist/Resources/i18n/es_ES/Admin.json b/files/application/Espo/Modules/DynamicChecklist/Resources/i18n/es_ES/Admin.json new file mode 100644 index 0000000..d71b36b --- /dev/null +++ b/files/application/Espo/Modules/DynamicChecklist/Resources/i18n/es_ES/Admin.json @@ -0,0 +1,5 @@ +{ + "fieldTypes": { + "dynamic-checklist": "Lista Interactiva" + } +} \ No newline at end of file diff --git a/files/application/Espo/Modules/DynamicChecklist/Resources/i18n/es_MX/Admin.json b/files/application/Espo/Modules/DynamicChecklist/Resources/i18n/es_MX/Admin.json new file mode 100644 index 0000000..d71b36b --- /dev/null +++ b/files/application/Espo/Modules/DynamicChecklist/Resources/i18n/es_MX/Admin.json @@ -0,0 +1,5 @@ +{ + "fieldTypes": { + "dynamic-checklist": "Lista Interactiva" + } +} \ No newline at end of file diff --git a/files/application/Espo/Modules/DynamicChecklist/Resources/metadata/app/client.json b/files/application/Espo/Modules/DynamicChecklist/Resources/metadata/app/client.json new file mode 100644 index 0000000..23c9ff6 --- /dev/null +++ b/files/application/Espo/Modules/DynamicChecklist/Resources/metadata/app/client.json @@ -0,0 +1,10 @@ +{ + "developerModeScriptList": [ + "__APPEND__", + "client/modules/dynamic-checklist/lib/module-js-functions.js" + ], + "scriptList": [ + "__APPEND__", + "client/modules/dynamic-checklist/lib/module-js-functions.js" + ] +} \ No newline at end of file diff --git a/files/application/Espo/Modules/DynamicChecklist/Resources/metadata/fields/dynamic-checklist.json b/files/application/Espo/Modules/DynamicChecklist/Resources/metadata/fields/dynamic-checklist.json new file mode 100644 index 0000000..89bd0b2 --- /dev/null +++ b/files/application/Espo/Modules/DynamicChecklist/Resources/metadata/fields/dynamic-checklist.json @@ -0,0 +1,40 @@ +{ + "view":"dynamic-checklist:views/fields/dynamic-checklist", + + "params":[ + { + "name":"required", + "type":"bool", + "default":false + }, + { + "name": "allowCustomOptions", + "type": "bool", + "hidden": true + }, + { + "name": "noEmptyString", + "type": "bool", + "default": true + }, + { + "name": "displayAsList", + "type":"bool", + "default": true + } + + ], + "validationList": [ + "required" + ], + "filter": true, + "notCreatable": false, + "notSortable": true, + "fieldDefs":{ + "type":"jsonArray", + "storeArrayValues": false + }, + "translatedOptions": true, + "dynamicLogicOptions": true, + "personalData": false +} diff --git a/files/client/modules/dynamic-checklist/lib/module-js-functions.js b/files/client/modules/dynamic-checklist/lib/module-js-functions.js new file mode 100644 index 0000000..aec4081 --- /dev/null +++ b/files/client/modules/dynamic-checklist/lib/module-js-functions.js @@ -0,0 +1,25 @@ +function decodeHTML(encodedHTML) { + var workArea = document.createElement('textarea'); + workArea.innerHTML = encodedHTML; + return workArea.value; +} + +function replaceLabel(objArray,oldLabel,newLabel) { + let obj = objArray.find((o,i) => { + if(o.label === oldLabel) { + objArray[i].label = newLabel; + return true; // stop searching + } + }); + return objArray; +} + +function replaceJsonObjectinArrayByReference(objArray,refName,refValue,newObject){ + let obj = objArray.find((o, i) => { + if (o.refName === refValue) { + objArray[i] = newObject; + return true; // stop searching + } + }); + return objArray; +} \ No newline at end of file diff --git a/files/client/modules/dynamic-checklist/res/templates/fields/dynamic-checklist/edit.tpl b/files/client/modules/dynamic-checklist/res/templates/fields/dynamic-checklist/edit.tpl new file mode 100644 index 0000000..ed32076 --- /dev/null +++ b/files/client/modules/dynamic-checklist/res/templates/fields/dynamic-checklist/edit.tpl @@ -0,0 +1,24 @@ + +
+{{#if hasOptions}} + +{{/if}} +{{#if allowCustomOptions}} +
+ + + + +
+ +{{/if}} +
diff --git a/files/client/modules/dynamic-checklist/src/views/fields/dynamic-checklist.js b/files/client/modules/dynamic-checklist/src/views/fields/dynamic-checklist.js new file mode 100644 index 0000000..ef62a74 --- /dev/null +++ b/files/client/modules/dynamic-checklist/src/views/fields/dynamic-checklist.js @@ -0,0 +1,402 @@ +/************************************************************************ + * This file is part of EspoCRM. + * + * EspoCRM - Open Source CRM application. + * Copyright (C) 2014-2019 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko + * Website: https://www.espocrm.com + * + * EspoCRM is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * EspoCRM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with EspoCRM. If not, see http://www.gnu.org/licenses/. + * + * The interactive user interfaces in modified source and object code versions + * of this program must display Appropriate Legal Notices, as required under + * Section 5 of the GNU General Public License version 3. + * + * In accordance with Section 7(b) of the GNU General Public License version 3, + * these Appropriate Legal Notices must retain the display of the "EspoCRM" word. + ************************************************************************/ + +define('dynamic-checklist:views/fields/dynamic-checklist', ['views/fields/array'], function (Dep) { + + return Dep.extend({ + + type: 'dynamic-checklist', + + listTemplate: 'fields/array/list', + + detailTemplate: 'fields/array/detail', + + editTemplate: 'dynamic-checklist:fields/dynamic-checklist/edit', + + searchTemplate: 'fields/array/search', + + searchTypeList: ['anyOf', 'noneOf', 'allOf', 'isEmpty', 'isNotEmpty'], + + maxItemLength: null, + + validations: ['required', 'maxCount'], + + isInversed: false, + + existingObj: null, + + displayAsList: true, + + data: function () { + var itemHtmlList = []; + (this.selected || []).forEach(function (jsonItem) { + itemHtmlList.push(this.getItemHtml(jsonItem)); + }, this); + + return _.extend({ + selected: this.selected, + translatedOptions: this.translatedOptions, + hasOptions: this.params.options ? true : false, + itemHtmlList: itemHtmlList, + isEmpty: (this.selected || []).length === 0, + valueIsSet: this.model.has(this.name), + maxItemLength: this.maxItemLength, + allowCustomOptions: this.allowCustomOptions + }, Dep.prototype.data.call(this)); + }, + + events: { + 'click [data-action="removeValue"]': function (e) { + var value = $(e.currentTarget).data('value').toString(); + this.removeValue(value); + }, + 'click [data-action="editValue"]': function (e) { + var existingValue = $(e.currentTarget).data('value').toString(); + this.editLabel(existingValue); + } + }, + + getItemHtml: function(jsonItem) { + // breakdown a given checklist item into its components: label and checkbox value + var label = this.escapeValue(jsonItem.label); + if (this.translatedOptions) { + if ((label in this.translatedOptions)) { + label = this.translatedOptions[label]; + label = label.toString(); + label = this.escapeValue(label); + } + } + var dataName = 'checklistItem-'+this.name+'-'+label; + var id = 'checklist-item-'+this.name+'-'+label; + var isChecked = false; + if( jsonItem.state == "1"){ + isChecked = true; + } + if(this.isInversed){ + isChecked = !isChecked; + } + var dataValue = this.escapeValue(JSON.stringify(jsonItem)); + var itemHtml = '
'; + itemHtml += '
'; + itemHtml += '
'; + itemHtml += '
'; + itemHtml += ''; + itemHtml += '
'; + return itemHtml; + }, + + addValue: function (label) { + var isNew = true; + // convert the label into a JSON object with the default state value of zero + var jsonItem = {}; + jsonItem.offVal = "0"; + jsonItem.label = label; + jsonItem.state = "0"; + var targetObj = this.selected.find(o => o.label === label); + if(targetObj) { + Espo.Ui.error("Duplicate checklist labels are not allowed"); + isNew = false; + } + // it it doesn't exist append to the "selected" array and to the list html + if(isNew) { + // create the rendering html code + var html = this.getItemHtml(jsonItem); + // append the html to the existing list of items + this.$list.append(html); + // append the new dynamic checklist item to the "selected" array + this.selected.push(jsonItem); + // trigger the "change" event + this.trigger('change'); + } + }, + + editLabel: function (existingLabel) { + var valueInternal = existingLabel.replace(/"/g, '\\"'); + // remove the element from the DOM + this.$list.children('[data-label="' + valueInternal + '"]').remove(); + // get the existing item object + this.existingObj = this.selected.find(o => o.label === existingLabel); + // display the label on the "updateItem" input container for editing + var $inputContainer = $('input.updateItem'); + $inputContainer.val(existingLabel); + // hide the "addItem" input-group div and display instead the "updateItem" input-group div + this.$el.find('div.addItem').hide(); + this.$el.find('div.updateItem').show(); + // enable the "updateItem" button + this.controlUpdateItemButton(); + }, + + updateLabel: function (newLabel) { + // get the existing element json object position inside the array + var index = this.selected.indexOf(this.existingObj); + // update the array of json objects + this.selected[index].label = newLabel; + this.selected[index].state = this.existingObj.state; + // create the updated element rendering html code + var html = this.getItemHtml(this.selected[index]); + // append the html to the existing list of items + this.$list.append(html); + // trigger the "change" event + this.trigger('change'); + }, + + removeValue: function (label) { + var valueInternal = label.replace(/"/g, '\\"'); + // remove the element from the DOM + this.$list.children('[data-label="' + valueInternal + '"]').remove(); + // find the element json object in the "selected" array + var targetObj = this.selected.find(o => o.label === label); + // get the element json object position inside the array + var index = this.selected.indexOf(targetObj); + // remove the element from the array of json objects + this.selected.splice(index, 1); + // trigger the 'change' event + this.trigger('change'); + }, + + getValueForDisplay: function () { + var list = this.selected.map(function (jsonItem) { + // get the checklist item label + var label = this.escapeValue(jsonItem.label); + if (this.translatedOptions) { + if ((label in this.translatedOptions)) { + label = this.translatedOptions[label]; + label = label.toString(); + label = this.escapeValue(label); + } + } + if (label === '') { + label = this.translate('None'); + } + var style = this.styleMap[jsonItem.label] || 'default'; + if (this.params.displayAsLabel) { + label = '' + label + ''; + } else { + if (style && style != 'default') { + label = '' + label + ''; + } + } + var displayHtml = ''; + // get the option checkbox boolean value and generate its html code + var dataName = 'checklistItem-'+this.name+'-'+label; + var id = 'checklist-item-'+this.name+'-'+label; + var isChecked = false; + if( jsonItem.state == "1"){ + isChecked = true; + } + if(this.isInversed){ + isChecked = !isChecked; + } + displayHtml += '
'; + displayHtml += '
'; + displayHtml += ''; + displayHtml += '
'; + //displayHtml += ' '+label; + return displayHtml; + }, this); + if (this.displayAsList) { + if (!list.length) return ''; + var itemClassName = 'multi-enum-item-container'; + if (this.displayAsLabel) { + itemClassName += ' multi-enum-item-label-container'; + } + return '
' + + list.join('
') + '
'; + } else if (this.displayAsLabel) { + return list.join(' '); + } else { + return list.join(', '); + } + }, + + fetchFromDom: function () { + var selected = []; + this.$el.find('.list-group .list-group-item').each(function (i, el) { + var updatedValue = {}; + // fetch the original data-value + var existingValue = $(el).data('value'); + var label = existingValue.label; + // fetch the current boolean value (0 or 1) + var currentState = $(el).find('input:checkbox:first:checked').length.toString(); + // build the current item object + updatedValue.label = label; + updatedValue.state = currentState; + // update the element's data-value attribute + $(el).attr('data-value', updatedValue); + // append the 'selected' array + selected.push(updatedValue); + }); + this.selected = selected; + }, + + controlAddItemButton: function () { + var $addItemInput = this.$addItemInput; + if (!$addItemInput) return; + if (!$addItemInput.get(0)) return; + + var value = $addItemInput.val().toString(); + if (!value && this.params.noEmptyString) { + this.$addButton.addClass('disabled').attr('disabled', 'disabled'); + } else { + this.$addButton.removeClass('disabled').removeAttr('disabled'); + } + }, + + controlUpdateItemButton: function () { + //alert("controlUpdateItemButton function invoked"); + var $updateItemInput = this.$updateItemInput; + if (!$updateItemInput) return; + if (!$updateItemInput.get(0)) return; + + var value = $updateItemInput.val().toString(); + if (!value && this.params.noEmptyString) { + this.$updateButton.addClass('disabled').attr('disabled', 'disabled'); + } else { + this.$updateButton.removeClass('disabled').removeAttr('disabled'); + } + }, + + afterRender: function () { + if (this.mode == 'edit') { + this.$list = this.$el.find('.list-group'); + + // prepare the add item and update item inputs + var $addItemInput = this.$addItemInput = this.$el.find('input.addItem'); + var $updateItemInput = this.$updateItemInput = this.$el.find('input.updateItem'); + + if (this.allowCustomOptions) { + this.$addButton = this.$el.find('button[data-action="addItem"]'); + this.$updateButton = this.$el.find('button[data-action="updateItem"]'); + + this.$addButton.on('click', function () { + var label = this.$addItemInput.val().toString(); + this.addValue(label); + $addItemInput.val(''); + this.controlAddItemButton(); + // update the model + this.inlineEditSave(); + // reinstate the inline edit mode + this.inlineEdit(); + }.bind(this)); + + this.$updateButton.on('click', function () { + var label = this.$updateItemInput.val().toString(); + this.updateLabel(label); + $updateItemInput.val(''); + this.controlUpdateItemButton(); + // update the model + this.inlineEditSave(); + // reinstate the inline edit mode + this.inlineEdit(); + }.bind(this)); + + $addItemInput.on('input', function () { + this.controlAddItemButton(); + }.bind(this)); + + $updateItemInput.on('input', function () { + this.controlUpdateItemButton(); + }.bind(this)); + + // add the new item and updated the list if user presses 'Enter' or "Tab" + $addItemInput.on('keypress', function (e) { + if (e.keyCode === 13 || e.keyCode === 9) { + var label = $addItemInput.val().toString(); + if (this.params.noEmptyString) { + if (label == '') { + return; + } + } + this.addValue(label); + $addItemInput.val(''); + this.controlAddItemButton(); + // update the model + //this.inlineEditSave(); + // reinstate the inline edit mode + //this.inlineEdit(); + } + }.bind(this)); + + // update the item and update the list if the user presses 'Enter' + $updateItemInput.on('keypress', function (e) { + if (e.keyCode === 13) { + var label = $updateItemInput.val().toString(); + if (this.params.noEmptyString) { + if (label == '') { + return; + } + } + this.updateLabel(label); + $updateItemInput.val(''); + this.controlUpdateItemButton(); + // update the model + //this.inlineEditSave(); + // reinstate the inline edit mode + //this.inlineEdit(); + } + }.bind(this)); + + this.controlAddItemButton(); + } + + this.$list.sortable({ + stop: function () { + this.fetchFromDom(); + this.trigger('change'); + }.bind(this) + }); + + } + + if (this.mode == 'search') { + this.renderSearch(); + } + + // whenever any checkbox changes, update the item data-value and trigger the change event + this.$el.find('input:checkbox').on('change', function () { + this.fetchFromDom(); + this.trigger('change'); + }.bind(this)); + + } + + }); +}); diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..520865e --- /dev/null +++ b/manifest.json @@ -0,0 +1,10 @@ +{ + "name": "Dynamic Checklist Field Type", + "version": "2.0.1", + "acceptableVersions": [ + ">=6.0.6" + ], + "releaseDate": "2020-12-01", + "author": "Omar A Gonsenheim", + "description": "Dynamic Checklist Field Type for EspoCRM" +}