ajax 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+ };