need these? Here are few tricks:
* You can create your own applications and pages with full AngularJS. (See also: [CiviCRM Developer Guide: AngularJS: Quick Start](https://docs.civicrm.org/dev/en/latest/framework/angular/quickstart/)).
- Then embed the afform (like `helloworld`) in your page with these steps:
- * Declare a dependency on module (`afformHelloworld`). This is usually done in `ang/MYMODULE.ang.php` and/or `ang/MYMODULE.js`.
- * In your HTML template, use the directive `<div afform-helloworld=""></div>`.
+ Then embed the afform (like `hello-world`) in your page with these steps:
+ * Declare a dependency on module (`helloWorld`). This is usually done in `ang/MYMODULE.ang.php` and/or `ang/MYMODULE.js`.
+ * In your HTML template, use the directive `<div hello-world=""></div>`.
* If you want to provide extra data, services, or actions for the form author -- then pass them along.
* You can write your own directives with full AngularJS (e.g. `civix generate:angular-directive`). These directives become available for use in other afforms.
* If you start out distributing an `afform` and later find it too limiting, then you can change your mind and convert it to static code in full AngularJS.
- As long as you name it consistently (e.g. `afform-helloworld`), downstream consumers can use the static version as a drop-in replacement.
+ As long as you name it consistently (`angular.module('helloWorld').directive('helloWorld')`), downstream consumers can use the static version as a drop-in replacement.
> *(FIXME: But if you do convert to static, could you still permit downstream folks customize the HTML? Let's
> re-assess after we've patched core to allow full participation in the lifecycle of HTML partials.)*
GUI applications to inspect the form using the API:
```
-$ cv api4 afform.get +w name=helloworld
+$ cv api4 afform.get +w name=helloWorld
{
"0": {
- "name": "helloworld",
+ "name": "helloWorld",
"requires": [
"afformCore"
],
Additionally, you can also update the forms:
```
-$ cv api4 afform.update +w name=helloworld +v title="The Foo Bar Screen"
+$ cv api4 afform.update +w name=helloWorld +v title="The Foo Bar Screen"
{
"0": {
- "name": "helloworld",
+ "name": "helloWorld",
"title": "The Foo Bar Screen"
}
}
* The changes made through the API are only applied on this site.
* Once you make a change with the CRUD API, there will be two copies of the form:
- * `[myextension]/afform/helloworld/` is the default, canonical version.
- * `[civicrm.files]/afform/helloworld/` is the local, custom version.
-* The `layout` field is stored as an Angular-style HTML document (`layout.html`), so you can edit it on disk like
+ * `[myextension]/ang/helloWorld.aff.html` is the default, canonical version.
+ * `[civicrm.files]/ang/helloWorld.aff.html` is the local, custom version.
+* The `layout` field is stored as an Angular-style HTML document (`helloWorld.aff.html`), so you can edit it on disk like
normal Angular code. However, when CRUD'ing the `layout` through the API, it is presented in JSON-style.
+
+To undo the change, you can use the `revert` API. This will remove any local overrides so that the canonical content
+(`[myextension]/ang/helloWorld.aff.html`) is activated.
+
+```
+$ cv api4 afform.revert +w name=helloWorld
+```
# Embedding Forms: Afform as reusable building-block
In the [quick-start example](quickstart.md), we registered a new route (`"server_route": "civicrm/hello-world"`) -- this created a
-simple, standalone page with the sole purpose of displaying the `helloworld` form. What if we want to embed the form
+simple, standalone page with the sole purpose of displaying the `helloWorld` form. What if we want to embed the form
somewhere else -- e.g. as a dialog inside an event-listing or membership directory? Afforms are actually *re-usable
sub-forms*.
-How does this work? Every `afform` is an *AngularJS directive*. For example, `helloworld` can be embedded with:
+How does this work? Every `afform` is an *AngularJS directive*. For example, `hello-world` can be embedded with:
```html
-<div afform-helloworld=""></div>
+<div hello-world=""></div>
```
-Moreover, you can pass options to `helloworld`:
+Moreover, you can pass options to `helloWorld`:
```html
-<div afform-helloworld="{phaseOfMoon: 'waxing'}"></div>
+<div hello-world="{phaseOfMoon: 'waxing'}"></div>
```
-Now, in `afform/helloworld/layout.html`, you can use `options.phaseOfMoon`:
+Now, in `ang/helloWorld.aff.html`, you can use `options.phaseOfMoon`:
```html
Hello, {{routeParams.name}}. The moon is currently {{options.phaseOfMoon}}.
First, we should make a few building-blocks:
-1. `afform/contactName/layout.html` displays a sub-form for editing first name, lastname, prefix, suffix, etc.
-2. `afform/contactAddressess/layout.html` displays a sub-form for editing street addresses.
-3. `afform/contactEmails/layout.html` displays a sub-form for editing email addresses.
+1. `ang/afformContactName.aff.html` displays a sub-form for editing first name, lastname, prefix, suffix, etc.
+2. `ang/afformContactAddresses.aff.html` displays a sub-form for editing street addresses.
+3. `ang/afformContactEmails.aff.html` displays a sub-form for editing email addresses.
-Next, we should create an overall `afform/contact/layout.html` which uses these building-blocks:
+Next, we should create an overall `ang/afformContact.aff.html` which uses these building-blocks:
```html
<div ng-form="contactForm">
</div>
```
-And we should create a `afform/contact/meta.json` looking like
+And we should create a `ang/afformContact.aff.json` looking like
```json
{
"requires" : ["afformContactName", "afformContactEmails", "afformContactAddresses"]
}
```
-> *(FIXME: In the parent form's `meta.json`, we need to manually add `afformContactName`, `afformContactAddresses`, `afformContactEmails` to the `requires` list. We should autodetect these instead.)*
+> *(FIXME: In the parent form's `*.aff.json`, we need to manually add `afformContactName`, `afformContactAddresses`, `afformContactEmails` to the `requires` list. We should autodetect these instead.)*
-What does this buy us? It means that a downstream admin (using APIs/GUIs) can fork `afform/contactName/layout.html` --
+We've created new files, so we'll need to flush the file-index
+
+```
+cv flush
+```
+
+and now we can open the page
+
+```
+cv open 'civicrm/contact?cid=100'
+```
+
+What does this buy us? It means that a downstream admin (using APIs/GUIs) can fork `ang/afformContactName.aff.html` --
but all the other components can cleanly track the canonical release. This significantly reduces the costs and risks
of manging upgrades and changes.
# Quick Start: Creating the canonical definition of a basic form
As an extension author, you can define a form along with its default,
-canonical content. Simply create a folder named `afform/<MY-FORM>`. In
-this example, we create a form named `helloworld`:
+canonical content. Simply create a file `ang/MYFORM.aff.html`. In
+this example, we create a form named `helloWorld`:
```
$ cd /path/to/my/own/extension
-$ mkdir -p afform/helloworld
-$ echo '{"server_route": "civicrm/hello-world"}' > afform/helloworld/meta.json
-$ echo '<div>Hello {{routeParams.name}}</div>' > afform/helloworld/layout.html
+$ mkdir ang
+$ echo '<div>Hello {{routeParams.name}}</div>' > ang/helloWorld.aff.html
+$ echo '{"server_route": "civicrm/hello-world"}' > ang/helloWorld.aff.json
$ cv flush
```
A few things to note:
-* We defined a route `civicrm/hello-world`. This is defined in the same routing system used by CiviCRM forms. It also supports properties such as `title` (page title) and `is_public` (defaults to `false`).
-* The file `layout.html` is an AngularJS HTML document. It has access to all the general features of Angular HTML (discussed more later).
+* The `ang` folder is the typical location for AngularJS modules in CiviCRM extensions.
+* We defined a route `civicrm/hello-world`. This appears in the same routing system used by CiviCRM forms. It also supports properties such as `title` (page title) and `is_public` (defaults to `false`).
* After creating a new form or file, we should flush the cache.
* If you're going to actively edit/revise the content of the file, then you should navigate
to **Administer > System Settings > Debugging** and disable asset caching.
+* The extension `*.aff.html` represents an AngularJS HTML document. It has access to all the general features of Angular HTML (discussed more later).
+* In AngularJS, there is a distinction between a "module" (unit-of-code to be shared; usually appears as `camelCase`) and a "directive" (a custom
+ HTML element; may appear as `camelCase` or as `kebab-case` depending on context). Afform supports a [tactical simplification](angular.md) in which one
+ `*.aff.html` corresponds to one eponymous module and one eponymous directive.
Now that we've created a form, we'll want to determine its URL. As with most
CiviCRM forms, the URL depends on the CMS configuration. Here is an example
* We generally need to provide more services for managing/accessing data (e.g. `crm-api3`).
* We need a formal way to enumerate the library of available tags/directives/attributes. This, in turn, will drive the
drag-drop UI and any validation/auditing.
-* Need to implement the `Afform.revert` API to undo local customizations.
* Haven't decided if we should support a `client_route` property (i.e. defining a skeletal controller and route for any form).
On the plus side, make it easier to add items to the `civicrm/a` base-page. On the flipside, we don't currently have
- a strong use-case, and developers can get the same effect with `civix generate:angular-page` and embedding `<div afform-helloworld/>`.
+ a strong use-case, and developers can get the same effect with `civix generate:angular-page` and embedding `<div hello-world/>`.
* Injecting an afform onto an existing Civi page is currently as difficult as injecting any other AngularJS widget --
- which is to say that (a) it's fine a pure-Angular page and (b) it's lousy on a non-Angular page.
+ which is to say that (a) it's fine for a Civi-Angular page and (b) it's lousy on a non-Angular page.
* The data-storage of user-edited forms supports primitive branching and no merging or rebasing. In an ideal world
(OHUPA-4), we'd incorporate a merge or rebase mechanism (and provide the diff/export on web+cli). To reduce unnecessary
merge-conflicts and allow structured UI for bona-fide merge-conflicts, the diff/merge should be based on HTML elements and
];
```
-## Form Metadata: meta.json
+## Form Metadata: *.aff.json
```json
{
}
```
-## Form Layout: layout.html
+## Form Layout: *.aff.html
```html
<afl-form>
However, we still want to preserve the strong symmetries in the filesystem where:
* The symbol in HTML (e.g. `afform-email`) should match the file-name (e.g.
- `afform/Email/layout.html` or `afform-email.html`)
+ `afform/Email/layout.html` or `afform-email.aff.html`)
* The file name in the base-code provided by an extension should match
the file-name in the local override folder.
# Writing Forms: Afform as basic AngularJS templates
-In AngularJS, the primary language for orchestrating a screen is HTML. You can do interesting things in Angular
+In AngularJS, the primary language for composing a screen is HTML. You can do interesting things in Angular
HTML, such as displaying variables and applying directives.
One key concept is *scope* -- the *scope* determines the list of variables which you can access. By default, `afform`
* `routeParams`: This is a reference to the [$routeParams](https://docs.angularjs.org/api/ngRoute/service/$routeParams)
service. In the example, we used `routeParams` to get a reference to a `name` from the URL.
-* `meta`: The stored meta data (`meta.json`) for this form.
+* `meta`: The stored meta data (`*.aff.json`) for this form.
* `ts`: This is a utility function which translates strings, as in `{{ts('Hello world')}}`.
-Additionally, AngularJS allows *directives* -- these are extra HTML attributes which create behavior. For example:
+Additionally, AngularJS allows *directives* -- these are extensions to HTML (custom tags and attributes) which create behavior. For example:
* `ng-if` will conditionally create or destroy elements in the page.
* `ng-repeat` will loop through data.
## Example: Contact record
-Let's say we want `helloworld` to become a basic "View Contact" page. A user
+Let's say we want `civicrm/hello-world` to become a basic "View Contact" page. A user
would request a URL like:
```
http://dmaster.localhost/civicrm/hello-world/#/?cid=123
```
-How do we use the `cid` to get information about the contact? Update `layout.html` to fetch data with
-`Contact.get` API and the [afform-api3](https://github.com/totten/afform/blob/master/ang/afformCore/Api3Ctrl.md) utility:
+How do we use the `cid` to get information about the contact? Update `helloWorld.aff.html` to fetch data with
+`Contact.get` API and call the [afform-api3](https://github.com/totten/afform/blob/master/ang/afformCore/Api3Ctrl.md) utility:
```html
<div ng-if="!routeParams.cid">
</div>
```
-This example is useful pedagogically and may be useful in a crunch -- but in the longer term,
-we should have a richer library of directives so that typical user-managed forms don't drill-down
-at this level of detail.
+This example is useful pedagogically and may be useful in a crunch -- but
+for typical user-managed forms, it would be better to use more high-level
+directives. You can create such directives by [embedding forms](embed.md)
+or creating [conventional AngularJS directives](angular.md).