From: Tim Otten Date: Fri, 16 Jan 2015 11:14:48 +0000 (-0800) Subject: CRM-15832 - crmResource - Add module to load partials+strings in batches. X-Git-Url: https://vcs.fsf.org/?a=commitdiff_plain;h=a2dc0f827ec289b77551752583f02523fbe7f288;p=civicrm-core.git CRM-15832 - crmResource - Add module to load partials+strings in batches. --- diff --git a/CRM/Core/Page/Angular.php b/CRM/Core/Page/Angular.php index 2e950361f2..59c4dc81ce 100644 --- a/CRM/Core/Page/Angular.php +++ b/CRM/Core/Page/Angular.php @@ -62,6 +62,7 @@ class CRM_Core_Page_Angular extends CRM_Core_Page { 'resourceUrls' => CRM_Extension_System::singleton()->getMapper()->getActiveModuleUrls(), 'angular' => array( 'modules' => array_merge(array('ngRoute'), array_keys($modules)), + 'cacheCode' => $this->res->getCacheCode(), ), 'crmAttachment' => array( 'token' => CRM_Core_Page_AJAX_Attachment::createToken(), diff --git a/CRM/Utils/File.php b/CRM/Utils/File.php index d9a115e9a2..f5ae019b08 100644 --- a/CRM/Utils/File.php +++ b/CRM/Utils/File.php @@ -610,9 +610,12 @@ HTACCESS; * base dir. * @param string $pattern * glob pattern, eg "*.txt". + * @param bool $relative + * TRUE if paths should be made relative to $dir * @return array(string) */ - public static function findFiles($dir, $pattern) { + public static function findFiles($dir, $pattern, $relative = FALSE) { + $dir = rtrim($dir, '/'); $todos = array($dir); $result = array(); while (!empty($todos)) { @@ -621,7 +624,7 @@ HTACCESS; if (is_array($matches)) { foreach ($matches as $match) { if (!is_dir($match)) { - $result[] = $match; + $result[] = $relative ? CRM_Utils_File::relativize($match, "$dir/") : $match; } } } diff --git a/Civi/Angular/Manager.php b/Civi/Angular/Manager.php index 852a0e685a..8466b937a1 100644 --- a/Civi/Angular/Manager.php +++ b/Civi/Angular/Manager.php @@ -55,6 +55,11 @@ class Manager { 'css' => array('css/angular-crmAttachment.css'), 'partials' => array('partials/crmAttachment/*.html'), ); + $angularModules['crmResource'] = array( + 'ext' => 'civicrm', + // 'js' => array('js/angular-crmResource/byModule.js'), // One HTTP request per module. + 'js' => array('js/angular-crmResource/all.js'), // One HTTP request for all modules. + ); $angularModules['crmUi'] = array( 'ext' => 'civicrm', 'js' => array('js/angular-crm-ui.js', 'packages/ckeditor/ckeditor.js'), @@ -148,20 +153,25 @@ class Manager { * Angular module name. * @return array * Array(string $extFilePath => string $html) + * @throws \Exception + * Invalid partials configuration. */ public function getPartials($name) { $module = $this->getModule($name); $result = array(); if (isset($module['partials'])) { - foreach ($module['partials'] as $file) { - $filename = $name . '/' . $file; - $result[$filename] = file_get_contents($this->res->getPath($module['ext'], $file)); + foreach ($module['partials'] as $partialDir) { + $partialDir = $this->res->getPath($module['ext']) . '/' . $partialDir; + $files = \CRM_Utils_File::findFiles($partialDir, '*.html', TRUE); + foreach ($files as $file) { + $filename = '~/' . $name . '/' . $file; + $result[$filename] = file_get_contents($partialDir . '/' . $file); + } } } return $result; } - /** * Get list of translated strings for a module. * @@ -206,13 +216,17 @@ class Manager { } } if (isset($module['partials'])) { - foreach ($module['partials'] as $file) { - $strings = $this->res->getStrings()->get( - $module['ext'], - $this->res->getPath($module['ext'], $file), - 'text/html' - ); - $result = array_unique(array_merge($result, $strings)); + foreach ($module['partials'] as $partialDir) { + $partialDir = $this->res->getPath($module['ext']) . '/' . $partialDir; + $files = \CRM_Utils_File::findFiles($partialDir, '*.html'); + foreach ($files as $file) { + $strings = $this->res->getStrings()->get( + $module['ext'], + $file, + 'text/html' + ); + $result = array_unique(array_merge($result, $strings)); + } } } return $result; diff --git a/js/angular-crmResource/all.js b/js/angular-crmResource/all.js new file mode 100644 index 0000000000..28ef6d6fec --- /dev/null +++ b/js/angular-crmResource/all.js @@ -0,0 +1,88 @@ +// crmResource: Given a templateUrl "~/mymodule/myfile.html", load the matching HTML. +// This implementation loads all partials and strings in one batch. +(function(angular, $, _) { + angular.module('crmResource', []); + + 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]) { + angular.forEach(dfrs, function(dfr) { + dfr.resolve({ + status: 200, + headers: function(name) { + var headers = {'Content-type': 'text/html'}; + return name ? headers[name] : headers; + }, + data: templates[url] + }); + }); + } + else { + angular.forEach(dfrs, function(dfr) { + dfr.reject({status: 500}); // FIXME + }); + } + }); + }; + + var moduleUrl = CRM.url('civicrm/ajax/angular-modules', {r: CRM.angular.cacheCode}); + $http.get(moduleUrl) + .success(function httpSuccess(data) { + templates = []; + angular.forEach(data, function(module) { + if (module.partials) { + angular.extend(templates, module.partials); + } + if (module.strings) { + angular.extend(CRM.strings, module.strings); + } + }); + notify(); + }) + .error(function httpError() { + templates = []; + notify(); + }); + + return { + // @return string|Promise + getUrl: function getUrl(url) { + if (templates !== null) { + return templates[url]; + } + else { + var deferred = $q.defer(); + if (!deferreds[url]) { + deferreds[url] = []; + } + deferreds[url].push(deferred); + return deferred.promise; + } + } + }; + }); + + angular.module('crmResource').config(function($provide) { + $provide.decorator('$templateCache', function($delegate, $http, $q, crmResource) { + var origGet = $delegate.get; + var urlPat = /^~\//; + $delegate.get = function(url) { + if (urlPat.test(url)) { + return crmResource.getUrl(url); + } + else { + return origGet.call(this, url); + } + }; + return $delegate; + }); + }); + +})(angular, CRM.$, CRM._); diff --git a/js/angular-crmResource/byModule.js b/js/angular-crmResource/byModule.js new file mode 100644 index 0000000000..0050c195a5 --- /dev/null +++ b/js/angular-crmResource/byModule.js @@ -0,0 +1,137 @@ +// crmResource: Given a templateUrl "~/mymodule/myfile.html", load the matching HTML. +// This implementation loads partials and strings in per-module batches. +// FIXME: handling of CRM.strings not well tested; may be racy +(function(angular, $, _) { + angular.module('crmResource', []); + + angular.module('crmResource').factory('crmResource', function($q, $http) { + var modules = {}; // moduleQueue[module] = 'loading'|Object; + var templates = {}; // templates[url] = HTML; + + function CrmResourceModule(name) { + this.name = name; + this.status = 'new'; // loading|loaded|error + this.data = null; + this.deferreds = []; + } + + angular.extend(CrmResourceModule.prototype, { + createDeferred: function createDeferred() { + var deferred = $q.defer(); + switch (this.status) { + case 'new': + case 'loading': + this.deferreds.push(deferred); + break; + case 'loaded': + deferred.resolve(this.data); + break; + case 'error': + deferred.reject(); + break; + default: + throw 'Unknown status: ' + this.status; + } + return deferred.promise; + }, + load: function load() { + var module = this; + this.status = 'loading'; + var moduleUrl = CRM.url('civicrm/ajax/angular-modules', {modules: module.name, r: CRM.angular.cacheCode}); + $http.get(moduleUrl) + .success(function httpSuccess(data) { + if (data[module.name]) { + module.onSuccess(data[module.name]); + } + else { + module.onError(); + } + }) + .error(function httpError() { + module.onError(); + }); + }, + onSuccess: function onSuccess(data) { + var module = this; + this.data = data; + this.status = 'loaded'; + if (this.data.partials) { + angular.extend(templates, this.data.partials); + } + if (this.data.strings) { + angular.extend(CRM.strings, this.data.strings); + } + angular.forEach(this.deferreds, function(deferred) { + deferred.resolve(module.data); + }); + delete this.deferreds; + }, + onError: function onError() { + this.status = 'error'; + angular.forEach(this.deferreds, function(deferred) { + deferred.reject(); + }); + delete this.deferreds; + } + }); + + return { + // @return Promise + getModule: function getModule(name) { + if (!modules[name]) { + modules[name] = new CrmResourceModule(name); + modules[name].load(); + } + return modules[name].createDeferred(); + }, + // @return string|Promise + getUrl: function getUrl(url) { + if (templates[url]) { + return templates[url]; + } + + var parts = url.split('/'); + var deferred = $q.defer(); + this.getModule(parts[1]).then( + function() { + if (templates[url]) { + deferred.resolve({ + status: 200, + headers: function(name) { + var headers = {'Content-type': 'text/html'}; + return name ? headers[name] : headers; + }, + data: templates[url] + }); + } + else { + deferred.reject({status: 500}); // FIXME + } + }, + function() { + deferred.reject({status: 500}); // FIXME + } + ); + + return deferred.promise; + } + }; + }); + + angular.module('crmResource').config(function($provide) { + $provide.decorator('$templateCache', function($delegate, $http, $q, crmResource) { + var origGet = $delegate.get; + var urlPat = /^~\//; + $delegate.get = function(url) { + if (urlPat.test(url)) { + return crmResource.getUrl(url); + } + else { + return origGet.call(this, url); + } + }; + return $delegate; + }); + }); + +})(angular, CRM.$, CRM._); diff --git a/tests/phpunit/Civi/Angular/ManagerTest.php b/tests/phpunit/Civi/Angular/ManagerTest.php index 28ce0d5619..230cc98e91 100644 --- a/tests/phpunit/Civi/Angular/ManagerTest.php +++ b/tests/phpunit/Civi/Angular/ManagerTest.php @@ -86,8 +86,8 @@ class ManagerTest extends \CiviUnitTestCase { } if (isset($module['partials'])) { $this->assertTrue(is_array($module['partials'])); - foreach ($module['partials'] as $file) { - $this->assertTrue(file_exists($this->res->getPath($module['ext'], $file))); + foreach ((array) $module['partials'] as $basedir) { + $this->assertTrue(is_dir($this->res->getPath($module['ext']) . '/' . $basedir)); $counts['partials']++; } } @@ -103,7 +103,7 @@ class ManagerTest extends \CiviUnitTestCase { */ public function testGetPartials() { $partials = $this->angular->getPartials('crmMailing'); - $this->assertRegExp('/\assertRegExp('/\