'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(),
* 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)) {
if (is_array($matches)) {
foreach ($matches as $match) {
if (!is_dir($match)) {
- $result[] = $match;
+ $result[] = $relative ? CRM_Utils_File::relativize($match, "$dir/") : $match;
}
}
}
'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'),
* 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.
*
}
}
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;
--- /dev/null
+// 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<string>
+ 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._);
--- /dev/null
+// 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<ModuleData>
+ getModule: function getModule(name) {
+ if (!modules[name]) {
+ modules[name] = new CrmResourceModule(name);
+ modules[name].load();
+ }
+ return modules[name].createDeferred();
+ },
+ // @return string|Promise<string>
+ 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._);
}
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']++;
}
}
*/
public function testGetPartials() {
$partials = $this->angular->getPartials('crmMailing');
- $this->assertRegExp('/\<form.*name="crmMailing"/', $partials['crmMailing/partials/crmMailing/edit.html']);
+ $this->assertRegExp('/\<form.*name="crmMailing"/', $partials['~/crmMailing/edit.html']);
// If crmMailing changes, feel free to use a different example.
}