intercooler
Version:
Making AJAX as easy as anchor tags
1,225 lines (1,112 loc) • 41.9 kB
JavaScript
////////////////////////////////////
/**
* 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'><" +
that.prop("tagName").toLowerCase() +
">" + (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, '<') + "</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;
}
}
})();