Angular - Support popping up an afform or other ang module via ajax modal
authorColeman Watts <coleman@civicrm.org>
Fri, 1 Jul 2022 01:53:54 +0000 (21:53 -0400)
committerColeman Watts <coleman@civicrm.org>
Wed, 6 Jul 2022 21:50:10 +0000 (17:50 -0400)
Fixes the AngularLoader to work with modal dialogs via ajax.
Dedupes the already-loaded modules to only load what's needed.

Civi/Angular/AngularLoader.php
js/angular-crmResource/all.js
js/crm.ajax.js

index a6b5f80a42e8f3c40237a69ba3fc994f755be7cc..ecfb4797fbb52f59e5064113d33dd63d9921d5e1 100644 (file)
@@ -47,6 +47,13 @@ class AngularLoader {
    */
   protected $region;
 
+  /**
+   * @var array
+   * When adding supplimental modules via snippet,
+   * these modules are already loaded.
+   */
+  protected $modulesAlreadyLoaded = [];
+
   /**
    * @var string
    *   Ex: 'civicrm/a'.
@@ -73,6 +80,11 @@ class AngularLoader {
     $this->region = \CRM_Utils_Request::retrieve('snippet', 'String') ? 'ajax-snippet' : 'html-header';
     $this->pageName = \CRM_Utils_System::currentPath();
     $this->modules = [];
+    if ($this->region === 'ajax-snippet' && !empty($_GET['crmAngularModules'])) {
+      $this->modulesAlreadyLoaded = explode(',', $_GET['crmAngularModules']);
+    }
+    // Ensure region exists
+    \CRM_Core_Region::instance($this->region);
   }
 
   /**
@@ -115,7 +127,13 @@ class AngularLoader {
       ]);
     }
 
-    $moduleNames = $this->findActiveModules();
+    $allModules = $this->findActiveModules();
+    $moduleNames = array_values(array_diff($allModules, $this->modulesAlreadyLoaded));
+
+    if (!$moduleNames && $this->modulesAlreadyLoaded) {
+      // No modules to load
+      return $this;
+    }
     if (!$this->isAllModules($moduleNames)) {
       $assetParams = ['modules' => implode(',', $moduleNames)];
     }
@@ -124,7 +142,7 @@ class AngularLoader {
       $assetParams = ['nonce' => md5(implode(',', $moduleNames))];
     }
 
-    $res->addSettingsFactory(function () use (&$moduleNames, $angular, $res, $assetParams) {
+    $res->addSettingsFactory(function () use (&$moduleNames, $angular, $res, $assetParams, $allModules) {
       // Merge static settings with the results of settingsFactory functions
       $settingsByModule = $angular->getResources($moduleNames, 'settings', 'settings');
       foreach ($angular->getResources($moduleNames, 'settingsFactory', 'settingsFactory') as $moduleName => $factory) {
@@ -144,7 +162,7 @@ class AngularLoader {
       return array_merge($settingsByModule, ['permissions' => $permissions], [
         'resourceUrls' => \CRM_Extension_System::singleton()->getMapper()->getActiveModuleUrls(),
         'angular' => [
-          'modules' => $moduleNames,
+          'modules' => $allModules,
           'requires' => $angular->getResources($moduleNames, 'requires', 'requires'),
           'cacheCode' => $res->getCacheCode(),
           'bundleUrl' => \Civi::service('asset_builder')->getUrl('angular-modules.json', $assetParams),
@@ -152,13 +170,17 @@ class AngularLoader {
       ]);
     });
 
-    $res->addScriptFile('civicrm', 'bower_components/angular/angular.min.js', 100, $this->getRegion(), FALSE);
+    if (!$this->modulesAlreadyLoaded) {
+      $res->addScriptFile('civicrm', 'bower_components/angular/angular.min.js', 100, $this->getRegion(), FALSE);
+    }
 
     $headOffset = 0;
     $config = \CRM_Core_Config::singleton();
-    if ($config->debug) {
-      // FIXME: The `resetLocationProviderHashPrefix.js` has to stay in sync with `\Civi\Angular\Page\Modules::buildAngularModules()`.
-      $res->addScriptFile('civicrm', 'ang/resetLocationProviderHashPrefix.js', 101, $this->getRegion(), FALSE);
+    if ($config->debug || $this->modulesAlreadyLoaded) {
+      if (!$this->modulesAlreadyLoaded) {
+        // FIXME: The `resetLocationProviderHashPrefix.js` has to stay in sync with `\Civi\Angular\Page\Modules::buildAngularModules()`.
+        $res->addScriptFile('civicrm', 'ang/resetLocationProviderHashPrefix.js', 101, $this->getRegion(), FALSE);
+      }
       foreach ($moduleNames as $moduleName) {
         foreach ($this->angular->getResources($moduleName, 'css', 'cacheUrl') as $url) {
           $res->addStyleUrl($url, self::DEFAULT_MODULE_WEIGHT + (++$headOffset), $this->getRegion());
@@ -187,8 +209,10 @@ class AngularLoader {
       }
     }
     // Add bundles
-    foreach ($this->angular->getResources($moduleNames, 'bundles', 'bundles') as $bundles) {
-      $res->addBundle($bundles);
+    if (!$this->modulesAlreadyLoaded) {
+      foreach ($this->angular->getResources($moduleNames, 'bundles', 'bundles') as $bundles) {
+        $res->addBundle($bundles);
+      }
     }
 
     return $this;
@@ -366,7 +390,9 @@ class AngularLoader {
   public function onRegionRender($e) {
     if ($e->region->_name === $this->region && ($this->modules || $this->crmApp)) {
       $this->loadAngularResources();
-      $this->res->addScriptFile('civicrm', 'js/crm-angularjs-loader.js', 200, $this->getRegion(), FALSE);
+      if (!$this->modulesAlreadyLoaded) {
+        $this->res->addScriptFile('civicrm', 'js/crm-angularjs-loader.js', 200, $this->getRegion(), FALSE);
+      }
     }
   }
 
index 356a582407b997921f773ae65f5dab5fda9b8574..bad03ba486ad68ea1cf1150b19935cda51c6238a 100644 (file)
@@ -5,14 +5,13 @@
 
   angular.module('crmResource').factory('crmResource', function($q, $http) {
     var deferreds = {}; // null|object; deferreds[url][idx] = Deferred;
-    var templates = null; // null|object; templates[url] = HTML;
 
     var notify = function notify() {
       var oldDfrds = deferreds;
       deferreds = null;
 
       angular.forEach(oldDfrds, function(dfrs, url) {
-        if (templates[url]) {
+        if (CRM.angular.templates[url]) {
           angular.forEach(dfrs, function(dfr) {
             dfr.resolve({
               status: 200,
@@ -20,7 +19,7 @@
                 var headers = {'Content-type': 'text/html'};
                 return name ? headers[name] : headers;
               },
-              data: templates[url]
+              data: CRM.angular.templates[url]
             });
           });
         }
     var moduleUrl = CRM.angular.bundleUrl;
     $http.get(moduleUrl)
       .then(function httpSuccess(response) {
-        templates = [];
-        angular.forEach(response.data, function(module) {
+        CRM.angular.templates = CRM.angular.templates || {};
+        angular.forEach(response.data, function (module) {
           if (module.partials) {
-            angular.extend(templates, module.partials);
+            angular.extend(CRM.angular.templates, module.partials);
           }
           if (module.strings) {
             CRM.addStrings(module.domain, module.strings);
         });
         notify();
       }, function httpError() {
-        templates = [];
         notify();
       });
 
     return {
       // @return string|Promise<string>
       getUrl: function getUrl(url) {
-        if (templates !== null) {
-          return templates[url];
+        if (CRM.angular.templates && CRM.angular.templates[url]) {
+          return CRM.angular.templates[url];
         }
         else {
           var deferred = $q.defer();
index 57ed34f9932a7d2f83a7c1573c10d20052dd7db5..2c892bfceac34248d0a852be3158b475cd0da919 100644 (file)
         } else {
           url = url.replace(/snippet=[^&]*/, 'snippet=' + snippetType);
         }
+        if (snippetType === 'json' && CRM.angular) {
+          url += '&crmAngularModules=' + CRM.angular.modules.join();
+        }
       }
       return url;
     },