├── .editorconfig
├── .gitignore
├── .jshintrc
├── Gruntfile.js
├── LICENSE.txt
├── README.md
├── api.md
├── bower.json
├── dist
├── angular-bootstrap-lightbox.css
├── angular-bootstrap-lightbox.js
├── angular-bootstrap-lightbox.min.css
└── angular-bootstrap-lightbox.min.js
├── index.js
├── package.json
└── src
├── angular-bootstrap-lightbox.css
├── image-loader-service.js
├── lightbox-service.js
├── lightbox-src-directive.js
├── lightbox.html
└── module.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 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /.tmp/
2 | /_site/
3 | /bower_components/
4 | /node_modules/
5 |
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "node": true,
3 | "esnext": true,
4 | "bitwise": true,
5 | "camelcase": true,
6 | "curly": true,
7 | "eqeqeq": true,
8 | "globals": {
9 | "angular": true,
10 | "document": true,
11 | "Image": true
12 | },
13 | "immed": true,
14 | "indent": 2,
15 | "latedef": true,
16 | "newcap": true,
17 | "noarg": true,
18 | "quotmark": "single",
19 | "regexp": true,
20 | "undef": true,
21 | "unused": true,
22 | "strict": false,
23 | "trailing": true,
24 | "smarttabs": true,
25 | "white": true
26 | }
27 |
--------------------------------------------------------------------------------
/Gruntfile.js:
--------------------------------------------------------------------------------
1 | module.exports = function (grunt) {
2 |
3 | // Load grunt tasks when they are needed
4 | require('jit-grunt')(grunt, {
5 | ngtemplates: 'grunt-angular-templates',
6 | jsdoc2md: 'grunt-jsdoc-to-markdown'
7 | });
8 |
9 | // Time how long tasks take. Can help when optimizing build times
10 | require('time-grunt')(grunt);
11 |
12 | grunt.initConfig({
13 | pkg: grunt.file.readJSON('package.json'),
14 | library: grunt.file.readJSON('bower.json'),
15 | concat: {
16 | options: {
17 | separator: ''
18 | },
19 | library: {
20 | src: [
21 | 'src/module.js',
22 | '.tmp/templates.js',
23 | 'src/image-loader-service.js',
24 | 'src/lightbox-service.js',
25 | 'src/lightbox-src-directive.js'
26 | ],
27 | dest: 'dist/<%= library.name %>.js'
28 | },
29 | css: {
30 | src: [
31 | 'src/<%= library.name %>.css',
32 | ],
33 | dest: 'dist/<%= library.name %>.css'
34 | }
35 | },
36 | jshint: {
37 | beforeConcat: {
38 | src: ['Gruntfile.js', 'src/**/*.js']
39 | },
40 | options: {
41 | jshintrc: '.jshintrc'
42 | }
43 | },
44 | ngAnnotate: {
45 | dist: {
46 | files: [{
47 | src: '<%= concat.library.dest %>',
48 | dest: 'dist/<%= library.name %>.min.js'
49 | }]
50 | }
51 | },
52 | uglify: {
53 | options: {
54 | banner: '/*! <%= pkg.name %> */\n'
55 | },
56 | jid: {
57 | files: {
58 | 'dist/<%= library.name %>.min.js': ['dist/<%= library.name %>.min.js']
59 | }
60 | }
61 | },
62 | cssmin: {
63 | minify: {
64 | expand: true,
65 | cwd: 'src',
66 | src: ['*.css'],
67 | dest: 'dist',
68 | ext: '.min.css'
69 | }
70 | },
71 | ngtemplates: {
72 | 'bootstrapLightbox': {
73 | cwd: 'src',
74 | src: '*.html',
75 | dest: '.tmp/templates.js',
76 | options: {
77 | htmlmin: {
78 | collapseBooleanAttributes: true,
79 | collapseWhitespace: true,
80 | removeAttributeQuotes: true,
81 | removeComments: true,
82 | removeEmptyAttributes: false,
83 | removeRedundantAttributes: true
84 | }
85 | }
86 | }
87 | },
88 | jsdoc2md: {
89 | doc: {
90 | src: 'src/*.js',
91 | dest: 'api.md'
92 | }
93 | },
94 | });
95 |
96 | grunt.registerTask('default', [
97 | 'ngtemplates',
98 | 'jshint:beforeConcat',
99 | 'concat',
100 | 'ngAnnotate',
101 | 'uglify',
102 | 'cssmin',
103 | ]);
104 | };
105 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # angular-bootstrap-lightbox
2 |
3 | This lightbox displays images using an [AngularUI Bootstrap Modal](http://angular-ui.github.io/bootstrap/#/modal) (v0.14).
4 |
5 | When the lightbox is opened, navigating to the previous/next image can be achieved by clicking buttons above the image, clicking the left/right arrow keys, or swiping to the left/right (optional with ngTouch). The escape key for closing the modal is automatically binded by AngularUI Bootstrap.
6 |
7 | Large images are scaled to fit inside the window. An optional image caption overlays the top left corner of the image.
8 |
9 | ## Demos
10 |
11 | [Demos](http://compact.github.io/angular-bootstrap-lightbox/) / [Plunker](http://plnkr.co/edit/pEXXDYLl0NtkqupEXaJK?p=preview)
12 |
13 | ## Setup
14 |
15 | 1. Install in one of the following ways:
16 |
17 | * Install with Bower: `bower install angular-bootstrap-lightbox --save`
18 | * Install with npm: `npm install angular-bootstrap-lightbox --save`
19 | * Manually save the script and stylesheet from [`dist`](dist).
20 |
21 | 2. Include the stylesheet in your app:
22 |
23 | ```html
24 |
25 | ```
26 |
27 | 3. Include the script in your app:
28 |
29 | ```html
30 |
31 | ```
32 |
33 | 4. The Angular module is named `bootstrapLightbox`. Add it as a dependency to your module:
34 |
35 | ```js
36 | angular.module('app', ['bootstrapLightbox']);
37 | ```
38 |
39 | 5. Optional dependencies:
40 |
41 | * To enable swipe navigation in the lightbox, include the [ngTouch](https://docs.angularjs.org/api/ngTouch) script.
42 | * To show a loading bar while an image is loading, include the [angular-loading-bar](https://github.com/chieffancypants/angular-loading-bar) script.
43 | * For video support, include the [ng-videosharing-embed](https://github.com/erost/ng-videosharing-embed) script.
44 |
45 | ## Basic example
46 |
47 | Gallery:
48 |
49 | ```html
50 |
57 | ```
58 |
59 | Controller:
60 |
61 | ```js
62 | angular.module('app').controller('GalleryCtrl', function ($scope, Lightbox) {
63 | $scope.images = [
64 | {
65 | 'url': '1.jpg',
66 | 'caption': 'Optional caption',
67 | 'thumbUrl': 'thumb1.jpg' // used only for this example
68 | },
69 | {
70 | 'url': '2.gif',
71 | 'thumbUrl': 'thumb2.jpg'
72 | },
73 | {
74 | 'url': '3.png',
75 | 'thumbUrl': 'thumb3.png'
76 | }
77 | ];
78 |
79 | $scope.openLightboxModal = function (index) {
80 | Lightbox.openModal($scope.images, index);
81 | };
82 | });
83 | ```
84 |
85 | ## Configuration
86 |
87 | ### Changing the appearance of the modal lightbox
88 |
89 | The default view template for the lightbox is [lightbox.html](src/lightbox.html). Its look can be changed by making your own custom template and/or adding CSS rules; for example, use the selector `.lightbox-image-caption` to style the caption.
90 |
91 | If you make your own template and save it at `lightbox.html`, no further code is necessary. If you save it at a different path, set it in the provider:
92 |
93 | ```js
94 | angular.module('app').config(function (LightboxProvider) {
95 | // set a custom template
96 | LightboxProvider.templateUrl = 'path/to/your-template.html';
97 | });
98 | ```
99 |
100 | ### Disabling the keyboard navigation
101 |
102 | The keyboard navigation in the lightbox with the left/right arrow keys can be enabled/disabled at any time by changing the value of the boolean `Lightbox.keyboardNavEnabled`.
103 |
104 | ### Array of images
105 |
106 | The first argument to `Lightbox.openModal` must be an array, and its elements may be of any type. In the basic example above, it is an array of objects with properties `url` and `caption`, but this is only the default and is not required. To let the `Lightbox` service know the correct values, set these methods in the provider:
107 |
108 | ```js
109 | angular.module('app').config(function (LightboxProvider) {
110 | LightboxProvider.getImageUrl = function (image) {
111 | return '/base/dir/' + image.getName();
112 | };
113 |
114 | LightboxProvider.getImageCaption = function (image) {
115 | return image.label;
116 | };
117 | });
118 | ```
119 |
120 | ### Image and modal scaling
121 |
122 | By default, images are scaled only if they are too large for the modal to contain without scrolling.
123 |
124 | If you want all images to be scaled to the maximum possible dimensions, update the `Lightbox.fullScreenMode` boolean:
125 |
126 | ```js
127 | angular.module('app').config(function (LightboxProvider) {
128 | LightboxProvider.fullScreenMode = true;
129 | });
130 | ```
131 |
132 | For more custom behaviour, see the [documentation](src/lightbox-service.js) of the methods `calculateImageDimensionLimits` and `calculateModalDimensions`.
133 |
134 | ### Videos
135 |
136 | An element in the array of 'images' is considered a video if it is an object with a `type` property having the value `video` (see the [demo](http://compact.github.io/angular-bootstrap-lightbox/demo5/index.html)). To change this, write your own `LightboxProvider.isVideo` method.
137 |
138 | By default, a video is embedded directly if its url ends in `.mp4`, `.ogg`, or `.webm`. Every other url is considered a video from an external sharing service (such as YouTube). To change this check, edit the `LightboxProvider.isSharedVideo` method. The `ng-videosharing-embed` library is used for embedding shared videos if it is included in your app. You can use another video library by changing the template.
139 |
140 | For now, the maximum video dimensions are fixed at 1280x720 (16:9).
141 |
142 | ## Development
143 |
144 | * [API documentation](api.md) of the services and directive in the Angular module
145 |
146 | * Setup:
147 |
148 | ```sh
149 | npm install
150 | bower install
151 | ```
152 |
153 | * Build:
154 |
155 | ```sh
156 | grunt
157 | ```
158 |
159 | * Generate docs:
160 |
161 | ```sh
162 | grunt jsdoc2md
163 | ```
164 |
165 | * Serve the GitHub Pages:
166 |
167 | ```sh
168 | git checkout gh-pages
169 | git checkout master -- dist/angular-bootstrap-lightbox.min.js dist/angular-bootstrap-lightbox.min.css
170 | bundle install
171 | bundle exec jekyll serve
172 | ```
173 |
--------------------------------------------------------------------------------
/api.md:
--------------------------------------------------------------------------------
1 |
2 | ## bootstrapLightbox : object
3 | **Kind**: global namespace
4 |
5 | * [bootstrapLightbox](#bootstrapLightbox) : object
6 | * [.ImageLoader](#bootstrapLightbox.ImageLoader)
7 | * [.load](#bootstrapLightbox.ImageLoader.load) ⇒ Promise
8 | * [.Lightbox](#bootstrapLightbox.Lightbox)
9 | * [.templateUrl](#bootstrapLightbox.Lightbox.templateUrl) : String
10 | * [.fullScreenMode](#bootstrapLightbox.Lightbox.fullScreenMode) : Boolean
11 | * [.getImageUrl](#bootstrapLightbox.Lightbox.getImageUrl) ⇒ String
12 | * [.getImageCaption](#bootstrapLightbox.Lightbox.getImageCaption) ⇒ String
13 | * [.calculateImageDimensionLimits](#bootstrapLightbox.Lightbox.calculateImageDimensionLimits) ⇒ Object
14 | * [.calculateModalDimensions](#bootstrapLightbox.Lightbox.calculateModalDimensions) ⇒ Object
15 | * [.isVideo](#bootstrapLightbox.Lightbox.isVideo) ⇒ Boolean
16 | * [.isSharedVideo](#bootstrapLightbox.Lightbox.isSharedVideo) ⇒ Boolean
17 | * [.images](#bootstrapLightbox.Lightbox.images) : Array
18 | * [.index](#bootstrapLightbox.Lightbox.index) : Number
19 | * [.keyboardNavEnabled](#bootstrapLightbox.Lightbox.keyboardNavEnabled) : Boolean
20 | * [.image](#bootstrapLightbox.Lightbox.image) : \*
21 | * [.modalInstance](#bootstrapLightbox.Lightbox.modalInstance) : Object
22 | * [.imageUrl](#bootstrapLightbox.Lightbox.imageUrl) : String
23 | * [.imageCaption](#bootstrapLightbox.Lightbox.imageCaption) : String
24 | * [.loading](#bootstrapLightbox.Lightbox.loading) : Boolean
25 | * [.openModal](#bootstrapLightbox.Lightbox.openModal) ⇒ Object
26 | * [.closeModal](#bootstrapLightbox.Lightbox.closeModal) : function
27 | * [.setImage](#bootstrapLightbox.Lightbox.setImage) : function
28 | * [.firstImage](#bootstrapLightbox.Lightbox.firstImage) : function
29 | * [.prevImage](#bootstrapLightbox.Lightbox.prevImage) : function
30 | * [.nextImage](#bootstrapLightbox.Lightbox.nextImage) : function
31 | * [.lastImage](#bootstrapLightbox.Lightbox.lastImage) : function
32 | * [.setImages](#bootstrapLightbox.Lightbox.setImages) : function
33 | * [.lightboxSrc](#bootstrapLightbox.lightboxSrc)
34 |
35 |
36 | ### bootstrapLightbox.ImageLoader
37 | Service for loading an image.
38 |
39 | **Kind**: static class of [bootstrapLightbox](#bootstrapLightbox)
40 |
41 | #### ImageLoader.load ⇒ Promise
42 | Load the image at the given URL.
43 |
44 | **Kind**: static property of [ImageLoader](#bootstrapLightbox.ImageLoader)
45 | **Returns**: Promise
- A $q promise that resolves when the image has loaded
46 | successfully.
47 |
48 | | Param | Type |
49 | | --- | --- |
50 | | url | String
|
51 |
52 |
53 | ### bootstrapLightbox.Lightbox
54 | Lightbox service.
55 |
56 | **Kind**: static class of [bootstrapLightbox](#bootstrapLightbox)
57 |
58 | * [.Lightbox](#bootstrapLightbox.Lightbox)
59 | * [.templateUrl](#bootstrapLightbox.Lightbox.templateUrl) : String
60 | * [.fullScreenMode](#bootstrapLightbox.Lightbox.fullScreenMode) : Boolean
61 | * [.getImageUrl](#bootstrapLightbox.Lightbox.getImageUrl) ⇒ String
62 | * [.getImageCaption](#bootstrapLightbox.Lightbox.getImageCaption) ⇒ String
63 | * [.calculateImageDimensionLimits](#bootstrapLightbox.Lightbox.calculateImageDimensionLimits) ⇒ Object
64 | * [.calculateModalDimensions](#bootstrapLightbox.Lightbox.calculateModalDimensions) ⇒ Object
65 | * [.isVideo](#bootstrapLightbox.Lightbox.isVideo) ⇒ Boolean
66 | * [.isSharedVideo](#bootstrapLightbox.Lightbox.isSharedVideo) ⇒ Boolean
67 | * [.images](#bootstrapLightbox.Lightbox.images) : Array
68 | * [.index](#bootstrapLightbox.Lightbox.index) : Number
69 | * [.keyboardNavEnabled](#bootstrapLightbox.Lightbox.keyboardNavEnabled) : Boolean
70 | * [.image](#bootstrapLightbox.Lightbox.image) : \*
71 | * [.modalInstance](#bootstrapLightbox.Lightbox.modalInstance) : Object
72 | * [.imageUrl](#bootstrapLightbox.Lightbox.imageUrl) : String
73 | * [.imageCaption](#bootstrapLightbox.Lightbox.imageCaption) : String
74 | * [.loading](#bootstrapLightbox.Lightbox.loading) : Boolean
75 | * [.openModal](#bootstrapLightbox.Lightbox.openModal) ⇒ Object
76 | * [.closeModal](#bootstrapLightbox.Lightbox.closeModal) : function
77 | * [.setImage](#bootstrapLightbox.Lightbox.setImage) : function
78 | * [.firstImage](#bootstrapLightbox.Lightbox.firstImage) : function
79 | * [.prevImage](#bootstrapLightbox.Lightbox.prevImage) : function
80 | * [.nextImage](#bootstrapLightbox.Lightbox.nextImage) : function
81 | * [.lastImage](#bootstrapLightbox.Lightbox.lastImage) : function
82 | * [.setImages](#bootstrapLightbox.Lightbox.setImages) : function
83 |
84 |
85 | #### Lightbox.templateUrl : String
86 | Template URL passed into `$uibModal.open()`.
87 |
88 | **Kind**: static property of [Lightbox](#bootstrapLightbox.Lightbox)
89 |
90 | #### Lightbox.fullScreenMode : Boolean
91 | Whether images should be scaled to the maximum possible dimensions.
92 |
93 | **Kind**: static property of [Lightbox](#bootstrapLightbox.Lightbox)
94 |
95 | #### Lightbox.getImageUrl ⇒ String
96 | **Kind**: static property of [Lightbox](#bootstrapLightbox.Lightbox)
97 | **Returns**: String
- The URL of the given image.
98 |
99 | | Param | Type | Description |
100 | | --- | --- | --- |
101 | | image | \*
| An element in the array of images. |
102 |
103 |
104 | #### Lightbox.getImageCaption ⇒ String
105 | **Kind**: static property of [Lightbox](#bootstrapLightbox.Lightbox)
106 | **Returns**: String
- The caption of the given image.
107 |
108 | | Param | Type | Description |
109 | | --- | --- | --- |
110 | | image | \*
| An element in the array of images. |
111 |
112 |
113 | #### Lightbox.calculateImageDimensionLimits ⇒ Object
114 | Calculate the max and min limits to the width and height of the displayed
115 | image (all are optional). The max dimensions override the min
116 | dimensions if they conflict.
117 |
118 | **Kind**: static property of [Lightbox](#bootstrapLightbox.Lightbox)
119 | **Returns**: Object
- May optionally contain the properties `minWidth`,
120 | `minHeight`, `maxWidth`, and `maxHeight`.
121 |
122 | | Param | Type | Description |
123 | | --- | --- | --- |
124 | | dimensions | Object
| Contains the properties `windowWidth`, `windowHeight`, `imageWidth`, and `imageHeight`. |
125 |
126 |
127 | #### Lightbox.calculateModalDimensions ⇒ Object
128 | Calculate the width and height of the modal. This method gets called
129 | after the width and height of the image, as displayed inside the modal,
130 | are calculated.
131 |
132 | **Kind**: static property of [Lightbox](#bootstrapLightbox.Lightbox)
133 | **Returns**: Object
- Must contain the properties `width` and `height`.
134 |
135 | | Param | Type | Description |
136 | | --- | --- | --- |
137 | | dimensions | Object
| Contains the properties `windowWidth`, `windowHeight`, `imageDisplayWidth`, and `imageDisplayHeight`. |
138 |
139 |
140 | #### Lightbox.isVideo ⇒ Boolean
141 | **Kind**: static property of [Lightbox](#bootstrapLightbox.Lightbox)
142 | **Returns**: Boolean
- Whether the provided element is a video.
143 |
144 | | Param | Type | Description |
145 | | --- | --- | --- |
146 | | image | \*
| An element in the array of images. |
147 |
148 |
149 | #### Lightbox.isSharedVideo ⇒ Boolean
150 | **Kind**: static property of [Lightbox](#bootstrapLightbox.Lightbox)
151 | **Returns**: Boolean
- Whether the provided element is a video that is to be
152 | embedded with an external service like YouTube. By default, this is
153 | determined by the url not ending in `.mp4`, `.ogg`, or `.webm`.
154 |
155 | | Param | Type | Description |
156 | | --- | --- | --- |
157 | | image | \*
| An element in the array of images. |
158 |
159 |
160 | #### Lightbox.images : Array
161 | Array of all images to be shown in the lightbox (not `Image` objects).
162 |
163 | **Kind**: static property of [Lightbox](#bootstrapLightbox.Lightbox)
164 |
165 | #### Lightbox.index : Number
166 | The index in the `Lightbox.images` aray of the image that is currently
167 | shown in the lightbox.
168 |
169 | **Kind**: static property of [Lightbox](#bootstrapLightbox.Lightbox)
170 |
171 | #### Lightbox.keyboardNavEnabled : Boolean
172 | Whether keyboard navigation is currently enabled for navigating through
173 | images in the lightbox.
174 |
175 | **Kind**: static property of [Lightbox](#bootstrapLightbox.Lightbox)
176 |
177 | #### Lightbox.image : \*
178 | The image currently shown in the lightbox.
179 |
180 | **Kind**: static property of [Lightbox](#bootstrapLightbox.Lightbox)
181 |
182 | #### Lightbox.modalInstance : Object
183 | The UI Bootstrap modal instance. See {@link
184 | http://angular-ui.github.io/bootstrap/#/modal}.
185 |
186 | **Kind**: static property of [Lightbox](#bootstrapLightbox.Lightbox)
187 |
188 | #### Lightbox.imageUrl : String
189 | The URL of the current image. This is a property of the service rather
190 | than of `Lightbox.image` because `Lightbox.image` need not be an
191 | object, and besides it would be poor practice to alter the given
192 | objects.
193 |
194 | **Kind**: static property of [Lightbox](#bootstrapLightbox.Lightbox)
195 |
196 | #### Lightbox.imageCaption : String
197 | The optional caption of the current image.
198 |
199 | **Kind**: static property of [Lightbox](#bootstrapLightbox.Lightbox)
200 |
201 | #### Lightbox.loading : Boolean
202 | Whether an image is currently being loaded.
203 |
204 | **Kind**: static property of [Lightbox](#bootstrapLightbox.Lightbox)
205 |
206 | #### Lightbox.openModal ⇒ Object
207 | Open the lightbox modal.
208 |
209 | **Kind**: static property of [Lightbox](#bootstrapLightbox.Lightbox)
210 | **Returns**: Object
- The created UI Bootstrap modal instance.
211 |
212 | | Param | Type | Description |
213 | | --- | --- | --- |
214 | | newImages | Array
| An array of images. Each image may be of any type. |
215 | | newIndex | Number
| The index in `newImages` to set as the current image. |
216 | | modalParams | Object
| Custom params for the angular UI bootstrap modal (in $uibModal.open()). |
217 |
218 |
219 | #### Lightbox.closeModal : function
220 | Close the lightbox modal.
221 |
222 | **Kind**: static property of [Lightbox](#bootstrapLightbox.Lightbox)
223 |
224 | | Param | Type | Description |
225 | | --- | --- | --- |
226 | | result | \*
| This argument can be useful if the modal promise gets handler(s) attached to it. |
227 |
228 |
229 | #### Lightbox.setImage : function
230 | This method can be used in all methods which navigate/change the
231 | current image.
232 |
233 | **Kind**: static property of [Lightbox](#bootstrapLightbox.Lightbox)
234 |
235 | | Param | Type | Description |
236 | | --- | --- | --- |
237 | | newIndex | Number
| The index in the array of images to set as the new current image. |
238 |
239 |
240 | #### Lightbox.firstImage : function
241 | Navigate to the first image.
242 |
243 | **Kind**: static property of [Lightbox](#bootstrapLightbox.Lightbox)
244 |
245 | #### Lightbox.prevImage : function
246 | Navigate to the previous image.
247 |
248 | **Kind**: static property of [Lightbox](#bootstrapLightbox.Lightbox)
249 |
250 | #### Lightbox.nextImage : function
251 | Navigate to the next image.
252 |
253 | **Kind**: static property of [Lightbox](#bootstrapLightbox.Lightbox)
254 |
255 | #### Lightbox.lastImage : function
256 | Navigate to the last image.
257 |
258 | **Kind**: static property of [Lightbox](#bootstrapLightbox.Lightbox)
259 |
260 | #### Lightbox.setImages : function
261 | Call this method to set both the array of images and the current image
262 | (based on the current index). A use case is when the image collection
263 | gets changed dynamically in some way while the lightbox is still
264 | open.
265 |
266 | **Kind**: static property of [Lightbox](#bootstrapLightbox.Lightbox)
267 |
268 | | Param | Type | Description |
269 | | --- | --- | --- |
270 | | newImages | Array
| The new array of images. |
271 |
272 |
273 | ### bootstrapLightbox.lightboxSrc
274 | This attribute directive is used in an ` ` element in the
275 | modal template in place of `src`. It handles resizing both the ` `
276 | element and its relevant parent elements within the modal.
277 |
278 | **Kind**: static class of [bootstrapLightbox](#bootstrapLightbox)
279 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular-bootstrap-lightbox",
3 | "version": "0.12.0",
4 | "main": [
5 | "dist/angular-bootstrap-lightbox.js",
6 | "dist/angular-bootstrap-lightbox.css"
7 | ],
8 | "ignore": [
9 | "src",
10 | ".*",
11 | "Gruntfile.js",
12 | "package.json"
13 | ],
14 | "dependencies": {
15 | "angular": "^1.4.10",
16 | "angular-bootstrap": "^1.3.1"
17 | },
18 | "devDependencies": {
19 | "bootstrap":"^3.3.6",
20 | "angular-loading-bar": "^0.8.0",
21 | "angular-touch": "^1.4.10",
22 | "ng-videosharing-embed": "^0.3.4"
23 | },
24 | "resolutions": {
25 | "angular": "^1.4.10"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/dist/angular-bootstrap-lightbox.css:
--------------------------------------------------------------------------------
1 | .lightbox-nav {
2 | position: relative;
3 | margin-bottom: 12px; /* the font-size of .btn-xs */
4 | height: 22px;
5 | text-align: center;
6 | font-size: 0; /* prevent the otherwise inherited font-size and line-height from adding extra space to the bottom of this div */
7 | }
8 |
9 | .lightbox-nav .btn-group {
10 | vertical-align: top;
11 | }
12 |
13 | .lightbox-nav .close {
14 | /* absolutely position this in order to center the nav buttons */
15 | position: absolute;
16 | top: 0;
17 | right: 0;
18 | }
19 |
20 | .lightbox-image-container {
21 | position: relative;
22 | text-align: center; /* center the image */
23 | }
24 |
25 | /* the caption overlays the top left corner of the image */
26 | .lightbox-image-caption {
27 | position: absolute;
28 | top: 0;
29 | left: 0;
30 | margin: 0.5em 0.9em; /* the left and right margins are offset by 0.4em for the span box-shadow */
31 | color: #000;
32 | font-size: 1.5em;
33 | font-weight: bold;
34 | text-align: left;
35 | text-shadow: 0.1em 0.1em 0.2em rgba(255, 255, 255, 0.5);
36 | }
37 |
38 | .lightbox-image-caption span {
39 | padding-top: 0.1em;
40 | padding-bottom: 0.1em;
41 | background-color: rgba(255, 255, 255, 0.75);
42 | /* pad the left and right of each line of text */
43 | box-shadow: 0.4em 0 0 rgba(255, 255, 255, 0.75),
44 | -0.4em 0 0 rgba(255, 255, 255, 0.75);
45 | }
46 |
--------------------------------------------------------------------------------
/dist/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 | "{{Lightbox.imageCaption}}
"
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 ` ` element in the
526 | * modal template in place of `src`. It handles resizing both the ` `
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 `` and not ``
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 | }]);
729 |
--------------------------------------------------------------------------------
/dist/angular-bootstrap-lightbox.min.css:
--------------------------------------------------------------------------------
1 | .lightbox-nav{position:relative;margin-bottom:12px;height:22px;text-align:center;font-size:0}.lightbox-nav .btn-group{vertical-align:top}.lightbox-nav .close{position:absolute;top:0;right:0}.lightbox-image-container{position:relative;text-align:center}.lightbox-image-caption{position:absolute;top:0;left:0;margin:.5em .9em;color:#000;font-size:1.5em;font-weight:700;text-align:left;text-shadow:.1em .1em .2em rgba(255,255,255,.5)}.lightbox-image-caption span{padding-top:.1em;padding-bottom:.1em;background-color:rgba(255,255,255,.75);box-shadow:.4em 0 0 rgba(255,255,255,.75),-.4em 0 0 rgba(255,255,255,.75)}
--------------------------------------------------------------------------------
/dist/angular-bootstrap-lightbox.min.js:
--------------------------------------------------------------------------------
1 | /*! angular-bootstrap-lightbox */
2 | angular.module("bootstrapLightbox",["ui.bootstrap"]);try{angular.module("angular-loading-bar"),angular.module("bootstrapLightbox").requires.push("angular-loading-bar")}catch(e){}try{angular.module("ngTouch"),angular.module("bootstrapLightbox").requires.push("ngTouch")}catch(e){}try{angular.module("videosharing-embed"),angular.module("bootstrapLightbox").requires.push("videosharing-embed")}catch(e){}angular.module("bootstrapLightbox").run(["$templateCache",function(a){"use strict";a.put("lightbox.html",'{{Lightbox.imageCaption}}
')}]),angular.module("bootstrapLightbox").service("ImageLoader",["$q",function(a){this.load=function(b){var c=a.defer(),d=new Image;return d.onload=function(){("boolean"==typeof this.complete&&this.complete===!1||"number"==typeof this.naturalWidth&&0===this.naturalWidth)&&c.reject(),c.resolve(d)},d.onerror=function(){c.reject()},d.src=b,c.promise}}]),angular.module("bootstrapLightbox").provider("Lightbox",function(){this.templateUrl="lightbox.html",this.fullScreenMode=!1,this.getImageUrl=function(a){return"string"==typeof a?a:a.url},this.getImageCaption=function(a){return a.caption},this.calculateImageDimensionLimits=function(a){return a.windowWidth>=768?{maxWidth:a.windowWidth-92,maxHeight:a.windowHeight-126}:{maxWidth:a.windowWidth-52,maxHeight:a.windowHeight-86}},this.calculateModalDimensions=function(a){var b=Math.max(400,a.imageDisplayWidth+32),c=Math.max(200,a.imageDisplayHeight+66);return(b>=a.windowWidth-20||a.windowWidth<768)&&(b="auto"),c>=a.windowHeight&&(c="auto"),{width:b,height:c}},this.isVideo=function(a){return"object"==typeof a&&a&&a.type?"video"===a.type:!1},this.isSharedVideo=function(a){return this.isVideo(a)&&!this.getImageUrl(a).match(/\.(mp4|ogg|webm)$/)},this.$get=["$document","$injector","$uibModal","$timeout","ImageLoader",function(a,b,c,d,e){var f=b.has("cfpLoadingBar")?b.get("cfpLoadingBar"):null,g={};return g.images=[],g.index=-1,g.templateUrl=this.templateUrl,g.fullScreenMode=this.fullScreenMode,g.getImageUrl=this.getImageUrl,g.getImageCaption=this.getImageCaption,g.calculateImageDimensionLimits=this.calculateImageDimensionLimits,g.calculateModalDimensions=this.calculateModalDimensions,g.isVideo=this.isVideo,g.isSharedVideo=this.isSharedVideo,g.keyboardNavEnabled=!1,g.image={},g.modalInstance=null,g.loading=!1,g.openModal=function(a,b,d){return g.images=a,g.setImage(b),g.modalInstance=c.open(angular.extend({templateUrl:g.templateUrl,controller:["$scope",function(a){a.Lightbox=g,g.keyboardNavEnabled=!0}],windowClass:"lightbox-modal"},d||{})),g.modalInstance.result["finally"](function(){g.images=[],g.index=1,g.image={},g.imageUrl=null,g.imageCaption=null,g.keyboardNavEnabled=!1,f&&f.complete()}),g.modalInstance},g.closeModal=function(a){return g.modalInstance.close(a)},g.setImage=function(a){if(!(a in g.images))throw"Invalid image.";g.loading=!0,f&&f.start();var b=g.images[a],c=g.getImageUrl(b),d=function(d){d=d||{},g.index=d.index||a,g.image=d.image||b,g.imageUrl=d.imageUrl||c,g.imageCaption=d.imageCaption||g.getImageCaption(b),g.loading=!1,f&&f.complete()};g.isVideo(b)?d():e.load(c).then(function(){d()},function(){d({imageUrl:"#",imageCaption:"Failed to load image"})})},g.firstImage=function(){g.setImage(0)},g.prevImage=function(){g.setImage((g.index-1+g.images.length)%g.images.length)},g.nextImage=function(){g.setImage((g.index+1)%g.images.length)},g.lastImage=function(){g.setImage(g.images.length-1)},g.setImages=function(a){g.images=a,g.setImage(g.index)},a.bind("keydown",function(a){if(g.keyboardNavEnabled){var b=null;switch(a.which){case 39:b="nextImage";break;case 37:b="prevImage"}null!==b&&-1===["input","textarea"].indexOf(a.target.tagName.toLowerCase())&&(d(function(){g[b]()}),a.preventDefault())}}),g}]}),angular.module("bootstrapLightbox").directive("lightboxSrc",["$window","ImageLoader","Lightbox",function(a,b,c){var d=function(a,b){var c=a.width,d=a.height,e=a.minWidth,f=a.minHeight,g=a.maxWidth,h=a.maxHeight,i=c,j=d;if(b){var k=Math.min(g/c,h/d),l=Math.round(c*k),m=Math.round(d*k);i=Math.max(e,l),j=Math.max(f,m)}else e>c&&f>d?c/d>g/h?(j=f,i=Math.round(c*f/d)):(i=e,j=Math.round(d*e/c)):e>c?(i=e,j=Math.round(d*e/c)):f>d&&(j=f,i=Math.round(c*f/d)),c>g&&d>h?c/d>g/h?(i=g,j=Math.round(d*g/c)):(j=h,i=Math.round(c*h/d)):c>g?(i=g,j=Math.round(d*g/c)):d>h&&(j=h,i=Math.round(c*h/d));return{width:i||0,height:j||0}},e=function(a){return"number"==typeof a?a+"px":a},f=0,g=0;return{link:function(h,i,j){var k=function(){var b=a.innerWidth,h=a.innerHeight,j=c.calculateImageDimensionLimits({windowWidth:b,windowHeight:h,imageWidth:f,imageHeight:g}),k=d(angular.extend({width:f,height:g,minWidth:1,minHeight:1,maxWidth:3e3,maxHeight:3e3},j),c.fullScreenMode),l=c.calculateModalDimensions({windowWidth:b,windowHeight:h,imageDisplayWidth:k.width,imageDisplayHeight:k.height});i.css({width:k.width+"px",height:k.height+"px"}),angular.element(document.querySelector(".lightbox-modal .modal-dialog")).css({width:e(l.width)}),angular.element(document.querySelector(".lightbox-modal .modal-content")).css({height:e(l.height)})};h.$watch(function(){return j.lightboxSrc},function(a){if(c.image)if(c.isVideo(c.image))f=1280,g=720,k(),i[0].src=a;else{i[0].src="#";var d=function(){f=0,g=0,k()};a?b.load(a).then(function(b){f=b.naturalWidth,g=b.naturalHeight,k(),i[0].src=a},d):d()}}),angular.element(a).on("resize",k)}}}]);
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | require('./dist/angular-bootstrap-lightbox');
2 | module.exports = 'angular-bootstrap-lightbox';
3 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular-bootstrap-lightbox",
3 | "version": "0.12.0",
4 | "description": "An AngularJS lightbox built using UI Bootstrap Modal.",
5 | "keywords": [
6 | "angular",
7 | "lightbox",
8 | "image",
9 | "gallery"
10 | ],
11 | "license": "MIT",
12 | "repository": {
13 | "type": "git",
14 | "url": "git://github.com/compact/angular-bootstrap-lightbox.git"
15 | },
16 | "homepage": "http://compact.github.io/angular-bootstrap-lightbox/",
17 | "dependencies": {},
18 | "devDependencies": {
19 | "grunt": "^0.4.5",
20 | "grunt-angular-templates": "^0.5.7",
21 | "grunt-contrib-concat": "^0.5.1",
22 | "grunt-contrib-cssmin": "^0.14.0",
23 | "grunt-contrib-htmlmin": "^0.5.0",
24 | "grunt-contrib-jshint": "^0.11.3",
25 | "grunt-contrib-uglify": "^0.9.2",
26 | "grunt-jsdoc-to-markdown": "^1.1.1",
27 | "grunt-ng-annotate": "^1.0.1",
28 | "jit-grunt": "^0.9.1",
29 | "time-grunt": "^1.2.2"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/angular-bootstrap-lightbox.css:
--------------------------------------------------------------------------------
1 | .lightbox-nav {
2 | position: relative;
3 | margin-bottom: 12px; /* the font-size of .btn-xs */
4 | height: 22px;
5 | text-align: center;
6 | font-size: 0; /* prevent the otherwise inherited font-size and line-height from adding extra space to the bottom of this div */
7 | }
8 |
9 | .lightbox-nav .btn-group {
10 | vertical-align: top;
11 | }
12 |
13 | .lightbox-nav .close {
14 | /* absolutely position this in order to center the nav buttons */
15 | position: absolute;
16 | top: 0;
17 | right: 0;
18 | }
19 |
20 | .lightbox-image-container {
21 | position: relative;
22 | text-align: center; /* center the image */
23 | }
24 |
25 | /* the caption overlays the top left corner of the image */
26 | .lightbox-image-caption {
27 | position: absolute;
28 | top: 0;
29 | left: 0;
30 | margin: 0.5em 0.9em; /* the left and right margins are offset by 0.4em for the span box-shadow */
31 | color: #000;
32 | font-size: 1.5em;
33 | font-weight: bold;
34 | text-align: left;
35 | text-shadow: 0.1em 0.1em 0.2em rgba(255, 255, 255, 0.5);
36 | }
37 |
38 | .lightbox-image-caption span {
39 | padding-top: 0.1em;
40 | padding-bottom: 0.1em;
41 | background-color: rgba(255, 255, 255, 0.75);
42 | /* pad the left and right of each line of text */
43 | box-shadow: 0.4em 0 0 rgba(255, 255, 255, 0.75),
44 | -0.4em 0 0 rgba(255, 255, 255, 0.75);
45 | }
46 |
--------------------------------------------------------------------------------
/src/image-loader-service.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @class ImageLoader
3 | * @classdesc Service for loading an image.
4 | * @memberOf bootstrapLightbox
5 | */
6 | angular.module('bootstrapLightbox').service('ImageLoader', ['$q',
7 | function ($q) {
8 | /**
9 | * Load the image at the given URL.
10 | * @param {String} url
11 | * @return {Promise} A $q promise that resolves when the image has loaded
12 | * successfully.
13 | * @type {Function}
14 | * @name load
15 | * @memberOf bootstrapLightbox.ImageLoader
16 | */
17 | this.load = function (url) {
18 | var deferred = $q.defer();
19 |
20 | var image = new Image();
21 |
22 | // when the image has loaded
23 | image.onload = function () {
24 | // check image properties for possible errors
25 | if ((typeof this.complete === 'boolean' && this.complete === false) ||
26 | (typeof this.naturalWidth === 'number' && this.naturalWidth === 0)) {
27 | deferred.reject();
28 | }
29 |
30 | deferred.resolve(image);
31 | };
32 |
33 | // when the image fails to load
34 | image.onerror = function () {
35 | deferred.reject();
36 | };
37 |
38 | // start loading the image
39 | image.src = url;
40 |
41 | return deferred.promise;
42 | };
43 | }]);
44 |
--------------------------------------------------------------------------------
/src/lightbox-service.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @class Lightbox
3 | * @classdesc Lightbox service.
4 | * @memberOf bootstrapLightbox
5 | */
6 | angular.module('bootstrapLightbox').provider('Lightbox', function () {
7 | /**
8 | * Template URL passed into `$uibModal.open()`.
9 | * @type {String}
10 | * @name templateUrl
11 | * @memberOf bootstrapLightbox.Lightbox
12 | */
13 | this.templateUrl = 'lightbox.html';
14 |
15 | /**
16 | * Whether images should be scaled to the maximum possible dimensions.
17 | * @type {Boolean}
18 | * @name fullScreenMode
19 | * @memberOf bootstrapLightbox.Lightbox
20 | */
21 | this.fullScreenMode = false;
22 |
23 | /**
24 | * @param {*} image An element in the array of images.
25 | * @return {String} The URL of the given image.
26 | * @type {Function}
27 | * @name getImageUrl
28 | * @memberOf bootstrapLightbox.Lightbox
29 | */
30 | this.getImageUrl = function (image) {
31 | return typeof image === 'string' ? image : image.url;
32 | };
33 |
34 | /**
35 | * @param {*} image An element in the array of images.
36 | * @return {String} The caption of the given image.
37 | * @type {Function}
38 | * @name getImageCaption
39 | * @memberOf bootstrapLightbox.Lightbox
40 | */
41 | this.getImageCaption = function (image) {
42 | return image.caption;
43 | };
44 |
45 | /**
46 | * Calculate the max and min limits to the width and height of the displayed
47 | * image (all are optional). The max dimensions override the min
48 | * dimensions if they conflict.
49 | * @param {Object} dimensions Contains the properties `windowWidth`,
50 | * `windowHeight`, `imageWidth`, and `imageHeight`.
51 | * @return {Object} May optionally contain the properties `minWidth`,
52 | * `minHeight`, `maxWidth`, and `maxHeight`.
53 | * @type {Function}
54 | * @name calculateImageDimensionLimits
55 | * @memberOf bootstrapLightbox.Lightbox
56 | */
57 | this.calculateImageDimensionLimits = function (dimensions) {
58 | if (dimensions.windowWidth >= 768) {
59 | return {
60 | // 92px = 2 * (30px margin of .modal-dialog
61 | // + 1px border of .modal-content
62 | // + 15px padding of .modal-body)
63 | // with the goal of 30px side margins; however, the actual side margins
64 | // will be slightly less (at 22.5px) due to the vertical scrollbar
65 | 'maxWidth': dimensions.windowWidth - 92,
66 | // 126px = 92px as above
67 | // + 34px outer height of .lightbox-nav
68 | 'maxHeight': dimensions.windowHeight - 126
69 | };
70 | } else {
71 | return {
72 | // 52px = 2 * (10px margin of .modal-dialog
73 | // + 1px border of .modal-content
74 | // + 15px padding of .modal-body)
75 | 'maxWidth': dimensions.windowWidth - 52,
76 | // 86px = 52px as above
77 | // + 34px outer height of .lightbox-nav
78 | 'maxHeight': dimensions.windowHeight - 86
79 | };
80 | }
81 | };
82 |
83 | /**
84 | * Calculate the width and height of the modal. This method gets called
85 | * after the width and height of the image, as displayed inside the modal,
86 | * are calculated.
87 | * @param {Object} dimensions Contains the properties `windowWidth`,
88 | * `windowHeight`, `imageDisplayWidth`, and `imageDisplayHeight`.
89 | * @return {Object} Must contain the properties `width` and `height`.
90 | * @type {Function}
91 | * @name calculateModalDimensions
92 | * @memberOf bootstrapLightbox.Lightbox
93 | */
94 | this.calculateModalDimensions = function (dimensions) {
95 | // 400px = arbitrary min width
96 | // 32px = 2 * (1px border of .modal-content
97 | // + 15px padding of .modal-body)
98 | var width = Math.max(400, dimensions.imageDisplayWidth + 32);
99 |
100 | // 200px = arbitrary min height
101 | // 66px = 32px as above
102 | // + 34px outer height of .lightbox-nav
103 | var height = Math.max(200, dimensions.imageDisplayHeight + 66);
104 |
105 | // first case: the modal width cannot be larger than the window width
106 | // 20px = arbitrary value larger than the vertical scrollbar
107 | // width in order to avoid having a horizontal scrollbar
108 | // second case: Bootstrap modals are not centered below 768px
109 | if (width >= dimensions.windowWidth - 20 || dimensions.windowWidth < 768) {
110 | width = 'auto';
111 | }
112 |
113 | // the modal height cannot be larger than the window height
114 | if (height >= dimensions.windowHeight) {
115 | height = 'auto';
116 | }
117 |
118 | return {
119 | 'width': width,
120 | 'height': height
121 | };
122 | };
123 |
124 | /**
125 | * @param {*} image An element in the array of images.
126 | * @return {Boolean} Whether the provided element is a video.
127 | * @type {Function}
128 | * @name isVideo
129 | * @memberOf bootstrapLightbox.Lightbox
130 | */
131 | this.isVideo = function (image) {
132 | if (typeof image === 'object' && image && image.type) {
133 | return image.type === 'video';
134 | }
135 |
136 | return false;
137 | };
138 |
139 | /**
140 | * @param {*} image An element in the array of images.
141 | * @return {Boolean} Whether the provided element is a video that is to be
142 | * embedded with an external service like YouTube. By default, this is
143 | * determined by the url not ending in `.mp4`, `.ogg`, or `.webm`.
144 | * @type {Function}
145 | * @name isSharedVideo
146 | * @memberOf bootstrapLightbox.Lightbox
147 | */
148 | this.isSharedVideo = function (image) {
149 | return this.isVideo(image) &&
150 | !this.getImageUrl(image).match(/\.(mp4|ogg|webm)$/);
151 | };
152 |
153 | this.$get = ['$document', '$injector', '$uibModal', '$timeout', 'ImageLoader',
154 | function ($document, $injector, $uibModal, $timeout, ImageLoader) {
155 | // optional dependency
156 | var cfpLoadingBar = $injector.has('cfpLoadingBar') ?
157 | $injector.get('cfpLoadingBar'): null;
158 |
159 | var Lightbox = {};
160 |
161 | /**
162 | * Array of all images to be shown in the lightbox (not `Image` objects).
163 | * @type {Array}
164 | * @name images
165 | * @memberOf bootstrapLightbox.Lightbox
166 | */
167 | Lightbox.images = [];
168 |
169 | /**
170 | * The index in the `Lightbox.images` aray of the image that is currently
171 | * shown in the lightbox.
172 | * @type {Number}
173 | * @name index
174 | * @memberOf bootstrapLightbox.Lightbox
175 | */
176 | Lightbox.index = -1;
177 |
178 | // set the configurable properties and methods, the defaults of which are
179 | // defined above
180 | Lightbox.templateUrl = this.templateUrl;
181 | Lightbox.fullScreenMode = this.fullScreenMode;
182 | Lightbox.getImageUrl = this.getImageUrl;
183 | Lightbox.getImageCaption = this.getImageCaption;
184 | Lightbox.calculateImageDimensionLimits = this.calculateImageDimensionLimits;
185 | Lightbox.calculateModalDimensions = this.calculateModalDimensions;
186 | Lightbox.isVideo = this.isVideo;
187 | Lightbox.isSharedVideo = this.isSharedVideo;
188 |
189 | /**
190 | * Whether keyboard navigation is currently enabled for navigating through
191 | * images in the lightbox.
192 | * @type {Boolean}
193 | * @name keyboardNavEnabled
194 | * @memberOf bootstrapLightbox.Lightbox
195 | */
196 | Lightbox.keyboardNavEnabled = false;
197 |
198 | /**
199 | * The image currently shown in the lightbox.
200 | * @type {*}
201 | * @name image
202 | * @memberOf bootstrapLightbox.Lightbox
203 | */
204 | Lightbox.image = {};
205 |
206 | /**
207 | * The UI Bootstrap modal instance. See {@link
208 | * http://angular-ui.github.io/bootstrap/#/modal}.
209 | * @type {Object}
210 | * @name modalInstance
211 | * @memberOf bootstrapLightbox.Lightbox
212 | */
213 | Lightbox.modalInstance = null;
214 |
215 | /**
216 | * The URL of the current image. This is a property of the service rather
217 | * than of `Lightbox.image` because `Lightbox.image` need not be an
218 | * object, and besides it would be poor practice to alter the given
219 | * objects.
220 | * @type {String}
221 | * @name imageUrl
222 | * @memberOf bootstrapLightbox.Lightbox
223 | */
224 |
225 | /**
226 | * The optional caption of the current image.
227 | * @type {String}
228 | * @name imageCaption
229 | * @memberOf bootstrapLightbox.Lightbox
230 | */
231 |
232 | /**
233 | * Whether an image is currently being loaded.
234 | * @type {Boolean}
235 | * @name loading
236 | * @memberOf bootstrapLightbox.Lightbox
237 | */
238 | Lightbox.loading = false;
239 |
240 | /**
241 | * Open the lightbox modal.
242 | * @param {Array} newImages An array of images. Each image may be of
243 | * any type.
244 | * @param {Number} newIndex The index in `newImages` to set as the
245 | * current image.
246 | * @param {Object} modalParams Custom params for the angular UI
247 | * bootstrap modal (in $uibModal.open()).
248 | * @return {Object} The created UI Bootstrap modal instance.
249 | * @type {Function}
250 | * @name openModal
251 | * @memberOf bootstrapLightbox.Lightbox
252 | */
253 | Lightbox.openModal = function (newImages, newIndex, modalParams) {
254 | Lightbox.images = newImages;
255 | Lightbox.setImage(newIndex);
256 |
257 | // store the modal instance so we can close it manually if we need to
258 | Lightbox.modalInstance = $uibModal.open(angular.extend({
259 | 'templateUrl': Lightbox.templateUrl,
260 | 'controller': ['$scope', function ($scope) {
261 | // $scope is the modal scope, a child of $rootScope
262 | $scope.Lightbox = Lightbox;
263 |
264 | Lightbox.keyboardNavEnabled = true;
265 | }],
266 | 'windowClass': 'lightbox-modal'
267 | }, modalParams || {}));
268 |
269 | // modal close handler
270 | Lightbox.modalInstance.result['finally'](function () {
271 | // prevent the lightbox from flickering from the old image when it gets
272 | // opened again
273 | Lightbox.images = [];
274 | Lightbox.index = 1;
275 | Lightbox.image = {};
276 | Lightbox.imageUrl = null;
277 | Lightbox.imageCaption = null;
278 |
279 | Lightbox.keyboardNavEnabled = false;
280 |
281 | // complete any lingering loading bar progress
282 | if (cfpLoadingBar) {
283 | cfpLoadingBar.complete();
284 | }
285 | });
286 |
287 | return Lightbox.modalInstance;
288 | };
289 |
290 | /**
291 | * Close the lightbox modal.
292 | * @param {*} result This argument can be useful if the modal promise
293 | * gets handler(s) attached to it.
294 | * @type {Function}
295 | * @name closeModal
296 | * @memberOf bootstrapLightbox.Lightbox
297 | */
298 | Lightbox.closeModal = function (result) {
299 | return Lightbox.modalInstance.close(result);
300 | };
301 |
302 | /**
303 | * This method can be used in all methods which navigate/change the
304 | * current image.
305 | * @param {Number} newIndex The index in the array of images to set as
306 | * the new current image.
307 | * @type {Function}
308 | * @name setImage
309 | * @memberOf bootstrapLightbox.Lightbox
310 | */
311 | Lightbox.setImage = function (newIndex) {
312 | if (!(newIndex in Lightbox.images)) {
313 | throw 'Invalid image.';
314 | }
315 |
316 | // update the loading flag and start the loading bar
317 | Lightbox.loading = true;
318 | if (cfpLoadingBar) {
319 | cfpLoadingBar.start();
320 | }
321 |
322 | var image = Lightbox.images[newIndex];
323 | var imageUrl = Lightbox.getImageUrl(image);
324 |
325 | var success = function (properties) {
326 | // update service properties for the image
327 | properties = properties || {};
328 | Lightbox.index = properties.index || newIndex;
329 | Lightbox.image = properties.image || image;
330 | Lightbox.imageUrl = properties.imageUrl || imageUrl;
331 | Lightbox.imageCaption = properties.imageCaption ||
332 | Lightbox.getImageCaption(image);
333 |
334 | // restore the loading flag and complete the loading bar
335 | Lightbox.loading = false;
336 | if (cfpLoadingBar) {
337 | cfpLoadingBar.complete();
338 | }
339 | };
340 |
341 | if (!Lightbox.isVideo(image)) {
342 | // load the image before setting it, so everything in the view is
343 | // updated at the same time; otherwise, the previous image remains while
344 | // the current image is loading
345 | ImageLoader.load(imageUrl).then(function () {
346 | success();
347 | }, function () {
348 | success({
349 | 'imageUrl': '#', // blank image
350 | // use the caption to show the user an error
351 | 'imageCaption': 'Failed to load image'
352 | });
353 | });
354 | } else {
355 | success();
356 | }
357 | };
358 |
359 | /**
360 | * Navigate to the first image.
361 | * @type {Function}
362 | * @name firstImage
363 | * @memberOf bootstrapLightbox.Lightbox
364 | */
365 | Lightbox.firstImage = function () {
366 | Lightbox.setImage(0);
367 | };
368 |
369 | /**
370 | * Navigate to the previous image.
371 | * @type {Function}
372 | * @name prevImage
373 | * @memberOf bootstrapLightbox.Lightbox
374 | */
375 | Lightbox.prevImage = function () {
376 | Lightbox.setImage((Lightbox.index - 1 + Lightbox.images.length) %
377 | Lightbox.images.length);
378 | };
379 |
380 | /**
381 | * Navigate to the next image.
382 | * @type {Function}
383 | * @name nextImage
384 | * @memberOf bootstrapLightbox.Lightbox
385 | */
386 | Lightbox.nextImage = function () {
387 | Lightbox.setImage((Lightbox.index + 1) % Lightbox.images.length);
388 | };
389 |
390 | /**
391 | * Navigate to the last image.
392 | * @type {Function}
393 | * @name lastImage
394 | * @memberOf bootstrapLightbox.Lightbox
395 | */
396 | Lightbox.lastImage = function () {
397 | Lightbox.setImage(Lightbox.images.length - 1);
398 | };
399 |
400 | /**
401 | * Call this method to set both the array of images and the current image
402 | * (based on the current index). A use case is when the image collection
403 | * gets changed dynamically in some way while the lightbox is still
404 | * open.
405 | * @param {Array} newImages The new array of images.
406 | * @type {Function}
407 | * @name setImages
408 | * @memberOf bootstrapLightbox.Lightbox
409 | */
410 | Lightbox.setImages = function (newImages) {
411 | Lightbox.images = newImages;
412 | Lightbox.setImage(Lightbox.index);
413 | };
414 |
415 | // Bind the left and right arrow keys for image navigation. This event
416 | // handler never gets unbinded. Disable this using the `keyboardNavEnabled`
417 | // flag. It is automatically disabled when the target is an input and or a
418 | // textarea. TODO: Move this to a directive.
419 | $document.bind('keydown', function (event) {
420 | if (!Lightbox.keyboardNavEnabled) {
421 | return;
422 | }
423 |
424 | // method of Lightbox to call
425 | var method = null;
426 |
427 | switch (event.which) {
428 | case 39: // right arrow key
429 | method = 'nextImage';
430 | break;
431 | case 37: // left arrow key
432 | method = 'prevImage';
433 | break;
434 | }
435 |
436 | if (method !== null && ['input', 'textarea'].indexOf(
437 | event.target.tagName.toLowerCase()) === -1) {
438 | // the view doesn't update without a manual digest
439 | $timeout(function () {
440 | Lightbox[method]();
441 | });
442 |
443 | event.preventDefault();
444 | }
445 | });
446 |
447 | return Lightbox;
448 | }];
449 | });
450 |
--------------------------------------------------------------------------------
/src/lightbox-src-directive.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @class lightboxSrc
3 | * @classdesc This attribute directive is used in an ` ` element in the
4 | * modal template in place of `src`. It handles resizing both the ` `
5 | * element and its relevant parent elements within the modal.
6 | * @memberOf bootstrapLightbox
7 | */
8 | angular.module('bootstrapLightbox').directive('lightboxSrc', ['$window',
9 | 'ImageLoader', 'Lightbox', function ($window, ImageLoader, Lightbox) {
10 | // Calculate the dimensions to display the image. The max dimensions override
11 | // the min dimensions if they conflict.
12 | var calculateImageDisplayDimensions = function (dimensions, fullScreenMode) {
13 | var w = dimensions.width;
14 | var h = dimensions.height;
15 | var minW = dimensions.minWidth;
16 | var minH = dimensions.minHeight;
17 | var maxW = dimensions.maxWidth;
18 | var maxH = dimensions.maxHeight;
19 |
20 | var displayW = w;
21 | var displayH = h;
22 |
23 | if (!fullScreenMode) {
24 | // resize the image if it is too small
25 | if (w < minW && h < minH) {
26 | // the image is both too thin and short, so compare the aspect ratios to
27 | // determine whether to min the width or height
28 | if (w / h > maxW / maxH) {
29 | displayH = minH;
30 | displayW = Math.round(w * minH / h);
31 | } else {
32 | displayW = minW;
33 | displayH = Math.round(h * minW / w);
34 | }
35 | } else if (w < minW) {
36 | // the image is too thin
37 | displayW = minW;
38 | displayH = Math.round(h * minW / w);
39 | } else if (h < minH) {
40 | // the image is too short
41 | displayH = minH;
42 | displayW = Math.round(w * minH / h);
43 | }
44 |
45 | // resize the image if it is too large
46 | if (w > maxW && h > maxH) {
47 | // the image is both too tall and wide, so compare the aspect ratios
48 | // to determine whether to max the width or height
49 | if (w / h > maxW / maxH) {
50 | displayW = maxW;
51 | displayH = Math.round(h * maxW / w);
52 | } else {
53 | displayH = maxH;
54 | displayW = Math.round(w * maxH / h);
55 | }
56 | } else if (w > maxW) {
57 | // the image is too wide
58 | displayW = maxW;
59 | displayH = Math.round(h * maxW / w);
60 | } else if (h > maxH) {
61 | // the image is too tall
62 | displayH = maxH;
63 | displayW = Math.round(w * maxH / h);
64 | }
65 | } else {
66 | // full screen mode
67 | var ratio = Math.min(maxW / w, maxH / h);
68 |
69 | var zoomedW = Math.round(w * ratio);
70 | var zoomedH = Math.round(h * ratio);
71 |
72 | displayW = Math.max(minW, zoomedW);
73 | displayH = Math.max(minH, zoomedH);
74 | }
75 |
76 | return {
77 | 'width': displayW || 0,
78 | 'height': displayH || 0 // NaN is possible when dimensions.width is 0
79 | };
80 | };
81 |
82 | // format the given dimension for passing into the `css()` method of `jqLite`
83 | var formatDimension = function (dimension) {
84 | return typeof dimension === 'number' ? dimension + 'px' : dimension;
85 | };
86 |
87 | // the dimensions of the image
88 | var imageWidth = 0;
89 | var imageHeight = 0;
90 |
91 | return {
92 | 'link': function (scope, element, attrs) {
93 | // resize the img element and the containing modal
94 | var resize = function () {
95 | // get the window dimensions
96 | var windowWidth = $window.innerWidth;
97 | var windowHeight = $window.innerHeight;
98 |
99 | // calculate the max/min dimensions for the image
100 | var imageDimensionLimits = Lightbox.calculateImageDimensionLimits({
101 | 'windowWidth': windowWidth,
102 | 'windowHeight': windowHeight,
103 | 'imageWidth': imageWidth,
104 | 'imageHeight': imageHeight
105 | });
106 |
107 | // calculate the dimensions to display the image
108 | var imageDisplayDimensions = calculateImageDisplayDimensions(
109 | angular.extend({
110 | 'width': imageWidth,
111 | 'height': imageHeight,
112 | 'minWidth': 1,
113 | 'minHeight': 1,
114 | 'maxWidth': 3000,
115 | 'maxHeight': 3000,
116 | }, imageDimensionLimits),
117 | Lightbox.fullScreenMode
118 | );
119 |
120 | // calculate the dimensions of the modal container
121 | var modalDimensions = Lightbox.calculateModalDimensions({
122 | 'windowWidth': windowWidth,
123 | 'windowHeight': windowHeight,
124 | 'imageDisplayWidth': imageDisplayDimensions.width,
125 | 'imageDisplayHeight': imageDisplayDimensions.height
126 | });
127 |
128 | // resize the image
129 | element.css({
130 | 'width': imageDisplayDimensions.width + 'px',
131 | 'height': imageDisplayDimensions.height + 'px'
132 | });
133 |
134 | // setting the height on .modal-dialog does not expand the div with the
135 | // background, which is .modal-content
136 | angular.element(
137 | document.querySelector('.lightbox-modal .modal-dialog')
138 | ).css({
139 | 'width': formatDimension(modalDimensions.width)
140 | });
141 |
142 | // .modal-content has no width specified; if we set the width on
143 | // .modal-content and not on .modal-dialog, .modal-dialog retains its
144 | // default width of 600px and that places .modal-content off center
145 | angular.element(
146 | document.querySelector('.lightbox-modal .modal-content')
147 | ).css({
148 | 'height': formatDimension(modalDimensions.height)
149 | });
150 | };
151 |
152 | // load the new image and/or resize the video whenever the attr changes
153 | scope.$watch(function () {
154 | return attrs.lightboxSrc;
155 | }, function (src) {
156 | // do nothing if there's no image
157 | if (!Lightbox.image) {
158 | return;
159 | }
160 |
161 | if (!Lightbox.isVideo(Lightbox.image)) { // image
162 | // blank the image before resizing the element
163 | element[0].src = '#';
164 |
165 | // handle failure to load the image
166 | var failure = function () {
167 | imageWidth = 0;
168 | imageHeight = 0;
169 |
170 | resize();
171 | };
172 |
173 | if (src) {
174 | ImageLoader.load(src).then(function (image) {
175 | // these variables must be set before resize(), as they are used
176 | // in it
177 | imageWidth = image.naturalWidth;
178 | imageHeight = image.naturalHeight;
179 |
180 | // resize the img element and the containing modal
181 | resize();
182 |
183 | // show the image
184 | element[0].src = src;
185 | }, failure);
186 | } else {
187 | failure();
188 | }
189 | } else { // video
190 | // default dimensions
191 | imageWidth = 1280;
192 | imageHeight = 720;
193 |
194 | // resize the video element and the containing modal
195 | resize();
196 |
197 | // the src attribute applies to `` and not ``
198 | element[0].src = src;
199 | }
200 | });
201 |
202 | // resize the image and modal whenever the window gets resized
203 | angular.element($window).on('resize', resize);
204 | }
205 | };
206 | }]);
207 |
--------------------------------------------------------------------------------
/src/lightbox.html:
--------------------------------------------------------------------------------
1 |
4 |
5 |
24 |
25 |
26 |
27 |
28 | {{Lightbox.imageCaption}}
29 |
30 |
31 |
32 |
34 |
35 |
36 |
38 |
39 |
43 |
44 |
45 |
47 |
52 | Watch video
53 |
54 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/src/module.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 |
--------------------------------------------------------------------------------