├── .bowerrc
├── .codeclimate.yml
├── .editorconfig
├── .eslintrc
├── .gitignore
├── .jscsrc
├── .yo-rc.json
├── LICENSE.md
├── README.md
├── bower.json
├── demo
├── app
│ ├── components
│ │ ├── attribute-field
│ │ │ ├── attribute-field.directive.js
│ │ │ ├── attribute-field.html
│ │ │ └── attribute-field.scss
│ │ ├── collection
│ │ │ ├── collection.directive.js
│ │ │ ├── collection.html
│ │ │ └── collection.scss
│ │ ├── error-message
│ │ │ ├── error-list.directive.js
│ │ │ ├── error-list.html
│ │ │ ├── error-message.directive.js
│ │ │ └── error-message.html
│ │ ├── object
│ │ │ ├── object-relationships.directive.js
│ │ │ ├── object-relationships.html
│ │ │ ├── object.directive.js
│ │ │ └── object.html
│ │ ├── promise-button
│ │ │ ├── promise-button.directive.js
│ │ │ └── promise-button.module.js
│ │ ├── search
│ │ │ ├── search-collection.filter.js
│ │ │ ├── search-object.filter.js
│ │ │ ├── search-polymorphic.html
│ │ │ ├── search.directive.js
│ │ │ ├── search.html
│ │ │ └── search.scss
│ │ └── utils
│ │ │ ├── bytes.filter.js
│ │ │ ├── semantic-ui.module.js
│ │ │ └── to-title-case.js
│ ├── footer
│ │ ├── footer.html
│ │ └── footer.scss
│ ├── frame
│ │ ├── frame.controller.js
│ │ ├── frame.html
│ │ └── hello.html
│ ├── index.constants.js
│ ├── index.js
│ ├── index.run.js
│ ├── index.scss
│ ├── request
│ │ ├── all.controller.js
│ │ ├── all.html
│ │ ├── get.controller.js
│ │ ├── get.html
│ │ └── request.controller.js
│ ├── resources
│ │ ├── jobs.factory.js
│ │ ├── laser-guns.factory.js
│ │ ├── locations.factory.js
│ │ ├── planets.factory.js
│ │ ├── power-armors.factory.js
│ │ ├── robot-models.factory.js
│ │ ├── robots.factory.js
│ │ ├── spaceship-models.factory.js
│ │ └── spaceships.factory.js
│ ├── sidebar
│ │ ├── sidebar.directive.js
│ │ └── sidebar.html
│ └── vendor.scss
├── assets
│ └── images
│ │ ├── .keep
│ │ ├── mario-down.gif
│ │ └── mario-up.gif
├── favicon.gif
└── index.html
├── dist-demo
├── assets
│ └── images
│ │ ├── mario-down.gif
│ │ └── mario-up.gif
├── favicon.gif
├── fonts
│ ├── icons.eot
│ ├── icons.svg
│ ├── icons.ttf
│ ├── icons.woff
│ └── icons.woff2
├── index.html
├── maps
│ ├── scripts
│ │ ├── app-4969e92e70.js.map
│ │ ├── lib-f2c7ec66be.js.map
│ │ └── vendor-5f48f0c060.js.map
│ └── styles
│ │ ├── app-3086bb8113.css.map
│ │ └── vendor-5ff773b98f.css.map
├── scripts
│ ├── app-4969e92e70.js
│ ├── lib-f2c7ec66be.js
│ └── vendor-5f48f0c060.js
└── styles
│ ├── app-3086bb8113.css
│ └── vendor-5ff773b98f.css
├── dist
├── angular-jsonapi.js
├── angular-jsonapi.js.map
├── angular-jsonapi.min.js
└── angular-jsonapi.min.js.map
├── gulp
├── .eslintrc
├── build.js
├── conf.js
├── e2e-tests.js
├── inject.js
├── scripts.js
├── server.js
├── styles.js
├── unit-tests.js
└── watch.js
├── gulpfile.js
├── karma.conf.js
├── package.json
├── protractor.conf.js
└── src
├── collection
├── collection.factory.js
└── collection.factory.spec.js
├── errors
├── errors-manager
│ ├── errors-manager.factory.js
│ └── errors-manager.factory.spec.js
├── source-error
│ ├── source-error.factory.js
│ └── source-error.factory.spec.js
└── validation-error
│ ├── validation-error.factory.js
│ └── validation-error.factory.spec.js
├── jsonapi.config.js
├── jsonapi.module.js
├── jsonapi.provider.js
├── jsonapi.provider.spec.js
├── model
├── abstract-model
│ ├── abstract-model.js
│ └── abstract-model.spec.js
├── model-factory.factory.js
├── model-factory.factory.spec.js
├── model-form
│ ├── model-form.factory.js
│ └── model-form.spec.js
└── model-linker
│ ├── model-linker.js
│ └── model-linker.spec.js
├── resource
├── resource-cache
│ ├── resource-cache.factory.js
│ └── resource-cache.factory.spec.js
├── resource.factory.js
└── resource.factory.spec.js
├── schema
├── schema-link.factory.js
├── schema-link.factory.spec.js
├── schema.factory.js
└── schema.factory.spec.js
├── sources
├── local
│ ├── source-local.decorator.js
│ ├── source-local.factory.js
│ └── source-local.module.js
├── parse
│ ├── source-parse.decorator.js
│ ├── source-parse.factory.js
│ └── source-parse.module.js
├── rest
│ ├── source-rest.decorator.js
│ ├── source-rest.factory.js
│ └── source-rest.module.js
└── source-prototype.js
├── synchronizers
├── synchronizer-prototype.js
├── synchronizer-simple.factory.js
└── synchronizer-simple.factory.spec.js
└── utils
├── all-settled.js
├── kebab-case.js
├── lazy-property.js
└── named-function.js
/.bowerrc:
--------------------------------------------------------------------------------
1 | {
2 | "directory": "bower_components"
3 | }
4 |
--------------------------------------------------------------------------------
/.codeclimate.yml:
--------------------------------------------------------------------------------
1 | languages:
2 | JavaScript: true
3 | exclude_paths:
4 | - "dist/*"
5 | - "dist-demo/*"
6 | - "demo/*"
7 | - "gulp/*"
8 | - "karma.conf.js"
9 | - "gulpfile.js"
10 | - "protractor.conf.js"
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | charset = utf-8
9 | trim_trailing_whitespace = true
10 | insert_final_newline = true
11 |
12 | [*.md]
13 | trim_trailing_whitespace = false
14 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "eslint:recommended",
3 | "plugins": ["angular"],
4 | "env": {
5 | "browser": true,
6 | "jasmine": true
7 | },
8 | "globals": {
9 | "angular": true,
10 | "module": true,
11 | "inject": true
12 | },
13 | "rules": {
14 | "angular/definedundefined": 0,
15 | "angular/controller-as": 0,
16 | "angular/controller-as-route": 0,
17 | "angular/controller-as-vm": [0, "vm"]
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | bower_components/
3 | .sass-cache/
4 | .tmp/
5 | .sublime-gulp.cache
--------------------------------------------------------------------------------
/.jscsrc:
--------------------------------------------------------------------------------
1 | {
2 | "preset": "airbnb",
3 | "requireTrailingComma": null
4 | }
--------------------------------------------------------------------------------
/.yo-rc.json:
--------------------------------------------------------------------------------
1 | {
2 | "generator-gulp-angular": {
3 | "version": "0.11.0",
4 | "props": {
5 | "angularVersion": "~1.3.4",
6 | "angularModules": [],
7 | "jQuery": {
8 | "key": "jquery2"
9 | },
10 | "resource": {
11 | "key": "none",
12 | "module": null
13 | },
14 | "router": {
15 | "key": "ui-router",
16 | "module": "ui.router"
17 | },
18 | "ui": {
19 | "key": "bootstrap",
20 | "module": null
21 | },
22 | "bootstrapComponents": {
23 | "key": "ui-bootstrap",
24 | "module": "ui.bootstrap"
25 | },
26 | "cssPreprocessor": {
27 | "key": "node-sass",
28 | "extension": "scss"
29 | },
30 | "jsPreprocessor": {
31 | "key": "none",
32 | "extension": "js",
33 | "srcExtension": "js"
34 | },
35 | "htmlPreprocessor": {
36 | "key": "none",
37 | "extension": "html"
38 | },
39 | "foundationComponents": {
40 | "name": null,
41 | "version": null,
42 | "key": null,
43 | "module": null
44 | },
45 | "paths": {
46 | "src": "src",
47 | "dist": "dist",
48 | "e2e": "e2e",
49 | "tmp": ".tmp"
50 | }
51 | }
52 | }
53 | }
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular-jsonapi",
3 | "version": "1.0.0-alpha.7",
4 | "main": "dist/angular-jsonapi.js",
5 | "dependencies": {
6 | "angular": "1.4.x",
7 | "angular-uuid4": "~0.3.0",
8 | "moment": "~2.10.6",
9 | "pluralize": "~1.1.5",
10 | "validate": "~0.9.0",
11 | "parse": "~1.6.7"
12 | },
13 | "ignore": [
14 | "demo/",
15 | "demo-dist/"
16 | ],
17 | "devDependencies": {
18 | "angular-recursion": "~1.0.5",
19 | "angular-ui-router": "~0.2.15",
20 | "faker": "https://github.com/Marak/faker.js.git#~2.1.2",
21 | "jquery": "~2.1.4",
22 | "json-formatter": "~0.2.7",
23 | "lodash": "~3.10.1",
24 | "ng-clip": "~0.2.6",
25 | "semantic": "semantic-ui#~2.1.3",
26 | "favico.js": "~0.3.10"
27 | },
28 | "resolutions": {
29 | "jquery": "~2.1.4",
30 | "angular": "1.4.x"
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/demo/app/components/attribute-field/attribute-field.directive.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | 'use strict';
3 |
4 | angular.module('promise-button')
5 | .directive('attributeField', attributeField);
6 |
7 | function attributeField() {
8 | return {
9 | restrict: 'A',
10 | templateUrl: 'app/components/attribute-field/attribute-field.html',
11 | scope: {
12 | object: '=',
13 | key: '='
14 | }
15 | };
16 | }
17 | })();
18 |
--------------------------------------------------------------------------------
/demo/app/components/attribute-field/attribute-field.html:
--------------------------------------------------------------------------------
1 |
5 |
6 |
12 |
13 |
14 |
15 | - {{error.message}}
16 |
17 |
18 |
--------------------------------------------------------------------------------
/demo/app/components/attribute-field/attribute-field.scss:
--------------------------------------------------------------------------------
1 | .ui.form .field.success .input, .ui.form .field.success label,
2 | .ui.form .fields.success .field .input,
3 | .ui.form .fields.success .field label {
4 | color: #1a531b;
5 | }
6 |
7 | .ui.form .field.success input:not([type]), .ui.form .field.success input[type=text], .ui.form .field.success input[type=email], .ui.form .field.success input[type=search], .ui.form .field.success input[type=password], .ui.form .field.success input[type=date], .ui.form .field.success input[type=datetime-local], .ui.form .field.success input[type=tel], .ui.form .field.success input[type=time], .ui.form .field.success input[type=url], .ui.form .field.success input[type=number], .ui.form .field.success select, .ui.form .field.success textarea, .ui.form .fields.success .field input:not([type]), .ui.form .fields.success .field input[type=text], .ui.form .fields.success .field input[type=email], .ui.form .fields.success .field input[type=search], .ui.form .fields.success .field input[type=password], .ui.form .fields.success .field input[type=date], .ui.form .fields.success .field input[type=datetime-local], .ui.form .fields.success .field input[type=tel], .ui.form .fields.success .field input[type=time], .ui.form .fields.success .field input[type=url], .ui.form .fields.success .field input[type=number], .ui.form .fields.success .field select, .ui.form .fields.success .field textarea {
8 | background: #fcfff5;
9 | border-color: #a3c293;
10 | color: #2c662d;
11 | border-radius: '';
12 | box-shadow: none;
13 | }
--------------------------------------------------------------------------------
/demo/app/components/collection/collection.directive.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | 'use strict';
3 |
4 | angular.module('angularJsonapiExample')
5 | .directive('angularJsonapiCollection', collection);
6 |
7 | function collection(RecursionHelper) {
8 | return {
9 | restrict: 'E',
10 | templateUrl: 'app/components/collection/collection.html',
11 | scope: {
12 | collection: '=data'
13 | },
14 | compile: RecursionHelper.compile,
15 | controller: function($scope, $interval, $jsonapi, $location, $state) {
16 | $interval(function() {
17 | $scope.updateDiff = (Date.now() - $scope.collection.updatedAt) / 1000;
18 | }, 100);
19 |
20 | $scope.objectKeys = objectKeys;
21 |
22 | $scope.newObjects = [];
23 |
24 | var filters = $scope.collection.params.filter || {};
25 | var params = $jsonapi.sourceRest.encodeParams({filter: filters});
26 | $location.search(params);
27 |
28 | $scope.close = close;
29 | $scope.clear = clear;
30 |
31 | $scope.add = add;
32 |
33 | $scope.filter = filter;
34 | $scope.addFilter = addFilter;
35 | $scope.removeFilter = removeFilter;
36 | $scope.clearFilter = clearFilter;
37 | $scope.filtersArray = [];
38 |
39 | angular.forEach($scope.collection.params.filter, function(filter, filterKey) {
40 | angular.forEach(filter, function(filterValue) {
41 | addFilter(filterKey, filterValue);
42 | });
43 | });
44 |
45 | function close() {
46 | $scope.$broadcast('close');
47 | }
48 |
49 | function clear() {
50 | $jsonapi.clearCache();
51 | }
52 |
53 | function add() {
54 | $scope.newObjects.push($scope.collection.resource.initialize());
55 | }
56 |
57 | function addFilter(filterKey, filterValue) {
58 | $scope.filtersArray.push({key: filterKey, value: filterValue});
59 | }
60 |
61 | function removeFilter(index) {
62 | $scope.filtersArray.splice(index, 1);
63 | }
64 |
65 | function filter() {
66 | var filters = {};
67 |
68 | angular.forEach($scope.filtersArray, function(filter) {
69 | filters[filter.key] = filters[filter.key] || [];
70 | filters[filter.key].push(filter.value);
71 | });
72 |
73 | $location.search($jsonapi.sourceRest.encodeParams({filter: filters}));
74 | $state.reload();
75 | }
76 |
77 | function clearFilter() {
78 | $location.search({});
79 | $state.reload();
80 | }
81 |
82 | function objectKeys(object) {
83 | if (angular.isObject(object)) {
84 | return Object.keys(object).length;
85 | } else {
86 | return 0;
87 | }
88 | }
89 | }
90 | };
91 | }
92 |
93 | })();
94 |
--------------------------------------------------------------------------------
/demo/app/components/collection/collection.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{collection.type}}
4 |
5 |
6 | Loading {{collection.loadingCount}}:{{collection.loading}}
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
19 |
23 |
24 |
28 |
29 |
34 |
35 |
36 |
37 |
38 |
Key
39 |
48 |
49 |
54 |
59 |
60 |
Synchronizing...
61 |
Synchronized {{updateDiff}} s. ago
70 |
71 |
72 |
73 |
74 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
New objects
110 |
111 |
116 |
117 |
118 |
119 |
120 |
121 |
All objects
122 |
Filtered objects
123 |
124 |
128 | Loading collection for the first time
129 |
130 |
131 |
135 | Collection not synchronized, refresh it to fetch data
136 |
137 |
138 |
143 |
144 |
145 |
Close all models
146 |
Clear cache
147 |
148 |
--------------------------------------------------------------------------------
/demo/app/components/collection/collection.scss:
--------------------------------------------------------------------------------
1 | .collection {
2 | margin-top: 2rem;
3 | position: relative;
4 | min-height: 20rem;
5 | }
6 |
--------------------------------------------------------------------------------
/demo/app/components/error-message/error-list.directive.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | 'use strict';
3 |
4 | angular.module('angularJsonapiExample')
5 | .directive('errorList', errorList);
6 |
7 | function errorList() {
8 | return {
9 | restrict: 'E',
10 | templateUrl: 'app/components/error-message/error-list.html',
11 | scope: {
12 | errors: '=data'
13 | },
14 | controller: controller
15 | };
16 |
17 | function controller($scope) {
18 | $scope.isObject = angular.isObject;
19 | $scope.isString = angular.isString;
20 | }
21 | }
22 |
23 | })();
24 |
--------------------------------------------------------------------------------
/demo/app/components/error-message/error-list.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
{{errorManager.description}}
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
{{error.message}}
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/demo/app/components/error-message/error-message.directive.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | 'use strict';
3 |
4 | angular.module('angularJsonapiExample')
5 | .directive('errorMessage', errorMessage);
6 |
7 | function errorMessage() {
8 | return {
9 | restrict: 'E',
10 | templateUrl: 'app/components/error-message/error-message.html',
11 | scope: {
12 | errors: '=data'
13 | }
14 | };
15 | }
16 |
17 | })();
18 |
--------------------------------------------------------------------------------
/demo/app/components/error-message/error-message.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/demo/app/components/object/object-relationships.directive.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | 'use strict';
3 |
4 | angular.module('angularJsonapiExample')
5 | .directive('angularJsonapiObjectRelationships', objectRelationships);
6 |
7 | function objectRelationships(RecursionHelper) {
8 | return {
9 | restrict: 'E',
10 | templateUrl: 'app/components/object/object-relationships.html',
11 | scope: {
12 | object: '=data'
13 | },
14 | compile: RecursionHelper.compile,
15 | controller: controller
16 | };
17 |
18 | function controller($scope) {
19 | $scope.isArray = angular.isArray;
20 | $scope.emptyRelationship = emptyRelationship;
21 | $scope.form = $scope.object.parent !== undefined;
22 |
23 | function emptyRelationship(relationship) {
24 | return relationship === undefined ||
25 | relationship === null ||
26 | angular.isArray(relationship) &&
27 | relationship.length === 0;
28 | }
29 | }
30 | }
31 |
32 | })();
33 |
--------------------------------------------------------------------------------
/demo/app/components/object/object-relationships.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
Form: {{object.schema.relationships[relationshipName].model}}
5 | polymorphic
6 |
7 |
8 |
15 |
16 |
21 |
22 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
Relationship data locked for new object
36 |
This object is new, you can only add relationships to its form.
37 |
38 |
39 |
40 |
Relationship data undefined
41 |
The relationship data hasn't been fetched yet, refresh the model to fetch it.
42 |
43 |
44 |
45 |
Has one relationship null
46 |
Set something as relationship object.
47 |
48 |
49 |
50 |
Has many relationship empty
51 |
Add something to the relationship array.
52 |
53 |
54 |
61 |
62 |
68 |
69 |
--------------------------------------------------------------------------------
/demo/app/components/object/object.directive.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | 'use strict';
3 |
4 | angular.module('angularJsonapiExample')
5 | .directive('angularJsonapiObject', object);
6 |
7 | function object(RecursionHelper) {
8 | return {
9 | restrict: 'E',
10 | templateUrl: 'app/components/object/object.html',
11 | scope: {
12 | object: '=data',
13 | unlink: '&',
14 | nested: '='
15 | },
16 | require: '^angularJsonapiObject',
17 | compile: RecursionHelper.compile,
18 | controller: controller
19 | };
20 |
21 | function controller($scope, $interval) {
22 | var interval;
23 |
24 | $scope.showMore = false;
25 | $scope.isArray = angular.isArray;
26 |
27 | $scope.$watch('showMore', toggleTimmer);
28 |
29 | $scope.$on('close', function() {
30 | $scope.showMore = false;
31 | });
32 |
33 | $scope.equals = angular.equals;
34 |
35 | function toggleTimmer(value) {
36 | if (value === true) {
37 | $scope.updateDiff = (Date.now() - $scope.object.updatedAt) / 1000;
38 | interval = $interval(function() {
39 | $scope.updateDiff = (Date.now() - $scope.object.updatedAt) / 1000;
40 | }, 100);
41 | } else if (value === false) {
42 | $interval.cancel(interval);
43 | }
44 | }
45 | }
46 | }
47 |
48 | })();
49 |
--------------------------------------------------------------------------------
/demo/app/components/object/object.html:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | Loading {{object.loadingCount}}:{{object.loading}} Saving {{object.savingCount}}:{{object.saving}}
7 |
{{object.toString()}}
8 | {{object.data.type}}:{{object.data.id}}
9 |
10 |
11 |
19 |
26 |
33 |
41 |
49 |
55 |
61 |
62 |
Not synchronized
63 |
68 | Synchronized
69 |
70 |
80 | Synchronized {{updateDiff}} s. ago
81 |
82 |
83 |
84 |
85 |
86 |
87 |
91 |
92 |
93 |
94 |
95 |
Data
96 |
102 | Show relationships
103 |
104 |
105 |
106 |
107 |
108 |
{{attributeName | toTitleCase}}
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
Form
126 |
132 | Show relationships
133 |
134 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
--------------------------------------------------------------------------------
/demo/app/components/promise-button/promise-button.directive.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | 'use strict';
3 |
4 | angular.module('promise-button')
5 | .directive('promiseButton', promiseButton);
6 |
7 | function promiseButton($parse, $q) {
8 | return {
9 | restrict: 'A',
10 | priority: -1,
11 | compile: compile
12 | };
13 |
14 | function compile($element, attr) {
15 | var loadingClass = attr.loadingClass || 'loading';
16 | var errorClass = attr.errorClass || 'negative';
17 | var successClass = attr.successClass || 'positive';
18 |
19 | var fn = $parse(attr.ngClick, null, true);
20 | return function ngEventHandler(scope, element) {
21 | element.on('click', onClick);
22 |
23 | function onClick(event) {
24 | event.preventDefault();
25 | event.stopImmediatePropagation();
26 | element.off('click');
27 |
28 | var callback = function() {
29 | element.addClass(loadingClass);
30 | element.removeClass(errorClass);
31 | element.removeClass(successClass);
32 | $q.resolve(fn(scope, {$event:event})).then(resolve, reject);
33 | };
34 |
35 | function resolve(response) {
36 | element.removeClass(loadingClass);
37 | element.addClass(successClass);
38 | element.on('click', onClick);
39 |
40 | return response;
41 | }
42 |
43 | function reject(response) {
44 | element.removeClass(loadingClass);
45 | element.addClass(errorClass);
46 | element.on('click', onClick);
47 |
48 | return response;
49 | }
50 |
51 | scope.$apply(callback);
52 | }
53 | };
54 | }
55 | }
56 | })();
57 |
--------------------------------------------------------------------------------
/demo/app/components/promise-button/promise-button.module.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | 'use strict';
3 |
4 | angular.module('promise-button', []);
5 | })();
6 |
--------------------------------------------------------------------------------
/demo/app/components/search/search-collection.filter.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | 'use strict';
3 |
4 | angular.module('angularJsonapiExample')
5 | .filter('angularJsonapiSearchCollection', searchFilter);
6 |
7 | function searchFilter() {
8 | return function(items, search) {
9 | if (!search) {
10 | return items;
11 | }
12 |
13 | var results = {};
14 | var words = search.split(' ');
15 | angular.forEach(items, function(value, key) {
16 | if (key.indexOf(words[0]) > -1) {
17 | results[key] = value;
18 | }
19 | });
20 |
21 | return results;
22 | };
23 | }
24 |
25 | })();
26 |
--------------------------------------------------------------------------------
/demo/app/components/search/search-object.filter.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | 'use strict';
3 |
4 | angular.module('angularJsonapiExample')
5 | .filter('angularJsonapiSearchObject', searchFilter);
6 |
7 | function searchFilter($jsonapi) {
8 | var names = $jsonapi.listResources();
9 |
10 | return function(items, search, relationship, polymorphic) {
11 | if(items === undefined) {
12 | return;
13 | }
14 |
15 | if (!search) {
16 | if (polymorphic === true) {
17 | return [];
18 | } else if (angular.isArray(relationship)) {
19 | return items.filter(function(value) {
20 | return relationship.indexOf(value) === -1;
21 | });
22 | } else {
23 | return items;
24 | }
25 | }
26 |
27 | var results = [];
28 | var words = search.split(' ');
29 | var searchWord;
30 |
31 | if (polymorphic === false) {
32 | searchWord = search;
33 | angular.forEach(items, function(value) {
34 | if (relationship.indexOf(value) === -1 && value.toString().toLowerCase().indexOf(searchWord.toLowerCase()) > -1) {
35 | results.push(value);
36 | }
37 | });
38 | } else {
39 | if (words.length > 1) {
40 | searchWord = words.splice(1).join(' ');
41 | angular.forEach(items, function(value) {
42 | if (relationship.indexOf(value) === -1 && value.toString().toLowerCase().indexOf(searchWord.toLowerCase()) > -1) {
43 | results.push(value);
44 | }
45 | });
46 | } else if (names.indexOf(words[0]) > -1) {
47 | if (angular.isArray(relationship)) {
48 | return items.filter(function(value) {
49 | return relationship.indexOf(value) === -1;
50 | });
51 | } else {
52 | return items.filter(function(value) {
53 | return relationship !== value;
54 | });
55 | }
56 | }
57 | }
58 |
59 | return results;
60 | };
61 | }
62 |
63 | })();
64 |
--------------------------------------------------------------------------------
/demo/app/components/search/search-polymorphic.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
9 |
10 | {{errorText}}
11 |
12 |
13 |
36 |
37 |
38 |
39 |
Your search returned no results
40 |
41 |
42 |
--------------------------------------------------------------------------------
/demo/app/components/search/search.directive.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | 'use strict';
3 |
4 | angular.module('angularJsonapiExample')
5 | .directive('angularJsonapiSearch', search);
6 |
7 | function search() {
8 | return {
9 | restrict: 'E',
10 | template: '',
11 | controller: controller,
12 | link: link,
13 | scope: {
14 | object: '=',
15 | key: '='
16 | }
17 | };
18 |
19 | function link(scope) {
20 | if (scope.schema.polymorphic === true) {
21 | scope.contentUrl = 'app/components/search/search-polymorphic.html';
22 | } else {
23 | scope.contentUrl = 'app/components/search/search.html';
24 | }
25 | }
26 |
27 | function controller($scope, $jsonapi, $timeout) {
28 | $scope.schema = $scope.object.schema.relationships[$scope.key];
29 | if ($scope.schema.polymorphic) {
30 | $scope.collections = {};
31 | angular.forEach($jsonapi.allResources(), function(resource, resourceName) {
32 | $scope.collections[resourceName] = resource.cache.index();
33 | });
34 | } else {
35 | $scope.model = $scope.schema.model;
36 | $scope.collection = $jsonapi.getResource($scope.model).cache.index();
37 | }
38 |
39 | $scope.show = false;
40 | $scope.isEmpty = isEmpty;
41 | $scope.addLink = addLink;
42 | $scope.showResults = showResults;
43 | $scope.hideResults = hideResults;
44 | $scope.setInput = setInput;
45 | $scope.getIndex = getIndex;
46 |
47 | function isEmpty(obj) {
48 | return Object.keys(obj).length === 0;
49 | }
50 |
51 | function addLink(target) {
52 | $scope.loading = true;
53 | $scope.object.link($scope.key, target).then(resolve, reject);
54 | $scope.show = false;
55 |
56 | function resolve() {
57 | $scope.loading = false;
58 | $scope.input = '';
59 | }
60 |
61 | function reject(error) {
62 | $scope.loading = false;
63 | $scope.error = true;
64 | $scope.errorText = error[0].statusText;
65 | }
66 | }
67 |
68 | function showResults() {
69 | $scope.error = false;
70 | $scope.show = true;
71 | }
72 |
73 | function hideResults() {
74 | $scope.show = false;
75 | }
76 |
77 | function setInput(value) {
78 | $scope.input = value;
79 | }
80 |
81 | function getIndex(modelName) {
82 | $timeout(function() {
83 | $scope.collection = $jsonapi.getResource(modelName).all().data;
84 | });
85 | }
86 | }
87 | }
88 |
89 | })();
90 |
--------------------------------------------------------------------------------
/demo/app/components/search/search.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
9 |
10 |
11 | {{errorText}}
12 |
13 |
45 |
--------------------------------------------------------------------------------
/demo/app/components/search/search.scss:
--------------------------------------------------------------------------------
1 | .ui.search > .results {
2 | display: block;
3 | }
4 |
5 | .ui.category.search > .results .category > .name {
6 | @include word-wrap();
7 | }
8 |
9 | .ui.search > .results .result {
10 | .description, .title {
11 | @include ellipsis();
12 | }
13 | }
--------------------------------------------------------------------------------
/demo/app/components/utils/bytes.filter.js:
--------------------------------------------------------------------------------
1 | // adopted from https://gist.github.com/thomseddon/3511330
2 |
3 | (function() {
4 | 'use strict';
5 |
6 | angular.module('angularJsonapiExample')
7 | .filter('bytes', bytes);
8 |
9 | function bytes() {
10 | return function(bytes, precision) {
11 | if (isNaN(parseFloat(bytes)) || !isFinite(bytes)) {
12 | return '-';
13 | }
14 |
15 | if (typeof precision === 'undefined') {
16 | precision = 1;
17 | }
18 |
19 | var units = ['bytes', 'kB', 'MB', 'GB', 'TB', 'PB'];
20 | var number = Math.floor(Math.log(bytes) / Math.log(1024));
21 |
22 | return (bytes / Math.pow(1024, Math.floor(number))).toFixed(precision) + ' ' + units[number];
23 | };
24 | }
25 | })();
26 |
--------------------------------------------------------------------------------
/demo/app/components/utils/semantic-ui.module.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | 'use strict';
3 | /* global $:false*/
4 |
5 | var app = angular.module('angularJsonapiExample');
6 |
7 | $.each($.site.settings.modules, function(index, module) {
8 | var fn = $.fn[module];
9 | var name = 'ui' + module.charAt(0).toUpperCase() + module.substring(1);
10 |
11 | /** @ngInject */
12 | app.directive(name, ['$timeout', '_', '$rootScope', function($timeout, _, $rootScope) {
13 | return {
14 | restrict: 'A',
15 | seModule: {
16 | name: module,
17 | fn: fn
18 | },
19 | scope: {
20 | options: '&',
21 | arguments: '=',
22 | ngModel: '='
23 | },
24 | link: function(scope, iElement) {
25 | if (!scope.options) {
26 | scope.options = {};
27 | }
28 |
29 | scope.options.directive = scope;
30 |
31 | scope.options.onChange = function(value) {
32 | $timeout(function() {
33 | scope.ngModel = value;
34 | });
35 | };
36 |
37 | $timeout(function() {
38 | var element = iElement[module](_.clone(scope.options));
39 | if (scope.arguments !== undefined) {
40 | iElement[module].apply(element, scope.arguments);
41 | }
42 | }, 300);
43 |
44 | var handler = $rootScope.$on('semantic-ui:reload', function() {
45 | $timeout(function() {
46 | var element = iElement[module](_.clone(scope.options));
47 | if (scope.arguments !== undefined) {
48 | iElement[module].apply(element, scope.arguments);
49 | }
50 | }, 300);
51 | });
52 |
53 | $rootScope.$on('$destroy', clear);
54 |
55 | function clear() {
56 | handler();
57 | }
58 | }
59 | };
60 | }]);
61 | });
62 | })();
63 |
--------------------------------------------------------------------------------
/demo/app/components/utils/to-title-case.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | 'use strict';
3 |
4 | angular.module('angularJsonapiExample')
5 | .constant('toTitleCase', toTitleCase)
6 | .filter('toTitleCase', toTitleCaseFilter);
7 |
8 | function toTitleCase(string) {
9 | var out = string.replace(/^\s*/, ''); // strip leading spaces
10 | out = out.replace(/^[a-z]|[^\s][A-Z]/g, function(str, offset) {
11 | if (offset === 0) {
12 | return str.toUpperCase();
13 | } else {
14 | return str.substr(0, 1) + ' ' + str.substr(1).toUpperCase();
15 | }
16 | });
17 |
18 | return out;
19 | }
20 |
21 | function toTitleCaseFilter() {
22 | return function(input) {
23 | return toTitleCase(input);
24 | };
25 | }
26 | })();
27 |
--------------------------------------------------------------------------------
/demo/app/footer/footer.html:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakubrohleder/angular-jsonapi/4af1857fb78aeb5e41c0645152794437f1e46e40/demo/app/footer/footer.html
--------------------------------------------------------------------------------
/demo/app/footer/footer.scss:
--------------------------------------------------------------------------------
1 | footer {
2 | min-height: 10rem;
3 | }
--------------------------------------------------------------------------------
/demo/app/frame/frame.controller.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | 'use strict';
3 |
4 | angular.module('angularJsonapiExample')
5 | .controller('FrameController', frameController);
6 |
7 | function frameController(
8 | $jsonapi,
9 | $scope
10 | ) {
11 | $scope.names = $jsonapi.listResources();
12 |
13 | $scope.localStoreSize = $jsonapi.sourceLocal.size;
14 | }
15 | })();
16 |
--------------------------------------------------------------------------------
/demo/app/frame/frame.html:
--------------------------------------------------------------------------------
1 |
23 |
24 |
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/demo/app/frame/hello.html:
--------------------------------------------------------------------------------
1 |
2 |
6 |
Go to one of the models in the menu and check out how this package works.
7 |
If you want to use it yourself check github readme and source code of this demo.
8 |
--------------------------------------------------------------------------------
/demo/app/index.constants.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | 'use strict';
3 |
4 | angular.module('angularJsonapiExample')
5 | /* global _: false, Favico: false */
6 | .constant('_', _)
7 | .constant('apiURL', 'http://localhost:3000')
8 | .constant('Favico', Favico);
9 | })();
10 |
--------------------------------------------------------------------------------
/demo/app/index.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | 'use strict';
3 |
4 | angular.module('angularJsonapiExample', [
5 | 'ui.router',
6 | 'angular-jsonapi',
7 | 'angular-jsonapi-local',
8 | 'angular-jsonapi-rest',
9 | 'jsonFormatter',
10 | 'ngClipboard',
11 | 'promise-button',
12 | 'RecursionHelper'
13 | ])
14 | .config(function(ngClipProvider) {
15 | ngClipProvider.setPath('bower_components/zeroclipboard/dist/ZeroClipboard.swf');
16 | })
17 | .config(function($stateProvider, $urlRouterProvider) {
18 | $stateProvider
19 | .state('frame', {
20 | url: '',
21 | templateUrl: 'app/frame/frame.html',
22 | controller: 'FrameController',
23 | abstract: true
24 | })
25 | .state('frame.hello', {
26 | url: '',
27 | templateUrl: 'app/frame/hello.html'
28 | })
29 | .state('frame.request', {
30 | url: '/{type}',
31 | template: '',
32 | controller: 'RequestController',
33 | abstract: true,
34 | resolve: {
35 | factory: function($jsonapi, $stateParams) {
36 | return $jsonapi.getResource($stateParams.type);
37 | }
38 | }
39 | })
40 | .state('frame.request.all', {
41 | url: '',
42 | templateUrl: 'app/request/all.html',
43 | controller: 'RequestAllController',
44 | resolve: {
45 | collection: function(factory, $location, $jsonapi) {
46 | var params = $jsonapi.sourceRest.decodeParams($location.search());
47 |
48 | return factory.all(params);
49 | }
50 | }
51 | })
52 | .state('frame.request.get', {
53 | url: '/{id}',
54 | templateUrl: 'app/request/get.html',
55 | controller: 'RequestGetController',
56 | resolve: {
57 | object: function(factory, $stateParams) {
58 | return factory.get($stateParams.id);
59 | }
60 | }
61 | });
62 |
63 | $urlRouterProvider.otherwise('/robots');
64 | });
65 | })();
66 |
--------------------------------------------------------------------------------
/demo/app/index.run.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | 'use strict';
3 |
4 | angular.module('angularJsonapiExample')
5 | .run(logEvents)
6 | .run(marioFavicon);
7 |
8 | function logEvents(
9 | $rootScope,
10 | $jsonapi,
11 | $log
12 | ) {
13 | var events = [
14 | 'resource:init',
15 | 'resource:clearCache',
16 | 'resource:initialize',
17 | 'object:add',
18 | 'object:update',
19 | 'object:refresh',
20 | 'object:remove',
21 | 'object:link',
22 | 'object:linkReflection',
23 | 'object:unlink',
24 | 'object:include',
25 | 'object:unlinkReflection',
26 | 'collection:fetch'
27 | ];
28 |
29 | var resources = $jsonapi.listResources();
30 | var watchers = [];
31 |
32 | angular.forEach(events, function(eventName) {
33 | angular.forEach(resources, function(resourceName) {
34 | logOnEvent(eventName, resourceName);
35 | });
36 | });
37 |
38 | function logOnEvent(eventName, resource) {
39 | var watcher = $rootScope.$on('angularJsonAPI:' + resource + ':' + eventName, function(event, status, object, response) {
40 | $log.info(resource, eventName, status, object, response);
41 | });
42 |
43 | watchers.push(watcher);
44 | }
45 |
46 | $rootScope.$on('$destroy', clearWatchers);
47 |
48 | function clearWatchers() {
49 | angular.forEach(watchers, function(watcher) {
50 | watcher();
51 | });
52 | }
53 | }
54 |
55 | function marioFavicon(Favico, $interval) {
56 | var favicon = new Favico({
57 | animation:'slide',
58 | position: 'up'
59 | });
60 | var up = new Image();
61 | up.src = 'assets/images/mario-up.gif';
62 |
63 | var down = new Image();
64 | down.src = 'assets/images/mario-down.gif';
65 |
66 | var isDown = true;
67 |
68 | $interval(changeIcon, 10);
69 |
70 | function changeIcon() {
71 | isDown = !isDown;
72 | if (isDown) {
73 | favicon.image(down);
74 | } else {
75 | favicon.image(up);
76 | }
77 | }
78 | }
79 | })();
80 |
--------------------------------------------------------------------------------
/demo/app/index.scss:
--------------------------------------------------------------------------------
1 | @mixin word-wrap() {
2 | word-break: break-word;
3 | -webkit-hyphens: auto;
4 | -moz-hyphens: auto;
5 | hyphens: auto;
6 | }
7 |
8 | @mixin ellipsis() {
9 | overflow: hidden;
10 | white-space: nowrap;
11 | text-overflow: ellipsis;
12 | }
13 |
14 | .site {
15 | display: flex;
16 | min-height: 100vh;
17 | flex-direction: column;
18 | }
19 |
20 | main {
21 | flex: 1;
22 | }
23 |
24 | .ui.list .list > .item .description, .ui.list > .item .description {
25 | @include word-wrap();
26 | }
27 | .ui.grid {
28 | margin-left: 0;
29 | margin-right: 0;
30 | }
31 |
32 | .ui.container {
33 | position: relative;
34 | }
35 |
36 | .ui.sidebar.menu {
37 | z-index: 20000;
38 | }
39 |
40 | .pushable > .pusher.dimmed .ui.loading.segment:before {
41 | z-index: 1;
42 | }
43 |
44 | .pushable > .pusher {
45 | position: absolute;
46 | left: 0;
47 | right: 0;
48 | }
49 |
50 | .ui.left.sidebar {
51 | transform: translate3d(-260px, 0, 0);
52 | }
53 |
54 | .ui.left.floated {
55 | float: left;
56 | }
57 |
58 | .ui.right.floated {
59 | float: right;
60 | }
61 |
62 | .icon.spin {
63 | -webkit-animation: spin 1000ms infinite linear;
64 | animation: spin 1000ms infinite linear;
65 | }
66 | @-webkit-keyframes spin {
67 | 0% {
68 | -webkit-transform: rotate(0deg);
69 | transform: rotate(0deg);
70 | }
71 | 100% {
72 | -webkit-transform: rotate(359deg);
73 | transform: rotate(359deg);
74 | }
75 | }
76 | @keyframes spin {
77 | 0% {
78 | -webkit-transform: rotate(0deg);
79 | transform: rotate(0deg);
80 | }
81 | 100% {
82 | -webkit-transform: rotate(359deg);
83 | transform: rotate(359deg);
84 | }
85 | }
86 |
87 | .show-loading, .show-error, .show-ok{
88 | display: none;
89 | }
90 | .loading .show-loading {
91 | display: inline-block;
92 | }
93 | .error .show-error {
94 | display: inline-block;
95 | }
96 | .ok .show-ok {
97 | display: inline-block;
98 | }
99 |
100 | .stats {
101 | position: absolute;
102 | width: 100vw;
103 | top: 0;
104 | left: 50%;
105 | z-index: 10;
106 | transform: translateX(-200%);
107 | background: white;
108 | transition: all 1s ease-in-out;
109 | border-right: 1px #444 solid;
110 | }
111 | .dimmer {
112 | display: none;
113 | background: black;
114 | opacity: 0;
115 | position: fixed;
116 | top: 0;
117 | bottom: 0;
118 | left: 0;
119 | right: 0;
120 | z-index: 5;
121 | transition: all 1s ease-in-out;
122 | }
123 | .site {
124 | min-height: 100vh;
125 | transition: all 1s ease-in-out;
126 | }
127 | .show-stats .stats {
128 | transform: translateX(-50%);
129 | }
130 | .show-stats .dimmer {
131 | display: block;
132 | opacity: .5;
133 | }
134 | .show-stats .site {
135 | transform: translateX(50%);
136 | }
137 |
138 | .ui.segment.fixed {
139 | position: fixed;
140 | bottom: 1em;
141 | right: 1em;
142 | z-index: 200;
143 | }
144 |
145 | a[ui-sref] {
146 | cursor: pointer;
147 | }
148 |
149 | .narrow.container {
150 | max-width: 80rem;
151 | margin: auto;
152 | padding: 3rem;
153 | }
154 | /* Do not remove this comments bellow. It's the markers used by gulp-inject to inject
155 | all your sass files automatically */
156 | // injector
157 | // endinjector
158 |
159 |
160 | @media only screen and (max-width: 1200px){
161 | .secondary.pointing.menu .item, .secondary.pointing.menu .menu {
162 | display: none;
163 | }
164 | .secondary.pointing.menu .toc.item {
165 | display: block;
166 | }
167 | }
168 |
169 | @media only screen and (min-width: 1201px){
170 | .secondary.pointing.menu .toc.item {
171 | display: none;
172 | }
173 | }
174 |
175 | .ui.mini.dropdown {
176 | font-size: 0.71428571em;
177 |
178 | .menu > .item {
179 | font-size: 1em;
180 | }
181 | }
--------------------------------------------------------------------------------
/demo/app/request/all.controller.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | 'use strict';
3 |
4 | angular.module('angularJsonapiExample')
5 | .controller('RequestAllController', RequestAllController);
6 |
7 | function RequestAllController(
8 | $scope, collection
9 | ) {
10 | $scope.collection = collection;
11 | }
12 | })();
13 |
--------------------------------------------------------------------------------
/demo/app/request/all.html:
--------------------------------------------------------------------------------
1 |
2 | All
3 |
--------------------------------------------------------------------------------
/demo/app/request/get.controller.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | 'use strict';
3 |
4 | angular.module('angularJsonapiExample')
5 | .controller('RequestGetController', RequestGetController);
6 |
7 | function RequestGetController(
8 | $scope, object
9 | ) {
10 | $scope.object = object;
11 | }
12 | })();
13 |
--------------------------------------------------------------------------------
/demo/app/request/get.html:
--------------------------------------------------------------------------------
1 | Get
2 |
3 | {{object.toString()}}
--------------------------------------------------------------------------------
/demo/app/request/request.controller.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | 'use strict';
3 |
4 | angular.module('angularJsonapiExample')
5 | .controller('RequestController', RequestController);
6 |
7 | function RequestController(
8 | ) {
9 | }
10 | })();
11 |
--------------------------------------------------------------------------------
/demo/app/resources/jobs.factory.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | 'use strict';
3 |
4 | angular.module('angularJsonapiExample')
5 | .run(function(
6 | $jsonapi,
7 | apiURL
8 | ) {
9 | var schema = {
10 | type: 'jobs',
11 | id: 'uuid4',
12 | attributes: {
13 | name: {presence: true, length: {maximum: 20, minimum: 3}},
14 | salary: {presence: true, numericality: {onlyInteger: true}}
15 | },
16 | relationships: {
17 | robots: {
18 | included: true,
19 | type: 'hasMany'
20 | }
21 | },
22 | functions: {
23 | toString: function() {
24 | if (!this.data.attributes.name) {
25 | return this.data.id;
26 | }
27 |
28 | return this.data.attributes.name;
29 | }
30 | }
31 | };
32 |
33 | var localeSynchro = $jsonapi.sourceLocal.create('LocalStore synchronization', 'AngularJsonAPI');
34 | var restSynchro = $jsonapi.sourceRest.create('Rest synchronization', apiURL + '/jobs');
35 | var synchronizer = $jsonapi.synchronizerSimple.create([localeSynchro, restSynchro]);
36 |
37 | $jsonapi.addResource(schema, synchronizer);
38 | })
39 | .factory('Jobs', Jobs);
40 |
41 | function Jobs(
42 | $jsonapi
43 | ) {
44 | return $jsonapi.getResource('jobs');
45 | }
46 | })();
47 |
--------------------------------------------------------------------------------
/demo/app/resources/laser-guns.factory.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | 'use strict';
3 |
4 | angular.module('angularJsonapiExample')
5 | .run(function(
6 | $jsonapi,
7 | apiURL
8 | ) {
9 | var schema = {
10 | type: 'laserGuns',
11 | id: 'uuid4',
12 | attributes: {
13 | name: {presence: true, length: {maximum: 20, minimum: 3}},
14 | durability: {presence: true, numericality: {onlyInteger: true}},
15 | quality: {presence: true, numericality: {onlyInteger: true}},
16 | power: {presence: true, numericality: {onlyInteger: true}},
17 | type: {presence: true, length: {maximum: 20, minimum: 3}},
18 | rarity: {presence: true, length: {maximum: 20, minimum: 3}}
19 | },
20 | relationships: {
21 | owner: {
22 | included: true,
23 | type: 'hasOne',
24 | polymorphic: true
25 | }
26 | },
27 | functions: {
28 | toString: function() {
29 | if (!this.data.attributes.name) {
30 | return this.data.id;
31 | }
32 |
33 | return this.data.attributes.name;
34 | }
35 | }
36 | };
37 |
38 | var localeSynchro = $jsonapi.sourceLocal.create('LocalStore synchronization', 'AngularJsonAPI');
39 | var restSynchro = $jsonapi.sourceRest.create('Rest synchronization', apiURL + '/laserGuns');
40 | var synchronizer = $jsonapi.synchronizerSimple.create([localeSynchro, restSynchro]);
41 |
42 | $jsonapi.addResource(schema, synchronizer);
43 | })
44 | .factory('Jobs', Jobs);
45 |
46 | function Jobs(
47 | $jsonapi
48 | ) {
49 | return $jsonapi.getResource('laserGuns');
50 | }
51 | })();
52 |
--------------------------------------------------------------------------------
/demo/app/resources/locations.factory.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | 'use strict';
3 |
4 | angular.module('angularJsonapiExample')
5 | .run(function(
6 | $jsonapi,
7 | apiURL
8 | ) {
9 | var schema = {
10 | type: 'locations',
11 | id: 'uuid4',
12 | attributes: {
13 | cordsX: {presence: true, numericality: {onlyInteger: true}},
14 | cordsY: {presence: true, numericality: {onlyInteger: true}}
15 | },
16 | relationships: {
17 | planet: {
18 | included: true,
19 | type: 'hasOne'
20 | },
21 | entity: {
22 | included: true,
23 | type: 'hasOne',
24 | polymorphic: true,
25 | reflection: 'location'
26 | }
27 | },
28 | functions: {
29 | toString: function() {
30 | if (!this.relationships.planet || !this.relationships.planet.data.attributes.name) {
31 | return this.data.id;
32 | }
33 |
34 | return this.relationships.planet.data.attributes.name;
35 | }
36 | }
37 | };
38 |
39 | var localeSynchro = $jsonapi.sourceLocal.create('LocalStore synchronization', 'AngularJsonAPI');
40 | var restSynchro = $jsonapi.sourceRest.create('Rest synchronization', apiURL + '/locations');
41 | var synchronizer = $jsonapi.synchronizerSimple.create([localeSynchro, restSynchro]);
42 |
43 | $jsonapi.addResource(schema, synchronizer);
44 | })
45 | .factory('Locations', Locations);
46 |
47 | function Locations(
48 | $jsonapi
49 | ) {
50 | return $jsonapi.getResource('locations');
51 | }
52 | })();
53 |
--------------------------------------------------------------------------------
/demo/app/resources/planets.factory.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | 'use strict';
3 |
4 | angular.module('angularJsonapiExample')
5 | .run(function(
6 | $jsonapi,
7 | apiURL
8 | ) {
9 | var schema = {
10 | type: 'planets',
11 | id: 'uuid4',
12 | attributes: {
13 | name: {presence: true, length: {maximum: 20, minimum: 3}},
14 | cordsX: {presence: true, numericality: {onlyInteger: true}},
15 | cordsY: {presence: true, numericality: {onlyInteger: true}},
16 | cordsZ: {presence: true, numericality: {onlyInteger: true}},
17 | size: {presence: true, numericality: {onlyInteger: true}}
18 | },
19 | relationships: {
20 | locations: {
21 | included: true,
22 | type: 'hasMany'
23 | }
24 | },
25 | functions: {
26 | toString: function() {
27 | if (!this.data.attributes.name) {
28 | return this.data.id;
29 | }
30 |
31 | return this.data.attributes.name;
32 | }
33 | }
34 | };
35 |
36 | var localeSynchro = $jsonapi.sourceLocal.create('LocalStore synchronization', 'AngularJsonAPI');
37 | var restSynchro = $jsonapi.sourceRest.create('Rest synchronization', apiURL + '/planets');
38 | var synchronizer = $jsonapi.synchronizerSimple.create([localeSynchro, restSynchro]);
39 |
40 | $jsonapi.addResource(schema, synchronizer);
41 | })
42 | .factory('Planets', Planets);
43 |
44 | function Planets(
45 | $jsonapi
46 | ) {
47 | return $jsonapi.getResource('planets');
48 | }
49 | })();
50 |
--------------------------------------------------------------------------------
/demo/app/resources/power-armors.factory.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | 'use strict';
3 |
4 | angular.module('angularJsonapiExample')
5 | .run(function(
6 | $jsonapi,
7 | apiURL
8 | ) {
9 | var schema = {
10 | type: 'powerArmors',
11 | id: 'uuid4',
12 | attributes: {
13 | name: {presence: true, length: {maximum: 20, minimum: 3}},
14 | durability: {presence: true, numericality: {onlyInteger: true}},
15 | quality: {presence: true, numericality: {onlyInteger: true}},
16 | armor: {presence: true, numericality: {onlyInteger: true}},
17 | type: {presence: true, length: {maximum: 20, minimum: 3}},
18 | rarity: {presence: true, length: {maximum: 20, minimum: 3}}
19 | },
20 | relationships: {
21 | owner: {
22 | included: true,
23 | type: 'hasOne',
24 | polymorphic: true
25 | }
26 | },
27 | functions: {
28 | toString: function() {
29 | if (!this.data.attributes.name) {
30 | return this.data.id;
31 | }
32 |
33 | return this.data.attributes.name;
34 | }
35 | }
36 | };
37 |
38 | var localeSynchro = $jsonapi.sourceLocal.create('LocalStore synchronization', 'AngularJsonAPI');
39 | var restSynchro = $jsonapi.sourceRest.create('Rest synchronization', apiURL + '/powerArmors');
40 | var synchronizer = $jsonapi.synchronizerSimple.create([localeSynchro, restSynchro]);
41 |
42 | $jsonapi.addResource(schema, synchronizer);
43 | })
44 | .factory('PowerArmors', PowerArmors);
45 |
46 | function PowerArmors(
47 | $jsonapi
48 | ) {
49 | return $jsonapi.getResource('powerArmors');
50 | }
51 | })();
52 |
--------------------------------------------------------------------------------
/demo/app/resources/robot-models.factory.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | 'use strict';
3 |
4 | angular.module('angularJsonapiExample')
5 | .run(function(
6 | $jsonapi,
7 | apiURL
8 | ) {
9 | var schema = {
10 | type: 'robotModels',
11 | id: 'uuid4',
12 | attributes: {
13 | name: {presence: true, length: {maximum: 20, minimum: 3}},
14 | code: {presence: true, length: {maximum: 20, minimum: 3}}
15 | },
16 | relationships: {
17 | robots: {
18 | included: true,
19 | type: 'hasMany'
20 | }
21 | },
22 | functions: {
23 | toString: function() {
24 | if (!this.data.attributes.name) {
25 | return this.data.id;
26 | }
27 |
28 | return this.data.attributes.name;
29 | }
30 | }
31 | };
32 |
33 | var localeSynchro = $jsonapi.sourceLocal.create('LocalStore synchronization', 'AngularJsonAPI');
34 | var restSynchro = $jsonapi.sourceRest.create('Rest synchronization', apiURL + '/robotModels');
35 | var synchronizer = $jsonapi.synchronizerSimple.create([localeSynchro, restSynchro]);
36 |
37 | $jsonapi.addResource(schema, synchronizer);
38 | })
39 | .factory('RobotModels', RobotModels);
40 |
41 | function RobotModels(
42 | $jsonapi
43 | ) {
44 | return $jsonapi.getResource('robotModels');
45 | }
46 | })();
47 |
--------------------------------------------------------------------------------
/demo/app/resources/robots.factory.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | 'use strict';
3 |
4 | angular.module('angularJsonapiExample')
5 | .run(function(
6 | $jsonapi,
7 | apiURL
8 | ) {
9 | var schema = {
10 | type: 'robots',
11 | id: 'uuid4',
12 | attributes: {
13 | nameFirst: {presence: true, length: {maximum: 20, minimum: 3}},
14 | nameLast: {presence: true, length: {maximum: 20, minimum: 3}},
15 | creationDate: {datetime: true},
16 | pictureUrl: {presence: true}
17 | },
18 | relationships: {
19 | location: {
20 | included: true,
21 | type: 'hasOne',
22 | reflection: 'entity'
23 | },
24 | robotModel: {
25 | included: true,
26 | type: 'hasOne'
27 | },
28 | job: {
29 | included: true,
30 | type: 'hasOne'
31 | },
32 | laserGuns: {
33 | included: true,
34 | type: 'hasMany',
35 | reflection: 'owner'
36 | },
37 | powerArmors: {
38 | included: true,
39 | type: 'hasMany',
40 | reflection: 'owner'
41 | },
42 | spaceships: {
43 | included: true,
44 | type: 'hasMany',
45 | reflection: 'pilot'
46 | }
47 | },
48 | include: {
49 | get: ['location.planet']
50 | },
51 | functions: {
52 | toString: function() {
53 | if (!this.data.attributes.nameFirst && !this.data.attributes.nameLast) {
54 | return this.data.id;
55 | }
56 |
57 | return this.data.attributes.nameFirst + this.data.attributes.nameLast;
58 | }
59 | }
60 | };
61 |
62 | var localeSynchro = $jsonapi.sourceLocal.create('LocalStore synchronization', 'AngularJsonAPI');
63 | var restSynchro = $jsonapi.sourceRest.create('Rest synchronization', apiURL + '/robots');
64 | var synchronizer = $jsonapi.synchronizerSimple.create([localeSynchro, restSynchro]);
65 |
66 | $jsonapi.addResource(schema, synchronizer);
67 | })
68 | .factory('Robots', Robots);
69 |
70 | function Robots(
71 | $jsonapi
72 | ) {
73 | return $jsonapi.getResource('robots');
74 | }
75 | })();
76 |
--------------------------------------------------------------------------------
/demo/app/resources/spaceship-models.factory.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | 'use strict';
3 |
4 | angular.module('angularJsonapiExample')
5 | .run(function(
6 | $jsonapi,
7 | apiURL
8 | ) {
9 | var schema = {
10 | type: 'spaceshipModels',
11 | id: 'uuid4',
12 | attributes: {
13 | name: {presence: true, length: {maximum: 20, minimum: 3}},
14 | code: {presence: true, length: {maximum: 20, minimum: 3}},
15 | speed: {presence: true, numericality: {onlyInteger: true}},
16 | cargo: {presence: true, numericality: {onlyInteger: true}},
17 | type: {presence: true, length: {maximum: 20, minimum: 3}}
18 | },
19 | relationships: {
20 | spaceships: {
21 | included: true,
22 | type: 'hasMany'
23 | }
24 | },
25 | functions: {
26 | toString: function() {
27 | if (!this.data.attributes.name) {
28 | return this.data.id;
29 | }
30 |
31 | return this.data.attributes.name;
32 | }
33 | }
34 | };
35 |
36 | var localeSynchro = $jsonapi.sourceLocal.create('LocalStore synchronization', 'AngularJsonAPI');
37 | var restSynchro = $jsonapi.sourceRest.create('Rest synchronization', apiURL + '/spaceshipModels');
38 | var synchronizer = $jsonapi.synchronizerSimple.create([localeSynchro, restSynchro]);
39 |
40 | $jsonapi.addResource(schema, synchronizer);
41 | })
42 | .factory('SpaceshipModels', SpaceshipModels);
43 |
44 | function SpaceshipModels(
45 | $jsonapi
46 | ) {
47 | return $jsonapi.getResource('spaceshipModels');
48 | }
49 | })();
50 |
--------------------------------------------------------------------------------
/demo/app/resources/spaceships.factory.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | 'use strict';
3 |
4 | angular.module('angularJsonapiExample')
5 | .run(function(
6 | $jsonapi,
7 | apiURL
8 | ) {
9 | var schema = {
10 | type: 'spaceships',
11 | id: 'uuid4',
12 | attributes: {
13 | name: {presence: true, length: {maximum: 20, minimum: 3}},
14 | durability: {presence: true, numericality: {onlyInteger: true}},
15 | quality: {presence: true, numericality: {onlyInteger: true}}
16 | },
17 | relationships: {
18 | pilot: {
19 | included: true,
20 | type: 'hasOne',
21 | reflection: 'spaceships',
22 | polymorphic: true
23 | },
24 | spaceshipModel: {
25 | included: true,
26 | type: 'hasOne'
27 | },
28 | location: {
29 | included: true,
30 | type: 'hasOne',
31 | reflection: 'entity'
32 | }
33 | },
34 | functions: {
35 | toString: function() {
36 | if (!this.data.attributes.name) {
37 | return this.data.id;
38 | }
39 |
40 | return this.data.attributes.name;
41 | }
42 | }
43 | };
44 |
45 | var localeSynchro = $jsonapi.sourceLocal.create('LocalStore synchronization', 'AngularJsonAPI');
46 | var restSynchro = $jsonapi.sourceRest.create('Rest synchronization', apiURL + '/spaceships');
47 | var synchronizer = $jsonapi.synchronizerSimple.create([localeSynchro, restSynchro]);
48 |
49 | $jsonapi.addResource(schema, synchronizer);
50 | })
51 | .factory('Spaceships', Spaceships);
52 |
53 | function Spaceships(
54 | $jsonapi
55 | ) {
56 | return $jsonapi.getResource('spaceships');
57 | }
58 | })();
59 |
--------------------------------------------------------------------------------
/demo/app/sidebar/sidebar.directive.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | 'use strict';
3 |
4 | angular.module('angularJsonapiExample')
5 | .directive('sidebar', sidebar);
6 |
7 | function sidebar() {
8 | return {
9 | restrict: 'E',
10 | templateUrl: 'app/sidebar/sidebar.html',
11 | controller: sidebarController,
12 | replace: true
13 | };
14 |
15 | function sidebarController($scope, $jsonapi) {
16 | $scope.names = $jsonapi.listResources();
17 | }
18 | }
19 |
20 | })();
21 |
--------------------------------------------------------------------------------
/demo/app/sidebar/sidebar.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/demo/app/vendor.scss:
--------------------------------------------------------------------------------
1 | /* Do not remove this comments bellow. It's the markers used by wiredep to inject
2 | sass dependencies when defined in the bower.json of your dependencies */
3 | // bower:scss
4 | // endbower
5 |
--------------------------------------------------------------------------------
/demo/assets/images/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakubrohleder/angular-jsonapi/4af1857fb78aeb5e41c0645152794437f1e46e40/demo/assets/images/.keep
--------------------------------------------------------------------------------
/demo/assets/images/mario-down.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakubrohleder/angular-jsonapi/4af1857fb78aeb5e41c0645152794437f1e46e40/demo/assets/images/mario-down.gif
--------------------------------------------------------------------------------
/demo/assets/images/mario-up.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakubrohleder/angular-jsonapi/4af1857fb78aeb5e41c0645152794437f1e46e40/demo/assets/images/mario-up.gif
--------------------------------------------------------------------------------
/demo/favicon.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakubrohleder/angular-jsonapi/4af1857fb78aeb5e41c0645152794437f1e46e40/demo/favicon.gif
--------------------------------------------------------------------------------
/demo/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | angularJsonapi
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
28 |
30 |
31 |
32 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/dist-demo/assets/images/mario-down.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakubrohleder/angular-jsonapi/4af1857fb78aeb5e41c0645152794437f1e46e40/dist-demo/assets/images/mario-down.gif
--------------------------------------------------------------------------------
/dist-demo/assets/images/mario-up.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakubrohleder/angular-jsonapi/4af1857fb78aeb5e41c0645152794437f1e46e40/dist-demo/assets/images/mario-up.gif
--------------------------------------------------------------------------------
/dist-demo/favicon.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakubrohleder/angular-jsonapi/4af1857fb78aeb5e41c0645152794437f1e46e40/dist-demo/favicon.gif
--------------------------------------------------------------------------------
/dist-demo/fonts/icons.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakubrohleder/angular-jsonapi/4af1857fb78aeb5e41c0645152794437f1e46e40/dist-demo/fonts/icons.eot
--------------------------------------------------------------------------------
/dist-demo/fonts/icons.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakubrohleder/angular-jsonapi/4af1857fb78aeb5e41c0645152794437f1e46e40/dist-demo/fonts/icons.ttf
--------------------------------------------------------------------------------
/dist-demo/fonts/icons.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakubrohleder/angular-jsonapi/4af1857fb78aeb5e41c0645152794437f1e46e40/dist-demo/fonts/icons.woff
--------------------------------------------------------------------------------
/dist-demo/fonts/icons.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakubrohleder/angular-jsonapi/4af1857fb78aeb5e41c0645152794437f1e46e40/dist-demo/fonts/icons.woff2
--------------------------------------------------------------------------------
/dist-demo/index.html:
--------------------------------------------------------------------------------
1 | angularJsonapi
--------------------------------------------------------------------------------
/dist-demo/maps/styles/app-3086bb8113.css.map:
--------------------------------------------------------------------------------
1 | {"version":3,"sources":["styles/app-3086bb8113.css"],"names":[],"mappings":"AAmGA,QAdA,OAmBE,IAAK,EATL,mBAAoB,IAAI,GAAG,YA/F7B,MACE,QAAS,YACT,QAAS,aACT,QAAS,YACT,QAAS,KAET,mBAAoB,SACpB,sBAAuB,OACvB,uBAAwB,OACpB,mBAAoB,OAChB,eAAgB,OAE1B,KACE,iBAAkB,EAClB,aAAc,EACV,SAAU,EACN,KAAM,EAEhB,kCAAqC,4BACnC,WAAY,WACZ,gBAAiB,KACjB,aAAc,KACd,YAAa,KACT,QAAS,KAEf,SACE,YAAa,EACb,aAAc,EAEhB,cACE,SAAU,SAEZ,iBACE,QAAS,MAEX,oDACE,QAAS,EAEX,kBACE,SAAU,SACV,KAAM,EACN,MAAO,EAET,iBACE,kBAAmB,wBACX,UAAW,wBAErB,iBACE,MAAO,KAET,kBACE,MAAO,MAET,WACE,kBAAmB,KAAK,GAAO,SAAS,OACxC,UAAW,KAAK,GAAO,SAAS,OAElC,wBACE,GACE,kBAAmB,UACnB,UAAW,UACb,KACE,kBAAmB,eACnB,UAAW,gBAEf,gBACE,GACE,kBAAmB,UACnB,UAAW,UACb,KACE,kBAAmB,eACnB,UAAW,gBAEA,YAAf,cAA4B,SAC1B,QAAS,KAKX,mBAHA,uBAMA,aALE,QAAS,aAQX,OACE,SAAU,SACV,MAAO,MAEP,KAAM,IACN,QAAS,GACT,kBAAmB,kBACf,cAAe,kBACX,UAAW,kBACnB,WAAY,KAEJ,WAAY,IAAI,GAAG,YAC3B,aAAc,IAAI,KAAK,MAEzB,QACE,QAAS,KACT,WAAY,KACZ,QAAS,EACT,SAAU,MAEV,OAAQ,EACR,KAAM,EACN,MAAO,EACP,QAAS,EAED,WAAY,IAAI,GAAG,YAE7B,MACE,WAAY,MACZ,mBAAoB,IAAI,GAAG,YACnB,WAAY,IAAI,GAAG,YAE7B,mBACE,kBAAmB,iBACf,cAAe,iBACX,UAAW,iBAErB,oBACE,QAAS,MACT,QAAS,GAEX,kBACE,kBAAmB,gBACf,cAAe,gBACX,UAAW,gBAErB,kBACE,SAAU,MACV,OAAQ,IACR,MAAO,IACP,QAAS,IAEX,WACE,OAAQ,QAEV,kBACE,UAAW,MACX,OAAQ,KACR,QAAS,KAMX,OACE,WAAY,MAEd,+BAAgC,8BAChC,uCACA,sCACE,MAAO,QAET,0CAA0N,yCAA0C,mDAA/K,0CAA+V,2CAAxQ,6CAA5C,2CAAwL,wCAA7Q,yCAAsT,yCAA0C,wCAAqF,+BAAgC,iCAAkC,kDAAkQ,iDAAkD,2DAA/M,kDAA+Z,mDAAxT,qDAApD,mDAAwN,gDAA7T,iDAA8W,iDAAkD,gDAAqG,uCAAwC,yCAChoC,WAAY,QACZ,aAAc,QACd,MAAO,QACP,cAAe,GACf,WAAY,KAEd,YACE,WAAY,KACZ,SAAU,SACV,WAAY,MAEd,oBACE,QAAS,MAEX,6CACE,WAAY,WACZ,gBAAiB,KACjB,aAAc,KACd,YAAa,KACT,QAAS,KAEf,yCAA4C,mCAC1C,SAAU,OACV,YAAa,OACb,cAAe,SAEjB,0CACE,+BAAgC,+BAC9B,QAAS,KACX,mCACE,QAAS,OAEb,0CACE,mCACE,QAAS,MAEb,kBACE,UAAW,YACX,8BACE,UAAW","file":"styles/app-3086bb8113.css","sourcesContent":[".site {\n display: -webkit-box;\n display: -webkit-flex;\n display: -ms-flexbox;\n display: flex;\n min-height: 100vh;\n -webkit-box-orient: vertical;\n -webkit-box-direction: normal;\n -webkit-flex-direction: column;\n -ms-flex-direction: column;\n flex-direction: column; }\n\nmain {\n -webkit-box-flex: 1;\n -webkit-flex: 1;\n -ms-flex: 1;\n flex: 1; }\n\n.ui.list .list > .item .description, .ui.list > .item .description {\n word-break: break-word;\n -webkit-hyphens: auto;\n -moz-hyphens: auto;\n -ms-hyphens: auto;\n hyphens: auto; }\n\n.ui.grid {\n margin-left: 0;\n margin-right: 0; }\n\n.ui.container {\n position: relative; }\n\n.ui.sidebar.menu {\n z-index: 20000; }\n\n.pushable > .pusher.dimmed .ui.loading.segment:before {\n z-index: 1; }\n\n.pushable > .pusher {\n position: absolute;\n left: 0;\n right: 0; }\n\n.ui.left.sidebar {\n -webkit-transform: translate3d(-260px, 0, 0);\n transform: translate3d(-260px, 0, 0); }\n\n.ui.left.floated {\n float: left; }\n\n.ui.right.floated {\n float: right; }\n\n.icon.spin {\n -webkit-animation: spin 1000ms infinite linear;\n animation: spin 1000ms infinite linear; }\n\n@-webkit-keyframes spin {\n 0% {\n -webkit-transform: rotate(0deg);\n transform: rotate(0deg); }\n 100% {\n -webkit-transform: rotate(359deg);\n transform: rotate(359deg); } }\n\n@keyframes spin {\n 0% {\n -webkit-transform: rotate(0deg);\n transform: rotate(0deg); }\n 100% {\n -webkit-transform: rotate(359deg);\n transform: rotate(359deg); } }\n\n.show-loading, .show-error, .show-ok {\n display: none; }\n\n.loading .show-loading {\n display: inline-block; }\n\n.error .show-error {\n display: inline-block; }\n\n.ok .show-ok {\n display: inline-block; }\n\n.stats {\n position: absolute;\n width: 100vw;\n top: 0;\n left: 50%;\n z-index: 10;\n -webkit-transform: translateX(-200%);\n -ms-transform: translateX(-200%);\n transform: translateX(-200%);\n background: white;\n -webkit-transition: all 1s ease-in-out;\n transition: all 1s ease-in-out;\n border-right: 1px #444 solid; }\n\n.dimmer {\n display: none;\n background: black;\n opacity: 0;\n position: fixed;\n top: 0;\n bottom: 0;\n left: 0;\n right: 0;\n z-index: 5;\n -webkit-transition: all 1s ease-in-out;\n transition: all 1s ease-in-out; }\n\n.site {\n min-height: 100vh;\n -webkit-transition: all 1s ease-in-out;\n transition: all 1s ease-in-out; }\n\n.show-stats .stats {\n -webkit-transform: translateX(-50%);\n -ms-transform: translateX(-50%);\n transform: translateX(-50%); }\n\n.show-stats .dimmer {\n display: block;\n opacity: .5; }\n\n.show-stats .site {\n -webkit-transform: translateX(50%);\n -ms-transform: translateX(50%);\n transform: translateX(50%); }\n\n.ui.segment.fixed {\n position: fixed;\n bottom: 1em;\n right: 1em;\n z-index: 200; }\n\na[ui-sref] {\n cursor: pointer; }\n\n.narrow.container {\n max-width: 80rem;\n margin: auto;\n padding: 3rem; }\n\n/* Do not remove this comments bellow. It's the markers used by gulp-inject to inject\n all your sass files automatically */\n/* Do not remove this comments bellow. It's the markers used by wiredep to inject\n sass dependencies when defined in the bower.json of your dependencies */\nfooter {\n min-height: 10rem; }\n\n.ui.form .field.success .input, .ui.form .field.success label,\n.ui.form .fields.success .field .input,\n.ui.form .fields.success .field label {\n color: #1a531b; }\n\n.ui.form .field.success input:not([type]), .ui.form .field.success input[type=text], .ui.form .field.success input[type=email], .ui.form .field.success input[type=search], .ui.form .field.success input[type=password], .ui.form .field.success input[type=date], .ui.form .field.success input[type=datetime-local], .ui.form .field.success input[type=tel], .ui.form .field.success input[type=time], .ui.form .field.success input[type=url], .ui.form .field.success input[type=number], .ui.form .field.success select, .ui.form .field.success textarea, .ui.form .fields.success .field input:not([type]), .ui.form .fields.success .field input[type=text], .ui.form .fields.success .field input[type=email], .ui.form .fields.success .field input[type=search], .ui.form .fields.success .field input[type=password], .ui.form .fields.success .field input[type=date], .ui.form .fields.success .field input[type=datetime-local], .ui.form .fields.success .field input[type=tel], .ui.form .fields.success .field input[type=time], .ui.form .fields.success .field input[type=url], .ui.form .fields.success .field input[type=number], .ui.form .fields.success .field select, .ui.form .fields.success .field textarea {\n background: #fcfff5;\n border-color: #a3c293;\n color: #2c662d;\n border-radius: '';\n box-shadow: none; }\n\n.collection {\n margin-top: 2rem;\n position: relative;\n min-height: 20rem; }\n\n.ui.search > .results {\n display: block; }\n\n.ui.category.search > .results .category > .name {\n word-break: break-word;\n -webkit-hyphens: auto;\n -moz-hyphens: auto;\n -ms-hyphens: auto;\n hyphens: auto; }\n\n.ui.search > .results .result .description, .ui.search > .results .result .title {\n overflow: hidden;\n white-space: nowrap;\n text-overflow: ellipsis; }\n\n@media only screen and (max-width: 1200px) {\n .secondary.pointing.menu .item, .secondary.pointing.menu .menu {\n display: none; }\n .secondary.pointing.menu .toc.item {\n display: block; } }\n\n@media only screen and (min-width: 1201px) {\n .secondary.pointing.menu .toc.item {\n display: none; } }\n\n.ui.mini.dropdown {\n font-size: 0.71428571em; }\n .ui.mini.dropdown .menu > .item {\n font-size: 1em; }\n\n/*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImluZGV4LnNjc3MiLCJ2ZW5kb3Iuc2NzcyIsImZvb3Rlci9mb290ZXIuc2NzcyIsImNvbXBvbmVudHMvYXR0cmlidXRlLWZpZWxkL2F0dHJpYnV0ZS1maWVsZC5zY3NzIiwiY29tcG9uZW50cy9jb2xsZWN0aW9uL2NvbGxlY3Rpb24uc2NzcyIsImNvbXBvbmVudHMvc2VhcmNoL3NlYXJjaC5zY3NzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQWFBO0VBQ0UscUJBQWM7RUFBZCxzQkFBYztFQUFkLHFCQUFjO0VBQWQsY0FBYztFQUNkLGtCQUFrQjtFQUNsQiw2QkFBdUI7RUFBdkIsOEJBQXVCO0VBQXZCLCtCQUF1QjtNQUF2QiwyQkFBdUI7VUFBdkIsdUJBQXVCLEVBSGxCOztBQU1QO0VBQ0Usb0JBQVE7RUFBUixnQkFBUTtNQUFSLFlBQVE7VUFBUixRQUFRLEVBREo7O0FBSWdEO0VBdEJwRCx1QkFBMkI7RUFDM0Isc0JBQXNCO0VBQ3RCLG1CQUFzQjtFQUN0QixrQkFBc0I7TUFBdEIsY0FBc0IsRUFtQjRDOztBQUdqRTtFQUNELGVBQWU7RUFDZixnQkFBZ0IsRUFGUjs7QUFLUDtFQUNELG1CQUFtQixFQUROOztBQUlKO0VBQ1QsZUFBZSxFQURDOztBQUk0QjtFQUM1QyxXQUFXLEVBRDBDOztBQUkzQztFQUNWLG1CQUFtQjtFQUNuQixRQUFRO0VBQ1IsU0FBUyxFQUhVOztBQU1iO0VBQ04sNkNBQXNCO1VBQXRCLHFDQUFzQixFQUROOztBQUlWO0VBQ04sWUFBWSxFQURJOztBQUlUO0VBQ1AsYUFBYSxFQURJOztBQUlkO0VBQ0QsK0NBQThDO0VBQzlDLHVDQUFzQyxFQUY5Qjs7QUFJWjtFQUNJO0lBQ0ksZ0NBQXlCO0lBQ3pCLHdCQUFpQixFQUFBO0VBRXJCO0lBQ0ksa0NBQXlCO0lBQ3pCLDBCQUFpQixFQUFBLEVBQUE7O0FBR3pCO0VBQ0k7SUFDSSxnQ0FBeUI7SUFDekIsd0JBQWlCLEVBQUE7RUFFckI7SUFDSSxrQ0FBeUI7SUFDekIsMEJBQWlCLEVBQUEsRUFBQTs7QUFJRztFQUMxQixjQUFjLEVBRHFCOztBQUc1QjtFQUNQLHNCQUFzQixFQURBOztBQUdqQjtFQUNMLHNCQUFzQixFQURKOztBQUdoQjtFQUNGLHNCQUFzQixFQURWOztBQUlkO0VBQ0UsbUJBQW1CO0VBQ25CLGFBQWE7RUFDYixPQUFPO0VBQ1AsVUFBVTtFQUNWLFlBQVk7RUFDWixxQ0FBcUI7TUFBckIsaUNBQXFCO1VBQXJCLDZCQUFxQjtFQUNyQixrQkFBa0I7RUFDbEIsdUNBQThCO1VBQTlCLCtCQUE4QjtFQUM5Qiw2QkFBNkIsRUFUdkI7O0FBV1I7RUFDRSxjQUFjO0VBQ2Qsa0JBQWtCO0VBQ2xCLFdBQVc7RUFDWCxnQkFBZ0I7RUFDaEIsT0FBTztFQUNQLFVBQVU7RUFDVixRQUFRO0VBQ1IsU0FBUztFQUNULFdBQVc7RUFDWCx1Q0FBOEI7VUFBOUIsK0JBQThCLEVBVnZCOztBQVlUO0VBQ0Usa0JBQWtCO0VBQ2xCLHVDQUE4QjtVQUE5QiwrQkFBOEIsRUFGekI7O0FBSUs7RUFDVixvQ0FBcUI7TUFBckIsZ0NBQXFCO1VBQXJCLDRCQUFxQixFQURIOztBQUdSO0VBQ1YsZUFBZTtFQUNmLFlBQVksRUFGTzs7QUFJVDtFQUNWLG1DQUFxQjtNQUFyQiwrQkFBcUI7VUFBckIsMkJBQXFCLEVBREo7O0FBSVI7RUFDVCxnQkFBZ0I7RUFDaEIsWUFBWTtFQUNaLFdBQVc7RUFDWCxhQUFhLEVBSkk7O0FBT1Y7RUFDUCxnQkFBZ0IsRUFETjs7QUFJTDtFQUNMLGlCQUFpQjtFQUNqQixhQUFhO0VBQ2IsY0FBYyxFQUhHOztBQUtuQjt1Q0FDdUM7QUMxSnZDOzJFQUMyRTtBQ0QzRTtFQUNFLGtCQUFrQixFQURaOztBQ0V3Qjs7O0VBQy9CLGVBQWUsRUFEdUI7O0FBSTJuQztFQUM5cEMsb0JBQW9CO0VBQ3BCLHNCQUFzQjtFQUN0QixlQUFlO0VBQ2Ysa0JBQWtCO0VBQ2xCLGlCQUFpQixFQUx1cEM7O0FDTjVxQztFQUNFLGlCQUFpQjtFQUNqQixtQkFBbUI7RUFDbkIsa0JBQWtCLEVBSFA7O0FDQUE7RUFDWCxlQUFlLEVBRE07O0FBSW9CO0VMSHpDLHVCQUEyQjtFQUMzQixzQkFBc0I7RUFDdEIsbUJBQXNCO0VBQ3RCLGtCQUFzQjtNQUF0QixjQUFzQixFS0EwQjs7QUFLbEM7RUxEZCxpQkFBaUI7RUFDakIsb0JBQW9CO0VBQ3BCLHdCQUF3QixFS0RGOztBTDJKeEI7RUFDMkQ7SUFDdkQsY0FBYyxFQURnRDtFQUduQztJQUMzQixlQUFlLEVBRG1CLEVBQUE7O0FBS3RDO0VBQytCO0lBQzNCLGNBQWMsRUFEb0IsRUFBQTs7QUFLOUI7RUFDTix3QkFBd0IsRUFEUDtFQUdUO0lBQ04sZUFBZSxFQURGIiwiZmlsZSI6ImluZGV4LmNzcyIsInNvdXJjZXNDb250ZW50IjpbIkBtaXhpbiB3b3JkLXdyYXAoKSB7XG4gIHdvcmQtYnJlYWs6ICAgICBicmVhay13b3JkO1xuICAtd2Via2l0LWh5cGhlbnM6IGF1dG87XG4gIC1tb3otaHlwaGVuczogICAgYXV0bztcbiAgaHlwaGVuczogICAgICAgICBhdXRvO1xufVxuXG5AbWl4aW4gZWxsaXBzaXMoKSB7XG4gIG92ZXJmbG93OiBoaWRkZW47XG4gIHdoaXRlLXNwYWNlOiBub3dyYXA7XG4gIHRleHQtb3ZlcmZsb3c6IGVsbGlwc2lzO1xufVxuXG4uc2l0ZSB7XG4gIGRpc3BsYXk6IGZsZXg7XG4gIG1pbi1oZWlnaHQ6IDEwMHZoO1xuICBmbGV4LWRpcmVjdGlvbjogY29sdW1uO1xufVxuXG5tYWluIHtcbiAgZmxleDogMTtcbn1cblxuLnVpLmxpc3QgLmxpc3QgPiAuaXRlbSAuZGVzY3JpcHRpb24sIC51aS5saXN0ID4gLml0ZW0gLmRlc2NyaXB0aW9uIHtcbiAgQGluY2x1ZGUgd29yZC13cmFwKCk7XG59XG4udWkuZ3JpZCB7XG4gIG1hcmdpbi1sZWZ0OiAwO1xuICBtYXJnaW4tcmlnaHQ6IDA7XG59XG5cbi51aS5jb250YWluZXIge1xuICBwb3NpdGlvbjogcmVsYXRpdmU7XG59XG5cbi51aS5zaWRlYmFyLm1lbnUge1xuICB6LWluZGV4OiAyMDAwMDtcbn1cblxuLnB1c2hhYmxlID4gLnB1c2hlci5kaW1tZWQgLnVpLmxvYWRpbmcuc2VnbWVudDpiZWZvcmUge1xuICB6LWluZGV4OiAxO1xufVxuXG4ucHVzaGFibGUgPiAucHVzaGVyIHtcbiAgcG9zaXRpb246IGFic29sdXRlO1xuICBsZWZ0OiAwO1xuICByaWdodDogMDtcbn1cblxuLnVpLmxlZnQuc2lkZWJhciB7XG4gIHRyYW5zZm9ybTogdHJhbnNsYXRlM2QoLTI2MHB4LCAwLCAwKTtcbn1cblxuLnVpLmxlZnQuZmxvYXRlZCB7XG4gIGZsb2F0OiBsZWZ0O1xufVxuXG4udWkucmlnaHQuZmxvYXRlZCB7XG4gIGZsb2F0OiByaWdodDtcbn1cblxuLmljb24uc3BpbiB7XG4gICAgLXdlYmtpdC1hbmltYXRpb246IHNwaW4gMTAwMG1zIGluZmluaXRlIGxpbmVhcjtcbiAgICBhbmltYXRpb246IHNwaW4gMTAwMG1zIGluZmluaXRlIGxpbmVhcjtcbn1cbkAtd2Via2l0LWtleWZyYW1lcyBzcGluIHtcbiAgICAwJSB7XG4gICAgICAgIC13ZWJraXQtdHJhbnNmb3JtOiByb3RhdGUoMGRlZyk7XG4gICAgICAgIHRyYW5zZm9ybTogcm90YXRlKDBkZWcpO1xuICAgIH1cbiAgICAxMDAlIHtcbiAgICAgICAgLXdlYmtpdC10cmFuc2Zvcm06IHJvdGF0ZSgzNTlkZWcpO1xuICAgICAgICB0cmFuc2Zvcm06IHJvdGF0ZSgzNTlkZWcpO1xuICAgIH1cbn1cbkBrZXlmcmFtZXMgc3BpbiB7XG4gICAgMCUge1xuICAgICAgICAtd2Via2l0LXRyYW5zZm9ybTogcm90YXRlKDBkZWcpO1xuICAgICAgICB0cmFuc2Zvcm06IHJvdGF0ZSgwZGVnKTtcbiAgICB9XG4gICAgMTAwJSB7XG4gICAgICAgIC13ZWJraXQtdHJhbnNmb3JtOiByb3RhdGUoMzU5ZGVnKTtcbiAgICAgICAgdHJhbnNmb3JtOiByb3RhdGUoMzU5ZGVnKTtcbiAgICB9XG59XG5cbi5zaG93LWxvYWRpbmcsIC5zaG93LWVycm9yLCAuc2hvdy1va3tcbiAgZGlzcGxheTogbm9uZTtcbn1cbi5sb2FkaW5nIC5zaG93LWxvYWRpbmcge1xuICBkaXNwbGF5OiBpbmxpbmUtYmxvY2s7XG59XG4uZXJyb3IgLnNob3ctZXJyb3Ige1xuICBkaXNwbGF5OiBpbmxpbmUtYmxvY2s7XG59XG4ub2sgLnNob3ctb2sge1xuICBkaXNwbGF5OiBpbmxpbmUtYmxvY2s7XG59XG5cbi5zdGF0cyB7XG4gIHBvc2l0aW9uOiBhYnNvbHV0ZTtcbiAgd2lkdGg6IDEwMHZ3O1xuICB0b3A6IDA7XG4gIGxlZnQ6IDUwJTtcbiAgei1pbmRleDogMTA7XG4gIHRyYW5zZm9ybTogdHJhbnNsYXRlWCgtMjAwJSk7XG4gIGJhY2tncm91bmQ6IHdoaXRlO1xuICB0cmFuc2l0aW9uOiBhbGwgMXMgZWFzZS1pbi1vdXQ7XG4gIGJvcmRlci1yaWdodDogMXB4ICM0NDQgc29saWQ7XG59XG4uZGltbWVyIHtcbiAgZGlzcGxheTogbm9uZTtcbiAgYmFja2dyb3VuZDogYmxhY2s7XG4gIG9wYWNpdHk6IDA7XG4gIHBvc2l0aW9uOiBmaXhlZDtcbiAgdG9wOiAwO1xuICBib3R0b206IDA7XG4gIGxlZnQ6IDA7XG4gIHJpZ2h0OiAwO1xuICB6LWluZGV4OiA1O1xuICB0cmFuc2l0aW9uOiBhbGwgMXMgZWFzZS1pbi1vdXQ7XG59XG4uc2l0ZSB7XG4gIG1pbi1oZWlnaHQ6IDEwMHZoO1xuICB0cmFuc2l0aW9uOiBhbGwgMXMgZWFzZS1pbi1vdXQ7XG59XG4uc2hvdy1zdGF0cyAuc3RhdHMge1xuICB0cmFuc2Zvcm06IHRyYW5zbGF0ZVgoLTUwJSk7XG59XG4uc2hvdy1zdGF0cyAuZGltbWVyIHtcbiAgZGlzcGxheTogYmxvY2s7XG4gIG9wYWNpdHk6IC41O1xufVxuLnNob3ctc3RhdHMgLnNpdGUge1xuICB0cmFuc2Zvcm06IHRyYW5zbGF0ZVgoNTAlKTtcbn1cblxuLnVpLnNlZ21lbnQuZml4ZWQge1xuICBwb3NpdGlvbjogZml4ZWQ7XG4gIGJvdHRvbTogMWVtO1xuICByaWdodDogMWVtO1xuICB6LWluZGV4OiAyMDA7XG59XG5cbmFbdWktc3JlZl0ge1xuICBjdXJzb3I6IHBvaW50ZXI7XG59XG5cbi5uYXJyb3cuY29udGFpbmVyIHtcbiAgbWF4LXdpZHRoOiA4MHJlbTtcbiAgbWFyZ2luOiBhdXRvO1xuICBwYWRkaW5nOiAzcmVtO1xufVxuLyogRG8gbm90IHJlbW92ZSB0aGlzIGNvbW1lbnRzIGJlbGxvdy4gSXQncyB0aGUgbWFya2VycyB1c2VkIGJ5IGd1bHAtaW5qZWN0IHRvIGluamVjdFxuICAgYWxsIHlvdXIgc2FzcyBmaWxlcyBhdXRvbWF0aWNhbGx5ICovXG4vLyBpbmplY3RvclxuQGltcG9ydCBcInZlbmRvci5zY3NzXCI7XG5AaW1wb3J0IFwiZm9vdGVyL2Zvb3Rlci5zY3NzXCI7XG5AaW1wb3J0IFwiY29tcG9uZW50cy9hdHRyaWJ1dGUtZmllbGQvYXR0cmlidXRlLWZpZWxkLnNjc3NcIjtcbkBpbXBvcnQgXCJjb21wb25lbnRzL2NvbGxlY3Rpb24vY29sbGVjdGlvbi5zY3NzXCI7XG5AaW1wb3J0IFwiY29tcG9uZW50cy9zZWFyY2gvc2VhcmNoLnNjc3NcIjtcbi8vIGVuZGluamVjdG9yXG5cblxuQG1lZGlhIG9ubHkgc2NyZWVuIGFuZCAobWF4LXdpZHRoOiAxMjAwcHgpe1xuICAuc2Vjb25kYXJ5LnBvaW50aW5nLm1lbnUgLml0ZW0sIC5zZWNvbmRhcnkucG9pbnRpbmcubWVudSAubWVudSB7XG4gICAgZGlzcGxheTogbm9uZTtcbiAgfVxuICAuc2Vjb25kYXJ5LnBvaW50aW5nLm1lbnUgLnRvYy5pdGVtIHtcbiAgICBkaXNwbGF5OiBibG9jaztcbiAgfVxufVxuXG5AbWVkaWEgb25seSBzY3JlZW4gYW5kIChtaW4td2lkdGg6IDEyMDFweCl7XG4gIC5zZWNvbmRhcnkucG9pbnRpbmcubWVudSAudG9jLml0ZW0ge1xuICAgIGRpc3BsYXk6IG5vbmU7XG4gIH1cbn1cblxuLnVpLm1pbmkuZHJvcGRvd24ge1xuICBmb250LXNpemU6IDAuNzE0Mjg1NzFlbTtcblxuICAubWVudSA+IC5pdGVtIHtcbiAgICBmb250LXNpemU6IDFlbTtcbiAgfVxufSIsIi8qIERvIG5vdCByZW1vdmUgdGhpcyBjb21tZW50cyBiZWxsb3cuIEl0J3MgdGhlIG1hcmtlcnMgdXNlZCBieSB3aXJlZGVwIHRvIGluamVjdFxuICAgc2FzcyBkZXBlbmRlbmNpZXMgd2hlbiBkZWZpbmVkIGluIHRoZSBib3dlci5qc29uIG9mIHlvdXIgZGVwZW5kZW5jaWVzICovXG4vLyBib3dlcjpzY3NzXG4vLyBlbmRib3dlclxuIiwiZm9vdGVyIHtcbiAgbWluLWhlaWdodDogMTByZW07XG59IiwiLnVpLmZvcm0gLmZpZWxkLnN1Y2Nlc3MgLmlucHV0LCAudWkuZm9ybSAuZmllbGQuc3VjY2VzcyBsYWJlbCxcbi51aS5mb3JtIC5maWVsZHMuc3VjY2VzcyAuZmllbGQgLmlucHV0LFxuLnVpLmZvcm0gLmZpZWxkcy5zdWNjZXNzIC5maWVsZCBsYWJlbCB7XG4gY29sb3I6ICMxYTUzMWI7XG59XG5cbi51aS5mb3JtIC5maWVsZC5zdWNjZXNzIGlucHV0Om5vdChbdHlwZV0pLCAudWkuZm9ybSAuZmllbGQuc3VjY2VzcyBpbnB1dFt0eXBlPXRleHRdLCAudWkuZm9ybSAuZmllbGQuc3VjY2VzcyBpbnB1dFt0eXBlPWVtYWlsXSwgLnVpLmZvcm0gLmZpZWxkLnN1Y2Nlc3MgaW5wdXRbdHlwZT1zZWFyY2hdLCAudWkuZm9ybSAuZmllbGQuc3VjY2VzcyBpbnB1dFt0eXBlPXBhc3N3b3JkXSwgLnVpLmZvcm0gLmZpZWxkLnN1Y2Nlc3MgaW5wdXRbdHlwZT1kYXRlXSwgLnVpLmZvcm0gLmZpZWxkLnN1Y2Nlc3MgaW5wdXRbdHlwZT1kYXRldGltZS1sb2NhbF0sIC51aS5mb3JtIC5maWVsZC5zdWNjZXNzIGlucHV0W3R5cGU9dGVsXSwgLnVpLmZvcm0gLmZpZWxkLnN1Y2Nlc3MgaW5wdXRbdHlwZT10aW1lXSwgLnVpLmZvcm0gLmZpZWxkLnN1Y2Nlc3MgaW5wdXRbdHlwZT11cmxdLCAudWkuZm9ybSAuZmllbGQuc3VjY2VzcyBpbnB1dFt0eXBlPW51bWJlcl0sIC51aS5mb3JtIC5maWVsZC5zdWNjZXNzIHNlbGVjdCwgLnVpLmZvcm0gLmZpZWxkLnN1Y2Nlc3MgdGV4dGFyZWEsIC51aS5mb3JtIC5maWVsZHMuc3VjY2VzcyAuZmllbGQgaW5wdXQ6bm90KFt0eXBlXSksIC51aS5mb3JtIC5maWVsZHMuc3VjY2VzcyAuZmllbGQgaW5wdXRbdHlwZT10ZXh0XSwgLnVpLmZvcm0gLmZpZWxkcy5zdWNjZXNzIC5maWVsZCBpbnB1dFt0eXBlPWVtYWlsXSwgLnVpLmZvcm0gLmZpZWxkcy5zdWNjZXNzIC5maWVsZCBpbnB1dFt0eXBlPXNlYXJjaF0sIC51aS5mb3JtIC5maWVsZHMuc3VjY2VzcyAuZmllbGQgaW5wdXRbdHlwZT1wYXNzd29yZF0sIC51aS5mb3JtIC5maWVsZHMuc3VjY2VzcyAuZmllbGQgaW5wdXRbdHlwZT1kYXRlXSwgLnVpLmZvcm0gLmZpZWxkcy5zdWNjZXNzIC5maWVsZCBpbnB1dFt0eXBlPWRhdGV0aW1lLWxvY2FsXSwgLnVpLmZvcm0gLmZpZWxkcy5zdWNjZXNzIC5maWVsZCBpbnB1dFt0eXBlPXRlbF0sIC51aS5mb3JtIC5maWVsZHMuc3VjY2VzcyAuZmllbGQgaW5wdXRbdHlwZT10aW1lXSwgLnVpLmZvcm0gLmZpZWxkcy5zdWNjZXNzIC5maWVsZCBpbnB1dFt0eXBlPXVybF0sIC51aS5mb3JtIC5maWVsZHMuc3VjY2VzcyAuZmllbGQgaW5wdXRbdHlwZT1udW1iZXJdLCAudWkuZm9ybSAuZmllbGRzLnN1Y2Nlc3MgLmZpZWxkIHNlbGVjdCwgLnVpLmZvcm0gLmZpZWxkcy5zdWNjZXNzIC5maWVsZCB0ZXh0YXJlYSB7XG4gICAgYmFja2dyb3VuZDogI2ZjZmZmNTtcbiAgICBib3JkZXItY29sb3I6ICNhM2MyOTM7XG4gICAgY29sb3I6ICMyYzY2MmQ7XG4gICAgYm9yZGVyLXJhZGl1czogJyc7XG4gICAgYm94LXNoYWRvdzogbm9uZTtcbn0iLCIuY29sbGVjdGlvbiB7XG4gIG1hcmdpbi10b3A6IDJyZW07XG4gIHBvc2l0aW9uOiByZWxhdGl2ZTtcbiAgbWluLWhlaWdodDogMjByZW07XG59XG4iLCIudWkuc2VhcmNoID4gLnJlc3VsdHMge1xuICBkaXNwbGF5OiBibG9jaztcbn1cblxuLnVpLmNhdGVnb3J5LnNlYXJjaCA+IC5yZXN1bHRzIC5jYXRlZ29yeSA+IC5uYW1lIHtcbiAgQGluY2x1ZGUgd29yZC13cmFwKCk7XG59XG5cbi51aS5zZWFyY2ggPiAucmVzdWx0cyAucmVzdWx0IHtcbiAgLmRlc2NyaXB0aW9uLCAudGl0bGUge1xuICAgIEBpbmNsdWRlIGVsbGlwc2lzKCk7XG4gIH1cbn0iXSwic291cmNlUm9vdCI6Ii9zb3VyY2UvIn0= */"],"sourceRoot":"/source/"}
--------------------------------------------------------------------------------
/dist-demo/styles/app-3086bb8113.css:
--------------------------------------------------------------------------------
1 | .dimmer,.stats{top:0;-webkit-transition:all 1s ease-in-out}.site{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column}main{-webkit-box-flex:1;-webkit-flex:1;-ms-flex:1;flex:1}.ui.list .list>.item .description,.ui.list>.item .description{word-break:break-word;-webkit-hyphens:auto;-moz-hyphens:auto;-ms-hyphens:auto;hyphens:auto}.ui.grid{margin-left:0;margin-right:0}.ui.container{position:relative}.ui.sidebar.menu{z-index:20000}.pushable>.pusher.dimmed .ui.loading.segment:before{z-index:1}.pushable>.pusher{position:absolute;left:0;right:0}.ui.left.sidebar{-webkit-transform:translate3d(-260px,0,0);transform:translate3d(-260px,0,0)}.ui.left.floated{float:left}.ui.right.floated{float:right}.icon.spin{-webkit-animation:spin 1s infinite linear;animation:spin 1s infinite linear}@-webkit-keyframes spin{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes spin{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.show-error,.show-loading,.show-ok{display:none}.error .show-error,.loading .show-loading,.ok .show-ok{display:inline-block}.stats{position:absolute;width:100vw;left:50%;z-index:10;-webkit-transform:translateX(-200%);-ms-transform:translateX(-200%);transform:translateX(-200%);background:#fff;transition:all 1s ease-in-out;border-right:1px #444 solid}.dimmer{display:none;background:#000;opacity:0;position:fixed;bottom:0;left:0;right:0;z-index:5;transition:all 1s ease-in-out}.site{min-height:100vh;-webkit-transition:all 1s ease-in-out;transition:all 1s ease-in-out}.show-stats .stats{-webkit-transform:translateX(-50%);-ms-transform:translateX(-50%);transform:translateX(-50%)}.show-stats .dimmer{display:block;opacity:.5}.show-stats .site{-webkit-transform:translateX(50%);-ms-transform:translateX(50%);transform:translateX(50%)}.ui.segment.fixed{position:fixed;bottom:1em;right:1em;z-index:200}a[ui-sref]{cursor:pointer}.narrow.container{max-width:80rem;margin:auto;padding:3rem}footer{min-height:10rem}.ui.form .field.success .input,.ui.form .field.success label,.ui.form .fields.success .field .input,.ui.form .fields.success .field label{color:#1a531b}.ui.form .field.success input:not([type]),.ui.form .field.success input[type=date],.ui.form .field.success input[type=datetime-local],.ui.form .field.success input[type=email],.ui.form .field.success input[type=number],.ui.form .field.success input[type=password],.ui.form .field.success input[type=search],.ui.form .field.success input[type=tel],.ui.form .field.success input[type=text],.ui.form .field.success input[type=time],.ui.form .field.success input[type=url],.ui.form .field.success select,.ui.form .field.success textarea,.ui.form .fields.success .field input:not([type]),.ui.form .fields.success .field input[type=date],.ui.form .fields.success .field input[type=datetime-local],.ui.form .fields.success .field input[type=email],.ui.form .fields.success .field input[type=number],.ui.form .fields.success .field input[type=password],.ui.form .fields.success .field input[type=search],.ui.form .fields.success .field input[type=tel],.ui.form .fields.success .field input[type=text],.ui.form .fields.success .field input[type=time],.ui.form .fields.success .field input[type=url],.ui.form .fields.success .field select,.ui.form .fields.success .field textarea{background:#fcfff5;border-color:#a3c293;color:#2c662d;border-radius:'';box-shadow:none}.collection{margin-top:2rem;position:relative;min-height:20rem}.ui.search>.results{display:block}.ui.category.search>.results .category>.name{word-break:break-word;-webkit-hyphens:auto;-moz-hyphens:auto;-ms-hyphens:auto;hyphens:auto}.ui.search>.results .result .description,.ui.search>.results .result .title{overflow:hidden;white-space:nowrap;text-overflow:ellipsis}@media only screen and (max-width:1200px){.secondary.pointing.menu .item,.secondary.pointing.menu .menu{display:none}.secondary.pointing.menu .toc.item{display:block}}@media only screen and (min-width:1201px){.secondary.pointing.menu .toc.item{display:none}}.ui.mini.dropdown{font-size:.71428571em}.ui.mini.dropdown .menu>.item{font-size:1em}
2 | /*# sourceMappingURL=../maps/styles/app-3086bb8113.css.map */
--------------------------------------------------------------------------------
/gulp/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "node": true
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/gulp/build.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var path = require('path');
4 | var gulp = require('gulp');
5 | var conf = require('./conf');
6 |
7 | var $ = require('gulp-load-plugins')({
8 | pattern: ['gulp-*', 'main-bower-files', 'uglify-save-license', 'del']
9 | });
10 |
11 | gulp.task('build:lib', function() {
12 | return gulp.src([
13 | path.join(conf.paths.lib, '/**/*.js'),
14 | path.join('!' + conf.paths.lib, '/**/*.spec.js'),
15 | path.join('!' + conf.paths.lib, '/**/*.mock.js')
16 | ])
17 | .pipe($.sourcemaps.init())
18 | .pipe($.ngAnnotate())
19 | .pipe($.angularFilesort())
20 | .pipe($.concat('angular-jsonapi.js'))
21 | .pipe($.sourcemaps.write('./'))
22 | .pipe(gulp.dest(conf.paths.dist.lib + '/'))
23 | .pipe($.filter('angular-jsonapi.js'))
24 | .pipe($.rename('angular-jsonapi.min.js'))
25 |
26 | .pipe($.uglify({ preserveComments: $.uglifySaveLicense })).on('error', conf.errorHandler('Uglify'))
27 | .pipe($.sourcemaps.write('./'))
28 | .pipe(gulp.dest(conf.paths.dist.lib + '/'))
29 | .pipe($.size({ title: conf.paths.dist.lib + '/', showFiles: true }));
30 | });
31 |
32 | gulp.task('partials', function() {
33 | return gulp.src([
34 | path.join(conf.paths.src, '/app/**/*.html'),
35 | path.join(conf.paths.tmp, '/serve/app/**/*.html')
36 | ])
37 | .pipe($.minifyHtml({
38 | empty: true,
39 | spare: true,
40 | quotes: true
41 | }))
42 | .pipe($.angularTemplatecache('templateCacheHtml.js', {
43 | module: 'angularJsonapiExample',
44 | root: 'app'
45 | }))
46 | .pipe(gulp.dest(conf.paths.tmp + '/partials/'));
47 | });
48 |
49 | gulp.task('html', ['inject', 'partials'], function() {
50 | var partialsInjectFile = gulp.src(path.join(conf.paths.tmp, '/partials/templateCacheHtml.js'), { read: false });
51 | var partialsInjectOptions = {
52 | starttag: '',
53 | ignorePath: path.join(conf.paths.tmp, '/partials'),
54 | addRootSlash: false
55 | };
56 |
57 | var htmlFilter = $.filter('*.html', { restore: true });
58 | var jsFilter = $.filter('**/*.js', { restore: true });
59 | var cssFilter = $.filter('**/*.css', { restore: true });
60 | var assets;
61 |
62 | return gulp.src(path.join(conf.paths.tmp, '/serve/*.html'))
63 | .pipe($.inject(partialsInjectFile, partialsInjectOptions))
64 | .pipe(assets = $.useref.assets())
65 | .pipe($.rev())
66 | .pipe(jsFilter)
67 | .pipe($.replace('localhost:3000', 'jsonapi-robot-wars.herokuapp.com'))
68 | .pipe($.sourcemaps.init())
69 | .pipe($.ngAnnotate())
70 | .pipe($.uglify({ preserveComments: $.uglifySaveLicense })).on('error', conf.errorHandler('Uglify'))
71 | .pipe($.sourcemaps.write('maps'))
72 | .pipe(jsFilter.restore)
73 | .pipe(cssFilter)
74 | .pipe($.replace('themes/default/assets/', '../'))
75 | .pipe($.sourcemaps.init())
76 | .pipe($.minifyCss({ processImport: false }))
77 | .pipe($.sourcemaps.write('maps'))
78 | .pipe(cssFilter.restore)
79 | .pipe(assets.restore())
80 | .pipe($.useref())
81 | .pipe($.revReplace())
82 | .pipe(htmlFilter)
83 | .pipe($.minifyHtml({
84 | empty: true,
85 | spare: true,
86 | quotes: true,
87 | conditionals: true
88 | }))
89 | .pipe(htmlFilter.restore)
90 | .pipe(gulp.dest(path.join(conf.paths.dist.demo, '/')))
91 | .pipe($.size({ title: path.join(conf.paths.dist.demo, '/'), showFiles: true }));
92 | });
93 |
94 | gulp.task('fonts', function() {
95 | return gulp.src($.mainBowerFiles({includeDev: 'inclusive', overrides: conf.overrides}))
96 | .pipe($.filter('**/*.{eot,svg,ttf,woff,woff2}'))
97 |
98 | .pipe($.flatten())
99 | .pipe(gulp.dest(path.join(conf.paths.dist.demo, '/fonts/')));
100 | });
101 |
102 | gulp.task('other', function() {
103 | var fileFilter = $.filter(function(file) {
104 | return file.stat.isFile();
105 | });
106 |
107 | return gulp.src([
108 | path.join(conf.paths.src, '/**/*'),
109 | path.join('!' + conf.paths.src, '/**/*.{html,css,js,scss}')
110 | ])
111 | .pipe(fileFilter)
112 | .pipe(gulp.dest(path.join(conf.paths.dist.demo, '/')));
113 | });
114 |
115 | gulp.task('clean', function() {
116 | return $.del([
117 | path.join(conf.paths.dist.demo, '/'),
118 | path.join(conf.paths.tmp, '/'),
119 | path.join(conf.paths.dist.lib, '/')
120 | ]);
121 | });
122 |
123 | gulp.task('build:demo', ['html', 'fonts', 'other']);
124 |
--------------------------------------------------------------------------------
/gulp/conf.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This file contains the variables used in other gulp files
3 | * which defines tasks
4 | * By design, we only put there very generic config values
5 | * which are used in several places to keep good readability
6 | * of the tasks
7 | */
8 |
9 | var gutil = require('gulp-util');
10 |
11 | /**
12 | * The main paths of your project handle these with care
13 | */
14 | exports.paths = {
15 | src: 'demo',
16 | lib: 'src',
17 | dist: {
18 | lib: 'dist',
19 | demo: 'dist-demo'
20 | },
21 | tmp: '.tmp',
22 | e2e: 'e2e'
23 | };
24 |
25 | exports.overrides = {
26 | semantic: {
27 | main: [
28 | 'dist/semantic.css',
29 | 'dist/semantic.js',
30 | 'dist/themes/default/assets/fonts/icons.eot',
31 | 'dist/themes/default/assets/fonts/icons.otf',
32 | 'dist/themes/default/assets/fonts/icons.svg',
33 | 'dist/themes/default/assets/fonts/icons.ttf',
34 | 'dist/themes/default/assets/fonts/icons.woff',
35 | 'dist/themes/default/assets/fonts/icons.woff2'
36 | ]
37 | }
38 | };
39 |
40 | /**
41 | * Wiredep is the lib which inject bower dependencies in your project
42 | * Mainly used to inject script tags in the index.html but also used
43 | * to inject css preprocessor deps and js files in karma
44 | */
45 | exports.wiredep = {
46 | exclude: [/bootstrap.js$/, /bootstrap-sass-official\/.*\.js/, /bootstrap\.css/, /semantic\.less/],
47 | devDependencies: true,
48 | directory: 'bower_components',
49 | overrides: exports.overrides
50 | };
51 |
52 | /**
53 | * Common implementation for an error handler of a Gulp plugin
54 | */
55 | exports.errorHandler = function(title) {
56 | 'use strict';
57 |
58 | return function(err) {
59 | gutil.log(gutil.colors.red('[' + title + ']'), err.toString());
60 | this.emit('end');
61 | };
62 | };
63 |
--------------------------------------------------------------------------------
/gulp/e2e-tests.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var path = require('path');
4 | var gulp = require('gulp');
5 | var conf = require('./conf');
6 |
7 | var browserSync = require('browser-sync');
8 |
9 | var $ = require('gulp-load-plugins')();
10 |
11 | // Downloads the selenium webdriver
12 | gulp.task('webdriver-update', $.protractor.webdriver_update);
13 |
14 | gulp.task('webdriver-standalone', $.protractor.webdriver_standalone);
15 |
16 | function runProtractor (done) {
17 | var params = process.argv;
18 | var args = params.length > 3 ? [params[3], params[4]] : [];
19 |
20 | gulp.src(path.join(conf.paths.e2e, '/**/*.js'))
21 | .pipe($.protractor.protractor({
22 | configFile: 'protractor.conf.js',
23 | args: args
24 | }))
25 | .on('error', function (err) {
26 | // Make sure failed tests cause gulp to exit non-zero
27 | throw err;
28 | })
29 | .on('end', function () {
30 | // Close browser sync server
31 | browserSync.exit();
32 | done();
33 | });
34 | }
35 |
36 | gulp.task('protractor', ['protractor:src']);
37 | gulp.task('protractor:src', ['serve:e2e', 'webdriver-update'], runProtractor);
38 | gulp.task('protractor:dist', ['serve:e2e-dist', 'webdriver-update'], runProtractor);
39 |
--------------------------------------------------------------------------------
/gulp/inject.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var path = require('path');
4 | var gulp = require('gulp');
5 | var conf = require('./conf');
6 |
7 | var $ = require('gulp-load-plugins')();
8 |
9 | var wiredep = require('wiredep').stream;
10 | var _ = require('lodash');
11 |
12 | gulp.task('inject', ['scripts', 'styles'], function() {
13 | var injectStyles = gulp.src([
14 | path.join(conf.paths.tmp, '/serve/app/**/*.css'),
15 | path.join('!' + conf.paths.tmp, '/serve/app/vendor.css')
16 | ], { read: false });
17 |
18 | var injectScripts = gulp.src([
19 | path.join(conf.paths.src, '/app/**/*.module.js'),
20 | path.join(conf.paths.src, '/app/**/*.js'),
21 | path.join('!' + conf.paths.src, '/app/**/*.spec.js'),
22 | path.join('!' + conf.paths.src, '/app/**/*.mock.js')
23 | ])
24 | .pipe($.angularFilesort()).on('error', conf.errorHandler('AngularFilesort'));
25 |
26 | var injectLib = gulp.src([
27 | path.join(conf.paths.lib, '/app/**/*.module.js'),
28 | path.join(conf.paths.lib, '/**/*.js'),
29 | path.join('!' + conf.paths.lib, '/**/*.spec.js'),
30 | path.join('!' + conf.paths.lib, '/**/*.mock.js')
31 | ])
32 | .pipe($.angularFilesort()).on('error', conf.errorHandler('AngularFilesort'));
33 |
34 | var injectOptions = {
35 | ignorePath: [conf.paths.src, path.join(conf.paths.tmp, '/serve')],
36 | addRootSlash: false
37 | };
38 |
39 | var injectLibOptions = {
40 | ignorePath: [conf.paths.src, path.join(conf.paths.tmp, '/serve')],
41 | addRootSlash: false,
42 | name: 'lib'
43 | };
44 |
45 | return gulp.src(path.join(conf.paths.src, '/*.html'))
46 | .pipe($.inject(injectStyles, injectOptions))
47 | .pipe($.inject(injectScripts, injectOptions))
48 | .pipe($.inject(injectLib, injectLibOptions))
49 | .pipe(wiredep(_.extend({}, conf.wiredep)))
50 | .pipe(gulp.dest(path.join(conf.paths.tmp, '/serve')));
51 | });
52 |
--------------------------------------------------------------------------------
/gulp/scripts.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var path = require('path');
4 | var gulp = require('gulp');
5 | var conf = require('./conf');
6 |
7 | var browserSync = require('browser-sync');
8 |
9 | var $ = require('gulp-load-plugins')();
10 |
11 | gulp.task('scripts', function() {
12 | return gulp.src([
13 | path.join(conf.paths.src, '/app/**/*.js'),
14 | path.join(conf.paths.lib, '/**/*.js')
15 | ])
16 | .pipe($.eslint())
17 | .pipe($.eslint.format())
18 | .pipe(browserSync.reload({ stream: true }))
19 | .pipe($.size());
20 | });
21 |
--------------------------------------------------------------------------------
/gulp/server.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var path = require('path');
4 | var gulp = require('gulp');
5 | var conf = require('./conf');
6 |
7 | var browserSync = require('browser-sync');
8 | var browserSyncSpa = require('browser-sync-spa');
9 |
10 | var util = require('util');
11 |
12 | var proxyMiddleware = require('http-proxy-middleware');
13 |
14 | function browserSyncInit(baseDir, browser) {
15 | browser = browser === undefined ? 'default' : browser;
16 |
17 | var routes = null;
18 | if (baseDir === conf.paths.src || (util.isArray(baseDir) && baseDir.indexOf(conf.paths.src) !== -1)) {
19 | routes = {
20 | '/bower_components': 'bower_components'
21 | };
22 |
23 | routes['/' + conf.paths.lib] = conf.paths.lib;
24 | }
25 |
26 | var server = {
27 | baseDir: baseDir,
28 | routes: routes
29 | };
30 |
31 | /*
32 | * You can add a proxy to your backend by uncommenting the line bellow.
33 | * You just have to configure a context which will we redirected and the target url.
34 | * Example: $http.get('/users') requests will be automatically proxified.
35 | *
36 | * For more details and option, https://github.com/chimurai/http-proxy-middleware/blob/v0.0.5/README.md
37 | */
38 | // server.middleware = proxyMiddleware('/users', {target: 'http://jsonplaceholder.typicode.com', proxyHost: 'jsonplaceholder.typicode.com'});
39 |
40 | browserSync.instance = browserSync.init({
41 | startPath: '/',
42 | port: 5000,
43 | server: server,
44 | browser: browser
45 | });
46 | }
47 |
48 | browserSync.use(browserSyncSpa({
49 | selector: '[ng-app]'// Only needed for angular apps
50 | }));
51 |
52 | gulp.task('serve', ['watch'], function() {
53 | browserSyncInit([path.join(conf.paths.tmp, '/serve'), conf.paths.src]);
54 | });
55 |
56 | gulp.task('serve:dist', ['build:demo'], function() {
57 | browserSyncInit(conf.paths.dist.demo);
58 | });
59 |
60 | gulp.task('serve:e2e', ['inject'], function() {
61 | browserSyncInit([conf.paths.tmp + '/serve', conf.paths.src], []);
62 | });
63 |
64 | gulp.task('serve:e2e-dist', ['build:lib'], function() {
65 | browserSyncInit(conf.paths.dist.lib, []);
66 | });
67 |
--------------------------------------------------------------------------------
/gulp/styles.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var path = require('path');
4 | var gulp = require('gulp');
5 | var conf = require('./conf');
6 |
7 | var browserSync = require('browser-sync');
8 |
9 | var $ = require('gulp-load-plugins')();
10 |
11 | var wiredep = require('wiredep').stream;
12 | var _ = require('lodash');
13 |
14 | gulp.task('styles', function() {
15 | var sassOptions = {
16 | style: 'expanded'
17 | };
18 |
19 | var injectFiles = gulp.src([
20 | path.join(conf.paths.src, '/app/**/*.scss'),
21 | path.join('!' + conf.paths.src, '/app/index.scss')
22 | ], { read: false });
23 |
24 | var injectOptions = {
25 | transform: function(filePath) {
26 | filePath = filePath.replace(conf.paths.src + '/app/', '');
27 | return '@import "' + filePath + '";';
28 | },
29 |
30 | starttag: '// injector',
31 | endtag: '// endinjector',
32 | addRootSlash: false
33 | };
34 |
35 | return gulp.src([
36 | path.join(conf.paths.src, '/app/index.scss')
37 | ])
38 | .pipe($.inject(injectFiles, injectOptions))
39 | .pipe(wiredep(_.extend({}, conf.wiredep)))
40 | .pipe($.sourcemaps.init())
41 | .pipe($.sass(sassOptions)).on('error', conf.errorHandler('Sass'))
42 | .pipe($.autoprefixer()).on('error', conf.errorHandler('Autoprefixer'))
43 | .pipe($.sourcemaps.write())
44 | .pipe(gulp.dest(path.join(conf.paths.tmp, '/serve/app/')))
45 | .pipe(browserSync.reload({ stream: true }));
46 | });
47 |
--------------------------------------------------------------------------------
/gulp/unit-tests.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var path = require('path');
4 | var gulp = require('gulp');
5 | var conf = require('./conf');
6 |
7 | var karma = require('karma');
8 |
9 | function runTests (singleRun, done) {
10 | karma.server.start({
11 | configFile: path.join(__dirname, '/../karma.conf.js'),
12 | singleRun: singleRun,
13 | autoWatch: !singleRun
14 | }, function() {
15 | done();
16 | });
17 | }
18 |
19 | gulp.task('test', ['scripts'], function(done) {
20 | runTests(true, done);
21 | });
22 |
23 | gulp.task('test:auto', ['watch'], function(done) {
24 | runTests(false, done);
25 | });
26 |
--------------------------------------------------------------------------------
/gulp/watch.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var path = require('path');
4 | var gulp = require('gulp');
5 | var conf = require('./conf');
6 |
7 | var browserSync = require('browser-sync');
8 |
9 | function isOnlyChange(event) {
10 | return event.type === 'changed';
11 | }
12 |
13 | gulp.task('watch', ['inject'], function() {
14 |
15 | gulp.watch([path.join(conf.paths.src, '/*.html'), 'bower.json'], ['inject']);
16 |
17 | gulp.watch([
18 | path.join(conf.paths.src, '/app/**/*.css'),
19 | path.join(conf.paths.src, '/app/**/*.scss')
20 | ], function(event) {
21 | if (isOnlyChange(event)) {
22 | gulp.start('styles');
23 | } else {
24 | gulp.start('inject');
25 | }
26 | });
27 |
28 | gulp.watch([
29 | path.join(conf.paths.src, '/app/**/*.js'),
30 | path.join(conf.paths.lib, '/**/*.js')
31 | ], function(event) {
32 | if (isOnlyChange(event)) {
33 | gulp.start('scripts');
34 | } else {
35 | gulp.start('inject');
36 | }
37 | });
38 |
39 | gulp.watch(path.join(conf.paths.lib, '/**/*.js'), function() {
40 | gulp.start('build:lib');
41 | });
42 |
43 | gulp.watch(path.join(conf.paths.src, '/app/**/*.html'), function(event) {
44 | browserSync.reload(event.path);
45 | });
46 | });
47 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Welcome to your gulpfile!
3 | * The gulp tasks are splitted in several files in the gulp directory
4 | * because putting all here was really too long
5 | */
6 |
7 | 'use strict';
8 |
9 | var gulp = require('gulp');
10 | var wrench = require('wrench');
11 |
12 | /**
13 | * This will load all js or coffee files in the gulp directory
14 | * in order to load all gulp tasks
15 | */
16 | wrench.readdirSyncRecursive('./gulp').filter(function(file) {
17 | return (/\.(js|coffee)$/i).test(file);
18 | }).map(function(file) {
19 | require('./gulp/' + file);
20 | });
21 |
22 |
23 | /**
24 | * Default task clean temporaries directories and launch the
25 | * main optimization build task
26 | */
27 | gulp.task('default', ['clean'], function() {
28 | gulp.start('build:lib');
29 | gulp.start('build:demo');
30 | });
31 |
--------------------------------------------------------------------------------
/karma.conf.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var path = require('path');
4 | var conf = require('./gulp/conf');
5 |
6 | var _ = require('lodash');
7 | var wiredep = require('wiredep');
8 |
9 | function listFiles() {
10 | var wiredepOptions = _.extend({}, conf.wiredep, {
11 | dependencies: true,
12 | devDependencies: true
13 | });
14 |
15 | return wiredep(wiredepOptions).js
16 | .concat([
17 | path.join(conf.paths.lib, '/**/*.module.js'),
18 | path.join(conf.paths.lib, '/**/*.js'),
19 | path.join(conf.paths.lib, '/**/*.spec.js'),
20 | path.join(conf.paths.lib, '/**/*.mock.js'),
21 | path.join(conf.paths.lib, '/**/*.html')
22 | ]);
23 | }
24 |
25 | module.exports = function(config) {
26 |
27 | var configuration = {
28 | files: listFiles(),
29 |
30 | singleRun: true,
31 |
32 | autoWatch: false,
33 |
34 | frameworks: ['jasmine', 'angular-filesort'],
35 |
36 | angularFilesort: {
37 | whitelist: [path.join(conf.paths.lib, '/**/!(*.html|*.spec|*.mock).js')]
38 | },
39 |
40 | ngHtml2JsPreprocessor: {
41 | stripPrefix: 'src/',
42 | moduleName: 'angularJsonapi'
43 | },
44 |
45 | browsers : ['PhantomJS'],
46 |
47 | plugins : [
48 | 'karma-phantomjs-launcher',
49 | 'karma-angular-filesort',
50 | 'karma-jasmine',
51 | 'karma-ng-html2js-preprocessor'
52 | ],
53 |
54 | preprocessors: {
55 | 'src/**/*.html': ['ng-html2js']
56 | }
57 | };
58 |
59 | // This block is needed to execute Chrome on Travis
60 | // If you ever plan to use Chrome and Travis, you can keep it
61 | // If not, you can safely remove it
62 | // https://github.com/karma-runner/karma/issues/1144#issuecomment-53633076
63 | if(configuration.browsers[0] === 'Chrome' && process.env.TRAVIS) {
64 | configuration.customLaunchers = {
65 | 'chrome-travis-ci': {
66 | base: 'Chrome',
67 | flags: ['--no-sandbox']
68 | }
69 | };
70 | configuration.browsers = ['chrome-travis-ci'];
71 | }
72 |
73 | config.set(configuration);
74 | };
75 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular-jsonapi",
3 | "version": "1.0.0-alpha.7",
4 | "dependencies": {},
5 | "scripts": {
6 | "test": "gulp test"
7 | },
8 | "devDependencies": {
9 | "browser-sync": "~2.9.2",
10 | "browser-sync-spa": "~1.0.2",
11 | "chalk": "~1.1.1",
12 | "del": "~2.0.1",
13 | "eslint-plugin-angular": "~0.11.0",
14 | "gulp": "~3.9.0",
15 | "gulp-angular-filesort": "~1.1.1",
16 | "gulp-angular-templatecache": "~1.7.0",
17 | "gulp-autoprefixer": "~3.0.1",
18 | "gulp-concat": "^2.6.0",
19 | "gulp-debug": "^2.1.0",
20 | "gulp-eslint": "~1.0.0",
21 | "gulp-filter": "~3.0.1",
22 | "gulp-flatten": "~0.2.0",
23 | "gulp-inject": "~1.5.0",
24 | "gulp-load-plugins": "~0.10.0",
25 | "gulp-minify-css": "~1.2.1",
26 | "gulp-minify-html": "~1.0.4",
27 | "gulp-ng-annotate": "~1.1.0",
28 | "gulp-protractor": "~1.0.0",
29 | "gulp-rename": "~1.2.2",
30 | "gulp-replace": "~0.5.4",
31 | "gulp-rev": "~6.0.1",
32 | "gulp-rev-replace": "~0.4.2",
33 | "gulp-sass": "~2.0.4",
34 | "gulp-size": "~2.0.0",
35 | "gulp-sourcemaps": "~1.5.2",
36 | "gulp-uglify": "~1.4.0",
37 | "gulp-useref": "~1.3.0",
38 | "gulp-util": "~3.0.6",
39 | "http-proxy-middleware": "~0.8.0",
40 | "karma": "~0.13.9",
41 | "karma-angular-filesort": "~1.0.0",
42 | "karma-coverage": "~0.5.2",
43 | "karma-jasmine": "~0.3.6",
44 | "karma-ng-html2js-preprocessor": "~0.1.2",
45 | "karma-phantomjs-launcher": "~0.2.1",
46 | "lodash": "~3.10.1",
47 | "main-bower-files": "~2.9.0",
48 | "uglify-save-license": "~0.4.1",
49 | "wiredep": "~2.2.2",
50 | "wrench": "~1.5.8"
51 | },
52 | "engines": {
53 | "node": ">=0.10.0"
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/protractor.conf.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var paths = require('./.yo-rc.json')['generator-gulp-angular'].props.paths;
4 |
5 | // An example configuration file.
6 | exports.config = {
7 | // The address of a running selenium server.
8 | //seleniumAddress: 'http://localhost:4444/wd/hub',
9 | //seleniumServerJar: deprecated, this should be set on node_modules/protractor/config.json
10 |
11 | // Capabilities to be passed to the webdriver instance.
12 | capabilities: {
13 | 'browserName': 'chrome'
14 | },
15 |
16 | baseUrl: 'http://localhost:3000',
17 |
18 | // Spec patterns are relative to the current working directly when
19 | // protractor is called.
20 | specs: [paths.e2e + '/**/*.js'],
21 |
22 | // Options to be passed to Jasmine-node.
23 | jasmineNodeOpts: {
24 | showColors: true,
25 | defaultTimeoutInterval: 30000
26 | }
27 | };
28 |
--------------------------------------------------------------------------------
/src/collection/collection.factory.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | 'use strict';
3 |
4 | angular.module('angular-jsonapi')
5 | .factory('AngularJsonAPICollection', AngularJsonAPICollectionWrapper);
6 |
7 | function AngularJsonAPICollectionWrapper(
8 | AngularJsonAPIModelSourceError,
9 | AngularJsonAPIModelErrorsManager,
10 | $rootScope,
11 | $injector,
12 | $q
13 | ) {
14 |
15 | AngularJsonAPICollection.prototype.fetch = fetch;
16 | AngularJsonAPICollection.prototype.refresh = fetch;
17 | AngularJsonAPICollection.prototype.get = get;
18 | AngularJsonAPICollection.prototype.hasErrors = hasErrors;
19 |
20 | return {
21 | create: AngularJsonAPICollectionFactory
22 | };
23 |
24 | function AngularJsonAPICollectionFactory(resource, params) {
25 | return new AngularJsonAPICollection(resource, params);
26 | }
27 |
28 | /**
29 | * Constructor
30 | * @param {AngularJsonAPIResource} resource Factory associated with the collection
31 | * @param {object} params Params associated with this resource (such as filters)
32 | */
33 | function AngularJsonAPICollection(resource, params) {
34 | var _this = this;
35 |
36 | _this.resource = resource;
37 | _this.type = resource.schema.type;
38 | _this.params = params || {};
39 |
40 | _this.errors = {
41 | synchronization: AngularJsonAPIModelErrorsManager.create(
42 | 'Source',
43 | 'Errors of synchronizations',
44 | AngularJsonAPIModelSourceError
45 | )
46 | };
47 |
48 | _this.data = _this.resource.cache.index(_this.params);
49 |
50 | _this.loading = false;
51 | _this.loadingCount = 0;
52 | _this.synchronized = false;
53 | _this.pristine = _this.data === undefined;
54 |
55 | _this.promise = $q.resolve(_this);
56 |
57 | var onObjectRemove = $rootScope.$on('angularJsonAPI:' + _this.type + ':object:remove', remove);
58 | var onFactoryClear = $rootScope.$on('angularJsonAPI:' + _this.type + ':resource:clearCache', clear);
59 | var onObjectAdd = $rootScope.$on('angularJsonAPI:' + _this.type + ':object:add', add);
60 |
61 | $rootScope.$on('$destroy', clearWatchers);
62 |
63 | function remove(event, status, object) {
64 | var index;
65 |
66 | if (status === 'resolved' && _this.data !== undefined) {
67 | index = _this.data.indexOf(object);
68 | if (index > -1) {
69 | _this.data.splice(index, 1);
70 | _this.resource.cache.setIndexIds(_this.data);
71 | }
72 | }
73 | }
74 |
75 | function clear() {
76 | _this.data = undefined;
77 | _this.pristine = true;
78 | }
79 |
80 | function add(event, status, object) {
81 | if (status === 'resolved') {
82 | _this.data = _this.data || [];
83 | _this.data.push(object);
84 | }
85 | }
86 |
87 | function clearWatchers() {
88 | onObjectRemove();
89 | onFactoryClear();
90 | onObjectAdd();
91 | }
92 | }
93 |
94 | /**
95 | * Check if the object has errors
96 | * @return {Boolean}
97 | */
98 | function hasErrors() {
99 | var _this = this;
100 | var answer = false;
101 |
102 | angular.forEach(_this.errors, function(error) {
103 | answer = error.hasErrors() || answer;
104 | });
105 |
106 | return answer;
107 | }
108 |
109 | /**
110 | * Shortcut to this.resource.get
111 | * @param {string} id Id of object]
112 | * @return {AngularJsonAPIModel} Model with id
113 | */
114 | function get(id, params) {
115 | var _this = this;
116 |
117 | return _this.resource.get(id, params);
118 | }
119 |
120 | /**
121 | * Synchronizes collection with the server
122 | * @return {promise} Promise associated with synchronization that resolves to this
123 | */
124 | function fetch() {
125 | var _this = this;
126 | var deferred = $q.defer();
127 | var $jsonapi = $injector.get('$jsonapi');
128 | var config = {
129 | action: 'all',
130 | params: _this.params
131 | };
132 |
133 | __incrementLoadingCounter(_this);
134 |
135 | angular.forEach(_this.data, __incrementLoadingCounter);
136 |
137 | _this.resource.synchronizer.synchronize(config)
138 | .then(resolve, reject, notify)
139 | .finally(__decrementLoadingCounter.bind(_this, undefined));
140 |
141 | return deferred.promise;
142 |
143 | function resolve(response) {
144 | var results = $jsonapi.__proccesResults(response.data);
145 | $rootScope.$emit('angularJsonAPI:' + _this.type + ':collection:fetch', 'resolved', _this, response);
146 | $q.allSettled(results.included.map(synchronizeIncluded)).then(resolveIncluded, deferred.reject);
147 |
148 | angular.forEach(_this.data, __decrementLoadingCounter);
149 |
150 | _this.data = results.data;
151 | _this.links = response.data.links;
152 |
153 | _this.updatedAt = Date.now();
154 | _this.synchronized = true;
155 | _this.pristine = false;
156 |
157 | _this.resource.cache.setIndexIds(_this.data);
158 | response.finish();
159 | _this.errors.synchronization.concat(response.errors);
160 |
161 | function synchronizeIncluded(object) {
162 | __incrementLoadingCounter(object);
163 |
164 | return object.synchronize({
165 | action: 'include',
166 | object: object
167 | }).finally(__decrementLoadingCounter.bind(object, undefined));
168 | }
169 |
170 | function resolveIncluded(includedResponse) {
171 | angular.forEach(includedResponse, function(operation, key) {
172 | if (operation.success === true) {
173 | $rootScope.$emit('angularJsonAPI:' + results.included[key].data.type + ':object:include', 'resolved', results.included[key], operation);
174 |
175 | operation.value.finish();
176 | }
177 | });
178 |
179 | deferred.resolve(response.data.meta);
180 | }
181 | }
182 |
183 | function reject(response) {
184 | $rootScope.$emit('angularJsonAPI:' + _this.type + ':collection:fetch', 'rejected', _this, response);
185 |
186 | angular.forEach(_this.data, __decrementLoadingCounter);
187 | response.finish();
188 | _this.errors.synchronization.concat(response.errors);
189 | deferred.reject(_this);
190 | }
191 |
192 | function notify(response) {
193 | $rootScope.$emit('angularJsonAPI:' + _this.type + ':collection:fetch', 'notify', _this, response);
194 |
195 | deferred.notify(response);
196 | }
197 | }
198 | }
199 |
200 | function __incrementLoadingCounter(object) {
201 | object = object === undefined ? this : object;
202 | object.loadingCount += 1;
203 | object.loading = true;
204 | }
205 |
206 | function __decrementLoadingCounter(object) {
207 | object = object === undefined ? this : object;
208 | object.loadingCount -= 1;
209 | object.loading = object.loadingCount > 0;
210 | }
211 | })();
212 |
--------------------------------------------------------------------------------
/src/collection/collection.factory.spec.js:
--------------------------------------------------------------------------------
1 | /*jshint expr: true*/
2 | (function() {
3 | 'use strict';
4 |
5 | describe('AngularJsonAPICollection', function() {
6 |
7 | beforeEach(module('angular-jsonapi'));
8 |
9 | it('returns valid model', inject(function(AngularJsonAPICollection) {
10 | expect(AngularJsonAPICollection).toBeDefined();
11 | }));
12 | });
13 | })();
14 |
--------------------------------------------------------------------------------
/src/errors/errors-manager/errors-manager.factory.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | 'use strict';
3 |
4 | angular.module('angular-jsonapi')
5 | .factory('AngularJsonAPIModelErrorsManager', AngularJsonAPIModelErrorsManagerWrapper);
6 |
7 | function AngularJsonAPIModelErrorsManagerWrapper() {
8 | ErrorsManager.prototype.constructor = ErrorsManager;
9 | ErrorsManager.prototype.concat = concat;
10 | ErrorsManager.prototype.clear = clear;
11 | ErrorsManager.prototype.add = add;
12 | ErrorsManager.prototype.hasErrors = hasErrors;
13 |
14 | return {
15 | create: ErrorsManagerFactory
16 | };
17 |
18 | function ErrorsManagerFactory(name, description, ErrorConstructor, defaultFilter) {
19 | return new ErrorsManager(name, description, ErrorConstructor, defaultFilter);
20 | }
21 |
22 | function ErrorsManager(name, description, ErrorConstructor, defaultFilter) {
23 | var _this = this;
24 | _this.name = name;
25 | _this.description = description;
26 |
27 | _this.ErrorConstructor = ErrorConstructor;
28 | _this.errors = {};
29 | _this.defaultFilter = defaultFilter || function() { return true; };
30 | }
31 |
32 | function clear(key) {
33 | var _this = this;
34 |
35 | if (key === undefined) {
36 | angular.forEach(_this.errors, function(obj, key) {
37 | _this.errors[key] = [];
38 | });
39 | } else {
40 | _this.errors[key] = [];
41 | }
42 | }
43 |
44 | function add(key, error) {
45 | var _this = this;
46 |
47 | _this.errors[key] = _this.errors[key] || [];
48 | _this.errors[key].push(error);
49 | }
50 |
51 | function concat(errors) {
52 | var _this = this;
53 |
54 | angular.forEach(errors, function(error) {
55 | _this.errors[error.key] = [];
56 | });
57 |
58 | angular.forEach(errors, function(error) {
59 | _this.errors[error.key].push(error.object);
60 | });
61 |
62 | }
63 |
64 | function hasErrors(key) {
65 | var _this = this;
66 |
67 | if (key === undefined) {
68 | var answer = false;
69 |
70 | angular.forEach(_this.errors, function(error) {
71 | answer = answer || error.length > 0;
72 | });
73 |
74 | return answer;
75 | } else {
76 | return _this.errors[key] !== undefined && _this.errors[key].length > 0;
77 | }
78 | }
79 | }
80 | })();
81 |
--------------------------------------------------------------------------------
/src/errors/errors-manager/errors-manager.factory.spec.js:
--------------------------------------------------------------------------------
1 | /*jshint expr: true*/
2 | (function() {
3 | 'use strict';
4 |
5 | describe('AngularJsonAPIModelErrorsManager', function() {
6 |
7 | beforeEach(module('angular-jsonapi'));
8 |
9 | it('returns valid model', inject(function(AngularJsonAPIModelErrorsManager) {
10 | expect(AngularJsonAPIModelErrorsManager).toBeDefined();
11 | }));
12 | });
13 | })();
14 |
--------------------------------------------------------------------------------
/src/errors/source-error/source-error.factory.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | 'use strict';
3 |
4 | angular.module('angular-jsonapi')
5 | .factory('AngularJsonAPIModelSourceError', AngularJsonAPIModelSourceErrorWrapper);
6 |
7 | function AngularJsonAPIModelSourceErrorWrapper() {
8 | SourceError.prototype = Object.create(Error.prototype);
9 | SourceError.prototype.constructor = SourceError;
10 | SourceError.prototype.name = 'SourceError';
11 |
12 | return {
13 | create: SourceErrorFactory
14 | };
15 |
16 | function SourceErrorFactory(message, source, code, action) {
17 | return new SourceError(message, source, code, action);
18 | }
19 |
20 | function SourceError(message, source, code, action) {
21 | var _this = this;
22 |
23 | _this.message = message;
24 | _this.context = {
25 | source: source,
26 | code: code,
27 | action: action
28 | };
29 | }
30 | }
31 | })();
32 |
--------------------------------------------------------------------------------
/src/errors/source-error/source-error.factory.spec.js:
--------------------------------------------------------------------------------
1 | /*jshint expr: true*/
2 | (function() {
3 | 'use strict';
4 |
5 | describe('AngularJsonAPIModelSourceError', function() {
6 |
7 | beforeEach(module('angular-jsonapi'));
8 |
9 | it('returns valid model', inject(function(AngularJsonAPIModelSourceError) {
10 | expect(AngularJsonAPIModelSourceError).toBeDefined();
11 | }));
12 | });
13 | })();
14 |
--------------------------------------------------------------------------------
/src/errors/validation-error/validation-error.factory.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | 'use strict';
3 |
4 | angular.module('angular-jsonapi')
5 | .factory('AngularJsonAPIModelValidationError', AngularJsonAPIModelValidationErrorWrapper);
6 |
7 | function AngularJsonAPIModelValidationErrorWrapper() {
8 | ValidationError.prototype = Object.create(Error.prototype);
9 | ValidationError.prototype.constructor = ValidationError;
10 | ValidationError.prototype.name = 'ValidationError';
11 |
12 | return {
13 | create: ValidationErrorFactory
14 | };
15 |
16 | function ValidationErrorFactory(message, attribute) {
17 | return new ValidationError(message, attribute);
18 | }
19 |
20 | function ValidationError(message, attribute) {
21 | var _this = this;
22 |
23 | _this.message = message;
24 | _this.context = {
25 | attribute: attribute
26 | };
27 | }
28 | }
29 | })();
30 |
--------------------------------------------------------------------------------
/src/errors/validation-error/validation-error.factory.spec.js:
--------------------------------------------------------------------------------
1 | /*jshint expr: true*/
2 | (function() {
3 | 'use strict';
4 |
5 | describe('AngularJsonAPIModelValidationError', function() {
6 |
7 | beforeEach(module('angular-jsonapi'));
8 |
9 | it('returns valid model', inject(function(AngularJsonAPIModelValidationError) {
10 | expect(AngularJsonAPIModelValidationError).toBeDefined();
11 | }));
12 | });
13 | })();
14 |
--------------------------------------------------------------------------------
/src/jsonapi.config.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | 'use strict';
3 |
4 | angular.module('angular-jsonapi')
5 | .config(function($logProvider) {
6 | $logProvider.debugEnabled(false);
7 | })
8 | .run(function(validateJS, $q) {
9 | validateJS.Promise = $q;
10 | });
11 | })();
12 |
--------------------------------------------------------------------------------
/src/jsonapi.module.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | 'use strict';
3 |
4 | angular.module('angular-jsonapi', ['uuid4'])
5 | /* global pluralize: false, validate: false */
6 | .constant('pluralize', pluralize)
7 | .constant('validateJS', validate);
8 | })();
9 |
--------------------------------------------------------------------------------
/src/jsonapi.provider.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | 'use strict';
3 |
4 | angular.module('angular-jsonapi')
5 | .provider('$jsonapi', jsonapiProvider);
6 |
7 | function jsonapiProvider(validateJS) {
8 | var memory = {};
9 | var names = [];
10 | this.$get = jsonapiFactory;
11 |
12 | function jsonapiFactory(
13 | $log,
14 | AngularJsonAPIResource,
15 | AngularJsonAPISynchronizerSimple
16 | ) {
17 | return {
18 | addResource: addResource,
19 | getResource: getResource,
20 | clearCache: clearCache,
21 | allResources: allResources,
22 | listResources: listResources,
23 | addValidator: addValidator,
24 | synchronizerSimple: AngularJsonAPISynchronizerSimple,
25 |
26 | __proccesResults: __proccesResults
27 | };
28 |
29 | function addResource(schema, synchronizer) {
30 | var resource = AngularJsonAPIResource.create(schema, synchronizer);
31 |
32 | memory[schema.type] = resource;
33 | names.push(schema.type);
34 | }
35 |
36 | function getResource(type) {
37 | return memory[type];
38 | }
39 |
40 | function allResources() {
41 | return memory;
42 | }
43 |
44 | function listResources() {
45 | return names;
46 | }
47 |
48 | function clearCache() {
49 | angular.forEach(memory, function(resource) {
50 | resource.clearCache();
51 | });
52 | }
53 |
54 | function addValidator(name, validator) {
55 | if (!angular.isString(name)) {
56 | $log.error('Validator name is not a string', name);
57 | return;
58 | } else if (validateJS.validators[name] === undefined) {
59 | $log.warn('Redeclaring validator', name);
60 | }
61 |
62 | validateJS.validators[name] = validator;
63 | }
64 |
65 | function __proccesResults(results) {
66 | var objects = {
67 | data: [],
68 | included: []
69 | };
70 |
71 | if (results === undefined) {
72 | $log.error('Can\'t proccess results:', results);
73 | return;
74 | }
75 |
76 | var config = {
77 | new: false,
78 | synchronized: true,
79 | stable: true,
80 | pristine: false,
81 | initialization: false
82 | };
83 |
84 | angular.forEach(results.included, function(data) {
85 | objects.included.push(getResource(data.type).cache.addOrUpdate(data, config));
86 | });
87 |
88 | if (angular.isArray(results.data)) {
89 | angular.forEach(results.data, function(data) {
90 | objects.data.push(getResource(data.type).cache.addOrUpdate(data, config));
91 | });
92 | } else if (results.data !== undefined) {
93 | objects.data.push(getResource(results.data.type).cache.addOrUpdate(results.data, config));
94 | }
95 |
96 | return objects;
97 | }
98 | }
99 | }
100 |
101 | })();
102 |
103 |
--------------------------------------------------------------------------------
/src/jsonapi.provider.spec.js:
--------------------------------------------------------------------------------
1 | /*jshint expr: true*/
2 | (function() {
3 | 'use strict';
4 |
5 | describe('$jsonapi', function() {
6 |
7 | beforeEach(module('angular-jsonapi'));
8 |
9 | it('returns valid model', inject(function($jsonapi) {
10 | expect($jsonapi).toBeDefined();
11 | }));
12 | });
13 | })();
14 |
--------------------------------------------------------------------------------
/src/model/abstract-model/abstract-model.spec.js:
--------------------------------------------------------------------------------
1 | /*jshint expr: true*/
2 | (function() {
3 | 'use strict';
4 |
5 | describe('AngularJsonAPIAbstractModel', function() {
6 |
7 | beforeEach(module('angular-jsonapi'));
8 |
9 | it('returns valid model', inject(function(AngularJsonAPIAbstractModel) {
10 | expect(AngularJsonAPIAbstractModel).toBeDefined();
11 | }));
12 | });
13 | })();
14 |
--------------------------------------------------------------------------------
/src/model/model-factory.factory.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | 'use strict';
3 |
4 | angular.module('angular-jsonapi')
5 | .factory('AngularJsonAPIModel', AngularJsonAPIModel);
6 |
7 | function AngularJsonAPIModel(
8 | AngularJsonAPIAbstractModel,
9 | AngularJsonAPISchema,
10 | namedFunction,
11 | pluralize,
12 | $log
13 | ) {
14 |
15 | return {
16 | modelFactory: createModelFactory
17 | };
18 |
19 | function createModelFactory(schemaObj, resource) {
20 | var constructorName = pluralize.plural(schemaObj.type, 1);
21 |
22 | var Model = namedFunction(constructorName, function(data, config, updatedAt) {
23 | var _this = this;
24 |
25 | if (data.type !== _this.schema.type) {
26 | $log.error('Data type other then declared in schema: ', data.type, ' instead of ', _this.schema.type);
27 | }
28 |
29 | AngularJsonAPIAbstractModel.call(_this, data, config, updatedAt);
30 |
31 | _this.form.parent = _this;
32 | });
33 |
34 | Model.prototype = Object.create(AngularJsonAPIAbstractModel.prototype);
35 | Model.prototype.constructor = Model;
36 |
37 | Model.prototype.schema = schemaObj;
38 | Model.prototype.resource = resource;
39 | Model.prototype.synchronize = resource.synchronizer.synchronize.bind(resource.synchronizer);
40 |
41 | angular.forEach(schemaObj.functions, function(metaFunction, metaFunctionName) {
42 | Model.prototype[metaFunctionName] = function() {
43 | return metaFunction.apply(this, arguments);
44 | };
45 | });
46 |
47 | return modelFactory;
48 |
49 | function modelFactory(data, updatedAt, isNew) {
50 | return new Model(data, updatedAt, isNew);
51 | }
52 | }
53 | }
54 | })();
55 |
--------------------------------------------------------------------------------
/src/model/model-factory.factory.spec.js:
--------------------------------------------------------------------------------
1 | /*jshint expr: true*/
2 | (function() {
3 | 'use strict';
4 |
5 | describe('AngularJsonAPIModel', function() {
6 |
7 | beforeEach(module('angular-jsonapi'));
8 |
9 | it('returns valid model', inject(function(AngularJsonAPIModel) {
10 | expect(AngularJsonAPIModel).toBeDefined();
11 | }));
12 | });
13 | })();
14 |
--------------------------------------------------------------------------------
/src/model/model-form/model-form.factory.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | 'use strict';
3 |
4 | angular.module('angular-jsonapi')
5 | .factory('AngularJsonAPIModelForm', AngularJsonAPIModelFormWrapper);
6 |
7 | function AngularJsonAPIModelFormWrapper(
8 | AngularJsonAPIModelValidationError,
9 | AngularJsonAPIModelLinkerService,
10 | validateJS,
11 | $q
12 | ) {
13 |
14 | AngularJsonAPIModelForm.prototype.save = save;
15 | AngularJsonAPIModelForm.prototype.reset = reset;
16 | AngularJsonAPIModelForm.prototype.validate = validate;
17 |
18 | AngularJsonAPIModelForm.prototype.link = link;
19 | AngularJsonAPIModelForm.prototype.unlink = unlink;
20 |
21 | AngularJsonAPIModelForm.prototype.toJson = toJson;
22 |
23 | return {
24 | create: AngularJsonAPIModelFormFactory
25 | };
26 |
27 | function AngularJsonAPIModelFormFactory(parent) {
28 | return new AngularJsonAPIModelForm(parent);
29 | }
30 |
31 | function AngularJsonAPIModelForm(parent) {
32 | var _this = this;
33 |
34 | _this.data = {
35 | id: parent.data.id,
36 | type: parent.data.type,
37 | attributes: {},
38 | relationships: {}
39 | };
40 |
41 | _this.relationships = {};
42 | _this.parent = parent;
43 | _this.schema = parent.schema;
44 | _this.reset();
45 | }
46 |
47 | /**
48 | * Encodes object into json
49 | * @return {json} Json object
50 | */
51 | function toJson() {
52 | var _this = this;
53 | var data = angular.copy(_this.data);
54 | var relationships = {};
55 |
56 | angular.forEach(data.relationships, function(value, key) {
57 | if (value.data !== undefined) {
58 | relationships[key] = value;
59 | }
60 | });
61 |
62 | data.relationships = relationships;
63 |
64 | return {
65 | data: data
66 | };
67 | }
68 |
69 | /**
70 | * Saves form, shortcut to parent.save()
71 | * @return {promise} Promise associated with synchronization
72 | */
73 | function save() {
74 | var _this = this;
75 |
76 | return _this.parent.save();
77 | }
78 |
79 | /**
80 | * Resets form to state of a parent
81 | * @return {undefined}
82 | */
83 | function reset(auto) {
84 | var _this = this;
85 |
86 | angular.forEach(_this.schema.relationships, function(data, key) {
87 | _this.data.relationships[key] = angular.copy(_this.parent.data.relationships[key]) || {};
88 | if (angular.isArray(_this.relationships[key])) {
89 | _this.relationships[key] = _this.parent.relationships[key].slice();
90 | } else {
91 | _this.relationships[key] = _this.parent.relationships[key];
92 | }
93 | });
94 |
95 | if (auto === true && _this.parent.synchronized === true) {
96 | return;
97 | }
98 |
99 | angular.forEach(_this.schema.attributes, function(validator, key) {
100 | _this.data.attributes[key] = angular.copy(_this.parent.data.attributes[key]);
101 | });
102 |
103 | _this.parent.errors.validation.clear();
104 | }
105 |
106 | /**
107 | * Validates form
108 | * @return {promise} Promise rejected to errors object indexed by keys. If the
109 | * key param i stated it only validates an attribute, else all attributes.
110 | */
111 | function validate(key) {
112 | var _this = this;
113 | var attributesWrapper;
114 | var constraintsWrapper;
115 | var deferred = $q.defer();
116 |
117 | if (key === undefined) {
118 | attributesWrapper = _this.data.attributes;
119 | constraintsWrapper = _this.schema.attributes;
120 | } else {
121 | attributesWrapper = {};
122 | constraintsWrapper = {};
123 |
124 | attributesWrapper[key] = _this.data.attributes[key];
125 | constraintsWrapper[key] = _this.schema.attributes[key];
126 | }
127 |
128 | validateJS.async(
129 | attributesWrapper,
130 | constraintsWrapper
131 | ).then(resolve, reject);
132 |
133 | function resolve() {
134 | if (key === undefined) {
135 | _this.parent.errors.validation.clear();
136 | } else {
137 | _this.parent.errors.validation.clear(key);
138 | }
139 |
140 | deferred.resolve();
141 | }
142 |
143 | function reject(errorsMap) {
144 | _this.parent.error = true;
145 | if (key === undefined) {
146 | _this.parent.errors.validation.clear();
147 | } else {
148 | _this.parent.errors.validation.clear(key);
149 | }
150 |
151 | angular.forEach(errorsMap, function(errors, attribute) {
152 | angular.forEach(errors, function(error) {
153 | _this.parent.errors.validation.add(attribute, AngularJsonAPIModelValidationError.create(error, attribute));
154 | });
155 | });
156 |
157 | deferred.reject(_this.parent.errors.validation);
158 | }
159 |
160 | return deferred.promise;
161 | }
162 |
163 | /**
164 | * Adds link to a form without synchronization
165 | * @param {string} key Relationship name
166 | * @param {AngularJsonAPIModel} target Object to be linked
167 | * @return {Boolean} Status
168 | */
169 | function link(key, target, oneWay) {
170 | var _this = this;
171 | oneWay = oneWay === undefined ? false : true;
172 |
173 | return $q.resolve(AngularJsonAPIModelLinkerService.link(_this.parent, key, target, oneWay, true));
174 | }
175 |
176 | /**
177 | * Removes link from form without synchronization
178 | * @param {[type]} key Relationship name
179 | * @param {AngularJsonAPIModel} target Object to be linked
180 | * @return {Boolean} Status
181 | */
182 | function unlink(key, target, oneWay) {
183 | var _this = this;
184 | oneWay = oneWay === undefined ? false : true;
185 |
186 | return $q.resolve(AngularJsonAPIModelLinkerService.unlink(_this.parent, key, target, oneWay, true));
187 | }
188 | }
189 | })();
190 |
--------------------------------------------------------------------------------
/src/model/model-form/model-form.spec.js:
--------------------------------------------------------------------------------
1 | /*jshint expr: true*/
2 | (function() {
3 | 'use strict';
4 |
5 | describe('AngularJsonAPIModelForm', function() {
6 |
7 | beforeEach(module('angular-jsonapi'));
8 |
9 | it('returns valid model', inject(function(AngularJsonAPIModelForm) {
10 | expect(AngularJsonAPIModelForm).toBeDefined();
11 | }));
12 | });
13 | })();
14 |
--------------------------------------------------------------------------------
/src/model/model-linker/model-linker.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | 'use strict';
3 |
4 | angular.module('angular-jsonapi')
5 | .service('AngularJsonAPIModelLinkerService', AngularJsonAPIModelLinkerService);
6 |
7 | function AngularJsonAPIModelLinkerService($log) {
8 | var _this = this;
9 |
10 | _this.toLinkData = toLinkData;
11 |
12 | _this.link = link;
13 | _this.unlink = unlink;
14 |
15 | return this;
16 |
17 | /**
18 | * Extracts data needed for relationship linking from object
19 | * @param {AngularJsonAPIModel} object Object
20 | * @return {json} Link data
21 | */
22 | function toLinkData(object) {
23 | if (object === null) {
24 | return null;
25 | }
26 |
27 | return {type: object.data.type, id: object.data.id};
28 | }
29 |
30 | /**
31 | * Add target to object relationships and data.relationships
32 | * @param {AngularJsonAPIModel} object Object to be modified
33 | * @param {string} key Relationship name
34 | * @param {AngularJsonAPIModel} target Object to be linked
35 | * @param {AngularJsonAPISchema} schema Relationship schema
36 | */
37 | function link(object, key, target, oneWay, form) {
38 | var schema;
39 | form = form === undefined ? false : form;
40 |
41 | if (object === undefined) {
42 | $log.error('Can\'t add link to non existing object', object, key, target);
43 | $log.error('Object:', object.data.type, object);
44 | $log.error('Target:', target.data.type, target);
45 | $log.error('Key:', key);
46 | return [];
47 | }
48 |
49 | schema = object.schema.relationships[key];
50 |
51 | if (target === undefined) {
52 | $log.error('Can\'t link non existing object', object, key, target, schema);
53 | $log.error('Object:', object.data.type, object);
54 | $log.error('Target:', target.data.type, target);
55 | $log.error('Key:', key);
56 | $log.error('Schema:', schema);
57 | return [];
58 | }
59 |
60 | if (schema === undefined) {
61 | $log.error('Can\'t add link not present in schema:', object, key, target, schema);
62 | $log.error('Object:', object.data.type, object);
63 | $log.error('Target:', target.data.type, target);
64 | $log.error('Key:', key);
65 | $log.error('Schema:', schema);
66 | return [];
67 | }
68 |
69 | if (target !== null && schema.polymorphic === false && schema.model !== target.data.type) {
70 | $log.error('This relation is not polymorphic, expected: ' + schema.model + ' instead of ' + target.data.type);
71 | $log.error('Object:', object.data.type, object);
72 | $log.error('Target:', target.data.type, target);
73 | $log.error('Key:', key);
74 | $log.error('Schema:', schema);
75 | return [];
76 | }
77 |
78 | if (schema.type === 'hasMany') {
79 | if (oneWay === true) {
80 | __addHasMany(object, key, target, form);
81 | return [];
82 | } else {
83 | return __processAddHasMany(object, key, target, form);
84 | }
85 | } else if (schema.type === 'hasOne') {
86 | if (oneWay === true) {
87 | __addHasOne(object, key, target, form);
88 | return [];
89 | } else {
90 | return __processAddHasOne(object, key, target, form);
91 | }
92 | }
93 | }
94 |
95 | /**
96 | * Remove target from object relationships and data.relationships
97 | * @param {AngularJsonAPIModel} object Object to be modified
98 | * @param {string} key Relationship name
99 | * @param {AngularJsonAPIModel} target Object to be unlinked
100 | * @param {AngularJsonAPISchema} schema Relationship schema
101 | */
102 | function unlink(object, key, target, oneWay, form) {
103 | var schema;
104 | form = form === undefined ? false : form;
105 |
106 | if (object === undefined) {
107 | $log.error('Can\'t remove link from non existing object', object, key, target);
108 | $log.error('Object:', object.data.type, object);
109 | $log.error('Target:', target.data.type, target);
110 | $log.error('Key:', key);
111 | return [];
112 | }
113 |
114 | schema = object.schema.relationships[key];
115 |
116 | if (schema === undefined) {
117 | $log.error('Can\'t remove link not present in schema:', object, key, target, schema);
118 | $log.error('Object:', object.data.type, object);
119 | $log.error('Target:', target.data.type, target);
120 | $log.error('Key:', key);
121 | $log.error('Schema:', schema);
122 | return [];
123 | }
124 |
125 | if (oneWay === true) {
126 | __removeHasMany(object, key, target, form);
127 | return [];
128 | } else {
129 | return __processRemove(object, key, target, form);
130 | }
131 | }
132 |
133 | /////////////
134 | // Private //
135 | /////////////
136 |
137 | function __processAddHasMany(object, key, target, form) {
138 | var reflectionKey = object.schema.relationships[key].reflection;
139 | var reflectionSchema;
140 |
141 | if (reflectionKey === false) {
142 | __addHasMany(object, key, target, form);
143 | return [];
144 | }
145 |
146 | reflectionSchema = target.schema.relationships[reflectionKey];
147 |
148 | if (reflectionSchema === undefined) {
149 | $log.error('Cannot find reflection of', key, 'relationship for', object.data.type, 'in', target.data.type);
150 | $log.error('For one side relationships set schema.reflection to false');
151 | return [];
152 | } else if (reflectionSchema.type === 'hasOne') {
153 | return __swapResults(
154 | __wrapResults(object, key, target),
155 | __wrapResults(target, reflectionKey, object),
156 | __processAddHasOne(target, reflectionKey, object, form)
157 | );
158 | } else if (reflectionSchema.type === 'hasMany') {
159 | __addHasMany(object, key, target, form);
160 | __addHasMany(target, reflectionKey, object, form);
161 | return [__wrapResults(target, reflectionKey, object)];
162 | }
163 | }
164 |
165 | function __processAddHasOne(object, key, target, form) {
166 | var reflectionKey = object.schema.relationships[key].reflection;
167 | var oldReflection = object.relationships[key];
168 | var reflectionSchema;
169 | var oldReflectionSchema;
170 | var result = [];
171 |
172 | __addHasOne(object, key, target, form);
173 |
174 | if (reflectionKey === false) {
175 | return [];
176 | }
177 |
178 | if (oldReflection !== undefined && oldReflection !== null) {
179 | oldReflectionSchema = oldReflection.schema.relationships[reflectionKey];
180 |
181 | if (oldReflectionSchema !== undefined) {
182 | if (oldReflectionSchema.type === 'hasOne') {
183 | __removeHasOne(oldReflection, reflectionKey, object, form);
184 | } else if (oldReflectionSchema.type === 'hasMany') {
185 | __removeHasMany(oldReflection, reflectionKey, object, form);
186 | }
187 |
188 | result.push(__wrapResults(oldReflection, reflectionKey, object));
189 | } else {
190 | $log.error('Cannot find reflection of', key, 'relationship for', object.data.type, 'in', target.data.type);
191 | $log.error('For one side relationships set schema.reflection to false');
192 | }
193 | }
194 |
195 | if (target !== undefined && target !== null && reflectionKey !== false) {
196 | reflectionSchema = target.schema.relationships[reflectionKey];
197 | if (reflectionSchema !== undefined) {
198 | if (reflectionSchema.type === 'hasOne') {
199 | __addHasOne(target, reflectionKey, object, form);
200 | } else if (reflectionSchema.type === 'hasMany') {
201 | __addHasMany(target, reflectionKey, object, form);
202 | }
203 |
204 | result.push(__wrapResults(target, reflectionKey, object));
205 | } else {
206 | $log.error('Cannot find reflection of', key, 'relationship for', object.data.type, 'in', target.data.type);
207 | $log.error('For one side relationships set schema.reflection to false');
208 | }
209 | }
210 |
211 | return result;
212 | }
213 |
214 | function __processRemove(object, key, target, form) {
215 | var schema = object.schema.relationships[key];
216 | var reflectionKey = schema.reflection;
217 | var reflectionSchema;
218 |
219 | if (schema.type === 'hasMany') {
220 | __removeHasMany(object, key, target, form);
221 | } else if (schema.type === 'hasOne') {
222 | __removeHasOne(object, key, target, form);
223 | }
224 |
225 | if (reflectionKey === false) {
226 | return [];
227 | }
228 |
229 | reflectionSchema = target.schema.relationships[reflectionKey];
230 |
231 | if (reflectionSchema !== undefined) {
232 | if (reflectionSchema.type === 'hasOne') {
233 | __removeHasOne(target, reflectionKey, object, form);
234 | } else if (reflectionSchema.type === 'hasMany') {
235 | __removeHasMany(target, reflectionKey, object, form);
236 | }
237 | } else {
238 | $log.error('Cannot find reflection of', key, 'relationship for', object.data.type, 'in', target.data.type);
239 | $log.error('For one side relationships set schema.reflection to false');
240 | return [];
241 | }
242 |
243 | return [__wrapResults(target, reflectionKey, object)];
244 | }
245 |
246 | function __addHasOne(object, key, target, form) {
247 | $log.debug('addHasOne', object, key, target);
248 |
249 | if (form === true) {
250 | object = object.form;
251 | }
252 |
253 | object.relationships[key] = target;
254 | object.data.relationships[key].data = toLinkData(target);
255 |
256 | if (form === false) {
257 | object.reset(true);
258 | }
259 |
260 | return true;
261 | }
262 |
263 | function __addHasMany(object, key, target, form) {
264 | $log.debug('addHasMany', object, key, target);
265 |
266 | var linkData = toLinkData(target);
267 | if (form === true) {
268 | object = object.form;
269 | }
270 |
271 | if (angular.isArray(object.relationships[key]) && object.relationships[key].indexOf(target) > -1) {
272 | return false;
273 | }
274 |
275 | object.relationships[key] = object.relationships[key] || [];
276 | object.data.relationships[key].data = object.data.relationships[key].data || [];
277 |
278 | object.relationships[key].push(target);
279 | object.data.relationships[key].data.push(linkData);
280 |
281 | if (form === false) {
282 | object.reset(true);
283 | }
284 |
285 | return true;
286 | }
287 |
288 | function __removeHasOne(object, key, target, form) {
289 | $log.debug('removeHasOne', object, key, target);
290 |
291 | if (form === true) {
292 | object = object.form;
293 | }
294 |
295 | if (target !== undefined && object.relationships[key] !== target) {
296 | return false;
297 | }
298 |
299 | object.relationships[key] = null;
300 | object.data.relationships[key].data = undefined;
301 |
302 | if (form === false) {
303 | object.reset(true);
304 | }
305 |
306 | return true;
307 | }
308 |
309 | function __removeHasMany(object, key, target, form) {
310 | $log.debug('removeHasMany', object, key, target);
311 |
312 | if (form === true) {
313 | object = object.form;
314 | }
315 |
316 | if (object.relationships[key] === undefined) {
317 | return;
318 | }
319 |
320 | if (target === undefined) {
321 | object.relationships[key] = [];
322 | object.data.relationships[key].data = [];
323 | if (form === false) {
324 | object.reset(true);
325 | }
326 |
327 | return true;
328 | }
329 |
330 | var index = object.relationships[key].indexOf(target);
331 |
332 | if (index === -1) {
333 | return false;
334 | }
335 |
336 | object.relationships[key].splice(index, 1);
337 | object.data.relationships[key].data.splice(index, 1);
338 |
339 | if (form === false) {
340 | object.reset(true);
341 | }
342 |
343 | return true;
344 | }
345 |
346 | function __wrapResults(object, key, target) {
347 | return {
348 | object: object,
349 | key: key,
350 | target: target
351 | };
352 | }
353 |
354 | function __swapResults(value, newValue, array) {
355 | var index = -1;
356 | angular.forEach(array, function(item, i) {
357 | if (item.object === value.object && item.key === value.key && item.target === value.target) {
358 | index = i;
359 | }
360 | });
361 |
362 | if (index > -1) {
363 | array[index] = newValue;
364 | } else {
365 | array.push(newValue);
366 | }
367 |
368 | return array;
369 | }
370 | }
371 | })();
372 |
--------------------------------------------------------------------------------
/src/model/model-linker/model-linker.spec.js:
--------------------------------------------------------------------------------
1 | /*jshint expr: true*/
2 | (function() {
3 | 'use strict';
4 |
5 | describe('AngularJsonAPIModelLinkerService', function() {
6 |
7 | beforeEach(module('angular-jsonapi'));
8 |
9 | it('returns valid model', inject(function(AngularJsonAPIModelLinkerService) {
10 | expect(AngularJsonAPIModelLinkerService).toBeDefined();
11 | }));
12 | });
13 | })();
14 |
--------------------------------------------------------------------------------
/src/resource/resource-cache/resource-cache.factory.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | 'use strict';
3 |
4 | angular.module('angular-jsonapi')
5 | .factory('AngularJsonAPIResourceCache', AngularJsonAPIResourceCacheWrapper);
6 |
7 | function AngularJsonAPIResourceCacheWrapper(
8 | uuid4,
9 | $log
10 | ) {
11 |
12 | AngularJsonAPIResourceCache.prototype.get = get;
13 | AngularJsonAPIResourceCache.prototype.index = index;
14 | AngularJsonAPIResourceCache.prototype.setIndexIds = setIndexIds;
15 | AngularJsonAPIResourceCache.prototype.addOrUpdate = addOrUpdate;
16 |
17 | AngularJsonAPIResourceCache.prototype.fromJson = fromJson;
18 | AngularJsonAPIResourceCache.prototype.toJson = toJson;
19 | AngularJsonAPIResourceCache.prototype.clear = clear;
20 |
21 | AngularJsonAPIResourceCache.prototype.remove = remove;
22 | AngularJsonAPIResourceCache.prototype.revertRemove = revertRemove;
23 | AngularJsonAPIResourceCache.prototype.clearRemoved = clearRemoved;
24 |
25 | return {
26 | create: AngularJsonAPIResourceCacheFactory
27 | };
28 |
29 | function AngularJsonAPIResourceCacheFactory(resource) {
30 | return new AngularJsonAPIResourceCache(resource);
31 | }
32 |
33 | /**
34 | * Constructor
35 | */
36 | function AngularJsonAPIResourceCache(resource) {
37 | var _this = this;
38 |
39 | _this.resource = resource;
40 | _this.data = {};
41 | _this.removed = {};
42 | _this.size = 0;
43 |
44 | _this.indexIds = undefined;
45 | }
46 |
47 | /**
48 | * Add new model or update existing with data
49 | * @param {object} validatedData Data that are used to update or create an object, has to be valid
50 | * @return {AngularJsonAPIModel} Created model
51 | */
52 | function addOrUpdate(validatedData, config, updatedAt) {
53 | var _this = this;
54 | var id = validatedData.id;
55 |
56 | if (id === undefined) {
57 | $log.error('Can\'t add data without id!', validatedData);
58 | return;
59 | }
60 |
61 | if (_this.data[id] === undefined) {
62 | _this.data[id] = _this.resource.modelFactory(validatedData, config, updatedAt);
63 | _this.size += 1;
64 | } else {
65 | _this.data[id].update(validatedData, !config.new, config.initialization);
66 | }
67 |
68 | return _this.data[id];
69 | }
70 |
71 |
72 | /**
73 | * Recreate object structure from json data
74 | * @param {json} json Json data
75 | * @return {undefined}
76 | */
77 | function fromJson(json) {
78 | var _this = this;
79 | var collection = angular.fromJson(json);
80 |
81 | var config = {
82 | new: false,
83 | synchronized: false,
84 | stable: false,
85 | pristine: false,
86 | initialization: true
87 | };
88 |
89 | if (angular.isObject(collection) && collection.data !== undefined) {
90 | _this.updatedAt = collection.updatedAt;
91 | _this.indexIds = collection.indexIds;
92 |
93 | angular.forEach(collection.data, function(objectData) {
94 | var data = objectData.data;
95 | _this.addOrUpdate(data, config, objectData.updatedAt);
96 | });
97 | }
98 | }
99 |
100 | /**
101 | * Encodes memory into json format
102 | * @return {json} Json encoded memory
103 | */
104 | function toJson() {
105 | var _this = this;
106 | var json = {
107 | data: [],
108 | updatedAt: _this.updatedAt,
109 | indexIds: _this.indexIds
110 | };
111 |
112 | angular.forEach(_this.data, function(object) {
113 | if (object.hasErrors() === false) {
114 | json.data.push(object.toJson());
115 | }
116 | });
117 |
118 | return angular.toJson(json);
119 | }
120 |
121 | /**
122 | * Clear memory
123 | * @return {undefined}
124 | */
125 | function clear() {
126 | var _this = this;
127 |
128 | _this.indexIds = undefined;
129 | _this.data = {};
130 | _this.removed = {};
131 | }
132 |
133 | /**
134 | * Low level get used internally, does not run any synchronization
135 | * @param {uuid} id
136 | * @return {AngularJsonAPIModel} Model associated with id
137 | */
138 | function get(id) {
139 | var _this = this;
140 |
141 | var data = {
142 | id: id,
143 | type: _this.resource.schema.type
144 | };
145 |
146 | var config = {
147 | new: false,
148 | synchronized: false,
149 | stable: false,
150 | pristine: true
151 | };
152 |
153 | if (_this.data[id] === undefined) {
154 | _this.data[id] = _this.resource.modelFactory(data, config);
155 | }
156 |
157 | return _this.data[id];
158 | }
159 |
160 | /**
161 | * Low level get used internally, does not run any synchronization, used for index requests
162 | * @param {objec} params
163 | * @return {AngularJsonAPIModel} Model associated with id
164 | */
165 | function index(params) {
166 | var _this = this;
167 | params = params || {};
168 |
169 | if (_this.indexIds === undefined) {
170 | return _this.indexIds;
171 | }
172 |
173 | return _this.indexIds.map(_this.get.bind(_this)).filter(filter);
174 |
175 | function filter(argument) {
176 | var filterParams = params.filter;
177 | var valid = true;
178 |
179 | angular.forEach(filterParams, function(constraint) {
180 | valid = valid && argument.data.attributes[constraint.key] === constraint.value;
181 | });
182 |
183 | return valid;
184 | }
185 | }
186 |
187 | /**
188 | * Cache ids of objects returned by index request
189 | * @param {ids array or AngularJsonAPIModel array} array Objects or ids to be cached
190 | */
191 | function setIndexIds(array) {
192 | var _this = this;
193 |
194 | _this.indexIds = [];
195 |
196 | angular.forEach(array, function(element) {
197 | if (angular.isString(element) && _this.resource.schema.id.validate(element)) {
198 | _this.indexIds.push(element);
199 | } else if (angular.isObject(element) && _this.resource.schema.id.validate(element.data.id)) {
200 | _this.indexIds.push(element.data.id);
201 | }
202 | });
203 | }
204 |
205 | /**
206 | * Remove object with given id from cache
207 | * @param {uuid} id
208 | * @return {AngularJsonAPIModel / undefined} Removed object, undefined if
209 | * object does not exist
210 | */
211 | function remove(id) {
212 | var _this = this;
213 |
214 | if (_this.data[id] !== undefined) {
215 | _this.removed[id] = _this.data[id];
216 | delete _this.data[id];
217 | _this.size -= 1;
218 | }
219 |
220 | return _this.removed[id];
221 | }
222 |
223 | /**
224 | * Revert removal of an object with given id from cache
225 | * @param {uuid} id
226 | * @return {AngularJsonAPIModel / undefined} Removed object, undefined if
227 | * object does not exist
228 | */
229 | function revertRemove(id) {
230 | var _this = this;
231 |
232 | if (_this.removed[id] !== undefined) {
233 | _this.data[id] = _this.removed[id];
234 | delete _this.removed[id];
235 | _this.size += 1;
236 | }
237 |
238 | return _this.data[id];
239 | }
240 |
241 | /**
242 | * Clear removed object from memory
243 | * @param {uuid} id
244 | * @return {undefined}
245 | */
246 | function clearRemoved(id) {
247 | var _this = this;
248 |
249 | delete _this.removed[id];
250 | }
251 | }
252 | })();
253 |
--------------------------------------------------------------------------------
/src/resource/resource-cache/resource-cache.factory.spec.js:
--------------------------------------------------------------------------------
1 | /*jshint expr: true*/
2 | (function() {
3 | 'use strict';
4 |
5 | describe('AngularJsonAPIResourceCache', function() {
6 |
7 | beforeEach(module('angular-jsonapi'));
8 |
9 | it('returns valid model', inject(function(AngularJsonAPIResourceCache) {
10 | expect(AngularJsonAPIResourceCache).toBeDefined();
11 | }));
12 |
13 | describe('#addOrUpdate', function() {
14 |
15 | });
16 |
17 | describe('#fromJson', function() {
18 |
19 | });
20 |
21 | describe('#toJson', function() {
22 |
23 | });
24 |
25 | describe('#clear', function() {
26 |
27 | });
28 |
29 | describe('#get', function() {
30 |
31 | });
32 |
33 | describe('#remove', function() {
34 |
35 | });
36 |
37 | describe('#revertRemove', function() {
38 |
39 | });
40 |
41 | describe('#clearRemoved', function() {
42 |
43 | });
44 |
45 | });
46 | })();
47 |
--------------------------------------------------------------------------------
/src/resource/resource.factory.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | 'use strict';
3 |
4 | angular.module('angular-jsonapi')
5 | .factory('AngularJsonAPIResource', AngularJsonAPIResourceWrapper);
6 |
7 | function AngularJsonAPIResourceWrapper(
8 | AngularJsonAPIModel,
9 | AngularJsonAPISchema,
10 | AngularJsonAPIResourceCache,
11 | AngularJsonAPICollection,
12 | $rootScope,
13 | $log,
14 | $q
15 | ) {
16 | AngularJsonAPIResource.prototype.get = get;
17 | AngularJsonAPIResource.prototype.all = all;
18 | AngularJsonAPIResource.prototype.remove = remove;
19 | AngularJsonAPIResource.prototype.initialize = initialize;
20 |
21 | AngularJsonAPIResource.prototype.clearCache = clearCache;
22 |
23 | return {
24 | create: AngularJsonAPIResourceFactory
25 | };
26 |
27 | function AngularJsonAPIResourceFactory(schema, synchronizer) {
28 | return new AngularJsonAPIResource(schema, synchronizer);
29 | }
30 |
31 | /**
32 | * AngularJsonAPIResource constructor
33 | * @param {json} schema Schema object
34 | * @param {AngularJsonAPISynchronizer} synchronizer Synchronizer for the resource
35 | */
36 | function AngularJsonAPIResource(schema, synchronizer) {
37 | var _this = this;
38 | var config = {
39 | action: 'init'
40 | };
41 |
42 | _this.schema = AngularJsonAPISchema.create(schema);
43 | _this.cache = AngularJsonAPIResourceCache.create(_this);
44 |
45 | _this.synchronizer = synchronizer;
46 | _this.synchronizer.resource = _this;
47 |
48 | _this.modelFactory = AngularJsonAPIModel.modelFactory(
49 | _this.schema,
50 | _this
51 | );
52 |
53 | _this.initialized = false;
54 | _this.type = _this.schema.type;
55 |
56 | synchronizer.resource = _this;
57 |
58 | _this.synchronizer.synchronize(config).then(resolve, reject, notify);
59 |
60 | function resolve(response) {
61 | $rootScope.$emit('angularJsonAPI:' + _this.type + ':resource:init', 'resolved', response);
62 | _this.cache.fromJson(response.data);
63 | _this.initialized = true;
64 |
65 | response.finish();
66 | }
67 |
68 | function reject(response) {
69 | $rootScope.$emit('angularJsonAPI:' + _this.type + ':resource:init', 'rejected', response);
70 | response.finish();
71 | _this.initialized = true;
72 | }
73 |
74 | function notify(response) {
75 | $rootScope.$emit('angularJsonAPI:' + _this.type + ':resource:init', 'notify', response);
76 | }
77 | }
78 |
79 | /**
80 | * Get request
81 | * @param {uuid} id
82 | * @return {AngularJsonAPIModel} Model associated with id, synchronized
83 | */
84 | function get(id, params) {
85 | var _this = this;
86 |
87 | if (!_this.schema.id.validate(id)) {
88 | return $q.reject({errors: [{status: 0, statusText: 'Invalid id'}]});
89 | }
90 |
91 | var object = _this.cache.get(id);
92 |
93 | object.promise = object.refresh(params);
94 |
95 | return object;
96 | }
97 |
98 | /**
99 | * All request
100 | * @param {object} params Object associated with params (for filters/pagination etc.)
101 | * @return {AngularJsonAPICollection} Collection of AngularJsonAPIModel, synchronized
102 | */
103 | function all(params) {
104 | var _this = this;
105 | params = angular.extend({}, _this.schema.params.all, params);
106 |
107 | var collection = AngularJsonAPICollection.create(
108 | _this,
109 | params
110 | );
111 |
112 | collection.promise = collection.fetch();
113 |
114 | return collection;
115 | }
116 |
117 | /**
118 | * Remove request
119 | * @param {uuid} id
120 | * @return {promise} Promise associated with the synchronization, in case of
121 | * fail object is reverted to previous state
122 | */
123 | function remove(id) {
124 | var _this = this;
125 | var object = _this.cache.remove(id);
126 |
127 | return object.remove();
128 | }
129 |
130 | /**
131 | * Initialize new AngularJsonAPIModel
132 | * @return {AngularJsonAPIModel} New model
133 | */
134 | function initialize() {
135 | var _this = this;
136 | var relationships = {};
137 |
138 | angular.forEach(_this.schema.relationships, function(relationshipSchema, relationshipName) {
139 | if (relationshipSchema.type === 'hasOne') {
140 | relationships[relationshipName] = {
141 | data: null
142 | };
143 | } else if (relationshipSchema.type === 'hasMany') {
144 | relationships[relationshipName] = {
145 | data: []
146 | };
147 | }
148 | });
149 |
150 | var data = {
151 | type: _this.type,
152 | id: _this.schema.id.generate(),
153 | attributes: {},
154 | relationships: relationships
155 | };
156 |
157 | var config = {
158 | new: true,
159 | synchronized: false,
160 | stable: false,
161 | pristine: false,
162 | initialization: false
163 | };
164 |
165 | var object = _this.modelFactory(data, config);
166 |
167 | $rootScope.$emit('angularJsonAPI:' + _this.type + ':resource:initialize', 'resolved', object);
168 |
169 | return object;
170 | }
171 |
172 | /**
173 | * Clears localy saved data
174 | * @return {promise} Promise associated with the synchronization resolves to nothing
175 | */
176 | function clearCache() {
177 | var _this = this;
178 | var deferred = $q.defer();
179 | var config = {
180 | action: 'clearCache'
181 | };
182 |
183 | _this.cache.clear();
184 |
185 | _this.synchronizer.synchronize(config).then(resolve, reject, notify);
186 |
187 | return deferred;
188 |
189 | function resolve(response) {
190 | $rootScope.$emit('angularJsonAPI:' + _this.type + ':resource:clearCache', 'resolved', response);
191 | response.finish();
192 |
193 | deferred.resolve(response);
194 | }
195 |
196 | function reject(response) {
197 | $rootScope.$emit('angularJsonAPI:' + _this.type + ':resource:clearCache', 'resolved', response);
198 | response.finish();
199 |
200 | deferred.reject(response);
201 | }
202 |
203 | function notify(response) {
204 | $rootScope.$emit('angularJsonAPI:' + _this.type + ':resource:clearCache', 'notify', response);
205 |
206 | deferred.notify(response);
207 | }
208 | }
209 | }
210 | })();
211 |
--------------------------------------------------------------------------------
/src/resource/resource.factory.spec.js:
--------------------------------------------------------------------------------
1 | /*jshint expr: true*/
2 | (function() {
3 | 'use strict';
4 |
5 | describe('AngularJsonAPIResource', function() {
6 |
7 | beforeEach(module('angular-jsonapi'));
8 |
9 | it('returns valid model', inject(function(AngularJsonAPIResource) {
10 | expect(AngularJsonAPIResource).toBeDefined();
11 | }));
12 | });
13 | })();
14 |
--------------------------------------------------------------------------------
/src/schema/schema-link.factory.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | 'use strict';
3 |
4 | angular.module('angular-jsonapi')
5 | .factory('AngularJsonAPISchemaLink', AngularJsonAPILinkSchrapperLink);
6 |
7 | function AngularJsonAPILinkSchrapperLink($log, pluralize) {
8 |
9 | return {
10 | create: AngularJsonAPISchemaLinkFactory
11 | };
12 |
13 | function AngularJsonAPISchemaLinkFactory(linkSchema, linkName, type) {
14 | return new AngularJsonAPISchemaLink(linkSchema, linkName, type);
15 | }
16 |
17 | function AngularJsonAPISchemaLink(linkSchema, linkName, type) {
18 | var _this = this;
19 |
20 | if (angular.isString(linkSchema)) {
21 | _this.model = pluralize.plural(linkName);
22 | _this.type = linkSchema;
23 | _this.polymorphic = false;
24 | _this.reflection = type;
25 | } else {
26 | if (linkSchema.type === undefined) {
27 | $log.error('Schema of link without a type: ', linkSchema, linkName);
28 | }
29 |
30 | if (linkSchema.type !== 'hasMany' && linkSchema.type !== 'hasOne') {
31 | $log.error('Schema of link with wrong type: ', linkSchema.type, 'available: hasOne, hasMany');
32 | }
33 |
34 | _this.model = linkSchema.model || pluralize.plural(linkName);
35 | _this.type = linkSchema.type;
36 | _this.polymorphic = linkSchema.polymorphic || false;
37 |
38 | if (linkSchema.reflection === undefined) {
39 | _this.reflection = _this.type === 'hasMany' ? pluralize.singular(type) : type;
40 | } else {
41 | _this.reflection = linkSchema.reflection;
42 | }
43 |
44 | _this.included = linkSchema.included || false;
45 | }
46 | }
47 |
48 | }
49 | })();
50 |
--------------------------------------------------------------------------------
/src/schema/schema-link.factory.spec.js:
--------------------------------------------------------------------------------
1 | /*jshint expr: true*/
2 | (function() {
3 | 'use strict';
4 |
5 | describe('AngularJsonAPISchemaLink', function() {
6 |
7 | beforeEach(module('angular-jsonapi'));
8 |
9 | it('returns valid model', inject(function(AngularJsonAPISchemaLink) {
10 | expect(AngularJsonAPISchemaLink).toBeDefined();
11 | }));
12 | });
13 | })();
14 |
--------------------------------------------------------------------------------
/src/schema/schema.factory.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | 'use strict';
3 |
4 | angular.module('angular-jsonapi')
5 | .factory('AngularJsonAPISchema', AngularJsonAPISchemaWrapper);
6 |
7 | function AngularJsonAPISchemaWrapper(
8 | $log,
9 | pluralize,
10 | uuid4,
11 | AngularJsonAPISchemaLink
12 | ) {
13 |
14 | return {
15 | create: AngularJsonAPISchemaFactory
16 | };
17 |
18 | function AngularJsonAPISchemaFactory(schema) {
19 | return new AngularJsonAPISchema(schema);
20 | }
21 |
22 | function AngularJsonAPISchema(schema) {
23 | var _this = this;
24 | var include = schema.include || {};
25 | schema.include = include;
26 | include.get = schema.include.get || [];
27 | include.all = schema.include.all || [];
28 |
29 | _this.params = {
30 | get: {},
31 | all: {}
32 | };
33 |
34 | if (schema.id === 'uuid4') {
35 | schema.id = uuid4;
36 | } else if (schema.id === 'int') {
37 | schema.id = {
38 | generate: angular.noop,
39 | validate: angular.isNumber
40 | };
41 | } else if (angular.isObject(schema.id)) {
42 | if (!angular.isFunction(schema.id.generate)) {
43 | schema.id.generate = angular.noop;
44 | }
45 | } else {
46 | schema.id = {
47 | generate: angular.noop,
48 | validate: angular.identity.bind(null, true)
49 | };
50 | }
51 |
52 | angular.forEach(schema.relationships, function(linkSchema, linkName) {
53 | var linkSchemaObj = AngularJsonAPISchemaLink.create(linkSchema, linkName, schema.type);
54 | schema.relationships[linkName] = linkSchemaObj;
55 | if (linkSchemaObj.included === true) {
56 | include.get.push(linkName);
57 | if (linkSchemaObj.type === 'hasOne') {
58 | include.all.push(linkName);
59 | }
60 | }
61 | });
62 |
63 | angular.extend(_this, schema);
64 |
65 | if (include.get.length > 0) {
66 | _this.params.get.include = include.get;
67 | }
68 |
69 | if (include.all.length > 0) {
70 | _this.params.all.include = include.all;
71 | }
72 | }
73 |
74 | }
75 | })();
76 |
--------------------------------------------------------------------------------
/src/schema/schema.factory.spec.js:
--------------------------------------------------------------------------------
1 | /*jshint expr: true*/
2 | (function() {
3 | 'use strict';
4 |
5 | describe('AngularJsonAPISchema', function() {
6 |
7 | beforeEach(module('angular-jsonapi'));
8 |
9 | it('returns valid model', inject(function(AngularJsonAPISchema) {
10 | expect(AngularJsonAPISchema).toBeDefined();
11 | }));
12 | });
13 | })();
14 |
--------------------------------------------------------------------------------
/src/sources/local/source-local.decorator.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | 'use strict';
3 |
4 | angular.module('angular-jsonapi-local')
5 | .config(provide);
6 |
7 | function provide($provide) {
8 | $provide.decorator('$jsonapi', decorator);
9 | }
10 |
11 | function decorator($delegate, AngularJsonAPISourceLocal) {
12 | var $jsonapi = $delegate;
13 |
14 | $jsonapi.sourceLocal = AngularJsonAPISourceLocal;
15 |
16 | return $jsonapi;
17 | }
18 | })();
19 |
--------------------------------------------------------------------------------
/src/sources/local/source-local.factory.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | 'use strict';
3 |
4 | angular.module('angular-jsonapi-local')
5 | .factory('AngularJsonAPISourceLocal', AngularJsonAPISourceLocalWrapper);
6 |
7 | function AngularJsonAPISourceLocalWrapper(
8 | AngularJsonAPISourcePrototype,
9 | $window,
10 | $q
11 | ) {
12 | var size = {
13 | max: 0,
14 | all: 0,
15 | limit: 5200000,
16 | list: {}
17 | };
18 |
19 | AngularJsonAPISourceLocal.prototype = Object.create(AngularJsonAPISourcePrototype.prototype);
20 | AngularJsonAPISourceLocal.prototype.constructor = AngularJsonAPISourceLocal;
21 |
22 | return {
23 | create: AngularJsonAPISourceLocalFactory,
24 | size: size
25 | };
26 |
27 | function AngularJsonAPISourceLocalFactory(name, prefix) {
28 | return new AngularJsonAPISourceLocal(name, prefix);
29 | }
30 |
31 | function AngularJsonAPISourceLocal(name, prefix) {
32 | var _this = this;
33 |
34 | prefix = prefix || 'AngularJsonAPI';
35 |
36 | _this.__updateStorage = updateStorage;
37 |
38 | AngularJsonAPISourcePrototype.apply(_this, arguments);
39 |
40 | _this.synchronization('init', init);
41 |
42 | _this.begin('clearCache', clear);
43 |
44 | _this.finish('init', updateStorage);
45 | _this.finish('clearCache', updateStorage);
46 | _this.finish('remove', updateStorage);
47 | _this.finish('refresh', updateStorage);
48 | _this.finish('unlink', updateStorage);
49 | _this.finish('unlinkReflection', updateStorage);
50 | _this.finish('link', updateStorage);
51 | _this.finish('linkReflection', updateStorage);
52 | _this.finish('update', updateStorage);
53 | _this.finish('add', updateStorage);
54 | _this.finish('get', updateStorage);
55 | _this.finish('all', updateStorage);
56 | _this.finish('include', updateStorage);
57 |
58 | function init() {
59 | var type = _this.synchronizer.resource.schema.type;
60 | return $q.resolve($window.localStorage.getItem(prefix + '.' + type));
61 | }
62 |
63 | function clear() {
64 | var type = _this.synchronizer.resource.schema.type;
65 | var key = prefix + '.' + type;
66 |
67 | size.all -= size.list[key];
68 | delete size.list[key];
69 | size.max = objectMaxKey(size.list);
70 | size.fraction = size.list[size.max] / size.limit * 100;
71 |
72 | $window.localStorage.removeItem(key);
73 | }
74 |
75 | function updateStorage() {
76 | var type = _this.synchronizer.resource.schema.type;
77 | var cache = _this.synchronizer.resource.cache;
78 | var json = cache.toJson();
79 | var key = prefix + '.' + type;
80 |
81 | size.list[key] = size.list[key] === undefined ? 0 : size.list[key];
82 | size.all += json.length - size.list[key];
83 | size.list[key] = json.length;
84 | size.max = objectMaxKey(size.list);
85 | size.fraction = size.list[size.max] / size.limit * 100;
86 |
87 | $window.localStorage.setItem(key, json);
88 | }
89 |
90 | function objectMaxKey(object) {
91 | return Object.keys(object).reduce(function(m, k) {
92 | return object[k] > object[m] ? k : m;
93 | }, Object.keys(object)[0]);
94 | }
95 | }
96 | }
97 | })();
98 |
--------------------------------------------------------------------------------
/src/sources/local/source-local.module.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | 'use strict';
3 |
4 | angular.module('angular-jsonapi-local', ['angular-jsonapi']);
5 |
6 | })();
7 |
--------------------------------------------------------------------------------
/src/sources/parse/source-parse.decorator.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | 'use strict';
3 |
4 | angular.module('angular-jsonapi-parse')
5 | .config(provide);
6 |
7 | function provide($provide) {
8 | $provide.decorator('$jsonapi', decorator);
9 | }
10 |
11 | function decorator($delegate, AngularJsonAPISourceParse) {
12 | var $jsonapi = $delegate;
13 |
14 | $jsonapi.sourceLocal = AngularJsonAPISourceParse;
15 |
16 | return $jsonapi;
17 | }
18 | })();
19 |
--------------------------------------------------------------------------------
/src/sources/parse/source-parse.factory.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | 'use strict';
3 |
4 | angular.module('angular-jsonapi-parse')
5 | .factory('AngularJsonAPISourceParse', AngularJsonAPISourceParseWrapper);
6 |
7 | function AngularJsonAPISourceParseWrapper(
8 | AngularJsonAPIModelSourceError,
9 | AngularJsonAPISourcePrototype,
10 | AngularJsonAPIModelLinkerService,
11 | pluralize,
12 | Parse,
13 | $log,
14 | $q
15 | ) {
16 |
17 | AngularJsonAPISourceParse.prototype = Object.create(AngularJsonAPISourcePrototype.prototype);
18 | AngularJsonAPISourceParse.prototype.constructor = AngularJsonAPISourceParse;
19 | AngularJsonAPISourceParse.prototype.initialize = initialize;
20 |
21 | return {
22 | create: AngularJsonAPISourceParseFactory
23 | };
24 |
25 | function AngularJsonAPISourceParseFactory(name, table) {
26 | return new AngularJsonAPISourceParse(name, table);
27 | }
28 |
29 | function AngularJsonAPISourceParse(name, table) {
30 | var _this = this;
31 |
32 | _this.ParseObject = Parse.Object.extend(table);
33 | _this.type = pluralize(table).charAt(0).toLowerCase() + pluralize(table).slice(1);
34 |
35 | AngularJsonAPISourcePrototype.apply(_this, arguments);
36 |
37 | _this.synchronization('remove', remove);
38 | _this.synchronization('update', update);
39 | _this.synchronization('add', update);
40 | _this.synchronization('all', all);
41 | _this.synchronization('get', get);
42 | _this.synchronization('refresh', get);
43 |
44 | function all(config) {
45 | var query = new Parse.Query(_this.ParseObject);
46 |
47 | if (config.params.limit !== undefined) {
48 | query.limit(config.params.limit);
49 | }
50 |
51 | angular.forEach(config.params.filter, function(filter) {
52 | query.equalTo(filter.key, filter.value);
53 | });
54 |
55 | return query.find().then(resolveParse, rejectParse.bind(null, 'all'));
56 | }
57 |
58 | function get(config) {
59 | var query = new Parse.Query(_this.ParseObject);
60 | return query.get(config.object.data.id).then(resolveParse, rejectParse.bind(null, 'get'));
61 | }
62 |
63 | function remove(config) {
64 | var object = new _this.ParseObject();
65 | object.set('id', config.object.data.id);
66 | return object.destroy().then(resolveParse, rejectParse.bind(null, 'remove'));
67 | }
68 |
69 | function update(config) {
70 | var object = toParseObject(config.object);
71 | return object.save(null).then(resolveParse, rejectParse.bind(null, 'update'));
72 | }
73 |
74 | function toParseObject(object) {
75 | var parseObject = new _this.ParseObject();
76 | angular.forEach(object.form.data.attributes, function(attribute, key) {
77 | parseObject.set(key, attribute);
78 | });
79 |
80 | angular.forEach(object.schema.relationships, function(relationship, key) {
81 | if (relationship.type === 'hasOne'
82 | && object.form.data.relationships[key].data !== null
83 | && object.form.data.relationships[key].data !== undefined
84 | ) {
85 | var table = pluralize(key, 1).charAt(0).toUpperCase() + pluralize(key, 1).slice(1);
86 | var parsePointer = new (Parse.Object.extend(table))();
87 | parsePointer.set('id', object.form.data.relationships[key].data.id);
88 | parseObject.set(key, parsePointer);
89 | }
90 | });
91 |
92 | return parseObject;
93 | }
94 |
95 | function fromParseObject(object) {
96 | var relationships = _this.synchronizer.resource.schema.relationships;
97 | object.type = _this.type;
98 | object.relationships = object.relationships || [];
99 | angular.forEach(relationships, function(relationship, key) {
100 | if (object.attributes[key] && relationship.type === 'hasOne') {
101 | object.relationships[key] = {
102 | data: {
103 | type: relationship.model,
104 | id: object.attributes[key].id
105 | }
106 | };
107 | }
108 | });
109 |
110 | return object;
111 | }
112 |
113 | function resolveParse(response) {
114 | if (angular.isArray(response)) {
115 | angular.forEach(response, function(elem, key) {
116 | response[key] = fromParseObject(elem);
117 | });
118 | } else if (angular.isObject(response)) {
119 | response = fromParseObject(response);
120 | }
121 |
122 | return $q.resolve({
123 | data: response
124 | });
125 | }
126 |
127 | function rejectParse(action, response) {
128 | $log.error('Parse error for', action, response);
129 | return $q.reject(response);
130 | }
131 | }
132 |
133 | function initialize(appId, jsKey) {
134 | Parse.initialize(appId, jsKey);
135 | }
136 | }
137 | })();
138 |
--------------------------------------------------------------------------------
/src/sources/parse/source-parse.module.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | 'use strict';
3 |
4 | /* global Parse: false */
5 | angular.module('angular-jsonapi-parse', ['angular-jsonapi'])
6 | .constant('Parse', Parse);
7 | })();
8 |
--------------------------------------------------------------------------------
/src/sources/rest/source-rest.decorator.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | 'use strict';
3 |
4 | angular.module('angular-jsonapi-rest')
5 | .config(provide);
6 |
7 | function provide($provide) {
8 | $provide.decorator('$jsonapi', decorator);
9 | }
10 |
11 | function decorator($delegate, AngularJsonAPISourceRest) {
12 | var $jsonapi = $delegate;
13 |
14 | $jsonapi.sourceRest = AngularJsonAPISourceRest;
15 |
16 | return $jsonapi;
17 | }
18 | })();
19 |
--------------------------------------------------------------------------------
/src/sources/rest/source-rest.factory.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | 'use strict';
3 |
4 | angular.module('angular-jsonapi-rest')
5 | .factory('AngularJsonAPISourceRest', AngularJsonAPISourceRestWrapper);
6 |
7 | function AngularJsonAPISourceRestWrapper(
8 | AngularJsonAPIModelSourceError,
9 | AngularJsonAPISourcePrototype,
10 | AngularJsonAPIModelLinkerService,
11 | toKebabCase,
12 | $q,
13 | $http
14 | ) {
15 |
16 | AngularJsonAPISourceRest.prototype = Object.create(AngularJsonAPISourcePrototype.prototype);
17 | AngularJsonAPISourceRest.prototype.constructor = AngularJsonAPISourceRest;
18 |
19 | return {
20 | create: AngularJsonAPISourceRestFactory,
21 | encodeParams: encodeParams,
22 | decodeParams: decodeParams
23 | };
24 |
25 | function AngularJsonAPISourceRestFactory(name, url) {
26 | return new AngularJsonAPISourceRest(name, url);
27 | }
28 |
29 | function AngularJsonAPISourceRest(name, url) {
30 | var _this = this;
31 | var headers = { // jscs:disable disallowQuotedKeysInObjects
32 | 'Accept': 'application/vnd.api+json',
33 | 'Content-Type': 'application/vnd.api+json'
34 | }; // jscs:enable disallowQuotedKeysInObjects
35 |
36 | AngularJsonAPISourcePrototype.apply(_this, arguments);
37 |
38 | _this.synchronization('remove', remove);
39 | _this.synchronization('unlink', unlink);
40 | _this.synchronization('link', link);
41 | _this.synchronization('update', update);
42 | _this.synchronization('add', add);
43 | _this.synchronization('all', all);
44 | _this.synchronization('get', get);
45 | _this.synchronization('refresh', get);
46 |
47 | function all(config) {
48 | return $http({
49 | method: 'GET',
50 | headers: headers,
51 | url: url,
52 | params: encodeParams(config.params)
53 | }).then(resolveHttp, rejectHttp.bind(null, 'all'));
54 | }
55 |
56 | function get(config) {
57 | return $http({
58 | method: 'GET',
59 | headers: headers,
60 | url: url + '/' + config.object.data.id,
61 | params: encodeParams(config.params)
62 | }).then(resolveHttp, rejectHttp.bind(null, 'get'));
63 | }
64 |
65 | function remove(config) {
66 | return $http({
67 | method: 'DELETE',
68 | headers: headers,
69 | url: url + '/' + config.object.data.id
70 | }).then(resolveHttp, rejectHttp.bind(null, 'remove'));
71 | }
72 |
73 | function unlink(config) {
74 | var deferred = $q.defer();
75 | var schema = config.object.schema.relationships[config.key];
76 |
77 | if (config.object.removed === true) {
78 | deferred.reject(AngularJsonAPIModelSourceError.create('Object has been removed', _this, 0, 'unlink'));
79 | } else if (config.target !== undefined && config.target.data.id === undefined) {
80 | deferred.reject(AngularJsonAPIModelSourceError.create('Can\'t unlink object without id through rest call', _this, 0, 'unlink'));
81 | } else if (schema.type === 'hasOne') {
82 | $http({
83 | method: 'DELETE',
84 | headers: headers,
85 | url: url + '/' + config.object.data.id + '/relationships/' + config.key
86 | }).then(resolveHttp, rejectHttp.bind(null, 'get')).then(deferred.resolve, deferred.reject);
87 | } else if (schema.type === 'hasMany') {
88 | if (config.target === undefined) {
89 | $http({
90 | method: 'PUT',
91 | headers: headers,
92 | data: {data: []},
93 | url: url + '/' + config.object.data.id + '/relationships/' + config.key
94 | }).then(resolveHttp, rejectHttp.bind(null, 'unlink')).then(deferred.resolve, deferred.reject);
95 | } else {
96 | $http({
97 | method: 'DELETE',
98 | headers: headers,
99 | url: url + '/' + config.object.data.id + '/relationships/' + config.key + '/' + config.target.data.id
100 | }).then(resolveHttp, rejectHttp.bind(null, 'unlink')).then(deferred.resolve, deferred.reject);
101 | }
102 | }
103 |
104 | return deferred.promise;
105 | }
106 |
107 | function link(config) {
108 | var deferred = $q.defer();
109 | var schema = config.object.schema.relationships[config.key];
110 |
111 | if (config.object.removed === true) {
112 | deferred.reject({errors: [{status: 0, statusText: 'Object has been removed'}]});
113 | } else if (config.target === undefined || config.target.data.id === undefined) {
114 | deferred.reject({errors: [{status: 0, statusText: 'Can\'t link object without id through rest call'}]});
115 | } else if (schema.type === 'hasOne') {
116 | $http({
117 | method: 'PUT',
118 | headers: headers,
119 | data: {data: AngularJsonAPIModelLinkerService.toLinkData(config.target)},
120 | url: url + '/' + config.object.data.id + '/relationships/' + config.key
121 | }).then(resolveHttp, rejectHttp.bind(null, 'link')).then(deferred.resolve, deferred.reject);
122 | } else if (schema.type === 'hasMany') {
123 | $http({
124 | method: 'POST',
125 | headers: headers,
126 | data: {data: [AngularJsonAPIModelLinkerService.toLinkData(config.target)]},
127 | url: url + '/' + config.object.data.id + '/relationships/' + config.key
128 | }).then(resolveHttp, rejectHttp.bind(null, 'link')).then(deferred.resolve, deferred.reject);
129 | }
130 |
131 | return deferred.promise;
132 | }
133 |
134 | function update(config) {
135 | return $http({
136 | method: 'PUT',
137 | headers: headers,
138 | url: url + '/' + config.object.data.id,
139 | data: config.object.form.toJson()
140 | }).then(resolveHttp, rejectHttp.bind(null, 'update'));
141 | }
142 |
143 | function add(config) {
144 | return $http({
145 | method: 'POST',
146 | headers: headers,
147 | url: url,
148 | data: config.object.form.toJson()
149 | }).then(resolveHttp, rejectHttp.bind(null, 'add'));
150 | }
151 |
152 | function resolveHttp(response) {
153 | return $q.resolve(response.data);
154 | }
155 |
156 | function rejectHttp(action, response) {
157 | var deferred = $q.defer();
158 |
159 | if (response.status === 0) {
160 | $http({
161 | method: 'GET',
162 | url: 'https://status.cloud.google.com/incidents.schema.json'
163 | }).then(rejectServerOffline, rejectNoConnection);
164 | } else {
165 | deferred.reject(AngularJsonAPIModelSourceError.create(response.statusText, _this, response.status, action));
166 | }
167 |
168 | return deferred.promise;
169 |
170 | function rejectServerOffline(response) {
171 | deferred.reject(AngularJsonAPIModelSourceError.create('Server is offline', _this, response.status, action));
172 | }
173 |
174 | function rejectNoConnection() {
175 | deferred.reject(AngularJsonAPIModelSourceError.create('No internet connection', _this, response.status, action));
176 | }
177 | }
178 | }
179 |
180 | function encodeParams(params) {
181 | var encodedParams = {};
182 |
183 | if (params === undefined) {
184 | return {};
185 | }
186 |
187 | angular.forEach(params, function(paramValue, paramKey) {
188 | if (angular.isArray(paramValue)) {
189 | encodedParams[paramKey] = encodeValue(paramValue);
190 | } else if (angular.isObject(paramValue)) {
191 | angular.forEach(paramValue, function(paramInnerValue, paramInnerKey) {
192 | encodedParams[paramKey + '[' + paramInnerKey + ']'] = encodeValue(paramInnerValue);
193 | });
194 | } else {
195 | encodedParams[paramKey] = paramValue;
196 | }
197 | });
198 |
199 | return encodedParams;
200 |
201 | function encodeValue(argument) {
202 | if (angular.isArray(argument)) {
203 | return argument.join(',');
204 | } else {
205 | return argument;
206 | }
207 | }
208 | }
209 |
210 | function decodeParams(params) {
211 | var decodedParams = {};
212 |
213 | angular.forEach(params, function(value, key) {
214 | var objectKeyStart = key.indexOf('[');
215 | value = value.split(',');
216 |
217 | if (objectKeyStart > -1) {
218 | var objectKey = key.substr(0, objectKeyStart);
219 | var objectElementKey = key.substr(objectKeyStart + 1, key.indexOf(']') - objectKeyStart - 1);
220 |
221 | decodedParams[objectKey] = decodedParams[objectKey] || {};
222 | decodedParams[objectKey][objectElementKey] = value;
223 | } else {
224 | decodedParams[key] = value;
225 | }
226 | });
227 |
228 | return decodedParams;
229 | }
230 | }
231 | })();
232 |
--------------------------------------------------------------------------------
/src/sources/rest/source-rest.module.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | 'use strict';
3 |
4 | angular.module('angular-jsonapi-rest', ['angular-jsonapi']);
5 |
6 | })();
7 |
--------------------------------------------------------------------------------
/src/sources/source-prototype.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | 'use strict';
3 |
4 | angular.module('angular-jsonapi')
5 | .factory('AngularJsonAPISourcePrototype', AngularJsonAPISourcePrototypeWrapper);
6 |
7 | function AngularJsonAPISourcePrototypeWrapper() {
8 | AngularJsonAPISourcePrototype.prototype.before = beforeSynchro;
9 | AngularJsonAPISourcePrototype.prototype.after = afterSynchro;
10 | AngularJsonAPISourcePrototype.prototype.begin = begin;
11 | AngularJsonAPISourcePrototype.prototype.finish = finish;
12 | AngularJsonAPISourcePrototype.prototype.synchronization = synchronization;
13 |
14 | return AngularJsonAPISourcePrototype;
15 |
16 | function AngularJsonAPISourcePrototype(name) {
17 | var _this = this;
18 | var allHooks = [
19 | 'add',
20 | 'init',
21 | 'get',
22 | 'all',
23 | 'clearCache',
24 | 'remove',
25 | 'unlink',
26 | 'unlinkReflection',
27 | 'link',
28 | 'linkReflection',
29 | 'update',
30 | 'refresh',
31 | 'include'
32 | ];
33 |
34 | _this.name = name;
35 | _this.state = {};
36 |
37 | _this.beginHooks = {};
38 | _this.beforeHooks = {};
39 | _this.synchronizationHooks = {};
40 | _this.afterHooks = {};
41 | _this.finishHooks = {};
42 |
43 | _this.options = {};
44 |
45 | angular.forEach(allHooks, function(hookName) {
46 | _this.beginHooks[hookName] = [];
47 | _this.beforeHooks[hookName] = [];
48 | _this.synchronizationHooks[hookName] = [];
49 | _this.afterHooks[hookName] = [];
50 | _this.finishHooks[hookName] = [];
51 | _this.state[hookName] = {
52 | loading: false,
53 | success: true
54 | };
55 | });
56 | }
57 |
58 | function begin(action, callback) {
59 | var _this = this;
60 |
61 | _this.beginHooks[action].push(callback);
62 | }
63 |
64 | function finish(action, callback) {
65 | var _this = this;
66 |
67 | _this.finishHooks[action].push(callback);
68 | }
69 |
70 | function beforeSynchro(action, callback) {
71 | var _this = this;
72 |
73 | _this.beforeHooks[action].push(callback);
74 | }
75 |
76 | function afterSynchro(action, callback) {
77 | var _this = this;
78 |
79 | _this.afterHooks[action].push(callback);
80 | }
81 |
82 | function synchronization(action, callback) {
83 | var _this = this;
84 |
85 | _this.synchronizationHooks[action].push(callback);
86 | }
87 |
88 | }
89 | })();
90 |
--------------------------------------------------------------------------------
/src/synchronizers/synchronizer-prototype.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | 'use strict';
3 |
4 | angular.module('angular-jsonapi')
5 | .factory('AngularJsonAPISynchronizerPrototype', AngularJsonAPISynchronizerPrototypeWrapper);
6 |
7 | function AngularJsonAPISynchronizerPrototypeWrapper($log) {
8 |
9 | AngularJsonAPISynchronizerPrototype.prototype.synchronize = synchronize;
10 |
11 | return AngularJsonAPISynchronizerPrototype;
12 |
13 | function AngularJsonAPISynchronizerPrototype(sources) {
14 | var _this = this;
15 |
16 | _this.sources = sources;
17 | }
18 |
19 | function synchronize(config) {
20 | var _this = this;
21 |
22 | $log.debug('Synchro Collection', _this.resource.schema.type, config);
23 |
24 | if (config.action === undefined) {
25 | $log.error('Can\'t synchronize undefined action', config);
26 | }
27 | }
28 | }
29 | })();
30 |
--------------------------------------------------------------------------------
/src/synchronizers/synchronizer-simple.factory.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | 'use strict';
3 |
4 | angular.module('angular-jsonapi')
5 | .factory('AngularJsonAPISynchronizerSimple', AngularJsonAPISynchronizerSimpleWrapper);
6 |
7 | function AngularJsonAPISynchronizerSimpleWrapper(AngularJsonAPISynchronizerPrototype, $q, $log) {
8 |
9 | AngularJsonAPISynchronizerSimple.prototype = Object.create(AngularJsonAPISynchronizerPrototype.prototype);
10 | AngularJsonAPISynchronizerSimple.prototype.constructor = AngularJsonAPISynchronizerSimple;
11 |
12 | AngularJsonAPISynchronizerSimple.prototype.synchronize = synchronize;
13 |
14 | return {
15 | create: AngularJsonAPISynchronizerSimpleFactory
16 | };
17 |
18 | function AngularJsonAPISynchronizerSimpleFactory(sources) {
19 | return new AngularJsonAPISynchronizerSimple(sources);
20 | }
21 |
22 | function AngularJsonAPISynchronizerSimple(sources) {
23 | var _this = this;
24 |
25 | _this.state = {};
26 |
27 | AngularJsonAPISynchronizerPrototype.call(_this, sources);
28 |
29 | angular.forEach(sources, function(source) {
30 | source.synchronizer = _this;
31 | });
32 | }
33 |
34 | function synchronize(config) {
35 | var _this = this;
36 | var promises = [];
37 | var deferred = $q.defer();
38 | var action = config.action;
39 |
40 | AngularJsonAPISynchronizerPrototype.prototype.synchronize.call(_this, config);
41 |
42 | angular.forEach(_this.sources, function(source) {
43 | angular.forEach(source.beginHooks[action], function(hook) {
44 | deferred.notify({step: 'begin', data: hook.call(_this, config)});
45 | });
46 | });
47 |
48 | angular.forEach(_this.sources, function(source) {
49 | angular.forEach(source.beforeHooks[action], function(hook) {
50 | deferred.notify({step: 'before', data: hook.call(_this, config)});
51 | });
52 | });
53 |
54 | angular.forEach(_this.sources, function(source) {
55 | angular.forEach(source.synchronizationHooks[action], function(hook) {
56 | promises.push(hook.call(_this, config));
57 | });
58 | });
59 |
60 | $q.allSettled(promises, resolvedCallback, rejectedCallback).then(resolved, rejected);
61 |
62 | function resolvedCallback(value) {
63 | deferred.notify({step: 'synchronization', data: value});
64 | }
65 |
66 | function rejectedCallback(reason) {
67 | deferred.notify({step: 'synchronization', errors: reason});
68 | }
69 |
70 | function resolved(results) {
71 | _this.state[action] = _this.state[action] || {};
72 | _this.state[action].success = true;
73 |
74 | angular.forEach(results, function(result) {
75 | if (result.success === false) {
76 | _this.state[action].success = false;
77 | }
78 | });
79 |
80 | angular.forEach(_this.sources, function(source) {
81 | angular.forEach(source.afterHooks[action], function(hook) {
82 | deferred.notify({step: 'after', errors: hook.call(_this, config, results)});
83 | });
84 | });
85 |
86 | var data;
87 | var errors = [];
88 |
89 | angular.forEach(results, function(result) {
90 | if (result.success === true) {
91 | data = result.value;
92 | } else {
93 | errors.push({
94 | key: action,
95 | object: result.reason
96 | });
97 | }
98 | });
99 |
100 | if (errors.length > 0) {
101 | deferred.reject({data: data || {}, finish: finish, errors: errors});
102 | } else {
103 | deferred.resolve({data: data || {}, finish: finish, errors: errors});
104 | }
105 | }
106 |
107 | function finish() {
108 | angular.forEach(_this.sources, function(source) {
109 | angular.forEach(source.finishHooks[action], function(hook) {
110 | deferred.notify({step: 'finish', errors: hook.call(_this, config)});
111 | });
112 | });
113 | }
114 |
115 | function rejected(errors) {
116 | $log.error('All settled rejected! Something went wrong');
117 |
118 | deferred.reject({finish: angular.noop, errors: errors});
119 | }
120 |
121 | return deferred.promise;
122 | }
123 | }
124 | })();
125 |
--------------------------------------------------------------------------------
/src/synchronizers/synchronizer-simple.factory.spec.js:
--------------------------------------------------------------------------------
1 | /*jshint expr: true*/
2 | (function() {
3 | 'use strict';
4 |
5 | describe('AngularJsonAPISynchronizerSimple', function() {
6 |
7 | beforeEach(module('angular-jsonapi'));
8 |
9 | it('returns valid model', inject(function(AngularJsonAPISynchronizerSimple) {
10 | expect(AngularJsonAPISynchronizerSimple).toBeDefined();
11 | }));
12 | });
13 | })();
14 |
--------------------------------------------------------------------------------
/src/utils/all-settled.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | 'use strict';
3 |
4 | angular.module('angular-jsonapi')
5 | .config(provide);
6 |
7 | function provide($provide) {
8 | $provide.decorator('$q', decorator);
9 | }
10 |
11 | function decorator($delegate) {
12 | var $q = $delegate;
13 |
14 | $q.allSettled = $q.allSettled || allSettled;
15 |
16 | function allSettled(promises, resolvedCallback, rejectedCallback) {
17 | // Implementation of allSettled function from Kris Kowal's Q:
18 | // https://github.com/kriskowal/q/wiki/API-Reference#promiseallsettled
19 | // by Michael Kropat from http://stackoverflow.com/a/27114615/1400432 slightly modified
20 |
21 | var wrapped = angular.isArray(promises) ? [] : {};
22 |
23 | angular.forEach(promises, function(promise, key) {
24 | if (!wrapped.hasOwnProperty(key)) {
25 | wrapped[key] = wrap(promise);
26 | }
27 | });
28 |
29 | return $q.all(wrapped);
30 |
31 | function wrap(promise) {
32 | return $q.resolve(promise)
33 | .then(function(value) {
34 | if (angular.isFunction(resolvedCallback)) {
35 | resolvedCallback(value);
36 | }
37 |
38 | return { success: true, value: value };
39 | },
40 |
41 | function(reason) {
42 | if (angular.isFunction(rejectedCallback)) {
43 | rejectedCallback(reason);
44 | }
45 |
46 | return { success: false, reason: reason };
47 | });
48 | }
49 | }
50 |
51 | return $q;
52 | }
53 | })();
54 |
--------------------------------------------------------------------------------
/src/utils/kebab-case.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | 'use strict';
3 |
4 | angular.module('angular-jsonapi')
5 | .constant('toKebabCase', function(str) {
6 | return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
7 | });
8 |
9 | })();
10 |
--------------------------------------------------------------------------------
/src/utils/lazy-property.js:
--------------------------------------------------------------------------------
1 | // from https://www.sitepen.com/blog/2012/10/19/lazy-property-access/
2 | (function() {
3 | 'use strict';
4 |
5 | angular.module('angular-jsonapi')
6 | .constant('lazyProperty', function(target, propertyName, callback) {
7 | var result;
8 | var done;
9 | Object.defineProperty(target, propertyName, {
10 | get: function() { // Define the getter
11 | if (!done) {
12 | // We cache the result and only compute once.
13 | done = true;
14 | result = callback.call(target);
15 | }
16 |
17 | return result;
18 | },
19 |
20 | // Keep it enumerable and configurable, certainly not necessary.
21 | enumerable: true,
22 | configurable: true
23 | });
24 | });
25 |
26 | })();
27 |
--------------------------------------------------------------------------------
/src/utils/named-function.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | 'use strict';
3 |
4 | angular.module('angular-jsonapi')
5 | .constant('namedFunction', namedFunction);
6 |
7 | function namedFunction(name, fn) {
8 | return new Function('fn',
9 | 'return function ' + name + '(){ return fn.apply(this,arguments)}'
10 | )(fn);
11 | }
12 | })();
13 |
--------------------------------------------------------------------------------