Proof-of-concept - Autogenerate AngularJS modules+directives for each form
authorTim Otten <totten@civicrm.org>
Wed, 13 Jun 2018 02:25:58 +0000 (19:25 -0700)
committerCiviCRM <info@civicrm.org>
Wed, 16 Sep 2020 02:13:16 +0000 (19:13 -0700)
ext/afform/README.md
ext/afform/afform.php
ext/afform/templates/afform/FormAsDirective.tpl [new file with mode: 0644]

index 4e95873a002d09d6271057f5a8df6002334c0c80..8ddd872dcabcad271c7a51cb017e296d1bb71eee 100644 (file)
@@ -91,6 +91,16 @@ $ cv api afform.create name=foobar title="The Foo Bar Screen"
 
 (* FIXME *)
 
+## Usage (Developers): Include a customizable subform in your own page
+
+Suppose you've created an AngularJS UI based on [the developer
+documentation](https://docs.civicrm.org/dev/en/latest/framework/angular/quickstart/).  You'd like to use the
+customizable `foobar` form as part of your UI.  Fortunately, `foobar` is available as an AngularJS module named
+`afformFoobar`.  You can use it with two steps:
+
+1. In your module metadata (`ang/MYMODULE.ang.php`), update the `requires` to include `afformFoobar`.
+2. In your HTML template, use the directive `<div afform-foobar="..."></div>`.
+
 ## Known Issues
 
 (* FIXME *)
index 59b22af5a4ee4e482aabb6f9ab5c9de9f8645fd1..5eaaa6ca13b25ce78901627756664dc5b68cba7c 100644 (file)
@@ -132,6 +132,23 @@ function afform_civicrm_caseTypes(&$caseTypes) {
  */
 function afform_civicrm_angularModules(&$angularModules) {
   _afform_civix_civicrm_angularModules($angularModules);
+
+  $scanner = new CRM_Afform_AfformScanner();
+  $names = array_keys($scanner->findFilePaths());
+  foreach ($names as $name) {
+    $meta = $scanner->getMeta($name);
+    $angularModules[_afform_angular_module_name($name)] = [
+      'ext' => E::LONG_NAME,
+      'js' => ['assetBuilder://afform.js?name=' . urlencode($name)],
+      'requires' => $meta['requires'],
+      'basePages' => [],
+    ];
+
+    // FIXME: The HTML layout template is embedded in the JS asset.
+    // This works at runtime for basic usage, but it bypasses
+    // the hook_alterAngular infrastructure, and I'm not sure translation works.
+    // We should update core so that 'partials' can be specified more dynamically.
+  }
 }
 
 /**
@@ -156,6 +173,40 @@ function afform_civicrm_entityTypes(&$entityTypes) {
 
 // --- Functions below this ship commented out. Uncomment as required. ---
 
+/**
+ * Implements hook_civicrm_buildAsset().
+ */
+function afform_civicrm_buildAsset($asset, $params, &$mimeType, &$content) {
+  if ($asset !== 'afform.js') {
+    return;
+  }
+
+  if (empty($params['name'])) {
+    throw new RuntimeException("Missing required parameter: afform.js?name=NAME");
+  }
+
+  $name = $params['name'];
+  $meta = civicrm_api3('Afform', 'getsingle', ['name' => $name]);
+  $scanner = new CRM_Afform_AfformScanner();
+
+  $smarty = CRM_Core_Smarty::singleton();
+  $smarty->assign('afform', [
+    'camel' => _afform_angular_module_name($name),
+    'meta' => $meta,
+    'layout' => file_get_contents($scanner->findFilePath($name, 'layout.html'))
+  ]);
+  $mimeType = 'text/javascript';
+  $content = $smarty->fetch('afform/FormAsDirective.tpl');
+}
+
+/**
+ * @param $name
+ * @return string
+ */
+function _afform_angular_module_name($name) {
+  return 'afform' . strtoupper($name{0}) . substr($name, 1);
+}
+
 /**
  * Implements hook_civicrm_preProcess().
  *
diff --git a/ext/afform/templates/afform/FormAsDirective.tpl b/ext/afform/templates/afform/FormAsDirective.tpl
new file mode 100644 (file)
index 0000000..75eb2a1
--- /dev/null
@@ -0,0 +1,26 @@
+{* This takes an $afform and generates an AngularJS directive.
+
+ @param string $afform.camel The full camel-case name of the AngularJS module being created
+ @param string $afform.meta  The full metadata record of the form
+ @param string $afform.layout  The template content
+ *}
+{literal}
+(function(angular, $, _) {
+  angular.module('{/literal}{$afform.camel}{literal}', CRM.angRequires('{/literal}{$afform.camel}{literal}'));
+  angular.module('{/literal}{$afform.camel}{literal}').directive('{/literal}{$afform.camel}{literal}', function() {
+    return {
+      restrict: 'AE',
+      template: {/literal}{$afform.layout|json}{literal},
+      scope: {
+        {/literal}{$afform.camel}{literal}: '='
+      },
+      link: function($scope, $el, $attr) {
+        var ts = $scope.ts = CRM.ts('{/literal}{$afform.camel}{literal}');
+        $scope.$watch('{/literal}{$afform.camel}{literal}', function(newValue){
+          $scope.myOptions = newValue;
+        });
+      }
+    };
+  });
+})(angular, CRM.$, CRM._);
+{/literal}
\ No newline at end of file