Skip to content

Instantly share code, notes, and snippets.

@pwFoo pwFoo/ajax-ic.js
Last active Mar 5, 2017

Embed
What would you like to do?
jqSlim - jQuery-like API super-tinyJavaScript library based on ki.js. Ajax-ic is a minimal IntercoolerJS inspired jqSlim plugin.
/**
* Load Module if DOM is ready
*/
$(function () {
/**
* GET click events
*/
$('[ic-get]').on('click', function () {
icAjax(this, 'GET')
});
/**
* POST click events
*/
$('[ic-post]').on('click', function () {
icAjax(this, 'POST')
});
/**
* DELETE click events
*/
$('[ic-delete]').on('click', function () {
icAjax(this, 'DELETE')
});
/**
* PUT click events
*/
$('[ic-put]').on('click', function () {
icAjax(this, 'PUT')
});
/**
* PATCH click events
*/
$('[ic-patch]').on('click', function () {
icAjax(this, 'PATCH')
})
/**
* History api change event
*/
window.onpopstate = function (event) {
//console.log(event.state);
if (event.state !== null) { // changed
$(event.state).ajax({url: window.location.href}); // Load current history url
}
};
/**
* Minimal IntercoolerJS magic
* https://github.com/LeadDyno/intercooler-js
* @param element Clicked element
* @param icMethod HTTP method to use
*/
function icAjax(clickedElement, icMethod) {
var element = $(clickedElement),
icTarget = element.attr('ic-target') || clickedElement, // defined destination OR current element ?! object type ?!
icUrl = element.attr('ic-' + icMethod); // URL to load
element.stop(); // prevent default click event
/**
* Quick & dirty local source solution...
*/
if (icUrl.charAt(0) === '#') {
$(icTarget).html($(icUrl).html());
return;
}
// Ajax call
$.ajax({
url: icUrl,
method: icMethod,
},
// success method
function (xhr) {
// history / url push support
// https://css-tricks.com/using-the-html5-history-api/
if (element.attr('ic-push-url')) {
history.pushState(
icTarget, // content (destination element/s) assigned to the history item
null, // history item title
icUrl // history item address
);
}
// fill response html to target
$(icTarget).html(xhr.response);
// trigger dependencies if source should be changed by POST / PATCH / DELETE / UPDATE
if (icMethod !== 'GET') {
/**
* ToDo:
* - reduce calls!? load identical source once and add to all targets
* - group elements by identical ic-get url / path
* - check also ic-deps to trigger manual dependencies too!
*/
$('[ic-get]').each(function (elem) {
elem = $(elem);
var path = elem.attr('ic-get');
//if (isDependent(getPath(icUrl), getPath(path))) {
if (isDependent(icUrl, path)) {
//console.log(path);
elem.trigger('click');
}
});
}
}
);
}
/**
* Get path from url
* @param url Address to get path from
* @return Path of URL
*/
function getPath(url) {
var a = $.create('a');
a.href = url;
return a.pathname;
}
/**
* Check url depencency
* @param url1 First address to compare
* @param url2 Second address to compare
* @return boolean Is dependent?
*/
function isDependent (url1, url2) {
return url1.indexOf(getPath(url2)) === 0 || url2.indexOf(getPath(url1)) === 0;
}
});
/*!
* jqSlim - jQuery-like API super-tinyJavaScript library
* A ki.js fork (Copyright (c) 2015 Denis Ciccale (@tdecs))
* Copyright (c) 2017
* Released under MIT license
*/
/**
* Self execute library with defaults
* @param document mapped document
* @param array empty array
* @param prototype mapped string
* @param element ???
*/
!function (document, array, prototype, element) {
/*
* $ main function
* selector = css selector, dom object, or function
* http://www.dustindiaz.com/smallest-domready-ever
* returns instance or executes function on ready
*/
$ = function (selector) {
return /^f/.test(typeof selector) ? // IF1 selector is a function (type starts with "f")
/c/.test(document.readyState) ? // IF2 dom ready (readyState contains "c")
selector() : // THEN2 call selector as function
$(document).on('DOMContentLoaded', selector) : // ELSE2 delay function call until dom ready
new jqSlim(selector); // ELSE1 initialize library
}
/*
* init function (internal use)
* selector = selector, dom element or function
*/
function jqSlim(selector) {
array.push.apply( // add second array to first
this, // first array
selector && selector.nodeType ? // IF1 selector is type node
[selector] : // THEN1 concat collection
'' + selector === selector ? // ELSE1 IF2 selector is string
query(selector) : // THEN2 query and concat collection
element // ELSE2 concat element (NULL?!)
);
}
/*
* improved query function
* http://ryanmorr.com/abstract-away-the-performance-faults-of-queryselectorall/
* https://github.com/james2doyle/saltjs
*
* @param selector css selector to get an array of elements
* @param context dom context element
* @return collection of elements
*/
function query(selector, context) {
context = context || document;
var type = selector.charAt(0), // first char to check selector type
selectorValue = selector.substr(1), // selector without first char (needed if type prefixed)
singleValue = /^[\w\*\-_\.\#\@]+$/; // # . @ * - _ aA-zZ 0-9
if (singleValue.exec(selector)) { // simple single word selector?
if (type === '#') { // single ID
return [context.getElementById(selectorValue)];
} else if (type === '.') { // single CLASS
return context.getElementsByClassName(selectorValue);
} else if (type === '@') { // elements name attribute
return context.getElementsByName(selectorValue);
} else { // single TAG NAME
return context.getElementsByTagName(selector);
}
} else { // complex selector...
return context.querySelectorAll(selector);
}
}
// set jqSlim prototype
$[prototype] = jqSlim[prototype] = $.fn = jqSlim.fn = {
// default length
length: 0,
/*
* on method
* @param eventType String event type i.e 'click'
* @param eventFunction Event callback function to bind
*/
on: function (eventType, eventFunction) {
return this.each(function (array) {
array.addEventListener(eventType, eventFunction);
});
},
/*
* off method
* @param eventType String event type i.e 'click'
* @param eventFunction Event callback function to remove
*/
off: function (eventType, eventFunction) {
return this.each(function (array) {
array.removeEventListener(eventType, eventFunction);
});
},
/**
* Trigger event on current colletion of elements
* modern browsers, IE9+
* @param type Event type to trigger
*/
trigger: function(type) {
var event = document.createEvent('HTMLEvents');
event.initEvent(type, false, true);
// Trigger event on each element in collection
this.each(function(element) {
element.dispatchEvent(event);
});
},
/**
* Prevent default event
* @return "this" because of chaining
*/
stop: function() {
window.event.preventDefault();
return this;
},
/*
* each loop method
* use native forEach to iterate collection
* @param callbackFunction The function to call on each iteration
* @param callbackParams Callback params
* @return "this" because of chaining
*/
each: function (callbackFunction, callbackParams) {
array.forEach.call(
this,
callbackFunction,
callbackParams
);
return this;
},
/**
* query dom element(s) in current element context
* @param selector A selector string
* @return Collection of matching elements
*/
query: function (selector) {
return query(selector, this);
},
/**
* Remove current colletion of elements
*/
remove: function() {
return this.each(function(element) {
element.parentNode.removeChild(element);
});
},
/**
* Insert element
* Maybe replacement for append / prepend
* @param insertElement new element
* @param position optional, prepend (0), append (null) or insert before position reference element
*
* ToDo:
* - Use append / prepend instead?!
* - Improve position check?!
*/
insert: function(insertElement, position) {
return this.each(function(element) {
position = (
position === 0 ? // IF
element.firstChild : // 0 = first child
!position ? // ELSE IF
null : // null = last child
position[0] // ELSE given position
);
element.insertBefore(insertElement, position);
});
},
/**
* Append an element to the current one
* jQuery compatibility...
* @param appendElement element to append
*
* ToDo:
* Move to a separated plugin if insert() will still be used
*/
append: function(appendElement) {
return this.insert (appendElement, null);
},
/**
* Prepend element to current one
* jQuery compatibility...
* @param insertElement element to prepend or insert before position
*
* ToDo:
* Move to a separated plugin if insert() will still be used
*/
prepend: function(insertElement) {
return this.insert (appendElement, 0);
},
/**
* Insert before element
* jQuery compatibility...
* @param html Content to insert
*/
before: function(html) {
return this.each(function(element) {
element.insertAdjacentHTML('beforebegin', html);
});
},
/**
* Insert before element
* jQuery compatibility...
* @param html Content to insert
*/
after: function(html) {
return this.each(function(element) {
element.insertAdjacentHTML('afterend', html);
});
},
/**
* Set collection / get single element html content
* @param html Optional value to set to the collection
* @return Optional return single / first element html content
*/
html: function (html) {
return html === []._ ? // IF param is undefined
this[0].innerHTML : // get element content
this.each(function(element) { // ELSE
element.innerHTML = html; // set new value
});
},
/**
* Set collection / get single element text content
* @param text Optional value to set to the collection
* @return Optional return single / first element text content
*/
text: function (text) {
return text === []._ ? // IF param is undefined
this[0].textContent : // get element content
this.each(function(element) { // ELSE
element.textContent = text; // set new value
});
},
/**
* Get parent for each DOM element in the collection
* @return Array of parent elements of each element in collection
*
* ToDo:
* Move to a separated plugin?
*/
parent: function() {
var result = [];
this.each(function(element) {
result.push(
element.parentNode ? // IF parent element
element.parentNode : // ... return it
[] // ELSE empty array
);
});
return result;
},
/**
* Check single element matches against selector
* modern browsers, IE9+
* @param selector to check against the current element
* @return boolean
*
* ToDo:
* Move to a separated plugin?
*/
is: function(selector) {
var flag = false;
this.each(function(element) {
// cross (modern) browser match test
if ((element.matches ||
element.matchesSelector ||
element.msMatchesSelector ||
element.webkitMatchesSelector
).call(element, selector)) { // element matching selector?
flag = true; // true if single hit in collection
}
});
return flag;
},
/**
* Closest single ancestors element matches selector
* http://clubmate.fi/jquerys-closest-function-and-pure-javascript-alternatives/
* @param selector Searched for matches
* @return Single dom element
*
* ToDo:
* Move to a separated plugin?
*/
closest: function(selector) {
var result = [];
this.each(function(element) {
result.push(
$(element).is(selector) ? // IF match
element : // ... return element
$(element.parentNode).closest(selector) // ELSE recursive call...
);
});
return result;
},
/**
* Filter collection of dom elements
* @param selector for find / filter matches
* @return Collection of elements
*
* ToDo:
* Move to a separated plugin?
*/
find: function (selector) {
var result = [];
this.each(function(element) {
result.push($(element).query(selector));
});
return result;
},
/**
* Get first element in collection
* @return Single DOM element object
*/
first: function() {
return $(this[0]);
},
/**
* Get last element in collection
* @return Single DOM element object
*/
last: function() {
return $(this[this.length - 1]);
},
/**
* Get element in collection
* @param index
* @return Single DOM element object
*/
get: function(index) {
return $(this[index]);
},
/**
* Get element / set elements attribute
* @param attribtue to handle
* @param value Optional value to set to the collection
* @return Optional attribute value
*/
attr: function(attribute, value) {
return value !== []._ ? // set mode because value is given
this.each(function(element) { // each element in collection
element.setAttribute(attribute, value); // set attribute value
}) :
this[0] == null ? // no dom element given...
false : // ... return false
this[0].getAttribute(attribute) || false; // attribute value OR false
},
/**
* Remove elements attribute
* @param attribute Attribute name
*/
removeAttr: function(attribute) {
return this.each(function(element) {
element.removeAttribute(attribute);
});
},
/**
* Add class to elements
* @param value Class to add
*/
addClass: function(classValue) {
return this.each(function(element) {
element.classList.add(classValue);
});
},
/**
* Remove class from elements
* @param value Class to remove
*/
removeClass: function(classValue) {
return this.each(function(element) {
element.classList.remove(classValue);
});
},
/**
* Toggle class from elements
* @param value Class to toggle
*/
toggleClass: function(classValue) {
return this.each(function(element) {
element.classList.toggle(classValue);
});
},
/**
* Check if element class list contains class
* @param value Class to check
* @return boolean
*/
hasClass: function(value) {
var flag = false;
this.each(function(element) {
if (element.classList.contains(value)) {
flag = true;
}
});
return flag;
},
/**
* Get single element style / set elements style by string or key => value object
* https://plainjs.com/javascript/styles/set-and-get-css-styles-of-elements-53/
* @param styleAttr style key string or object with key => value to handle
* @param value Optional value to set to the collection
* @return Optional style value
*/
/*
css: function (styleAttr, value) {
return value === []._ && // IF value is undefined
styleAttr === '' + styleAttr ? // AND IF styleAttr is string
getComputedStyle(this[0])[styleAttr] : // get style value of first element
this.each(function(element) { // ELSE set to each element in collection
if (styleAttr === '' + styleAttr) { // IF styleAttr is string
element.style[styleAttr] = value; // set style value to first element
} else { // ESLE styleAttr should be an object
for (var key in styleAttr) { // loop object key => value
$(element).css (key, styleAttr[key]); // set each pair to element
}
}
});
},*/
/**
* Get single element style / set elements style by string or key => value object
* Optional save original style value to restore / toggle styles
* https://plainjs.com/javascript/styles/set-and-get-css-styles-of-elements-53/
* @param styleAttr style key string or object with key => value to handle
* @param value Optional value to set to the collection
* @param restore Optional save / restore orig value to data attribute
* @return Optional style value
*
* ToDo:
* Move to a separated plugin?
*/
css: function (styleAttr, value, restore) {
if (styleAttr === '' + styleAttr) {
if (value === []._) { // undefined
return getComputedStyle(this[0])[styleAttr]; // get current value
} else {
this.each(function(element) { // loop dom elements
if (restore !== []._) { // isset restore param
if (element.dataset['__' + styleAttr] !== []._) { // isset backuped orig value
value = element.dataset['__' + styleAttr]; // get orig value
delete element.dataset['__' + styleAttr]; // delete orig value backup data attribute
} else if (element.style[styleAttr] !== []._) { // no saved orig value? first change...
element.dataset['__' + styleAttr] = element.style[styleAttr]; // set backup orig value to data attribute
}
}
element.style[styleAttr] = value; // set element style value...
});
}
} else {
// object! Loop properties and recursive call method self
for (var key in styleAttr) {
this.css (key, styleAttr[key], restore);
}
}
return this;
},
/**
* Hide current element
*
* ToDo:
* Move to a separated plugin?
*/
hide: function() {
this.css('display', 'none', true);
/*var display = 'display';
return this.each(function(element) {
if (element.dataset['__' + display] === []._) { // is undefined
// set helper attribute with element style or just null value
element.dataset['__' + display] = element.style[display];
element.style[display] = 'none';
}
});*/
},
/**
* Show current element
*
* ToDo:
* Move to a separated plugin?
*/
show: function() {
this.css('display', null, true);
/*var display = 'display';
return this.each(function(element) {
if (element.dataset['__' + display] !== []._) { // is undefined
// restore initial value or computed default display style
element.style[display] = element.dataset['__' + display] || '';
delete element.dataset['__' + display];
}
});*/
},
/**
* $.ajax() wrapper with destination current element
* @param options Ajax call options to pass
*/
ajax: function(options) {
var element = this;
$.ajax(
options,
function (xhr) {
element.html(xhr.response);
},
function () {
element.html('Ajax error!')
}
);
},
// for some reason is needed to get an array-like
// representation instead of an object
splice: array.splice,
}
/**
* Ajax implementation
* https://blog.garstasio.com/you-dont-need-jquery/ajax/
* @param options Ajac call options object
* @param success Callback executed on successful ajax call
* @param error Callback function executed on failure
* @return Execute the success | error callback
*
* ToDo:
* Move to a separated plugin?
*/
$.ajax = function(options, success, error) {
var method = options.method || 'GET', // method OR default GET
url = options.cache == false ? // IF caching disabled
options.url + '?' + new Date().getTime() // add get param to avoid caching
: // ELSE = caching is allowed
options.url, // just set the url
httpRequest = new XMLHttpRequest(); // create XHR object
httpRequest.open(method, url, true);
httpRequest.withCredentials = options.hasOwnProperty('withCredentials'); // true || false
setHeaders(options.headers, httpRequest); // set all the headers from options object
httpRequest.onload = function() {
if (httpRequest.status >= 200 && httpRequest.status < 400) {
// Call sucessfully done! Execute and success callback
return success(httpRequest);
} else {
// We reached our target server, but it returned an error
// execute and success callback
return error(httpRequest);
}
};
httpRequest.onerror = function() {
// There was a connection error of some sort
return error(httpRequest);
};
// Send ajax call with data
httpRequest.send(setData(options.data));
/**
* Set header pairs
* @param params Key => value array
* @param xhr The httpRequest object to add the header information
*/
function setHeaders(params, xhr) {
for (var property in params) {
xhr.setRequestHeader(property, params[property])
};
}
/**
* Check if content-type option is set
* @param headers Array with header pairs
* @return boolean
*/
function hasContentType (headers) {
return Object.keys(headers).some(function (name) {
return name.toLowerCase() === 'content-type'
})
}
/**
* Set xhr request data
* @param data Object with key => value pairs
* @return Param (encoded) string
*/
function setData (data) {
return data === []._ ? null : data === '' + data ? data : Object.keys(data).map(
function(key){ return encodeURIComponent(key) + '=' + encodeURIComponent(data[key]) }
).join('&');
}
}
/**
* Example ajax GET shorthand
*/
/*
$.get = function (url, data, success) {
return $.ajax({
method: 'GET',
url: url,
data: data || {}
},
success);
}
*/
/**
* Create a new dom element
* @param elementTagName dom element to create
* @return created and empty dom element
*/
$.create = function (elementTagName) {
return document.createElement(elementTagName);
}
/**
* Simple map method
* @param array
* @param callback Function to execute as callback
*
* ToDo:
* Really needed?
*/
$.map = function (array, callback) {
return array.map(callback);
}
/**
* Simple each loop method
* @param collection DOM elements
* @param callback Function to execute
* @param callbackParams Callback params
*
* ToDo:
* Really needed?
*/
$.each = function (collection, callback, callbackParams) {
collection.forEach.call(this, callback, callbackParams);
}
}( document, [], 'prototype' );
@pwFoo

This comment has been minimized.

Copy link
Owner Author

commented Mar 5, 2017

Minified / gzip with http://refresh-sf.com/

jqSlim:
Input: 24.33 KB
Output: 3.82 KB
Gzip: 1.45 KB

ajax-ic:
Input: 3.90 KB
Output: 852 bytes
Gzip: 422 bytes

jqSlim + ajax-ic
Input: 28.23 KB
Output: 4.67 KB
Gzip: 1.76 KB

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.