Skip to content

Instantly share code, notes, and snippets.

@totten
Last active January 9, 2016 05:51
Show Gist options
  • Save totten/acfca15a8df8ec921296 to your computer and use it in GitHub Desktop.
Save totten/acfca15a8df8ec921296 to your computer and use it in GitHub Desktop.
Pseudocode for hook_civicrm_angularMetadata($module, &$metadata)
<?php
// In this example, we modify the CiviMail "recipient options" dialog
// by inserting an extra <H3> and a custom <INPUT> tag.
function example_civicrm_angularMetadata($module, &$m) {
if ($module === 'crmMailing') {
$doc = phpQuery::newDocument($m['partials']['~/crmMailing/EditRecipOptionsDialogCtrl.html']);
$doc->find('[crm-ui-id=editRecipOptionsForm.email_selection_method]')
->before('
<h3>Hello world</h3>
<input my-directive ng-model="model.mailing.subject">
');
$m['partials']['~/crmMailing/EditRecipOptionsDialogCtrl.html'] = (string)$doc;
}
}
<?php
// In this example, the module simply overrides the HTML files in core.
// For example, to override `ang/crmMailing/EditRecipOptionsDialogCtrl.html`, create
// a file `org.example.mymodule/overrides/crmMailing/EditRecipOptionsDialogCtrl.html`.
function example_civicrm_angularMetadata(&$m) {
$overrideDir = __DIR__ . '/overrides';
foreach ($m['partials'] as $k => $v) {
$file = preg_replace('/^~/', $overrideDir);
if (file_exists($file)) {
$m['partials'][$k] = file_get_contents($file);
}
}
}
<?php
// In this example, we modify the CiviMail "recipient options" dialog
// by inserting a new field. This is a new/custom/independent field. It
// uses a hypothetical (unimplemented) Angular directive for API data.
function example_civicrm_angularMetadata($module, &$m) {
if ($module === 'crmMailing') {
$doc = phpQuery::newDocument($m['partials']['~/crmMailing/EditRecipOptionsDialogCtrl.html']);
$doc->find('[crm-ui-id=editRecipOptionsForm.email_selection_method]')
->parent()
->before('
<div crm-ui-field="{title: ts(\'Foo\'), help: hs(\'foo\')}"
crm-api="[\'Foo\', \'getsingle\', {mailing_id: model.mailing.id}]"
crm-api-var="myApiRecord">
<select
ng-model="myApiRecord.result[0]"
ng-change="myApiRecord.save()"
...>
</select>
</div>
');
$m['partials']['~/crmMailing/EditRecipOptionsDialogCtrl.html'] = (string)$doc;
}
}
<?php
// In this example, we modify the CiviMail "recipient options" dialog
// by inserting a new field. This is a new/custom/independent field that has its
// own save logic ("MyCtrl.js").
function example_civicrm_angularMetadata($module, &$m) {
if ($module === 'crmMailing') {
$m['js'][] = Civi::resources()->getUrl('org.example.me', 'MyCtrl.js');
/* which looks a bit like this pseudocode...
angular.module('crmMailing').controller('MyCtrl', function($scope, crmApi){
$scope.myData = crmApi('Foo', 'get', {mailing_id: $scope.mailing.id});
$scope.mySave = function(){
crmApi('Foo', 'create', myData);
}
});
*/
$doc = phpQuery::newDocument($m['partials']['~/crmMailing/EditRecipOptionsDialogCtrl.html']);
$doc->find('[crm-ui-id=editRecipOptionsForm.email_selection_method]')
->parent()
->before('
<div crm-ui-field="{title: ts(\'Foo\'), help: hs(\'foo\')}" ng-controller="MyCtrl">
<select
ng-model="myData"
ng-change="mySave()"
...>
</select>
</div>
');
$m['partials']['~/crmMailing/EditRecipOptionsDialogCtrl.html'] = (string)$doc;
// Hmm, all the above is getting a bit verbose. Might nice to have a file-naming convention
// which can automatically handle some of the glue.
}
}

Design:

  • MVP: Just declare the new hook in civicrm-core and bundle a copy of phpQuery.
  • Nice-to-have: Cache output of hook_civicrm_angularMetadata. (Doing lots of calls to phpQuery could be slow? Though current caching might be enough.)
  • Nice-to-have: Prettier notation for accessing phpQuery.
  • Nice-to-have: Update ang/**.html documents to include more attributes/selectors.
  • Nice-to-have: Extend phpQuery to work with Angular's JS attributes. (crm-ui-field="{name: '...', title:ts('...')}")
  • Nice-to-have: Reporting/workflows for developers working with ang/**.html file -- e.g. browse all partials, preview the content of an HTML partial, get an alert if civicrm-core changes a partial that's modified by your module.

Strengths:

  • Quick and simple to implement MVP.
  • Allows fine-grained amendments or wholesale overrides of Angular HTML content.
  • HTML is the documented standard for AngularJS dev, and it's widely understood. Can use most AngularJS features.

Weaknesses:

  • HTML isn't very pleasant as a programmatic data-model. It would be more pleasant to work with objects/classes or arrays/JSON/YAML. Probably not a big deal for coders working with hooks, but probably a bigger issue for GUI-based configuration of UI.
  • The only way to test is to do high-level testing (e.g. Selenium, Protractor, or some other framework that loads JS files via HTTP).
  • Would be nice if we could steal the JSON GUID trick from Backdrop layouts.

TODO:

  • This only provides an interface to for module-developers. Also need to think about the lifecycle of user-customizations (made through UI).
  • See notes on proposed 3-way merge algorithm for HTML data.
  • The handling of JS controller logic doesn't feel right. We probably need a generic CRUD controller which emits events for load/save/etc.
  • The technique of putting all the code inside the hook-body is a bit overwhelming. Should de-emphasize the hook function. Really need an example where these changes are broken into files with a straight-forward naming convention and better locality.
  • We still have the problem of API permissions. Need to prepare notes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment