Merge pull request #10740 from eileenmcnaughton/def_country
[civicrm-core.git] / ang / crmRouteBinder.md
1 # crmRouteBinder
2
3 Live-update the URL to stay in sync with controller data.
4
5 ## Example
6
7 ```js
8 angular.module('sandbox').config(function($routeProvider) {
9 $routeProvider.when('/example-route', {
10 reloadOnSearch: false,
11 template: '<input ng-model="filters.foo" />',
12 controller: function($scope) {
13 $scope.$bindToRoute({
14 param: 'f',
15 expr: 'filters',
16 default: {foo: 'default-value'}
17 });
18 }
19 });
20 });
21 ```
22
23 Things to try out:
24
25 * Navigate to `#/example-route`. Observe that the URL automatically
26 updates to `#/example-route?f={"foo":"default-value"}`.
27 * Edit the content in the `<input>` field. Observe that the URL changes.
28 * Initiate a change in the browser -- by editing the URL bar or pressing
29 the "Back" button. The page should refresh.
30
31 ## Functions
32
33 * `$scope.$bindToRoute(options)`
34 * The `options` object should contain keys:
35 * `expr` (string): The name of a scoped variable to sync.
36 * `param` (string): The name of a query-parameter to sync. (If the `param` is included in the URL, it will initialize the expr.)
37 * `format` (string): The type of data to put in `param`. May be one of:
38 * `json` (default): The `param` is JSON, and the `expr` is a decoded object.
39 * `raw`: The `param` is string, and the `expr` is a string.
40 * `int`: the `param` is an integer-like string, and the expr is an integer.
41 * `bool`: The `param` is '0'/'1', and the `expr` is false/true.
42 * `default` (object): The default data. (If the `param` is not included in the URL, it will initialize the expr.)
43
44 ## Suggested Usage
45
46 `$bindToRoute()` was written for a complicated routing scenario with
47 multiple parameters, e.g. `caseFilters:Object`, `caseId:Int`, `tab:String`,
48 `activityFilters:Object`, `activityId:Int`. If you're use-case is one or
49 two scalar values, then stick to vanilla `ngRoute`. This is only for
50 complicated scenarios.
51
52 If you are using `$bindToRoute()`, should you split up parameters -- with
53 some using `ngRoute` and some using `$bindToRoute()`? I'd pick one style
54 and stick to it. You're in a complex use-case where `$bindToRoute()` makes
55 sense, then you already need to put thought into the different
56 flows/input-combinations. Having two technical styles will increase the
57 mental load.
58
59 A goal of `bindToRoute()` is to accept inputs interchangably from the URL or
60 HTML fields. Using `ngRoute`'s `resolve:` option only addresses the URL
61 half. If you want one piece of code handling all inputs the same way, you
62 should avoid `resolve:` and instead write a controller focused on
63 orchestrating I/O:
64
65 ```js
66 angular.module('sandbox').config(function($routeProvider) {
67 $routeProvider.when('/example-route', {
68 reloadOnSearch: false,
69 template:
70 '<div filter-toolbar-a="filterSetA" />'
71 + '<div filter-toolbar-b="filterSetB" />'
72 + '<div filter-toolbar-c="filterSetC" />'
73 + '<div data-set-a="dataSetA" />'
74 + '<div data-set-b="dataSetB" />'
75 + '<div data-set-c="dataSetC" />',
76 controller: function($scope) {
77 $scope.$bindToRoute({expr:'filterSetA', param:'a', default:{}});
78 $scope.$watchCollection('filterSetA', function(){
79 crmApi(...).then(function(...){
80 $scope.dataSetA = ...;
81 });
82 });
83
84 $scope.$bindToRoute({expr:'filterSetB', param:'b', default:{}});
85 $scope.$watchCollection('filterSetB', function(){
86 crmApi(...).then(function(...){
87 $scope.dataSetB = ...;
88 });
89 });
90
91 $scope.$bindToRoute({expr:'filterSetC', param:'c', default:{}});
92 $scope.$watchCollection('filterSetC', function(){
93 crmApi(...).then(function(...){
94 $scope.dataSetC = ...;
95 });
96 });
97 }
98 });
99 });
100 ```
101
102 (This example is a little more symmetric than a real one -- because the A,
103 B, and C datasets look independent. In practice, their loading may be
104 intermingled.)