From 326b74ebc2235bff1c2277cc5e4a68e453c2833d Mon Sep 17 00:00:00 2001 From: Tim Otten Date: Fri, 2 Jun 2017 17:05:50 -0700 Subject: [PATCH 01/17] Add chapters for "Asset Builder" --- docs/framework/asset-builder.md | 137 ++++++++++++++++++++++++++ docs/hooks/hook_civicrm_buildAsset.md | 32 ++++++ 2 files changed, 169 insertions(+) create mode 100644 docs/framework/asset-builder.md create mode 100644 docs/hooks/hook_civicrm_buildAsset.md diff --git a/docs/framework/asset-builder.md b/docs/framework/asset-builder.md new file mode 100644 index 00000000..78ee6b31 --- /dev/null +++ b/docs/framework/asset-builder.md @@ -0,0 +1,137 @@ +# Asset Builder + +The `AssetBuilder` manages semi-dynamic assets, such as generated JS/CSS +files. Semi-dynamic assets are simultaneously: + + * __Dynamic__: They vary depending on the current system configuration. + You cannot lock-in a correct version of the asset because each + installation may need a slightly different version. + * __Static__: Within a given site, the asset is not likely to change. + It could even be served directly by the web-server (without the overhead + of PHP/CMS/Civi bootstrap) + +!!! note "Example: Batch loading Angular HTML" + When visiting the AngularJS base page `civicrm/a`, one needs to load a + mix of many small HTML files. It's ideal to aggregate them into one + bigger file and reduce the number of round-trip HTTP requests. + + This asset is _dynamic_ because extensions can add or modify HTML files. + Two different sites would have different HTML files (depending on the + mix of extensions). Yet, within a particular site/configuration, the + content is _static_ because the HTML doesn't actually change at runtime. + +The `AssetBuilder` addresses this use-case with *lazy* building. Assets are not +distributed as part of `git` or `tar.gz`. Rather, the first time you try to +use an asset, it fires off a hook to build the asset. The content is stored +in a cache file. + +!!! tip "Tip: Caching and development" + If you are developing or patching assets, then the caching behavior may + get distracting. To bypass the cache, enable **debug mode**. + +## Usage: Simple asset + +There are generally two aspects to using `AssetBuilder` -- creating a URL +for the asset, and defining the content of the asset. + +For example, suppose we wanted to define a static file named +`api-fields.json` which lists all the fields of all the API entities. + +```php +// Get the URL to `api-fields.json`. +$url = \Civi::service('asset_builder')->getUrl('api-fields.json'); + +// Define the content of `api-fields.json` using `hook_civicrm_buildAsset`. +function mymodule_civicrm_buildAsset($asset, $params, &$mimeType, &$content) { + if ($asset !== 'api-fields.json') return; + + $entities = civicrm_api3('Entity', 'get', array()); + $fields = array(); + foreach ($entities['values'] as $entity) { + $fields[$entity] = civicrm_api3($entity, 'getfields'); + } + + $mimeType = 'application/json'; + $content = json_encode($fields); +} +``` + +!!! note "What does `getUrl(...)` do?" + + In normal/production mode, `getUrl(...)` checks to see if the asset + already exists. If necessary, it fires the hook and saves the asset to + disk. Finally, it returns the direct URL for the asset -- which allows + the user to fetch it quickly (without extra PHP/CMS/Civi bootstrapping). + + In debug mode, `getUrl(...)` returns the URL of a PHP script. The PHP + script will build the asset every time it's requested. + +## Usage: Parameterized asset + +What should you do if you need to create a series of similar assets, based on slightly +different permutations or configurations? Add parameters (aka `$params`). + +For example, we might want a copy of `api-fields.json` which only includes a +handful of chosen entities. Simply pass the chosen entities into +`getUrl()`, then update the definition to use `$params['entities']`, as in: + +```php +// Get the URL to `api-fields.json`. This variant only includes +// a few contact-related entities. +$contactEntitiesUrl = \Civi::service('asset_builder') + ->getUrl('api-fields.json', array( + 'entities' => array('Contact', 'Phone', 'Email', 'Address'), + ) +); + +// Get the URL to `api-fields.json`. This variant only includes +// a few case-related entities. +$caseEntitiesUrl = \Civi::service('asset_builder') + ->getUrl('api-fields.json', array( + 'entities' => array('Case', 'Activity', 'Relationship'), + ) +); + +// Define the content of `api-fields.json` using `hook_civicrm_buildAsset`. +function mymodule_civicrm_buildAsset($asset, $params, &$mimeType, &$content) { + if ($asset !== 'api-fields.json') return; + + $fields = array(); + foreach ($params['entities'] as $entity) { + $fields[$entity] = civicrm_api3($entity, 'getfields'); + } + + $mimeType = 'application/json'; + $content = json_encode($fields); +} +``` + +!!! note "Note: Parmaters and caching" + Each combination of ($asset,$params) will be cached separately. + +!!! tip "Tip: Economize parameter size" + In debug mode, all parameters are passed as part of the URL. `AssetBuilder` + will try to compress them, but fundamentally: long `$params` will produce + long URLs. + +## Other considerations + +!!! note "Compare: How does AssetBuilder differ from [Assetic](https://github.com/kriswallsmith/assetic)?" + Both are written in PHP, but they address differet parts of the process: + + * `AssetBuilder` provides URL-routing, caching, and parameterization. + Its strength is defining the *lifecycle* of a dynamic asset. + * `Assetic` provides a library of generators and filters. Its strength + is defining the *content* of an asset. + + You could use them together -- e.g. in `hook_civicrm_buildAsset`, + declare a new asset and use `Assetic` to build its content. + +!!! caution "Caution: Assimilate non-confidential data" + The current implementation does not take aggressive measures to keep + assets confidential. For example, an asset built from public JS files + is fine, but an asset built from permissioned data (contact-records + or activity-records) could be problematic. + + It may be possible to fix this by computing URL digests differently, but + (at time of writing) we don't have a need/use-case. diff --git a/docs/hooks/hook_civicrm_buildAsset.md b/docs/hooks/hook_civicrm_buildAsset.md new file mode 100644 index 00000000..18c5ffa0 --- /dev/null +++ b/docs/hooks/hook_civicrm_buildAsset.md @@ -0,0 +1,32 @@ +# hook_civicrm_buildAsset + +## Description + +This hook fires whenever the system builds a semi-dynamic asset. For more +discussion, see [AssetBuilder](/framework/asset-builder.md). + +## Definition + + hook_civicrm_buildAsset($asset, $params, &$mimeType, &$content) + +## Parameters + + * `$asset` (string): the logical file name of an asset (ex: `hello-world.json`) + * `$params` (array): an optional set of parameters describing how to build the asset + * `$mimeType` (string, output): the MIME type of the asset (ex: `application/json`) + * `$content` (string, output): the full content of the asset + +## Returns + + * null + +## Example + +```php +function mymodule_civicrm_buildAsset($asset, $params, &$mimeType, &$content) { + if ($asset === 'hello-world.json') { + $mimeType = 'application/json'; + $content = json_encode(array('hello', 'world')); + } +} +``` \ No newline at end of file From 8af94e00d95fdf511dfe1cb3355f2a806d18578d Mon Sep 17 00:00:00 2001 From: Tim Otten Date: Fri, 2 Jun 2017 17:06:09 -0700 Subject: [PATCH 02/17] Add chapters for AngularJS --- docs/framework/angular.md | 99 ++++++++++++++++++++++ docs/framework/angular/files.md | 70 ++++++++++++++++ docs/framework/angular/loader.md | 94 +++++++++++++++++++++ docs/framework/angular/modify.md | 14 ++++ docs/framework/angular/quickstart.md | 120 +++++++++++++++++++++++++++ 5 files changed, 397 insertions(+) create mode 100644 docs/framework/angular.md create mode 100644 docs/framework/angular/files.md create mode 100644 docs/framework/angular/loader.md create mode 100644 docs/framework/angular/modify.md create mode 100644 docs/framework/angular/quickstart.md diff --git a/docs/framework/angular.md b/docs/framework/angular.md new file mode 100644 index 00000000..2598672a --- /dev/null +++ b/docs/framework/angular.md @@ -0,0 +1,99 @@ +# AngularJS: Overview + +AngularJS is a client-side framework for development of rich web +applications. The core CiviCRM application uses AngularJS for several +administrative screens, and extensions increasingly use AngularJS for +"leaps" that add or replace major parts of the application. + +This documentation aims to explain how AngularJS works within a CiviCRM +context. + +## Two cultures + +CiviCRM is an extensible PHP application (similar to Drupal, Joomla, or +WordPress). In this culture, the common expectation is that an +*administrator* installs the main application. To customize it, they +download, evaluate, and configure a set of business-oriented modules. The +administrator's workflow is dominated by web-based config screens and CLI +commands. + +AngularJS is a frontend, Javascript development framework. In this culture, +the expectation is that a *developer* creates a new application. To +customize it, they download, evaluate, and configure a set of +function-oriented libraries. The developer's workflow is dominated by CLI's +and code. + +The CiviCRM-AngularJS integration must balance the expectations of these +two cultures. The balance works as follows: + + * __JS+HTML__: The general structure of the Javascript and HTML files + should meet the frontend developers' expectations. These files should be + grounded in the same notations and concepts as the upstream AngularJS + framework. (This means that AngularJS is not abstracted, wrapped, or + mapped by an intermediary like HTML_QuickForm, Symfony Forms or Drupal + Form API.) + * __Build__: The process of building or activating modules should meet + administrators' expectations. They should be managed by the PHP + application. (This means that you won't see `gulp` or `grunt` managing + the final build -- because PHP logic fills that role.) + * __Web Services__: The general structure of web-services should meet + the backend developers' expectations. These are implemented in PHP + (typically with CiviCRM APIv3). + +## Basics + +AngularJS is a client-side Javascript framework, and it interacts with +CiviCRM in two major ways. To see this, let's consider an example AngularJS +page -- it's an HTML document that looks a lot like this: + +```html + + 1: + 2: + 3: + 4: + 5: + 6: + 7:
...site wide header...
+ 8:
+ 9:
...site wide footer...
+10: +11: +``` + +The first interaction comes when CiviCRM generates the initial HTML page: + + * CiviCRM listens for requests to the path `civicrm/a`. (It does this in a + way which is compatible with multiple CMSs -- Drupal, Joomla, WordPress, etc.) + * CiviCRM builds the list of JS/CSS files in lines 3-4. (It does this in a + way which allows extensions to add new JS/CSS files.) + * CiviCRM ensures that the page includes the site-wide elements, such as + lines 7 and 9. (It does this in a way which is compatible with multiple CMSs.) + +Once the page is loaded, it works just like any AngularJS 1.x application. +It uses concepts like `ng-app`, "module", "directive", "service", "component", and +"partial". + +!!! seealso "Read more about AngularJS 1.x" + A good resource for understanding AngularJS concepts is [the + official AngularJS tutorial](https://code.angularjs.org/1.5.11/docs/tutorial). + +The second interaction comes when the AngularJS application loads or stores +data. This uses the CiviCRM API. Key concepts in CiviCRM API include +"entity", "action", "params", the "API Explorer", and the bindings for PHP/Javascript/CLI. + +!!! seealso "Read more about CiviCRM API" + A good resource for understanding CiviCRM API concepts is the [APIv3: + Intro](/api/general.md). + +In the remainder of this document, we'll try to avoid in-depth discussion +about the internals of AngularJS 1.x or APIv3. You should be able to follow +the discussion if you have a beginner-level understanding of both. + + +## Additional pages + + * [Quick Start](angular/quickstart.md): How to create a new screen in the CiviCRM-Angular UI + * [File Names](angular/files.md): How AngularJS files are named in `civicrm-core` and `civix` + * [Loader](angular/loader.md): How JS/CSS files are loaded + * [Modifications](angular/modify.md): How a third-party can modify the content of a CiviCRM-Angular UI diff --git a/docs/framework/angular/files.md b/docs/framework/angular/files.md new file mode 100644 index 00000000..0fc28b31 --- /dev/null +++ b/docs/framework/angular/files.md @@ -0,0 +1,70 @@ +# AngularJS: File names + +As a developer working with CiviCRM-Angular, you write *Angular modules* -- +these modules are composed of various JS/CSS/HTML files which define the +*services*, *directives*, *controllers*, and *HTML partials*. + +For sake of predictability, these files follow a naming convention. All +major Angular files are placed in the `ang/` folder. + +!!! note "How does this work with `civix`?" + When you generate Angular code via `civix`, the files are + named according to convention. + + One of the files, `ang/{mymodule}.ang.php` provides instructions for the + file-loader. + +!!! note "What if my code doesn't follow the naming convention? What if I don't use `civix`?" + The file-loader needs some information about the name and location of + your AngularJS code, but you don't need to follow the convention. You + can configure it via hook. See: [AngularJS: Loading](/framework/angular/loader.md). + +## Abridged convention + +Some Angular modules have a very narrow purpose -- such as defining a +singular `service` or `directive`. These modules only have 2 or 3 files. + + * `ang/{mymodule}.ang.php` - General metadata about the module (per [hook_civicrm_angularModules](/hooks/hook_civicrm_angularModules.md)). + * `ang/{mymodule}.js` - All Javascript for the module + * `ang/{mymodule}.css` - All CSS for the module (if applicable) + +## Full convention + +Some Angular modules have broader purpose -- such as defining a new screen +with a series of related `directive`s, `controller`s, and `service`s. Each +of these elements may have multiple aspects (JS/HTML/CSS). + +__Module Files__ + + * `ang/{mymodule}.ang.php` - General metadata about the module (per [hook_civicrm_angularModules](/hooks/hook_civicrm_angularModules.md)). + * `ang/{mymodule}.js` - General metadata about the module. + * `ang/{mymodule}.css` - General CSS that applies throughout the module. + +__Directive Files__ + + * `ang/{mymodule}/{FooBar}.js` - The declaration and logic for a directive named `mymoduleFooBar` or `
`. + * `ang/{mymodule}/{FooBar}.html` - The main/default template for the directive (if applicable). + * `ang/{mymodule}/{FooBar}/{Extra}.html` - If you have multiple templates used by the same directive (e.g. via `ng-include` or conditional logic), then put them in a subdir. + * `ang/{mymodule}/{FooBar}.css` - Any CSS specifically intended for `mymoduleFooBar` (if applicable). + +__Controller Files__ (These follow the same convention as directives, but they have the suffix `Ctrl`.) + + * `ang/{mymodule}/{FooBar}Ctrl.js` - The declaration and logic for a controller named `MymoduleFooBarCtrl`. + * `ang/{mymodule}/{FooBar}Ctrl.html` - The main/default template for the controller (if applicable). + * `ang/{mymodule}/{FooBar}Ctrl/{Extra}.html` - If you have multiple templates used with the same controller (e.g. via `ng-include` or conditional logic), then put them in a subdir. + * `ang/{mymodule}/{FooBar}Ctrl.css` - Any CSS specifically intended for `MymoduleFooBarCtrl` (if applicable). + +__Service Files__ + + * `ang/{mymodule}/{FooBar}.js` - The declaration and logic for a service named `mymoduleFooBar`. + +!!! tip "Use tilde (`~`) to load HTML templates" + When writing code for Angular, you might use an expression like + `{templateUrl: 'https://example.org/FooBar.html'}`. However, + constructing a full URL that works in every Civi deployment would be + complex. Instead, use the tilde prefix. For example, `{templateUrl: '~/mymodule/FooBar.html'}`. + + diff --git a/docs/framework/angular/loader.md b/docs/framework/angular/loader.md new file mode 100644 index 00000000..05bf3415 --- /dev/null +++ b/docs/framework/angular/loader.md @@ -0,0 +1,94 @@ +# AngularJS: Loader + +What happens when a user visits a CiviCR-Angular page, such as +`https://example.org/civicrm/a/#/mailing/new`? Broadly speaking, two steps: + + 1. (Server-side) CiviCRM processes the request for `civicrm/a`. It + displays a web-page with all your Angular modules. + 2. (Client-side) AngularJS processes the request for `mailing/new`. + It uses an HTML template to setup the UI. + +The client-side behavior is well-defined by Angular +[ngRoute](https://docs.angularjs.org/api/ngRoute). We'll explore the +server-side in greater depth because that is unique to the CiviCRM-Angular +integration. + +## The default base page (`civicrm/a`) + +CiviCRM includes a default base-page -- any module can add new routes on +this page. For example, the `crmMailing` module defines a route +`mailing/new`. You can view this at: + + * `https://example.org/civicrm/a/#/mailing/new` + +The default base-page is special because *all registered Angular modules +will be included by default*. You can expect the markup to look roughly +like this: + +```html + + + + + + + + +... + +... + + +
+ + +``` + +The PHP application instantiates `AngularLoader` + +```php +$loader = new \Civi\Angular\AngularLoader(); +$loader->setPageName('civicrm/a'); +$loader->setModules(array('crmApp')); +$loader->load(); +``` + +The `load()` function determines the necessary JS/CSS/HTML/JSON resources +and loads them on the page. Roughly speaking, it outputs: + + + +More specifically, the `load()` function gets a list of all available +Angular modules (including their JS/CSS/HTTML files). Then it loads the +files for `crmApp` as well as any dependencies (like `crmUi`). + +The most important thing to understand is how it *gets the list of Angular +modules*. A few Angular modules are bundled with core (eg `crmUi` or +`crmUtil`), but most new Angular modules should be loaded via +[hook_civicrm_angularModules](/hooks/hook_civicrm_angularModules.md) + +For example, if you created an extension `org.example.foobar` with an +Angular module named `myBigAngularModule`, then the hook might look like: + +```php +/** + * Implements hook_civicrm_angularModules. + */ +function foobar_civicrm_angularModules(&$angularModules) { + $angularModules['myBigAngularModule'] = array( + 'ext' => 'org.example.foobar', + 'basePages' => array('civicrm/a'), + 'requires' => array('crmUi', 'crmUtils', 'ngRoute'), + 'js' => array('ang/myBigangularModule/*.js'), + 'css' => array('ang/myBigangularModule/*.css'), + 'partials' => array('ang/myBigangularModule'), + ); +} +``` + +!!! tip "`civix` code-generator" + In practice, you usually don't need to implement. The `civix` + code-generator creates a file named `ang/{mymodule}.ang.php` and + automatically loads as part of `hook_civicrm_angularModules`. + + diff --git a/docs/framework/angular/modify.md b/docs/framework/angular/modify.md new file mode 100644 index 00000000..cc0ca5f4 --- /dev/null +++ b/docs/framework/angular/modify.md @@ -0,0 +1,14 @@ +# AngularJS: Alteration + +```php +function example_civicrm_alterAngular($angular) { + $angular->add(\Civi\Angular\ChangeSet::create('mychanges') + ->alterHtml('~/crmMailing/EditMailingCtrl/2step.html', function(phpQueryObject $doc) { + $doc->find('[ng-form="crmMailingSubform"]')->attr('cat-stevens', 'ts(\'wild world\')'); + }) + ); +} +``` + + +cv ang:html:show \ No newline at end of file diff --git a/docs/framework/angular/quickstart.md b/docs/framework/angular/quickstart.md new file mode 100644 index 00000000..7c1fde4b --- /dev/null +++ b/docs/framework/angular/quickstart.md @@ -0,0 +1,120 @@ +# AngularJS: Quick Start + +Let's create a new CiviCRM extension with an AngularJS page. It will present +a small "About Me" screen. + +## Create a CiviCRM extension + +First, we'll need a skeletal CiviCRM extension: + +``` +$ civix generate:module org.example.aboutme +Initalize module org.example.aboutme +Write org.example.aboutme/info.xml +Write org.example.aboutme/aboutme.php +Write org.example.aboutme/aboutme.civix.php +Write org.example.aboutme/LICENSE.txt +``` + +## Create an Angular module + +Of course, AngularJS also has its own module system -- any Angular routes, +directives, or services need to live within an Angular `module`. Let's +create one. + +``` +$ cd org.example.aboutme +$ civix generate:angular-module +Initialize Angular module "aboutme" +Write ang/aboutme.ang.php +Write ang/aboutme.js +Write ang/aboutme.css +``` + +!!! tip "Tip: Angular module names" + By default, `civix` assumes that your Angular module name matches your + extension name. In this case, both are named `aboutme`. However, this + is not required -- the option `--am` can specify a different name. This + can be useful if you want to organize your code into multiple modules. + + +The first file, `ang/aboutme.ang.php`, configures the file-loader: + +```php +return array( + 'requires' => array('crmUi', 'crmUtil', 'ngRoute'), + 'js' => array( + 'ang/aboutme.js', + 'ang/aboutme/*.js', + 'ang/aboutme/*/*.js', + ), + 'css' => array ('ang/aboutme.css'), + 'partials' => array('ang/aboutme'), + 'settings' => array(), +); +``` + +And the second file, `ang/aboutme.js`, declares the Angular module's dependencies. + +```js + angular.module('aboutme', [ + 'crmUi', 'crmUtil', 'ngRoute' + ]); +``` + +!!! tip "Tip: angular.module() and angular.crmDepends()" + The list of dependencies is declared once in PHP and once in JS. To + remove this duplication, change `angular.module('aboutme',...)` to + `angular.crmDepends('aboutme');` + +!!! note "Note: `ang/` folder" + By convention, AngularJS source code is stored in the `ang/` folder. + The convention is discussed in more detail in [AngularJS: File + Names](/framework/angular/files.md) + +## Add an Angular route, etal + +Now let's add a new Angular-based page. This page will require a `route` +with a `controller` and an HTML template. The command `civix +generate:angular-page` will create each of these: + +``` +$ civix generate:angular-page EditCtrl about/me +Initialize Angular page "AboutmeEditCtrl" (civicrm/a/#/about/me) +Write ang/aboutme/EditCtrl.js +Write ang/aboutme/EditCtrl.html +``` + +If you inspect the code, you'll find a basic AngularJS app which uses +`$routeProvider`, `angular.module(...).controller(...)`, and so on. + +The generated code will display a small "About Me" screen with the current +user's first-name and last-name. + +!!! tip "Tip: Flush caches _or_ enable debug mode" + By default, CiviCRM aggregates AngularJS files and caches them. You can + flush this cache manually (`cv flush`). However, it may be easier to + disable some of the aggregation/caching features by [enabling debug + mode](/dev-tools/debugging.md). + +## Open the page + +Finally, we'd like to take a look at this page in the web-browser. + +By default, CiviCRM combines all Angular modules into one page, `civicrm/a`. +The URL of this page depends on your system configuration. Here are a few +examples: + +``` +# Example: Lookup the URL on Drupal 7 +$ cv url 'civicrm/a/#/about/me' +"http://dmaster.l/civicrm/a/#/about/me" + +# Example: Lookup the URL on WordPress +$ cv url 'civicrm/a/#/about/me' +"http://wpmaster.l/wp-admin/admin.php?page=CiviCRM&q=civicrm/a/#/about/me" +``` + +!!! tip "Tip: Open the browser from the command-line" + If you're developing locally on a Linux/OSX workstation, pass the + option `--open` to automatically open the page in a web browser. From 63465e080c1d9d019908cbff8b59b36f46ab6ed3 Mon Sep 17 00:00:00 2001 From: Tim Otten Date: Fri, 2 Jun 2017 17:06:23 -0700 Subject: [PATCH 03/17] mkdocs.yml - Add "Asset Builder" and "AngularJS" to TOC --- mkdocs.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/mkdocs.yml b/mkdocs.yml index d378847c..3f7d2803 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -43,6 +43,13 @@ pages: - Troubleshooting: extensions/troubleshooting.md - Advanced patterns: extensions/advanced.md - Framework Reference: + - AngularJS: + - Overview: framework/angular.md + - Quick Start: framework/angular/quickstart.md + - File Names: framework/angular/files.md + - Loader: framework/angular/loader.md + - Modifications: framework/angular/modify.md + - Asset Builder: framework/asset-builder.md # Bootstrap: /framework/bootstrap.md # Cache: /framework/cache.md # Components: /framework/components.md @@ -168,6 +175,7 @@ pages: - hook_civicrm_alterSettingsMetaData: hooks/hook_civicrm_alterSettingsMetaData.md - hook_civicrm_angularModules: hooks/hook_civicrm_angularModules.md - hook_civicrm_apiWrappers: hooks/hook_civicrm_apiWrappers.md + - hook_civicrm_buildAsset: hooks/hook_civicrm_buildAsset.md - hook_civicrm_buildStateProvinceForCountry: hooks/hook_civicrm_buildStateProvinceForCountry.md - hook_civicrm_check: hooks/hook_civicrm_check.md - hook_civicrm_config: hooks/hook_civicrm_config.md From 9720649e3a75af0c83b919073e10c8e68d4fb3d6 Mon Sep 17 00:00:00 2001 From: Tim Otten Date: Mon, 5 Jun 2017 19:38:45 -0700 Subject: [PATCH 04/17] "AngularJS: Quick Start" -- Various tweaks --- docs/framework/angular/quickstart.md | 34 +++++++++++++--------------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/docs/framework/angular/quickstart.md b/docs/framework/angular/quickstart.md index 7c1fde4b..2a1b20ba 100644 --- a/docs/framework/angular/quickstart.md +++ b/docs/framework/angular/quickstart.md @@ -37,42 +37,40 @@ Write ang/aboutme.css is not required -- the option `--am` can specify a different name. This can be useful if you want to organize your code into multiple modules. +!!! note "Note: `ang/` folder" + By convention, AngularJS source code is stored in the `ang/` folder, and + each item is named after its module. The convention is discussed in + more detail in [AngularJS: File Names](/framework/angular/files.md) -The first file, `ang/aboutme.ang.php`, configures the file-loader: +The first file, `ang/aboutme.ang.php`, provides metadata for the PHP-based +file-loader, e.g. ```php return array( - 'requires' => array('crmUi', 'crmUtil', 'ngRoute'), - 'js' => array( - 'ang/aboutme.js', - 'ang/aboutme/*.js', - 'ang/aboutme/*/*.js', - ), - 'css' => array ('ang/aboutme.css'), + 'requires' => array('ngRoute', 'crmUi', 'crmUtil'), + 'js' => array('ang/aboutme.js', 'ang/aboutme/*.js', 'ang/aboutme/*/*.js'), + 'css' => array('ang/aboutme.css'), 'partials' => array('ang/aboutme'), 'settings' => array(), ); ``` -And the second file, `ang/aboutme.js`, declares the Angular module's dependencies. +The second file, `ang/aboutme.js`, provides metadata for the JS-based +Angular runtime, e.g. ```js angular.module('aboutme', [ - 'crmUi', 'crmUtil', 'ngRoute' + 'ngRoute', 'crmUi', 'crmUtil' ]); ``` -!!! tip "Tip: angular.module() and angular.crmDepends()" +!!! tip "Tip: angular.module() and CRM.angRequires()" The list of dependencies is declared once in PHP and once in JS. To - remove this duplication, change `angular.module('aboutme',...)` to - `angular.crmDepends('aboutme');` + remove this duplication, replace the array `['ngRoute', 'crmUi', 'crmUtil']` + with a function call `CRM.angRequires('aboutme')`. -!!! note "Note: `ang/` folder" - By convention, AngularJS source code is stored in the `ang/` folder. - The convention is discussed in more detail in [AngularJS: File - Names](/framework/angular/files.md) -## Add an Angular route, etal +## Add an Angular-based page Now let's add a new Angular-based page. This page will require a `route` with a `controller` and an HTML template. The command `civix From 984433403497c6214368198ea2e72af842601faa Mon Sep 17 00:00:00 2001 From: Tim Otten Date: Mon, 5 Jun 2017 19:39:02 -0700 Subject: [PATCH 05/17] "AngularJS: File Names" - Tweak heading --- docs/framework/angular/files.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/framework/angular/files.md b/docs/framework/angular/files.md index 0fc28b31..3316125e 100644 --- a/docs/framework/angular/files.md +++ b/docs/framework/angular/files.md @@ -58,7 +58,7 @@ __Service Files__ * `ang/{mymodule}/{FooBar}.js` - The declaration and logic for a service named `mymoduleFooBar`. -!!! tip "Use tilde (`~`) to load HTML templates" +!!! tip "Tip: Use tilde (`~`) to load HTML templates" When writing code for Angular, you might use an expression like `{templateUrl: 'https://example.org/FooBar.html'}`. However, constructing a full URL that works in every Civi deployment would be From 52eabe30bb897f7d79fa06e63a1266834a08aeae Mon Sep 17 00:00:00 2001 From: Tim Otten Date: Mon, 5 Jun 2017 21:36:05 -0700 Subject: [PATCH 06/17] "AngularJS: Loader" - Various updates --- docs/framework/angular/loader.md | 120 +++++++++++++--------- docs/hooks/hook_civicrm_angularModules.md | 19 +++- 2 files changed, 88 insertions(+), 51 deletions(-) diff --git a/docs/framework/angular/loader.md b/docs/framework/angular/loader.md index 05bf3415..4041166b 100644 --- a/docs/framework/angular/loader.md +++ b/docs/framework/angular/loader.md @@ -1,7 +1,11 @@ # AngularJS: Loader -What happens when a user visits a CiviCR-Angular page, such as -`https://example.org/civicrm/a/#/mailing/new`? Broadly speaking, two steps: +What happens when a user visits a CiviCR-Angular page? For example, let's +consider this URL: + + * `https://example.org/civicrm/a/#/mailing/new` + +Broadly speaking, two things happen: 1. (Server-side) CiviCRM processes the request for `civicrm/a`. It displays a web-page with all your Angular modules. @@ -13,19 +17,64 @@ The client-side behavior is well-defined by Angular server-side in greater depth because that is unique to the CiviCRM-Angular integration. -## The default base page (`civicrm/a`) +## The library of AngularJS modules -CiviCRM includes a default base-page -- any module can add new routes on -this page. For example, the `crmMailing` module defines a route -`mailing/new`. You can view this at: +CiviCRM needs a list of available AngularJS modules. Technically, these +modules are defined via +[hook_civicrm_angularModules](/hooks/hook_civicrm_angularModules.md), e.g. - * `https://example.org/civicrm/a/#/mailing/new` +```php +/** + * Implements hook_civicrm_angularModules. + */ +function foobar_civicrm_angularModules(&$angularModules) { + $angularModules['myBigAngularModule'] = array( + 'ext' => 'org.example.foobar', + 'basePages' => array('civicrm/a'), + 'requires' => array('crmUi', 'crmUtils', 'ngRoute'), + 'js' => array('ang/myBigangularModule/*.js'), + 'css' => array('ang/myBigangularModule/*.css'), + 'partials' => array('ang/myBigangularModule'), + ); +} +``` + +!!! tip "Tip: Generating skeletal code with `civix`" + In practice, this skeletal code can be autogenerated usin `civix`. + For details, see [AngularJS: Quick Start](/framework/angular/quickstart.md). + +The list of available modules varies depending on your system configuration: +if you install more CiviCRM extensions, then you might have more Angular +modules. Use `cv` to inspect the list of available Angular modules: + +``` +$ cv ang:module:list +For a full list, try passing --user=[username]. ++-------------------+-------------+------------------------------------------------------------------------------------+ +| name | basePages | requires | ++-------------------+-------------+------------------------------------------------------------------------------------+ +| angularFileUpload | civicrm/a | | +| bw.paging | (as-needed) | | +| civicase | (as-needed) | crmUi, crmUtil, ngRoute, angularFileUpload, bw.paging, crmRouteBinder, crmResource | +| crmApp | civicrm/a | | +| crmAttachment | civicrm/a | angularFileUpload, crmResource | +| crmAutosave | civicrm/a | crmUtil | +| crmCaseType | civicrm/a | ngRoute, ui.utils, crmUi, unsavedChanges, crmUtil, crmResource | +... +``` + +!!! tip "Tip: More options for `cv ang:module:list`" + * Use `--columns` to specify which columns to display. Ex: `cv ang:module:list --columns=name,ext,extDir` + * Use `--out` to specify an output format. Ex: `cv ang:module:list --out=json-pretty` + * Use `--user` to specify a login user using with. This may reveal permission-dependent modules. Ex: `cv ang:module:list --user=admin` -The default base-page is special because *all registered Angular modules -will be included by default*. You can expect the markup to look roughly -like this: +## The default base page (`civicrm/a`) + +CiviCRM includes a default base-page named `civicrm/a`. Any module can add new routes on +this page. The page might look like this: ```html + @@ -44,51 +93,26 @@ like this: ``` -The PHP application instantiates `AngularLoader` +!!! note "In practice, the JS files may be aggregated and/or minimized." + +The markup is generated by a PHP class, `AngularLoader`, using logic like this: ```php $loader = new \Civi\Angular\AngularLoader(); -$loader->setPageName('civicrm/a'); $loader->setModules(array('crmApp')); +$loader->setPageName('civicrm/a'); $loader->load(); ``` The `load()` function determines the necessary JS/CSS/HTML/JSON resources -and loads them on the page. Roughly speaking, it outputs: - - - -More specifically, the `load()` function gets a list of all available -Angular modules (including their JS/CSS/HTTML files). Then it loads the -files for `crmApp` as well as any dependencies (like `crmUi`). - -The most important thing to understand is how it *gets the list of Angular -modules*. A few Angular modules are bundled with core (eg `crmUi` or -`crmUtil`), but most new Angular modules should be loaded via -[hook_civicrm_angularModules](/hooks/hook_civicrm_angularModules.md) - -For example, if you created an extension `org.example.foobar` with an -Angular module named `myBigAngularModule`, then the hook might look like: - -```php -/** - * Implements hook_civicrm_angularModules. - */ -function foobar_civicrm_angularModules(&$angularModules) { - $angularModules['myBigAngularModule'] = array( - 'ext' => 'org.example.foobar', - 'basePages' => array('civicrm/a'), - 'requires' => array('crmUi', 'crmUtils', 'ngRoute'), - 'js' => array('ang/myBigangularModule/*.js'), - 'css' => array('ang/myBigangularModule/*.css'), - 'partials' => array('ang/myBigangularModule'), - ); -} -``` - -!!! tip "`civix` code-generator" - In practice, you usually don't need to implement. The `civix` - code-generator creates a file named `ang/{mymodule}.ang.php` and - automatically loads as part of `hook_civicrm_angularModules`. +and loads them on the page. This will include: + * Any AngularJS modules explicitly listed in `setModules(...)`. (Ex: `crmApp`) + * Any AngularJS modules with matching `basePages`. (Ex: The value `civicrm/a` + is specified by both `setPageName(...)` and `myBigAngularModule` [above].) + * Any AngularJS modules transitively required by the above. +!!! note "What makes `civicrm/a` special?" + When declaring a module, the property `basePages` will default to + `array('civicrm/a')`. In other words, if you don't specify otherwise, + all modules are loaded on `civicrm/a`. diff --git a/docs/hooks/hook_civicrm_angularModules.md b/docs/hooks/hook_civicrm_angularModules.md index 21dd0901..0ce3c933 100644 --- a/docs/hooks/hook_civicrm_angularModules.md +++ b/docs/hooks/hook_civicrm_angularModules.md @@ -16,12 +16,23 @@ available in CiviCRM 4.6+. ## Parameters -- &$angularModules - an array containing a list of all Angular - modules. + * `&$angularModules` - an array containing a list of all Angular modules. Each item is keyed by the Angular module name. + +Each item `angularModules` may include these properties: + + * `ext` (`string`): The name of the CiviCRM extension which has the source-code. + * `js` (`array`): List of Javascript files. May use the wildcard (`*`). Relative to the extension. + * `css` (`array`): List of CSS files. May use the wildcard (`*`). Relative to the extension. + * `partials` (`array`): List of HTML folders. Relative to the extension. + * `requires` (`array`): List of AngularJS modules required by this module. Default: `array()`. (`v4.7.21+`) + * `basePages` (`array`): Uncondtionally load this module onto the given Angular pages. (`v4.7.21+`) + * If omitted, the default is `array('civicrm/a')`. This provides backward compatibility with behavior since `v4.6+`. + * For a utility that should only be loaded on-demand, use `array()`. + * For a utility that should be loaded in all pages use, `array('*')`. ## Returns -- null + * `null` ## Example @@ -32,6 +43,8 @@ available in CiviCRM 4.6+. ); $angularModules['myBigAngularModule'] = array( 'ext' => 'org.example.mymod', + 'requires' => array('ngRoute', 'crmUi'), + 'basePages' => array('civicrm/a'), 'js' => array('js/part1.js', 'js/part2.js'), 'css' => array('css/myAngularModule.css'), 'partials' => array('partials/myBigAngularModule'), From cbfcf6b4b59f945afde4a803a05e4fd01a1f40e1 Mon Sep 17 00:00:00 2001 From: Tim Otten Date: Tue, 6 Jun 2017 00:09:06 -0700 Subject: [PATCH 07/17] "AngularJS: Loader" - Various updates --- docs/framework/angular/loader.md | 181 +++++++++++++++++++++---------- 1 file changed, 122 insertions(+), 59 deletions(-) diff --git a/docs/framework/angular/loader.md b/docs/framework/angular/loader.md index 4041166b..6d1db086 100644 --- a/docs/framework/angular/loader.md +++ b/docs/framework/angular/loader.md @@ -3,49 +3,33 @@ What happens when a user visits a CiviCR-Angular page? For example, let's consider this URL: - * `https://example.org/civicrm/a/#/mailing/new` + * `https://example.org/civicrm/a/#/caseType` Broadly speaking, two things happen: 1. (Server-side) CiviCRM processes the request for `civicrm/a`. It - displays a web-page with all your Angular modules. - 2. (Client-side) AngularJS processes the request for `mailing/new`. - It uses an HTML template to setup the UI. + displays a web-page with all your Angular modules -- such as + `crmAttachment`, `crmCaseType`, `crmUi`, and so on. + 2. (Client-side) AngularJS processes the request. It finds that the + module `crmCaseType` includes a route for `#/caseType` and loads the + appropriate HTML template. The client-side behavior is well-defined by Angular [ngRoute](https://docs.angularjs.org/api/ngRoute). We'll explore the server-side in greater depth because that is unique to the CiviCRM-Angular integration. -## The library of AngularJS modules +!!! caution "Discusses new/experimental interfaces" -CiviCRM needs a list of available AngularJS modules. Technically, these -modules are defined via -[hook_civicrm_angularModules](/hooks/hook_civicrm_angularModules.md), e.g. + Some elements of this document have been around since CiviCRM v4.6 + and should remain well-supported. Other elements are new circa v4.7.21 + and are still flagged experimental. -```php -/** - * Implements hook_civicrm_angularModules. - */ -function foobar_civicrm_angularModules(&$angularModules) { - $angularModules['myBigAngularModule'] = array( - 'ext' => 'org.example.foobar', - 'basePages' => array('civicrm/a'), - 'requires' => array('crmUi', 'crmUtils', 'ngRoute'), - 'js' => array('ang/myBigangularModule/*.js'), - 'css' => array('ang/myBigangularModule/*.css'), - 'partials' => array('ang/myBigangularModule'), - ); -} -``` +## Library of modules -!!! tip "Tip: Generating skeletal code with `civix`" - In practice, this skeletal code can be autogenerated usin `civix`. - For details, see [AngularJS: Quick Start](/framework/angular/quickstart.md). - -The list of available modules varies depending on your system configuration: -if you install more CiviCRM extensions, then you might have more Angular -modules. Use `cv` to inspect the list of available Angular modules: +The CiviCRM-Angular loader needs a list of available AngularJS modules. +This list depends on your system configuration (e.g. which CiviCRM +extensions are enabled). To view the current list, run `cv`: ``` $ cv ang:module:list @@ -60,42 +44,58 @@ For a full list, try passing --user=[username]. | crmAttachment | civicrm/a | angularFileUpload, crmResource | | crmAutosave | civicrm/a | crmUtil | | crmCaseType | civicrm/a | ngRoute, ui.utils, crmUi, unsavedChanges, crmUtil, crmResource | -... +...snip... ``` !!! tip "Tip: More options for `cv ang:module:list`" - * Use `--columns` to specify which columns to display. Ex: `cv ang:module:list --columns=name,ext,extDir` - * Use `--out` to specify an output format. Ex: `cv ang:module:list --out=json-pretty` - * Use `--user` to specify a login user using with. This may reveal permission-dependent modules. Ex: `cv ang:module:list --user=admin` + Use `--columns` to specify which columns to display. Ex: -## The default base page (`civicrm/a`) + ``` + $ cv ang:module:list --columns=name,ext,extDir + ``` -CiviCRM includes a default base-page named `civicrm/a`. Any module can add new routes on -this page. The page might look like this: + Use `--out` to specify an output format. Ex: -```html - - - - - - - - - -... - -... - - -
- - + ``` + $ cv ang:module:list --out=json-pretty + ``` + + Use `--user` to specify a login user. This may reveal permission-dependent modules. Ex: + + ``` + $ cv ang:module:list --user=admin + ``` + +Under-the-hood, this library of modules is defined via +[hook_civicrm_angularModules](/hooks/hook_civicrm_angularModules.md), e.g. + +```php +/** + * Implements hook_civicrm_angularModules. + */ +function foobar_civicrm_angularModules(&$angularModules) { + $angularModules['myBigAngularModule'] = array( + 'ext' => 'org.example.foobar', + 'basePages' => array('civicrm/a'), + 'requires' => array('crmUi', 'crmUtils', 'ngRoute'), + 'js' => array('ang/myBigangularModule/*.js'), + 'css' => array('ang/myBigangularModule/*.css'), + 'partials' => array('ang/myBigangularModule'), + ); +} ``` -!!! note "In practice, the JS files may be aggregated and/or minimized." +!!! tip "Tip: Generating skeletal code with `civix`" + In practice, one usually doesn't need to implement this hook directly. + Instead, generate skeletal code with `civix`. For details, see + [AngularJS: Quick Start](/framework/angular/quickstart.md). + +## Default base-page -The markup is generated by a PHP class, `AngularLoader`, using logic like this: +CiviCRM includes a "base-page" named `civicrm/a`. By default, this page +includes the core AngularJS files as well as all the modules in the library. + +The page is generated with a PHP class, `AngularLoader`, using logic like this: ```php $loader = new \Civi\Angular\AngularLoader(); @@ -109,10 +109,73 @@ and loads them on the page. This will include: * Any AngularJS modules explicitly listed in `setModules(...)`. (Ex: `crmApp`) * Any AngularJS modules with matching `basePages`. (Ex: The value `civicrm/a` - is specified by both `setPageName(...)` and `myBigAngularModule` [above].) + is specified by both `setPageName(...)` [above] and `myBigAngularModule` [above].) * Any AngularJS modules transitively required by the above. !!! note "What makes `civicrm/a` special?" When declaring a module, the property `basePages` will default to `array('civicrm/a')`. In other words, if you don't specify otherwise, - all modules are loaded on `civicrm/a`. + all modules are loaded on `civicrm/a`. + +## Custom base-pages + +Loading all Angular modules on one page poses a trade-off. On one hand, it +warms up the caches and enables quick transitions between screens. On the +other hand, the page could become bloated with modules that aren't actually +used. + +If this is a concern, then you can create new base-pages which are tuned to +a more specific use-case. For example, suppose we want to create a page +which *only* has the CiviCase administrative UI. + +First, create a skeletal CiviCRM page: + +``` +$ civix generate:page CaseAdmin civicrm/caseadmin +``` + +Second, edit the new PHP file and update the `run()` function. Create an +`AngularLoader` to load all the JS/CSS files: + +```php +public function run() { + $loader = new \Civi\Angular\AngularLoader(); + $loader->setModules(array('crmCaseType')); + $loader->setPageName('civicrm/caseadmin'); + $loader->load(); + parent::run(); +} +``` + +Third, initialize AngularJS on the client-side. You might put this +in the Smarty template: + +```html +
+
+
+``` + +Finally, flush the cache and visit the new page. + +``` +# Flush the cache +$ cv flush + +# Lookup the URL (Drupal 7 example) +$ cv url 'civicrm/caseadmin/#/caseType' +"http://dmaster.l/civicrm/caseadmin/#/caseType" +``` + +!!! seealso "See Also: AngularJS Bootstrap Guide" + + There are a few ways to boot Angular, such as `ng-app="..."` or + `angular.bootstrap(...);`. These techniques are discussed more in the + [AngularJS Bootstrap Guide](https://code.angularjs.org/1.5.11/docs/guide/bootstrap). + +!!! caution "Embedding Angular in other contexts" + + The `AngularLoader` does not require a standalone page -- for + example, you might inject Angular onto a pre-existing, non-Angular page + by using `hook_civicrm_pageRun` and `AngularLoader`. Some extensions do + this -- though it remains to be seen whether this is *wise*. From 57f681a5b45d6b7ca3d0a5648a8c3d665d984898 Mon Sep 17 00:00:00 2001 From: Tim Otten Date: Tue, 6 Jun 2017 01:06:52 -0700 Subject: [PATCH 08/17] Asset Builder - Copyedits --- docs/framework/asset-builder.md | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/docs/framework/asset-builder.md b/docs/framework/asset-builder.md index 78ee6b31..2aa66ff4 100644 --- a/docs/framework/asset-builder.md +++ b/docs/framework/asset-builder.md @@ -1,7 +1,7 @@ # Asset Builder -The `AssetBuilder` manages semi-dynamic assets, such as generated JS/CSS -files. Semi-dynamic assets are simultaneously: +The `AssetBuilder` manages lazily-generated assets, such as aggregated +JS/CSS files. Lazy assets are simultaneously: * __Dynamic__: They vary depending on the current system configuration. You cannot lock-in a correct version of the asset because each @@ -12,18 +12,19 @@ files. Semi-dynamic assets are simultaneously: !!! note "Example: Batch loading Angular HTML" When visiting the AngularJS base page `civicrm/a`, one needs to load a - mix of many small HTML files. It's ideal to aggregate them into one + mix of many small HTML templates. It's ideal to aggregate them into one bigger file and reduce the number of round-trip HTTP requests. - This asset is _dynamic_ because extensions can add or modify HTML files. - Two different sites would have different HTML files (depending on the - mix of extensions). Yet, within a particular site/configuration, the - content is _static_ because the HTML doesn't actually change at runtime. + This asset is _dynamic_ because extensions can add or modify HTML + templates. Two different sites would have different HTML templates + (depending on the mix of extensions). Yet, within a particular + site/configuration, the content is _static_ because the HTML doesn't + actually change at runtime. -The `AssetBuilder` addresses this use-case with *lazy* building. Assets are not -distributed as part of `git` or `tar.gz`. Rather, the first time you try to -use an asset, it fires off a hook to build the asset. The content is stored -in a cache file. +The `AssetBuilder` addresses this use-case with *lazy* building. Assets are +not distributed as part of `git` or `tar.gz`. Rather, the first time you +use an asset, it fires off a hook which builds the content. The content is +stored in a cache file. !!! tip "Tip: Caching and development" If you are developing or patching assets, then the caching behavior may @@ -41,6 +42,8 @@ For example, suppose we wanted to define a static file named // Get the URL to `api-fields.json`. $url = \Civi::service('asset_builder')->getUrl('api-fields.json'); +... + // Define the content of `api-fields.json` using `hook_civicrm_buildAsset`. function mymodule_civicrm_buildAsset($asset, $params, &$mimeType, &$content) { if ($asset !== 'api-fields.json') return; @@ -92,6 +95,8 @@ $caseEntitiesUrl = \Civi::service('asset_builder') ) ); +... + // Define the content of `api-fields.json` using `hook_civicrm_buildAsset`. function mymodule_civicrm_buildAsset($asset, $params, &$mimeType, &$content) { if ($asset !== 'api-fields.json') return; @@ -120,14 +125,14 @@ function mymodule_civicrm_buildAsset($asset, $params, &$mimeType, &$content) { Both are written in PHP, but they address differet parts of the process: * `AssetBuilder` provides URL-routing, caching, and parameterization. - Its strength is defining the *lifecycle* of a dynamic asset. + Its strength is defining a *lazy lifecycle* for the assets. * `Assetic` provides a library of generators and filters. Its strength is defining the *content* of an asset. You could use them together -- e.g. in `hook_civicrm_buildAsset`, declare a new asset and use `Assetic` to build its content. -!!! caution "Caution: Assimilate non-confidential data" +!!! caution "Caution: Confidentiality and lazy assets" The current implementation does not take aggressive measures to keep assets confidential. For example, an asset built from public JS files is fine, but an asset built from permissioned data (contact-records From d4a922587b48c7a4b56e69ed3a80db7eff33b1b2 Mon Sep 17 00:00:00 2001 From: Tim Otten Date: Tue, 6 Jun 2017 01:07:03 -0700 Subject: [PATCH 09/17] AngularJS - Copyedits --- docs/framework/angular/files.md | 4 ++-- docs/framework/angular/loader.md | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/framework/angular/files.md b/docs/framework/angular/files.md index 3316125e..69e5225b 100644 --- a/docs/framework/angular/files.md +++ b/docs/framework/angular/files.md @@ -4,8 +4,8 @@ As a developer working with CiviCRM-Angular, you write *Angular modules* -- these modules are composed of various JS/CSS/HTML files which define the *services*, *directives*, *controllers*, and *HTML partials*. -For sake of predictability, these files follow a naming convention. All -major Angular files are placed in the `ang/` folder. +For sake of predictability, these files are placed in the `ang/` folder, and +they follow a naming convention. !!! note "How does this work with `civix`?" When you generate Angular code via `civix`, the files are diff --git a/docs/framework/angular/loader.md b/docs/framework/angular/loader.md index 6d1db086..4ef07f59 100644 --- a/docs/framework/angular/loader.md +++ b/docs/framework/angular/loader.md @@ -1,6 +1,6 @@ # AngularJS: Loader -What happens when a user visits a CiviCR-Angular page? For example, let's +What happens when a user visits a CiviCRM-Angular page? For example, let's consider this URL: * `https://example.org/civicrm/a/#/caseType` @@ -9,8 +9,8 @@ Broadly speaking, two things happen: 1. (Server-side) CiviCRM processes the request for `civicrm/a`. It displays a web-page with all your Angular modules -- such as - `crmAttachment`, `crmCaseType`, `crmUi`, and so on. - 2. (Client-side) AngularJS processes the request. It finds that the + `ngRoute`, `crmAttachment`, `crmCaseType`, `crmUi`, and so on. + 2. (Client-side) AngularJS processes the HTML/JS/CSS. It finds that the module `crmCaseType` includes a route for `#/caseType` and loads the appropriate HTML template. @@ -19,7 +19,7 @@ The client-side behavior is well-defined by Angular server-side in greater depth because that is unique to the CiviCRM-Angular integration. -!!! caution "Discusses new/experimental interfaces" +!!! caution "Caution: Discusses new/experimental interfaces" Some elements of this document have been around since CiviCRM v4.6 and should remain well-supported. Other elements are new circa v4.7.21 From 9fe28806beea4e81cbfa3bad980ab470a58bd38d Mon Sep 17 00:00:00 2001 From: Tim Otten Date: Wed, 14 Jun 2017 13:54:56 -0700 Subject: [PATCH 10/17] AngularJS Quick Start - Improve code snippets --- docs/framework/angular/quickstart.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/docs/framework/angular/quickstart.md b/docs/framework/angular/quickstart.md index 2a1b20ba..9759a552 100644 --- a/docs/framework/angular/quickstart.md +++ b/docs/framework/angular/quickstart.md @@ -66,15 +66,18 @@ Angular runtime, e.g. !!! tip "Tip: angular.module() and CRM.angRequires()" The list of dependencies is declared once in PHP and once in JS. To - remove this duplication, replace the array `['ngRoute', 'crmUi', 'crmUtil']` - with a function call `CRM.angRequires('aboutme')`. + remove this duplication, call `CRM.angRequires(...)`, as in: + + ```js + angular.module('aboutme', CRM.angRequires('aboutme')); + ``` ## Add an Angular-based page Now let's add a new Angular-based page. This page will require a `route` -with a `controller` and an HTML template. The command `civix -generate:angular-page` will create each of these: +with a `controller` and an HTML template. The command +`civix generate:angular-page` will create each of these: ``` $ civix generate:angular-page EditCtrl about/me From e7fa4a97563f1104601d822b16e4879dd4463e2b Mon Sep 17 00:00:00 2001 From: Tim Otten Date: Wed, 14 Jun 2017 16:02:41 -0700 Subject: [PATCH 11/17] AngularJS Files - Prose tweaks. Mention *.md. --- docs/framework/angular/files.md | 37 ++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/docs/framework/angular/files.md b/docs/framework/angular/files.md index 69e5225b..f7acd243 100644 --- a/docs/framework/angular/files.md +++ b/docs/framework/angular/files.md @@ -2,7 +2,7 @@ As a developer working with CiviCRM-Angular, you write *Angular modules* -- these modules are composed of various JS/CSS/HTML files which define the -*services*, *directives*, *controllers*, and *HTML partials*. +*services*, *directives*, *controllers*, and *routes*. For sake of predictability, these files are placed in the `ang/` folder, and they follow a naming convention. @@ -11,34 +11,39 @@ they follow a naming convention. When you generate Angular code via `civix`, the files are named according to convention. - One of the files, `ang/{mymodule}.ang.php` provides instructions for the - file-loader. + One file, `ang/{mymodule}.ang.php`, provides instructions for the + file-loader. It lists any files which match the naming + convention. -!!! note "What if my code doesn't follow the naming convention? What if I don't use `civix`?" +!!! note "What if I don't use `civix`? What if my code doesn't follow the naming convention?" The file-loader needs some information about the name and location of your AngularJS code, but you don't need to follow the convention. You can configure it via hook. See: [AngularJS: Loading](/framework/angular/loader.md). ## Abridged convention -Some Angular modules have a very narrow purpose -- such as defining a -singular `service` or `directive`. These modules only have 2 or 3 files. +The abridged convention applies to small Angular modules with a narrow +purpose -- such as defining a singular `service` or `directive`. These +modules only have 2 or 3 files. * `ang/{mymodule}.ang.php` - General metadata about the module (per [hook_civicrm_angularModules](/hooks/hook_civicrm_angularModules.md)). - * `ang/{mymodule}.js` - All Javascript for the module - * `ang/{mymodule}.css` - All CSS for the module (if applicable) + * `ang/{mymodule}.js` - All Javascript for the module. + * `ang/{mymodule}.css` - All CSS for the module (if applicable). + * `ang/{mymodule}.md` - Developer documentation about the module (if applicable). ## Full convention -Some Angular modules have broader purpose -- such as defining a new screen -with a series of related `directive`s, `controller`s, and `service`s. Each -of these elements may have multiple aspects (JS/HTML/CSS). +The full convention applies to bigger Angular modules which serve a broader +purpose -- such as defining a new screen with a series of related +`directive`s, `controller`s, and `service`s. Each of these elements may +have multiple aspects (JS/HTML/CSS). __Module Files__ * `ang/{mymodule}.ang.php` - General metadata about the module (per [hook_civicrm_angularModules](/hooks/hook_civicrm_angularModules.md)). * `ang/{mymodule}.js` - General metadata about the module. - * `ang/{mymodule}.css` - General CSS that applies throughout the module. + * `ang/{mymodule}.css` - General CSS that applies throughout the module (if applicable). + * `ang/{mymodule}.md` - Developer documentation about the module (if applicable). __Directive Files__ @@ -46,6 +51,7 @@ __Directive Files__ * `ang/{mymodule}/{FooBar}.html` - The main/default template for the directive (if applicable). * `ang/{mymodule}/{FooBar}/{Extra}.html` - If you have multiple templates used by the same directive (e.g. via `ng-include` or conditional logic), then put them in a subdir. * `ang/{mymodule}/{FooBar}.css` - Any CSS specifically intended for `mymoduleFooBar` (if applicable). + * `ang/{mymodule}/{FooBar}.md` - Developer documentation about the directive (if applicable). __Controller Files__ (These follow the same convention as directives, but they have the suffix `Ctrl`.) @@ -53,18 +59,15 @@ __Controller Files__ (These follow the same convention as directives, but they h * `ang/{mymodule}/{FooBar}Ctrl.html` - The main/default template for the controller (if applicable). * `ang/{mymodule}/{FooBar}Ctrl/{Extra}.html` - If you have multiple templates used with the same controller (e.g. via `ng-include` or conditional logic), then put them in a subdir. * `ang/{mymodule}/{FooBar}Ctrl.css` - Any CSS specifically intended for `MymoduleFooBarCtrl` (if applicable). + * `ang/{mymodule}/{FooBar}Ctrl.md` - Developer documentation about the controller (if applicable). __Service Files__ * `ang/{mymodule}/{FooBar}.js` - The declaration and logic for a service named `mymoduleFooBar`. + * `ang/{mymodule}/{FooBar}.md` - Developer documentation about the service (if applicable). !!! tip "Tip: Use tilde (`~`) to load HTML templates" When writing code for Angular, you might use an expression like `{templateUrl: 'https://example.org/FooBar.html'}`. However, constructing a full URL that works in every Civi deployment would be complex. Instead, use the tilde prefix. For example, `{templateUrl: '~/mymodule/FooBar.html'}`. - - From 5cb11b87ceffec88b8eabba7dceb1b39430a95b3 Mon Sep 17 00:00:00 2001 From: Tim Otten Date: Wed, 14 Jun 2017 16:11:25 -0700 Subject: [PATCH 12/17] AngularJS Loader - Misc copy-edit --- docs/framework/angular/loader.md | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/docs/framework/angular/loader.md b/docs/framework/angular/loader.md index 4ef07f59..3ba2473c 100644 --- a/docs/framework/angular/loader.md +++ b/docs/framework/angular/loader.md @@ -66,7 +66,7 @@ For a full list, try passing --user=[username]. $ cv ang:module:list --user=admin ``` -Under-the-hood, this library of modules is defined via +Under-the-hood, this library of modules is built via [hook_civicrm_angularModules](/hooks/hook_civicrm_angularModules.md), e.g. ```php @@ -117,7 +117,11 @@ and loads them on the page. This will include: `array('civicrm/a')`. In other words, if you don't specify otherwise, all modules are loaded on `civicrm/a`. -## Custom base-pages +!!! note "How does `load()` output the ` - 4: - 5: - 6: - 7:
...site wide header...
- 8:
- 9:
...site wide footer...
-10: -11: + 3: + 4: + 5: + 6: + 7: + 8:
...site wide header...
+ 9:
+10:
...site wide footer...
+11: +12: ``` The first interaction comes when CiviCRM generates the initial HTML page: * CiviCRM listens for requests to the path `civicrm/a`. (It does this in a way which is compatible with multiple CMSs -- Drupal, Joomla, WordPress, etc.) - * CiviCRM builds the list of JS/CSS files in lines 3-4. (It does this in a - way which allows extensions to add new JS/CSS files.) + * CiviCRM builds the list of CSS/JS/JSON resources in lines 3-5. (It does this in a + way which allows extensions to add new CSS/JS/JSON. See also: + [Resource Reference](https://wiki.civicrm.org/confluence/display/CRMDOC/Resource+Reference).) * CiviCRM ensures that the page includes the site-wide elements, such as - lines 7 and 9. (It does this in a way which is compatible with multiple CMSs.) + lines 8 and 10. (It does this in a way which is compatible with multiple CMSs.) Once the page is loaded, it works just like any AngularJS 1.x application. It uses concepts like `ng-app`, "module", "directive", "service", "component", and @@ -95,5 +97,5 @@ the discussion if you have a beginner-level understanding of both. * [Quick Start](angular/quickstart.md): How to create a new screen in the CiviCRM-Angular UI * [File Names](angular/files.md): How AngularJS files are named in `civicrm-core` and `civix` - * [Loader](angular/loader.md): How JS/CSS files are loaded + * [Loader](angular/loader.md): How to find and load JS/CSS files for CiviCRM-Angular * [Modifications](angular/modify.md): How a third-party can modify the content of a CiviCRM-Angular UI From 19230d6a8732bcc28e9969a2ee2b0b215c484b96 Mon Sep 17 00:00:00 2001 From: Tim Otten Date: Tue, 20 Jun 2017 14:11:31 -0700 Subject: [PATCH 14/17] AngularJS Change Sets - Update title. Add intro para. --- docs/framework/angular/modify.md | 38 +++++++++++++++++++++++++------- mkdocs.yml | 2 +- 2 files changed, 31 insertions(+), 9 deletions(-) diff --git a/docs/framework/angular/modify.md b/docs/framework/angular/modify.md index cc0ca5f4..01bba605 100644 --- a/docs/framework/angular/modify.md +++ b/docs/framework/angular/modify.md @@ -1,14 +1,36 @@ -# AngularJS: Alteration +# AngularJS: Change Sets + +The [Quick Start](quickstart.md) and [Loader](loader.md) provide examples of +creating *new* screens. But what if you need to alter an *existing* screen? +CiviCRM allows third-parties to define *change sets* which programmatically +manipulate Angular content (before sending it to the client). + +## Background + +Most AngularJS tutorials focus on idealized projects where a single +developer or product-owner exercises full authority over their application. +But CiviCRM is an _ecosystem_ with a range of stakeholders, including many +developers (authoring indpendent extensions) and administrators (managing +independent deployments with independent configurations). + +... + +## tldr ```php -function example_civicrm_alterAngular($angular) { - $angular->add(\Civi\Angular\ChangeSet::create('mychanges') - ->alterHtml('~/crmMailing/EditMailingCtrl/2step.html', function(phpQueryObject $doc) { - $doc->find('[ng-form="crmMailingSubform"]')->attr('cat-stevens', 'ts(\'wild world\')'); - }) - ); +function mailwords_civicrm_alterAngular(\Civi\Angular\Manager $angular) { + $changeSet = \Civi\Angular\ChangeSet::create('inject_mailwords') + // ->requires('crmMailing', 'mailwords') + ->alterHtml('~/crmMailing/BlockSummary.html', + function (phpQueryObject $doc) { + $doc->find('.crm-group')->append(' +
+ +
+ '); + }); + $angular->add($changeSet); } ``` - cv ang:html:show \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index 3f7d2803..aaa6bb0a 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -48,7 +48,7 @@ pages: - Quick Start: framework/angular/quickstart.md - File Names: framework/angular/files.md - Loader: framework/angular/loader.md - - Modifications: framework/angular/modify.md + - Change Sets: framework/angular/modify.md - Asset Builder: framework/asset-builder.md # Bootstrap: /framework/bootstrap.md # Cache: /framework/cache.md From e0dca17cc7220f7dd2a386b4645c7405a600146f Mon Sep 17 00:00:00 2001 From: Tim Otten Date: Tue, 20 Jun 2017 14:21:04 -0700 Subject: [PATCH 15/17] AngularJS - Change TOC. Use index.md. Fix stale links. --- docs/framework/{angular.md => angular/index.md} | 10 +++++----- docs/framework/angular/quickstart.md | 2 +- mkdocs.yml | 10 +++++----- 3 files changed, 11 insertions(+), 11 deletions(-) rename docs/framework/{angular.md => angular/index.md} (91%) diff --git a/docs/framework/angular.md b/docs/framework/angular/index.md similarity index 91% rename from docs/framework/angular.md rename to docs/framework/angular/index.md index 689cde20..59fd81ce 100644 --- a/docs/framework/angular.md +++ b/docs/framework/angular/index.md @@ -86,7 +86,7 @@ data. This uses the CiviCRM API. Key concepts in CiviCRM API include !!! seealso "Read more about CiviCRM API" A good resource for understanding CiviCRM API concepts is the [APIv3: - Intro](/api/general.md). + Intro](/api/index.md). In the remainder of this document, we'll try to avoid in-depth discussion about the internals of AngularJS 1.x or APIv3. You should be able to follow @@ -95,7 +95,7 @@ the discussion if you have a beginner-level understanding of both. ## Additional pages - * [Quick Start](angular/quickstart.md): How to create a new screen in the CiviCRM-Angular UI - * [File Names](angular/files.md): How AngularJS files are named in `civicrm-core` and `civix` - * [Loader](angular/loader.md): How to find and load JS/CSS files for CiviCRM-Angular - * [Modifications](angular/modify.md): How a third-party can modify the content of a CiviCRM-Angular UI + * [Quick Start](quickstart.md): How to create a new screen in the CiviCRM-Angular UI + * [File Names](files.md): How AngularJS files are named in `civicrm-core` and `civix` + * [Loader](loader.md): How to find and load JS/CSS files for CiviCRM-Angular + * [Modifications](modify.md): How a third-party can modify the content of a CiviCRM-Angular UI diff --git a/docs/framework/angular/quickstart.md b/docs/framework/angular/quickstart.md index 9759a552..9a52eb49 100644 --- a/docs/framework/angular/quickstart.md +++ b/docs/framework/angular/quickstart.md @@ -96,7 +96,7 @@ user's first-name and last-name. By default, CiviCRM aggregates AngularJS files and caches them. You can flush this cache manually (`cv flush`). However, it may be easier to disable some of the aggregation/caching features by [enabling debug - mode](/dev-tools/debugging.md). + mode](/tools/debugging.md). ## Open the page diff --git a/mkdocs.yml b/mkdocs.yml index aaa6bb0a..51c0f742 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -44,11 +44,11 @@ pages: - Advanced patterns: extensions/advanced.md - Framework Reference: - AngularJS: - - Overview: framework/angular.md - - Quick Start: framework/angular/quickstart.md - - File Names: framework/angular/files.md - - Loader: framework/angular/loader.md - - Change Sets: framework/angular/modify.md + - "AngularJS: Intro": framework/angular/index.md + - "AngularJS: Quick Start": framework/angular/quickstart.md + - "AngularJS: File Names": framework/angular/files.md + - "AngularJS: Loader": framework/angular/loader.md + - "AngularJS: Change Sets": framework/angular/modify.md - Asset Builder: framework/asset-builder.md # Bootstrap: /framework/bootstrap.md # Cache: /framework/cache.md From 7e85d02c154ca6af3f5bf50c1d42acb0cc798558 Mon Sep 17 00:00:00 2001 From: Tim Otten Date: Tue, 20 Jun 2017 14:29:14 -0700 Subject: [PATCH 16/17] AngularJS Changesets - Use consistent naming --- .../angular/{modify.md => changeset.md} | 16 ++++++++++++---- docs/framework/angular/index.md | 2 +- mkdocs.yml | 2 +- 3 files changed, 14 insertions(+), 6 deletions(-) rename docs/framework/angular/{modify.md => changeset.md} (78%) diff --git a/docs/framework/angular/modify.md b/docs/framework/angular/changeset.md similarity index 78% rename from docs/framework/angular/modify.md rename to docs/framework/angular/changeset.md index 01bba605..7307a7d4 100644 --- a/docs/framework/angular/modify.md +++ b/docs/framework/angular/changeset.md @@ -1,9 +1,13 @@ -# AngularJS: Change Sets +# AngularJS: Changesets + +!!! caution "Work in progress" + + This documentation is still a work in progress. The [Quick Start](quickstart.md) and [Loader](loader.md) provide examples of creating *new* screens. But what if you need to alter an *existing* screen? -CiviCRM allows third-parties to define *change sets* which programmatically -manipulate Angular content (before sending it to the client). +CiviCRM allows third-parties to define *changesets* which programmatically +manipulate Angular content before sending it to the client. ## Background @@ -33,4 +37,8 @@ function mailwords_civicrm_alterAngular(\Civi\Angular\Manager $angular) { } ``` -cv ang:html:show \ No newline at end of file +``` +cv ang:html:list +cv ang:html:show +cv ang:html:show --diff +``` diff --git a/docs/framework/angular/index.md b/docs/framework/angular/index.md index 59fd81ce..d20fb051 100644 --- a/docs/framework/angular/index.md +++ b/docs/framework/angular/index.md @@ -98,4 +98,4 @@ the discussion if you have a beginner-level understanding of both. * [Quick Start](quickstart.md): How to create a new screen in the CiviCRM-Angular UI * [File Names](files.md): How AngularJS files are named in `civicrm-core` and `civix` * [Loader](loader.md): How to find and load JS/CSS files for CiviCRM-Angular - * [Modifications](modify.md): How a third-party can modify the content of a CiviCRM-Angular UI + * [Changesets](changeset.md): How a third-party can modify the content of a CiviCRM-Angular UI diff --git a/mkdocs.yml b/mkdocs.yml index 51c0f742..1553482c 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -48,7 +48,7 @@ pages: - "AngularJS: Quick Start": framework/angular/quickstart.md - "AngularJS: File Names": framework/angular/files.md - "AngularJS: Loader": framework/angular/loader.md - - "AngularJS: Change Sets": framework/angular/modify.md + - "AngularJS: Changesets": framework/angular/changeset.md - Asset Builder: framework/asset-builder.md # Bootstrap: /framework/bootstrap.md # Cache: /framework/cache.md From 0a33f1262d5e494c341738fc16532954b4b283d2 Mon Sep 17 00:00:00 2001 From: Tim Otten Date: Tue, 20 Jun 2017 14:59:33 -0700 Subject: [PATCH 17/17] asset-builder.md - Copy-edits --- docs/framework/asset-builder.md | 39 +++++++++++++++++---------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/docs/framework/asset-builder.md b/docs/framework/asset-builder.md index 2aa66ff4..b77e46fb 100644 --- a/docs/framework/asset-builder.md +++ b/docs/framework/asset-builder.md @@ -1,13 +1,17 @@ # Asset Builder The `AssetBuilder` manages lazily-generated assets, such as aggregated -JS/CSS files. Lazy assets are simultaneously: +JS/CSS files. The first time you request a lazy asset, the `AssetBuilder` +fires a hook which builds the content. The content is stored in a cache +file, and subsequent requests use the cache file. + +Lazy assets are simultaneously dynamic and static: * __Dynamic__: They vary depending on the current system configuration. - You cannot lock-in a correct version of the asset because each - installation may need a slightly different version. - * __Static__: Within a given site, the asset is not likely to change. - It could even be served directly by the web-server (without the overhead + You cannot lock-in a singular version of the asset because each + deployment may need a slightly different version. + * __Static__: Within a given deployment, the asset is not likely to change. + It can even be served directly by the web-server (without the overhead of PHP/CMS/Civi bootstrap) !!! note "Example: Batch loading Angular HTML" @@ -16,19 +20,16 @@ JS/CSS files. Lazy assets are simultaneously: bigger file and reduce the number of round-trip HTTP requests. This asset is _dynamic_ because extensions can add or modify HTML - templates. Two different sites would have different HTML templates - (depending on the mix of extensions). Yet, within a particular - site/configuration, the content is _static_ because the HTML doesn't + templates. Two different deployments would have different HTML + templates (depending on the mix of extensions). Yet, within a + particular deployment, the content is _static_ because the HTML doesn't actually change at runtime. -The `AssetBuilder` addresses this use-case with *lazy* building. Assets are -not distributed as part of `git` or `tar.gz`. Rather, the first time you -use an asset, it fires off a hook which builds the content. The content is -stored in a cache file. - !!! tip "Tip: Caching and development" If you are developing or patching assets, then the caching behavior may - get distracting. To bypass the cache, enable **debug mode**. + get distracting. To bypass the cache, navigate to + __Administer > System Settings > Debugging__ and enable debugging. + ## Usage: Simple asset @@ -112,12 +113,12 @@ function mymodule_civicrm_buildAsset($asset, $params, &$mimeType, &$content) { ``` !!! note "Note: Parmaters and caching" - Each combination of ($asset,$params) will be cached separately. + Each combination of (`$asset`,`$params`) will be cached separately. -!!! tip "Tip: Economize parameter size" - In debug mode, all parameters are passed as part of the URL. `AssetBuilder` - will try to compress them, but fundamentally: long `$params` will produce - long URLs. +!!! tip "Tip: Economize on parameters" + In debug mode, all parameters are passed as part of the URL. + `AssetBuilder` will try to compress them, but it can only do so much. + Fundamentally, long `$params` will produce long URLs. ## Other considerations