diff --git a/docs/framework/angular/changeset.md b/docs/framework/angular/changeset.md new file mode 100644 index 0000000000000000000000000000000000000000..7307a7d414cac92ad441906d8c890ab119e8a6f9 --- /dev/null +++ b/docs/framework/angular/changeset.md @@ -0,0 +1,44 @@ +# 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 *changesets* 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 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(' + <div crm-ui-field="{name: \'subform.mailwords\', title: ts(\'Keywords\')}"> + <input crm-ui-id="subform.mailwords" class="crm-form-text" name="mailwords" ng-model="mailing.template_options.keywords"> + </div> + '); + }); + $angular->add($changeSet); +} +``` + +``` +cv ang:html:list +cv ang:html:show <file> +cv ang:html:show <file> --diff +``` diff --git a/docs/framework/angular/files.md b/docs/framework/angular/files.md new file mode 100644 index 0000000000000000000000000000000000000000..f7acd243556de7842b5461503ebbf375e6a6e239 --- /dev/null +++ b/docs/framework/angular/files.md @@ -0,0 +1,73 @@ +# 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 *routes*. + +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 + named according to convention. + + One file, `ang/{mymodule}.ang.php`, provides instructions for the + file-loader. It lists any files which match the naming + convention. + +!!! 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 + +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}.md` - Developer documentation about the module (if applicable). + +## Full convention + +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 (if applicable). + * `ang/{mymodule}.md` - Developer documentation about the module (if applicable). + +__Directive Files__ + + * `ang/{mymodule}/{FooBar}.js` - The declaration and logic for a directive named `mymoduleFooBar` or `<div mymodule-foo-bar>`. + * `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`.) + + * `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). + * `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'}`. diff --git a/docs/framework/angular/index.md b/docs/framework/angular/index.md new file mode 100644 index 0000000000000000000000000000000000000000..d20fb0515d453488c3aa9ae3806105b47318a056 --- /dev/null +++ b/docs/framework/angular/index.md @@ -0,0 +1,101 @@ +# 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: + + * __Build/Activation__: The process of building or activating modules + should meet administrators' expectations. It should be managed by the + PHP application. (This means that you won't see `gulp` or `grunt` + orchestrating the final build -- because PHP logic fills that role.) + * __Frontend Code uses Angular (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.) + * __Backend Code uses Civi API (PHP)__: 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 +<!-- URL: https://example.org/civicrm/a --> + 1: <html> + 2: <head> + 3: <link rel="stylesheet" href="**all the CSS files**" /> + 4: <script type="text/javascript" src="**all the Javascript files**"></script> + 5: <script type="text/javascript">var CRM = {**prefetched settings/data**};</script> + 6: </head> + 7: <body> + 8: <div>...site wide header...</div> + 9: <div ng-app="crmApp"></div> +10: <div>...site wide footer...</div> +11: </body> +12: </html> +``` + +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 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 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 +"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/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 +the discussion if you have a beginner-level understanding of both. + + +## Additional pages + + * [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 + * [Changesets](changeset.md): How a third-party can modify the content of a CiviCRM-Angular UI diff --git a/docs/framework/angular/loader.md b/docs/framework/angular/loader.md new file mode 100644 index 0000000000000000000000000000000000000000..3ba2473c86740cbeebe5193060224a1d80ab0d15 --- /dev/null +++ b/docs/framework/angular/loader.md @@ -0,0 +1,186 @@ +# AngularJS: Loader + +What happens when a user visits a CiviCRM-Angular page? For example, let's +consider this URL: + + * `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 -- such as + `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. + +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. + +!!! 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 + and are still flagged experimental. + +## Library of 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 +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 | +...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. This may reveal permission-dependent modules. Ex: + + ``` + $ cv ang:module:list --user=admin + ``` + +Under-the-hood, this library of modules is built 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'), + ); +} +``` + +!!! 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 + +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(); +$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. 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(...)` [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`. + +!!! note "How does `load()` output the `<script>` tag(s)?" + `load()` uses [CRM_Core_Resources](https://wiki.civicrm.org/confluence/display/CRMDOC/Resource+Reference) + to register JS/CSS files. + +## Other 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 +<div ng-app="crmCaseType"> + <div ng-view=""></div> +</div> +``` + +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" + + In the example, we created a new, standalone page. But you can use + `AngularLoader` in other ways -- eg, you might listen for + `hook_civicrm_pageRun` and embed Angular onto a pre-existing, + non-Angular page. Some extensions do this -- though it remains to be + seen whether this is *wise*. diff --git a/docs/framework/angular/quickstart.md b/docs/framework/angular/quickstart.md new file mode 100644 index 0000000000000000000000000000000000000000..9a52eb498442a7d516939a21f519f93c45096623 --- /dev/null +++ b/docs/framework/angular/quickstart.md @@ -0,0 +1,121 @@ +# 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. + +!!! 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`, provides metadata for the PHP-based +file-loader, e.g. + +```php +return array( + '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(), +); +``` + +The second file, `ang/aboutme.js`, provides metadata for the JS-based +Angular runtime, e.g. + +```js + angular.module('aboutme', [ + 'ngRoute', 'crmUi', 'crmUtil' + ]); +``` + +!!! tip "Tip: angular.module() and CRM.angRequires()" + The list of dependencies is declared once in PHP and once in JS. To + 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: + +``` +$ 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](/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. diff --git a/docs/framework/asset-builder.md b/docs/framework/asset-builder.md new file mode 100644 index 0000000000000000000000000000000000000000..b77e46fbc27aae6dbc4f51080dc5a66571d933fc --- /dev/null +++ b/docs/framework/asset-builder.md @@ -0,0 +1,143 @@ +# Asset Builder + +The `AssetBuilder` manages lazily-generated assets, such as aggregated +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 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" + When visiting the AngularJS base page `civicrm/a`, one needs to load a + 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 + 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. + +!!! tip "Tip: Caching and development" + If you are developing or patching assets, then the caching behavior may + get distracting. To bypass the cache, navigate to + __Administer > System Settings > Debugging__ and enable debugging. + + +## 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 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 + +!!! 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 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: 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 + 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_angularModules.md b/docs/hooks/hook_civicrm_angularModules.md index 21dd0901557e4cafd18886de074eff54f25a5241..0ce3c933ba614c9a61f398d08411ebaf51bf8483 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'), diff --git a/docs/hooks/hook_civicrm_buildAsset.md b/docs/hooks/hook_civicrm_buildAsset.md new file mode 100644 index 0000000000000000000000000000000000000000..18c5ffa026d889a3098d71ddc2a95f2694aaab67 --- /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 diff --git a/mkdocs.yml b/mkdocs.yml index d378847ca21080e4168cbd7df3c46871bce4b6b3..1553482c7cabaf8823b5183be150bc454adc988c 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -43,6 +43,13 @@ pages: - Troubleshooting: extensions/troubleshooting.md - Advanced patterns: extensions/advanced.md - Framework Reference: + - AngularJS: + - "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: Changesets": framework/angular/changeset.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