Advertisement
Guest User

Untitled

a guest
Sep 12th, 2012
225
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. /**
  2.  * AJAX Nette Framework plugin for jQuery
  3.  *
  4.  * @copyright Copyright (c) 2009, 2010 Jan Marek
  5.  * @copyright Copyright (c) 2009, 2010 David Grudl
  6.  * @copyright Copyright (c) 2012 Vojtěch Dobeš
  7.  * @license MIT
  8.  */
  9.  
  10. (function($, undefined) {
  11.  
  12. if (typeof $ != 'function') {
  13.     return console.error('nette.ajax.js: jQuery is missing, load it please');
  14. }
  15.  
  16. var nette = function () {
  17.     var inner = {
  18.         self: this,
  19.         initialized: false,
  20.         contexts: {},
  21.         on: {
  22.             init: {},
  23.             load: {},
  24.             before: {},
  25.             start: {},
  26.             success: {},
  27.             complete: {},
  28.             error: {}
  29.         },
  30.         fire: function () {
  31.             var result = true;
  32.             var args = Array.prototype.slice.call(arguments);
  33.             var props = args.shift();
  34.             var name = (typeof props == 'string') ? props : props.name;
  35.             var off = (typeof props == 'object') ? props.off || {} : {};
  36.             args.push(inner.self);
  37.             $.each(inner.on[name], function (index, reaction) {
  38.                 if (reaction === undefined || $.inArray(index, off) !== -1) return true;
  39.                 var temp = reaction.apply(inner.contexts[index], args);
  40.                 return result = (temp === undefined || temp);
  41.             });
  42.             return result;
  43.         },
  44.         requestHandler: function (e) {
  45.             if (!inner.self.ajax({}, this, e)) return;
  46.         },
  47.         ext: function (callbacks, context, name) {
  48.             while (!name) {
  49.                 name = 'ext_' + Math.random();
  50.                 if (inner.context[name]) {
  51.                     name = undefined;
  52.                 }
  53.             }
  54.  
  55.             $.each(callbacks, function (event, callback) {
  56.                 inner.on[event][name] = callback;
  57.             });
  58.             inner.contexts[name] = $.extend(context ? context : {}, {
  59.                 name: function () {
  60.                     return name;
  61.                 },
  62.                 ext: function (name, force) {
  63.                     var ext = inner.contexts[name];
  64.                     if (!ext && force) throw "Extension '" + this.name() + "' depends on disabled extension '" + name + "'.";
  65.                     return ext;
  66.                 }
  67.             });
  68.         }
  69.     };
  70.  
  71.     /**
  72.      * Allows manipulation with extensions.
  73.      * When called with 1. argument only, it returns extension with given name.
  74.      * When called with 2. argument equal to false, it removes extension entirely.
  75.      * When called with 2. argument equal to hash of event callbacks, it adds new extension.
  76.      *
  77.      * @param  {string} Name of extension
  78.      * @param  {bool|object|null} Set of callbacks for any events OR false for removing extension.
  79.      * @param  {object|null} Context for added extension
  80.      * @return {$.nette|object} Provides a fluent interface OR returns extensions with given name
  81.      */
  82.     this.ext = function (name, callbacks, context) {
  83.         if (callbacks === undefined) {
  84.             return inner.contexts[name];
  85.         } else if (!callbacks) {
  86.             $.each(['init', 'load', 'before', 'start', 'success', 'complete', 'error'], function (index, event) {
  87.                 inner.on[event][name] = undefined;
  88.             });
  89.             inner.contexts[name] = undefined;
  90.         } else if (typeof name == 'string' && inner.contexts[name] !== undefined) {
  91.             throw 'Cannot override already registered nette-ajax extension.';
  92.         } else if (typeof name == 'object') {
  93.             inner.ext(name, callbacks);
  94.         } else {
  95.             inner.ext(callbacks, context, name);
  96.         }
  97.         return this;
  98.     };
  99.  
  100.     /**
  101.      * Initializes the plugin:
  102.      * - fires 'init' event, then 'load' event
  103.      * - when called with any arguments, it will override default 'init' extension
  104.      *   with provided callbacks
  105.      *
  106.      * @param  {function|object|null} Callback for 'load' event or entire set of callbacks for any events
  107.      * @param  {object|null} Context provided for callbacks in first argument
  108.      * @return {$.nette} Provides a fluent interface
  109.      */
  110.     this.init = function (load, loadContext) {
  111.         if (inner.initialized) throw 'Cannot initialize nette-ajax twice.';
  112.  
  113.         if (typeof load == 'function') {
  114.             this.ext('init', null);
  115.             this.ext('init', {
  116.                 load: load
  117.             }, loadContext);
  118.         } else if (typeof load == 'object') {
  119.             this.ext('init', null);
  120.             this.ext('init', load, loadContext);
  121.         } else if (load !== undefined) {
  122.             throw 'Argument of init() can be function or function-hash only.';
  123.         }
  124.  
  125.         inner.initialized = true;
  126.  
  127.         inner.fire('init');
  128.         this.load();
  129.         return this;
  130.     };
  131.  
  132.     /**
  133.      * Fires 'load' event
  134.      *
  135.      * @return {$.nette} Provides a fluent interface
  136.      */
  137.     this.load = function () {
  138.         inner.fire('load', inner.requestHandler);
  139.         return this;
  140.     };
  141.  
  142.     /**
  143.      * Executes AJAX request. Attaches listeners and events.
  144.      *
  145.      * @param  {object} settings
  146.      * @param  {Element|null} ussually Anchor or Form
  147.      * @param  {event|null} event causing the request
  148.      * @return {jqXHR|null}
  149.      */
  150.     this.ajax = function (settings, ui, e) {
  151.         if (!settings.nette && ui && e) {
  152.             var $el = $(ui);
  153.             var analyze = settings.nette = {
  154.                 ui: ui,
  155.                 el: $el,
  156.                 isForm: $el.is('form'),
  157.                 isSubmit: $el.is('input[type=submit]'),
  158.                 isImage: $el.is('input[type=image]'),
  159.                 form: null
  160.             };
  161.  
  162.             if (analyze.isSubmit || analyze.isImage) {
  163.                 analyze.form = analyze.el.closest('form');
  164.             } else if (analyze.isForm) {
  165.                 analyze.form = analyze.el;
  166.             }
  167.  
  168.             if (!settings.url) {
  169.                 settings.url = analyze.form ? analyze.form.attr('action') : ui.href;
  170.             }
  171.             if (!settings.type) {
  172.                 settings.type = analyze.form ? analyze.form.attr('method') : 'get';
  173.             }
  174.  
  175.             if ($el.is('[data-ajax-off]')) {
  176.                 settings.off = $el.data('ajaxOff');
  177.                 if (typeof settings.off == 'string') setting.off = [settings.off];
  178.             }
  179.         }
  180.  
  181.         if (!inner.fire({
  182.             name: 'before',
  183.             off: settings.off || {}
  184.         }, settings, ui, e)) return;
  185.  
  186.         return $.ajax($.extend({
  187.             beforeSend: function (xhr) {
  188.                 return inner.fire({
  189.                     name: 'start',
  190.                     off: settings.off || {}
  191.                 }, xhr, settings);
  192.             }
  193.         }, settings)).done(function (payload) {
  194.             inner.fire({
  195.                 name: 'success',
  196.                 off: settings.off || {}
  197.             }, payload);
  198.         }).fail(function (xhr, status, error) {
  199.             inner.fire({
  200.                 name :'error',
  201.                 off: settings.off || {}
  202.             }, xhr, status, error);
  203.         }).always(function () {
  204.             inner.fire({
  205.                 name: 'complete',
  206.                 off: settings.off || {}
  207.             });
  208.         });
  209.     };
  210. };
  211.  
  212. $.nette = new ($.extend(nette, $.nette ? $.nette : {}));
  213.  
  214. $.fn.netteAjax = function (e, options) {
  215.     return $.nette.ajax(options || {}, this[0], e);
  216. };
  217.  
  218. $.nette.ext('validation', {
  219.     before: function (settings, ui, e) {
  220.         if (!settings.nette || !e) return true;
  221.         else var analyze = settings.nette;
  222.  
  223.         var validate = $.extend({
  224.             keys: true,
  225.             url: true,
  226.             form: true
  227.         }, settings.validate || (function () {
  228.             if (!analyze.el.is('[data-ajax-validate]')) return;
  229.             var attr = analyze.el.data('ajaxValidate');
  230.             if (attr === false) return {
  231.                 keys: false,
  232.                 url: false,
  233.                 form: false
  234.             }; else if (typeof attr == 'object') return attr;
  235.         })() || {});
  236.  
  237.         if (validate.keys) {
  238.             // thx to @vrana
  239.             var explicitNoAjax = e.button || e.ctrlKey || e.shiftKey || e.altKey || e.metaKey;
  240.  
  241.             if (analyze.form) {
  242.                 if (explicitNoAjax && analyze.isSubmit) {
  243.                     this.explicitNoAjax = true;
  244.                     return false;
  245.                 } else if (analyze.isForm && this.explicitNoAjax) {
  246.                     this.explicitNoAjax = false;
  247.                     return false;
  248.                 }
  249.             } else if (explicitNoAjax) return false;
  250.         }
  251.  
  252.         if (validate.form && analyze.form && !((analyze.isSubmit || analyze.isImage) && analyze.el.attr('formnovalidate') !== undefined)) {
  253.             if (analyze.form.get(0).onsubmit && !analyze.form.get(0).onsubmit()) {
  254.                 e.stopImmediatePropagation();
  255.                 e.preventDefault();
  256.                 return false;
  257.             }
  258.         }
  259.  
  260.         if (validate.url) {
  261.             // thx to @vrana
  262.             if (/:|^#/.test(analyze.form ? settings.url : analyze.el.attr('href'))) return false;
  263.         }
  264.  
  265.         e.stopPropagation();
  266.         e.preventDefault();
  267.         return true;
  268.     }
  269. }, {
  270.     explicitNoAjax: false
  271. });
  272.  
  273. $.nette.ext('forms', {
  274.     success: function (payload) {
  275.         var snippets;
  276.         if (!window.Nette || !payload.snippets || !(snippets = this.ext('snippets'))) return;
  277.  
  278.         for (var id in payload.snippets) {
  279.             snippets.getElement(id).find('form').each(function() {
  280.                 window.Nette.initForm(this);
  281.             });
  282.         }
  283.     },
  284.     before: function (settings, ui, e) {
  285.         var analyze = settings.nette;
  286.         if (!analyze || !analyze.form) return;
  287.  
  288.         settings.data = settings.data || {};
  289.  
  290.         if (analyze.isSubmit) {
  291.             settings.data[analyze.el.attr('name')] = analyze.el.val() || '';
  292.         } else if (analyze.isImage) {
  293.             var offset = analyze.el.offset();
  294.             var name = analyze.el.attr('name');
  295.             var dataOffset = [ Math.max(0, e.pageX - offset.left), Math.max(0, e.pageY - offset.top) ];
  296.  
  297.             if (name.indexOf('[', 0) !== -1) { // inside a container
  298.                 settings.data[name] = dataOffset;
  299.             } else {
  300.                 settings.data[name + '.x'] = dataOffset[0];
  301.                 settings.data[name + '.y'] = dataOffset[1];
  302.             }
  303.         }
  304.  
  305.         settings.data = analyze.form.serialize() + '&' + $.param(settings.data);
  306.     }
  307. });
  308.  
  309. // default snippet handler
  310. $.nette.ext('snippets', {
  311.     success: function (payload) {
  312.         if (payload.snippets) {
  313.             for (var i in payload.snippets) {
  314.                 this.updateSnippet(i, payload.snippets[i]);
  315.             }
  316.         }
  317.     }
  318. }, {
  319.     updateSnippet: function (id, html) {
  320.         var $el = this.getElement(id);
  321.         // Fix for setting document title in IE
  322.         if ($el.eq(0).tagName == 'TITLE') {
  323.             document.title = html;
  324.         } else {
  325.             this.applySnippet($el, html);
  326.         }
  327.     },
  328.     getElement: function (id) {
  329.         return $('#' + this.escapeSelector(id));
  330.     },
  331.     applySnippet: function ($el, html) {
  332.         $el.html(html);
  333.     },
  334.     escapeSelector: function (selector) {
  335.         // thx to @uestla (https://github.com/uestla)
  336.         return selector.replace(/[\!"#\$%&'\(\)\*\+,\.\/:;<=>\?@\[\\\]\^`\{\|\}~]/g, '\\$&');
  337.     }
  338. });
  339.  
  340. // support $this->redirect()
  341. $.nette.ext('redirect', {
  342.     success: function (payload) {
  343.         if (payload.redirect) {
  344.             window.location.href = payload.redirect;
  345.             return false;
  346.         }
  347.     }
  348. });
  349.  
  350. // change URL (requires HTML5)
  351. if (!!(window.history && history.pushState)) { // check borrowed from Modernizr
  352.     $.nette.ext('history', {
  353.         before: function (settings, ui) {
  354.             var $el = $(ui);
  355.             if ($el.is('a')) {
  356.                 this.href = ui.href;
  357.             }
  358.         },
  359.         success: function (payload) {
  360.             if (payload.url) {
  361.                 this.href = payload.url;
  362.             }
  363.             if (!payload.signal && this.href) {
  364.                 history.pushState({href: this.href}, '', this.href);
  365.             }
  366.         }
  367.     }, {href: null});
  368. }
  369.  
  370. // current page state
  371. $.nette.ext('state', {
  372.     success: function (payload) {
  373.         if (payload.state) {
  374.             this.state = payload.state;
  375.         }
  376.     }
  377. }, {state: null});
  378.  
  379. // abort last request if new started
  380. $.nette.ext('unique', {
  381.     start: function (xhr) {
  382.         if (this.xhr) {
  383.             this.xhr.abort();
  384.         }
  385.         this.xhr = xhr;
  386.     },
  387.     complete: function () {
  388.         this.xhr = null;
  389.     }
  390. }, {xhr: null});
  391.  
  392. // option to abort by ESC (thx to @vrana)
  393. $.nette.ext('abort', {
  394.     init: function () {
  395.         $('body').keydown($.proxy(function (e) {
  396.             if (this.xhr && (e.keyCode == 27 // Esc
  397.             && !(e.ctrlKey || e.shiftKey || e.altKey || e.metaKey))
  398.             ) {
  399.                 this.xhr.abort();
  400.             }
  401.         }, this));
  402.     },
  403.     start: function (xhr) {
  404.         this.xhr = xhr;
  405.     },
  406.     complete: function () {
  407.         this.xhr = null;
  408.     }
  409. }, {xhr: null});
  410.  
  411. $.nette.ext({
  412.         start: function (xhr, settings) {
  413.                 var question = settings.nette.el.data('confirm');
  414.                 if (question) {
  415.                         return confirm(question);
  416.                 }
  417.         }
  418. });
  419.  
  420. // default ajaxification (can be overridden in init())
  421. $.nette.ext('init', {
  422.     load: function (rh) {
  423.         $(this.linkSelector).off('click.nette', rh).on('click.nette', rh);
  424.         var $forms = $(this.formSelector);
  425.         $forms.off('submit.nette', rh).on('submit.nette', rh);
  426.         $forms.off('click.nette', ':image', rh).on('click.nette', ':image', rh);
  427.         $forms.off('click.nette', ':submit', rh).on('click.nette', ':submit', rh);
  428.  
  429.         var buttonSelector = this.buttonSelector;
  430.         $(buttonSelector).each(function () {
  431.             $(this).closest('form')
  432.                 .off('click.nette', buttonSelector, rh)
  433.                 .on('click.nette', buttonSelector, rh);
  434.         });
  435.     },
  436.     success: function () {
  437.         $.nette.load();
  438.     }
  439. }, {
  440.     linkSelector: 'a.ajax',
  441.     formSelector: 'form.ajax',
  442.     buttonSelector: 'input.ajax[type="submit"], input.ajax[type="image"]'
  443. });
  444.  
  445. })(window.jQuery);
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement