Skip to content

Instantly share code, notes, and snippets.

@mrclay
Created November 11, 2015 01:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mrclay/f2e6b667b3ae5736e83e to your computer and use it in GitHub Desktop.
Save mrclay/f2e6b667b3ae5736e83e to your computer and use it in GitHub Desktop.
Sets up Drupal so that any form can be fetched and submitted over Ajax. Unlike Drupal's ajax module, there are no modifications required to the form at all. Even redirects are captured and sent back to the client.
/**
* Notes:
* - This is not the complete JS module
*/
/**
* An object to simplify fetching Drupal forms via Ajax
*
* @param {object} spec Object with keys:
*
* urlArgs : {object} (optional) arguments for creating the URL.
*
* urlTemplate : {string} (optional) template for the URL (using urlArgs).
*
* urlFactory : {function} (optional) if no template is provided, you must provide a function
* that can take the urlArgs as an argument and return a URL.
*
* @constructor
*/
ceedar.AjaxForm = function (spec) {
// for making unique iframe names so forms could post simultaneously if needed
ceedar.AjaxForm.i = ceedar.AjaxForm.i || 0;
var self = this;
this.urlArgs = spec.urlArgs || {};
this.formId = '';
this.getUrl = function () {
var url;
if (spec.urlTemplate) {
url = nano(spec.urlTemplate, self.urlArgs);
} else {
url = spec.urlFactory(self.urlArgs);
}
if (!/^https?:/.test(url)) {
url = ceedar.base_path + url;
}
return url;
};
this.renderPage = function (response) {
return '<h1>' + response.title + '</h1>' + response.msgs + response.html;
};
this.addMessages = function (response) {
// use the last element that matches
$($(".page-header, .page-header + .nav-tabs").toArray().pop()).after(response.msgs);
};
/**
* Fetch a Drupal Form
*
* @param {object} data
* @returns {Deferred} use .done() to handle the data received
*/
this.fetch = function (data) {
data = data || {};
data.ceedar_as_json = 1;
var url = self.getUrl(),
def = $.Deferred();
ceedar.wait.begin();
$.post(url, data, 'json').always(function (response, state) {
ceedar.wait.end();
if (state !== 'success') {
ceedar.ajax_fail();
def.reject();
return;
}
var m = response.html.match(/<form ([^>]+)>/);
if (m[1]) {
m = m[1].match(/ id="([^"]+)"/);
if (m) {
self.formId = m[1];
}
}
response.render = function () {
return self.renderPage(response);
};
response.addMessages = function () {
self.addMessages(response);
};
def.resolve(response);
});
return def;
};
/**
* Submit a Drupal Form (done via hidden iframe to allow file uploads)
*
* @param {*} $form jQuery-wrapped form object, selector, or form DOM element.
* @returns {Deferred} use .done() to handle the data received
*/
this.submitForm = function ($form) {
if (!($form instanceof $)) {
$form = $($form);
}
ceedar.wait.begin();
ceedar.AjaxForm.i += 1;
var def = $.Deferred(),
target = 'ceedar_iframe' + ceedar.AjaxForm.i,
orig_target = $form.prop('target'),
$iframe = $('<iframe name="' + target + '" style="display:none" src="about:blank" />'),
$json_input = $('<input type="hidden" name="ceedar_as_json" value="1">');
$iframe.appendTo('body').on('load', function () {
var response = $.parseJSON($iframe.contents().text());
ceedar.wait.end();
$form.prop('target', orig_target ? orig_target : '');
$json_input.remove();
$iframe.remove();
response.render = function () {
return self.renderPage(response);
};
response.addMessages = function () {
self.addMessages(response);
};
def.resolve(response);
});
$form.append($json_input);
$form.prop('target', target);
$form[0].submit();
return def;
};
// https://github.com/trix/nano
function nano(template, data) {
return template.replace(/\{([\w\.]*)\}/g, function(str, key) {
var keys = key.split("."), v = data[keys.shift()];
for (var i = 0, l = keys.length; i < l; i++) v = v[keys[i]];
return (typeof v !== "undefined" && v !== null) ? v : "";
});
}
};
<?php
/**
* Notes:
* - This is not the complete ceedar_main.module file
* - ceedar_main()->ajax returns a singleton instance of the UFCOE_Drupal_Ajax class.
*/
/**
* This is called at the end of each request, and gives us a chance to change the page delivery function.
*
* Implements hook_page_delivery_callback_alter().
*
* @param callable $callback
*
* @see drupal_deliver_page
* @see ajax_deliver
* @see drupal_deliver_html_page
*/
function ceedar_main_page_delivery_callback_alter(&$callback) {
if (ceedar_main()->ajax->isRequestingJson()) {
$callback = '_ceedar_main_ajax_deliver';
}
}
/**
* Here we alter all forms, and wire up our submit handler if we've requested JSON
*
* Implements hook_form_alter().
*
* @param array $form The form
* @param array $form_state The form state
* @param string $form_id The ID of the form
*/
function ceedar_main_form_alter(&$form, &$form_state, $form_id) {
if (!ceedar_main()->ajax->isRequestingJson()) {
return;
}
$form['#submit'][] = '_ceedar_main_submit_handler';
}
/**
* This is a function wrapper for UFCOE\Drupal\Ajax::submitHandler()
*
* @param array $form The form
* @param array $form_state The form state
*
* @see UFCOE\Drupal\Ajax::submitHandler()
*/
function _ceedar_main_submit_handler(&$form, &$form_state) {
ceedar_main()->ajax->submitHandler($form, $form_state);
}
/**
* This is a function wrapper for UFCOE\Drupal\Ajax::deliverJson()
*
* @param mixed $page_callback_result
*
* @see UFCOE\Drupal\Ajax::deliverJson()
*/
function _ceedar_main_ajax_deliver($page_callback_result) {
ceedar_main()->ajax->deliverJson($page_callback_result);
}
/**
* Clicking #show-form loads a Drupal form via ajax and places it in a lightbox.
*
* The form can be submitted via Ajax and redisplayed if there were errors.
*/
$(document).on('click', '#show-form', function () {
var formApi = new ceedar.AjaxForm({
urlTemplate: "task/add_task" // path of a Drupal form
});
formApi.fetch().done(function (response) {
// when the submit button is clicked on our loaded form
function onFormSubmit(e) {
e.preventDefault(); // don't actually submit the form, we'll use the API.
// submit the form via iframe and give us a response object
formApi.submitForm(this).done(function (response) {
if (response.error) {
// re-display form (with errors) in colorbox
$('#cboxLoadedContent').html(response.render());
return;
}
// forward to URL specified by Drupal's redirect
if (response.goto) {
location.href = ceedar.base_path + response.goto;
return;
}
// or just close the lightbox
$.colorbox.close();
});
}
// open the lightbox and put the Drupal form in it
$.colorbox({
html: response.render(),
onComplete: function () {
$('#colorbox').on('submit', 'form', onFormSubmit);
},
onClosed: function () {
$('#colorbox').off('submit', 'form', onFormSubmit);
}
});
});
});
<?php
namespace UFCOE\Drupal;
class Ajax {
protected $redirect;
protected $commands = array();
/**
* Add a command to be interpreted client-side
*
* @param string $command
* @param array $args
*/
public function addCommand($command, array $args = array()) {
$this->commands[] = array($command, $args);
}
/**
* Is the request from an XHR client?
*
* @return bool
*/
public function isXhr() {
return $this->isRequestingJson()
|| (!empty($_SERVER['HTTP_X_REQUESTED_WITH'])
&& strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest');
}
/**
* Is the request asking for JSON response?
*
* @return bool
*/
public function isRequestingJson() {
return !empty($_POST['ceedar_as_json']);
}
/**
* Deliver a page result as JSON (simplified version of ajax_deliver)
*
* @param mixed $page
*
* @see ajax_deliver
*/
public function deliverJson($page) {
if (!is_array($page) || empty($page['#type']) || $page['#type'] !== 'form') {
drupal_deliver_html_page($page);
return;
}
drupal_add_http_header('Content-Type', 'application/json; charset=utf-8');
echo json_encode(array(
'html' => drupal_render($page),
'msgs' => theme('status_messages'),
'goto' => $this->redirect,
'error' => (bool)form_get_errors(),
'commands' => $this->commands,
'title' => drupal_get_title(),
));
ajax_footer();
}
/**
* @param array $form The form
* @param array $form_state The form state
*/
public function submitHandler(&$form, &$form_state) {
if (!empty($form_state['redirect'])) {
$this->redirect = $form_state['redirect'];
$form_state['redirect'] = null;
$form_state['no_redirect'] = true;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment