Last active
March 5, 2017 21:39
-
-
Save pwFoo/3f521c41d71f9a4bd1c9ac2651c13221 to your computer and use it in GitHub Desktop.
jqSlim - jQuery-like API super-tinyJavaScript library based on ki.js. Ajax-ic is a minimal IntercoolerJS inspired jqSlim plugin.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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; | |
} | |
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/*! | |
* 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' ); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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