From b20ea91388fcd4941b2187dc35ffa4d6a16a8762 Mon Sep 17 00:00:00 2001 From: Tim Otten Date: Wed, 24 May 2017 20:43:07 -0700 Subject: [PATCH] CRM-20600, CRM-20112 - AngularLoader - Add helper for base-pages and injections This commit extracts the `Civi\Angular\Page\Main::registerResources()` and creates a new utility-class, `AngularLoader`. The `AngularLoader` can be used in new Angular base-pages, e.g. ```php class Example extends CRM_Core_Page { public function run() { $loader = new \Civi\Angular\AngularLoader(); $loader->setPageName('civicrm/foo/bar'); $loader->setModules(array('crmApp', '...')); $loader->load(); return parent::run(); } } ``` The `AngularLoader` only loads the resources or assets (JS/CSS/HTML files). To start Angular, you'll need to call `ng-app` or `angular.bootstrap(...)`. One way to do this is to define a page-template: ```html
``` Or you can reuse the existing template: ```php public function getTemplateFileName() { return 'Civi/Angular/Page/Main.tpl'; } ``` Note: This is framed as a utility-class which loads the Angular resource files. It's not a page-class/base-class. This approach allows us to call the utility from other situations -- e.g. inject AngularJS onto an pre-existing page via hook. Doing that may or may not be wise, but the class-hierarchy shouldn't be the issue. --- Civi/Angular/AngularLoader.php | 236 +++++++++++++++++++++++++++++++++ Civi/Angular/Page/Main.php | 80 ++--------- ang/crmApp.js | 10 ++ 3 files changed, 260 insertions(+), 66 deletions(-) create mode 100644 Civi/Angular/AngularLoader.php diff --git a/Civi/Angular/AngularLoader.php b/Civi/Angular/AngularLoader.php new file mode 100644 index 0000000000..5c5bb9a51f --- /dev/null +++ b/Civi/Angular/AngularLoader.php @@ -0,0 +1,236 @@ +` or `angular.bootstrap(...)`. + * + * @code + * $loader = new AngularLoader(); + * $loader->setPageName('civicrm/case/a'); + * $loader->setModules(array('crmApp')); + * $loader->load(); + * @endCode + * + * @link https://docs.angularjs.org/guide/bootstrap + */ +class AngularLoader { + + /** + * The weight to assign to any Angular JS module files. + */ + const DEFAULT_MODULE_WEIGHT = 200; + + /** + * The resource manager. + * + * Do not use publicly. Inject your own copy! + * + * @var \CRM_Core_Resources + */ + protected $res; + + /** + * The Angular module manager. + * + * Do not use publicly. Inject your own copy! + * + * @var \Civi\Angular\Manager + */ + protected $angular; + + /** + * The region of the page into which JavaScript will be loaded. + * + * @var string + */ + protected $region; + + /** + * @var string + * Ex: 'civicrm/a'. + */ + protected $pageName; + + /** + * @var array + * A list of modules to load. + */ + protected $modules; + + /** + * AngularLoader constructor. + */ + public function __construct() { + $this->res = \CRM_Core_Resources::singleton(); + $this->angular = \Civi::service('angular'); + $this->region = \CRM_Utils_Request::retrieve('snippet', 'String') ? 'ajax-snippet' : 'html-header'; + $this->pageName = isset($_GET['q']) ? $_GET['q'] : NULL; + $this->modules = array(); + } + + /** + * Register resources required by Angular. + */ + public function load() { + $angular = $this->getAngular(); + $res = $this->getRes(); + + $moduleNames = $this->findActiveModules(); + if (!$this->isAllModules($moduleNames)) { + $assetParams = array('modules' => implode(',', $moduleNames)); + } + else { + // The module list will be "all modules that the user can see". + $assetParams = array('nonce' => md5(implode(',', $moduleNames))); + } + + $res->addSettingsFactory(function () use (&$moduleNames, $angular, $res, $assetParams) { + // TODO optimization; client-side caching + $result = array_merge($angular->getResources($moduleNames, 'settings', 'settings'), array( + 'resourceUrls' => \CRM_Extension_System::singleton()->getMapper()->getActiveModuleUrls(), + 'angular' => array( + 'modules' => $moduleNames, + 'requires' => $angular->getResources($moduleNames, 'requires', 'requires'), + 'cacheCode' => $res->getCacheCode(), + 'bundleUrl' => \Civi::service('asset_builder')->getUrl('angular-modules.json', $assetParams), + ), + )); + return $result; + }); + + $res->addScriptFile('civicrm', 'bower_components/angular/angular.min.js', 100, $this->getRegion(), FALSE); + $res->addScriptFile('civicrm', 'js/crm.angular.js', 101, $this->getRegion(), FALSE); + + $headOffset = 0; + $config = \CRM_Core_Config::singleton(); + if ($config->debug) { + foreach ($moduleNames as $moduleName) { + foreach ($this->angular->getResources($moduleName, 'css', 'cacheUrl') as $url) { + $res->addStyleUrl($url, self::DEFAULT_MODULE_WEIGHT + (++$headOffset), $this->getRegion()); + } + foreach ($this->angular->getResources($moduleName, 'js', 'cacheUrl') as $url) { + $res->addScriptUrl($url, self::DEFAULT_MODULE_WEIGHT + (++$headOffset), $this->getRegion()); + // addScriptUrl() bypasses the normal string-localization of addScriptFile(), + // but that's OK because all Angular strings (JS+HTML) will load via crmResource. + } + } + } + else { + // Note: addScriptUrl() bypasses the normal string-localization of addScriptFile(), + // but that's OK because all Angular strings (JS+HTML) will load via crmResource. + // $aggScriptUrl = \CRM_Utils_System::url('civicrm/ajax/angular-modules', 'format=js&r=' . $res->getCacheCode(), FALSE, NULL, FALSE); + $aggScriptUrl = \Civi::service('asset_builder')->getUrl('angular-modules.js', $assetParams); + $res->addScriptUrl($aggScriptUrl, 120, $this->getRegion()); + + // FIXME: The following CSS aggregator doesn't currently handle path-adjustments - which can break icons. + //$aggStyleUrl = \CRM_Utils_System::url('civicrm/ajax/angular-modules', 'format=css&r=' . $res->getCacheCode(), FALSE, NULL, FALSE); + //$aggStyleUrl = \Civi::service('asset_builder')->getUrl('angular-modules.css', $assetParams); + //$res->addStyleUrl($aggStyleUrl, 120, $this->getRegion()); + + foreach ($this->angular->getResources($moduleNames, 'css', 'cacheUrl') as $url) { + $res->addStyleUrl($url, self::DEFAULT_MODULE_WEIGHT + (++$headOffset), $this->getRegion()); + } + } + } + + /** + * Get a list of all Angular modules which should be activated on this + * page. + * + * @return array + * List of module names. + * Ex: array('angularFileUpload', 'crmUi', 'crmUtil'). + */ + public function findActiveModules() { + return $this->angular->resolveDependencies(array_merge( + $this->getModules(), + $this->angular->resolveDefaultModules($this->getPageName()) + )); + } + + /** + * @param $moduleNames + * @return int + */ + private function isAllModules($moduleNames) { + $allModuleNames = array_keys($this->angular->getModules()); + return count(array_diff($allModuleNames, $moduleNames)) === 0; + } + + /** + * @return \CRM_Core_Resources + */ + public function getRes() { + return $this->res; + } + + /** + * @param \CRM_Core_Resources $res + */ + public function setRes($res) { + $this->res = $res; + } + + /** + * @return \Civi\Angular\Manager + */ + public function getAngular() { + return $this->angular; + } + + /** + * @param \Civi\Angular\Manager $angular + */ + public function setAngular($angular) { + $this->angular = $angular; + } + + /** + * @return string + */ + public function getRegion() { + return $this->region; + } + + /** + * @param string $region + */ + public function setRegion($region) { + $this->region = $region; + } + + /** + * @return string + * Ex: 'civicrm/a'. + */ + public function getPageName() { + return $this->pageName; + } + + /** + * @param string $pageName + * Ex: 'civicrm/a'. + */ + public function setPageName($pageName) { + $this->pageName = $pageName; + } + + /** + * @return array + */ + public function getModules() { + return $this->modules; + } + + /** + * @param array $modules + */ + public function setModules($modules) { + $this->modules = $modules; + } + +} diff --git a/Civi/Angular/Page/Main.php b/Civi/Angular/Page/Main.php index b09e5bfadb..a13a18c068 100644 --- a/Civi/Angular/Page/Main.php +++ b/Civi/Angular/Page/Main.php @@ -8,6 +8,7 @@ namespace Civi\Angular\Page; * @link https://issues.civicrm.org/jira/browse/CRM-14479 */ class Main extends \CRM_Core_Page { + /** * The weight to assign to any Angular JS module files. */ @@ -19,16 +20,17 @@ class Main extends \CRM_Core_Page { * Do not use publicly. Inject your own copy! * * @var \CRM_Core_Resources + * @deprecated */ public $res; - /** * The Angular module manager. * * Do not use publicly. Inject your own copy! * * @var \Civi\Angular\Manager + * @deprecated */ public $angular; @@ -36,6 +38,7 @@ class Main extends \CRM_Core_Page { * The region of the page into which JavaScript will be loaded. * * @var String + * @deprecated */ public $region; @@ -71,82 +74,27 @@ class Main extends \CRM_Core_Page { * Register resources required by Angular. */ public function registerResources() { - $page = $this; // PHP 5.3 does not propagate $this to inner functions. - - $allModuleNames = array_keys($this->angular->getModules()); - $moduleNames = $this->getModules(); - if (count(array_diff($allModuleNames, $moduleNames))) { - $assetParams = array('modules' => implode(',', $moduleNames)); - } - else { - // The module list will be "all modules that the user can see". - $assetParams = array('nonce' => md5(implode(',', $moduleNames))); - } - - $this->res->addSettingsFactory(function () use (&$moduleNames, $page, $assetParams) { - // TODO optimization; client-side caching - return array_merge($page->angular->getResources($moduleNames, 'settings', 'settings'), array( - 'resourceUrls' => \CRM_Extension_System::singleton()->getMapper()->getActiveModuleUrls(), - 'angular' => array( - 'modules' => array_merge(array('ngRoute'), $moduleNames), - 'requires' => $page->angular->getResources($moduleNames, 'requires', 'requires'), - 'cacheCode' => $page->res->getCacheCode(), - 'bundleUrl' => \Civi::service('asset_builder')->getUrl('angular-modules.json', $assetParams), - ), - )); - }); - - $this->res->addScriptFile('civicrm', 'bower_components/angular/angular.min.js', 100, $this->region, FALSE); - $this->res->addScriptFile('civicrm', 'js/crm.angular.js', 101, $this->region, FALSE); - - $headOffset = 0; - $config = \CRM_Core_Config::singleton(); - if ($config->debug) { - foreach ($moduleNames as $moduleName) { - foreach ($this->angular->getResources($moduleName, 'css', 'cacheUrl') as $url) { - $this->res->addStyleUrl($url, self::DEFAULT_MODULE_WEIGHT + (++$headOffset), $this->region); - } - foreach ($this->angular->getResources($moduleName, 'js', 'cacheUrl') as $url) { - $this->res->addScriptUrl($url, self::DEFAULT_MODULE_WEIGHT + (++$headOffset), $this->region); - // addScriptUrl() bypasses the normal string-localization of addScriptFile(), - // but that's OK because all Angular strings (JS+HTML) will load via crmResource. - } - } - } - else { - // Note: addScriptUrl() bypasses the normal string-localization of addScriptFile(), - // but that's OK because all Angular strings (JS+HTML) will load via crmResource. - // $aggScriptUrl = \CRM_Utils_System::url('civicrm/ajax/angular-modules', 'format=js&r=' . $page->res->getCacheCode(), FALSE, NULL, FALSE); - $aggScriptUrl = \Civi::service('asset_builder')->getUrl('angular-modules.js', $assetParams); - $this->res->addScriptUrl($aggScriptUrl, 120, $this->region); - - // FIXME: The following CSS aggregator doesn't currently handle path-adjustments - which can break icons. - //$aggStyleUrl = \CRM_Utils_System::url('civicrm/ajax/angular-modules', 'format=css&r=' . $page->res->getCacheCode(), FALSE, NULL, FALSE); - //$aggStyleUrl = \Civi::service('asset_builder')->getUrl('angular-modules.css', $assetParams); - //$this->res->addStyleUrl($aggStyleUrl, 120, $this->region); - - foreach ($this->angular->getResources($moduleNames, 'css', 'cacheUrl') as $url) { - $this->res->addStyleUrl($url, self::DEFAULT_MODULE_WEIGHT + (++$headOffset), $this->region); - } - } + $loader = new \Civi\Angular\AngularLoader(); + $loader->setPageName('civicrm/a'); + $loader->setModules(array('crmApp')); + $loader->load(); // If trying to load an Angular page via AJAX, the route must be passed as a // URL parameter, since the server doesn't receive information about // URL fragments (i.e, what comes after the #). \CRM_Core_Resources::singleton()->addSetting(array( + 'crmApp' => array( + 'defaultRoute' => NULL, + ), 'angularRoute' => \CRM_Utils_Request::retrieve('route', 'String'), )); } /** - * Get a list of Angular modules to include on this page. - * - * @return array - * List of module names. - * Ex: array('angularFileUpload', 'crmUi', 'crmUtil'). + * @inheritdoc */ - public function getModules() { - return array_keys($this->angular->getModules()); + public function getTemplateFileName() { + return 'Civi/Angular/Page/Main.tpl'; } } diff --git a/ang/crmApp.js b/ang/crmApp.js index 0726a04504..c7bb81e29b 100644 --- a/ang/crmApp.js +++ b/ang/crmApp.js @@ -5,6 +5,16 @@ var crmApp = angular.module('crmApp', CRM.angular.modules); crmApp.config(['$routeProvider', function($routeProvider) { + + if (CRM.crmApp.defaultRoute) { + $routeProvider.when('/', { + template: '
', + controller: function($location) { + $location.path(CRM.crmApp.defaultRoute); + } + }); + } + $routeProvider.otherwise({ template: ts('Unknown path') }); -- 2.25.1