Skip to content

Instantly share code, notes, and snippets.

@ql-owo-lp
Last active October 12, 2015 03:28
Show Gist options
  • Save ql-owo-lp/3964092 to your computer and use it in GitHub Desktop.
Save ql-owo-lp/3964092 to your computer and use it in GitHub Desktop.
Kissogram Toolkit JS Library
var $K = KissogramToolkit = (function ($$d) {
// some configuration
var DEBUG_ON = false;
// Basic function ==============================================
// for each
function each($arr, $func) {
var item;
if (!$arr)
return;
//_debug('each function is called. arr length is '+ $arr.length);
if ($func)
for (item in $arr)
$func.call($arr[item]);
else
// the $arr is collection of function itself
for (item in $arr)
if (typeof $arr[item]==='function')
$arr[item]();
}
var utils = {
"isStrictArray" : function ($obj) {
return Object.prototype.toString.apply($obj) === '[object Array]';
},
"isRegExp" : function ($obj) {
return Object.prototype.toString.apply($obj) === '[object RegExp]';
},
"toArray" : function ($obj) {
if (!this.isArray($obj))
return [$obj];
else if (!this.isStrictArray($obj))
return Array.prototype.slice.apply($obj);
else
return $obj;
},
"isArray" : function ($obj) {
var type = Object.prototype.toString.apply($obj);
return type === '[object Array]' // array
|| type === '[object NodeList]' // document.querySelectorAll
|| type === '[object Arguments]' // function arguments
;
},
"trim" : function ($str) {
return $str.replace(/^\s+|\s+$/g, '');
},
"trimS" : function ($str) {
return this.trim($str).replace(/\s{2,}/g, ' ');
}
};
// shallow copy
function extend($target, $options) {
var name;
for (name in $options) {
if ($target === $options[name])
continue;
if ($options[name])
$target[name] = $options[name];
}
return $target;
}
// Basic function ends =============================================
// limit the interval/delay of running a specific function, return the changed function
function setFunctionTiming($func, $opt) {
$opt = $opt || {};
var opt = {
interval : $opt.interval || 0,
delay : $opt.delay || 0,
check : $opt.check || 0
};
return (function () {
var lastRunTime = 0, instances = [], isRunning = false, checkInterval = null;
var res = function () {
if (opt.check > 0 && checkInterval==null)
checkInterval = setInterval(res, opt.check);
var timeRemain = (new Date().getTime()) - lastRunTime;
var _this = this, args = utils.toArray(arguments);
// the real function
function runFunc() {
lastRunTime = new Date().getTime();
isRunning = true;
$func.apply(_this, args);
isRunning = false;
instances.shift();
};
if (instances.length < 1 || isRunning) {
// not time yet
if (timeRemain < opt.interval)
instances.push(setTimeout(runFunc, Math.max(100, opt.delay + opt.interval - (isRunning ? 0 : timeRemain))));
else
instances.push(setTimeout(runFunc, Math.max(100, opt.delay)));
}
};
return res;
})();
}
var $$browser = (function getNavigator($n) {
var navigatorString = $n.userAgent.toLowerCase(),
// browser agent
rBrowsers = [
/.*version\/([\w.]+).*(safari).*/,
/.*(msie) ([\w.]+).*/,
/.*(firefox)\/([\w.]+).*/,
/(opera).+version\/([\w.]+)/,
/.*(chrome)\/([\w.]+).*/
],
// result
ret = {
name : 'unknown',
version : 'unknown',
language: $n['language'] || $n['userLanguage'] || '',
toString : function () {
return this.name;
}
};
for (var i = 0, match=null; i < rBrowsers.length; ++i)
if ( match = rBrowsers[i].exec(navigatorString) ) {
// match safari
ret.name = (i==0 ? match[2] : match[1]) || 'unknown';
ret.version = (i==0 ? match[1] : match[2]) || 'unknown';
ret[ret.name] = true;
break;
}
return ret;
})(window.navigator);
// get unsafeWindow
var $$w = (function () {
var w = null, // window object
resizeTasks = [],
scrollTasks = [];
function getSize() {
return {
windowHeight : window.innerHeight,
windowWidth : window.innerWidth,
height : $$d.documentElement.clientHeight,
width : $$d.documentElement.clientWidth
};
}
function _isScroll(el) {
// test targets
var elems = el ? [el] : [document.documentElement, document.body];
var scrollX = false, scrollY = false;
for (var i = 0; i < elems.length; i++) {
var o = elems[i];
// test horizontal
var sl = o.scrollLeft;
o.scrollLeft += (sl > 0) ? -1 : 1;
o.scrollLeft !== sl && (scrollX = scrollX || true);
o.scrollLeft = sl;
// test vertical
var st = o.scrollTop;
o.scrollTop += (st > 0) ? -1 : 1;
o.scrollTop !== st && (scrollY = scrollY || true);
o.scrollTop = st;
}
// ret
return {
scrollX: scrollX,
scrollY: scrollY
};
};
window.addEventListener('resize', function () {
each(resizeTasks);
}, false);
window.addEventListener('scroll', function () {
each(scrollTasks);
}, false);
// return true when unsafeWindow is loaded successfully
function _init($var) {
if (!w) {
// load unsafeWindow
if (typeof(unsafeWindow) !== "undefined" && typeof(unsafeWindow[$var]) !== "undefined")
w = unsafeWindow;
else if (typeof(window[$var]) !== "undefined")
w = window;
else
try {
// for Chrome
var a = document.createElement("a");
a.setAttribute("onclick", "return window;");
var win = a.onclick();
if (typeof(win[$var]) !== "undefined")
w = win;
}
catch (e) {
_debug('Kissogram Toolkit : Unable to load unsafeWindow Object!');
w = null;
}
}
return w;
}
function _onUnsafeWindowReady($var, $func, $options) {
$options = $options || {};
$options.retry = (typeof $options.retry != "number") ? 30 : $options.retry;
$options.interval = $options.interval || 300;
if (_init($var) && (!$options.test || $options.test(w))) {
_debug("Kissogram Toolkit : unsafeWindow injection succeed!");
return $func(w, w[$var]);
}
if ($options.retry-- > 0)
setTimeout(function () { _onUnsafeWindowReady($var, $func, $options); }, $options.interval);
}
var $c = {
// get unsafeWindow if possible
get : function ($var) {
return (this.getUnsafeWindow($var) || window)[$var]; // return safe window
},
// get unsafeWindow
getUnsafeWindow : function ($var) {
return _init($var);
},
/*
when specific function is ready
options : {
test : a function that test if specific variable is loaded properly
retry : retry times before test() returns a failure, default is 40
interval : the interval between every check, default is 300 ms
}
*/
onReady : _onUnsafeWindowReady ,
size : getSize,
onResize : function ($func, $init) {
if ($init)
$func();
resizeTasks.push($func);
return resizeTasks.length-1;
},
_onResize : function ($id) {
if ($id || $id==0)
delete resizeTasks[$id];
},
isScroll : _isScroll,
onScroll : function ($func, $init) {
if ($init)
$func();
scrollTasks.push($func);
return scrollTasks.length-1;
},
_onScroll : function ($id) {
if ($id || $id==0)
delete scrollTasks[$id];
}
};
return $c;
})();
// css Class
var $$css = (function () {
var css_enabled = [],
root = $$d.documentElement,
FEATURE_LIST_ATTR = "feature-list",
CSS_ELEM_REGEXP = /^\s*[.\w][.\w\d-]+[\w\d-]\s*$/;
var instance = function ($arg) {
extend(this, $c);
if ($arg)
this.dictionary = this.dictionary.concat($arg.reverse()); //define the css dictionary
};
// effective only for Chrome
function _getMediaQueriesWidth() {
if ($$browser == "firefox")
return window.innerWidth;
// for Chrome, the width in Media Queries is quite close to window.outerWidth
for (var i=1, width = window.outerWidth, match=false; !match; i++) {
if (width > 0)
match = window.matchMedia('(min-width :' + width + 'px) and (max-width:'+ width + 'px)').matches;
if (match)
return width;
width += (i%2 == 0 ? 1 : -1) * i;
}
}
// to be compatible with Opera
var _addStyle = (typeof GM_addStyle != "undefined") ? GM_addStyle : function($css) {
var NSURI = 'http://www.w3.org/1999/xhtml';
var head = document.getElementsByTagName('head')[0];
var p = head || document.documentElement;
var elem = document.createElementNS(NSURI,'link');
elem.setAttributeNS(NSURI,'rel','stylesheet');
elem.setAttributeNS(NSURI,'type','text/css');
elem.setAttributeNS(NSURI,'href','data:text/css,'+encodeURIComponent($css));
if (head)
p.appendChild(elem);
else
p.insertBefore(elem, p.firstChild);
}
var $c = {
dictionary : [],
ns : 'gpp-', // name space
// append a class to an element
add : function ($elem, $className) {
if (!$elem || !CSS_ELEM_REGEXP.test($className = this.get($className)))
return;
$className = $className.replace(/\./g, ' ');
var arr = $className.split(' '), appendList = "";
for (var i=0, clazz = " "+$elem.className+" "; i<arr.length; i++)
if (arr[i] && clazz.indexOf(' '+arr[i]+' ') < 0)
appendList+= ' '+ arr[i];
$elem.className = utils.trimS($elem.className + appendList);
},
remove : function ($elem, $className) {
if (!$elem || !CSS_ELEM_REGEXP.test($className = this.get($className)))
return;
$className = utils.trimS($className.replace(/\./g, ' '));
var arr = $className.split(' '), clazz = " "+$elem.className+" ";
for (var i=0; i<arr.length; i++)
clazz = clazz.replace(' '+ arr[i] +' ', ' ');
$elem.className = utils.trimS(clazz);
},
// append css
set : function ($str) {
_addStyle(this.get($str));
},
get : function ($str) {
$str = ($str || '').replace(/\/\*[\s\S]*?\*\//g, ''); // clear the comment
// backforwards
for (var i=0; i<this.dictionary.length; i++)
$str = $str.replace(this.dictionary[i][0], this.dictionary[i][1]);
return $str;
},
// get value
val : function ($str) {
var arr = this.get($str).split(/[^\w-]/);
do {
var val = arr.pop();
if (val)
return val;
} while (arr.length>0);
return null; // not found
},
push : function ($arg, $str, $opt) {
$opt = $opt || {};
var condition = this.getCondition($arg);
if ($opt.enable)
this.enable({ name: $arg, value: $opt.value });
$str = $str.replace(/((?:[^,{]+,?)*)\s*{([^}]+)}/g, condition+"$1 {$2}");
$str = $str.replace(/,/g, ","+condition);
this.set($str);
},
pull : function ($feature) {
return css_enabled[$feature] || null;
},
enable : function ($arg) {
if (!$arg) return;
var feaAttr = this.getFeatureListAttr(),
ns = this.ns,
data = " "+(root.getAttribute(feaAttr)||"")+" ",
appendList = "";
utils.toArray($arg).forEach(
function ($item) {
var obj = (typeof $item=="string") ? { name: $item } : $item;
// when $value is null, assert it is a boolean
if (!obj.value && obj.value != 0) {
var name = " "+ obj.name +" ";
if (data.indexOf(name) < 0)
appendList += name;
}
else
root.setAttribute(ns + obj.name, obj.value);
css_enabled[obj.name] = obj.value || true;
}
);
if (appendList)
root.setAttribute(feaAttr, utils.trimS(data + appendList));
},
disable : function ($arg) {
if (!$arg) return '';
var feaAttr = this.getFeatureListAttr(),
ns = this.ns,
hasFeature = root.hasAttribute(feaAttr),
data = " "+(root.getAttribute(feaAttr)||"")+" ";
utils.toArray($arg).forEach(
function ($item) {
var obj = (typeof $item=="string") ? { name: $item } : $item;
if (hasFeature)
data = data.replace(" "+obj.name+" "," ");
root.removeAttribute(ns + obj.name);
delete css_enabled[obj.name];
}
);
if (hasFeature)
root.setAttribute(feaAttr, utils.trimS(data));
},
getFeatureListAttr : function () {
return this.ns + FEATURE_LIST_ATTR;
},
// has specific class
is : function ($elem, $className) {
if (!$elem)
return false;
$className = utils.trimS(this.get($className).replace(/\./g, ' '));
var arr = $className.split(' '), clazz = " "+$elem.className+" ";
for (var i=0; i<arr.length; i++)
if (clazz.indexOf(' '+ arr[i] +' ', ' ') < 0)
return false;
return true;
},
select : function ($str) {
return $$d.querySelector(this.get($str));
},
selectAll : function ($str) {
return utils.toArray($$d.querySelectorAll(this.get($str)));
},
getMediaQueriesWidth : _getMediaQueriesWidth,
extendDictionary : function ($dic) {
this.dictionary = this.dictionary.concat($dic.reverse());
},
// notice the difference between getCondition("string") and getCondition({name: "string"})
// the first one will return featAttr~="string", the second one will return [ns+"string"]
getCondition : function ($arg) {
if (!$arg) return '';
var condition = "html",
feaAttr = this.getFeatureListAttr(),
ns = this.ns;
utils.toArray($arg).forEach(
function ($item) {
var obj = (typeof $item=="string") ? { name: $item, blooeanVal: true } : $item;
condition += "["+ (
obj.blooeanVal ?
feaAttr +'~="'+ obj.name +'"' :
(obj.value || obj.value==0) ?
ns + obj.name +'="'+ obj.value +'"' :
ns + obj.name
)+"]"
;
}
);
return condition+' ';
},
// return a number of piexl from '##px'
getPiexls : function ($str) {
if (!/^\d+(px)?$/i.test($str))
return null; // may be 'auto' or anything else
return parseInt($str.replace(/px$/i, ""));
},
// get the absolute x / y of an element
getAbsPos : function ($e) {
var t = l = 0;
do {
t += $e.offsetTop;
l += $e.offsetLeft;
} while ($e = $e.offsetParent);
return {
left: l,
top: t
};
}
};
return extend(instance, $c);
})();
// the Class that process url change
var $$url = (function () {
var _url = formatUrl(),
urlChangeTasks = [],
hashChangeTasks = [],
urlMonitor = null;
function isUrlChanged($url) {
var url = formatUrl($url);
if (url != _url) {
_url = url;
return true;
}
else
return false;
}
// turn http://xxx.xxx into http://xxx.xxx/
function formatUrl($url) {
var url = $url || $$d.location.href;
if (/^https?:\/\/[\w.]+\w+$/.test(url))
url += '/';
return url;
}
function execTask($e) {
if (!$e) {
_debug('Kissogram Toolkit: URL changed!');
each(urlChangeTasks);
}
else if ($e.type == "popstate") {
_debug('Kissogram Toolkit: URL [popstate] changed!');
each(urlChangeTasks);
}
else if ($e.type == "hashchange") { // hashchange
_debug('Kissogram Toolkit: URL [hash] changed!');
each(hashChangeTasks);
}
}
// bind onpopstate
window.addEventListener('popstate', function (e) {
if (isUrlChanged())
execTask(e);
}, false);
// hashchange
window.addEventListener('hashchange', function (e) {
execTask(e);
}, false);
var $c = {
onUrlChange : function ($func, $init) {
if ($init)
$func();
// mointor
if (urlMonitor == null) {
_debug('Kissogram Toolkit: URL onChange inited!');
urlMonitor = setInterval(function () {
if (isUrlChanged())
execTask();
}, 500);
}
urlChangeTasks.push($func);
},
onHashChange : function ($func, $init) {
if ($init)
$func();
hashChangeTasks.push($func);
},
onUrlMatch : function ($match, $func, $init) {
if (!$match)
return;
},
toString : function () {
return _url = formatUrl();
}
};
return $c;
})();
/*
listen to specific event
$options {
init : boolean / function
runOnce : boolean
interval
}
*/
var listen = (function () {
var interval_count=[]; // collection of interval count
var _support = {};
function testSupport($event) {
var e = $$d.querySelector("body");
e.addEventListener($event, function fn() {
_support[$event] = true;
e.removeEventListener($event, fn, false);
}, false);
}
function doTest() {
testSupport("DOMSubtreeModified");
testSupport("DOMNodeInserted");
testSupport("DOMNodeRemoved");
// try insert a div and then remove it, will trigger all these three event if succeed
var test = $$d.createElement("div"),
e = $$d.querySelector("body") || $$d.body || $$d.documentElement;
e.appendChild(test);
e.removeChild(test);
}
// do dom event support test now -> Opear does not support DOMSubtreeModified
try {
doTest();
}
catch (e) {
_debug("DOMSubtreeModified test failed. Something is wrong");
}
var func = function ($selector, $event, $func, $options) {
$options = $options || {};
// $event & $init cannot be false at the same time
if (!$event && !$options.init)
return;
var evt_listener = (function ($s, $e, $f, $o) {
var id = interval_count.length,
funcWithTiming = setFunctionTiming($f, {
interval : $o.interval || 0,
delay : $o.delay || 0
});
// bind event to dom object
var _bind = function ($d, $evt) {
$d.addEventListener($evt,
(function () {
var runOnceFunc = setFunctionTiming(function () {
$f.apply($d, utils.toArray(arguments), false);
$d.removeEventListener($evt, runOnceFunc);
}, { delay: $o.delay }),
newFunc = function () {
funcWithTiming.apply($d, utils.toArray(arguments));
};
return $o.runOnce ? runOnceFunc : newFunc ;
})()
);
};
return function () {
// if $s is a element itself
var dom = utils.toArray(
(typeof $s == 'string') ? $$css.selectAll($s) : $s
);
if (dom.length > 0) {
// dom is captured
clearInterval(interval_count[id]);
delete interval_count[id];
// to be compatible with Opera!! for DOMSubtreeModified!
if (!_support["DOMSubtreeModified"]) {
if (utils.isStrictArray($e)) {
var DOMInsert = -1, DOMRemove = -1, DOMSub = -1;
for (var i=0;i<$e.length;i++)
if ($e == "DOMNodeInserted")
DOMInsert = i;
else if ($e == "DOMNodeRemoved")
DOMRemove = i;
else if ($e == "DOMSubtreeModified")
DOMSub = i;
if (DOMSub > -1) {
$e.splice(DOMSub, i);
if (DOMInsert < 0)
$e.push("DOMNodeInserted");
if (DOMRemove < 0)
$e.push("DOMNodeRemoved");
}
}
else if ($e == "DOMSubtreeModified")
$e = ["DOMNodeInserted", "DOMNodeRemoved"];
}
for (var i=0; i<dom.length; i++) {
// if the function need initiation (when the listen function capture the dom objects the first time)
if ($o.init) {
if (typeof $o.init == "function")
$o.init.call(dom[i]);
else
$f.call(dom[i]);
}
if (utils.isStrictArray($e))
each($e, function () { _bind(dom[i], this); });
else if (typeof $e == "string") // when $e != null
_bind(dom[i], $e);
else { // do nothing
}
}
}
}
})($selector, $event, $func, $options);
// check it later
interval_count.push(setInterval(evt_listener, 500));
};
return func;
})();
var mouse = (function () {
// simluate a click event
function click($elem, $options) {
if (!$elem)
return;
$options = $options || {};
var opt = {
button : $options.button || 0
};
// dispatch click event following the W3C order
var e = $$d.createEvent("MouseEvents");
e.initMouseEvent("mousedown", true, true, window, 0, 0, 0, 0, 0, false, false, false, false, opt.button, null);
$elem.dispatchEvent(e);
e = $$d.createEvent("MouseEvents");
e.initMouseEvent("mouseup", true, true, window, 0, 0, 0, 0, 0, false, false, false, false, opt.button, null);
$elem.dispatchEvent(e);
e = $$d.createEvent("MouseEvents");
e.initMouseEvent("click", true, true, window, 0, 0, 0, 0, 0, false, false, false, false, opt.button, null);
$elem.dispatchEvent(e);
}
return {
"click" : click
};
})();
// xml http request
var httpRequest = (function () {
function _send($arg) {
var req = new XMLHttpRequest();
req.onreadystatechange = function() {
var res = {
responseXML: (req.readyState==4 ? req.responseXML : ''),
responseText: (req.readyState==4 ? req.responseText : ''),
readyState: req.readyState,
responseHeaders: (req.readyState==4 ? req.getAllResponseHeaders() : ''),
status: (req.readyState==4 ? req.status : 0),
statusText: (req.readyState==4 ? req.statusText : '')
}
if (req.readyState == 4)
res = {
responseXML: req.responseXML,
responseText: req.responseText,
readyState: req.readyState,
responseHeaders: req.getAllResponseHeaders(),
status: req.status,
statusText: req.statusText
};
if ($arg["onreadystatechange"])
$arg["onreadystatechange"](res);
if (req.readyState == 4) {
if ($arg["onload"] && req.status>=200 && req.status<300)
$arg["onload"](res);
if ($arg["onerror"] && (req.status<200 || req.status>=300))
$arg["onerror"](res);
}
}
try {
//cannot do cross domain
req.open($arg.method, $arg.url);
}
catch(e) {
if ($arg["onerror"]) {
//simulate a real error
$arg["onerror"]({
responseXML:'',
responseText:'',
readyState:4,
responseHeaders:'',
status:403,
statusText:'Forbidden'
});
}
return;
}
if ($arg.headers) {
for (var prop in $arg.headers)
req.setRequestHeader(prop, $arg.headers[prop]);
}
req.send((typeof($arg.data)!='undefined') ? $arg.data : null);
}
return {
"send": (typeof GM_xmlhttpRequest == "undefined") ? _send : GM_xmlhttpRequest
};
})();
function _debug($msg) {
if (DEBUG_ON)
console.debug($msg);
}
// Main function begin ========================================
// constructor
var $$c = function () {
};
return extend($$c, {
"each" : each,
"extend" : extend,
"css" : $$css,
"listen" : listen,
"url" : $$url,
"mouse" : mouse,
"browser" : $$browser,
"window" : $$w,
"select" : function (e) { return $$css.select(e) },
"selectAll" : function (e) { return $$css.selectAll(e) },
"tickTork" : setFunctionTiming,
"utils" : utils,
"debug" : _debug,
"httpRequest" : httpRequest
});
})(document);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment