the_garage 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +162 -0
  4. data/Rakefile +33 -0
  5. data/app/assets/javascripts/garage/application.js +16 -0
  6. data/app/assets/javascripts/garage/docs/console.js.coffee +90 -0
  7. data/app/assets/javascripts/garage/docs/jquery.colorbox.js +1026 -0
  8. data/app/assets/stylesheets/garage/application.css +14 -0
  9. data/app/assets/stylesheets/garage/colorbox.scss +62 -0
  10. data/app/assets/stylesheets/garage/style.scss +59 -0
  11. data/app/assets/stylesheets/vendor/bootstrap.min.css +9 -0
  12. data/app/controllers/garage/application_controller.rb +4 -0
  13. data/app/controllers/garage/docs/resources_controller.rb +103 -0
  14. data/app/controllers/garage/meta/docs_controller.rb +20 -0
  15. data/app/controllers/garage/meta/services_controller.rb +20 -0
  16. data/app/helpers/garage/application_helper.rb +4 -0
  17. data/app/helpers/garage/docs/resources_helper.rb +24 -0
  18. data/app/models/garage/hash_representer.rb +11 -0
  19. data/app/views/garage/docs/resources/_layout_navigation.html.haml +5 -0
  20. data/app/views/garage/docs/resources/_navigation.html.haml +6 -0
  21. data/app/views/garage/docs/resources/callback.html.haml +5 -0
  22. data/app/views/garage/docs/resources/console.html.haml +45 -0
  23. data/app/views/garage/docs/resources/index.html.haml +2 -0
  24. data/app/views/garage/docs/resources/show.html.haml +16 -0
  25. data/app/views/layouts/garage/application.html.haml +26 -0
  26. data/config/routes.rb +0 -0
  27. data/lib/garage/app_responder.rb +22 -0
  28. data/lib/garage/authorizable.rb +26 -0
  29. data/lib/garage/config.rb +76 -0
  30. data/lib/garage/controller_helper.rb +110 -0
  31. data/lib/garage/docs/anchor_building.rb +28 -0
  32. data/lib/garage/docs/application.rb +24 -0
  33. data/lib/garage/docs/config.rb +61 -0
  34. data/lib/garage/docs/console_link_building.rb +14 -0
  35. data/lib/garage/docs/document.rb +141 -0
  36. data/lib/garage/docs/engine.rb +35 -0
  37. data/lib/garage/docs/example.rb +26 -0
  38. data/lib/garage/docs/renderer.rb +17 -0
  39. data/lib/garage/docs/toc_renderer.rb +14 -0
  40. data/lib/garage/docs.rb +9 -0
  41. data/lib/garage/exceptions.rb +49 -0
  42. data/lib/garage/hypermedia_filter.rb +44 -0
  43. data/lib/garage/hypermedia_responder.rb +120 -0
  44. data/lib/garage/meta/engine.rb +16 -0
  45. data/lib/garage/meta/remote_service.rb +78 -0
  46. data/lib/garage/meta.rb +6 -0
  47. data/lib/garage/meta_resource.rb +17 -0
  48. data/lib/garage/nested_field_query.rb +183 -0
  49. data/lib/garage/optional_response_body_responder.rb +16 -0
  50. data/lib/garage/paginating_responder.rb +113 -0
  51. data/lib/garage/permission.rb +13 -0
  52. data/lib/garage/permissions.rb +75 -0
  53. data/lib/garage/representer.rb +214 -0
  54. data/lib/garage/resource_casting_responder.rb +13 -0
  55. data/lib/garage/restful_actions.rb +219 -0
  56. data/lib/garage/strategy/access_token.rb +57 -0
  57. data/lib/garage/strategy/auth_server.rb +200 -0
  58. data/lib/garage/strategy/no_authentication.rb +13 -0
  59. data/lib/garage/strategy/test.rb +44 -0
  60. data/lib/garage/strategy.rb +4 -0
  61. data/lib/garage/test/migrator.rb +31 -0
  62. data/lib/garage/token_scope.rb +134 -0
  63. data/lib/garage/utils.rb +28 -0
  64. data/lib/garage/version.rb +3 -0
  65. data/lib/garage.rb +23 -0
  66. metadata +275 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 30b92d8e2787314c6bcca163423334303f9ec6dc
4
+ data.tar.gz: f3e866410bf36ec5ee28333f060643e1ea090f6b
5
+ SHA512:
6
+ metadata.gz: 0bf3747c2ea3e19c47205d98ca7161faa2acba58eb63a63b11c4810ab2e07460ceaabd20aa8a189ab1fce8c0bdefb80b91b598e27661d0f0a2d856f96619108f
7
+ data.tar.gz: 4f7cbbdca6e56d47c8f8d479b6df5b66c838fb3696953488a9af8524a056114a894e98210b84bee1cea0e3d6e3518aeeeb96a877eac907e7a049eba7e2c71c28
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2013 COOKPAD Inc.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,162 @@
1
+ # Garage
2
+ [![Build Status](https://travis-ci.org/cookpad/garage.svg?branch=master)](https://travis-ci.org/cookpad/garage)
3
+
4
+ Rails framework to add RESTful hypermedia API to your application.
5
+
6
+ ## What Is It?
7
+
8
+ Garage provides a simple, Hypermedia friendly RESTful API to your Rails application using its native RESTful routes. Garage provides a descriptive way to serve your ActiveRecord models, as well as plain old Ruby objects as JSON-based resources.
9
+
10
+ Garage supports OAuth 2 authorizations via Doorkeeper (more extensions to come), and provides resource-based access controls.
11
+
12
+ ## Quickstart
13
+
14
+ In `Gemfile`:
15
+
16
+ ```ruby
17
+ # Notice this gem has "the_" prefix for gem name.
18
+ gem 'the_garage'
19
+ ```
20
+
21
+ In your Rails model class:
22
+
23
+ ```ruby
24
+ class Employee < ActiveRecord::Base
25
+ include Garage::Representer
26
+
27
+ belongs_to :division
28
+ has_many :projects
29
+ property :id
30
+ property :title
31
+ property :first_name
32
+ property :last_name
33
+
34
+ property :division, selectable: true
35
+ collection :projects, selectable: true
36
+
37
+ link(:division) { division_path(division) }
38
+ link(:projects) { employee_projects_path(self) }
39
+
40
+ def self.build_permissions(perms, other, target)
41
+ perms.permits! :read
42
+ end
43
+ end
44
+ ```
45
+
46
+ In your controller class:
47
+
48
+ ```ruby
49
+ class EmployeesController < ApplicationController
50
+ include Garage::RestfulActions
51
+
52
+ def require_resources
53
+ @resources = Employee.all
54
+ end
55
+ end
56
+ ```
57
+
58
+ ## Advanced Configurations
59
+
60
+ In `config/initializers/garage.rb`:
61
+
62
+ ```ruby
63
+ Garage.configure {}
64
+
65
+ # Optional
66
+ Garage::TokenScope.configure do
67
+ register :public, desc: "accessing publicly available data" do
68
+ access :read, Recipe
69
+ end
70
+
71
+ register :read_post, desc: "reading blog post" do
72
+ access :read, Post
73
+ end
74
+ end
75
+
76
+ # If you want to use different authentication/authorization logic.
77
+ Garage.configuration.strategy = Garage::Strategy::AuthServer
78
+ ```
79
+
80
+ The following authentication strategies are available.
81
+
82
+ - `Garage::Strategy::NoAuthentication` - Does not authenticate request and
83
+ does not verify permission and access on resource operation. For non-public,
84
+ internal-use Garage application.
85
+ - `Garage::Strategy::Test` - Trust request thoroughly, and build access token
86
+ from request headers. For testing or prototyping.
87
+ - `Garage::Strategy::Doorkeeper` - Authenticate request with doorkeeper gem.
88
+ To use this strategy, bundle [garage-doorkeeper gem](https://github.com/cookpad/garage-doorkeeper).
89
+ - `Garage::Strategy::AuthServer` - Delegate authentication to OAuth server.
90
+ This auth strategy has configurations.
91
+
92
+ ## Delegate Authentication/Authorization to your OAuth server
93
+
94
+ To delegate auth to your OAuth server, use `Garage::Strategy::AuthServer` strategy.
95
+ Then configure auth server strategy:
96
+
97
+ - `Garage.configuration.auth_server_url` - A full url of your OAuth server's
98
+ access token validation endpoint. i.e. `https://example.com/token`.
99
+ - `Garage.configuration.auth_server_host` - A host header value to request to
100
+ your OAuth server. Can be empty.
101
+ - `Garage.configuration.auth_server_timeout` - A read timeout second. Default
102
+ is 1 second.
103
+
104
+ The OAuth server must response a json with following structure.
105
+
106
+ - `token`(string) - OAuth access token value.
107
+ - `token_type` (string) - OAuth access token value. i.e. `bearer` type.
108
+ - `scope` (string) - OAuth scopes separated by spaces. i.e. `public read_user`.
109
+ - `application_id` (integer) - OAuth application id of the access token.
110
+ - `resource_owner_id` (integer, null) - Resource owner id of the access token.
111
+ - `expired_at` (string, null) - Expire datetime with string representation.
112
+ - `revoked_at` (string, null) - Revoked datetime with string representation.
113
+
114
+ When requested access token is invalid, OAuth server must response 401.
115
+
116
+ ## Customize Authentication/Authorization
117
+
118
+ Garage supports customizable Authentication/Authorization strategy.
119
+ The Strategy has some conventions to follow.
120
+
121
+ - Offer OAuth access token via `access_token` method. With no access token case
122
+ (does not authenticate request) `access_token` should return `nil`.
123
+ - Register `verify_auth` hook as before filter in `included` block if
124
+ authenticate request. Or register custom authentication hook. The custom
125
+ authentication hook should response unauthorized using
126
+ `unauthorized_render_options` when fails to authenticate a request.
127
+ - Offer whether verify permission and access in `RestfulActions` via
128
+ `verify_permission` method. Return `true` to verify them.
129
+
130
+ ```ruby
131
+ module MyStrategy
132
+ extend ActiveSupport::Concern
133
+
134
+ included do
135
+ # Register verify_auth hook if you want to authenticate request.
136
+ before_action :verify_auth
137
+ end
138
+
139
+ def access_token
140
+ # Fetch some `attributes` from DB or auth server API using request.
141
+ # Then returns an AccessToken with caching.
142
+ @access_token ||= Garage::Strategy::AccessToken.new(attributes)
143
+ end
144
+
145
+ # Whether verify permission and access in `RestfulActions`.
146
+ def verify_permission?
147
+ true
148
+ end
149
+ end
150
+ ```
151
+
152
+ ## Authors
153
+
154
+ * Tatsuhiko Miyagawa
155
+ * Taiki Ono
156
+ * Yusuke Mito
157
+ * Ryo Nakamura
158
+
159
+ ## Inspired By
160
+
161
+ * [roar](https://github.com/apotonick/roar)
162
+ * [doorkeeper](https://github.com/doorkeeper-gem/doorkeeper)
data/Rakefile ADDED
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env rake
2
+ begin
3
+ require 'bundler/setup'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+ begin
8
+ require 'rdoc/task'
9
+ rescue LoadError
10
+ require 'rdoc/rdoc'
11
+ require 'rake/rdoctask'
12
+ RDoc::Task = Rake::RDocTask
13
+ end
14
+
15
+ RDoc::Task.new(:rdoc) do |rdoc|
16
+ rdoc.rdoc_dir = 'rdoc'
17
+ rdoc.title = 'Garage'
18
+ rdoc.options << '--line-numbers'
19
+ rdoc.rdoc_files.include('README.rdoc')
20
+ rdoc.rdoc_files.include('lib/**/*.rb')
21
+ end
22
+
23
+ APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
24
+ load 'rails/tasks/engine.rake'
25
+
26
+ Bundler::GemHelper.install_tasks
27
+
28
+ begin
29
+ require 'rspec/core/rake_task'
30
+ RSpec::Core::RakeTask.new(:spec)
31
+ task :default => :spec
32
+ rescue LoadError
33
+ end
@@ -0,0 +1,16 @@
1
+ // This is a manifest file that'll be compiled into application.js, which will include all the files
2
+ // listed below.
3
+ //
4
+ // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
5
+ // or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
6
+ //
7
+ // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8
+ // the compiled file.
9
+ //
10
+ // WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD
11
+ // GO AFTER THE REQUIRES BELOW.
12
+ //
13
+ //= require jquery
14
+ //= require jquery_ujs
15
+ //= require garage/docs/jquery.colorbox
16
+ //= require garage/docs/console
@@ -0,0 +1,90 @@
1
+ jQuery ()->
2
+ $(".get-oauth-token").colorbox(
3
+ transition: "none"
4
+ href: "#oauth-dialog"
5
+ inline: true
6
+ )
7
+
8
+ $(".modal-close").click (ev) ->
9
+ $.colorbox.close()
10
+ ev.preventDefault()
11
+
12
+ $("#oauth-dialog .authenticate").click (ev) ->
13
+ $("form#oauth-authenticate").submit()
14
+ ev.preventDefault()
15
+
16
+ $(".console .validate-token").click (ev) ->
17
+ $("#method").val('GET')
18
+ $("#location").val("/oauth/token/info")
19
+ $('.send-request').click()
20
+
21
+ buildAuthorizedUrl = (base, location, token) ->
22
+ url = base + location
23
+ if url.indexOf('?') > 0
24
+ url += '&'
25
+ else
26
+ url += '?'
27
+ url + 'access_token=' + token
28
+
29
+ addNewParamField = (container) ->
30
+ nextId = "parameter-" + $('.parameter', container).length
31
+ copy = $('.template .parameter').clone().attr('id', nextId)
32
+ $('.close-field', copy).click (ev) ->
33
+ $('#' + nextId).detach()
34
+ $('.add-field', copy).click (ev) ->
35
+ addNewParamField(container)
36
+ copy.show().appendTo(container)
37
+
38
+ buildData = (container) ->
39
+ data = {}
40
+ $('.parameter', container).each (index) ->
41
+ name = $('.name', this).val()
42
+ value = $('.value', this).val()
43
+ data[name] = value
44
+ data
45
+
46
+ $('.console #method').change (ev) ->
47
+ if $(this).val() == 'POST' or $(this).val() == 'PUT'
48
+ if $('.parameters .parameter').length == 0
49
+ addNewParamField($('.parameters'))
50
+ else
51
+ $('.parameters').empty()
52
+
53
+ buildHyperlinks = (json) ->
54
+ html = $('<div/>').text(json).html().replace /"href": "(\/.*?)"/g, "\"href\": \"<a>$1</a>\""
55
+ dom = $("<div>#{html}</div>")
56
+ $('a', dom).each (i) ->
57
+ $(this).attr('href', "#{location.pathname}?location=#{encodeURIComponent($(this).text())}&method=GET")
58
+ dom.html()
59
+
60
+ $('.console .send-request').click (ev) ->
61
+ $('#api-headers').text ''
62
+ $('#api-response').text ''
63
+
64
+ url = buildAuthorizedUrl $('#base').val(), $('#location').val(), $('#access_token').val()
65
+ console.log buildData($('.parameters'))
66
+ $.ajax
67
+ type: $('#method').val(),
68
+ url: url,
69
+ cache: false,
70
+ data: buildData($('.parameters')),
71
+ dataType: 'json',
72
+ complete: ->
73
+ queryString = $.param({'location': $('#location').val(), 'method': $('#method').val()})
74
+ history.pushState('', '', "#{location.pathname}?#{queryString}")
75
+ success: (data, textStatus, xhr) ->
76
+ $('#api-headers').text("#{xhr.status} #{xhr.statusText}\n" + xhr.getAllResponseHeaders())
77
+ $('#api-response').html buildHyperlinks(JSON.stringify(data, undefined, 2))
78
+ error: (xhr, textStatus, error) ->
79
+ $('#api-headers').text("#{xhr.status} #{xhr.statusText}\n" + xhr.getAllResponseHeaders())
80
+ $('#api-response').text xhr.responseText
81
+ ev.preventDefault()
82
+
83
+ if $('.console #token').val() != '' && $('.console #location').val() != '' && $('.console #method').val() == 'GET'
84
+ $('.send-request').click()
85
+
86
+ if $('.oauth-callback-redirect').size() > 0
87
+ token = window.location.hash.match(/\#access_token=(\w+)/)[1]
88
+ if token
89
+ $('#access_token').val(token)
90
+ $('form.oauth-callback-redirect').submit()
@@ -0,0 +1,1026 @@
1
+ /*!
2
+ jQuery Colorbox v1.4.15 - 2013-04-22
3
+ (c) 2013 Jack Moore - jacklmoore.com/colorbox
4
+ license: http://www.opensource.org/licenses/mit-license.php
5
+ */
6
+ (function ($, document, window) {
7
+ var
8
+ // Default settings object.
9
+ // See http://jacklmoore.com/colorbox for details.
10
+ defaults = {
11
+ transition: "elastic",
12
+ speed: 300,
13
+ fadeOut: 300,
14
+ width: false,
15
+ initialWidth: "600",
16
+ innerWidth: false,
17
+ maxWidth: false,
18
+ height: false,
19
+ initialHeight: "450",
20
+ innerHeight: false,
21
+ maxHeight: false,
22
+ scalePhotos: true,
23
+ scrolling: true,
24
+ inline: false,
25
+ html: false,
26
+ iframe: false,
27
+ fastIframe: true,
28
+ photo: false,
29
+ href: false,
30
+ title: false,
31
+ rel: false,
32
+ opacity: 0.9,
33
+ preloading: true,
34
+ className: false,
35
+
36
+ // alternate image paths for high-res displays
37
+ retinaImage: false,
38
+ retinaUrl: false,
39
+ retinaSuffix: '@2x.$1',
40
+
41
+ // internationalization
42
+ current: "image {current} of {total}",
43
+ previous: "previous",
44
+ next: "next",
45
+ close: "close",
46
+ xhrError: "This content failed to load.",
47
+ imgError: "This image failed to load.",
48
+
49
+ open: false,
50
+ returnFocus: true,
51
+ reposition: true,
52
+ loop: true,
53
+ slideshow: false,
54
+ slideshowAuto: true,
55
+ slideshowSpeed: 2500,
56
+ slideshowStart: "start slideshow",
57
+ slideshowStop: "stop slideshow",
58
+ photoRegex: /\.(gif|png|jp(e|g|eg)|bmp|ico|webp)((#|\?).*)?$/i,
59
+
60
+ onOpen: false,
61
+ onLoad: false,
62
+ onComplete: false,
63
+ onCleanup: false,
64
+ onClosed: false,
65
+ overlayClose: true,
66
+ escKey: true,
67
+ arrowKey: true,
68
+ top: false,
69
+ bottom: false,
70
+ left: false,
71
+ right: false,
72
+ fixed: false,
73
+ data: undefined
74
+ },
75
+
76
+ // Abstracting the HTML and event identifiers for easy rebranding
77
+ colorbox = 'colorbox',
78
+ prefix = 'cbox',
79
+ boxElement = prefix + 'Element',
80
+
81
+ // Events
82
+ event_open = prefix + '_open',
83
+ event_load = prefix + '_load',
84
+ event_complete = prefix + '_complete',
85
+ event_cleanup = prefix + '_cleanup',
86
+ event_closed = prefix + '_closed',
87
+ event_purge = prefix + '_purge',
88
+
89
+ // Cached jQuery Object Variables
90
+ $overlay,
91
+ $box,
92
+ $wrap,
93
+ $content,
94
+ $topBorder,
95
+ $leftBorder,
96
+ $rightBorder,
97
+ $bottomBorder,
98
+ $related,
99
+ $window,
100
+ $loaded,
101
+ $loadingBay,
102
+ $loadingOverlay,
103
+ $title,
104
+ $current,
105
+ $slideshow,
106
+ $next,
107
+ $prev,
108
+ $close,
109
+ $groupControls,
110
+ $events = $('<a/>'),
111
+
112
+ // Variables for cached values or use across multiple functions
113
+ settings,
114
+ interfaceHeight,
115
+ interfaceWidth,
116
+ loadedHeight,
117
+ loadedWidth,
118
+ element,
119
+ index,
120
+ photo,
121
+ open,
122
+ active,
123
+ closing,
124
+ loadingTimer,
125
+ publicMethod,
126
+ div = "div",
127
+ className,
128
+ requests = 0,
129
+ init;
130
+
131
+ // ****************
132
+ // HELPER FUNCTIONS
133
+ // ****************
134
+
135
+ // Convenience function for creating new jQuery objects
136
+ function $tag(tag, id, css) {
137
+ var element = document.createElement(tag);
138
+
139
+ if (id) {
140
+ element.id = prefix + id;
141
+ }
142
+
143
+ if (css) {
144
+ element.style.cssText = css;
145
+ }
146
+
147
+ return $(element);
148
+ }
149
+
150
+ // Get the window height using innerHeight when available to avoid an issue with iOS
151
+ // http://bugs.jquery.com/ticket/6724
152
+ function winheight() {
153
+ return window.innerHeight ? window.innerHeight : $(window).height();
154
+ }
155
+
156
+ // Determine the next and previous members in a group.
157
+ function getIndex(increment) {
158
+ var
159
+ max = $related.length,
160
+ newIndex = (index + increment) % max;
161
+
162
+ return (newIndex < 0) ? max + newIndex : newIndex;
163
+ }
164
+
165
+ // Convert '%' and 'px' values to integers
166
+ function setSize(size, dimension) {
167
+ return Math.round((/%/.test(size) ? ((dimension === 'x' ? $window.width() : winheight()) / 100) : 1) * parseInt(size, 10));
168
+ }
169
+
170
+ // Checks an href to see if it is a photo.
171
+ // There is a force photo option (photo: true) for hrefs that cannot be matched by the regex.
172
+ function isImage(settings, url) {
173
+ return settings.photo || settings.photoRegex.test(url);
174
+ }
175
+
176
+ function retinaUrl(settings, url) {
177
+ return settings.retinaUrl && window.devicePixelRatio > 1 ? url.replace(settings.photoRegex, settings.retinaSuffix) : url;
178
+ }
179
+
180
+ function trapFocus(e) {
181
+ if ('contains' in $box[0] && !$box[0].contains(e.target)) {
182
+ e.stopPropagation();
183
+ $box.focus();
184
+ }
185
+ }
186
+
187
+ // Assigns function results to their respective properties
188
+ function makeSettings() {
189
+ var i,
190
+ data = $.data(element, colorbox);
191
+
192
+ if (data == null) {
193
+ settings = $.extend({}, defaults);
194
+ if (console && console.log) {
195
+ console.log('Error: cboxElement missing settings object');
196
+ }
197
+ } else {
198
+ settings = $.extend({}, data);
199
+ }
200
+
201
+ for (i in settings) {
202
+ if ($.isFunction(settings[i]) && i.slice(0, 2) !== 'on') { // checks to make sure the function isn't one of the callbacks, they will be handled at the appropriate time.
203
+ settings[i] = settings[i].call(element);
204
+ }
205
+ }
206
+
207
+ settings.rel = settings.rel || element.rel || $(element).data('rel') || 'nofollow';
208
+ settings.href = settings.href || $(element).attr('href');
209
+ settings.title = settings.title || element.title;
210
+
211
+ if (typeof settings.href === "string") {
212
+ settings.href = $.trim(settings.href);
213
+ }
214
+ }
215
+
216
+ function trigger(event, callback) {
217
+ // for external use
218
+ $(document).trigger(event);
219
+
220
+ // for internal use
221
+ $events.trigger(event);
222
+
223
+ if ($.isFunction(callback)) {
224
+ callback.call(element);
225
+ }
226
+ }
227
+
228
+ // Slideshow functionality
229
+ function slideshow() {
230
+ var
231
+ timeOut,
232
+ className = prefix + "Slideshow_",
233
+ click = "click." + prefix,
234
+ clear,
235
+ set,
236
+ start,
237
+ stop;
238
+
239
+ if (settings.slideshow && $related[1]) {
240
+ clear = function () {
241
+ clearTimeout(timeOut);
242
+ };
243
+
244
+ set = function () {
245
+ if (settings.loop || $related[index + 1]) {
246
+ timeOut = setTimeout(publicMethod.next, settings.slideshowSpeed);
247
+ }
248
+ };
249
+
250
+ start = function () {
251
+ $slideshow
252
+ .html(settings.slideshowStop)
253
+ .unbind(click)
254
+ .one(click, stop);
255
+
256
+ $events
257
+ .bind(event_complete, set)
258
+ .bind(event_load, clear)
259
+ .bind(event_cleanup, stop);
260
+
261
+ $box.removeClass(className + "off").addClass(className + "on");
262
+ };
263
+
264
+ stop = function () {
265
+ clear();
266
+
267
+ $events
268
+ .unbind(event_complete, set)
269
+ .unbind(event_load, clear)
270
+ .unbind(event_cleanup, stop);
271
+
272
+ $slideshow
273
+ .html(settings.slideshowStart)
274
+ .unbind(click)
275
+ .one(click, function () {
276
+ publicMethod.next();
277
+ start();
278
+ });
279
+
280
+ $box.removeClass(className + "on").addClass(className + "off");
281
+ };
282
+
283
+ if (settings.slideshowAuto) {
284
+ start();
285
+ } else {
286
+ stop();
287
+ }
288
+ } else {
289
+ $box.removeClass(className + "off " + className + "on");
290
+ }
291
+ }
292
+
293
+ function launch(target) {
294
+ if (!closing) {
295
+
296
+ element = target;
297
+
298
+ makeSettings();
299
+
300
+ $related = $(element);
301
+
302
+ index = 0;
303
+
304
+ if (settings.rel !== 'nofollow') {
305
+ $related = $('.' + boxElement).filter(function () {
306
+ var data = $.data(this, colorbox),
307
+ relRelated;
308
+
309
+ if (data) {
310
+ relRelated = $(this).data('rel') || data.rel || this.rel;
311
+ }
312
+
313
+ return (relRelated === settings.rel);
314
+ });
315
+ index = $related.index(element);
316
+
317
+ // Check direct calls to Colorbox.
318
+ if (index === -1) {
319
+ $related = $related.add(element);
320
+ index = $related.length - 1;
321
+ }
322
+ }
323
+
324
+ $overlay.css({
325
+ opacity: parseFloat(settings.opacity),
326
+ cursor: settings.overlayClose ? "pointer" : "auto",
327
+ visibility: 'visible'
328
+ }).show();
329
+
330
+
331
+ if (className) {
332
+ $box.add($overlay).removeClass(className);
333
+ }
334
+ if (settings.className) {
335
+ $box.add($overlay).addClass(settings.className);
336
+ }
337
+ className = settings.className;
338
+
339
+ $close.html(settings.close).show();
340
+
341
+ if (!open) {
342
+ open = active = true; // Prevents the page-change action from queuing up if the visitor holds down the left or right keys.
343
+
344
+ // Show colorbox so the sizes can be calculated in older versions of jQuery
345
+ $box.css({visibility:'hidden', display:'block'});
346
+
347
+ $loaded = $tag(div, 'LoadedContent', 'width:0; height:0; overflow:hidden').appendTo($content);
348
+
349
+ // Cache values needed for size calculations
350
+ interfaceHeight = $topBorder.height() + $bottomBorder.height() + $content.outerHeight(true) - $content.height();
351
+ interfaceWidth = $leftBorder.width() + $rightBorder.width() + $content.outerWidth(true) - $content.width();
352
+ loadedHeight = $loaded.outerHeight(true);
353
+ loadedWidth = $loaded.outerWidth(true);
354
+
355
+
356
+ // Opens inital empty Colorbox prior to content being loaded.
357
+ settings.w = setSize(settings.initialWidth, 'x');
358
+ settings.h = setSize(settings.initialHeight, 'y');
359
+ publicMethod.position();
360
+
361
+ slideshow();
362
+
363
+ trigger(event_open, settings.onOpen);
364
+
365
+ $groupControls.add($title).hide();
366
+
367
+ $box.focus();
368
+
369
+ // Confine focus to the modal
370
+ // Uses event capturing that is not supported in IE8-
371
+ if (document.addEventListener) {
372
+
373
+ document.addEventListener('focus', trapFocus, true);
374
+
375
+ $events.one(event_closed, function () {
376
+ document.removeEventListener('focus', trapFocus, true);
377
+ });
378
+ }
379
+
380
+ // Return focus on closing
381
+ if (settings.returnFocus) {
382
+ $events.one(event_closed, function () {
383
+ $(element).focus();
384
+ });
385
+ }
386
+ }
387
+
388
+ load();
389
+ }
390
+ }
391
+
392
+ // Colorbox's markup needs to be added to the DOM prior to being called
393
+ // so that the browser will go ahead and load the CSS background images.
394
+ function appendHTML() {
395
+ if (!$box && document.body) {
396
+ init = false;
397
+ $window = $(window);
398
+ $box = $tag(div).attr({
399
+ id: colorbox,
400
+ 'class': $.support.opacity === false ? prefix + 'IE' : '', // class for optional IE8 & lower targeted CSS.
401
+ role: 'dialog',
402
+ tabindex: '-1'
403
+ }).hide();
404
+ $overlay = $tag(div, "Overlay").hide();
405
+ $loadingOverlay = $tag(div, "LoadingOverlay").add($tag(div, "LoadingGraphic"));
406
+ $wrap = $tag(div, "Wrapper");
407
+ $content = $tag(div, "Content").append(
408
+ $title = $tag(div, "Title"),
409
+ $current = $tag(div, "Current"),
410
+ $prev = $('<button type="button"/>').attr({id:prefix+'Previous'}),
411
+ $next = $('<button type="button"/>').attr({id:prefix+'Next'}),
412
+ $slideshow = $tag('button', "Slideshow"),
413
+ $loadingOverlay,
414
+ $close = $('<button type="button"/>').attr({id:prefix+'Close'})
415
+ );
416
+
417
+ $wrap.append( // The 3x3 Grid that makes up Colorbox
418
+ $tag(div).append(
419
+ $tag(div, "TopLeft"),
420
+ $topBorder = $tag(div, "TopCenter"),
421
+ $tag(div, "TopRight")
422
+ ),
423
+ $tag(div, false, 'clear:left').append(
424
+ $leftBorder = $tag(div, "MiddleLeft"),
425
+ $content,
426
+ $rightBorder = $tag(div, "MiddleRight")
427
+ ),
428
+ $tag(div, false, 'clear:left').append(
429
+ $tag(div, "BottomLeft"),
430
+ $bottomBorder = $tag(div, "BottomCenter"),
431
+ $tag(div, "BottomRight")
432
+ )
433
+ ).find('div div').css({'float': 'left'});
434
+
435
+ $loadingBay = $tag(div, false, 'position:absolute; width:9999px; visibility:hidden; display:none');
436
+
437
+ $groupControls = $next.add($prev).add($current).add($slideshow);
438
+
439
+ $(document.body).append($overlay, $box.append($wrap, $loadingBay));
440
+ }
441
+ }
442
+
443
+ // Add Colorbox's event bindings
444
+ function addBindings() {
445
+ function clickHandler(e) {
446
+ // ignore non-left-mouse-clicks and clicks modified with ctrl / command, shift, or alt.
447
+ // See: http://jacklmoore.com/notes/click-events/
448
+ if (!(e.which > 1 || e.shiftKey || e.altKey || e.metaKey || e.control)) {
449
+ e.preventDefault();
450
+ launch(this);
451
+ }
452
+ }
453
+
454
+ if ($box) {
455
+ if (!init) {
456
+ init = true;
457
+
458
+ // Anonymous functions here keep the public method from being cached, thereby allowing them to be redefined on the fly.
459
+ $next.click(function () {
460
+ publicMethod.next();
461
+ });
462
+ $prev.click(function () {
463
+ publicMethod.prev();
464
+ });
465
+ $close.click(function () {
466
+ publicMethod.close();
467
+ });
468
+ $overlay.click(function () {
469
+ if (settings.overlayClose) {
470
+ publicMethod.close();
471
+ }
472
+ });
473
+
474
+ // Key Bindings
475
+ $(document).bind('keydown.' + prefix, function (e) {
476
+ var key = e.keyCode;
477
+ if (open && settings.escKey && key === 27) {
478
+ e.preventDefault();
479
+ publicMethod.close();
480
+ }
481
+ if (open && settings.arrowKey && $related[1] && !e.altKey) {
482
+ if (key === 37) {
483
+ e.preventDefault();
484
+ $prev.click();
485
+ } else if (key === 39) {
486
+ e.preventDefault();
487
+ $next.click();
488
+ }
489
+ }
490
+ });
491
+
492
+ if ($.isFunction($.fn.on)) {
493
+ // For jQuery 1.7+
494
+ $(document).on('click.'+prefix, '.'+boxElement, clickHandler);
495
+ } else {
496
+ // For jQuery 1.3.x -> 1.6.x
497
+ // This code is never reached in jQuery 1.9, so do not contact me about 'live' being removed.
498
+ // This is not here for jQuery 1.9, it's here for legacy users.
499
+ $('.'+boxElement).live('click.'+prefix, clickHandler);
500
+ }
501
+ }
502
+ return true;
503
+ }
504
+ return false;
505
+ }
506
+
507
+ // Don't do anything if Colorbox already exists.
508
+ if ($.colorbox) {
509
+ return;
510
+ }
511
+
512
+ // Append the HTML when the DOM loads
513
+ $(appendHTML);
514
+
515
+
516
+ // ****************
517
+ // PUBLIC FUNCTIONS
518
+ // Usage format: $.colorbox.close();
519
+ // Usage from within an iframe: parent.jQuery.colorbox.close();
520
+ // ****************
521
+
522
+ publicMethod = $.fn[colorbox] = $[colorbox] = function (options, callback) {
523
+ var $this = this;
524
+
525
+ options = options || {};
526
+
527
+ appendHTML();
528
+
529
+ if (addBindings()) {
530
+ if ($.isFunction($this)) { // assume a call to $.colorbox
531
+ $this = $('<a/>');
532
+ options.open = true;
533
+ } else if (!$this[0]) { // colorbox being applied to empty collection
534
+ return $this;
535
+ }
536
+
537
+ if (callback) {
538
+ options.onComplete = callback;
539
+ }
540
+
541
+ $this.each(function () {
542
+ $.data(this, colorbox, $.extend({}, $.data(this, colorbox) || defaults, options));
543
+ }).addClass(boxElement);
544
+
545
+ if (($.isFunction(options.open) && options.open.call($this)) || options.open) {
546
+ launch($this[0]);
547
+ }
548
+ }
549
+
550
+ return $this;
551
+ };
552
+
553
+ publicMethod.position = function (speed, loadedCallback) {
554
+ var
555
+ css,
556
+ top = 0,
557
+ left = 0,
558
+ offset = $box.offset(),
559
+ scrollTop,
560
+ scrollLeft;
561
+
562
+ $window.unbind('resize.' + prefix);
563
+
564
+ // remove the modal so that it doesn't influence the document width/height
565
+ $box.css({top: -9e4, left: -9e4});
566
+
567
+ scrollTop = $window.scrollTop();
568
+ scrollLeft = $window.scrollLeft();
569
+
570
+ if (settings.fixed) {
571
+ offset.top -= scrollTop;
572
+ offset.left -= scrollLeft;
573
+ $box.css({position: 'fixed'});
574
+ } else {
575
+ top = scrollTop;
576
+ left = scrollLeft;
577
+ $box.css({position: 'absolute'});
578
+ }
579
+
580
+ // keeps the top and left positions within the browser's viewport.
581
+ if (settings.right !== false) {
582
+ left += Math.max($window.width() - settings.w - loadedWidth - interfaceWidth - setSize(settings.right, 'x'), 0);
583
+ } else if (settings.left !== false) {
584
+ left += setSize(settings.left, 'x');
585
+ } else {
586
+ left += Math.round(Math.max($window.width() - settings.w - loadedWidth - interfaceWidth, 0) / 2);
587
+ }
588
+
589
+ if (settings.bottom !== false) {
590
+ top += Math.max(winheight() - settings.h - loadedHeight - interfaceHeight - setSize(settings.bottom, 'y'), 0);
591
+ } else if (settings.top !== false) {
592
+ top += setSize(settings.top, 'y');
593
+ } else {
594
+ top += Math.round(Math.max(winheight() - settings.h - loadedHeight - interfaceHeight, 0) / 2);
595
+ }
596
+
597
+ $box.css({top: offset.top, left: offset.left, visibility:'visible'});
598
+
599
+ // setting the speed to 0 to reduce the delay between same-sized content.
600
+ speed = ($box.width() === settings.w + loadedWidth && $box.height() === settings.h + loadedHeight) ? 0 : speed || 0;
601
+
602
+ // this gives the wrapper plenty of breathing room so it's floated contents can move around smoothly,
603
+ // but it has to be shrank down around the size of div#colorbox when it's done. If not,
604
+ // it can invoke an obscure IE bug when using iframes.
605
+ $wrap[0].style.width = $wrap[0].style.height = "9999px";
606
+
607
+ function modalDimensions(that) {
608
+ $topBorder[0].style.width = $bottomBorder[0].style.width = $content[0].style.width = (parseInt(that.style.width,10) - interfaceWidth)+'px';
609
+ $content[0].style.height = $leftBorder[0].style.height = $rightBorder[0].style.height = (parseInt(that.style.height,10) - interfaceHeight)+'px';
610
+ }
611
+
612
+ css = {width: settings.w + loadedWidth + interfaceWidth, height: settings.h + loadedHeight + interfaceHeight, top: top, left: left};
613
+
614
+ if(speed===0){ // temporary workaround to side-step jQuery-UI 1.8 bug (http://bugs.jquery.com/ticket/12273)
615
+ $box.css(css);
616
+ }
617
+ $box.dequeue().animate(css, {
618
+ duration: speed,
619
+ complete: function () {
620
+ modalDimensions(this);
621
+
622
+ active = false;
623
+
624
+ // shrink the wrapper down to exactly the size of colorbox to avoid a bug in IE's iframe implementation.
625
+ $wrap[0].style.width = (settings.w + loadedWidth + interfaceWidth) + "px";
626
+ $wrap[0].style.height = (settings.h + loadedHeight + interfaceHeight) + "px";
627
+
628
+ if (settings.reposition) {
629
+ setTimeout(function () { // small delay before binding onresize due to an IE8 bug.
630
+ $window.bind('resize.' + prefix, publicMethod.position);
631
+ }, 1);
632
+ }
633
+
634
+ if (loadedCallback) {
635
+ loadedCallback();
636
+ }
637
+ },
638
+ step: function () {
639
+ modalDimensions(this);
640
+ }
641
+ });
642
+ };
643
+
644
+ publicMethod.resize = function (options) {
645
+ if (open) {
646
+ options = options || {};
647
+
648
+ if (options.width) {
649
+ settings.w = setSize(options.width, 'x') - loadedWidth - interfaceWidth;
650
+ }
651
+ if (options.innerWidth) {
652
+ settings.w = setSize(options.innerWidth, 'x');
653
+ }
654
+ $loaded.css({width: settings.w});
655
+
656
+ if (options.height) {
657
+ settings.h = setSize(options.height, 'y') - loadedHeight - interfaceHeight;
658
+ }
659
+ if (options.innerHeight) {
660
+ settings.h = setSize(options.innerHeight, 'y');
661
+ }
662
+ if (!options.innerHeight && !options.height) {
663
+ $loaded.css({height: "auto"});
664
+ settings.h = $loaded.height();
665
+ }
666
+ $loaded.css({height: settings.h});
667
+
668
+ publicMethod.position(settings.transition === "none" ? 0 : settings.speed);
669
+ }
670
+ };
671
+
672
+ publicMethod.prep = function (object) {
673
+ if (!open) {
674
+ return;
675
+ }
676
+
677
+ var callback, speed = settings.transition === "none" ? 0 : settings.speed;
678
+
679
+ $loaded.empty().remove(); // Using empty first may prevent some IE7 issues.
680
+
681
+ $loaded = $tag(div, 'LoadedContent').append(object);
682
+
683
+ function getWidth() {
684
+ settings.w = settings.w || $loaded.width();
685
+ settings.w = settings.mw && settings.mw < settings.w ? settings.mw : settings.w;
686
+ return settings.w;
687
+ }
688
+ function getHeight() {
689
+ settings.h = settings.h || $loaded.height();
690
+ settings.h = settings.mh && settings.mh < settings.h ? settings.mh : settings.h;
691
+ return settings.h;
692
+ }
693
+
694
+ $loaded.hide()
695
+ .appendTo($loadingBay.show())// content has to be appended to the DOM for accurate size calculations.
696
+ .css({width: getWidth(), overflow: settings.scrolling ? 'auto' : 'hidden'})
697
+ .css({height: getHeight()})// sets the height independently from the width in case the new width influences the value of height.
698
+ .prependTo($content);
699
+
700
+ $loadingBay.hide();
701
+
702
+ // floating the IMG removes the bottom line-height and fixed a problem where IE miscalculates the width of the parent element as 100% of the document width.
703
+
704
+ $(photo).css({'float': 'none'});
705
+
706
+ callback = function () {
707
+ var total = $related.length,
708
+ iframe,
709
+ frameBorder = 'frameBorder',
710
+ allowTransparency = 'allowTransparency',
711
+ complete;
712
+
713
+ if (!open) {
714
+ return;
715
+ }
716
+
717
+ function removeFilter() { // Needed for IE7 & IE8 in versions of jQuery prior to 1.7.2
718
+ if ($.support.opacity === false) {
719
+ $box[0].style.removeAttribute('filter');
720
+ }
721
+ }
722
+
723
+ complete = function () {
724
+ clearTimeout(loadingTimer);
725
+ $loadingOverlay.hide();
726
+ trigger(event_complete, settings.onComplete);
727
+ };
728
+
729
+
730
+ $title.html(settings.title).add($loaded).show();
731
+
732
+ if (total > 1) { // handle grouping
733
+ if (typeof settings.current === "string") {
734
+ $current.html(settings.current.replace('{current}', index + 1).replace('{total}', total)).show();
735
+ }
736
+
737
+ $next[(settings.loop || index < total - 1) ? "show" : "hide"]().html(settings.next);
738
+ $prev[(settings.loop || index) ? "show" : "hide"]().html(settings.previous);
739
+
740
+ if (settings.slideshow) {
741
+ $slideshow.show();
742
+ }
743
+
744
+ // Preloads images within a rel group
745
+ if (settings.preloading) {
746
+ $.each([getIndex(-1), getIndex(1)], function(){
747
+ var src,
748
+ img,
749
+ i = $related[this],
750
+ data = $.data(i, colorbox);
751
+
752
+ if (data && data.href) {
753
+ src = data.href;
754
+ if ($.isFunction(src)) {
755
+ src = src.call(i);
756
+ }
757
+ } else {
758
+ src = $(i).attr('href');
759
+ }
760
+
761
+ if (src && isImage(data, src)) {
762
+ src = retinaUrl(data, src);
763
+ img = new Image();
764
+ img.src = src;
765
+ }
766
+ });
767
+ }
768
+ } else {
769
+ $groupControls.hide();
770
+ }
771
+
772
+ if (settings.iframe) {
773
+ iframe = $tag('iframe')[0];
774
+
775
+ if (frameBorder in iframe) {
776
+ iframe[frameBorder] = 0;
777
+ }
778
+
779
+ if (allowTransparency in iframe) {
780
+ iframe[allowTransparency] = "true";
781
+ }
782
+
783
+ if (!settings.scrolling) {
784
+ iframe.scrolling = "no";
785
+ }
786
+
787
+ $(iframe)
788
+ .attr({
789
+ src: settings.href,
790
+ name: (new Date()).getTime(), // give the iframe a unique name to prevent caching
791
+ 'class': prefix + 'Iframe',
792
+ allowFullScreen : true, // allow HTML5 video to go fullscreen
793
+ webkitAllowFullScreen : true,
794
+ mozallowfullscreen : true
795
+ })
796
+ .one('load', complete)
797
+ .appendTo($loaded);
798
+
799
+ $events.one(event_purge, function () {
800
+ iframe.src = "//about:blank";
801
+ });
802
+
803
+ if (settings.fastIframe) {
804
+ $(iframe).trigger('load');
805
+ }
806
+ } else {
807
+ complete();
808
+ }
809
+
810
+ if (settings.transition === 'fade') {
811
+ $box.fadeTo(speed, 1, removeFilter);
812
+ } else {
813
+ removeFilter();
814
+ }
815
+ };
816
+
817
+ if (settings.transition === 'fade') {
818
+ $box.fadeTo(speed, 0, function () {
819
+ publicMethod.position(0, callback);
820
+ });
821
+ } else {
822
+ publicMethod.position(speed, callback);
823
+ }
824
+ };
825
+
826
+ function load () {
827
+ var href, setResize, prep = publicMethod.prep, $inline, request = ++requests;
828
+
829
+ active = true;
830
+
831
+ photo = false;
832
+
833
+ element = $related[index];
834
+
835
+ makeSettings();
836
+
837
+ trigger(event_purge);
838
+
839
+ trigger(event_load, settings.onLoad);
840
+
841
+ settings.h = settings.height ?
842
+ setSize(settings.height, 'y') - loadedHeight - interfaceHeight :
843
+ settings.innerHeight && setSize(settings.innerHeight, 'y');
844
+
845
+ settings.w = settings.width ?
846
+ setSize(settings.width, 'x') - loadedWidth - interfaceWidth :
847
+ settings.innerWidth && setSize(settings.innerWidth, 'x');
848
+
849
+ // Sets the minimum dimensions for use in image scaling
850
+ settings.mw = settings.w;
851
+ settings.mh = settings.h;
852
+
853
+ // Re-evaluate the minimum width and height based on maxWidth and maxHeight values.
854
+ // If the width or height exceed the maxWidth or maxHeight, use the maximum values instead.
855
+ if (settings.maxWidth) {
856
+ settings.mw = setSize(settings.maxWidth, 'x') - loadedWidth - interfaceWidth;
857
+ settings.mw = settings.w && settings.w < settings.mw ? settings.w : settings.mw;
858
+ }
859
+ if (settings.maxHeight) {
860
+ settings.mh = setSize(settings.maxHeight, 'y') - loadedHeight - interfaceHeight;
861
+ settings.mh = settings.h && settings.h < settings.mh ? settings.h : settings.mh;
862
+ }
863
+
864
+ href = settings.href;
865
+
866
+ loadingTimer = setTimeout(function () {
867
+ $loadingOverlay.show();
868
+ }, 100);
869
+
870
+ if (settings.inline) {
871
+ // Inserts an empty placeholder where inline content is being pulled from.
872
+ // An event is bound to put inline content back when Colorbox closes or loads new content.
873
+ $inline = $tag(div).hide().insertBefore($(href)[0]);
874
+
875
+ $events.one(event_purge, function () {
876
+ $inline.replaceWith($loaded.children());
877
+ });
878
+
879
+ prep($(href));
880
+ } else if (settings.iframe) {
881
+ // IFrame element won't be added to the DOM until it is ready to be displayed,
882
+ // to avoid problems with DOM-ready JS that might be trying to run in that iframe.
883
+ prep(" ");
884
+ } else if (settings.html) {
885
+ prep(settings.html);
886
+ } else if (isImage(settings, href)) {
887
+
888
+ href = retinaUrl(settings, href);
889
+
890
+ $(photo = new Image())
891
+ .addClass(prefix + 'Photo')
892
+ .bind('error',function () {
893
+ settings.title = false;
894
+ prep($tag(div, 'Error').html(settings.imgError));
895
+ })
896
+ .one('load', function () {
897
+ var percent;
898
+
899
+ if (request !== requests) {
900
+ return;
901
+ }
902
+
903
+ photo.alt = $(element).attr('alt') || $(element).attr('data-alt') || '';
904
+
905
+ if (settings.retinaImage && window.devicePixelRatio > 1) {
906
+ photo.height = photo.height / window.devicePixelRatio;
907
+ photo.width = photo.width / window.devicePixelRatio;
908
+ }
909
+
910
+ if (settings.scalePhotos) {
911
+ setResize = function () {
912
+ photo.height -= photo.height * percent;
913
+ photo.width -= photo.width * percent;
914
+ };
915
+ if (settings.mw && photo.width > settings.mw) {
916
+ percent = (photo.width - settings.mw) / photo.width;
917
+ setResize();
918
+ }
919
+ if (settings.mh && photo.height > settings.mh) {
920
+ percent = (photo.height - settings.mh) / photo.height;
921
+ setResize();
922
+ }
923
+ }
924
+
925
+ if (settings.h) {
926
+ photo.style.marginTop = Math.max(settings.mh - photo.height, 0) / 2 + 'px';
927
+ }
928
+
929
+ if ($related[1] && (settings.loop || $related[index + 1])) {
930
+ photo.style.cursor = 'pointer';
931
+ photo.onclick = function () {
932
+ publicMethod.next();
933
+ };
934
+ }
935
+
936
+ photo.style.width = photo.width + 'px';
937
+ photo.style.height = photo.height + 'px';
938
+
939
+ setTimeout(function () { // A pause because Chrome will sometimes report a 0 by 0 size otherwise.
940
+ prep(photo);
941
+ }, 1);
942
+ });
943
+
944
+ setTimeout(function () { // A pause because Opera 10.6+ will sometimes not run the onload function otherwise.
945
+ photo.src = href;
946
+ }, 1);
947
+ } else if (href) {
948
+ $loadingBay.load(href, settings.data, function (data, status) {
949
+ if (request === requests) {
950
+ prep(status === 'error' ? $tag(div, 'Error').html(settings.xhrError) : $(this).contents());
951
+ }
952
+ });
953
+ }
954
+ }
955
+
956
+ // Navigates to the next page/image in a set.
957
+ publicMethod.next = function () {
958
+ if (!active && $related[1] && (settings.loop || $related[index + 1])) {
959
+ index = getIndex(1);
960
+ launch($related[index]);
961
+ }
962
+ };
963
+
964
+ publicMethod.prev = function () {
965
+ if (!active && $related[1] && (settings.loop || index)) {
966
+ index = getIndex(-1);
967
+ launch($related[index]);
968
+ }
969
+ };
970
+
971
+ // Note: to use this within an iframe use the following format: parent.jQuery.colorbox.close();
972
+ publicMethod.close = function () {
973
+ if (open && !closing) {
974
+
975
+ closing = true;
976
+
977
+ open = false;
978
+
979
+ trigger(event_cleanup, settings.onCleanup);
980
+
981
+ $window.unbind('.' + prefix);
982
+
983
+ $overlay.fadeTo(settings.fadeOut || 0, 0);
984
+
985
+ $box.stop().fadeTo(settings.fadeOut || 0, 0, function () {
986
+
987
+ $box.add($overlay).css({'opacity': 1, cursor: 'auto'}).hide();
988
+
989
+ trigger(event_purge);
990
+
991
+ $loaded.empty().remove(); // Using empty first may prevent some IE7 issues.
992
+
993
+ setTimeout(function () {
994
+ closing = false;
995
+ trigger(event_closed, settings.onClosed);
996
+ }, 1);
997
+ });
998
+ }
999
+ };
1000
+
1001
+ // Removes changes Colorbox made to the document, but does not remove the plugin.
1002
+ publicMethod.remove = function () {
1003
+ if (!$box) { return; }
1004
+
1005
+ $box.stop();
1006
+ $.colorbox.close();
1007
+ $box.stop().remove();
1008
+ $overlay.remove();
1009
+ closing = false;
1010
+ $box = null;
1011
+ $('.' + boxElement)
1012
+ .removeData(colorbox)
1013
+ .removeClass(boxElement);
1014
+
1015
+ $(document).unbind('click.'+prefix);
1016
+ };
1017
+
1018
+ // A method for fetching the current element Colorbox is referencing.
1019
+ // returns a jQuery object.
1020
+ publicMethod.element = function () {
1021
+ return $(element);
1022
+ };
1023
+
1024
+ publicMethod.settings = defaults;
1025
+
1026
+ }(jQuery, document, window));