UNPKG

intercooler

Version:

Making AJAX as easy as anchor tags

1,225 lines (1,112 loc) 41.9 kB
//////////////////////////////////// /** * Intercooler.js * * A javascript library for people who don't don't want to write a lot * of javascript. * */ var Intercooler = Intercooler || (function () { 'use strict'; // inside function for better merging //-------------------------------------------------- // Vars //-------------------------------------------------- var _MACROS = ['ic-get-from', 'ic-post-to', 'ic-put-to', 'ic-delete-from', 'ic-style-src', 'ic-attr-src', 'ic-prepend-from', 'ic-append-from']; var _remote = $; var _scrollHandler = null; var _UUID = 1; //============================================================ // Base Transition Definitions //============================================================ var _transitions = {}; var _defaultTransition = 'fadeFast'; function _defineTransition(name, def) { if(def.newContent == null) { //noinspection JSUnusedLocalSymbols def.newContent = function(parent, newContent, isReverse, replaceParent, after) { if(replaceParent) { parent.replaceWith(newContent); after(newContent); } else { parent.empty().append(newContent); after(); } } } if(def.remove == null) { def.remove = function(elt) { elt.remove(); } } if(def.show == null){ def.show = function(elt) { elt.show(); } } if(def.hide == null){ def.hide = function(elt) { elt.hide(); } } _transitions[name] = def; } _defineTransition('none', {}); _defineTransition('fadeFast', { newContent : function(parent, newContent, isReverse, replaceParent, after){ if(replaceParent) { parent.fadeOut('fast', function () { after(newContent.replaceAll(parent)); newContent.fadeIn('fast'); }); } else { var fadeTarget = (parent.children().length == parent.contents().length & parent.contents().length > 0) ? parent.children() : parent; fadeTarget.fadeOut('fast', function () { parent.empty().append(newContent); fadeTarget.hide(); after(); fadeTarget.fadeIn('fast'); }); } }, remove : function(elt) { elt.fadeOut('fast', function(){ elt.remove(); }) }, show : function(elt) { elt.fadeIn('fast'); }, hide : function(elt) { elt.fadeOut('fast'); } }); _defineTransition('prepend', { newContent : function(parent, newContent, isReverse, replaceParent, after){ newContent.hide(); parent.prepend(newContent); after(); newContent.fadeIn(); if (parent.attr('ic-limit-children')) { var limit = parseInt(parent.attr('ic-limit-children')); if (parent.children().length > limit) { parent.children().slice(limit, parent.children().length).remove(); } } } }); _defineTransition('append', { newContent : function(parent, newContent, isReverse, replaceParent, after){ newContent.hide(); parent.append(newContent); after(); newContent.fadeIn(); if (parent.attr('ic-limit-children')) { var limit = parseInt(parent.attr('ic-limit-children')); if (parent.children().length > limit) { parent.children().slice(0, parent.children().length - limit).remove(); } } } }); //============================================================ // Utility Methods //============================================================ function fingerprint(elt) { if(elt == null || elt == undefined) { return 0; } var str = elt.toString(); var fp = 0, i, chr, len; if (str.length == 0) return fp; for (i = 0, len = str.length; i < len; i++) { chr = str.charCodeAt(i); fp = ((fp << 5) - fp) + chr; fp |= 0; // Convert to 32bit integer } return fp; } function log(elt, msg, level) { if(elt == null) { elt = $('body'); } elt.trigger("log.ic", [msg, level, elt]); } function uuid() { return _UUID++; } function icSelectorFor(elt) { return "[ic-id='" + getIntercoolerId(elt) + "']"; } function findById(x) { return $("#" + x); } function parseInterval(str) { log(null, "POLL: Parsing interval string " + str, 'DEBUG'); if (str == "null" || str == "false" || str == "") { return null; } else if (str.lastIndexOf("ms") == str.length - 2) { return parseInt(str.substr(0, str.length - 2)); } else if (str.lastIndexOf("s") == str.length - 1) { return parseInt(str.substr(0, str.length - 1)) * 1000; } else { return 1000; } } function initScrollHandler() { if (_scrollHandler == null) { _scrollHandler = function () { $("[ic-trigger-on='scrolled-into-view']").each(function () { if (isScrolledIntoView($(this)) && $(this).data('ic-scrolled-into-view-loaded') != true) { $(this).data('ic-scrolled-into-view-loaded', true); fireICRequest($(this)); } }) }; $(window).scroll(_scrollHandler); } } function currentUrl() { return window.location.pathname + window.location.search + window.location.hash; } //============================================================ // Request/Parameter/Include Processing //============================================================ function getTarget(elt) { var targetValue = closestAttrValue(elt, 'ic-target'); if(targetValue && targetValue.indexOf('this.') != 0) { if(targetValue.indexOf('closest ') == 0) { return elt.closest(targetValue.substr(8)); } else { return $(targetValue); } } else { return elt; } } function handleHistory(elt, xhr, originalHtml) { if (xhr.getResponseHeader("X-IC-PushURL")) { log(elt, "X-IC-PushURL: pushing " + xhr.getResponseHeader("X-IC-PushURL"), "DEBUG"); _historySupport.pushUrl(xhr.getResponseHeader("X-IC-SetLocation"), elt, originalHtml); } else { if(closestAttrValue(elt, 'ic-push-url') == "true") { _historySupport.pushUrl(elt.attr('ic-src'), elt, originalHtml); } } } function processHeaders(elt, xhr) { elt.trigger("beforeHeaders.ic", elt, xhr); log(elt, "response headers: " + xhr.getAllResponseHeaders(), "DEBUG"); var target = null; if (xhr.getResponseHeader("X-IC-Refresh")) { var pathsToRefresh = xhr.getResponseHeader("X-IC-Refresh").split(","); log(elt, "X-IC-Refresh: refreshing " + pathsToRefresh, "DEBUG"); $.each(pathsToRefresh, function (i, str) { refreshDependencies(str.replace(/ /g, ""), elt); }); } if (xhr.getResponseHeader("X-IC-Script")) { log(elt, "X-IC-Script: evaling " + xhr.getResponseHeader("X-IC-Script"), "DEBUG"); eval(xhr.getResponseHeader("X-IC-Script")); } if (xhr.getResponseHeader("X-IC-Redirect")) { log(elt, "X-IC-Redirect: redirecting to " + xhr.getResponseHeader("X-IC-Redirect"), "DEBUG"); window.location = xhr.getResponseHeader("X-IC-Redirect"); } if (xhr.getResponseHeader("X-IC-CancelPolling") == "true") { cancelPolling(elt); } if (xhr.getResponseHeader("X-IC-Open")) { log(elt, "X-IC-Open: opening " + xhr.getResponseHeader("X-IC-Open"), "DEBUG"); window.open(xhr.getResponseHeader("X-IC-Open")); } if(xhr.getResponseHeader("X-IC-Transition")) { log(elt, "X-IC-Transition: setting transition to " + xhr.getResponseHeader("X-IC-Transition"), "DEBUG"); target = getTarget(elt); target.data("ic-tmp-transition", xhr.getResponseHeader("X-IC-Transition")); } if(xhr.getResponseHeader("X-IC-Trigger")) { log(elt, "X-IC-Trigger: found trigger " + xhr.getResponseHeader("X-IC-Trigger"), "DEBUG"); target = getTarget(elt); var triggerArgs = []; if(xhr.getResponseHeader("X-IC-Trigger-Data")){ triggerArgs = $.parseJSON(xhr.getResponseHeader("X-IC-Trigger-Data")) } target.trigger(xhr.getResponseHeader("X-IC-Trigger"), triggerArgs); } if (xhr.getResponseHeader("X-IC-Remove")) { if (elt) { target = getTarget(elt); log(elt, "X-IC-REMOVE header found.", "DEBUG"); var transition = getTransition(elt, target); transition.remove(target); } } elt.trigger("afterHeaders.ic", elt, xhr); return true; } function beforeRequest(elt) { elt.addClass('disabled'); elt.data('ic-request-in-flight', true); } function afterRequest(elt) { elt.removeClass('disabled'); elt.data('ic-request-in-flight', false); if(elt.data('ic-next-request')) { elt.data('ic-next-request')(); elt.data('ic-next-request', null); } } function replaceOrAddMethod(data, actualMethod) { var regex = /(&|^)_method=[^&]*/; var content = "&_method=" + actualMethod; if(regex.test(data)) { return data.replace(regex, content) } else { return data + "&" + content; } } function globalEval(script) { return window[ "eval" ].call(window, script); } function closestAttrValue(elt, attr) { var closestElt = $(elt).closest('[' + attr + ']'); if(closestElt) { return closestElt.attr(attr); } else { return null; } } function handleRemoteRequest(elt, type, url, data, success) { beforeRequest(elt); data = replaceOrAddMethod(data, type); // Spinner support var indicator = findIndicator(elt); var indicatorTransition = getTransition(indicator, indicator); if(indicator.length > 0) { indicatorTransition.show(indicator); } var requestId = uuid(); var requestStart = new Date(); _remote.ajax({ type: type, url: url, data: data, dataType: 'text', headers: { "Accept": "text/html-partial, */*; q=0.9", "X-IC-Request": true, "X-HTTP-Method-Override": type }, beforeSend : function(xhr, settings){ elt.trigger("beforeSend.ic", elt, data, settings, xhr); log(elt, "before AJAX request " + requestId + ": " + type + " to " + url, "DEBUG"); var onBeforeSend = closestAttrValue(elt, 'ic-on-beforeSend'); if(onBeforeSend) { globalEval('(function (data, settings, xhr) {' + onBeforeSend + '})')(data, settings, xhr); } }, success: function (data, textStatus, xhr) { elt.trigger("success.ic", elt, data, textStatus, xhr); log(elt, "AJAX request " + requestId + " was successful.", "DEBUG"); var onSuccess = closestAttrValue(elt, 'ic-on-success'); if(onSuccess) { if(globalEval('(function (data, textStatus, xhr) {' + onSuccess + '})')(data, textStatus, xhr) == false) { return; } } var target = getTarget(elt); target.data("ic-tmp-transition", closestAttrValue(elt, 'ic-transition')); // copy transition var beforeHeaders = new Date(); if (processHeaders(elt, xhr)) { log(elt, "Processed headers for request " + requestId + " in " + (new Date() - beforeHeaders) + "ms", "DEBUG"); var beforeSuccess = new Date(); var originalHtml = target.html(); success(data, textStatus, elt, xhr); handleHistory(elt, xhr, originalHtml); log(elt, "Process content for request " + requestId + " in " + (new Date() - beforeSuccess) + "ms", "DEBUG"); } elt.trigger("after.success.ic", elt, data, textStatus, xhr); target.data("ic-tmp-transition", null); }, error: function (xhr, status, str) { elt.trigger("error.ic", elt, status, str, xhr); var onError = closestAttrValue(elt, 'ic-on-error'); if(onError) { globalEval('(function (status, str, xhr) {' + onError + '})')(status, str, xhr); } log(elt, "AJAX request " + requestId + " experienced an error: " + str, "ERROR"); }, complete : function(xhr, status){ log(elt, "AJAX request " + requestId + " completed in " + (new Date() - requestStart) + "ms", "DEBUG"); afterRequest(elt); elt.trigger("complete.ic", elt, data, status, xhr); var onComplete = closestAttrValue(elt, 'ic-on-complete'); if(onComplete) { globalEval('(function (xhr, status) {' + onComplete + '})')(xhr, status); } if (indicator.length > 0) { indicatorTransition.hide(indicator); } } }) } function findIndicator(elt) { var indicator = null; if ($(elt).attr('ic-indicator')) { indicator = $($(elt).attr('ic-indicator')).first(); } else { indicator = $(elt).find(".ic-indicator").first(); if (indicator.length == 0) { var parent = closestAttrValue(elt, 'ic-indicator'); if (parent) { indicator = $(parent).first(); } } } return indicator; } function processIncludes(str) { var returnString = ""; if($.trim(str).indexOf("{") == 0) { var obj = $.parseJSON( str ); $.each(obj, function(key, value){ returnString += "&" + encodeURIComponent(key) + "=" + encodeURIComponent(value); }); } else { $(str).each(function(){ returnString += "&" + $(this).serialize(); }); } return returnString; } function getParametersForElement(elt, triggerOrigin) { var target = getTarget(elt); var str = "ic-request=true"; // if the element is in a form, include the entire form if(elt.closest('form').length > 0) { str += "&" + elt.closest('form').serialize(); } else { // otherwise include the element str += "&" + elt.serialize(); } if (elt.attr('id')) { str += "&ic-element-id=" + elt.attr('id'); } if (elt.attr('name')) { str += "&ic-element-name=" + elt.attr('name'); } if (target.attr('ic-id')) { str += "&ic-id=" + target.attr('ic-id'); } if (triggerOrigin && triggerOrigin.attr('id')) { str += "&ic-trigger-id=" + triggerOrigin.attr('id'); } if (triggerOrigin && triggerOrigin.attr('name')) { str += "&ic-trigger-name=" + triggerOrigin.attr('name'); } if (target.attr('ic-last-refresh')) { str += "&ic-last-refresh=" + target.attr('ic-last-refresh'); } if (target.attr('ic-fingerprint')) { str += "&ic-fingerprint=" + target.attr('ic-fingerprint'); } var includeAttr = closestAttrValue(elt, 'ic-include'); if (includeAttr) { str += processIncludes(includeAttr); } str += "&ic-current-url=" + encodeURIComponent(currentUrl()); log(elt, "request parameters " + str, "DEBUG"); return str; } function maybeSetIntercoolerInfo(elt) { var target = getTarget(elt); getIntercoolerId(target); maybeSetIntercoolerMetadata(target); if(elt.data('elementAdded.ic') != true){ elt.data('elementAdded.ic', true); elt.trigger("elementAdded.ic"); } } function updateIntercoolerMetaData(elt) { elt.attr('ic-fingerprint', fingerprint(elt.html())); elt.attr('ic-last-refresh', new Date().getTime()); } function maybeSetIntercoolerMetadata(elt) { if (!elt.attr('ic-fingerprint')) { updateIntercoolerMetaData(elt); } } function getIntercoolerId(elt) { if (!elt.attr('ic-id')) { elt.attr('ic-id', uuid()); } return elt.attr('ic-id'); } //============================================================ // Tree Processing //============================================================ function processNodes(elt) { processMacros(elt); processSources(elt); processPolling(elt); processTriggerOn(elt); processRemoveAfter(elt); $(elt).trigger('nodesProcessed.ic'); } function processSources(elt) { if ($(elt).is("[ic-src]")) { maybeSetIntercoolerInfo($(elt)); } $(elt).find("[ic-src]").each(function () { maybeSetIntercoolerInfo($(this)); }); } //============================================================ // Polling support //============================================================ function startPolling(elt) { if(elt.data('ic-poll-interval-id') == null) { var interval = parseInterval(elt.attr('ic-poll')); if(interval != null) { var selector = icSelectorFor(elt); var repeats = parseInt(elt.attr('ic-poll-repeats')) || -1; var currentIteration = 0; log(elt, "POLL: Starting poll for element " + selector, "DEBUG"); var timerId = setInterval(function () { var target = $(selector); elt.trigger("onPoll.ic", target); if ((target.length == 0) || (currentIteration == repeats)) { log(elt, "POLL: Clearing poll for element " + selector, "DEBUG"); clearTimeout(timerId); } else { fireICRequest(target); } currentIteration++; }, interval); elt.data('ic-poll-interval-id', timerId); } } } function cancelPolling(elt) { if(elt.data('ic-poll-interval-id') != null) { clearTimeout(elt.data('ic-poll-interval-id')); } } function processPolling(elt) { if ($(elt).is('[ic-poll]')) { maybeSetIntercoolerInfo($(elt)); startPolling(elt); } $(elt).find('[ic-poll]').each(function () { maybeSetIntercoolerInfo($(this)); startPolling($(this)); }); } //============================================================---- // Dependency support //============================================================---- function refreshDependencies(dest, src) { log(src, "refreshing dependencies for path " + dest, "DEBUG"); $('[ic-src]').each(function () { var fired = false; if(verbFor($(this)) == "GET" && $(this).attr('ic-deps') != 'ignore') { if (isDependent(dest, $(this).attr('ic-src'))) { if (src == null || $(src)[0] != $(this)[0]) { fireICRequest($(this)); fired = true; } } else if (isDependent(dest, $(this).attr('ic-deps')) || $(this).attr('ic-deps') == "*") { if (src == null || $(src)[0] != $(this)[0]) { fireICRequest($(this)); fired = true; } } } if(fired) { log($(this), "depends on path " + dest + ", refreshing...", "DEBUG") } }); } function isDependent(src, dest) { return (src && dest) && (dest.indexOf(src) == 0 || src.indexOf(dest) == 0); } //============================================================---- // Trigger-On support //============================================================---- function verbFor(elt) { if (elt.attr('ic-verb')) { return elt.attr('ic-verb').toUpperCase(); } return "GET"; } function eventFor(attr, elt) { if(attr == "default") { if($(elt).is('button')) { return 'click'; } else if($(elt).is('form')) { return 'submit'; } else if($(elt).is(':input')) { return 'change'; } else { return 'click'; } } else { return attr; } } function preventDefault(elt) { return elt.is('form') || (elt.is(':submit') && elt.closest('form').length == 1); } function handleRemoveAfter(elt) { if ($(elt).attr('ic-remove-after')) { var transition = getTransition(elt, elt); var interval = parseInterval($(elt).attr('ic-remove-after')); setTimeout(function () { transition.remove(elt); }, interval); } } function handleTriggerOn(elt) { if ($(elt).attr('ic-trigger-on')) { if ($(elt).attr('ic-trigger-on') == 'load') { fireICRequest(elt); } else if ($(elt).attr('ic-trigger-on') == 'scrolled-into-view') { initScrollHandler(); setTimeout(function () { $(window).trigger('scroll'); }, 100); // Trigger a scroll in case element is already viewable } else { var triggerOn = $(elt).attr('ic-trigger-on').split(" "); $(elt).on(eventFor(triggerOn[0], $(elt)), function (e) { if(triggerOn[1] == 'changed') { var currentVal = $(elt).val(); var previousVal = $(elt).data('ic-previous-val'); $(elt).data('ic-previous-val', currentVal); if( currentVal != previousVal ) { fireICRequest($(elt)); } } else { fireICRequest($(elt)); } if(preventDefault(elt)){ e.preventDefault(); return false; } return true; }); } } } function processTriggerOn(elt) { handleTriggerOn(elt); $(elt).find('[ic-trigger-on]').each(function () { handleTriggerOn($(this)); }); } function processRemoveAfter(elt) { handleRemoveAfter(elt); $(elt).find('[ic-remove-after]').each(function () { handleRemoveAfter($(this)); }); } //============================================================---- // Macro support //============================================================---- function processMacros(elt) { $.each(_MACROS, function (i, macro) { if ($(elt).is('[' + macro + ']')) { processMacro(macro, $(elt)); } $(elt).find('[' + macro + ']').each(function () { processMacro(macro, $(this)); }); }); } function processMacro(macro, elt) { // action attributes if(macro == 'ic-post-to') { setIfAbsent(elt, 'ic-src', elt.attr('ic-post-to')); setIfAbsent(elt, 'ic-verb', 'POST'); setIfAbsent(elt, 'ic-trigger-on', 'default'); setIfAbsent(elt, 'ic-deps', 'ignore'); } if(macro == 'ic-put-to') { setIfAbsent(elt, 'ic-src', elt.attr('ic-put-to')); setIfAbsent(elt, 'ic-verb', 'PUT'); setIfAbsent(elt, 'ic-trigger-on', 'default'); setIfAbsent(elt, 'ic-deps', 'ignore'); } if(macro == 'ic-get-from') { setIfAbsent(elt, 'ic-src', elt.attr('ic-get-from')); setIfAbsent(elt, 'ic-trigger-on', 'default'); setIfAbsent(elt, 'ic-deps', 'ignore'); } if(macro == 'ic-delete-from') { setIfAbsent(elt, 'ic-src', elt.attr('ic-delete-from')); setIfAbsent(elt, 'ic-verb', 'DELETE'); setIfAbsent(elt, 'ic-trigger-on', 'default'); setIfAbsent(elt, 'ic-deps', 'ignore'); } // non-action attributes var value = null; var url = null; if(macro == 'ic-style-src') { value = elt.attr('ic-style-src').split(":"); var styleAttribute = value[0]; url = value[1]; setIfAbsent(elt, 'ic-src', url); setIfAbsent(elt, 'ic-target', 'this.style.' + styleAttribute); } if(macro == 'ic-attr-src') { value = elt.attr('ic-attr-src').split(":"); var attribute = value[0]; url = value[1]; setIfAbsent(elt, 'ic-src', url); setIfAbsent(elt, 'ic-target', 'this.' + attribute); } if(macro == 'ic-prepend-from') { setIfAbsent(elt, 'ic-src', elt.attr('ic-prepend-from')); setIfAbsent(elt, 'ic-transition', 'prepend'); } if(macro == 'ic-append-from') { setIfAbsent(elt, 'ic-src', elt.attr('ic-append-from')); setIfAbsent(elt, 'ic-transition', 'append'); } } function setIfAbsent(elt, attr, value) { if(elt.attr(attr) == null) { elt.attr(attr, value); } } //============================================================---- // Utilities //============================================================---- function isScrolledIntoView(elem) { var docViewTop = $(window).scrollTop(); var docViewBottom = docViewTop + $(window).height(); var elemTop = $(elem).offset().top; var elemBottom = elemTop + $(elem).height(); return ((elemBottom >= docViewTop) && (elemTop <= docViewBottom) && (elemBottom <= docViewBottom) && (elemTop >= docViewTop)); } function getTransition(elt, target) { var transition = null; if(elt.attr('ic-transition')) { transition = _transitions[elt.attr('ic-transition')] } if(target.attr('ic-transition')) { transition = _transitions[target.attr('ic-transition')] } if(target.data('ic-tmp-transition')) { transition = _transitions[target.data('ic-tmp-transition')] } if(transition == null) { transition = _transitions[_defaultTransition]; } if(transition == null) { transition = _transitions['none']; } return transition; } function processICResponse(newContent, elt) { if (newContent && /\S/.test(newContent)) { log(elt, "response content: \n" + newContent, "DEBUG"); var target = getTarget(elt); // always update if the user tells us to or if there is a script (to reevaluate the script) var updateContent = closestAttrValue(elt, 'ic-always-update') != 'false' || newContent.indexOf("<script>") >= 0; if(updateContent == false) { var dummy = document.createElement('div'); dummy.innerHTML = newContent; processMacros(dummy); updateContent = fingerprint($(dummy).html()) != target.attr('ic-fingerprint'); if(dummy.remove) { //mobile fix dummy.remove(); } } if (updateContent) { var transition = getTransition(elt, target); var isReplaceParent = closestAttrValue(elt, 'ic-replace-target') == "true"; var contentToSwap = maybeFilter(newContent, closestAttrValue(elt, 'ic-select-from-response')); transition.newContent(target, contentToSwap, false, isReplaceParent, function (replacement) { if(replacement) { processNodes(replacement); updateIntercoolerMetaData(target); } else { $(target).children().each(function() { processNodes($(this)); }); updateIntercoolerMetaData(target); } updateIntercoolerMetaData(target); }); } } } function maybeFilter(newContent, filter) { var content = $.parseHTML(newContent, null, true); var asQuery = $(content); if(filter) { if(!asQuery.is(filter)) { asQuery = asQuery.find(filter); } } return asQuery; } function getStyleTarget(elt) { var val = closestAttrValue(elt, 'ic-target'); if(val && val.indexOf("this.style.") == 0) { return val.substr(11) } else { return null; } } function getAttrTarget(elt) { var val = closestAttrValue(elt, 'ic-target'); if(val && val.indexOf("this.") == 0) { return val.substr(5) } else { return null; } } function fireICRequest(elt) { var triggerOrigin = elt; if(!elt.is('[ic-src]')) { elt = elt.closest('[ic-src]'); } var confirmText = closestAttrValue(elt, 'ic-confirm'); if(confirmText) { if(!confirm(confirmText)) { return; } } if(elt.length > 0) { var icEventId = uuid(); elt.data('ic-event-id', icEventId); var invokeRequest = function () { // if an existing request is in flight for this element, push this request as the next to be executed if(elt.data('ic-request-in-flight') == true) { elt.data('ic-next-request', invokeRequest); return; } if (elt.data('ic-event-id') == icEventId) { var styleTarget = getStyleTarget(elt); var attrTarget = styleTarget ? null : getAttrTarget(elt); var verb = verbFor(elt); handleRemoteRequest(elt, verb, elt.attr('ic-src'), getParametersForElement(elt, triggerOrigin), function (data) { if (styleTarget) { elt.css(styleTarget, data); } else if (attrTarget) { elt.attr(attrTarget, data); } else { processICResponse(data, elt); if (verb != 'GET') { refreshDependencies(elt.attr('ic-src'), elt); } } }); } }; var triggerDelay = closestAttrValue(elt, 'ic-trigger-delay'); if (triggerDelay) { setTimeout(invokeRequest, parseInterval(triggerDelay)); } else { invokeRequest(); } } } //============================================================ // History Support //============================================================ var _historySupport = { currentRestorationId : null, /* functions */ genId: function() { return "ic-hist-elt-" + Math.random().toString(); }, newRestorationData : function(id, html){ var histSupport = JSON.parse(localStorage.getItem('ic-history-support')); if (histSupport == null) { histSupport = {}; } var restorationData = { "id": _historySupport.genId(), "elementId": id, "content": html, "timestamp": new Date().getTime(), "previous": histSupport.last }; // if there is no first, store this new element as first if (histSupport.first == null) { histSupport.first = restorationData.id; } // update last to point to the new element next if (histSupport.last) { var last = JSON.parse(localStorage.getItem(histSupport.last)); last['next'] = restorationData.id; localStorage.setItem(histSupport.last, JSON.stringify(last)); } // limit history to 100 items if (histSupport.count == null) { histSupport.count = 1; } else { histSupport.count++; } if (histSupport.count > 200) { // 200 entries works out to about 100 total history elements var first = JSON.parse(localStorage.getItem(histSupport.first)); histSupport.first = first.next; localStorage.removeItem(first.id); } //save the new element and history support data localStorage.setItem(restorationData.id, JSON.stringify(restorationData)); localStorage.setItem('ic-history-support', JSON.stringify(histSupport)); return restorationData; }, updateHistoryData : function(id, html){ var restorationData = JSON.parse(localStorage.getItem(id)); if (restorationData == null) { log($('body'), "Could not find restoration data with id " + id, "ERROR"); return } restorationData.content = html; //save the new element and history support data localStorage.setItem(restorationData.id, JSON.stringify(restorationData)); }, onPageLoad: function () { if (window.onpopstate == null || window.onpopstate['ic-on-pop-state-handler'] != true) { var currentOnPopState = window.onpopstate; window.onpopstate = function(event) { if(!_historySupport.handlePop(event)){ if(currentOnPopState) { currentOnPopState(event); } } }; window.onpopstate['ic-on-pop-state-handler'] = true; } }, pushUrl: function (url, elt, originalHtml) { log(elt, "pushing location into history: " + url, "DEBUG"); var target = getTarget(elt); var id = target.attr('id'); if(id == null) { log(elt, "To support history for a given element, you must have a valid id attribute on the element", "ERROR"); return; } if(_historySupport.currentRestorationId != null) { _historySupport.updateHistoryData(_historySupport.currentRestorationId, originalHtml); } else { var originalData = _historySupport.newRestorationData(id, originalHtml); window.history.replaceState({"ic-id" : originalData.id}, "", ""); } var restorationData = _historySupport.newRestorationData(id, target.html()); window.history.pushState({'ic-id': restorationData.id}, "", url); _historySupport.currentRestorationId = restorationData.id; elt.trigger("pushUrl.ic", target, restorationData); }, handlePop: function (event) { var data = event.state; if (data && data['ic-id']) { var historyData = JSON.parse(localStorage.getItem(data['ic-id'])); var elt = findById(historyData["elementId"]); if(_historySupport.currentRestorationId != null) { _historySupport.updateHistoryData(_historySupport.currentRestorationId, elt.html()); } processICResponse(historyData["content"], elt); _historySupport.currentRestorationId = historyData.id; return true; } return false; } }; //============================================================ // Bootstrap //============================================================ $(function () { processNodes('body'); _historySupport.onPageLoad(); if(location.search && location.search.indexOf("ic-launch-debugger=true") >= 0) { Intercooler.debug(); } }); return { /* =================================================== * Core API * =================================================== */ refresh: function (val) { if (typeof val == 'string' || val instanceof String) { refreshDependencies(val); } else { fireICRequest(val); } return Intercooler; }, updateHistory: function(id) { var restoData = _historySupport.newRestorationData($(id).attr('id'), $(id).html()); window.history.replaceState({"ic-id" : restoData.id}, "", ""); }, processNodes: function(elt) { return processNodes(elt); }, defaultTransition: function (name) { _defaultTransition = name; }, defineTransition: function (name, def) { _defineTransition(name, def); }, debug: function() { var debugPanel = $(window).data('ic-debug-panel'); if(debugPanel == null) { (function() { function generateDetailPanel(elt) { var dp = $("<div><div><strong>Details</strong></div>" + "<div><strong>URL: </strong>" + elt.attr('ic-src') + "</div>" + "<div><strong>Verb: </strong>" + verbFor(elt) + "</div>" + (elt.attr('ic-trigger-on') ? "<div><strong>Trigger: </strong>" + elt.attr('ic-trigger-on') + "</div>" : "") + "</div>" ); if(elt.attr('ic-target')) { dp.append($("<div><strong>Target: </strong></div>").append(linkForElt(getTarget(elt)))); } if(elt.attr('ic-deps')) { dp.append($("<div><strong>Dependencies: </strong></div>").append(elt.attr('ic-deps'))); } if(verbFor(elt) != "GET") { var depsList = $("<div><strong>Dependant Elements:</strong><ul style='list-style-position: inside;font-size:12px;'></ul></div>") .appendTo(dp).find("ul"); $('[ic-src]').each(function () { if(verbFor($(this)) == "GET" && $(this).attr('ic-deps') != 'ignore') { if ((isDependent(elt.attr('ic-src'), $(this).attr('ic-src'))) || (isDependent(elt.attr('ic-src'), $(this).attr('ic-deps')) || $(this).attr('ic-deps') == "*")) { if (elt == null || elt[0] != $(this)[0]) { $("<li style='font-size:12px'></li>").append(linkForElt($(this))).appendTo(depsList); } } } }); } return dp; } function linkForElt(that) { if(that && that.length > 0) { return $("<a style='border-bottom: 1px solid #d3d3d3'>&lt;" + that.prop("tagName").toLowerCase() + "&gt;" + (that.attr('ic-src') ? " - " + that.attr('ic-src') : "") + "</a>").data('ic-debug-elt', that); } else { return $("<span>no element</span>") } } function generateDebugPanel() { return $("<div id='ic-debug-panel' style='font-size: 14px;font-family: Arial;background:white;width:100%;height:200px;position:fixed;left:0;border-top: 1px solid #d3d3d3;'>" + " <div style='padding:4px;width:100%;border-bottom: 1px solid #d3d3d3;background: #f5f5f5'><img src='/images/Intercooler_CMYK_noType_64.png' height='16px'> <strong>intercooler.js debugger</strong>" + " <span style='float:right'><a>Hide</a> | <a>[x]</a></span>" + " </div>" + " <div style='padding:4px;width:100%;border-bottom: 1px solid #d3d3d3;'>" + " <a style='font-weight: bold'>Elements</a> | <a>Logs</a> | <a>Errors</a>" + " </div>" + " <div>" + " <div id='ic-debug-Elements'>" + " <div id='ic-debug-Elements-list' style='width:200px;float: left;height: 142px;overflow-y: scroll;'>" + " </div>" + " <div id='ic-debug-Elements-detail' style='height: 142px;overflow-y: scroll;'>" + " </div>" + " </div>" + " <div id='ic-debug-Logs' style='display:none;overflow-y: scroll;height: 142px'>" + " </div>" + " <div id='ic-debug-Errors' style='display:none;overflow-y: scroll;height: 142px'>" + " </div>" + " </div>" + "</div>"); } function debugSourceElt(elt) { var eltLink = linkForElt(elt); eltLink.clone(true).css({'display' : 'block'}).appendTo($("#ic-debug-Elements-list")); if(elt.attr('ic-target') && getTarget(elt).length == 0) { $("<div> - bad target selector:" + elt.attr('ic-target') + "</div>").prepend(eltLink.clone(true)).appendTo($("#ic-debug-Errors")); } if(elt.attr('ic-indicator') && $(elt.attr('ic-indicator')).length == 0) { $("<div> - bad indicator selector:" + elt.attr('ic-indicator') + "</div>").prepend(eltLink.clone(true)).appendTo($("#ic-debug-Errors")); } if(elt.attr('ic-push-url') && getTarget($(elt)).attr('id') == null) { $("<div> - ic-push-url requires target to have id</div>").prepend(eltLink.clone(true)).appendTo($("#ic-debug-Errors")); } } function maybeCleanDebugInfo() { $('#ic-debug-Elements-list').find('a').each(function(){ if($(this).data('ic-debug-elt') && $.contains( document.body, $(this).data('ic-debug-elt')[0])) { // you live } else { $(this).remove(); } }); } debugPanel = generateDebugPanel().appendTo($('body')); $(window).data('ic-debug-panel', debugPanel); var lastElt; $('#ic-debug-panel').on('click', 'a', function(){ if($(this).text() == "Hide") { $("#ic-debug-panel").data('ic-minimized', true); $(this).text("Show"); $(window).resize(); } else if ($(this).text() == "Show") { $("#ic-debug-panel").data('ic-minimized', false); $(this).text("Hide"); $(window).resize(); } else if ($(this).text() == "[x]") { if(lastElt) { lastElt.css({'border': ''}); } debugPanel.hide(); $('html').css('margin-bottom', "0"); } else if (["Elements", "Logs", "Errors"].indexOf($(this).text()) >= 0) { $(this).parent().find('a').css({"font-weight":"normal"}); $(this).css({"font-weight":"bold"}); $("#ic-debug-" + $(this).text()).parent().children().hide(); $("#ic-debug-" + $(this).text()).show(); } else if($(this).data('ic-debug-elt')) { var that = $(this); var newElt = that.data('ic-debug-elt'); var delay = Math.min(newElt.offset().top - 75, 300); $('html, body').animate({ scrollTop: newElt.offset().top - 75 }, delay); if(lastElt) { lastElt.css({'border': ''}); } lastElt = newElt; newElt.css({'border' : "2px solid red"}); if(that.parent().attr('id') == 'ic-debug-Elements-list') { $('#ic-debug-Elements-detail').html(generateDetailPanel(newElt)); } } }); $('[ic-src]').each(function(){ debugSourceElt($(this)); }); $(window).on('log.ic',function (e, msg, level) { $("<div style='border-bottom: 1px solid #d3d3d3'>] - " + msg.replace(/</g, '&lt;') + "</div>") .appendTo($("#ic-debug-Logs")) .prepend(linkForElt($(e.target))) .prepend(level + " ["); }).on('elementAdded.ic',function (e) { debugSourceElt($(e.target)); }).on('nodesProcessed.ic',function () { maybeCleanDebugInfo(); }).on('resize', function () { if(!debugPanel.is(":hidden")) { var winOffset = $(window).height() - (debugPanel.data('ic-minimized') == true ? 29 : 200); debugPanel.css('top', winOffset + "px"); $('html').css('margin-bottom', (debugPanel.data('ic-minimized') == true ? 29 : 200) + "px"); } }); })(); } else { debugPanel.show(); } $(window).resize(); }, /* =================================================== * Mock Testing API * =================================================== */ addURLHandler: function () { throw "This method is no longer supported. Please use the jQuery mockjax plugin instead: https://github.com/jakerella/jquery-mockjax"; }, setRemote: function (remote) { _remote = remote; return Intercooler; } } })();