├── .bowerrc
├── .editorconfig
├── .gitignore
├── .jshintrc
├── .travis.yml
├── .yo-rc.json
├── LICENSE
├── README.md
├── bower.json
├── dist
├── angular-contentful.js
└── angular-contentful.min.js
├── examples
├── how-to-use-the-contentful-service
│ ├── app.js
│ └── index.html
└── how-to-use-the-directives
│ ├── app.js
│ └── index.html
├── gulpfile.js
├── karma-dist-concatenated.conf.js
├── karma-dist-minified.conf.js
├── karma-src.conf.js
├── package.json
├── src
└── contentful
│ ├── contentful.module.js
│ ├── controllers
│ └── contentful-directive.controller.js
│ ├── directives
│ ├── contentful-entries.directive.js
│ └── contentful-entry.directive.js
│ └── services
│ ├── contentful-helpers.service.js
│ └── contentful.service.js
└── test
└── unit
└── contentful
├── contentful.module.spec.js
├── directives
├── contentful-entries.directive.spec.js
└── contentful-entry.directive.spec.js
└── services
├── contentful-helpers.service.spec.js
└── contentful.service.spec.js
/.bowerrc:
--------------------------------------------------------------------------------
1 | {
2 | "directory": "bower"
3 | }
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | indent_style = space
6 | indent_size = 2
7 | charset = utf-8
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 |
11 | [*.md]
12 | trim_trailing_whitespace = false
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ._*
2 | .~lock.*
3 | .buildpath
4 | .DS_Store
5 | .idea
6 | .project
7 | .settings
8 |
9 | # Ignore node stuff
10 | node_modules/
11 | npm-debug.log
12 | libpeerconnection.log
13 |
14 | # OS-specific
15 | .DS_Store
16 |
17 | # Bower components
18 | bower
19 |
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "bitwise": true,
3 | "camelcase": true,
4 | "curly": true,
5 | "eqeqeq": true,
6 | "es3": false,
7 | "forin": true,
8 | "freeze": true,
9 | "immed": true,
10 | "indent": 2,
11 | "latedef": "nofunc",
12 | "newcap": true,
13 | "noarg": true,
14 | "noempty": true,
15 | "nonbsp": true,
16 | "nonew": true,
17 | "plusplus": false,
18 | "quotmark": "single",
19 | "undef": true,
20 | "unused": false,
21 | "strict": false,
22 | "maxparams": 10,
23 | "maxdepth": 5,
24 | "maxstatements": 40,
25 | "maxcomplexity": 8,
26 | "maxlen": 120,
27 |
28 | "asi": false,
29 | "boss": false,
30 | "debug": false,
31 | "eqnull": true,
32 | "esnext": false,
33 | "evil": false,
34 | "expr": false,
35 | "funcscope": false,
36 | "globalstrict": false,
37 | "iterator": false,
38 | "lastsemic": false,
39 | "laxbreak": false,
40 | "laxcomma": false,
41 | "loopfunc": true,
42 | "maxerr": false,
43 | "moz": false,
44 | "multistr": false,
45 | "notypeof": false,
46 | "proto": false,
47 | "scripturl": false,
48 | "shadow": false,
49 | "sub": true,
50 | "supernew": false,
51 | "validthis": false,
52 | "noyield": false,
53 |
54 | "browser": true,
55 | "node": true,
56 |
57 | "globals": {
58 | "angular": false,
59 | "$": false
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - 4.0
4 | before_script:
5 | - npm install -g bower
6 | - bower install
7 |
--------------------------------------------------------------------------------
/.yo-rc.json:
--------------------------------------------------------------------------------
1 | {
2 | "generator-angularjs-library": {
3 | "props": {
4 | "author": {
5 | "name": "Jurgen Van de Moere",
6 | "email": "jurgen.van.de.moere@gmail.com"
7 | },
8 | "libraryName": {
9 | "original": "contentful",
10 | "camelized": "contentful",
11 | "dasherized": "contentful",
12 | "slugified": "contentful",
13 | "parts": [
14 | "contentful"
15 | ]
16 | },
17 | "includeModuleDirectives": true,
18 | "includeModuleFilters": false,
19 | "includeModuleServices": true,
20 | "includeAngularModuleResource": false,
21 | "includeAngularModuleCookies": false,
22 | "includeAngularModuleSanitize": false,
23 | "librarySrcDirectory": "src/contentful",
24 | "libraryUnitTestDirectory": "test/unit/contentful",
25 | "libraryUnitE2eDirectory": "test/e2e/contentful"
26 | }
27 | }
28 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2015 Jurgen Van de Moere
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | "Software"), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # AngularJS Contentful
2 |
3 | AngularJS module to easily access the [Contentful](https://www.contentful.com) [content delivery API](https://www.contentful.com/developers/documentation/content-delivery-api/):
4 |
5 | 
6 |
7 | [](https://travis-ci.org/jvandemo/angular-contentful)
8 |
9 | - lightweight (< 3KB minified)
10 | - no external dependencies
11 | - automatically resolves linked content in the response for maximum convenience
12 | - uses the native AngularJS `$http` service to connect to the API
13 | - returns native AngularJS `$q` promises
14 |
15 | ## Demo
16 |
17 | There are working demo's available in the [examples](examples) directory.
18 |
19 | ## Usage
20 |
21 | First install the module using bower:
22 |
23 | ```bash
24 | $ bower install angular-contentful
25 | ```
26 |
27 | or npm:
28 |
29 | ```bash
30 | $ npm install angular-contentful
31 | ```
32 |
33 | and add the library to your application:
34 |
35 | ```xml
36 |
37 | ```
38 |
39 | > The `src` attribute value above is an example when using `bower`. Your local library path may vary depending on whether you used `bower` or `npm` as your installation method and whether or not you have a build process in place.
40 |
41 | Then add the `contentful` module to the dependencies of your AngularJS application module:
42 |
43 | ```javascript
44 | angular.module('yourApp', ['contentful']);
45 | ```
46 |
47 | and configure the `contentful` service in a config block using the provider:
48 |
49 | ```javascript
50 | angular
51 | .module('yourApp')
52 | .config(function(contentfulProvider){
53 | contentfulProvider.setOptions({
54 | space: 'yourSpace',
55 | accessToken: 'yourAccessToken'
56 | });
57 | });
58 | ```
59 |
60 | Now you can use one of the directives to fetch Contentful data right from within your markup:
61 |
62 | ```xml
63 |
64 | {{ $contentfulEntry | json }}
65 |
66 | ```
67 |
68 | or you can use the `contentful` service anywhere in your application code:
69 |
70 | ```javascript
71 | angular
72 | .module('yourApp')
73 | .controller('SomeCtrl', function(contentful){
74 |
75 | // Get all entries
76 | contentful
77 | .entries()
78 | .then(
79 |
80 | // Success handler
81 | function(response){
82 | var entries = response.data;
83 | console.log(entries);
84 | },
85 |
86 | // Error handler
87 | function(response){
88 | console.log('Oops, error ' + response.status);
89 | }
90 | );
91 |
92 | });
93 | ```
94 |
95 | ## The contentful-entry directive
96 |
97 | Fetches a Contentful entry asynchronously in the background and makes it available in your child markup as `$contentfulEntry` as soon as a response from Contentful is received.
98 |
99 | Requires a Contentful entry id or a query string to be passed.
100 |
101 | #### Fetch an entry by id
102 |
103 | To display an entire entry with id *6KntaYXaHSyIw8M6eo26OK*:
104 |
105 | ```xml
106 |
107 |
108 | {{ $contentfulEntry | json }}
109 |
110 |
111 |
112 |
113 | {{ $contentfulEntry | json }}
114 |
115 | ```
116 |
117 | Or to display only one field of the entry:
118 |
119 | ```xml
120 |
121 | Hi {{ $contentfulEntry.fields.name }}!
122 |
123 | ```
124 |
125 | `$contentfulEntry` is available in the child elements as well:
126 |
127 | ```xml
128 |
129 |
130 | {{ $contentfulEntry.fields.sectionOne }}
131 |
132 |
133 | {{ $contentfulEntry.fields.sectionTwo }}
134 |
135 |
136 | ```
137 |
138 | To make Contentful resolve the linked content in the entry, use [include](https://www.contentful.com/developers/documentation/content-delivery-api/#search-link):
139 |
140 | ```xml
141 |
142 | {{ $contentfulEntry | json }}
143 |
144 | ```
145 |
146 | to specify the number of levels of linked entries to resolve.
147 |
148 | #### Fetch an entry by query string
149 |
150 | Often you want to fetch an entry by a property other than `sys.id`.
151 |
152 | Therefore the directive also allows you to specify a query string instead of an id like this:
153 |
154 | ```xml
155 |
156 | Hi {{ $contentfulEntry.fields.name }}!
157 |
158 | ```
159 |
160 | **Notice**
161 |
162 | Behind the scenes all entries matching your query will be fetched and the first item will be assigned to `$contentfulEntry`.
163 |
164 | To reduce data traffic it is highly recommended to use a query string that results in only one entry or add a `limit=1` statement to your query like this:
165 |
166 | ```xml
167 |
168 | Hi {{ $contentfulEntry.fields.name }}!
169 |
170 | ```
171 |
172 | ## The contentful-entries directive
173 |
174 | Fetches multiple Contentful entries asynchronously in the background and makes them available in your child markup as `$contentfulEntries` as soon as a response from Contentful is received.
175 |
176 | Takes an optional query string value to pass to the Contentful content delivery API.
177 |
178 | For example, to fetch all entries in your space:
179 |
180 | ```xml
181 |
182 |
183 | {{ entry.fields.name }}
184 |
185 |
186 | ```
187 |
188 | Or specify a query string to filter the entries:
189 |
190 | ```xml
191 |
192 |
193 |
194 | {{ dog.fields.name }}
195 |
196 |
197 |
198 |
199 |
200 |
201 | {{ dog.fields.name }}
202 |
203 |
204 | ```
205 |
206 | The optional query string is passed to the Contentful API, so you can use all [supported filters](https://www.contentful.com/developers/documentation/content-management-api/#search-filter).
207 |
208 | Links are automatically resolved too, so you can easily access linked content as embedded data like this:
209 |
210 | ```xml
211 |
212 |
213 |
{{ dog.fields.name }}
214 |
215 |
216 |
217 | ```
218 |
219 | ## The contentful service
220 |
221 | The `contentful` service can be injected anywhere in your application and exposes the following API:
222 |
223 | ### contentful.asset(id, optionSet)
224 |
225 | Get an asset.
226 |
227 | ##### Arguments
228 |
229 | - **id** - {string} - Asset id, required
230 | - **optionSet** - {string} - optional - Contentful space options key passed to `setOptions` method of provider. Defaults to `default` key, or if only one space settings are passed it will use that one.
231 |
232 | ###### Example
233 |
234 | ```javascript
235 | // pass key defined in setOptions method of provider
236 | contentful.asset(id, 'another');
237 | // or you can pass 'default', although it will implicitly use this key
238 | contentful.asset(id, 'default');
239 | ```
240 |
241 | ##### Returns
242 |
243 | Promise.
244 |
245 | ### contentful.assets(queryString, optionSet)
246 |
247 | Get assets.
248 |
249 | ##### Arguments
250 |
251 | - **queryString** - {string} - Query string to pass to API, optional
252 | - **optionSet** - {string} - optional - Contentful space options key passed to `setOptions` method of provider. Defaults to `default` key, or if only one space settings are passed it will use that one.
253 |
254 | ###### Example
255 |
256 | ```javascript
257 | // pass key defined in setOptions method of provider
258 | contentful.assets(queryString, 'another');
259 | // or you can pass 'default', although it will implicitly use this key
260 | contentful.assets(queryString, 'default');
261 | ```
262 |
263 | ##### Returns
264 |
265 | Promise.
266 |
267 | ### contentful.contentType(id, optionSet)
268 |
269 | Get a content type.
270 |
271 | ##### Arguments
272 |
273 | - **id** - {string} - Content type id, required
274 | - **optionSet** - {string} - optional - Contentful space options (one of keys passed to setOptions method of provider). If not passed it will use `default` key, or if only one space settings are passed it will use that one.
275 |
276 | ###### Example
277 |
278 | ```javascript
279 | // pass key defined in setOptions method of provider
280 | contentful.contentType(id, 'another');
281 | // or you can pass 'default', although it will implicitly use this key
282 | contentful.contentType(id, 'default');
283 | ```
284 |
285 | ##### Returns
286 |
287 | Promise.
288 |
289 | ### contentful.contentTypes(queryString, optionSet)
290 |
291 | Get content types.
292 |
293 | ##### Arguments
294 |
295 | - **queryString** - {string} - Query string to pass to API, optional
296 | - **optionSet** - {string} - optional - Contentful space options key passed to `setOptions` method of provider. Defaults to `default` key, or if only one space settings are passed it will use that one.
297 |
298 | ###### Example
299 |
300 | ```javascript
301 | // pass key defined in setOptions method of provider
302 | contentful.contentTypes(queryString, 'another');
303 | // or you can pass 'default', although it will implicitly use this key if parameter is omitted
304 | contentful.contentTypes(queryString, 'default');
305 | ```
306 |
307 | ##### Returns
308 |
309 | Promise.
310 |
311 | ### contentful.entry(id, optionSet)
312 |
313 | Get an entry.
314 |
315 | ##### Arguments
316 |
317 | - **id** - {string} - Entry id, required
318 | - **optionSet** - {string} - optional - Contentful space options key passed to `setOptions` method of provider. Defaults to `default` key, or if only one space settings are passed it will use that one.
319 |
320 | ###### Example
321 |
322 | ```javascript
323 | // pass key defined in setOptions method of provider
324 | contentful.entry(id, 'another');
325 | // or you can pass 'default', although it will implicitly use this key if parameter is omitted
326 | contentful.entry(id, 'default');
327 | ```
328 |
329 | ##### Returns
330 |
331 | Promise.
332 |
333 | ### contentful.entries(queryString, optionSet)
334 |
335 | Get entries.
336 |
337 | ##### Arguments
338 |
339 | - **queryString** - {string} - Query string to pass to API, optional
340 | - **optionSet** - {string} - optional - Contentful space options key passed to `setOptions` method of provider. Defaults to `default` key, or if only one space settings are passed it will use that one.
341 |
342 | ###### Example
343 |
344 | ```javascript
345 | // pass key defined in setOptions method of provider
346 | contentful.entries(queryString, 'another');
347 | // or you can pass 'default', although it will implicitly use this key if parameter is omitted
348 | contentful.entries(queryString, 'default');
349 | ```
350 |
351 | ##### Returns
352 |
353 | Promise.
354 |
355 | ### contentful.space(optionSet)
356 |
357 | Get space.
358 |
359 | ##### Arguments
360 |
361 | - **optionSet** - {string} - optional - Contentful space options key passed to `setOptions` method of provider. Defaults to `default` key, or if only one space settings are passed it will use that one.
362 |
363 | ###### Example
364 |
365 | ```javascript
366 | // pass key defined in setOptions method of provider
367 | contentful.space('another');
368 | // or you can pass 'default', although it will implicitly use this key if parameter is omitted
369 | contentful.space('default');
370 | ```
371 |
372 | ##### Returns
373 |
374 | Promise.
375 |
376 | ## Promises
377 |
378 | All methods return a promise.
379 |
380 | Depending on the reponse of the API, either the success handler or the error handler
381 | is called with a destructured representation of the response with the following properties:
382 |
383 | - **data** – {object} – The response body transformed with the transform functions.
384 | - **status** – {number} – HTTP status code of the response.
385 | - **headers** – {function([headerName])} – Header getter function.
386 | - **config** – {object} – The configuration object that was used to generate the request.
387 | - **statusText** – {string} – HTTP status text of the response.
388 |
389 | The data property contains the Contentful data. The other properties are passed for convenience in case you need them.
390 |
391 | ```javascript
392 | contentful
393 | .entries()
394 | .then(
395 |
396 | // Success handler
397 | function(response){
398 | var entries = response.data;
399 | console.log(entries);
400 | },
401 |
402 | // Error handler
403 | function(response){
404 | console.log('Oops, error ' + response.status);
405 | }
406 | );
407 | ```
408 |
409 | ## Automatically resolved linked content
410 |
411 | Angular-contentful automatically resolves linked content for you.
412 |
413 | If the Contentful API response includes linked content such as linked entries or linked assets, they are
414 | automatically attached to their parent content for maximum convenience.
415 |
416 | Suppose you have a collection of dogs that have an image linked to them, you can now access the image
417 | as a direct property instead of having to resolve the image manually:
418 |
419 | ```xml
420 |
421 |
422 |
{{ dog.fields.name }}
423 |
424 |
425 |
426 | ```
427 |
428 | Due to how the Contentful API works, linked content is only available when using `contentful-entries`, not when using `contentful-entry`. [Read more details here](https://github.com/jvandemo/angular-contentful/issues/11#issuecomment-140298861).
429 |
430 | #### Notice
431 |
432 | Resolving links hierarchically can cause circular links.
433 |
434 | Although this isn't harmful, it may hamper you from outputting the entire response e.g. using `{{ $contentfulEntries | json }}`.
435 |
436 | ## Connecting to multiple spaces
437 |
438 | If you need to connect to more than one Contentful space, you can specify additional spaces in the `contentfulProvider` configuration:
439 |
440 | ```javascript
441 | angular
442 | .module('yourApp')
443 | .config(function(contentfulProvider){
444 | contentfulProvider.setOptions({
445 | 'default': {
446 | host: 'cdn.contentful.com',
447 | space: 'first_space',
448 | accessToken: 'first_token'
449 | },
450 | 'another': {
451 | host: 'cdn.contentful.com',
452 | space: 'second_space',
453 | accessToken: 'second_token'
454 | }
455 | ...
456 | });
457 | });
458 | ```
459 |
460 | and pass in the space key as the `optionSet` argument when calling a contentful service method:
461 |
462 | ```javascript
463 | angular
464 | .module('yourApp')
465 | .controller('SomeCtrl', function(contentful){
466 |
467 | // Get all entries
468 | contentful
469 | .entries('', 'another')
470 | .then(
471 |
472 | // Success handler
473 | function(response){
474 | var entries = response.data;
475 | console.log(entries);
476 | },
477 |
478 | // Error handler
479 | function(response){
480 | console.log('Oops, error ' + response.status);
481 | }
482 | );
483 |
484 | });
485 | ```
486 |
487 | If you initialize `contentfulProvider` with only one set of options, it will be treated as the default one.
488 |
489 | Currently, the directives do not allow you to specify a space and will always connect to the default space.
490 |
491 | ## Example raw Contentful responses
492 |
493 | These raw response examples give you an idea of what original
494 | Contentful responses look like.
495 |
496 | If you are interested in the details, please visit the [Contentful delivery API documentation](https://www.contentful.com/developers/documentation/content-delivery-api/).
497 |
498 | #### Example Contentful response for successful request
499 |
500 | ```javascript
501 | {
502 | "data": {
503 | "sys": {
504 | "type": "Space",
505 | "id": "cfexampleapi"
506 | },
507 | "name": "Contentful Example API",
508 | "locales": [
509 | {
510 | "code": "en-US",
511 | "default": true,
512 | "name": "English"
513 | },
514 | {
515 | "code": "tlh",
516 | "default": false,
517 | "name": "Klingon"
518 | }
519 | ]
520 | },
521 | "status": 200,
522 | "config": {
523 | "method": "GET",
524 | "transformRequest": [
525 | null
526 | ],
527 | "transformResponse": [
528 | null
529 | ],
530 | "headers": {
531 | "Accept": "application/json, text/plain, */*"
532 | },
533 | "params": {
534 | "access_token": "b4c0n73n7fu1"
535 | },
536 | "url": "https://cdn.contentful.com:443/spaces/cfexampleapi"
537 | },
538 | "statusText": "OK"
539 | }
540 | ```
541 |
542 | #### Example response for error
543 |
544 | ```javascript
545 | {
546 | "data": {
547 | "sys": {
548 | "type": "Error",
549 | "id": "NotFound"
550 | },
551 | "message": "The resource could not be found.",
552 | "details": {
553 | "sys": {
554 | "type": "Space"
555 | }
556 | },
557 | "requestId": "71a-1131131513"
558 | },
559 | "status": 404,
560 | "config": {
561 | "method": "GET",
562 | "transformRequest": [
563 | null
564 | ],
565 | "transformResponse": [
566 | null
567 | ],
568 | "headers": {
569 | "Accept": "application/json, text/plain, */*"
570 | },
571 | "params": {
572 | "access_token": "b4c0n73n7fu1"
573 | },
574 | "url": "https://cdn.contentful.com:443/spaces/cfexampleapiii"
575 | },
576 | "statusText": "Not Found"
577 | }
578 | ```
579 |
580 | ## Why not use the official contentful.js?
581 |
582 | The official Contentful to way is to include 2 libraries in your application:
583 |
584 | ```xml
585 |
586 |
587 | ```
588 |
589 | #### The problem
590 |
591 | `contentful.js` is the main Contentful JavaScript library that relies on different external libraries such as:
592 |
593 | - `questor` to perform HTTP requests
594 | - `bluebird` for promises
595 |
596 | and then bundles everything together in `contentful.js` using Browserify, resulting in a file that packs over `100KB` minified.
597 |
598 | `ng-contenful.js` then forms a wrapper around `contentful.js` that takes care of converting the `bluebird` promises back to AngularJS promises.
599 |
600 | This makes sense if you are in a non-AngularJS environment such as node.js, but AngularJS already has built-in services to perform HTTP requests and provide promises.
601 |
602 | #### The solution
603 |
604 | This AngularJS module uses native AngularJS services to provide a similar API using:
605 |
606 | - `$http` to perform HTTP requests
607 | - `$q` for promises
608 |
609 | which results in:
610 |
611 | - **NOT** having to include `contentful.js` and `ng-contentful.js`, saving you an expensive 100KB+ client side download when your application loads
612 | - less CPU cycles in the client by not having to convert promises
613 |
614 | ## Contribute
615 |
616 | To update the build in the `dist` directory:
617 |
618 | ```bash
619 | $ gulp
620 | ```
621 |
622 | To run the unit tests using the src files:
623 |
624 | ```bash
625 | $ gulp test-src
626 | ```
627 |
628 | To run the unit tests using the unminified library:
629 |
630 | ```bash
631 | $ gulp test-dist-concatenated
632 | ```
633 |
634 | To run the unit tests using the minified library:
635 |
636 | ```bash
637 | $ gulp test-dist-minified
638 | ```
639 |
640 | ## Change log
641 |
642 | ### v2.2.0
643 |
644 | - Added support for multiple spaces [#24](https://github.com/jvandemo/angular-contentful/pull/24) (credits to [Vuk Stanković](https://github.com/vuk))
645 |
646 | ### v2.1.0
647 |
648 | - Added module property to package.json for webpack compatibility
649 |
650 | ### v2.0.0
651 |
652 | - **BREAKING CHANGE**: Added support for specifying expressions in directive attributes
653 |
654 | ### v1.1.0
655 |
656 | - Added query string support to contentful-entry directive
657 |
658 | ### v1.0.0
659 |
660 | - Simplified service API so it always resolves links by default
661 | - Simplified contentful-entries directive API to make data available more intuitively using `$contentfulEntries` instead of `$contentfulEntries.entries`
662 | - Simplified contentful-entry directive API to make data available more intuitively using `$contentfulEntry` instead of `$contentfulEntry.entry`
663 | - Removed support for `success` and `error` shorthand methods in favor of more consistent API with `then`.
664 | - Updated documentation
665 |
666 | ### v0.5.1
667 |
668 | - Update contentful-entries directive so it return response identical to contentful service method
669 |
670 | ### v0.5.0
671 |
672 | - Added support to automatically resolve links when multiple entries are returned
673 | - Updated documentation
674 |
675 | ### v0.4.0
676 |
677 | - Added contentfulEntries directive
678 | - Added additional unit tests
679 | - Updated documentation
680 |
681 | ### v0.3.0
682 |
683 | - Added contentfulEntry directive
684 | - Added additional unit tests
685 | - Updated documentation
686 |
687 | ### v0.2.0
688 |
689 | - Added demo application
690 | - Added shorthand support for `success` and `error` handlers
691 | - Added documentation
692 |
693 | ### v0.1.0
694 |
695 | - Added contentful service
696 | - Added unit tests
697 | - Added initial documentation
698 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular-contentful",
3 | "version": "2.2.0",
4 | "authors": [
5 | {
6 | "name": "Jurgen Van de Moere",
7 | "email": "jurgen.van.de.moere@gmail.com"
8 | }
9 | ],
10 | "keywords": [
11 | "angular",
12 | "angularjs",
13 | "cms",
14 | "contentful",
15 | "contentful delivery api"
16 | ],
17 | "main": ["dist/angular-contentful.js"],
18 | "ignore": [
19 | "src",
20 | "test",
21 | "gulpfile.js",
22 | "karma-dist-concatenated.conf.js",
23 | "karma-dist-minified.conf.js",
24 | "karma-src.conf.js",
25 | "**/.*"
26 | ],
27 | "dependencies": {},
28 | "devDependencies": {
29 | "angular-mocks": ">=1.2.0 <1.5.0",
30 | "angular-scenario": ">=1.2.0 <1.5.0",
31 | "angular": ">=1.2.0 <1.5.0"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/dist/angular-contentful.js:
--------------------------------------------------------------------------------
1 | (function () {
2 |
3 | // Modules
4 | angular.module('contentful', []);
5 |
6 | })();
7 |
8 | (function () {
9 |
10 | /**
11 | * Controller for contentful directives
12 | *
13 | * By separating the controller we can avoid a lot of
14 | * repetitive code and keep the code base as small as
15 | * possible.
16 | *
17 | * @param $attrs
18 | * @param contentful
19 | * @constructor
20 | */
21 | function ContentfulDirectiveCtrl($scope, $attrs, contentful, contentfulHelpers) {
22 |
23 | var query;
24 |
25 | // Passed value is required entry id
26 | if ($attrs.contentfulEntry) {
27 |
28 | query = $scope.$eval($attrs.contentfulEntry);
29 |
30 | // In case we detect a query string instead of simple id, we fetch the
31 | // collection and return the first entry
32 | if(contentfulHelpers.isQueryString(query)){
33 |
34 | // Fetch entry by query
35 | contentful
36 | .entries(query)
37 | .then(
38 | function (response) {
39 | var firstEntry = {};
40 | if(response.data && response.data.items && response.data.items.length){
41 | firstEntry = response.data.items[0];
42 | }
43 | $scope.$contentfulEntry = firstEntry;
44 | },
45 | function(){
46 | $scope.$contentfulEntry = {};
47 | }
48 | );
49 |
50 | } else {
51 |
52 | // Fetch entry by id
53 | contentful
54 | .entry(query)
55 | .then(
56 | function (response) {
57 | $scope.$contentfulEntry = response.data;
58 | },
59 | function(){
60 | $scope.$contentfulEntry = {};
61 | }
62 | );
63 |
64 | }
65 |
66 | }
67 |
68 | // Passed value is optional query
69 | if ($attrs.hasOwnProperty('contentfulEntries')) {
70 |
71 | query = $scope.$eval($attrs.contentfulEntries);
72 |
73 | contentful
74 | .entries(query)
75 | .then(
76 | function (response) {
77 | $scope.$contentfulEntries = response.data;
78 | },
79 | function(){
80 | $scope.$contentfulEntries = {
81 | limit: 0,
82 | skip: 0,
83 | total: 0,
84 | items: []
85 | };
86 | }
87 | );
88 | }
89 |
90 | }
91 |
92 | // Inject controller dependencies
93 | ContentfulDirectiveCtrl.$inject = ['$scope', '$attrs', 'contentful', 'contentfulHelpers'];
94 |
95 | // Export
96 | angular
97 | .module('contentful')
98 | .controller('ContentfulDirectiveCtrl', ContentfulDirectiveCtrl);
99 |
100 | })();
101 |
102 | (function () {
103 |
104 | /**
105 | * Directive
106 | *
107 | * @returns {object} directive definition object
108 | */
109 | function contentfulEntriesDirective() {
110 |
111 | return {
112 | restrict: 'EA',
113 | scope: true,
114 | controller: 'ContentfulDirectiveCtrl'
115 | };
116 |
117 | }
118 |
119 | // Inject directive dependencies
120 | contentfulEntriesDirective.$inject = [];
121 |
122 | // Export
123 | angular
124 | .module('contentful')
125 | .directive('contentfulEntries', contentfulEntriesDirective);
126 |
127 | })();
128 |
129 | (function () {
130 |
131 | /**
132 | * Directive
133 | *
134 | * @returns {object} directive definition object
135 | */
136 | function contentfulEntryDirective() {
137 |
138 | return {
139 | restrict: 'EA',
140 | scope: true,
141 | controller: 'ContentfulDirectiveCtrl'
142 | };
143 |
144 | }
145 |
146 | // Inject directive dependencies
147 | contentfulEntryDirective.$inject = [];
148 |
149 | // Export
150 | angular
151 | .module('contentful')
152 | .directive('contentfulEntry', contentfulEntryDirective);
153 |
154 | })();
155 |
156 | (function () {
157 |
158 | function contentfulHelpersFactory() {
159 |
160 | function ContentfulHelpers() {
161 | }
162 |
163 | /**
164 | * Resolve a complete response
165 | *
166 | * @param response
167 | * @returns {Array}
168 | */
169 | ContentfulHelpers.prototype.resolveResponse = function resolveResponse(response) {
170 | var self = this;
171 | self.walkMutate(response, self.isLink, function (link) {
172 | return self.getLink(response, link) || link;
173 | });
174 | return response.items || [];
175 | };
176 |
177 | /**
178 | * Check if object is a link
179 | *
180 | * @param {object}
181 | * @returns {boolean}
182 | */
183 | ContentfulHelpers.prototype.isLink = function isLink(object) {
184 | if (object && object.sys && object.sys.type && object.sys.type === 'Link') {
185 | return true;
186 | }
187 | return false;
188 | };
189 |
190 | /**
191 | * Find and return a link in a response
192 | *
193 | * @param response
194 | * @param link
195 | * @returns {object|null} Link
196 | */
197 | ContentfulHelpers.prototype.getLink = function getLink(response, link) {
198 | var self = this;
199 | var type = link.sys.linkType;
200 | var id = link.sys.id;
201 | var pred = function (resource) {
202 | return resource && resource.sys && resource.sys.type === type && resource.sys.id === id;
203 | };
204 | return self.findLink(response.items, pred) ||
205 | response.includes && self.findLink(response.includes[type], pred);
206 | };
207 |
208 | /**
209 | * Helper method to find a link in an array
210 | *
211 | * @param {Array} arr - Array to search
212 | * @param {function} pred - Predicate function
213 | * @returns {object|null} Link
214 | */
215 | ContentfulHelpers.prototype.findLink = function findLink(arr, pred) {
216 | var i;
217 | var link = null;
218 | if (!angular.isArray(arr)) {
219 | return link;
220 | }
221 | for (i = 0; i < arr.length; i++) {
222 | if (pred(arr[i])) {
223 | link = arr[i];
224 | break;
225 | }
226 | }
227 | return link;
228 | };
229 |
230 | /**
231 | * Walk a data structure and mutate properties that match the predicate function
232 | *
233 | * @param {object|array} input - Input data
234 | * @param {function} pred - Prediction function
235 | * @param {function} mutator - Mutator function
236 | * @returns {*}
237 | */
238 | ContentfulHelpers.prototype.walkMutate = function walkMutate(input, pred, mutator) {
239 | var self = this;
240 | if (pred(input)){
241 | return mutator(input);
242 | }
243 |
244 | if (angular.isArray(input) || angular.isObject(input)) {
245 | angular.forEach(input, function (item, key) {
246 | input[key] = self.walkMutate(item, pred, mutator);
247 | });
248 | return input;
249 | }
250 | return input;
251 | };
252 |
253 |
254 | /**
255 | * Check if a string is a query string
256 | *
257 | * @param {string} input
258 | * @returns {boolean}
259 | */
260 | ContentfulHelpers.prototype.isQueryString = function isQueryString(input) {
261 | if(input.toString().indexOf('=') > -1){
262 | return true;
263 | }
264 | if(input.toString().indexOf('&') > -1){
265 | return true;
266 | }
267 | if(input.toString().indexOf('?') > -1){
268 | return true;
269 | }
270 | return false;
271 | };
272 |
273 |
274 |
275 | return new ContentfulHelpers();
276 |
277 | }
278 |
279 | // Export
280 | angular
281 | .module('contentful')
282 | .factory('contentfulHelpers', contentfulHelpersFactory);
283 |
284 | })();
285 |
286 | (function () {
287 |
288 | /**
289 | * Contentful service provider
290 | */
291 | function contentfulProvider() {
292 |
293 | // Default options
294 | var options = {
295 | 'default': {
296 | host: 'cdn.contentful.com',
297 | space: null,
298 | accessToken: null,
299 | secure: true
300 | }
301 | };
302 |
303 | /**
304 | * Set options
305 | *
306 | * @param {object} newOptions
307 | * @returns {contentfulProvider}
308 | */
309 | this.setOptions = function (newOptions) {
310 | if (newOptions && newOptions.space) {
311 | angular.extend(options['default'], newOptions);
312 | } else {
313 | angular.extend(options, newOptions);
314 | }
315 | return this;
316 | };
317 |
318 | this.$get = contentfulFactory;
319 |
320 | /**
321 | * Create the contentful service
322 | *
323 | * @returns {contentfulProvider.Contentful}
324 | */
325 | function contentfulFactory($http, $q, contentfulHelpers) {
326 | return new Contentful($http, $q, contentfulHelpers, options);
327 | }
328 |
329 | // Inject dependencies in factory
330 | contentfulFactory.$inject = ['$http', '$q', 'contentfulHelpers'];
331 |
332 | /**
333 | * Contentful service constructor
334 | *
335 | * @constructor
336 | */
337 | function Contentful($http, $q, contentfulHelpers, options) {
338 |
339 | this._$http = $http;
340 | this._$q = $q;
341 | this._contentfulHelpers = contentfulHelpers;
342 | this.options = options;
343 |
344 | if (typeof $http.get !== 'function') {
345 | throw new Error('The contentful service needs a valid http service to work with');
346 | }
347 |
348 | if (typeof $q.when !== 'function') {
349 | throw new Error('The contentful service needs a valid promise service to work with');
350 | }
351 | }
352 |
353 | /**
354 | * Perform request
355 | *
356 | * @param {string} path
357 | * @param {object} config
358 | * @param {object} optionSet
359 | * @returns {promise}
360 | */
361 | Contentful.prototype.request = function (path, config, optionSet) {
362 | optionSet = optionSet || 'default';
363 | var url;
364 | var nonEmptyParams = {};
365 |
366 | // Make sure config is valid
367 | config = config || {};
368 | config.headers = config.headers || {};
369 | config.params = config.params || {};
370 |
371 | // Add required configuration
372 | config.headers['Content-Type'] = 'application/vnd.contentful.delivery.v1+json';
373 | config.params['access_token'] = this.options[optionSet].accessToken;
374 |
375 | // Build url
376 | url = [
377 | this.options[optionSet].secure ? 'https' : 'http',
378 | '://',
379 | this.options[optionSet].host,
380 | ':',
381 | this.options[optionSet].secure ? '443' : '80',
382 | '/spaces/',
383 | this.options[optionSet].space,
384 | path
385 | ].join('');
386 |
387 | // Perform request and return promise
388 | return this._$http.get(url, config);
389 | };
390 |
391 | /**
392 | * Get an asset
393 | *
394 | * @param id
395 | * @param optionSet {object}
396 | * @returns {promise}
397 | */
398 | Contentful.prototype.asset = function (id, optionSet) {
399 | optionSet = optionSet || 'default';
400 | return this.request('/assets/' + id, {}, optionSet);
401 | };
402 |
403 | /**
404 | * Get assets
405 | *
406 | * @param query
407 | * @param optionSet {object}
408 | * @returns {promise}
409 | */
410 | Contentful.prototype.assets = function (querystring, optionSet) {
411 | optionSet = optionSet || 'default';
412 | return this.processResponseWithMultipleEntries(
413 | this.request('/assets', configifyParams(paramifyQuerystring(querystring)), optionSet)
414 | );
415 | };
416 |
417 | /**
418 | * Get content type
419 | *
420 | * @param id
421 | * @param optionSet {object}
422 | * @returns {promise}
423 | */
424 | Contentful.prototype.contentType = function (id, optionSet) {
425 | optionSet = optionSet || 'default';
426 | return this.request('/content_types/' + id, {}, optionSet);
427 | };
428 |
429 | /**
430 | * Get content types
431 | *
432 | * @param query
433 | * @param optionSet {object}
434 | * @returns {promise}
435 | */
436 | Contentful.prototype.contentTypes = function (querystring, optionSet) {
437 | optionSet = optionSet || 'default';
438 | return this.processResponseWithMultipleEntries(
439 | this.request('/content_types', configifyParams(paramifyQuerystring(querystring)), optionSet)
440 | );
441 | };
442 |
443 | /**
444 | * Get entry
445 | *
446 | * @param id
447 | * @param optionSet {object}
448 | * @returns {promise}
449 | */
450 | Contentful.prototype.entry = function (id, optionSet) {
451 | optionSet = optionSet || 'default';
452 | return this.request('/entries/' + id, {}, optionSet);
453 | };
454 |
455 | /**
456 | * Get entries
457 | *
458 | * @param query
459 | * @param optionSet {object}
460 | * @returns {promise}
461 | */
462 | Contentful.prototype.entries = function (querystring, optionSet) {
463 | optionSet = optionSet || 'default';
464 | return this.processResponseWithMultipleEntries(
465 | this.request('/entries', configifyParams(paramifyQuerystring(querystring)), optionSet)
466 | );
467 | };
468 |
469 | /**
470 | * Get space
471 | *
472 | * @param optionSet {object}
473 | * @returns {promise}
474 | */
475 | Contentful.prototype.space = function (optionSet) {
476 | optionSet = optionSet || 'default';
477 | return this.request('', {}, optionSet);
478 | };
479 |
480 | /**
481 | * Process multiple incoming entries
482 | *
483 | * Resolves links in the response.data.
484 | *
485 | * @param promise
486 | * @returns {*}
487 | */
488 | Contentful.prototype.processResponseWithMultipleEntries = function(promise){
489 | var self = this;
490 | if(promise && promise.then){
491 | promise.then(
492 |
493 | // Automatically resolve links on success
494 | function(response){
495 | var entries = {
496 | limit: response.data.limit,
497 | skip: response.data.skip,
498 | total: response.data.total
499 | };
500 | entries.items = self._contentfulHelpers.resolveResponse(response.data);
501 | response.data = entries;
502 | return response;
503 | },
504 |
505 | // Forward error on failure
506 | function(response){
507 | return response;
508 | }
509 | );
510 | }
511 | return promise;
512 | };
513 |
514 | /**
515 | * Process single incoming entry
516 | *
517 | * For now, this is just a noop but it exists so it makes
518 | * sure a $q promise is returned with only then method
519 | * (removing shorthand success and error methods)
520 | * and to have single point of entry in case transformation
521 | * is needed in the future.
522 | *
523 | * @param promise
524 | * @returns {*}
525 | */
526 | Contentful.prototype.processResponseWithSingleEntry = function(promise){
527 | if(promise && promise.then){
528 | promise.then(
529 |
530 | // Forward error on failure
531 | function(response){
532 | return response;
533 | },
534 |
535 | // Forward error on failure
536 | function(response){
537 | return response;
538 | }
539 | );
540 | }
541 | return promise;
542 | };
543 |
544 | }
545 |
546 | /**
547 | * Create params object from querystring
548 | *
549 | * @param querystring
550 | * @returns {object} params
551 | */
552 | function paramifyQuerystring(querystring){
553 | var params = {};
554 |
555 | if(!querystring){
556 | return params;
557 | }
558 |
559 | // Split querystring in parts separated by '&'
560 | var couples = querystring.toString().split('&');
561 | angular.forEach(couples, function(couple){
562 |
563 | // Split in parts separated by '='
564 | var parts = couple.split('=');
565 |
566 | // Only add if an actual value is passed
567 | // to prevent empty params in the url
568 | if(parts.length > 1){
569 | params[parts[0]] = parts[1];
570 | }
571 | });
572 | return params;
573 | }
574 |
575 | /**
576 | * Create config object from params
577 | *
578 | * @param params
579 | * @returns {object} config
580 | */
581 | function configifyParams(params){
582 | if(!angular.isObject(params)){
583 | params = {};
584 | }
585 | return {
586 | params: params
587 | };
588 | }
589 |
590 | // Export
591 | angular
592 | .module('contentful')
593 | .provider('contentful', contentfulProvider);
594 |
595 | })();
596 |
--------------------------------------------------------------------------------
/dist/angular-contentful.min.js:
--------------------------------------------------------------------------------
1 | !function(){angular.module("contentful",[])}(),function(){function t(t,n,e,r){var i;n.contentfulEntry&&(i=t.$eval(n.contentfulEntry),r.isQueryString(i)?e.entries(i).then(function(n){var e={};n.data&&n.data.items&&n.data.items.length&&(e=n.data.items[0]),t.$contentfulEntry=e},function(){t.$contentfulEntry={}}):e.entry(i).then(function(n){t.$contentfulEntry=n.data},function(){t.$contentfulEntry={}})),n.hasOwnProperty("contentfulEntries")&&(i=t.$eval(n.contentfulEntries),e.entries(i).then(function(n){t.$contentfulEntries=n.data},function(){t.$contentfulEntries={limit:0,skip:0,total:0,items:[]}}))}t.$inject=["$scope","$attrs","contentful","contentfulHelpers"],angular.module("contentful").controller("ContentfulDirectiveCtrl",t)}(),function(){function t(){return{restrict:"EA",scope:!0,controller:"ContentfulDirectiveCtrl"}}t.$inject=[],angular.module("contentful").directive("contentfulEntries",t)}(),function(){function t(){return{restrict:"EA",scope:!0,controller:"ContentfulDirectiveCtrl"}}t.$inject=[],angular.module("contentful").directive("contentfulEntry",t)}(),function(){function t(){function t(){}return t.prototype.resolveResponse=function(t){var n=this;return n.walkMutate(t,n.isLink,function(e){return n.getLink(t,e)||e}),t.items||[]},t.prototype.isLink=function(t){return t&&t.sys&&t.sys.type&&"Link"===t.sys.type?!0:!1},t.prototype.getLink=function(t,n){var e=this,r=n.sys.linkType,i=n.sys.id,o=function(t){return t&&t.sys&&t.sys.type===r&&t.sys.id===i};return e.findLink(t.items,o)||t.includes&&e.findLink(t.includes[r],o)},t.prototype.findLink=function(t,n){var e,r=null;if(!angular.isArray(t))return r;for(e=0;e-1?!0:t.toString().indexOf("&")>-1?!0:t.toString().indexOf("?")>-1?!0:!1},new t}angular.module("contentful").factory("contentfulHelpers",t)}(),function(){function t(){function t(t,n,e){return new r(t,n,e,i)}function r(t,n,e,r){if(this._$http=t,this._$q=n,this._contentfulHelpers=e,this.options=r,"function"!=typeof t.get)throw new Error("The contentful service needs a valid http service to work with");if("function"!=typeof n.when)throw new Error("The contentful service needs a valid promise service to work with")}var i={"default":{host:"cdn.contentful.com",space:null,accessToken:null,secure:!0}};this.setOptions=function(t){return t&&t.space?angular.extend(i["default"],t):angular.extend(i,t),this},this.$get=t,t.$inject=["$http","$q","contentfulHelpers"],r.prototype.request=function(t,n,e){e=e||"default";var r;return n=n||{},n.headers=n.headers||{},n.params=n.params||{},n.headers["Content-Type"]="application/vnd.contentful.delivery.v1+json",n.params.access_token=this.options[e].accessToken,r=[this.options[e].secure?"https":"http","://",this.options[e].host,":",this.options[e].secure?"443":"80","/spaces/",this.options[e].space,t].join(""),this._$http.get(r,n)},r.prototype.asset=function(t,n){return n=n||"default",this.request("/assets/"+t,{},n)},r.prototype.assets=function(t,r){return r=r||"default",this.processResponseWithMultipleEntries(this.request("/assets",e(n(t)),r))},r.prototype.contentType=function(t,n){return n=n||"default",this.request("/content_types/"+t,{},n)},r.prototype.contentTypes=function(t,r){return r=r||"default",this.processResponseWithMultipleEntries(this.request("/content_types",e(n(t)),r))},r.prototype.entry=function(t,n){return n=n||"default",this.request("/entries/"+t,{},n)},r.prototype.entries=function(t,r){return r=r||"default",this.processResponseWithMultipleEntries(this.request("/entries",e(n(t)),r))},r.prototype.space=function(t){return t=t||"default",this.request("",{},t)},r.prototype.processResponseWithMultipleEntries=function(t){var n=this;return t&&t.then&&t.then(function(t){var e={limit:t.data.limit,skip:t.data.skip,total:t.data.total};return e.items=n._contentfulHelpers.resolveResponse(t.data),t.data=e,t},function(t){return t}),t},r.prototype.processResponseWithSingleEntry=function(t){return t&&t.then&&t.then(function(t){return t},function(t){return t}),t}}function n(t){var n={};if(!t)return n;var e=t.toString().split("&");return angular.forEach(e,function(t){var e=t.split("=");e.length>1&&(n[e[0]]=e[1])}),n}function e(t){return angular.isObject(t)||(t={}),{params:t}}angular.module("contentful").provider("contentful",t)}();
--------------------------------------------------------------------------------
/examples/how-to-use-the-contentful-service/app.js:
--------------------------------------------------------------------------------
1 | (function () {
2 |
3 | // Modules
4 | angular.module('app', ['contentful']);
5 |
6 | angular
7 | .module('app')
8 | .config(function (contentfulProvider) {
9 | contentfulProvider.setOptions({
10 | space: 'cfexampleapi',
11 | accessToken: 'b4c0n73n7fu1'
12 | });
13 | });
14 |
15 | angular
16 | .module('app')
17 | .controller('DemoCtrl', function ($scope, contentful) {
18 |
19 | var promise;
20 |
21 | $scope.busy = false;
22 | $scope.response = null;
23 |
24 | $scope.$watch('action', function (action, old) {
25 |
26 | // Still performing previous request
27 | if ($scope.busy) {
28 | return;
29 | }
30 |
31 | if (action === old) {
32 | return;
33 | }
34 |
35 | promise = null;
36 | $scope.busy = true;
37 |
38 | if (action === 'space') {
39 | promise = contentful.space();
40 | }
41 |
42 | if (action === 'contentTypes') {
43 | promise = contentful.contentTypes();
44 | }
45 |
46 | if (action === 'entry') {
47 | promise = contentful.entry('6KntaYXaHSyIw8M6eo26OK');
48 | }
49 |
50 | if (action === 'entries') {
51 | promise = contentful.entries();
52 | }
53 |
54 | if (!promise) {
55 | $scope.response = null;
56 | $scope.busy = false;
57 | return;
58 | }
59 |
60 | promise.then(
61 | function (response) {
62 | $scope.response = response;
63 | $scope.busy = false;
64 | },
65 | function (response) {
66 | $scope.response = response;
67 | $scope.busy = false;
68 | }
69 | )
70 |
71 | });
72 |
73 | });
74 |
75 | })();
76 |
--------------------------------------------------------------------------------
/examples/how-to-use-the-contentful-service/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Simple demo application
4 |
5 |
6 |
7 |
8 |
9 |
Simple demo app
10 |
This simple demo uses the following Contentful configuration: