ajax 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.rdoc +291 -0
  3. data/Rakefile +49 -0
  4. data/VERSION +1 -0
  5. data/app/controllers/ajax_controller.rb +3 -0
  6. data/app/views/ajax/framework.html.erb +7 -0
  7. data/config/initializers/ajax.rb +14 -0
  8. data/lib/ajax.rb +79 -0
  9. data/lib/ajax/action_controller.rb +154 -0
  10. data/lib/ajax/action_view.rb +56 -0
  11. data/lib/ajax/helpers.rb +15 -0
  12. data/lib/ajax/helpers/request_helper.rb +76 -0
  13. data/lib/ajax/helpers/robot_helper.rb +31 -0
  14. data/lib/ajax/helpers/url_helper.rb +47 -0
  15. data/lib/ajax/railtie.rb +7 -0
  16. data/lib/ajax/routes.rb +12 -0
  17. data/lib/ajax/spec/extension.rb +34 -0
  18. data/lib/ajax/spec/helpers.rb +95 -0
  19. data/lib/ajax/tasks.rb +1 -0
  20. data/lib/rack-ajax.rb +60 -0
  21. data/lib/rack-ajax/decision_tree.rb +60 -0
  22. data/lib/rack-ajax/parser.rb +115 -0
  23. data/public/images/loading-icon-large.gif +0 -0
  24. data/public/images/loading-icon-small.gif +0 -0
  25. data/public/javascripts/ajax.js +529 -0
  26. data/public/javascripts/jquery.address-1.1.js +450 -0
  27. data/public/javascripts/jquery.address-1.1.min.js +11 -0
  28. data/public/javascripts/jquery.address-1.2.js +528 -0
  29. data/public/javascripts/jquery.address-1.2.min.js +25 -0
  30. data/public/javascripts/jquery.address-1.2rc.js +599 -0
  31. data/public/javascripts/jquery.address-1.2rc.min.js +27 -0
  32. data/public/javascripts/jquery.json-2.2.js +178 -0
  33. data/public/javascripts/jquery.json-2.2.min.js +31 -0
  34. data/rails/init.rb +4 -0
  35. data/rails/install.rb +23 -0
  36. data/rails/uninstall.rb +1 -0
  37. data/spec/ajax/helpers_spec.rb +102 -0
  38. data/spec/ajax/request_helper_spec.rb +33 -0
  39. data/spec/integration/ajax_spec.rb +146 -0
  40. data/spec/rack-ajax/parser_spec.rb +62 -0
  41. data/spec/spec.opts +1 -0
  42. data/spec/spec_helper.rb +18 -0
  43. data/tasks/ajax_tasks.rake +15 -0
  44. metadata +106 -0
data/lib/ajax/tasks.rb ADDED
@@ -0,0 +1 @@
1
+ load(File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'tasks', 'ajax_tasks.rake')))
data/lib/rack-ajax.rb ADDED
@@ -0,0 +1,60 @@
1
+ require 'rack-ajax/decision_tree'
2
+ require 'rack-ajax/parser'
3
+ require 'json'
4
+
5
+ module Rack
6
+ class Ajax
7
+ extend Rack::Ajax::DecisionTree
8
+
9
+ cattr_accessor :decision_tree
10
+ attr_accessor :user, :request, :params
11
+
12
+ # If called with a block, executes that block as the "decision tree".
13
+ # This is useful when testing.
14
+ #
15
+ # To integrate Rack::Ajax into your app you should store the decision
16
+ # tree in a class-attribute <tt>decision_tree</tt>. This
17
+ # decision tree will be used unless a block is provided.
18
+ def initialize(app)
19
+ @app = app
20
+ @decision_tree = block_given? ? Proc.new : (self.class.decision_tree || self.class.default_decision_tree)
21
+ end
22
+
23
+ def call(env)
24
+ return @app.call(env) unless ::Ajax.is_enabled?
25
+
26
+ # Parse the Ajax-Info header
27
+ if env["HTTP_AJAX_INFO"].nil?
28
+ env["Ajax-Info"] = {}
29
+ elsif env["HTTP_AJAX_INFO"].is_a?(String)
30
+ env["Ajax-Info"] = (JSON.parse(env['HTTP_AJAX_INFO']) rescue {})
31
+ end
32
+
33
+ @parser = Parser.new(env)
34
+ rack_response = @parser.instance_eval(&@decision_tree)
35
+
36
+ # Clear the value of session[:redirected_to]
37
+ unless env['rack.session'].nil?
38
+ env['rack.session']['redirected_to'] = env['rack.session'][:redirected_to] = nil
39
+ end
40
+
41
+ # If we are testing our Rack::Ajax middleware, return
42
+ # a Rack response now rather than falling through
43
+ # to the application.
44
+ #
45
+ # To test rewrites, return a 200 response with
46
+ # the modified request environment encoded as Yaml.
47
+ #
48
+ # The Ajax::Spec::Helpers module includes a helper
49
+ # method to test the result of a rewrite.
50
+ if ::Ajax.is_mocked?
51
+ rack_response.nil? ? Rack::Ajax::Parser.rack_response(env.to_yaml) : rack_response
52
+ elsif !rack_response.nil?
53
+ rack_response
54
+ else
55
+ # Fallthrough to the app.
56
+ @app.call(env)
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,60 @@
1
+ module Rack
2
+ class Ajax
3
+ module DecisionTree
4
+
5
+ # Decision tree for Rack rewrites and redirects.
6
+ #
7
+ # To use your own decision tree set it on the <tt>Ajax</tt> instance with:
8
+ #
9
+ # Ajax.decision_tree = Proc.new do
10
+ # # your code
11
+ # end
12
+ #
13
+ # Note: User agents never send the hashed part of the URL, meaning some of
14
+ # the conditions below will never be true, but I've included them for
15
+ # completeness.
16
+ def default_decision_tree
17
+ @@default_decision_tree ||= Proc.new do
18
+ ::Ajax.logger.debug("[ajax] rack session #{@env['rack.session'].inspect}")
19
+ ::Ajax.logger.debug("[ajax] Ajax-Info #{@env['Ajax-Info'].inspect}")
20
+
21
+ if !::Ajax.exclude_path?(@env['PATH_INFO'] || @env['REQUEST_URI'])
22
+ if ajax_request?
23
+ if hashed_url? # the browser never sends the hashed part
24
+ rewrite_to_traditional_url_from_fragment
25
+ end
26
+ else
27
+ if url_is_root?
28
+ if hashed_url? # the browser never sends the hashed part
29
+ rewrite_to_traditional_url_from_fragment
30
+ elsif get_request? && !user_is_robot?
31
+ # When we render the framework we would like to show the
32
+ # page the user wants on the first request. If the
33
+ # session has a value for <tt>redirected_to</tt> then
34
+ # that page will be rendered.
35
+ if redirected_to = (@env['rack.session'][:redirected_to] || @env['rack.session']['redirected_to'])
36
+ redirected_to = ::Ajax.is_hashed_url?(redirected_to) ? ::Ajax.traditional_url_from_fragment(redirected_to) : redirected_to
37
+ ::Ajax.logger.debug("[ajax] showing #{redirected_to} instead of root_url")
38
+ rewrite(redirected_to)
39
+ else
40
+ rewrite_to_render_ajax_framework
41
+ end
42
+ end
43
+ else
44
+ if !user_is_robot?
45
+ if hashed_url? # will never be true
46
+ redirect_to_hashed_url_from_fragment
47
+ else
48
+ if get_request?
49
+ redirect_to_hashed_url_equivalent
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,115 @@
1
+ # The <tt>rewrite</tt> and <tt>redirect</tt> methods are terminal methods meaning
2
+ # that they return a Rack response or modify the Rack request.
3
+ #
4
+ # Return <tt>nil</tt> to allow the request to fall-through to the Application.
5
+ module Rack
6
+ class Ajax
7
+ class Parser
8
+
9
+ # Instantiate an ActionController::Request object to make it
10
+ # easier to introspect the headers.
11
+ def initialize(env)
12
+ @env = env
13
+ @request = ActionController::Request.new(env)
14
+ end
15
+
16
+ protected
17
+
18
+ def hashed_url?
19
+ @hashed_url ||= ::Ajax.is_hashed_url?(@env['REQUEST_URI'])
20
+ end
21
+
22
+ def ajax_request?
23
+ @request.xml_http_request?
24
+ end
25
+
26
+ def get_request?
27
+ @request.get?
28
+ end
29
+
30
+ def post_request?
31
+ @request.post?
32
+ end
33
+
34
+ def url_is_root?
35
+ @url_is_root ||= ::Ajax.url_is_root?(@env['PATH_INFO'])
36
+ end
37
+
38
+ # Return a boolean indicating if the request is from a robot.
39
+ #
40
+ # Inspect the headers first - if there are any - so we don't
41
+ # look in the database unneccessarily.
42
+ #
43
+ # Sets the result in a header {Ajax-Info}[user_is_robot] so we
44
+ # don't have to repeat this check in the application.
45
+ def user_is_robot?
46
+ return @user_is_robot if instance_variable_defined?(:@user_is_robot)
47
+ @user_is_robot =
48
+ if @request.user_agent.nil?
49
+ false
50
+ else
51
+ ::Ajax.is_robot?(@request.user_agent)
52
+ end
53
+ ::Ajax.set_header(@env, :robot, @user_is_robot)
54
+ @user_is_robot
55
+ end
56
+
57
+ def rewrite_to_traditional_url_from_fragment
58
+ rewrite(::Ajax.traditional_url_from_fragment(@env['REQUEST_URI']))
59
+ end
60
+
61
+ # Redirect to a hashed URL consisting of the fragment portion of the current URL.
62
+ # This is an edge case. What can theoretically happen is a user visits a
63
+ # bookmarked URL, then browses via AJAX and ends up with a URL like
64
+ # '/Beyonce#/Akon'. Redirect them to '/#/Akon'.
65
+ def redirect_to_hashed_url_from_fragment
66
+ r302(::Ajax.hashed_url_from_fragment(@env['REQUEST_URI']))
67
+ end
68
+
69
+ # Redirect to the hashed URL equivalent of the current traditional URL.
70
+ # The user has likely followed a traditional link or bookmark.
71
+ def redirect_to_hashed_url_equivalent
72
+ r302(::Ajax.hashed_url_from_traditional(@env['REQUEST_URI']))
73
+ end
74
+
75
+ def rewrite_to_render_ajax_framework
76
+ rewrite('/ajax/framework')
77
+ end
78
+
79
+ private
80
+
81
+ def r302(url)
82
+ rack_response('Redirecting...', 302, 'Location' => url)
83
+ end
84
+
85
+ def rewrite(interpreted_to)
86
+ @env['REQUEST_URI'] = interpreted_to
87
+ if q_index = interpreted_to.index('?')
88
+ @env['PATH_INFO'] = interpreted_to[0..q_index-1]
89
+ @env['QUERY_STRING'] = interpreted_to[q_index+1..interpreted_to.size-1]
90
+ else
91
+ @env['PATH_INFO'] = interpreted_to
92
+ @env['QUERY_STRING'] = ''
93
+ end
94
+
95
+ nil # fallthrough to app
96
+ end
97
+
98
+ # You can use this method during integration testing Rack::Ajax
99
+ # in your Rails app. If you don't return a proper Rack response
100
+ # during integration testing, ActiveSupport can't parse the
101
+ # response.
102
+ #
103
+ # If you're testing Rack without Rails you can return base types
104
+ # so you don't need this method.
105
+ def self.rack_response(msg, code=200, headers={})
106
+ headers.reverse_merge!({'Content-Type' => 'text/html'})
107
+ [code, headers, [msg.to_s]]
108
+ end
109
+
110
+ def rack_response(*args)
111
+ self.class.rack_response(*args)
112
+ end
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,529 @@
1
+ /**
2
+ * AjaxAssets
3
+ *
4
+ * A class representing an Array of assets. Call with an instance of
5
+ * Array which will be extended with special methods.
6
+ *
7
+ * Example: self.javascripts = new AjaxAssets([]);
8
+ *
9
+ * Once an asset is loaded, it is not loaded again. Pass with the
10
+ * following values:
11
+ *
12
+ * Ajax-Info{}
13
+ * assets{}
14
+ * javascripts []
15
+ * stylesheets []
16
+ *
17
+ */
18
+ var AjaxAssets = function(array, type) {
19
+ var DATA_URI_START = "<!--[if (!IE)|(gte IE 8)]><!-->";
20
+ var DATA_URI_END = "<!--<![endif]-->";
21
+ var MHTML_START = "<!--[if lte IE 7]>";
22
+ var MHTML_END = "<![endif]-->";
23
+
24
+ return jQuery.extend(array, {
25
+ /**
26
+ * Add an asset, but don't load it.
27
+ */
28
+ addAsset: function(path) {
29
+ this.push(this.sanitizePath(path));
30
+ },
31
+
32
+ /**
33
+ * Load and add an asset. The asset is loaded using the
34
+ * unsanitized path should you need to put something in the
35
+ * query string.
36
+ */
37
+ loadAsset: function(path) {
38
+ console.log('[ajax] loading', type, path);
39
+ this.push(this.sanitizePath(path));
40
+ this.appendScriptTag(path);
41
+ },
42
+
43
+ /**
44
+ * Return a boolean indicating whether an asset has
45
+ * already been loaded.
46
+ */
47
+ loadedAsset: function(path) {
48
+ path = this.sanitizePath(path);
49
+ for (var i=0; i < this.length; i++) {
50
+ if (this[i] == path) {
51
+ return true;
52
+ }
53
+ }
54
+ return false;
55
+ },
56
+
57
+ /**
58
+ * Remove query strings and otherwise cleanup paths
59
+ * before adding them.
60
+ */
61
+ sanitizePath: function(path) {
62
+ return path.replace(/\?.*/, '');
63
+ },
64
+
65
+ /**
66
+ * Supports debugging and references the script files as external resources
67
+ * rather than inline.
68
+ *
69
+ * @see http://stackoverflow.com/questions/690781/debugging-scripts-added-via-jquery-getscript-function
70
+ */
71
+ appendScriptTag: function(url, callback) {
72
+ if (type == 'js') {
73
+ var head = document.getElementsByTagName("head")[0];
74
+ var script = document.createElement("script");
75
+ script.src = url;
76
+ script.type = 'text/javascript'
77
+
78
+ { // Handle Script loading
79
+ var done = false;
80
+
81
+ // Attach handlers for all browsers
82
+ script.onload = script.onreadystatechange = function(){
83
+ if ( !done && (!this.readyState ||
84
+ this.readyState == "loaded" || this.readyState == "complete") ) {
85
+ done = true;
86
+ if (callback)
87
+ callback();
88
+
89
+ // Handle memory leak in IE
90
+ script.onload = script.onreadystatechange = null;
91
+ }
92
+ };
93
+ }
94
+ head.appendChild(script);
95
+ } else if (type == 'css') {
96
+ if (url.match(/datauri/)) {
97
+ $(DATA_URI_START + '<link type="text/css" rel="stylesheet" href="'+ url +'">' + DATA_URI_END).appendTo('head');
98
+ } else if (url.match(/mhtml/)) {
99
+ $(MHTML_START + '<link type="text/css" rel="stylesheet" href="'+ url +'">' + MHTML_END).appendTo('head');
100
+ } else {
101
+ $('<link type="text/css" rel="stylesheet" href="'+ url +'">').appendTo('head');
102
+ }
103
+ }
104
+ return undefined;
105
+ }
106
+ });
107
+ };
108
+
109
+ /**
110
+ * Class Ajax
111
+ *
112
+ * Options:
113
+ * <tt>enabled</tt> boolean indicating whether the plugin is enabled.
114
+ * This must be set if you are using Ajax callbacks in your code,
115
+ * and you want them to still fire if Ajax is not enabled.
116
+ *
117
+ * <tt>default_container</tt> string jQuery selector of the default
118
+ * container element to receive content.
119
+ *
120
+ * Callbacks:
121
+ *
122
+ * Callbacks can be specified using Ajax-Info{ callbacks: 'javascript to eval' },
123
+ * or by adding callbacks directly to the Ajax instance:
124
+ *
125
+ * window.ajax.onLoad(function() { doSomething(args); });
126
+ *
127
+ * Order of execution:
128
+ *
129
+ *
130
+ */
131
+ var Ajax = function(options) {
132
+ var self = this;
133
+
134
+ self.enabled = true;
135
+ self.default_container = undefined;
136
+ self.loaded_by_framework = false;
137
+ self.loading_icon = $('#loading-icon-small');
138
+ self.javascripts = undefined;
139
+ self.stylesheets = new AjaxAssets([], 'css');
140
+ self.callbacks = [];
141
+ self.loaded = false;
142
+
143
+ // For initial position of the loading icon. Often the mouse does not
144
+ // move so position it by the link that was clicked.
145
+ self.last_click_coords = undefined;
146
+
147
+ // Parse options
148
+ self.options = options;
149
+ self.default_container = options.default_container;
150
+ if (options.enabled !== undefined) {
151
+ self.enabled = options.enabled;
152
+ }
153
+
154
+ // Initialize on DOM ready
155
+ $(function() { self.init() });
156
+
157
+ /**
158
+ * Initializations run on DOM ready.
159
+ *
160
+ * Bind event handlers and setup jQuery Address.
161
+ */
162
+ self.init = function() {
163
+
164
+ // Configure jQuery Address
165
+ $.address.history(true);
166
+ $.address.change = self.addressChanged;
167
+
168
+ // Insert loading image
169
+ var image = '<img src="/images/loading-icon-small.gif" id="loading-icon-small" alt="Loading..." />'
170
+ $(image).hide().appendTo($('body'));
171
+
172
+ // Bind a live event to all ajax-enabled links
173
+ $('a[data-deep-link]').live('click', self.linkClicked);
174
+
175
+ // Initialize the list of javascript assets
176
+ if (self.javascripts === undefined) {
177
+ self.javascripts = new AjaxAssets([], 'js');
178
+
179
+ $(document).find('script[type=text/javascript][src!=]').each(function() {
180
+ var script = $(this);
181
+ var src = script.attr('src');
182
+
183
+ // Local scripts only
184
+ if (src.match(/^\//)) {
185
+
186
+ // Parse parameters passed to the script via the query string.
187
+ // TODO: Untested. It's difficult for us to use this with Jammit.
188
+ if (src.match(/\Wajax.js\?.+/)) {
189
+ var params = src.split('?')[1].split('&');
190
+ jQuery.each(params, function(idx, param) {
191
+ param = param.split('=');
192
+ if (param.length == 1) { return true; }
193
+
194
+ switch(param[0]) {
195
+ case 'enabled':
196
+ self.enabled = param[1] == 'false' ? false : true;
197
+ console.log('[ajax] set param enabled=', self.enabled);
198
+ break;
199
+ case 'default_container':
200
+ self.default_container = param[1];
201
+ console.log('[ajax] set param default_container=', self.default_container);
202
+ break;
203
+ }
204
+ });
205
+ }
206
+
207
+ self.javascripts.addAsset(script.attr('src'));
208
+ }
209
+ });
210
+ }
211
+ self.initialized = true;
212
+
213
+ // Run onInit() callbacks
214
+ };
215
+
216
+ /**
217
+ * jQuery Address callback triggered when the address changes.
218
+ */
219
+ self.addressChanged = function() {
220
+ if (document.location.pathname != '/') { return false; }
221
+
222
+ if (typeof(self.loaded_by_framework) == 'undefined' || self.loaded_by_framework != true) {
223
+ self.loaded_by_framework = true;
224
+ return false;
225
+ }
226
+
227
+ self.loadPage({
228
+ url: $.address.value().replace(/\/\//, '/')
229
+ });
230
+ return true;
231
+ };
232
+
233
+ /**
234
+ * loadPage
235
+ *
236
+ * Request new content and insert it into the document. If the response
237
+ * Ajax-Info header contains any of the following we take the associated
238
+ * action:
239
+ *
240
+ * [title] String, Set the page title
241
+ * [tab] jQuery selector, trigger the 'activate' event on the tab
242
+ * [container] The container to receive the content, or <tt>main</tt> by default.
243
+ * [assets] Assets to load
244
+ * [callback] Execute a callback after assets have loaded
245
+ *
246
+ * Cookies in the response are automatically set on the document.cookie.
247
+ */
248
+ self.loadPage = function(options) {
249
+ if (!self.enabled) {
250
+ document.location = options.url;
251
+ return true;
252
+ }
253
+ self.loaded = false;
254
+ self.showLoadingImage();
255
+
256
+ jQuery.ajax({
257
+ url: options.url,
258
+ method: options.method || 'GET',
259
+ beforeSend: self.setRequestHeaders,
260
+ success: self.responseHandler,
261
+ complete: function(XMLHttpRequest, responseText) {
262
+ // Stop watching the mouse position and scroll to the top of the page.
263
+ $(document).unbind('mousemove', self.updateImagePosition).scrollTop(0);
264
+ $('#loading-icon-small').hide();
265
+ self.loaded = true;
266
+ },
267
+ error: function(XMLHttpRequest, textStatus, errorThrown) {
268
+ var responseText = XMLHttpRequest.responseText;
269
+ self.responseHandler(responseText, textStatus, XMLHttpRequest);
270
+ }
271
+ });
272
+ };
273
+
274
+ /**
275
+ * setRequestHeaders
276
+ *
277
+ * Set the AJAX_INFO request header. This includes all the data
278
+ * defined on the main (or receiving) container, plus some other
279
+ * useful information like the:
280
+ *
281
+ * referer - the current document.location
282
+ *
283
+ */
284
+ self.setRequestHeaders = function(XMLHttpRequest) {
285
+ var data = $(self.default_container).data('ajax-info');
286
+ if (data === undefined || data === null) { data = {}; }
287
+ data['referer'] = document.location.href;
288
+ XMLHttpRequest.setRequestHeader('AJAX_INFO', $.toJSON(data));
289
+ };
290
+
291
+ /**
292
+ * linkClicked
293
+ *
294
+ * Called when the an AJAX-enabled link is clicked.
295
+ * Redirect back to the root URL if we are not on it.
296
+ *
297
+ */
298
+ self.linkClicked = function(event) {
299
+ if (document.location.pathname != '/') {
300
+ var url = $.address.baseURL().replace(new RegExp(document.location.pathname), '')
301
+ url += '/#/' + $(this).attr('data-deep-link');
302
+ url.replace(/\/\//, '/');
303
+ document.location = url;
304
+ } else {
305
+ self.last_click_coords = { pageX: event.pageX, pageY: event.pageY };
306
+ $.address.value($(this).attr('data-deep-link'));
307
+ }
308
+ return false;
309
+ };
310
+
311
+ /**
312
+ * responseHandler
313
+ *
314
+ * Process the response of an AJAX call and put the contents in
315
+ * the appropriate container, activate tabs etc.
316
+ *
317
+ */
318
+ self.responseHandler = function(responseText, textStatus, XMLHttpRequest) {
319
+ var data = self.processResponseHeaders(XMLHttpRequest);
320
+ var container = data.container === undefined ? $(self.default_container) : $(data.container);
321
+
322
+ // Redirect? Let the JS execute. It will set the new window location.
323
+ if (responseText && responseText.match(/try\s{\swindow\.location\.href/)) { return true; }
324
+
325
+ /**
326
+ * Extract the body
327
+ */
328
+ if (responseText.search(/<\s*body[^>]*>/) != -1) {
329
+ var start = responseText.search(/<\s*body[^>]*>/);
330
+ start += responseText.match(/<\s*body[^>]*>/)[0].length;
331
+ var end = responseText.search(/<\s*\/\s*body\s*\>/);
332
+
333
+ console.log('Extracting body ['+start+'..'+end+'] chars');
334
+ responseText = responseText.substr(start, end - start);
335
+ }
336
+
337
+ // Handle special header instructions
338
+ // title - set page title
339
+ // tab - activate a tab
340
+ // assets - load assets
341
+ // callback - execute a callback
342
+ if (data.title !== undefined) {
343
+ console.log('Using page title '+data.title);
344
+ $.address.title(data.title);
345
+ }
346
+
347
+ if (data.tab !== undefined) {
348
+ console.log('Activating tab '+data.tab);
349
+ $(data.tab).trigger('activate');
350
+ }
351
+
352
+ /**
353
+ * Load assets
354
+ */
355
+ if (data.assets !== undefined && data.assets.stylesheets !== undefined) {
356
+ jQuery.each(jQuery.makeArray(data.assets.stylesheets), function(idx, url) {
357
+ if (self.stylesheets.loadedAsset(url)) {
358
+ console.log('[ajax] skipping css', url);
359
+ return true;
360
+ } else {
361
+ self.stylesheets.loadAsset(url);
362
+ }
363
+ });
364
+ }
365
+
366
+ if (data.assets !== undefined && data.assets.javascripts !== undefined) {
367
+ jQuery.each(jQuery.makeArray(data.assets.javascripts), function(idx, url) {
368
+ if (self.javascripts.loadedAsset(url)) {
369
+ console.log('[ajax] skipping js', url);
370
+ return true;
371
+ } else {
372
+ self.javascripts.loadAsset(url);
373
+ }
374
+ });
375
+ }
376
+
377
+ /**
378
+ * Insert response
379
+ */
380
+ console.log('Using container ',container.selector);
381
+ console.log('Set data ',data);
382
+ container.data('ajax-info', data)
383
+ container.html(responseText);
384
+
385
+ /**
386
+ * Execute callbacks
387
+ */
388
+ if (data.callbacks !== undefined) {
389
+ jQuery.each(jQuery.makeArray(data.callbacks), function(idx, callback) {
390
+ self.executeCallback(callback);
391
+ });
392
+ }
393
+
394
+ if (self.callbacks.length > 0) {
395
+ jQuery.each(self.callbacks, function(idx, callback) {
396
+ self.executeCallback(callback);
397
+ });
398
+ self.callbacks = [];
399
+ }
400
+
401
+ /**
402
+ * Set cookies
403
+ */
404
+ var cookie = XMLHttpRequest.getResponseHeader('Set-Cookie');
405
+ if (cookie !== null) {
406
+ console.log('Setting cookie');
407
+ document.cookie = cookie;
408
+ }
409
+ };
410
+
411
+ /**
412
+ * Process the response headers.
413
+ *
414
+ * Set the page title.
415
+ */
416
+ self.processResponseHeaders = function(XMLHttpRequest) {
417
+ var data = XMLHttpRequest.getResponseHeader('Ajax-Info');
418
+ if (data !== null) {
419
+ try { data = jQuery.parseJSON(data); }
420
+ catch(e) {
421
+ console.log('Failed to parse Ajax-Info header as JSON!', data);
422
+ }
423
+ }
424
+ if (data === null || data === undefined) {
425
+ data = {};
426
+ }
427
+ return data;
428
+ };
429
+
430
+ /**
431
+ * Show the loading image.
432
+ */
433
+ self.showLoadingImage = function() {
434
+ var icon = $('#loading-icon-small');
435
+
436
+ // Follow the mouse pointer
437
+ $(document).bind('mousemove', self.updateImagePosition);
438
+
439
+ // Display at last click coords initially
440
+ if (self.last_click_coords !== undefined) {
441
+ self.updateImagePosition(self.last_click_coords);
442
+
443
+ // Center it
444
+ } else {
445
+ icon.css({
446
+ position: 'absolute',
447
+ left: '50%',
448
+ top: '50%',
449
+ zIndex: '99',
450
+ marginTop: parseInt(icon.css('marginTop'), 10) + jQuery(window).scrollTop(),
451
+ marginLeft: parseInt(icon.css('marginLeft'), 10) + jQuery(window).scrollLeft()
452
+ });
453
+ }
454
+ icon.show();
455
+ };
456
+
457
+ /**
458
+ * Update the position of the loading icon.
459
+ */
460
+ self.updateImagePosition = function(e) {
461
+ $('#loading-icon-small').css({
462
+ zIndex: 99,
463
+ position: 'absolute',
464
+ top: e.pageY + 14,
465
+ left: e.pageX + 14
466
+ });
467
+ };
468
+
469
+
470
+ /**
471
+ * onLoad
472
+ *
473
+ * Register a callback to be executed in the global scope
474
+ * once all Ajax assets have been loaded. Callbacks are
475
+ * appended to the queue.
476
+ *
477
+ * If the plugin is disabled, callbacks are executed immediately
478
+ * on DOM ready.
479
+ */
480
+ self.onLoad = function(callback) {
481
+ if (self.enabled && !self.loaded) {
482
+ self.callbacks.push(callback);
483
+ console.log('[ajax] appending callback', callback);
484
+ } else {
485
+ self.executeCallback(callback, true);
486
+ }
487
+ };
488
+
489
+ /**
490
+ * prependOnLoad
491
+ *
492
+ * Add a callback to the start of the queue.
493
+ *
494
+ * @see onLoad
495
+ */
496
+ self.prependOnLoad = function(callback) {
497
+ if (self.enabled && !self.loaded) {
498
+ self.callbacks.unshift(callback);
499
+ console.log('[ajax] prepending callback', callback);
500
+ } else {
501
+ self.executeCallback(callback, true);
502
+ }
503
+ };
504
+
505
+ /**
506
+ * Execute a callback given as a string or function reference.
507
+ *
508
+ * <tt>dom_ready</tt> (optional) boolean, if true, the callback
509
+ * is wrapped in a DOM-ready jQuery callback.
510
+ */
511
+ self.executeCallback = function(callback, dom_ready) {
512
+ if (dom_ready !== undefined && dom_ready) {
513
+ $(function() {
514
+ self.executeCallback(callback);
515
+ })
516
+ } else {
517
+ console.log('[ajax] executing callback', callback);
518
+ try {
519
+ if (jQuery.isFunction(callback)) {
520
+ callback();
521
+ } else {
522
+ jQuery.globalEval(callback);
523
+ }
524
+ } catch(e) {
525
+ console.log('[ajax] callback failed with exception', e);
526
+ }
527
+ }
528
+ };
529
+ };