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.
- data/MIT-LICENSE +20 -0
- data/README.rdoc +291 -0
- data/Rakefile +49 -0
- data/VERSION +1 -0
- data/app/controllers/ajax_controller.rb +3 -0
- data/app/views/ajax/framework.html.erb +7 -0
- data/config/initializers/ajax.rb +14 -0
- data/lib/ajax.rb +79 -0
- data/lib/ajax/action_controller.rb +154 -0
- data/lib/ajax/action_view.rb +56 -0
- data/lib/ajax/helpers.rb +15 -0
- data/lib/ajax/helpers/request_helper.rb +76 -0
- data/lib/ajax/helpers/robot_helper.rb +31 -0
- data/lib/ajax/helpers/url_helper.rb +47 -0
- data/lib/ajax/railtie.rb +7 -0
- data/lib/ajax/routes.rb +12 -0
- data/lib/ajax/spec/extension.rb +34 -0
- data/lib/ajax/spec/helpers.rb +95 -0
- data/lib/ajax/tasks.rb +1 -0
- data/lib/rack-ajax.rb +60 -0
- data/lib/rack-ajax/decision_tree.rb +60 -0
- data/lib/rack-ajax/parser.rb +115 -0
- data/public/images/loading-icon-large.gif +0 -0
- data/public/images/loading-icon-small.gif +0 -0
- data/public/javascripts/ajax.js +529 -0
- data/public/javascripts/jquery.address-1.1.js +450 -0
- data/public/javascripts/jquery.address-1.1.min.js +11 -0
- data/public/javascripts/jquery.address-1.2.js +528 -0
- data/public/javascripts/jquery.address-1.2.min.js +25 -0
- data/public/javascripts/jquery.address-1.2rc.js +599 -0
- data/public/javascripts/jquery.address-1.2rc.min.js +27 -0
- data/public/javascripts/jquery.json-2.2.js +178 -0
- data/public/javascripts/jquery.json-2.2.min.js +31 -0
- data/rails/init.rb +4 -0
- data/rails/install.rb +23 -0
- data/rails/uninstall.rb +1 -0
- data/spec/ajax/helpers_spec.rb +102 -0
- data/spec/ajax/request_helper_spec.rb +33 -0
- data/spec/integration/ajax_spec.rb +146 -0
- data/spec/rack-ajax/parser_spec.rb +62 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +18 -0
- data/tasks/ajax_tasks.rake +15 -0
- 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
|
Binary file
|
Binary file
|
@@ -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
|
+
};
|