Revision: 51269
Initial Code
Initial URL
Initial Description
Initial Title
Initial Tags
Initial Language
at September 20, 2011 00:48 by damientailhades
Initial Code
/* JQuery Hijax plugin: ~~~~~~~~~~~~~~~~~~~~ @product JQuery Hijax plugin @version 1.0.0 @copyright Copyright (c) 2010 Yaron Tadmor @site http://www.yarontadmor.co.cc/hijax @license GPL license (http://www.gnu.org/licenses/gpl.html) @requires jquery.history.js Revision History: ~~~~~~~~~~~~~~~~ 0.9.5 - Fixed bug on IE (extra comma in default option list). 0.9.6 - Added support for changing title on Ajax - Added support for proper JS handling in Hijaxed links via _hijax_ready. - Minor bug fixes for end-cases of IE. 0.9.7 - Changed title handling so title is in "title" tag (instead of a div) - Use the content of the source element instead of the element itself 0.9.8 - Slight modification to hash saving code. Solves bug of going back and forward to the last page - Fixed a bug of .live() for [hijax*=] on FireFox 0.9.9 - Changed _hijax_ready to be an array and not a function. - Changed regex script, to a more efficient version, due to problems with FF4 and Chrom 10 - Replace hash delimiter from "/" to ":" to allow paths in URL 1.0.0 - Replaced dontAjaxInitialPage parameter of init() with initialState. - Hande sync of multiple hijaxing (clicking a link before ajax operation ended) NOTE: start/end callbacks must take into account that start might be called before end has ended. - adde "fail" parameter to endCB. - Removed formHref. Forms url must distinguish form submit and history load via form params. - Added support for real hash tags which cause scrolling TODO: - take care of HTML validity issues. - handle google #! - Fix real hash support scrolling (scroll only after loaded when history changes) License and Disclaimer: ~~~~~~~~~~~~~~~~~~~~~~ This software is licensed under the GPL open source license. You may distribute it freely, and use it in your own software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPLICIT OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * - internal use only trg : {[startCB: <start load function>, ] [endCB: <end load callback>, ] url: <url to load>, - src: <source ID in returned html>, *[force: <force data reload even if url/src didn't change>,] *[formData: <if form a serialized version of form's data>] } targets: { <array of trg objects> } options: { trgs: { <array of trg objects> }, defaults: { src: <default source>, (used for targets with no source reference) url: <default url>, (used for targets with no URL) startCB: <default start load function>, endCB: <default end load function>, }, defaultTrg: <default target>, (used for links/forms with no target reference at all) selectionClass: <class to add for selected objects> } */ (function ($) { // global hijax params var _init = false; var _curHash = "*"; // This causes empty hash (initial page) to load default state (since ""!="*") var _numPenddingAjaxLoads = 0, _pendingRequest = null, _pendingScroll = null; // Add hijaxing programmatically to a certain link or form. (can be done by using the hijax attr) $.fn.hijax = function (targets) { if (!targets) targets = {}; return this.each (function() { var $this = $(this); // save the targets passed to us var locTargets = $.extend (true, {}, targets); this.hijaxTargets = locTargets; // register the proper event handler. if (!$this.hasClass ("hijax")) { if ($this.is ("a")) $this.click ($.fn.hijax.event); else if ($this.is ("form")) $this.submit ($.fn.hijax.event) } }); } // handles click on hijaxed link or submit $.fn.hijax.event = function (e) { if (!_init) return; e.preventDefault(); // get targets of object, and override current state with object targets var curStateTargets = $.fn.hijax.parseHash (_curHash, true); var targets = $.fn.hijax.buildTargets.call (this); targets = $.extend (true, curStateTargets, targets); _pendingScroll = ""; // calc resulting hash var newHash = $.fn.hijax.buildHash (targets); // save new hash in history, and do ajax calls // NOTE: Since targets might contain form data which is not saved in hashs, we disable // plugin for the history load and call do ajax explicitly _init = false; $.history.load (newHash); _init = true; $.fn.hijax.doAjax (targets, newHash); } // handles click on links containing hash only (href="#<element>") $.fn.hijax.hashEvent = function (e) { if (!_init) return; e.preventDefault(); // build full hash of link with current state var $this = $(this); var newHash = _curHash.split ("::"); _pendingScroll = $this.attr('href').substring(1); newHash[0] = _pendingScroll; newHash = newHash.join ("::"); // save new hash in history if (newHash == _curHash) // browser won't refresh the history so simulate it. $.fn.hijax.historyCB (newHash) else // (this will trigger doAjax()) $.history.load (newHash); //$.fn.hijax.doAjax (targets, hash); } $.fn.hijax.buildTargets = function () { $this = $(this); var targets = this.hijaxTargets; if (!targets) targets = {}; // get target=source pairs and update (or add) them var connections = $this.attr("hijax"); if (connections != undefined && connections.length == 0) connections = undefined; if (connections != undefined) connections = connections.split ("&"); for (connection in connections) { connection = connections[connection]; var split = connection.split ("="); var trg = split[0]; var src = split[1]; var newTarget = {}; newTarget[trg] = { src: src} $.extend (true, targets, newTarget); } // if no targets, use default target var noTargets = true; for (t in targets) { noTargets = false; break; } if (noTargets && $.fn.hijax.options.defaultTrg) targets[$.fn.hijax.options.defaultTrg] = {}; // Call tag specific code to handle targets if ($this.is ("a")) targets = $.fn.hijax.buildTargets.a.call (this, targets); else if ($this.is ("form")) targets = $.fn.hijax.buildTargets.form.call (this, targets); // add defaults: for (t in targets) targets[t] = $.extend (true, {}, $.fn.hijax.options.trgs[t], targets[t]); return (targets); } $.fn.hijax.buildTargets.a = function (targets) { $this = $(this); // get url and set it to all requested targets var url = $this.attr("href"); for (trg in targets) $.extend (true, targets[trg], { url: url }); // targets[trg].url = url; return (targets); } $.fn.hijax.buildTargets.form = function (targets) { $this = $(this); // url, alternative href and form data and set to targets var url = $this.attr("action"); var formData = $this.serialize(); for (trg in targets) $.extend (true, targets[trg], { url: url, formData: formData }); return (targets); } $.fn.hijax.buildHash = function (targets) { var hash = ""; for (trg in targets) { var trgData = targets[trg]; // if no sourc element or url available, we can't process the target if (!trgData.src || !trgData.url) continue; hash += "::" + trg; hash += ":" + trgData.src; hash += ":" + trgData.url; } return (hash); } $.fn.hijax.parseHash = function (hash, withDefaults) { // load the default state var targets = {}; if (withDefaults) targets = $.extend (true, {}, $.fn.hijax.options.trgs); // parse pars of hash parts = hash.split ("::"); parts.shift(); // will always be empty, or not interesting for (part in parts) { part = parts[part]; subParts = part.split (":", 3); var trg = subParts[0]; var src = subParts[1]; var url = subParts[2]; var newTarget = {}; newTarget[trg] = {url: url, src: src}; $.extend (true, targets, newTarget); } return (targets); } $.fn.hijax.doAjax = function (targets, hash) { // if there are still pending requests, wait until they end. if (_numPenddingAjaxLoads > 0) { _pendingRequest = { targets: targets, hash: hash }; return; } // reset pending ajax _numPenddingAjaxLoads = 0; // get current state var curTargets = $.fn.hijax.parseHash (_curHash, false); // loop and update all targets for (trg in targets) { var trgData = targets[trg]; // if target hasn't changed and was not forced, ignore it var curTrgData = curTargets[trg]; if (curTrgData && trgData.force != true && trgData.src == curTrgData.src && trgData.url == curTrgData.url && !trgData.formData) continue; if (!trgData.src || !trgData.url) continue; // add "ajax" var to URL var url = trgData.url; if (url.search (/?/) == -1) url += "?ajax"; else url += "&ajax"; $.fn.hijax.doAjaxHelper (url, trg, trgData); } if (_numPenddingAjaxLoads == 0) $.fn.hijax.allAjaxLoaded(); // change state _curHash = hash; $.fn.hijax.doSelection(targets); return; } $.fn.hijax.doAjaxHelper = function (url, trg, trgData) { _numPenddingAjaxLoads++; // setup local data copies for internal functions var trgDataLoc = $.extend (true, {}, trgData); var trgLocal = trg; var $trgLocal = $("#"+trgLocal); trgDataLoc.startCB.call ($trgLocal, function () { var ajaxLoader = $.post(url, trgDataLoc.formData) .success (function (data, status, res) { var regex_script = /<scriptb[^<]*(?:(?!</script>)<[^<]*)*</script>/gi; var regex_head = /<headb[^<]*(?:(?!</head>)<[^<]*)*</head>/gi; var regex_title = /<titleb[^<]*(?:(?!</title>)<[^<]*)*</title>/gi; // extract title data, and remove head var titleData = data.match (regex_title); data = data.replace (regex_head, ""); // append result to DOM. if (trgDataLoc.src && trgDataLoc.src.length) { var $tmpDiv = $("<div />").append(data); $trgLocal.html ($tmpDiv.find("#"+trgDataLoc.src).contents()); } else $trgLocal.html (data); // copy title data var trgTitleAttr = "hijax"+trgLocal; if (titleData) { titleData = titleData[0]; $titleData = $(titleData); var titleText = $titleData.attr("hijaxTitle"); if (titleText) $("title").attr ("hijaxTitle", titleText); var trgTitleText = $titleData.attr (trgTitleAttr); if (trgTitleText) $("title").attr (trgTitleAttr, trgTitleText); } // trigger the loaded event for the target var $trg = $("#"+trgLocal); $trg.trigger ('hijaxReadyEvent'); // setup title $.fn.hijax.doTitle(); // end callback trgDataLoc.endCB.call($trgLocal, true); }) .error (function() { // end callback trgDataLoc.endCB.call($trgLocal, false); }) .complete (function() { _numPenddingAjaxLoads--; if (_numPenddingAjaxLoads == 0) $.fn.hijax.allAjaxLoaded(); }); }); } $.fn.hijax.allAjaxLoaded = function () { // if there's a pending request, load it. if (_pendingRequest) { var tmp = _pendingRequest; _pendingRequest = null; $.fn.hijax.doAjax (tmp.targets, tmp.hash); } // handle scrolling if (_pendingScroll) { if (typeof (_pendingScroll) === typeof ("")) { if (_pendingScroll.length > 0) { var $targetElem = $("#"+_pendingScroll); if ($targetElem[0]) { var offset = $targetElem.offset(); $('html, body').scrollTop (offset.top); } } } else { $('html,body').scrollTop(_pendingScroll) } _pendingScroll = null; } } // handles adding selection attribute by loaded page $.fn.hijax.doSelection = function (curTargets) { $("[hijax*=], [hijaxTargets*=]").each (function() { var objTargets = $.fn.hijax.buildTargets.call (this); var match = true; var hasTargets = false; for (trg in objTargets) { hasTargets = true; if (objTargets[trg].src != curTargets[trg].src || objTargets[trg].url != curTargets[trg].url || objTargets[trg].formData) { match = false; break; } } // get object to change $obj = $(this); var selectionTarget = $obj.attr("hijaxSelectionTarget"); if (selectionTarget) var $obj = $obj.closest(selectionTarget); // change selection if (match && hasTargets) $obj.addClass ($.fn.hijax.options.selectionClass); else $obj.removeClass ($.fn.hijax.options.selectionClass); }); } // handles setting the title $.fn.hijax.doTitle = function () { var $title = $("title"); // If we have hijax title on, parse all targets for the text. var titleText = $title.attr ("hijaxTitle"); if (titleText) { for (trg in $.fn.hijax.options.trgs) { // get text for target var trgTitleAtr = "hijax"+trg; var trgTitleTxt = $title.attr (trgTitleAtr); if (!trgTitleTxt) continue; // put target's text in title titleText = titleText.replace ("#"+trg, trgTitleTxt); } // if we have text for ALL titles, we can use it. if (titleText.search ("#") == -1) document.title = titleText; } } $.fn.hijax.historyCB = function (hash) { if (!_init) return; // save current scroll position var curScroll = $('body').scrollTop() // get targets by hash var targets = $.fn.hijax.parseHash (hash, true); // load the state $.fn.hijax.doAjax (targets, hash); // remember scroll position, so we can reset it back after loading // restore current scroll position until all ajax is done if (_numPenddingAjaxLoads > 0) { _pendingScroll = $('body').scrollTop(); $('html,body').scrollTop(curScroll) } } $.hijax = function (options, initialState) { if (_init) return; // update options $.extend (true, $.fn.hijax.options, options || {}); // if we have a default target, make sure it's in the trgs. if ($.fn.hijax.options.defaultTrg) { var newTarget = {}; newTarget[$.fn.hijax.options.defaultTrg] = { } $.extend (true, $.fn.hijax.options.trgs, newTarget); } // init targets with default values if needed for (trg in $.fn.hijax.options.trgs) { $.fn.hijax.options.trgs[trg] = $.extend (true, {}, $.fn.hijax.options.defaults, $.fn.hijax.options.trgs[trg]) } // init the history module if (initialState === true) initialState = $.extend (true, {}, $.fn.hijax.options.trgs); if (initialState) { _curHash = $.fn.hijax.buildHash (initialState); $.fn.hijax.doSelection (initialState); } _init = true; $.history.init($.fn.hijax.historyCB, { unescape: true }); } // BW compat $.fn.hijax.init = function (options, initialState) { alert ("HIJAX ERROR:nCode is written for an older version of the Hijax plugin.n"+ "You must change your site code accordingly:n"+ "- Init function changed from $.fn.hijax.init to $.hijaxn" + "- formHref and href attribute on form is no longer supported,n" + " You should make sure your form url knows when form was submittedn" + " and when it was loaded via history according to form parameters.n" + "nnPlease see docs for more information.nn"); } $.fn.hijax.defaultStartCB = function (cb) { cb(); } $.fn.hijax.defaultEndCB = function () { } // basic options so we have call backs. $.fn.hijax.options = {trgs: {}, defaults: { src: null, url: null, startCB: $.fn.hijax.defaultStartCB, endCB: $.fn.hijax.defaultEndCB }, defaultTrg: undefined, selectionClass: "selected" }; // Install handler to catch all hijax HTML elements (the *= is to allow empty attribute) if ($.browser.msie) { $("a[hijax*=]").live ("click", $.fn.hijax.event); $("form[hijax*=]").live ("submit", $.fn.hijax.event); } else { $("a[hijax]").live ("click", $.fn.hijax.event); $("form[hijax]").live ("submit", $.fn.hijax.event); } // install handlers to catch hash only links $("a[href^=#]").live ("click", $.fn.hijax.hashEvent); }) (jQuery);
Initial URL
Initial Description
Initial Title
jquery_hijax_js_1.0.0
Initial Tags
js, textmate, jquery
Initial Language
Other