diff --git a/e107_handlers/e107_class.php b/e107_handlers/e107_class.php
index 59cf5deab..b54b48e4d 100644
--- a/e107_handlers/e107_class.php
+++ b/e107_handlers/e107_class.php
@@ -156,6 +156,7 @@ class e107
'e_admin_request' => '{e_HANDLER}admin_ui.php',
'e_admin_response' => '{e_HANDLER}admin_ui.php',
'e_admin_ui' => '{e_HANDLER}admin_ui.php',
+ 'e_ajax_class' => '{e_HANDLER}e_ajax_class.php',
'e_array' => '{e_HANDLER}core_functions.php', // Old ArrayStorage.
'e_bbcode' => '{e_HANDLER}bbcode_handler.php',
'e_bb_base' => '{e_HANDLER}bbcode_handler.php',
@@ -1655,6 +1656,16 @@ class e107
return self::getSingleton('eMessage', true);
}
+ /**
+ * Retrieve ajax singleton object
+ *
+ * @return e_ajax_class
+ */
+ public static function getAjax()
+ {
+ return self::getSingleton('e_ajax_class', true);
+ }
+
/**
* Retrieve Library Manager singleton object (internal use only. Use e107::library())
*
diff --git a/e107_handlers/e_ajax_class.php b/e107_handlers/e_ajax_class.php
new file mode 100644
index 000000000..39bdfe506
--- /dev/null
+++ b/e107_handlers/e_ajax_class.php
@@ -0,0 +1,286 @@
+commandSettings(array('foo' => 'bar'));
+ * // Remove 'disabled' attribute from the '#object-1' element.
+ * $commands[] = $ajax->commandInvoke('#object-1', 'removeAttr', array('disabled'));
+ * // Insert HTML content into the '#object-1' element.
+ * $commands[] = $ajax->commandInsert('#object-1', 'html', 'some html content');
+ *
+ * // This method returns with data in JSON format. It sets the header for
+ * // JavaScript output.
+ * $ajax->response($commands);
+ * @endcode
+ */
+class e_ajax_class
+{
+
+ /**
+ * Constructor.
+ * Use {@link getInstance()}, direct instantiating is not possible for signleton
+ * objects.
+ */
+ public function __construct()
+ {
+ }
+
+ /**
+ * @return void
+ */
+ protected function _init()
+ {
+ }
+
+ /**
+ * Cloning is not allowed.
+ */
+ private function __clone()
+ {
+ }
+
+ /**
+ * Returns data in JSON format.
+ *
+ * This function should be used for JavaScript callback functions returning
+ * data in JSON format. It sets the header for JavaScript output.
+ *
+ * @param $var
+ * (optional) If set, the variable will be converted to JSON and output.
+ */
+ public function response($var = null)
+ {
+ // We are returning JSON, so tell the browser.
+ header('Content-Type: application/json');
+
+ if(isset($var))
+ {
+ echo $this->render($var);
+ }
+ }
+
+ /**
+ * Renders a commands array into JSON.
+ *
+ * @param array $commands
+ * A list of macro commands generated by the use of e107::ajax()->command*
+ * methods.
+ *
+ * @return string
+ */
+ public function render($commands = array())
+ {
+ $tp = e107::getParser();
+ return $tp->toJSON($commands);
+ }
+
+ /**
+ * Creates an Ajax 'alert' command.
+ *
+ * The 'alert' command instructs the client to display a JavaScript alert
+ * dialog box.
+ *
+ * @param $text
+ * The message string to display to the user.
+ *
+ * @return array
+ * An array suitable for use with the e107::ajax()->render() function.
+ */
+ public function commandAlert($text)
+ {
+ return array(
+ 'command' => 'alert',
+ 'text' => $text,
+ );
+ }
+
+ /**
+ * Creates an Ajax 'insert' command.
+ *
+ * This command instructs the client to insert the given HTML.
+ *
+ * @param $target
+ * A jQuery target selector.
+ * @param $method
+ * Selected method fo DOM manipulation:
+ * 'replaceWith', 'append', 'prepend', 'before', 'after', 'html'
+ * @param $html
+ * The data to use with the jQuery method.
+ *
+ * @return array
+ * An array suitable for use with the e107::ajax()->render() function.
+ */
+ public function commandInsert($target, $method, $html)
+ {
+ return array(
+ 'command' => 'insert',
+ 'method' => $method,
+ 'target' => $target,
+ 'data' => $html,
+ );
+ }
+
+ /**
+ * Creates an Ajax 'remove' command.
+ *
+ * The 'remove' command instructs the client to use jQuery's remove() method
+ * to remove each of elements matched by the given target, and everything
+ * within them.
+ *
+ * @param $target
+ * A jQuery selector string.
+ *
+ * @return array
+ * An array suitable for use with the e107::ajax()->render() function.
+ *
+ * @see http://docs.jquery.com/Manipulation/remove#expr
+ */
+ public function commandRemove($target)
+ {
+ return array(
+ 'command' => 'remove',
+ 'target' => $target,
+ );
+ }
+
+ /**
+ * Creates an Ajax 'css' command.
+ *
+ * The 'css' command will instruct the client to use the jQuery css() method
+ * to apply the CSS arguments to elements matched by the given target.
+ *
+ * @param $target
+ * A jQuery selector string.
+ * @param $argument
+ * An array of key/value pairs to set in the CSS for the target.
+ *
+ * @return array
+ * An array suitable for use with the e107::ajax()->render() function.
+ *
+ * @see http://docs.jquery.com/CSS/css#properties
+ */
+ public function commandCSS($target, $argument)
+ {
+ return array(
+ 'command' => 'css',
+ 'target' => $target,
+ 'argument' => $argument,
+ );
+ }
+
+ /**
+ * Creates an Ajax 'settings' command.
+ *
+ * The 'settings' command instructs the client to extend e107.settings with
+ * the given array.
+ *
+ * @param $settings
+ * An array of key/value pairs to add to the settings. This will be utilized
+ * for all commands after this if they do not include their own settings
+ * array.
+ *
+ * @return array
+ * An array suitable for use with the e107::ajax()->render() function.
+ */
+ public function commandSettings($settings)
+ {
+ return array(
+ 'command' => 'settings',
+ 'settings' => $settings,
+ );
+ }
+
+ /**
+ * Creates an Ajax 'data' command.
+ *
+ * The 'data' command instructs the client to attach the name=value pair of
+ * data to the target via jQuery's data cache.
+ *
+ * @param $target
+ * A jQuery selector string.
+ * @param $name
+ * The name or key (in the key value pair) of the data attached to this
+ * target.
+ * @param $value
+ * The value of the data. Not just limited to strings can be any format.
+ *
+ * @return array
+ * An array suitable for use with the e107::ajax()->render() function.
+ *
+ * @see http://docs.jquery.com/Core/data#namevalue
+ */
+ public function commandData($target, $name, $value)
+ {
+ return array(
+ 'command' => 'data',
+ 'target' => $target,
+ 'name' => $name,
+ 'value' => $value,
+ );
+ }
+
+ /**
+ * Creates an Ajax 'invoke' command.
+ *
+ * The 'invoke' command will instruct the client to invoke the given jQuery
+ * method with the supplied arguments on the elements matched by the given
+ * target. Intended for simple jQuery commands, such as attr(), addClass(),
+ * removeClass(), toggleClass(), etc.
+ *
+ * @param $target
+ * A jQuery selector string.
+ * @param $method
+ * The jQuery method to invoke.
+ * @param $arguments
+ * (optional) A list of arguments to the jQuery $method, if any.
+ *
+ * @return array
+ * An array suitable for use with the e107::ajax()->render() function.
+ */
+ public function commandInvoke($target, $method, array $arguments = array())
+ {
+ return array(
+ 'command' => 'invoke',
+ 'target' => $target,
+ 'method' => $method,
+ 'arguments' => $arguments,
+ );
+ }
+
+}
\ No newline at end of file
diff --git a/e107_web/js/core/all.jquery.js b/e107_web/js/core/all.jquery.js
index 434101af6..6e85c635a 100644
--- a/e107_web/js/core/all.jquery.js
+++ b/e107_web/js/core/all.jquery.js
@@ -117,208 +117,50 @@ var e107 = e107 || {'settings': {}, 'behaviors': {}};
});
/**
- * Behavior to attach a click event to links with .e-ajax class.
- *
- * @type {{attach: Function}}
+ * Attaches the AJAX behavior to each AJAX form/page elements. E107 uses
+ * this behavior to enhance form/page elements with .e-ajax class.
*/
- e107.behaviors.eAjaxLink = {
+ e107.behaviors.eAJAX = {
attach: function (context, settings)
{
- $(context).find('a.e-ajax').once('e-ajax-link').each(function ()
+ $(context).find('.e-ajax').once('e-ajax').each(function ()
{
- $(this).click(function ()
- {
- // Old way - href='myscript.php#id-to-target
- var href = $(this).attr("href");
- // Target container for result.
- var target = $(this).attr("data-target");
- // Image to show loading.
- var loading = $(this).attr('data-loading');
- // If this is a navigation controller, e.g. pager...
- var nav = $(this).attr('data-nav-inc');
- // Method: 'replaceWith', 'append', 'prepend', 'before', 'after', 'html' (default).
- var method = $(this).attr('data-method');
+ var $this = $(this);
+ var event = $this.attr('data-event') || e107.callbacks.getDefaultEventHandler($this);
- if(nav != null)
+ $this.on(event, function ()
+ {
+ var $element = $(this);
+
+ var ajaxOptions = {
+ // URL for Ajax request.
+ url: $element.attr('data-src'),
+ // Ajax type: POST or GET.
+ type: $element.attr('data-ajax-type'),
+ // Target container for result.
+ target: $element.attr("data-target"),
+ // Method: 'replaceWith', 'append', 'prepend', 'before', 'after', 'html' (default).
+ method: $element.attr('data-method'),
+ // Image to show loading.
+ loading: $element.attr('data-loading'),
+ // If this is a navigation controller, e.g. pager.
+ nav: $element.attr('data-nav-inc'),
+ // Old way - href='myscript.php#id-to-target.
+ href: $element.attr("href")
+ };
+
+ // If this is a navigation controller, e.g. pager.
+ if(ajaxOptions.nav != null)
{
// Modify data-src value for next/prev. 'from='
e107.callbacks.eNav(this, '.e-ajax');
+ // Update URL for Ajax request.
+ ajaxOptions.url = $element.attr('data-src');
+ // Set Ajax type to "GET".
+ ajaxOptions.type = 'GET';
}
- // URL for Ajax request.
- var handler = $(this).attr("data-src");
-
- var $target = $("#" + target);
- var html = null; // Ajax result.
- var $loadingImage = null;
-
- // TODO: set default loading icon?
- if(loading != null)
- {
- $loadingImage = $("
");
- $(this).after($loadingImage);
- }
-
- if(target == null || handler == null) // Old way - href='myscript.php#id-to-target
- {
- if(href != null)
- {
- var tmp = href.split('#');
- var id = tmp[1];
-
- if(handler == null)
- {
- handler = tmp[0];
- }
-
- if(target == null)
- {
- $target = $('#' + id);
- }
- }
- }
-
- $.ajax({
- type: 'GET',
- url: handler,
- complete: function ()
- {
- if($loadingImage)
- {
- $loadingImage.remove();
- }
- },
- success: function (data)
- {
- switch(method)
- {
- case 'replaceWith':
- html = $.parseHTML(data);
- $target.replaceWith(html);
- break;
-
- case 'append':
- html = $.parseHTML(data);
- $target.append(html);
- break;
-
- case 'prepend':
- html = $.parseHTML(data);
- $target.prepend(html);
- break;
-
- case 'before':
- html = $.parseHTML(data);
- $target.before(html);
- break;
-
- case 'after':
- html = $.parseHTML(data);
- $target.after(html);
- break;
-
- case 'html':
- default:
- $target.html(data).hide().show("slow");
- break;
- }
-
- // Attach all registered behaviors to the new content.
- e107.attachBehaviors();
- }
- });
-
- return false;
- });
- });
- }
- };
-
- /**
- * Behavior to attach a change event to selects with .e-ajax class.
- *
- * @type {{attach: Function}}
- */
- e107.behaviors.eAjaxSelect = {
- attach: function (context, settings)
- {
- $(context).find('select.e-ajax').once('e-ajax-select').each(function ()
- {
- $(this).on('change', function ()
- {
- var form = $(this).closest("form").attr('id');
-
- // Target container for result.
- var target = $(this).attr("data-target");
- // Image to show loading.
- var loading = $(this).attr('data-loading');
- // URL for Ajax request.
- var handler = $(this).attr('data-src');
- // Method: 'replaceWith', 'append', 'prepend', 'before', 'after', 'html' (default).
- var method = $(this).attr('data-method');
-
- var data = $('#' + form).serialize();
- var $target = $("#" + target);
- var html = null;
- var $loadingImage = null;
-
- // TODO: set default loading icon?
- if(loading != null)
- {
- $loadingImage = $("
");
- $(this).after($loadingImage);
- }
-
- $.ajax({
- type: 'post',
- url: handler,
- data: data,
- complete: function ()
- {
- if($loadingImage)
- {
- $loadingImage.remove();
- }
- },
- success: function (data)
- {
- switch(method)
- {
- case 'replaceWith':
- html = $.parseHTML(data);
- $target.replaceWith(html);
- break;
-
- case 'append':
- html = $.parseHTML(data);
- $target.append(html);
- break;
-
- case 'prepend':
- html = $.parseHTML(data);
- $target.prepend(html);
- break;
-
- case 'before':
- html = $.parseHTML(data);
- $target.before(html);
- break;
-
- case 'after':
- html = $.parseHTML(data);
- $target.after(html);
- break;
-
- case 'html':
- default:
- $target.html(data).hide().show("slow");
- break;
- }
-
- // Attach all registered behaviors to the new content.
- e107.attachBehaviors();
- }
- });
+ e107.callbacks.ajaxRequestHandler($element, ajaxOptions);
return false;
});
@@ -452,6 +294,285 @@ var e107 = e107 || {'settings': {}, 'behaviors': {}};
}
};
+ /**
+ * Get a reasonable default event handler for a (jQuery) element.
+ *
+ * @param $element
+ * JQuery element.
+ */
+ e107.callbacks.getDefaultEventHandler = function ($element)
+ {
+ var event = 'click'; // Default event handler.
+ var tag = $element.prop("tagName").toLowerCase();
+
+ if(tag == 'input')
+ {
+ var type = $element.attr('type').toLowerCase();
+
+ switch(type)
+ {
+ case 'submit':
+ case 'button':
+ // Pressing the ENTER key within a textfield triggers the click event of
+ // the form's first submit button. Triggering Ajax in this situation
+ // leads to problems, like breaking autocomplete textfields, so we bind
+ // to mousedown instead of click.
+ event = 'mousedown';
+ break;
+
+ case 'radio':
+ case 'checkbox':
+ event = 'change';
+ break;
+
+ // text, number, password, date, datetime, datetime-local, month, week, time,
+ // email, search, tel, url, color, range
+ default:
+ event = 'blur';
+ break;
+ }
+ }
+ else
+ {
+ switch(tag)
+ {
+ case 'button':
+ // Pressing the ENTER key within a textfield triggers the click event of
+ // the form's first submit button. Triggering Ajax in this situation
+ // leads to problems, like breaking autocomplete textfields, so we bind
+ // to mousedown instead of click.
+ event = 'mousedown';
+ break;
+
+ case 'select':
+ event = 'change';
+ break;
+
+ case 'textarea':
+ event = 'blur';
+ break;
+ }
+ }
+
+ return event;
+ };
+
+ /**
+ * Handler fo Ajax requests.
+ *
+ * @param $element
+ * JQuery element which fired the event.
+ * @param options
+ * An object with Ajax request options.
+ */
+ e107.callbacks.ajaxRequestHandler = function ($element, options)
+ {
+ var $loadingImage = null;
+
+ // Loading image.
+ if(options.loading != null)
+ {
+ $loadingImage = $(options.loading);
+ $element.after($loadingImage);
+ }
+
+ // Old way - href='myscript.php#id-to-target.
+ if(options.target == null || options.url == null)
+ {
+ if(options.href != null)
+ {
+ var tmp = options.href.split('#');
+ var id = tmp[1];
+
+ if(options.url == null)
+ {
+ options.url = tmp[0];
+ }
+
+ if(options.target == null)
+ {
+ options.target = id;
+ }
+ }
+ }
+
+ // BC.
+ if(options.target && options.target.charAt(0) != "#" && options.target.charAt(0) != ".")
+ {
+ options.target = "#" + options.target;
+ }
+
+ var form = $element.closest("form").attr('id');
+ var data = $('#' + form).serialize();
+
+ $.ajax({
+ type: options.type || 'POST',
+ url: options.url,
+ data: data || '',
+ complete: function ()
+ {
+ if($loadingImage)
+ {
+ $loadingImage.remove();
+ }
+ },
+ success: function (response)
+ {
+ var $target = $(options.target);
+ var jsonObject = response;
+
+ if(typeof response == 'string')
+ {
+ try
+ {
+ jsonObject = $.parseJSON(response);
+ } catch(e)
+ {
+ // Not JSON.
+ }
+ }
+
+ if(typeof jsonObject == 'object')
+ {
+ // If result is JSON.
+ e107.callbacks.ajaxJsonResponseHandler($target, options, jsonObject);
+ }
+ else
+ {
+ // If result is a simple text/html.
+ e107.callbacks.ajaxResponseHandler($target, options, response);
+ }
+ }
+ });
+ };
+
+ /**
+ * Handler for JSON responses. Provides a series of commands that the server
+ * can request the client perform.
+ *
+ * @param $target
+ * JQuery (target) object.
+ * @param options
+ * Object with options for Ajax request.
+ * @param commands
+ * JSON object with commands.
+ */
+ e107.callbacks.ajaxJsonResponseHandler = function ($target, options, commands)
+ {
+ $(commands).each(function ()
+ {
+ var command = this;
+ // Get target selector from the response. If it is not there, default to our presets.
+ var $newtarget = command.target ? $(command.target) : $target;
+
+ switch(command.command)
+ {
+ // Command to insert new content into the DOM.
+ case 'insert':
+ var newOptions = options;
+ newOptions.method = command.method;
+ e107.callbacks.ajaxResponseHandler($newtarget, newOptions, command.data);
+ break;
+
+ // Command to remove a chunk from the page.
+ case 'remove':
+ e107.detachBehaviors($(command.target));
+ $(command.target).remove();
+ break;
+
+ // Command to provide an alert.
+ case 'alert':
+ alert(command.text, command.title);
+ break;
+
+ // Command to provide the jQuery css() function.
+ case 'css':
+ $(command.target).css(command.arguments);
+ break;
+
+ // Command to set the settings that will be used for other commands in this response.
+ case 'settings':
+ if(typeof command.settings == 'object')
+ {
+ $.extend(true, e107.settings, command.settings);
+ }
+ break;
+
+ // Command to attach data using jQuery's data API.
+ case 'data':
+ $(command.target).data(command.name, command.value);
+ break;
+
+ // Command to apply a jQuery method.
+ case 'invoke':
+ var $element = $(command.target);
+ $element[command.method].apply($element, command.arguments);
+ break;
+ }
+ });
+ };
+
+ /**
+ * Handler for text/html responses. Inserting new content into the DOM.
+ *
+ * @param $target
+ * JQuery (target) object.
+ * @param options
+ * An object with Ajax request options.
+ * @param data
+ * Text/HTML content.
+ */
+ e107.callbacks.ajaxResponseHandler = function ($target, options, data)
+ {
+ var html = null;
+
+ // If removing content from the wrapper, detach behaviors first.
+ switch(options.method)
+ {
+ case 'html':
+ case 'replaceWith':
+ e107.detachBehaviors($target);
+ break;
+ }
+
+ // Inserting content.
+ switch(options.method)
+ {
+ case 'replaceWith':
+ html = $.parseHTML(data);
+ $target.replaceWith(html);
+ break;
+
+ case 'append':
+ html = $.parseHTML(data);
+ $target.append(html);
+ break;
+
+ case 'prepend':
+ html = $.parseHTML(data);
+ $target.prepend(html);
+ break;
+
+ case 'before':
+ html = $.parseHTML(data);
+ $target.before(html);
+ break;
+
+ case 'after':
+ html = $.parseHTML(data);
+ $target.after(html);
+ break;
+
+ case 'html':
+ default:
+ $target.html(data).hide().show("slow");
+ break;
+ }
+
+ // Attach all registered behaviors to the new content.
+ e107.attachBehaviors();
+ };
+
})(jQuery);
$.ajaxSetup({