Commit 31b73898 authored by Gábor Hojtsy's avatar Gábor Hojtsy

#157752 by quicksketch: extend AHAH functionality to most types of form...

#157752 by quicksketch: extend AHAH functionality to most types of form elements, without writing JavaScript. Also AHAH enable the blocks admin page.
parent 5fc06cec
......@@ -1575,7 +1575,7 @@ function expand_radios($element) {
/**
* Add AHAH information about a form element to the page to communicate with
* javascript. If #ahah_path is set on an element, this additional javascript is
* javascript. If #ahah[path] is set on an element, this additional javascript is
* added to the page header to attach the AHAH behaviors. See ahah.js for more
* information.
*
......@@ -1590,24 +1590,43 @@ function expand_radios($element) {
function form_expand_ahah($element) {
static $js_added = array();
// Add a reasonable default event handler if none specified.
if (isset($element['#ahah']['path']) && !isset($element['#ahah']['event'])) {
switch ($element['#type']) {
case 'submit':
case 'button':
case 'image_button':
$element['#ahah']['event'] = 'click';
break;
case 'password':
case 'textfield':
case 'textarea':
$element['#ahah']['event'] = 'blur';
break;
case 'radio':
case 'checkbox':
case 'select':
$element['#ahah']['event'] = 'change';
break;
}
}
// Adding the same javascript settings twice will cause a recursion error,
// we avoid the problem by checking if the javascript has already been added.
if (!isset($js_added[$element['#id']]) && isset($element['#ahah_event']) && isset($element['#ahah_path'])) {
if (isset($element['#ahah']['path']) && isset($element['#ahah']['event']) && !isset($js_added[$element['#id']])) {
drupal_add_js('misc/jquery.form.js');
drupal_add_js('misc/ahah.js');
drupal_add_js('misc/progress.js');
$ahah_binding = array(
'id' => $element['#id'],
'uri' => url($element['#ahah_path']),
'event' => $element['#ahah_event'],
'effect' => empty($element['#ahah_effect']) ? 'none' : $element['#ahah_effect'],
'method' => empty($element['#ahah_method']) ? 'replace' : $element['#ahah_method'],
'url' => url($element['#ahah']['path']),
'event' => $element['#ahah']['event'],
'wrapper' => empty($element['#ahah']['wrapper']) ? NULL : $element['#ahah']['wrapper'],
'selector' => empty($element['#ahah']['selector']) ? '#'. $element['#id'] : $element['#ahah']['selector'],
'effect' => empty($element['#ahah']['effect']) ? 'none' : $element['#ahah']['effect'],
'method' => empty($element['#ahah']['method']) ? 'replace' : $element['#ahah']['method'],
);
if (!empty($element['#ahah_wrapper'])) {
$ahah_binding['wrapper'] = $element['#ahah_wrapper'];
}
drupal_add_js(array('ahah' => array($element['#id'] => $ahah_binding)), 'setting');
$js_added[$element['#id']] = TRUE;
......
......@@ -7,8 +7,8 @@
* page. The request returns a small chunk of HTML, which is then directly
* injected into the page.
*
* Drupal uses this file to enhance form elements with #ahah_path and
* #ahah_wrapper properties. If set, this file will automatically be included
* Drupal uses this file to enhance form elements with #ahah[path] and
* #ahah[wrapper] properties. If set, this file will automatically be included
* to provide AHAH capabilities.
*/
......@@ -18,8 +18,13 @@
Drupal.behaviors.ahah = function(context) {
for (var base in Drupal.settings.ahah) {
if (!$('#'+ base + '.ahah-processed').size()) {
var element = Drupal.settings.ahah[base];
var ahah = new Drupal.ahah(base, element);
var element_settings = Drupal.settings.ahah[base];
$(element_settings.selector).each(function() {
element_settings.element = this;
var ahah = new Drupal.ahah(base, element_settings);
});
$('#'+ base).addClass('ahah-processed');
}
}
......@@ -28,95 +33,148 @@ Drupal.behaviors.ahah = function(context) {
/**
* AHAH object.
*/
Drupal.ahah = function(base, element) {
Drupal.ahah = function(base, element_settings) {
// Set the properties for this object.
this.id = '#' + base;
this.event = element.event;
this.uri = element.uri;
this.wrapper = '#'+ element.wrapper;
this.effect = element.effect;
this.method = element.method;
this.element = element_settings.element;
this.selector = element_settings.selector;
this.event = element_settings.event;
this.url = element_settings.url;
this.wrapper = '#'+ element_settings.wrapper;
this.effect = element_settings.effect;
this.method = element_settings.method;
if (this.effect == 'none') {
this.showEffect = 'show';
this.hideEffect = 'hide';
this.showSpeed = '';
}
else if (this.effect == 'fade') {
this.showEffect = 'fadeIn';
this.hideEffect = 'fadeOut';
this.showSpeed = 'slow';
}
else {
this.showEffect = this.effect + 'Toggle';
this.hideEffect = this.effect + 'Toggle';
this.showSpeed = 'slow';
}
Drupal.redirectFormButton(this.uri, $(this.id).get(0), this);
// Record the form action and target, needed for iFrame file uploads.
var form = $(this.element).parents('form');
this.form_action = form.attr('action');
this.form_target = form.attr('target');
this.form_encattr = form.attr('encattr');
// Set the options for the ajaxSubmit function.
// The 'this' variable will not persist inside of the options object.
var ahah = this;
var options = {
url: ahah.url,
beforeSubmit: function(form_values, element_settings, options) {
return ahah.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 ahah.success(response, status);
},
complete: function(response, status) {
if (status == 'error') {
return ahah.error(response.responseText);
}
},
dataType: 'json',
type: 'POST'
};
// Bind the ajaxSubmit function to the element event.
$(element_settings.element).bind(element_settings.event, function() {
$(element_settings.element).parents('form').ajaxSubmit(options);
return false;
});
};
/**
* Handler for the form redirection submission.
*/
Drupal.ahah.prototype.onsubmit = function () {
Drupal.ahah.prototype.beforeSubmit = function (form_values, element, options) {
// Insert progressbar and stretch to take the same space.
this.progress = new Drupal.progressBar('ahah_progress');
this.progress.setProgress(-1, Drupal.t('Please wait...'));
var wrapper = $(this.wrapper);
var button = $(this.id);
var progress_element = $(this.progress.element);
progress_element.css('float', 'left').css({
display: 'none',
width: '10em',
margin: '0 0 0 20px'
});
button.css('float', 'left').attr('disabled', true).after(progress_element);
if (progress_element[this.showEffect]) {
progress_element[this.showEffect]();
}
var progress_element = $(this.progress.element).addClass('ahah-progress');
$(this.element).addClass('progress-disabled').attr('disabled', true).after(progress_element);
};
/**
* Handler for the form redirection completion.
*/
Drupal.ahah.prototype.oncomplete = function (data) {
Drupal.ahah.prototype.success = function (response, status) {
var wrapper = $(this.wrapper);
var button = $(this.id);
var form = $(this.element).parents('form');
var progress_element = $(this.progress.element);
var new_content = $('<div>' + data + '</div>');
// Manually insert HTML into the jQuery object, using $() directly crashes
// Safari with long string lengths. http://dev.jquery.com/ticket/1152
var new_content = $('<div></div>').html(response.data);
Drupal.freezeHeight();
// Restore the previous action and target to the form.
form.attr('action', this.form_action);
this.form_target ? form.attr('target', this.form_target) : form.removeAttr('target');
this.form_encattr ? form.attr('target', this.form_encattr) : form.removeAttr('encattr');
// Remove the progress element.
progress_element.remove();
$(this.element).removeClass('progess-disabled').attr('disabled', false);
// Hide the new content before adding to page.
new_content.hide();
if (this.showEffect != 'show') {
new_content.hide();
}
// Add the form and re-attach behavior.
// Add the new content to the page.
Drupal.freezeHeight();
if (this.method == 'replace') {
wrapper.empty().append(new_content);
}
else if (wrapper[this.method]) {
else {
wrapper[this.method](new_content);
}
if (new_content[this.showEffect]) {
new_content[this.showEffect]();
// Determine what effect use and what content will receive the effect, then
// show the new content. For browser compatibility, Safari is excluded from
// using effects on table rows.
if ($('.ahah-new-content', new_content).size() > 0 && !($.browser.safari && $("tr.ahah-new-content", new_content).size() > 0)) {
$('.ahah-new-content', new_content).hide();
new_content.show();
$(".ahah-new-content", new_content)[this.showEffect](this.showSpeed);
}
else if (this.showEffect != 'show') {
new_content[this.showEffect](this.showSpeed);
}
// Attach all javascript behaviors to the new content, if it was successfully
// added to the page, this if statement allows #ahah[wrapper] to be optional.
if (new_content.parents('html').length > 0) {
Drupal.attachBehaviors(new_content);
}
button.css('float', 'none').attr('disabled', false);
Drupal.attachBehaviors(new_content);
Drupal.unfreezeHeight();
};
/**
* Handler for the form redirection error.
*/
Drupal.ahah.prototype.onerror = function (error) {
Drupal.ahah.prototype.error = function (error) {
alert(Drupal.t('An error occurred:\n\n@error', { '@error': error }));
// Resore the previous action and target to the form.
element.parent('form').attr( { action: this.form_action, target: this.form_target} );
// Remove progressbar.
$(this.progress.element).remove();
this.progress = null;
// Undo hide.
$(this.wrapper).show();
// Re-enable the element.
$(this.id).css('float', 'none').attr('disabled', false);
$(this.element).removeClass('progess-disabled').attr('disabled', false);
};
......@@ -181,103 +181,15 @@ Drupal.theme = function(func) {
};
/**
* Redirects a button's form submission to a hidden iframe and displays the result
* in a given wrapper. The iframe should contain a call to
* window.parent.iframeHandler() after submission.
*/
Drupal.redirectFormButton = function (uri, button, handler) {
// Trap the button
button.onmouseover = button.onfocus = function() {
button.onclick = function() {
// Create target iframe
Drupal.deleteIframe();
Drupal.createIframe();
// Prepare variables for use in anonymous function.
var button = this;
var action = button.form.action;
var target = button.form.target;
// Redirect form submission to iframe
this.form.action = uri;
this.form.target = 'redirect-target';
this.form.submit();
handler.onsubmit();
// Set iframe handler for later
window.iframeHandler = function () {
var iframe = $('#redirect-target').get(0);
// Restore form submission
button.form.action = action;
button.form.target = target;
// Get response from iframe body
try {
response = (iframe.contentWindow || iframe.contentDocument || iframe).document.body.innerHTML;
// Firefox 1.0.x hack: Remove (corrupted) control characters
response = response.replace(/[\f\n\r\t]/g, ' ');
if (window.opera) {
// Opera-hack: it returns innerHTML sanitized.
response = response.replace(/&quot;/g, '"');
}
}
catch (e) {
response = null;
}
response = eval('('+ response +');');
// Check response code
if (!response || response.status == 0) {
handler.onerror(response.data || Drupal.t('Error parsing response'));
return;
}
handler.oncomplete(response.data);
return true;
};
return true;
};
};
button.onmouseout = button.onblur = function() {
button.onclick = null;
};
};
/**
* Create an invisible iframe for form submissions.
* Parse a JSON response.
*
* The result is either the JSON object, or an object with 'status' 0 and 'data' an error message.
*/
Drupal.createIframe = function () {
if ($('#redirect-holder').size()) {
return;
Drupal.parseJson = function (data) {
if ((data.substring(0, 1) != '{') && (data.substring(0, 1) != '[')) {
return { status: 0, data: data.length ? data : Drupal.t('Unspecified error') };
}
// Note: some browsers require the literal name/id attributes on the tag,
// some want them set through JS. We do both.
window.iframeHandler = function () {};
var div = document.createElement('div');
div.id = 'redirect-holder';
$(div).html('<iframe name="redirect-target" id="redirect-target" class="redirect" onload="window.iframeHandler();"></iframe>');
var iframe = div.firstChild;
$(iframe)
.attr({
name: 'redirect-target',
id: 'redirect-target'
})
.css({
position: 'absolute',
height: '1px',
width: '1px',
visibility: 'hidden'
});
$('body').append(div);
};
/**
* Delete the invisible iframe
*/
Drupal.deleteIframe = function () {
$('#redirect-holder').remove();
return eval('(' + data + ');');
};
/**
......
......@@ -4,3 +4,12 @@
padding-left: inherit;
padding-right: 1.5em;
}
#blocks select {
margin-left: 18px;
}
#blocks select.progress-disabled {
margin-left: 0px;
}
#blocks .progress .bar {
float: right;
}
This diff is collapsed.
......@@ -12,3 +12,27 @@
margin-bottom: 4px;
padding: 3px;
}
#blocks select {
margin-right: 18px; /* LTR */
}
#blocks select.progress-disabled {
margin-right: 0px; /* LTR */
}
#blocks tr.ahah-new-content {
background-color: #ffd;
}
#blocks .progress {
width: 15px;
height: 15px;
margin: -2px 0;
}
#blocks .progress .bar {
width: 15px;
height: 15px;
background: transparent url(../../misc/throbber.gif) no-repeat 0px -18px;
border: none;
float: left; /* LTR */
}
#blocks .progress .message {
display: none;
}
......@@ -102,8 +102,8 @@ function block_help($path, $arg) {
*/
function block_theme() {
return array(
'block_admin_display' => array(
'template' => 'block-admin-display',
'block_admin_display_form' => array(
'template' => 'block-admin-display-form',
'file' => 'block.admin.inc',
'arguments' => array('form' => NULL),
),
......@@ -124,8 +124,7 @@ function block_menu() {
$items['admin/build/block'] = array(
'title' => 'Blocks',
'description' => 'Configure what block content appears in your site\'s sidebars and other regions.',
'page callback' => 'drupal_get_form',
'page arguments' => array('block_admin_display'),
'page callback' => 'block_admin_display',
'access arguments' => array('administer blocks'),
'file' => 'block.admin.inc',
);
......@@ -134,6 +133,12 @@ function block_menu() {
'type' => MENU_DEFAULT_LOCAL_TASK,
'weight' => -10,
);
$items['admin/build/block/list/js'] = array(
'title' => 'Javascript List Form',
'page callback' => 'block_admin_display_js',
'type' => MENU_CALLBACK,
'file' => 'block.admin.inc',
);
$items['admin/build/block/configure'] = array(
'title' => 'Configure block',
'page arguments' => array('block_admin_configure'),
......@@ -148,6 +153,7 @@ function block_menu() {
);
$items['admin/build/block/add'] = array(
'title' => 'Add block',
'page callback' => 'drupal_get_form',
'page arguments' => array('block_add_block_form'),
'type' => MENU_LOCAL_TASK,
'file' => 'block.admin.inc',
......@@ -156,7 +162,7 @@ function block_menu() {
foreach (list_themes() as $key => $theme) {
$items['admin/build/block/list/'. $key] = array(
'title' => check_plain($theme->info['name']),
'page arguments' => array('block_admin_display', $key),
'page arguments' => array($key),
'type' => $key == $default ? MENU_DEFAULT_LOCAL_TASK : MENU_LOCAL_TASK,
'weight' => $key == $default ? -10 : 0,
'file' => 'block.admin.inc',
......
......@@ -83,6 +83,12 @@ div.teaser-button-wrapper {
.progress .percentage {
float: left;
}
.progess-disabled {
float: right;
}
.ahah-progress {
float: right;
}
input.password-field {
margin-left: 10px;
margin-right: inherit;
......
......@@ -418,8 +418,9 @@ html.js .no-js {
.progress .bar {
background: #fff url(../../misc/progress.gif);
border: 1px solid #00375a;
width: 5em;
height: 1.5em;
margin-top: 0.2em;
margin: 0.2em 0 0 0.2em;
}
.progress .filled {
background: #0072b9;
......@@ -430,6 +431,12 @@ html.js .no-js {
.progress .percentage {
float: right; /* LTR */
}
.progress-disabled {
float: left; /* LTR */
}
.ahah-progress {
float: left; /* LTR */
}
/*
** Formatting for welcome page
......
......@@ -122,28 +122,28 @@ function system_elements() {
$type['form'] = array('#method' => 'post', '#action' => request_uri());
// Inputs
$type['submit'] = array('#input' => TRUE, '#name' => 'op', '#button_type' => 'submit', '#executes_submit_callback' => TRUE, '#ahah_event' => 'submit', '#process' => array('form_expand_ahah'));
$type['button'] = array('#input' => TRUE, '#name' => 'op', '#button_type' => 'submit', '#executes_submit_callback' => FALSE, '#ahah_event' => 'submit', '#process' => array('form_expand_ahah'));
$type['image_button'] = array('#input' => TRUE, '#button_type' => 'submit', '#executes_submit_callback' => TRUE, '#ahah_event' => 'submit', '#process' => array('form_expand_ahah'), '#return_value' => TRUE, '#has_garbage_value' => TRUE, '#src' => NULL);
$type['textfield'] = array('#input' => TRUE, '#size' => 60, '#maxlength' => 128, '#autocomplete_path' => FALSE);
$type['password'] = array('#input' => TRUE, '#size' => 60, '#maxlength' => 128);
$type['submit'] = array('#input' => TRUE, '#name' => 'op', '#button_type' => 'submit', '#executes_submit_callback' => TRUE, '#process' => array('form_expand_ahah'));
$type['button'] = array('#input' => TRUE, '#name' => 'op', '#button_type' => 'submit', '#executes_submit_callback' => FALSE, '#process' => array('form_expand_ahah'));
$type['image_button'] = array('#input' => TRUE, '#button_type' => 'submit','#executes_submit_callback' => TRUE, '#process' => array('form_expand_ahah'), '#return_value' => TRUE, '#has_garbage_value' => TRUE, '#src' => NULL);
$type['textfield'] = array('#input' => TRUE, '#size' => 60, '#maxlength' => 128, '#autocomplete_path' => FALSE, '#process' => array('form_expand_ahah'));
$type['password'] = array('#input' => TRUE, '#size' => 60, '#maxlength' => 128, '#process' => array('form_expand_ahah'));
$type['password_confirm'] = array('#input' => TRUE, '#process' => array('expand_password_confirm'));
$type['textarea'] = array('#input' => TRUE, '#cols' => 60, '#rows' => 5, '#resizable' => TRUE);
$type['textarea'] = array('#input' => TRUE, '#cols' => 60, '#rows' => 5, '#resizable' => TRUE, '#process' => array('form_expand_ahah'));
$type['radios'] = array('#input' => TRUE, '#process' => array('expand_radios'));
$type['radio'] = array('#input' => TRUE, '#default_value' => NULL);
$type['radio'] = array('#input' => TRUE, '#default_value' => NULL, '#process' => array('form_expand_ahah'));
$type['checkboxes'] = array('#input' => TRUE, '#process' => array('expand_checkboxes'), '#tree' => TRUE);
$type['checkbox'] = array('#input' => TRUE, '#return_value' => 1);
$type['select'] = array('#input' => TRUE, '#size' => 0, '#multiple' => FALSE);
$type['weight'] = array('#input' => TRUE, '#delta' => 10, '#default_value' => 0, '#process' => array('process_weight'));
$type['checkbox'] = array('#input' => TRUE, '#return_value' => 1, '#process' => array('form_expand_ahah'));
$type['select'] = array('#input' => TRUE, '#size' => 0, '#multiple' => FALSE, '#process' => array('form_expand_ahah'));
$type['weight'] = array('#input' => TRUE, '#delta' => 10, '#default_value' => 0, '#process' => array('process_weight', 'form_expand_ahah'));
$type['date'] = array('#input' => TRUE, '#process' => array('expand_date'), '#element_validate' => array('date_validate'));
$type['file'] = array('#input' => TRUE, '#size' => 60);
// Form structure
$type['item'] = array('#value' => '');
$type['hidden'] = array('#input' => TRUE);
$type['hidden'] = array('#input' => TRUE, '#process' => array('expand_ahah'));
$type['value'] = array('#input' => TRUE);
$type['markup'] = array('#prefix' => '', '#suffix' => '');
$type['fieldset'] = array('#collapsible' => FALSE, '#collapsed' => FALSE, '#value' => NULL);
$type['fieldset'] = array('#collapsible' => FALSE, '#collapsed' => FALSE, '#value' => NULL, '#process' => array('expand_ahah'));
$type['token'] = array('#input' => TRUE);
return $type;
}
......
......@@ -499,8 +499,10 @@ function _upload_form($node) {
'#type' => 'submit',
'#value' => t('Attach'),
'#name' => 'attach',
'#ahah_path' => 'upload/js',
'#ahah_wrapper' => 'attach-wrapper',
'#ahah' => array(
'path' => 'upload/js',
'wrapper' => 'attach-wrapper',
),
'#submit' => array('node_form_submit_build_node'),
);
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment