From 9bd30577003960d454442b0ee426946d581ff875 Mon Sep 17 00:00:00 2001 From: Coleman Watts Date: Fri, 26 Mar 2021 20:29:15 -0400 Subject: [PATCH 1/3] AngularLoader - Support multiple angularjs apps running on the page This creates a service named 'angularjs.loader' to manage multiple angular apps on the page. Because can only be used once per page, a new tag, has been created. --- CRM/Core/Region.php | 1 + Civi/Angular/AngularLoader.php | 29 +++++++++++++++++++++++++---- Civi/Core/Container.php | 5 +++++ js/crm-angularjs-loader.js | 11 +++++++++++ 4 files changed, 42 insertions(+), 4 deletions(-) create mode 100644 js/crm-angularjs-loader.js diff --git a/CRM/Core/Region.php b/CRM/Core/Region.php index c7eab5c58418..388d8d17f223 100644 --- a/CRM/Core/Region.php +++ b/CRM/Core/Region.php @@ -142,6 +142,7 @@ public function render($default, $allowCmsOverride = TRUE) { } }; + Civi::dispatcher()->dispatch('civi.region.render', \Civi\Core\Event\GenericHookEvent::create(['region' => $this])); foreach ($this->snippets as $snippet) { if (empty($snippet['disabled'])) { $renderSnippet($snippet); diff --git a/Civi/Angular/AngularLoader.php b/Civi/Angular/AngularLoader.php index 60252c83b383..d446fec947f8 100644 --- a/Civi/Angular/AngularLoader.php +++ b/Civi/Angular/AngularLoader.php @@ -78,8 +78,18 @@ public function __construct() { } /** - * Register resources required by Angular. + * Calling this method from outside this class is deprecated. * + * The correct way to use this class is as a service, which will load automatically. E.g.: + * + * ``` + * Civi::service('angularjs.loader') + * ->addModules('moduleFoo') + * ->useApp(); // Optional, if Civi's routing is desired (full-page apps only) + * ``` + * + * @internal + * @deprecated * @return AngularLoader */ public function load() { @@ -88,9 +98,7 @@ public function load() { if ($this->crmApp !== NULL) { $this->addModules($this->crmApp['modules']); - $region = \CRM_Core_Region::instance($this->crmApp['region']); - $region->update('default', ['disabled' => TRUE]); - $region->add(['template' => $this->crmApp['file'], 'weight' => 0]); + $this->res->addSetting([ 'crmApp' => [ 'defaultRoute' => $this->crmApp['defaultRoute'], @@ -216,6 +224,9 @@ public function useApp($settings = []) { 'file' => 'Civi/Angular/Page/Main.tpl', ]; $this->crmApp = array_merge($defaults, $settings); + $region = \CRM_Core_Region::instance($this->crmApp['region']); + $region->update('default', ['disabled' => TRUE]); + $region->add(['template' => $this->crmApp['file'], 'weight' => 0]); return $this; } @@ -335,4 +346,14 @@ public function setModules($modules) { return $this; } + /** + * @param \Civi\Core\Event\GenericHookEvent $e + */ + public function onRegionRender($e) { + if ($e->region->_name === $this->region && ($this->modules || $this->crmApp)) { + $this->load(); + $this->res->addScriptFile('civicrm', 'js/crm-angularjs-loader.js', 200, $this->getRegion(), FALSE); + } + } + } diff --git a/Civi/Core/Container.php b/Civi/Core/Container.php index 7a02a56b4517..39749a81bbda 100644 --- a/Civi/Core/Container.php +++ b/Civi/Core/Container.php @@ -123,6 +123,9 @@ public function createContainer() { )) ->setFactory([new Reference(self::SELF), 'createAngularManager'])->setPublic(TRUE); + $container->setDefinition('angularjs.loader', new Definition('Civi\Angular\AngularLoader', [])) + ->setPublic(TRUE); + $container->setDefinition('dispatcher', new Definition( 'Civi\Core\CiviEventDispatcher', [] @@ -351,6 +354,7 @@ public function createAngularManager() { */ public function createEventDispatcher() { // Continue building on the original dispatcher created during bootstrap. + /** @var CiviEventDispatcher $dispatcher */ $dispatcher = static::getBootService('dispatcher.boot'); $dispatcher->addListener('civi.core.install', ['\Civi\Core\InstallationCanary', 'check']); @@ -370,6 +374,7 @@ public function createEventDispatcher() { $dispatcher->addListener('hook_civicrm_eventDefs', ['\Civi\API\Events', 'hookEventDefs']); $dispatcher->addListener('hook_civicrm_eventDefs', ['\Civi\Core\Event\SystemInstallEvent', 'hookEventDefs']); $dispatcher->addListener('hook_civicrm_buildAsset', ['\Civi\Angular\Page\Modules', 'buildAngularModules']); + $dispatcher->addListenerService('civi.region.render', ['angularjs.loader', 'onRegionRender']); $dispatcher->addListener('hook_civicrm_buildAsset', ['\CRM_Utils_VisualBundle', 'buildAssetJs']); $dispatcher->addListener('hook_civicrm_buildAsset', ['\CRM_Utils_VisualBundle', 'buildAssetCss']); $dispatcher->addListener('hook_civicrm_buildAsset', ['\CRM_Core_Resources', 'renderMenubarStylesheet']); diff --git a/js/crm-angularjs-loader.js b/js/crm-angularjs-loader.js new file mode 100644 index 000000000000..5a2fe57f2198 --- /dev/null +++ b/js/crm-angularjs-loader.js @@ -0,0 +1,11 @@ +// http://civicrm.org/licensing +(function($, _) { + "use strict"; + + $(document).on('crmLoad', function(e) { + $('crm-angular-js', e.target).not('.ng-scope').each(function() { + angular.bootstrap(this, $(this).attr('modules').split()); + }); + }); + +})(CRM.$, CRM._); From b1510da43317f8c5edfa3285f910320db9b167e1 Mon Sep 17 00:00:00 2001 From: Coleman Watts Date: Sat, 27 Mar 2021 12:15:50 -0400 Subject: [PATCH 2/3] AngularLoader - use new service to run angular modules --- CRM/Api4/Page/Api4Explorer.php | 11 ++++------- CRM/Contact/Page/DashBoard.php | 9 +++------ CRM/Export/Form/Map.php | 7 +++---- Civi/Angular/AngularLoader.php | 4 ++++ ang/crmDashboard.ang.php | 2 +- ext/afform/admin/CRM/AfformAdmin/Page/Base.php | 5 ++--- ext/search/CRM/Search/Page/Admin.php | 9 +++------ ext/search/CRM/Search/Page/Search.php | 8 +------- templates/CRM/Export/Form/Map.tpl | 4 ++-- 9 files changed, 23 insertions(+), 36 deletions(-) diff --git a/CRM/Api4/Page/Api4Explorer.php b/CRM/Api4/Page/Api4Explorer.php index d78ef91ddc55..655800bfe596 100644 --- a/CRM/Api4/Page/Api4Explorer.php +++ b/CRM/Api4/Page/Api4Explorer.php @@ -48,13 +48,10 @@ public function run() { ->addScriptFile('civicrm', 'bower_components/google-code-prettify/bin/prettify.min.js') ->addStyleFile('civicrm', 'bower_components/google-code-prettify/bin/prettify.min.css'); - $loader = new Civi\Angular\AngularLoader(); - $loader->setModules(['api4Explorer']); - $loader->setPageName('civicrm/api4'); - $loader->useApp([ - 'defaultRoute' => '/explorer', - ]); - $loader->load(); + Civi::service('angularjs.loader') + ->addModules('api4Explorer') + ->useApp(['defaultRoute' => '/explorer']); + parent::run(); } diff --git a/CRM/Contact/Page/DashBoard.php b/CRM/Contact/Page/DashBoard.php index 55d48127df4e..48ea3ac19cec 100644 --- a/CRM/Contact/Page/DashBoard.php +++ b/CRM/Contact/Page/DashBoard.php @@ -44,24 +44,21 @@ public function run() { } } - $loader = new Civi\Angular\AngularLoader(); + $loader = Civi::service('angularjs.loader'); + $loader->addModules('crmDashboard'); $loader->setPageName('civicrm/dashboard'); // For each dashlet that requires an angular directive, load the angular module which provides that directive - $modules = []; foreach (CRM_Core_BAO_Dashboard::getContactDashlets() as $dashlet) { if (!empty($dashlet['directive'])) { foreach ($loader->getAngular()->getModules() as $name => $module) { if (!empty($module['exports'][$dashlet['directive']])) { - $modules[] = $name; + $loader->addModules($name); continue; } } } } - $loader->setModules($modules); - - $loader->load(); return parent::run(); } diff --git a/CRM/Export/Form/Map.php b/CRM/Export/Form/Map.php index 9e4e5fbaf1f5..4bbc28bbc5bb 100644 --- a/CRM/Export/Form/Map.php +++ b/CRM/Export/Form/Map.php @@ -69,10 +69,9 @@ public function preProcess() { ], ]); - // Bootstrap angular and load exportui app - $loader = new Civi\Angular\AngularLoader(); - $loader->setModules(['exportui']); - $loader->load(); + // Add exportui app + Civi::service('angularjs.loader') + ->addModules('exportui'); } public function buildQuickForm() { diff --git a/Civi/Angular/AngularLoader.php b/Civi/Angular/AngularLoader.php index d446fec947f8..6ddd90ed0909 100644 --- a/Civi/Angular/AngularLoader.php +++ b/Civi/Angular/AngularLoader.php @@ -338,6 +338,10 @@ public function getModules() { } /** + * Replace all previously set modules. + * + * Use with caution, as it can cause conflicts with other extensions who have added modules. + * * @param array $modules * @return AngularLoader */ diff --git a/ang/crmDashboard.ang.php b/ang/crmDashboard.ang.php index 65f312a7e201..ea27d015c4c3 100644 --- a/ang/crmDashboard.ang.php +++ b/ang/crmDashboard.ang.php @@ -10,7 +10,7 @@ 'css' => ['css/dashboard.css'], 'partials' => ['ang/crmDashboard'], 'partialsCallback' => ['CRM_Contact_Page_DashBoard', 'angularPartials'], - 'basePages' => ['civicrm/dashboard'], + 'basePages' => [], 'requires' => ['crmUi', 'crmUtil', 'ui.sortable', 'dialogService', 'api4'], 'settingsFactory' => ['CRM_Contact_Page_DashBoard', 'angularSettings'], 'permissions' => ['administer CiviCRM'], diff --git a/ext/afform/admin/CRM/AfformAdmin/Page/Base.php b/ext/afform/admin/CRM/AfformAdmin/Page/Base.php index b01400badb77..d9395e9a9706 100644 --- a/ext/afform/admin/CRM/AfformAdmin/Page/Base.php +++ b/ext/afform/admin/CRM/AfformAdmin/Page/Base.php @@ -23,10 +23,9 @@ public function run() { CRM_Utils_System::appendBreadCrumb([$breadCrumb]); // Load angular module - $loader = new Civi\Angular\AngularLoader(); - $loader->setPageName('civicrm/admin/afform'); + $loader = Civi::service('angularjs.loader'); $loader->useApp(); - $loader->load(); + parent::run(); } diff --git a/ext/search/CRM/Search/Page/Admin.php b/ext/search/CRM/Search/Page/Admin.php index 0d6cc55fad4d..315a0efc960e 100644 --- a/ext/search/CRM/Search/Page/Admin.php +++ b/ext/search/CRM/Search/Page/Admin.php @@ -24,12 +24,9 @@ public function run() { CRM_Utils_System::appendBreadCrumb([$breadCrumb]); // Load angular module - $loader = new Civi\Angular\AngularLoader(); - $loader->setPageName('civicrm/admin/search'); - $loader->useApp([ - 'defaultRoute' => '/list', - ]); - $loader->load(); + Civi::service('angularjs.loader') + ->useApp(['defaultRoute' => '/list']); + parent::run(); } diff --git a/ext/search/CRM/Search/Page/Search.php b/ext/search/CRM/Search/Page/Search.php index ffd4ce40cde7..d73e1b274f53 100644 --- a/ext/search/CRM/Search/Page/Search.php +++ b/ext/search/CRM/Search/Page/Search.php @@ -18,13 +18,7 @@ class CRM_Search_Page_Search extends CRM_Core_Page { public function run() { - Civi::resources()->addBundle('bootstrap3'); - - // Load angular module - $loader = new Civi\Angular\AngularLoader(); - $loader->setPageName('civicrm/search'); - $loader->useApp(); - $loader->load(); + Civi::service('angularjs.loader')->useApp(); if (CRM_Core_Permission::check('administer CiviCRM')) { CRM_Utils_System::appendBreadCrumb([['title' => E::ts('Search Kit'), 'url' => CRM_Utils_System::url('civicrm/admin/search')]]); diff --git a/templates/CRM/Export/Form/Map.tpl b/templates/CRM/Export/Form/Map.tpl index dda47d32d2e4..24eb7a98167c 100644 --- a/templates/CRM/Export/Form/Map.tpl +++ b/templates/CRM/Export/Form/Map.tpl @@ -22,9 +22,9 @@ {include file="CRM/common/WizardHeader.tpl"}
{include file="CRM/common/formButtons.tpl" location="top"}
-
+
-
+
{include file="CRM/common/formButtons.tpl" location="bottom"}
{$initHideBoxes} From ac27ff2e03b2c6905b97e485e307795beed7f154 Mon Sep 17 00:00:00 2001 From: Tim Otten Date: Sat, 27 Mar 2021 14:34:09 -0700 Subject: [PATCH 3/3] CRM_Core_Region - Apply programmatic filters before sorting --- CRM/Core/Region.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CRM/Core/Region.php b/CRM/Core/Region.php index 388d8d17f223..4d9f733068c6 100644 --- a/CRM/Core/Region.php +++ b/CRM/Core/Region.php @@ -61,6 +61,8 @@ public function render($default, $allowCmsOverride = TRUE) { $this->snippets['default']['markup'] = $default; } + Civi::dispatcher()->dispatch('civi.region.render', \Civi\Core\Event\GenericHookEvent::create(['region' => $this])); + $this->sort(); $cms = CRM_Core_Config::singleton()->userSystem; @@ -142,7 +144,6 @@ public function render($default, $allowCmsOverride = TRUE) { } }; - Civi::dispatcher()->dispatch('civi.region.render', \Civi\Core\Event\GenericHookEvent::create(['region' => $this])); foreach ($this->snippets as $snippet) { if (empty($snippet['disabled'])) { $renderSnippet($snippet);