From 0406c8f9babc630b2c85fb14875329462a91ee15 Mon Sep 17 00:00:00 2001 From: Coleman Watts Date: Sun, 29 Dec 2019 23:29:18 -0500 Subject: [PATCH] GUI - present Individual/Organization/Household as seperate entities Also adds per-contact-type default name blocks. --- .../core/ang/blockNameHousehold.aff.html | 4 + .../core/ang/blockNameHousehold.aff.json | 4 + .../core/ang/blockNameIndividual.aff.json | 2 +- .../core/ang/blockNameOrganization.aff.html | 5 + .../core/ang/blockNameOrganization.aff.json | 4 + ext/afform/gui/afformEntities/Activity.php | 6 ++ ext/afform/gui/afformEntities/Household.php | 12 +++ ext/afform/gui/afformEntities/Individual.php | 12 +++ .../gui/afformEntities/Organization.php | 12 +++ ext/afform/gui/afform_gui.php | 92 +++++++++++-------- ext/afform/gui/ang/afGuiEditor.js | 51 ++++++---- ext/afform/gui/ang/afGuiEditor/entity.html | 8 +- ext/afform/gui/ang/afGuiEditor/palette.html | 4 +- 13 files changed, 149 insertions(+), 67 deletions(-) create mode 100644 ext/afform/core/ang/blockNameHousehold.aff.html create mode 100644 ext/afform/core/ang/blockNameHousehold.aff.json create mode 100644 ext/afform/core/ang/blockNameOrganization.aff.html create mode 100644 ext/afform/core/ang/blockNameOrganization.aff.json create mode 100644 ext/afform/gui/afformEntities/Activity.php create mode 100644 ext/afform/gui/afformEntities/Household.php create mode 100644 ext/afform/gui/afformEntities/Individual.php create mode 100644 ext/afform/gui/afformEntities/Organization.php diff --git a/ext/afform/core/ang/blockNameHousehold.aff.html b/ext/afform/core/ang/blockNameHousehold.aff.html new file mode 100644 index 0000000000..a4f7a4cb7a --- /dev/null +++ b/ext/afform/core/ang/blockNameHousehold.aff.html @@ -0,0 +1,4 @@ +
+ + +
diff --git a/ext/afform/core/ang/blockNameHousehold.aff.json b/ext/afform/core/ang/blockNameHousehold.aff.json new file mode 100644 index 0000000000..dc0847fc13 --- /dev/null +++ b/ext/afform/core/ang/blockNameHousehold.aff.json @@ -0,0 +1,4 @@ +{ + "title": "Household Name (default)", + "block": "Household" +} diff --git a/ext/afform/core/ang/blockNameIndividual.aff.json b/ext/afform/core/ang/blockNameIndividual.aff.json index 9a328cac39..3d05402fb0 100644 --- a/ext/afform/core/ang/blockNameIndividual.aff.json +++ b/ext/afform/core/ang/blockNameIndividual.aff.json @@ -1,4 +1,4 @@ { "title": "Individual Name (default)", - "block": "Contact" + "block": "Individual" } diff --git a/ext/afform/core/ang/blockNameOrganization.aff.html b/ext/afform/core/ang/blockNameOrganization.aff.html new file mode 100644 index 0000000000..6ab5eb2b49 --- /dev/null +++ b/ext/afform/core/ang/blockNameOrganization.aff.html @@ -0,0 +1,5 @@ +
+ + + +
diff --git a/ext/afform/core/ang/blockNameOrganization.aff.json b/ext/afform/core/ang/blockNameOrganization.aff.json new file mode 100644 index 0000000000..34ab2fd53d --- /dev/null +++ b/ext/afform/core/ang/blockNameOrganization.aff.json @@ -0,0 +1,4 @@ +{ + "title": "Organization Name (default)", + "block": "Organization" +} diff --git a/ext/afform/gui/afformEntities/Activity.php b/ext/afform/gui/afformEntities/Activity.php new file mode 100644 index 0000000000..4d362f1fda --- /dev/null +++ b/ext/afform/gui/afformEntities/Activity.php @@ -0,0 +1,6 @@ + 'Activity', + 'label' => ts('Activity'), + 'defaults' => "{'url-autofill': '1'}", +]; diff --git a/ext/afform/gui/afformEntities/Household.php b/ext/afform/gui/afformEntities/Household.php new file mode 100644 index 0000000000..0788e51fe1 --- /dev/null +++ b/ext/afform/gui/afformEntities/Household.php @@ -0,0 +1,12 @@ + 'Contact', + 'contact_type' => 'Household', + 'defaults' => "{ + data: { + contact_type: 'Household', + source: afform.title + }, + 'url-autofill': '1' + }", +]; diff --git a/ext/afform/gui/afformEntities/Individual.php b/ext/afform/gui/afformEntities/Individual.php new file mode 100644 index 0000000000..626518f3bf --- /dev/null +++ b/ext/afform/gui/afformEntities/Individual.php @@ -0,0 +1,12 @@ + 'Contact', + 'contact_type' => 'Individual', + 'defaults' => "{ + data: { + contact_type: 'Individual', + source: afform.title + }, + 'url-autofill': '1' + }", +]; diff --git a/ext/afform/gui/afformEntities/Organization.php b/ext/afform/gui/afformEntities/Organization.php new file mode 100644 index 0000000000..358dcf3cda --- /dev/null +++ b/ext/afform/gui/afformEntities/Organization.php @@ -0,0 +1,12 @@ + 'Contact', + 'contact_type' => 'Organization', + 'defaults' => "{ + data: { + contact_type: 'Organization', + source: afform.title + }, + 'url-autofill': '1' + }", +]; diff --git a/ext/afform/gui/afform_gui.php b/ext/afform/gui/afform_gui.php index db98953764..040fc2e1a8 100644 --- a/ext/afform/gui/afform_gui.php +++ b/ext/afform/gui/afform_gui.php @@ -162,27 +162,51 @@ function afform_gui_civicrm_buildAsset($asset, $params, &$mimeType, &$content) { return; } - $entityWhitelist = $data = []; + $getFieldParams = [ + 'checkPermissions' => FALSE, + 'includeCustom' => TRUE, + 'loadOptions' => TRUE, + 'action' => 'create', + 'select' => ['name', 'title', 'input_type', 'input_attrs', 'required', 'options', 'help_pre', 'help_post', 'serialize', 'data_type'], + 'where' => [['input_type', 'IS NOT NULL']], + ]; - // First scan the entityDefaults directory for our list of supported entities - // FIXME: Need a way to load this from other extensions too - foreach (glob(__DIR__ . '/ang/afGuiEditor/entityDefaults/*.json') as $file) { - $matches = []; - preg_match('/([-a-z_A-Z0-9]*).json/', $file, $matches); - $entityWhitelist[] = $entity = $matches[1]; - // No json_decode, the files are not strict json and will go through angular.$parse clientside - $data['defaults'][$entity] = trim(CRM_Utils_JS::stripComments(file_get_contents($file))); - } + $data = [ + 'entities' => [ + 'Contact' => [ + 'entity' => 'Contact', + 'label' => ts('Contact'), + 'fields' => (array) civicrm_api4('Contact', 'getFields', $getFieldParams, 'name'), + ], + ], + 'blocks' => [], + ]; - // Load main entities - $data['entities'] = (array) Civi\Api4\Entity::get() - ->setCheckPermissions(FALSE) - ->setSelect(['name', 'description']) - ->addWhere('name', 'IN', $entityWhitelist) - ->execute(); + $contactTypes = CRM_Contact_BAO_ContactType::basicTypeInfo(); + + // Scan all extensions for our list of supported entities + foreach (CRM_Extension_System::singleton()->getMapper()->getActiveModuleFiles() as $ext) { + $dir = CRM_Utils_File::addTrailingSlash(dirname($ext['filePath'])) . 'afformEntities'; + if (is_dir($dir)) { + foreach (glob($dir . '/*.php') as $file) { + $entity = include $file; + // Skip disabled contact types + if (!empty($entity['contact_type']) && !isset($contactTypes[$entity['contact_type']])) { + continue; + } + if (!empty($entity['contact_type'])) { + $entity['label'] = $contactTypes[$entity['contact_type']]['label']; + } + // For Contact pseudo-entities (Individual, Organization, Household) + $values = array_intersect_key($entity, ['contact_type' => NULL]); + $afformEntity = $entity['contact_type'] ?? $entity['entity']; + $entity['fields'] = (array) civicrm_api4($entity['entity'], 'getFields', $getFieldParams + ['values' => $values], 'name'); + $data['entities'][$afformEntity] = $entity; + } + } + } // Load blocks - $data['blocks'] = []; $blockData = \Civi\Api4\Afform::get() ->setCheckPermissions(FALSE) ->addWhere('block', 'IS NOT NULL') @@ -191,8 +215,11 @@ function afform_gui_civicrm_buildAsset($asset, $params, &$mimeType, &$content) { ->setLayoutFormat('shallow') ->execute(); foreach ($blockData as $block) { - if (!empty($block['join']) && !in_array($block['join'], $entityWhitelist)) { - $entityWhitelist[] = $block['join']; + if (!empty($block['join']) && !isset($data['entities'][$block['join']]['fields'])) { + $data['entities'][$block['join']]['entity'] = $block['join']; + // Normally you shouldn't pass variables to ts() but very common strings like "Email" should already exist + $data['entities'][$block['join']]['label'] = ts($block['join']); + $data['entities'][$block['join']]['fields'] = (array) civicrm_api4($block['join'], 'getFields', $getFieldParams, 'name'); } $data['blocks'][_afform_angular_module_name($block['name'], 'dash')] = $block; } @@ -239,34 +266,19 @@ function afform_gui_civicrm_buildAsset($asset, $params, &$mimeType, &$content) { ], ]; - $getFieldParams = [ - 'checkPermissions' => FALSE, - 'includeCustom' => TRUE, - 'loadOptions' => TRUE, - 'action' => 'create', - 'select' => ['name', 'title', 'input_type', 'input_attrs', 'required', 'options', 'help_pre', 'help_post', 'serialize', 'data_type'], - 'where' => [['input_type', 'IS NOT NULL']], - ]; - - // Get fields for main entities + joined entities - foreach (array_unique($entityWhitelist) as $entityName) { - $data['fields'][$entityName] = (array) civicrm_api4($entityName, 'getFields', $getFieldParams, 'name'); - - // TODO: Teach the api to return options in this format - foreach ($data['fields'][$entityName] as $name => $field) { + // Reformat options + // TODO: Teach the api to return options in this format + foreach ($data['entities'] as $entityName => $entity) { + foreach ($entity['fields'] as $name => $field) { if (!empty($field['options'])) { - $data['fields'][$entityName][$name]['options'] = CRM_Utils_Array::makeNonAssociative($field['options'], 'key', 'label'); + $data['entities'][$entityName]['fields'][$name]['options'] = CRM_Utils_Array::makeNonAssociative($field['options'], 'key', 'label'); } else { - unset($data['fields'][$entityName][$name]['options']); + unset($data['entities'][$entityName]['fields'][$name]['options']); } } } - // Now adjust the field metadata - // FIXME: This should probably be a callback event or something to allow extensions to tweak the metadata for their entities - $data['fields']['Contact']['contact_type']['required_data'] = TRUE; - // Scan for input types // FIXME: Need a way to load this from other extensions too foreach (glob(__DIR__ . '/ang/afGuiEditor/inputType/*.html') as $file) { diff --git a/ext/afform/gui/ang/afGuiEditor.js b/ext/afform/gui/ang/afGuiEditor.js index 2297fcbafa..39bdca95c2 100644 --- a/ext/afform/gui/ang/afGuiEditor.js +++ b/ext/afform/gui/ang/afGuiEditor.js @@ -51,7 +51,7 @@ else { $timeout(function() { initialize(_.cloneDeep(newForm)); - editor.addEntity('Contact'); + editor.addEntity('Individual'); $scope.layout['#children'].push({ "#tag": "button", "class": 'af-button btn btn-primary', @@ -79,38 +79,42 @@ }, true); } - this.addEntity = function(entityType) { - var existingEntitiesofThisType = _.map(_.filter($scope.entities, {type: entityType}), 'name'), - num = existingEntitiesofThisType.length + 1; + this.addEntity = function(type) { + var meta = editor.meta.entities[type], + num = 1; // Give this new entity a unique name - while (_.contains(existingEntitiesofThisType, entityType + num)) { + while (!!$scope.entities[type + num]) { num++; } - $scope.entities[entityType + num] = _.assign($parse(this.meta.defaults[entityType])($scope), { + $scope.entities[type + num] = _.assign($parse(meta.defaults)($scope), { '#tag': 'af-entity', - type: entityType, - name: entityType + num, - label: entityType + ' ' + num + type: meta.entity, + name: type + num, + label: meta.label + ' ' + num }); // Add this af-entity tag after the last existing one var pos = 1 + _.findLastIndex($scope.layout['#children'], {'#tag': 'af-entity'}); - $scope.layout['#children'].splice(pos, 0, $scope.entities[entityType + num]); + $scope.layout['#children'].splice(pos, 0, $scope.entities[type + num]); // Create a new af-fieldset container for the entity var fieldset = { '#tag': 'fieldset', - 'af-fieldset': entityType + num, + 'af-fieldset': type + num, '#children': [ { '#tag': 'legend', 'class': 'af-text', '#children': [ { - '#text': entityType + ' ' + num + '#text': meta.label + ' ' + num } ] } ] }; + // Add default contact name block + if (meta.entity === 'Contact') { + fieldset['#children'].push({'#tag': 'block-name-' + type.toLowerCase()}); + } // Attempt to place the new af-fieldset after the last one on the form pos = 1 + _.findLastIndex($scope.layout['#children'], 'af-fieldset'); if (pos) { @@ -118,7 +122,7 @@ } else { $scope.layout['#children'].push(fieldset); } - return entityType + num; + return type + num; }; this.removeEntity = function(entityName) { @@ -133,14 +137,14 @@ }; this.getField = function(entityType, fieldName) { - return $scope.meta.fields[entityType][fieldName]; + return $scope.meta.entities[entityType].fields[fieldName]; }; this.getEntity = function(entityName) { return $scope.entities[entityName]; }; - this.getselectedEntityName = function() { + this.getSelectedEntityName = function() { return $scope.selectedEntityName; }; @@ -244,10 +248,15 @@ $scope.elementList = []; $scope.elementTitles = []; + $scope.getMeta = function() { + var type = $scope.entity.type === 'Contact' ? $scope.entity.data.contact_type : $scope.entity.type; + return $scope.editor ? $scope.editor.meta.entities[type] : {}; + }; + $scope.valuesFields = function() { - var fields = $scope.editor ? _.transform($scope.editor.meta.fields[$scope.entity.type], function(fields, field) { + var fields = _.transform($scope.getMeta().fields, function(fields, field) { fields.push({id: field.name, text: field.title, disabled: $scope.fieldInUse(field.name)}); - }, []) : []; + }, []); return {results: fields}; }; @@ -264,7 +273,7 @@ function buildFieldList(search) { $scope.fieldList.length = 0; - _.each($scope.editor.meta.fields[$scope.entity.type], function(field) { + _.each($scope.getMeta().fields, function(field) { if (!search || _.contains(field.name, search) || _.contains(field.title.toLowerCase(), search)) { $scope.fieldList.push({ "#tag": "af-field", @@ -278,7 +287,9 @@ $scope.blockList.length = 0; $scope.blockTitles.length = 0; _.each($scope.editor.meta.blocks, function(block, directive) { - if (!search || _.contains(directive, search) || _.contains(block.name.toLowerCase(), search) || _.contains(block.title.toLowerCase(), search)) { + if ((!search || _.contains(directive, search) || _.contains(block.name.toLowerCase(), search) || _.contains(block.title.toLowerCase(), search)) && + (block.block === '*' || block.block === $scope.entity.type || ($scope.entity.type === 'Contact' && block.block === $scope.entity.data.contact_type)) + ) { var item = {"#tag": block.join ? "div" : directive}; if (block.join) { item['af-join'] = block.join; @@ -418,7 +429,7 @@ }; $scope.isSelectedFieldset = function(entityName) { - return entityName === $scope.editor.getselectedEntityName(); + return entityName === $scope.editor.getSelectedEntityName(); }; $scope.selectEntity = function() { diff --git a/ext/afform/gui/ang/afGuiEditor/entity.html b/ext/afform/gui/ang/afGuiEditor/entity.html index dcc760aa0c..e89dfc220d 100644 --- a/ext/afform/gui/ang/afGuiEditor/entity.html +++ b/ext/afform/gui/ang/afGuiEditor/entity.html @@ -1,10 +1,10 @@
{{ ts('Values:') }} -
-
+
+
- +
@@ -48,7 +48,7 @@
- + diff --git a/ext/afform/gui/ang/afGuiEditor/palette.html b/ext/afform/gui/ang/afGuiEditor/palette.html index 43c7bf4079..e5855b8eb4 100644 --- a/ext/afform/gui/ang/afGuiEditor/palette.html +++ b/ext/afform/gui/ang/afGuiEditor/palette.html @@ -15,8 +15,8 @@ -- 2.25.1