Built motion from commit 5b01f56.|0.0.106
[motion.git] / public / bower_components / angular-bootstrap-lightbox / angular-bootstrap-lightbox.js
1 /**
2  * @namespace bootstrapLightbox
3  */
4 angular.module('bootstrapLightbox', [
5   'ui.bootstrap'
6 ]);
7
8 // optional dependencies
9 try {
10   angular.module('angular-loading-bar');
11   angular.module('bootstrapLightbox').requires.push('angular-loading-bar');
12 } catch (e) {}
13
14 try {
15   angular.module('ngTouch');
16   angular.module('bootstrapLightbox').requires.push('ngTouch');
17 } catch (e) {}
18
19 try {
20   angular.module('videosharing-embed');
21   angular.module('bootstrapLightbox').requires.push('videosharing-embed');
22 } catch (e) {}
23 angular.module('bootstrapLightbox').run(['$templateCache', function($templateCache) {
24   'use strict';
25
26   $templateCache.put('lightbox.html',
27     "<div class=modal-body ng-swipe-left=Lightbox.nextImage() ng-swipe-right=Lightbox.prevImage()><div class=lightbox-nav><button class=close aria-hidden=true ng-click=$dismiss()>×</button><div class=btn-group ng-if=\"Lightbox.images.length > 1\"><a class=\"btn btn-xs btn-default\" ng-click=Lightbox.prevImage()>‹ Previous</a> <a ng-href={{Lightbox.imageUrl}} target=_blank class=\"btn btn-xs btn-default\" title=\"Open in new tab\">Open image in new tab</a> <a class=\"btn btn-xs btn-default\" ng-click=Lightbox.nextImage()>Next ›</a></div></div><div class=lightbox-image-container><div class=lightbox-image-caption><span>{{Lightbox.imageCaption}}</span></div><img ng-if=!Lightbox.isVideo(Lightbox.image) lightbox-src={{Lightbox.imageUrl}}><div ng-if=Lightbox.isVideo(Lightbox.image) class=\"embed-responsive embed-responsive-16by9\"><video ng-if=!Lightbox.isSharedVideo(Lightbox.image) lightbox-src={{Lightbox.imageUrl}} controls autoplay></video><embed-video ng-if=Lightbox.isSharedVideo(Lightbox.image) lightbox-src={{Lightbox.imageUrl}} ng-href={{Lightbox.imageUrl}} iframe-id=lightbox-video class=embed-responsive-item><a ng-href={{Lightbox.imageUrl}}>Watch video</a></embed-video></div></div></div>"
28   );
29
30 }]);
31 /**
32  * @class     ImageLoader
33  * @classdesc Service for loading an image.
34  * @memberOf  bootstrapLightbox
35  */
36 angular.module('bootstrapLightbox').service('ImageLoader', ['$q',
37     function ($q) {
38   /**
39    * Load the image at the given URL.
40    * @param    {String} url
41    * @return   {Promise} A $q promise that resolves when the image has loaded
42    *   successfully.
43    * @type     {Function}
44    * @name     load
45    * @memberOf bootstrapLightbox.ImageLoader
46    */
47   this.load = function (url) {
48     var deferred = $q.defer();
49
50     var image = new Image();
51
52     // when the image has loaded
53     image.onload = function () {
54       // check image properties for possible errors
55       if ((typeof this.complete === 'boolean' && this.complete === false) ||
56           (typeof this.naturalWidth === 'number' && this.naturalWidth === 0)) {
57         deferred.reject();
58       }
59
60       deferred.resolve(image);
61     };
62
63     // when the image fails to load
64     image.onerror = function () {
65       deferred.reject();
66     };
67
68     // start loading the image
69     image.src = url;
70
71     return deferred.promise;
72   };
73 }]);
74 /**
75  * @class     Lightbox
76  * @classdesc Lightbox service.
77  * @memberOf  bootstrapLightbox
78  */
79 angular.module('bootstrapLightbox').provider('Lightbox', function () {
80   /**
81    * Template URL passed into `$uibModal.open()`.
82    * @type     {String}
83    * @name     templateUrl
84    * @memberOf bootstrapLightbox.Lightbox
85    */
86   this.templateUrl = 'lightbox.html';
87
88   /**
89    * Whether images should be scaled to the maximum possible dimensions.
90    * @type     {Boolean}
91    * @name     fullScreenMode
92    * @memberOf bootstrapLightbox.Lightbox
93    */
94   this.fullScreenMode = false;
95
96   /**
97    * @param    {*} image An element in the array of images.
98    * @return   {String} The URL of the given image.
99    * @type     {Function}
100    * @name     getImageUrl
101    * @memberOf bootstrapLightbox.Lightbox
102    */
103   this.getImageUrl = function (image) {
104     return typeof image === 'string' ? image : image.url;
105   };
106
107   /**
108    * @param    {*} image An element in the array of images.
109    * @return   {String} The caption of the given image.
110    * @type     {Function}
111    * @name     getImageCaption
112    * @memberOf bootstrapLightbox.Lightbox
113    */
114   this.getImageCaption = function (image) {
115     return image.caption;
116   };
117
118   /**
119    * Calculate the max and min limits to the width and height of the displayed
120    *   image (all are optional). The max dimensions override the min
121    *   dimensions if they conflict.
122    * @param    {Object} dimensions Contains the properties `windowWidth`,
123    *   `windowHeight`, `imageWidth`, and `imageHeight`.
124    * @return   {Object} May optionally contain the properties `minWidth`,
125    *   `minHeight`, `maxWidth`, and `maxHeight`.
126    * @type     {Function}
127    * @name     calculateImageDimensionLimits
128    * @memberOf bootstrapLightbox.Lightbox
129    */
130   this.calculateImageDimensionLimits = function (dimensions) {
131     if (dimensions.windowWidth >= 768) {
132       return {
133         // 92px = 2 * (30px margin of .modal-dialog
134         //             + 1px border of .modal-content
135         //             + 15px padding of .modal-body)
136         // with the goal of 30px side margins; however, the actual side margins
137         // will be slightly less (at 22.5px) due to the vertical scrollbar
138         'maxWidth': dimensions.windowWidth - 92,
139         // 126px = 92px as above
140         //         + 34px outer height of .lightbox-nav
141         'maxHeight': dimensions.windowHeight - 126
142       };
143     } else {
144       return {
145         // 52px = 2 * (10px margin of .modal-dialog
146         //             + 1px border of .modal-content
147         //             + 15px padding of .modal-body)
148         'maxWidth': dimensions.windowWidth - 52,
149         // 86px = 52px as above
150         //        + 34px outer height of .lightbox-nav
151         'maxHeight': dimensions.windowHeight - 86
152       };
153     }
154   };
155
156   /**
157    * Calculate the width and height of the modal. This method gets called
158    *   after the width and height of the image, as displayed inside the modal,
159    *   are calculated.
160    * @param    {Object} dimensions Contains the properties `windowWidth`,
161    *   `windowHeight`, `imageDisplayWidth`, and `imageDisplayHeight`.
162    * @return   {Object} Must contain the properties `width` and `height`.
163    * @type     {Function}
164    * @name     calculateModalDimensions
165    * @memberOf bootstrapLightbox.Lightbox
166    */
167   this.calculateModalDimensions = function (dimensions) {
168     // 400px = arbitrary min width
169     // 32px = 2 * (1px border of .modal-content
170     //             + 15px padding of .modal-body)
171     var width = Math.max(400, dimensions.imageDisplayWidth + 32);
172
173     // 200px = arbitrary min height
174     // 66px = 32px as above
175     //        + 34px outer height of .lightbox-nav
176     var height = Math.max(200, dimensions.imageDisplayHeight + 66);
177
178     // first case:  the modal width cannot be larger than the window width
179     //              20px = arbitrary value larger than the vertical scrollbar
180     //                     width in order to avoid having a horizontal scrollbar
181     // second case: Bootstrap modals are not centered below 768px
182     if (width >= dimensions.windowWidth - 20 || dimensions.windowWidth < 768) {
183       width = 'auto';
184     }
185
186     // the modal height cannot be larger than the window height
187     if (height >= dimensions.windowHeight) {
188       height = 'auto';
189     }
190
191     return {
192       'width': width,
193       'height': height
194     };
195   };
196
197   /**
198    * @param    {*} image An element in the array of images.
199    * @return   {Boolean} Whether the provided element is a video.
200    * @type     {Function}
201    * @name     isVideo
202    * @memberOf bootstrapLightbox.Lightbox
203    */
204   this.isVideo = function (image) {
205     if (typeof image === 'object' && image && image.type) {
206       return image.type === 'video';
207     }
208
209     return false;
210   };
211
212   /**
213    * @param    {*} image An element in the array of images.
214    * @return   {Boolean} Whether the provided element is a video that is to be
215    *   embedded with an external service like YouTube. By default, this is
216    *   determined by the url not ending in `.mp4`, `.ogg`, or `.webm`.
217    * @type     {Function}
218    * @name     isSharedVideo
219    * @memberOf bootstrapLightbox.Lightbox
220    */
221   this.isSharedVideo = function (image) {
222     return this.isVideo(image) &&
223       !this.getImageUrl(image).match(/\.(mp4|ogg|webm)$/);
224   };
225
226   this.$get = ['$document', '$injector', '$uibModal', '$timeout', 'ImageLoader',
227       function ($document, $injector, $uibModal, $timeout, ImageLoader) {
228     // optional dependency
229     var cfpLoadingBar = $injector.has('cfpLoadingBar') ?
230       $injector.get('cfpLoadingBar'): null;
231
232     var Lightbox = {};
233
234     /**
235      * Array of all images to be shown in the lightbox (not `Image` objects).
236      * @type     {Array}
237      * @name     images
238      * @memberOf bootstrapLightbox.Lightbox
239      */
240     Lightbox.images = [];
241
242     /**
243      * The index in the `Lightbox.images` aray of the image that is currently
244      *   shown in the lightbox.
245      * @type     {Number}
246      * @name     index
247      * @memberOf bootstrapLightbox.Lightbox
248      */
249     Lightbox.index = -1;
250
251     // set the configurable properties and methods, the defaults of which are
252     // defined above
253     Lightbox.templateUrl = this.templateUrl;
254     Lightbox.fullScreenMode = this.fullScreenMode;
255     Lightbox.getImageUrl = this.getImageUrl;
256     Lightbox.getImageCaption = this.getImageCaption;
257     Lightbox.calculateImageDimensionLimits = this.calculateImageDimensionLimits;
258     Lightbox.calculateModalDimensions = this.calculateModalDimensions;
259     Lightbox.isVideo = this.isVideo;
260     Lightbox.isSharedVideo = this.isSharedVideo;
261
262     /**
263      * Whether keyboard navigation is currently enabled for navigating through
264      *   images in the lightbox.
265      * @type     {Boolean}
266      * @name     keyboardNavEnabled
267      * @memberOf bootstrapLightbox.Lightbox
268      */
269     Lightbox.keyboardNavEnabled = false;
270
271     /**
272      * The image currently shown in the lightbox.
273      * @type     {*}
274      * @name     image
275      * @memberOf bootstrapLightbox.Lightbox
276      */
277     Lightbox.image = {};
278
279     /**
280      * The UI Bootstrap modal instance. See {@link
281      *   http://angular-ui.github.io/bootstrap/#/modal}.
282      * @type     {Object}
283      * @name     modalInstance
284      * @memberOf bootstrapLightbox.Lightbox
285      */
286     Lightbox.modalInstance = null;
287
288     /**
289      * The URL of the current image. This is a property of the service rather
290      *   than of `Lightbox.image` because `Lightbox.image` need not be an
291      *   object, and besides it would be poor practice to alter the given
292      *   objects.
293      * @type     {String}
294      * @name     imageUrl
295      * @memberOf bootstrapLightbox.Lightbox
296      */
297
298     /**
299      * The optional caption of the current image.
300      * @type     {String}
301      * @name     imageCaption
302      * @memberOf bootstrapLightbox.Lightbox
303      */
304
305     /**
306      * Whether an image is currently being loaded.
307      * @type     {Boolean}
308      * @name     loading
309      * @memberOf bootstrapLightbox.Lightbox
310      */
311     Lightbox.loading = false;
312
313     /**
314      * Open the lightbox modal.
315      * @param    {Array}  newImages An array of images. Each image may be of
316      *   any type.
317      * @param    {Number} newIndex  The index in `newImages` to set as the
318      *   current image.
319      * @param    {Object} modalParams  Custom params for the angular UI
320      *   bootstrap modal (in $uibModal.open()).
321      * @return   {Object} The created UI Bootstrap modal instance.
322      * @type     {Function}
323      * @name     openModal
324      * @memberOf bootstrapLightbox.Lightbox
325      */
326     Lightbox.openModal = function (newImages, newIndex, modalParams) {
327       Lightbox.images = newImages;
328       Lightbox.setImage(newIndex);
329
330       // store the modal instance so we can close it manually if we need to
331       Lightbox.modalInstance = $uibModal.open(angular.extend({
332         'templateUrl': Lightbox.templateUrl,
333         'controller': ['$scope', function ($scope) {
334           // $scope is the modal scope, a child of $rootScope
335           $scope.Lightbox = Lightbox;
336
337           Lightbox.keyboardNavEnabled = true;
338         }],
339         'windowClass': 'lightbox-modal'
340       }, modalParams || {}));
341
342       // modal close handler
343       Lightbox.modalInstance.result['finally'](function () {
344         // prevent the lightbox from flickering from the old image when it gets
345         // opened again
346         Lightbox.images = [];
347         Lightbox.index = 1;
348         Lightbox.image = {};
349         Lightbox.imageUrl = null;
350         Lightbox.imageCaption = null;
351
352         Lightbox.keyboardNavEnabled = false;
353
354         // complete any lingering loading bar progress
355         if (cfpLoadingBar) {
356           cfpLoadingBar.complete();
357         }
358       });
359
360       return Lightbox.modalInstance;
361     };
362
363     /**
364      * Close the lightbox modal.
365      * @param    {*} result This argument can be useful if the modal promise
366      *   gets handler(s) attached to it.
367      * @type     {Function}
368      * @name     closeModal
369      * @memberOf bootstrapLightbox.Lightbox
370      */
371     Lightbox.closeModal = function (result) {
372       return Lightbox.modalInstance.close(result);
373     };
374
375     /**
376      * This method can be used in all methods which navigate/change the
377      *   current image.
378      * @param    {Number} newIndex The index in the array of images to set as
379      *   the new current image.
380      * @type     {Function}
381      * @name     setImage
382      * @memberOf bootstrapLightbox.Lightbox
383      */
384     Lightbox.setImage = function (newIndex) {
385       if (!(newIndex in Lightbox.images)) {
386         throw 'Invalid image.';
387       }
388
389       // update the loading flag and start the loading bar
390       Lightbox.loading = true;
391       if (cfpLoadingBar) {
392         cfpLoadingBar.start();
393       }
394
395       var image = Lightbox.images[newIndex];
396       var imageUrl = Lightbox.getImageUrl(image);
397
398       var success = function (properties) {
399         // update service properties for the image
400         properties = properties || {};
401         Lightbox.index = properties.index || newIndex;
402         Lightbox.image = properties.image || image;
403         Lightbox.imageUrl = properties.imageUrl || imageUrl;
404         Lightbox.imageCaption = properties.imageCaption ||
405           Lightbox.getImageCaption(image);
406
407         // restore the loading flag and complete the loading bar
408         Lightbox.loading = false;
409         if (cfpLoadingBar) {
410           cfpLoadingBar.complete();
411         }
412       };
413
414       if (!Lightbox.isVideo(image)) {
415         // load the image before setting it, so everything in the view is
416         // updated at the same time; otherwise, the previous image remains while
417         // the current image is loading
418         ImageLoader.load(imageUrl).then(function () {
419           success();
420         }, function () {
421           success({
422             'imageUrl': '#', // blank image
423             // use the caption to show the user an error
424             'imageCaption': 'Failed to load image'
425           });
426         });
427       } else {
428         success();
429       }
430     };
431
432     /**
433      * Navigate to the first image.
434      * @type     {Function}
435      * @name     firstImage
436      * @memberOf bootstrapLightbox.Lightbox
437      */
438     Lightbox.firstImage = function () {
439       Lightbox.setImage(0);
440     };
441
442     /**
443      * Navigate to the previous image.
444      * @type     {Function}
445      * @name     prevImage
446      * @memberOf bootstrapLightbox.Lightbox
447      */
448     Lightbox.prevImage = function () {
449       Lightbox.setImage((Lightbox.index - 1 + Lightbox.images.length) %
450         Lightbox.images.length);
451     };
452
453     /**
454      * Navigate to the next image.
455      * @type     {Function}
456      * @name     nextImage
457      * @memberOf bootstrapLightbox.Lightbox
458      */
459     Lightbox.nextImage = function () {
460       Lightbox.setImage((Lightbox.index + 1) % Lightbox.images.length);
461     };
462
463     /**
464      * Navigate to the last image.
465      * @type     {Function}
466      * @name     lastImage
467      * @memberOf bootstrapLightbox.Lightbox
468      */
469     Lightbox.lastImage = function () {
470       Lightbox.setImage(Lightbox.images.length - 1);
471     };
472
473     /**
474      * Call this method to set both the array of images and the current image
475      *   (based on the current index). A use case is when the image collection
476      *   gets changed dynamically in some way while the lightbox is still
477      *   open.
478      * @param {Array} newImages The new array of images.
479      * @type     {Function}
480      * @name     setImages
481      * @memberOf bootstrapLightbox.Lightbox
482      */
483     Lightbox.setImages = function (newImages) {
484       Lightbox.images = newImages;
485       Lightbox.setImage(Lightbox.index);
486     };
487
488     // Bind the left and right arrow keys for image navigation. This event
489     // handler never gets unbinded. Disable this using the `keyboardNavEnabled`
490     // flag. It is automatically disabled when the target is an input and or a
491     // textarea. TODO: Move this to a directive.
492     $document.bind('keydown', function (event) {
493       if (!Lightbox.keyboardNavEnabled) {
494         return;
495       }
496
497       // method of Lightbox to call
498       var method = null;
499
500       switch (event.which) {
501       case 39: // right arrow key
502         method = 'nextImage';
503         break;
504       case 37: // left arrow key
505         method = 'prevImage';
506         break;
507       }
508
509       if (method !== null && ['input', 'textarea'].indexOf(
510           event.target.tagName.toLowerCase()) === -1) {
511         // the view doesn't update without a manual digest
512         $timeout(function () {
513           Lightbox[method]();
514         });
515
516         event.preventDefault();
517       }
518     });
519
520     return Lightbox;
521   }];
522 });
523 /**
524  * @class     lightboxSrc
525  * @classdesc This attribute directive is used in an `<img>` element in the
526  *   modal template in place of `src`. It handles resizing both the `<img>`
527  *   element and its relevant parent elements within the modal.
528  * @memberOf  bootstrapLightbox
529  */
530 angular.module('bootstrapLightbox').directive('lightboxSrc', ['$window',
531     'ImageLoader', 'Lightbox', function ($window, ImageLoader, Lightbox) {
532   // Calculate the dimensions to display the image. The max dimensions override
533   // the min dimensions if they conflict.
534   var calculateImageDisplayDimensions = function (dimensions, fullScreenMode) {
535     var w = dimensions.width;
536     var h = dimensions.height;
537     var minW = dimensions.minWidth;
538     var minH = dimensions.minHeight;
539     var maxW = dimensions.maxWidth;
540     var maxH = dimensions.maxHeight;
541
542     var displayW = w;
543     var displayH = h;
544
545     if (!fullScreenMode) {
546       // resize the image if it is too small
547       if (w < minW && h < minH) {
548         // the image is both too thin and short, so compare the aspect ratios to
549         // determine whether to min the width or height
550         if (w / h > maxW / maxH) {
551           displayH = minH;
552           displayW = Math.round(w * minH / h);
553         } else {
554           displayW = minW;
555           displayH = Math.round(h * minW / w);
556         }
557       } else if (w < minW) {
558         // the image is too thin
559         displayW = minW;
560         displayH = Math.round(h * minW / w);
561       } else if (h < minH) {
562         // the image is too short
563         displayH = minH;
564         displayW = Math.round(w * minH / h);
565       }
566
567       // resize the image if it is too large
568       if (w > maxW && h > maxH) {
569         // the image is both too tall and wide, so compare the aspect ratios
570         // to determine whether to max the width or height
571         if (w / h > maxW / maxH) {
572           displayW = maxW;
573           displayH = Math.round(h * maxW / w);
574         } else {
575           displayH = maxH;
576           displayW = Math.round(w * maxH / h);
577         }
578       } else if (w > maxW) {
579         // the image is too wide
580         displayW = maxW;
581         displayH = Math.round(h * maxW / w);
582       } else if (h > maxH) {
583         // the image is too tall
584         displayH = maxH;
585         displayW = Math.round(w * maxH / h);
586       }
587     } else {
588       // full screen mode
589       var ratio = Math.min(maxW / w, maxH / h);
590
591       var zoomedW = Math.round(w * ratio);
592       var zoomedH = Math.round(h * ratio);
593
594       displayW = Math.max(minW, zoomedW);
595       displayH = Math.max(minH, zoomedH);
596     }
597
598     return {
599       'width': displayW || 0,
600       'height': displayH || 0 // NaN is possible when dimensions.width is 0
601     };
602   };
603
604   // format the given dimension for passing into the `css()` method of `jqLite`
605   var formatDimension = function (dimension) {
606     return typeof dimension === 'number' ? dimension + 'px' : dimension;
607   };
608
609   // the dimensions of the image
610   var imageWidth = 0;
611   var imageHeight = 0;
612
613   return {
614     'link': function (scope, element, attrs) {
615       // resize the img element and the containing modal
616       var resize = function () {
617         // get the window dimensions
618         var windowWidth = $window.innerWidth;
619         var windowHeight = $window.innerHeight;
620
621         // calculate the max/min dimensions for the image
622         var imageDimensionLimits = Lightbox.calculateImageDimensionLimits({
623           'windowWidth': windowWidth,
624           'windowHeight': windowHeight,
625           'imageWidth': imageWidth,
626           'imageHeight': imageHeight
627         });
628
629         // calculate the dimensions to display the image
630         var imageDisplayDimensions = calculateImageDisplayDimensions(
631           angular.extend({
632             'width': imageWidth,
633             'height': imageHeight,
634             'minWidth': 1,
635             'minHeight': 1,
636             'maxWidth': 3000,
637             'maxHeight': 3000,
638           }, imageDimensionLimits),
639           Lightbox.fullScreenMode
640         );
641
642         // calculate the dimensions of the modal container
643         var modalDimensions = Lightbox.calculateModalDimensions({
644           'windowWidth': windowWidth,
645           'windowHeight': windowHeight,
646           'imageDisplayWidth': imageDisplayDimensions.width,
647           'imageDisplayHeight': imageDisplayDimensions.height
648         });
649
650         // resize the image
651         element.css({
652           'width': imageDisplayDimensions.width + 'px',
653           'height': imageDisplayDimensions.height + 'px'
654         });
655
656         // setting the height on .modal-dialog does not expand the div with the
657         // background, which is .modal-content
658         angular.element(
659           document.querySelector('.lightbox-modal .modal-dialog')
660         ).css({
661           'width': formatDimension(modalDimensions.width)
662         });
663
664         // .modal-content has no width specified; if we set the width on
665         // .modal-content and not on .modal-dialog, .modal-dialog retains its
666         // default width of 600px and that places .modal-content off center
667         angular.element(
668           document.querySelector('.lightbox-modal .modal-content')
669         ).css({
670           'height': formatDimension(modalDimensions.height)
671         });
672       };
673
674       // load the new image and/or resize the video whenever the attr changes
675       scope.$watch(function () {
676         return attrs.lightboxSrc;
677       }, function (src) {
678         // do nothing if there's no image
679         if (!Lightbox.image) {
680           return;
681         }
682
683         if (!Lightbox.isVideo(Lightbox.image)) { // image
684           // blank the image before resizing the element
685           element[0].src = '#';
686
687           // handle failure to load the image
688           var failure = function () {
689             imageWidth = 0;
690             imageHeight = 0;
691
692             resize();
693           };
694
695           if (src) {
696             ImageLoader.load(src).then(function (image) {
697               // these variables must be set before resize(), as they are used
698               // in it
699               imageWidth = image.naturalWidth;
700               imageHeight = image.naturalHeight;
701
702               // resize the img element and the containing modal
703               resize();
704
705               // show the image
706               element[0].src = src;
707             }, failure);
708           } else {
709             failure();
710           }
711         } else { // video
712           // default dimensions
713           imageWidth = 1280;
714           imageHeight = 720;
715
716           // resize the video element and the containing modal
717           resize();
718
719           // the src attribute applies to `<video>` and not `<embed-video>`
720           element[0].src = src;
721         }
722       });
723
724       // resize the image and modal whenever the window gets resized
725       angular.element($window).on('resize', resize);
726     }
727   };
728 }]);