Newer
Older
Angie Byron
committed
// $Id$
(function ($) {
/**
* Provides AJAX page updating via jQuery $.ajax (Asynchronous JavaScript and XML).
*
* AJAX is a method of making a request via Javascript while viewing an HTML
* page. The request returns an array of commands encoded in JSON, which is
* then executed to make any changes that are necessary to the page.
*
* Drupal uses this file to enhance form elements with #ajax['path'] and
* #ajax['wrapper'] properties. If set, this file will automatically be included
* to provide AJAX capabilities.
*/
Drupal.ajax = Drupal.ajax || {};
/**
* Attaches the AJAX behavior to each AJAX form element.
*/
Drupal.behaviors.AJAX = {
attach: function (context, settings) {
// Load all AJAX behaviors specified in the settings.
for (var base in settings.ajax) {
if (!$('#' + base + '.ajax-processed').length) {
var element_settings = settings.ajax[base];
$(element_settings.selector).each(function () {
Angie Byron
committed
element_settings.element = this;
Angie Byron
committed
Drupal.ajax[base] = new Drupal.ajax(base, this, element_settings);
});
$('#' + base).addClass('ajax-processed');
}
}
// Bind AJAX behaviors to all items showing the class.
$('.use-ajax:not(.ajax-processed)').addClass('ajax-processed').each(function () {
var element_settings = {};
// For anchor tags, these will go to the target of the anchor rather
// than the usual location.
if ($(this).attr('href')) {
element_settings.url = $(this).attr('href');
Angie Byron
committed
element_settings.event = 'click';
Angie Byron
committed
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
}
var base = $(this).attr('id');
Drupal.ajax[base] = new Drupal.ajax(base, this, element_settings);
});
// This class means to submit the form to the action using AJAX.
$('.use-ajax-submit:not(.ajax-processed)').addClass('ajax-processed').each(function () {
var element_settings = {};
// AJAX submits specified in this manner automatically submit to the
// normal form action.
element_settings.url = $(this.form).attr('action');
element_settings.set_click = TRUE;
var base = $(this).attr('id');
Drupal.ajax[base] = new Drupal.ajax(base, this, element_settings);
});
}
};
/**
* AJAX object.
*
* All AJAX objects on a page are accessible through the global Drupal.ajax
* object and are keyed by the submit button's ID. You can access them from
* your module's JavaScript file to override properties or functions.
*
* For example, if your AJAX enabled button has the ID 'edit-submit', you can
* redefine the function that is called to insert the new content like this
* (inside a Drupal.behaviors attach block):
* @code
* Drupal.behaviors.myCustomAJAXStuff = {
* attach: function (context, settings) {
* Drupal.ajax['edit-submit'].commands.insert = function (ajax, response, status) {
* new_content = $(response.data);
* $('#my-wrapper').append(new_content);
* alert('New content was appended to #my-wrapper');
* }
* }
* };
* @endcode
*/
Drupal.ajax = function (base, element, element_settings) {
var defaults = {
url: 'system/ajax',
event: 'mousedown',
keypress: true,
selector: '#' + base,
effect: 'none',
speed: 'slow',
method: 'replace',
progress: {
type: 'bar',
message: 'Please wait...'
},
Dries Buytaert
committed
button: {}
Angie Byron
committed
};
$.extend(this, defaults, element_settings);
this.element = element;
// Replacing 'nojs' with 'ajax' in the URL allows for an easy method to let
// the server detect when it needs to degrade gracefully.
this.url = element_settings.url.replace('/nojs/', '/ajax/');
this.wrapper = '#' + element_settings.wrapper;
// If there isn't a form, jQuery.ajax() will be used instead, allowing us to
// bind AJAX to links as well.
if (this.element.form) {
this.form = $(this.element.form);
}
// Set the options for the ajaxSubmit function.
// The 'this' variable will not persist inside of the options object.
var ajax = this;
var options = {
url: ajax.url,
data: ajax.button,
beforeSerialize: function (element_settings, options) {
return ajax.beforeSerialize(element_settings, options);
},
Angie Byron
committed
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
beforeSubmit: function (form_values, element_settings, options) {
return ajax.beforeSubmit(form_values, element_settings, options);
},
success: function (response, status) {
// Sanity check for browser support (object expected).
// When using iFrame uploads, responses must be returned as a string.
if (typeof response == 'string') {
response = Drupal.parseJson(response);
}
return ajax.success(response, status);
},
complete: function (response, status) {
if (status == 'error' || status == 'parsererror') {
return ajax.error(response, ajax.url);
}
},
dataType: 'json',
type: 'POST'
};
// Bind the ajaxSubmit function to the element event.
$(this.element).bind(element_settings.event, function () {
if (ajax.form) {
// If setClick is set, we must set this to ensure that the button's
// value is passed.
if (ajax.setClick) {
// Mark the clicked button. 'form.clk' is a special variable for
// ajaxSubmit that tells the system which element got clicked to
// trigger the submit. Without it there would be no 'op' or
// equivalent.
ajax.form.clk = this.element;
}
ajax.form.ajaxSubmit(options);
}
else {
$.ajax(options);
}
return false;
});
// If necessary, enable keyboard submission so that AJAX behaviors
// can be triggered through keyboard input as well as e.g. a mousedown
// action.
if (element_settings.keypress) {
$(element_settings.element).keypress(function (event) {
// Detect enter key.
if (event.keyCode == 13) {
$(element_settings.element).trigger(element_settings.event);
return false;
}
});
}
};
/**
* Handler for the form serialization.
*
* Runs before the beforeSubmit() handler (see below), and unlike that one, runs
* before field data is collected.
*/
Drupal.ajax.prototype.beforeSerialize = function (element, options) {
// Allow detaching behaviors to update field values before collecting them.
var settings = this.settings || Drupal.settings;
Drupal.detachBehaviors(this.form, settings, 'serialize');
};
Angie Byron
committed
/**
* Handler for the form redirection submission.
*/
Drupal.ajax.prototype.beforeSubmit = function (form_values, element, options) {
// Disable the element that received the change.
$(this.element).addClass('progress-disabled').attr('disabled', true);
Dries Buytaert
committed
// Server-side code needs to know what element triggered the call, so it can
// find the #ajax binding.
form_values.push({ name: 'ajax_triggering_element', value: this.formPath });
Angie Byron
committed
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
// Insert progressbar or throbber.
if (this.progress.type == 'bar') {
var progressBar = new Drupal.progressBar('ajax-progress-' + this.element.id, eval(this.progress.update_callback), this.progress.method, eval(this.progress.error_callback));
if (this.progress.message) {
progressBar.setProgress(-1, this.progress.message);
}
if (this.progress.url) {
progressBar.startMonitoring(this.progress.url, this.progress.interval || 1500);
}
this.progress.element = $(progressBar.element).addClass('ajax-progress ajax-progress-bar');
this.progress.object = progressBar;
$(this.element).after(this.progress.element);
}
else if (this.progress.type == 'throbber') {
this.progress.element = $('<div class="ajax-progress ajax-progress-throbber"><div class="throbber"> </div></div>');
if (this.progress.message) {
$('.throbber', this.progress.element).after('<div class="message">' + this.progress.message + '</div>');
}
$(this.element).after(this.progress.element);
}
};
/**
* Handler for the form redirection completion.
*/
Drupal.ajax.prototype.success = function (response, status) {
// Remove the progress element.
if (this.progress.element) {
$(this.progress.element).remove();
}
if (this.progress.object) {
this.progress.object.stopMonitoring();
}
$(this.element).removeClass('progress-disabled').attr('disabled', false);
Drupal.freezeHeight();
for (i in response) {
if (response[i]['command'] && this.commands[response[i]['command']]) {
this.commands[response[i]['command']](this, response[i], status);
}
}
// Reattach behaviors that were detached in beforeSerialize(). The
// attachBehaviors() called on the new content from processing the response
// commands is not sufficient, because behaviors from the entire form need
// to be reattached.
var settings = this.settings || Drupal.settings;
Drupal.attachBehaviors(this.form, settings);
Angie Byron
committed
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
Drupal.unfreezeHeight();
// Remove any response-specific settings so they don't get used on the next
// call by mistake.
this.settings = {};
};
/**
* Build an effect object which tells us how to apply the effect when adding new HTML.
*/
Drupal.ajax.prototype.getEffect = function (response) {
var type = response.effect || this.effect;
var speed = response.speed || this.speed;
var effect = {};
if (type == 'none') {
effect.showEffect = 'show';
effect.hideEffect = 'hide';
effect.showSpeed = '';
}
else if (type == 'fade') {
effect.showEffect = 'fadeIn';
effect.hideEffect = 'fadeOut';
effect.showSpeed = speed;
}
else {
effect.showEffect = type + 'Toggle';
effect.hideEffect = type + 'Toggle';
effect.showSpeed = speed;
}
return effect;
Angie Byron
committed
/**
* Handler for the form redirection error.
*/
Drupal.ajax.prototype.error = function (response, uri) {
alert(Drupal.ajaxError(response, uri));
// Remove the progress element.
if (this.progress.element) {
$(this.progress.element).remove();
}
if (this.progress.object) {
this.progress.object.stopMonitoring();
}
// Undo hide.
$(this.wrapper).show();
// Re-enable the element.
$(this.element).removeClass('progress-disabled').attr('disabled', false);
// Reattach behaviors that were detached in beforeSerialize().
var settings = response.settings || this.settings || Drupal.settings;
Drupal.attachBehaviors(this.form, settings);
Angie Byron
committed
};
/**
* Provide a series of commands that the server can request the client perform.
*/
Drupal.ajax.prototype.commands = {
/**
* Command to insert new content into the DOM.
*/
insert: function (ajax, response, status) {
// Get information from the response. If it is not there, default to
// our presets.
var wrapper = response.selector ? $(response.selector) : $(ajax.wrapper);
var method = response.method || ajax.method;
var effect = ajax.getEffect(response);
// Manually insert HTML into the jQuery object, using $() directly crashes
// Safari with long string lengths. http://dev.jquery.com/ticket/3178
var new_content = $('<div></div>').html(response.data);
// If removing content from the wrapper, detach behaviors first.
switch (method) {
case 'html':
case 'replaceWith':
case 'replaceAll':
case 'empty':
case 'remove':
var settings = response.settings || ajax.settings || Drupal.settings;
Drupal.detachBehaviors(wrapper, settings);
}
Angie Byron
committed
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
// Add the new content to the page.
wrapper[method](new_content);
// Immediately hide the new content if we're using any effects.
if (effect.showEffect != 'show') {
new_content.hide();
}
// Determine which effect to use and what content will receive the
// effect, then show the new content.
if ($('.ajax-new-content', new_content).length > 0) {
$('.ajax-new-content', new_content).hide();
new_content.show();
$('.ajax-new-content', new_content)[effect.showEffect](effect.showSpeed);
}
else if (effect.showEffect != 'show') {
new_content[effect.showEffect](effect.showSpeed);
}
// Attach all JavaScript behaviors to the new content, if it was successfully
// added to the page, this if statement allows #ajax['wrapper'] to be
// optional.
if (new_content.parents('html').length > 0) {
// Apply any settings from the returned JSON if available.
var settings = response.settings || ajax.settings || Drupal.settings;
Drupal.attachBehaviors(new_content, settings);
}
},
/**
* Command to remove a chunk from the page.
*/
remove: function (ajax, response, status) {
var settings = response.settings || ajax.settings || Drupal.settings;
Drupal.detachBehaviors($(response.selector), settings);
Angie Byron
committed
$(response.selector).remove();
},
/**
* Command to mark a chunk changed.
*/
changed: function (ajax, response, status) {
if (!$(response.selector).hasClass('ajax-changed')) {
$(response.selector).addClass('ajax-changed');
if (response.asterisk) {
$(response.selector).find(response.asterisk).append(' <span class="ajax-changed">*</span> ');
}
}
},
/**
* Command to provide an alert.
*/
alert: function (ajax, response, status) {
alert(response.text, response.title);
},
/**
* Command to provide the jQuery css() function.
*/
css: function (ajax, response, status) {
$(response.selector).css(response.argument);
},
Angie Byron
committed
/**
* Command to set the settings that will be used for other commands in this response.
*/
settings: function (ajax, response, status) {
Angie Byron
committed
if (response.merge) {
$.extend(true, Drupal.settings, response.settings);
}
else {
ajax.settings = response.settings;
}
Angie Byron
committed
},
/**
* Command to attach data using jQuery's data API.
*/
data: function (ajax, response, status) {
$(response.selector).data(response.name, response.value);
},
/**
* Command to restripe a table.
*/
restripe: function (ajax, response, status) {
// :even and :odd are reversed because jQuery counts from 0 and
// we count from 1, so we're out of sync.
$('tbody tr:not(:hidden)', $(response.selector))
.removeClass('even').removeClass('odd')
.filter(':even')
.addClass('odd').end()
.filter(':odd')
.addClass('even');
}
};
})(jQuery);