From 20ca5e272b90c8068b377a829f37aa4644bab54a Mon Sep 17 00:00:00 2001 From: Timothy Warren Date: Thu, 21 Jul 2011 14:18:10 -0400 Subject: [PATCH] Split source into modules, and added script to combine them --- README.md | 9 +- combine.php | 81 ++++++++++ kis.js => kis-all.js | 0 src/DOM.js | 364 +++++++++++++++++++++++++++++++++++++++++++ src/QS.js | 91 +++++++++++ src/ajax.js | 81 ++++++++++ src/core.js | 169 ++++++++++++++++++++ src/event.js | 144 +++++++++++++++++ src/module_vars.js | 2 + src/store.js | 40 +++++ 10 files changed, 979 insertions(+), 2 deletions(-) create mode 100755 combine.php rename kis.js => kis-all.js (100%) create mode 100644 src/DOM.js create mode 100644 src/QS.js create mode 100644 src/ajax.js create mode 100644 src/core.js create mode 100644 src/event.js create mode 100644 src/module_vars.js create mode 100644 src/store.js diff --git a/README.md b/README.md index aa5fd10..58a61c4 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,12 @@ A Minimal, Modular Javascript library for Modern browsers. -Aims to be fast, small, and easily split into individual modules. +Aims to be fast, small, and easily split into individual modules. + +You can create your own library by adding and removing modules from the +src directory, and running the "combine.php" script. This will output a +"kis-custom.js" file. (Be careful, as the script will overwrite any "kis-custom.js" +file that already exists). Browser support: IE8+, Latest versions of Firefox, Chrome, Safari, Opera @@ -10,7 +15,7 @@ Browser support: IE8+, Latest versions of Firefox, Chrome, Safari, Opera * Function: `$_(selector).module.function(params);` -## Modules: ## +## Official Modules: ## **Global**: Core functions properties: diff --git a/combine.php b/combine.php new file mode 100755 index 0000000..2edeef8 --- /dev/null +++ b/combine.php @@ -0,0 +1,81 @@ +'.htmlspecialchars($new_file).''; \ No newline at end of file diff --git a/kis.js b/kis-all.js similarity index 100% rename from kis.js rename to kis-all.js diff --git a/src/DOM.js b/src/DOM.js new file mode 100644 index 0000000..5c25b77 --- /dev/null +++ b/src/DOM.js @@ -0,0 +1,364 @@ +/** + * Dom manipulation object + * + */ +(function (){ + var d; + + //Private function for getting/setting attributes + function _attr(sel, name, value) + { + var oldVal, doAttr; + + //Get the value of the attribute, if it exists + if (typeof sel.hasAttribute !== "undefined") + { + if (sel.hasAttribute(name)) + { + oldVal = sel.getAttribute(name); + } + + doAttr = true; + } + else if (typeof sel[name] !== "undefined") + { + oldVal = sel[name]; + doAttr = false; + } + else if (name === "class" && typeof sel.className !== "undefined") //className attribute + { + name = "className"; + oldVal = sel.className; + doAttr = false; + } + + //Well, I guess that attribute doesn't exist + if (typeof oldVal === "undefined" && (typeof value === "undefined" || value === null)) + { + console.log(value); + console.log(sel); + console.log("Element does not have the selected attribute"); + return; + } + + //No value to set? Return the current value + if (typeof value === "undefined") + { + return oldVal; + } + + //Determine what to do with the attribute + if (typeof value !== "undefined" && value !== null) + { + if(doAttr === true) + { + sel.setAttribute(name, value); + } + else + { + sel[name] = value; + } + } + else if (value === null) + { + if(doAttr === true) + { + sel.removeAttribute(name); + } + else + { + delete sel[name]; + } + } + + return (typeof value !== "undefined") ? value : oldVal; + } + + /* + * classList.js: Cross-browser full element.classList implementation. + * 2011-06-15 + * + * By Eli Grey, http://eligrey.com + * Public Domain. + * NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. + */ + + /*global self, document, DOMException */ + + /*! @source http://purl.eligrey.com/github/classList.js/blob/master/classList.js*/ + + if (typeof document !== "undefined" && !("classList" in document.createElement("a"))) + { + (function (view){ + + var classListProp = "classList", + protoProp = "prototype", + elemCtrProto = (view.HTMLElement || view.Element)[protoProp], + objCtr = Object, + strTrim = String[protoProp].trim || + function () + { + return this.replace(/^\s+|\s+$/g, ""); + }, + arrIndexOf = Array[protoProp].indexOf || + function (item) + { + var + i = 0, + len = this.length; + for (; i < len; i++) + { + if (i in this && this[i] === item) + { + return i; + } + } + return -1; + } + // Vendors: please allow content code to instantiate DOMExceptions + , + DOMEx = function (type, message) + { + this.name = type; + this.code = DOMException[type]; + this.message = message; + }, + checkTokenAndGetIndex = function (classList, token) + { + if (token === "") + { + throw new DOMEx("SYNTAX_ERR", "An invalid or illegal string was specified"); + } + if (/\s/.test(token)) + { + throw new DOMEx("INVALID_CHARACTER_ERR", "String contains an invalid character"); + } + return arrIndexOf.call(classList, token); + }, + ClassList = function (elem) + { + var + trimmedClasses = strTrim.call(elem.className), + classes = trimmedClasses ? trimmedClasses.split(/\s+/) : [], + i = 0, + len = classes.length; + for (; i < len; i++) + { + this.push(classes[i]); + } + this._updateClassName = function () + { + elem.className = this.toString(); + }; + }, + classListProto = ClassList[protoProp] = [], + classListGetter = function () + { + return new ClassList(this); + }; + // Most DOMException implementations don't allow calling DOMException's toString() + // on non-DOMExceptions. Error's toString() is sufficient here. + DOMEx[protoProp] = Error[protoProp]; + classListProto.item = function (i) + { + return this[i] || null; + }; + classListProto.contains = function (token) + { + token += ""; + return checkTokenAndGetIndex(this, token) !== -1; + }; + classListProto.add = function (token) + { + token += ""; + if (checkTokenAndGetIndex(this, token) === -1) + { + this.push(token); + this._updateClassName(); + } + }; + classListProto.remove = function (token) + { + token += ""; + var index = checkTokenAndGetIndex(this, token); + if (index !== -1) + { + this.splice(index, 1); + this._updateClassName(); + } + }; + classListProto.toggle = function (token) + { + token += ""; + if (checkTokenAndGetIndex(this, token) === -1) + { + this.add(token); + } + else + { + this.remove(token); + } + }; + classListProto.toString = function () + { + return this.join(" "); + }; + + if (objCtr.defineProperty) + { + var classListPropDesc = { + get: classListGetter, + enumerable: true, + configurable: true + }; + try + { + objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc); + } + catch (ex) + { // IE 8 doesn't support enumerable:true + if (ex.number === -0x7FF5EC54) + { + classListPropDesc.enumerable = false; + objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc); + } + } + } + else if (objCtr[protoProp].__defineGetter__) + { + elemCtrProto.__defineGetter__(classListProp, classListGetter); + } + + }(self)); + } + + function _toCamel(s) + { + return s.replace(/(\-[a-z])/g, function($1){ + return $1.toUpperCase().replace('-',''); + }); + } + + function _css(sel, prop, val) + { + var equi; + + //Camel-case + prop = _toCamel(prop); + + //Let's try the easy way first + if(typeof sel.style[prop] !== "undefined") + { + sel.style[prop] = val; + + //Short circuit + return; + } + + //Let have an object with equivalent properties + //for `special` browsers, and other quirks + + //Todo: get more common properties + equi = { + outerHeight: "offsetHeight", + outerWidth: "offsetWidth", + top: "posTop" + }; + + if(sel.style[equi[prop]]) + { + sel.style[equi[prop]] = val; + return; + } + + //No matches? Well, lets log it for now + console.log("Property " + prop + " nor an equivalent seems to exist"); + } + + // -------------------------------------------------------------------------- + + d = { + addClass: function (c) + { + $_.each(function (e){ + e.classList.add(c); + }); + }, + removeClass: function (c) + { + $_.each(function (e){ + e.classList.remove(c); + }); + }, + hide: function () + { + this.css('display', 'none'); + }, + show: function (type) + { + if (typeof type === "undefined") + { + type = "block"; + } + + this.css("display", type); + }, + attr: function (name, value) + { + var sel = this.el; + + //Make sure you don't try to get a bunch of elements + if (sel.length > 1 && typeof value === "undefined") + { + console.log(sel); + console.log("Must be a singular element"); + return; + } + else if (sel.length > 1 && typeof value !== "undefined") //You can set a bunch, though + { + $_.each(function (e){ + return _attr(e, name, value); + }); + } + else //Normal behavior + { + return _attr(sel, name, value); + } + }, + text: function (value) + { + var oldValue, set, type, sel; + + sel = this.el; + + set = (typeof value !== "undefined") ? true : false; + + type = (typeof sel.innerText !== "undefined") + ? "innerText" + : (typeof sel.textContent !== "undefined") + ? "textContent" + : "innerHTML"; + + oldValue = sel[type]; + + if(set) + { + sel[type] = value; + return value; + } + else + { + return oldValue; + } + }, + css: function (prop, val) + { + $_.each(function (e){ + _css(e, prop, val); + }); + } + }; + + $_.ext('dom', d); + +}()); \ No newline at end of file diff --git a/src/QS.js b/src/QS.js new file mode 100644 index 0000000..8b9dce1 --- /dev/null +++ b/src/QS.js @@ -0,0 +1,91 @@ +/** + * Qs + * + * Object for encoding and decoding querystrings and hashbang strings + */ +(function (){ + + $_.hb = (history.pushState) ? false : true; + + var qs = { + parse: function (hb) + { + hb = hb || $_.hb; + + var h, i, hString, pairs, pLen, data, y; + + data = {}; + + if (hb === true) + { + h = location.hash.split('#!/'); + hString = (h.length > 1) ? h[1] : ''; + } + else if (hb === false || hb === undefined) + { + hString = window.location.search.substring(1); + } + else + { + return false; + } + + pairs = hString.split('&'); + + pLen = pairs.length; + + for (i = 0; i < pLen; i++) + { + y = pairs[i].split('='); + + if (y.length < 2) + { + return data; + } + + data[y[0]] = y[1]; + } + + return data; + }, + set: function (key, value, hb) + { + hb = hb || $_.hb; + var pairs = this.parse(hb); + + if (key !== undefined && value !== undefined) + { + pairs[key] = value; + } + + var vars = []; + + for (var x in pairs) + { + if (pairs.hasOwnProperty(x)) + { + vars.push(x + '=' + pairs[x]); + } + } + + var qs = vars.join('&'); + + if (hb === true) + { + qs = '!/' + qs; + location.hash = qs; + } + + return qs; + }, + get: function (key, hb) + { + hb = hb || $_.hb; + var pairs = this.parse(hb); + return (pairs[key]) ? pairs[key] : ''; + } + }; + + $_.ext('qs', qs); + +}()); diff --git a/src/ajax.js b/src/ajax.js new file mode 100644 index 0000000..f5cec71 --- /dev/null +++ b/src/ajax.js @@ -0,0 +1,81 @@ +/** + * Ajax + * + * Object for making ajax requests + */ +(function (){ + + var ajax = { + _do: function (url, data, callback, isPost) + { + if (typeof callback === "undefined") + { + callback = function (){}; + } + + var request = (typeof window.XMLHttpRequest !== "undefined") + ? new XMLHttpRequest() + : false; + + var type = (isPost) ? "POST" : "GET"; + + url += (type === "GET") ? "?"+this._serialize(data, true) : ''; + + request.open(type, url); + + request.onreadystatechange = function () + { + if (request.readyState === 4) + { + callback(request.responseText); + } + }; + + if (type === "POST") + { + request.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); + request.send(this._serialize(data)); + } + else + { + request.send(null); + } + }, + _serialize: function (data, encode) + { + var pairs = []; + + for (var name in data) + { + if (!data.hasOwnProperty(name)) + { + continue; + } + if (typeof data[name] === "function") + { + continue; + } + + var value = data[name].toString(); + + if (encode === true) + { + name = encodeURIComponent(name.replace(" ", "+")); + value = encodeURIComponent(value.replace(" ", "+")); + } + + pairs.push(name + "=" + value); + } + + return pairs.join("&"); + } + }; + + $_.ext('get', function (url, data, callback){ + ajax._do(url, data, callback, false); + }); + + $_.ext('post', function (url, data, callback){ + ajax._do(url, data, callback, true); + }); +}()); \ No newline at end of file diff --git a/src/core.js b/src/core.js new file mode 100644 index 0000000..1993b41 --- /dev/null +++ b/src/core.js @@ -0,0 +1,169 @@ +/** + Kis JS Keep It Simple JS Library + Copyright Timothy J. Warren + License Public Domain + Version 0.2.0 + */ +(function (){ + + "use strict"; + + //Browser requirements check + if (!document.querySelectorAll) + { + return; + } + + var $_, $, dcopy, sel; + + /** + * $ + * + * Simple DOM selector function + */ + $ = function (a) + { + var x; + if (typeof a !== "string" || typeof a === "undefined"){ return a;} + + //Pick the quickest method for each kind of selector + if(a.match(/^#([\w\-]+$)/)) + { + return document.getElementById(a.split('#')[1]); + } + else if(a.match(/^([\w\-]+)$/)) + { + x = document.getElementsByTagName(a); + } + else + { + x = document.querySelectorAll(a); + } + + //Return the single object if applicable + return (x.length === 1) ? x[0] : x; + }; + + /** + * $_ + * + * Constructor function + */ + $_ = function(s) + { + //Have documentElement be default selector, just in case + if(typeof s == "undefined") + { + sel = (typeof $_.el !== "undefined") + ? $_.el + : document.documentElement; + } + else + { + sel = $(s); + } + + // Make a copy before adding properties + var self = dcopy($_); + + // Give sel to each extension. + for(var i in self) + { + if(typeof self[i] === "object") + { + self[i].el = sel; + } + } + + self.el = sel; + + return self; + }; + + /** + * Deep copy/prototypical constructor function + */ + dcopy = function(obj) + { + var type, F; + + if(typeof obj === "undefined") + { + return; + } + + if(typeof Object.create !== "undefined") + { + return Object.create(obj); + } + + type = typeof obj; + + if(type !== "object" && type !== "function") + { + return; + } + + F = function(){}; + + F.prototype = obj; + + return new F(); + + }; + + //Function to add to $_ object, and get sel + $_.ext = function(name, obj) + { + $_[name] = obj; + obj.el = sel; + }; + + //Selector iteration + $_.ext('each', function (callback) + { + if(typeof sel.length !== "undefined" && sel !== window) + { + var len = sel.length; + + if (len === 0) + { + return; + } + + var selx; + for (var x = 0; x < len; x++) + { + selx = (sel.item(x)) ? sel.item(x) : sel[x]; + callback(selx); + } + } + else + { + callback(sel); + } + }); + + //Set global variables + $_ = window.$_ = window.$_ || $_; + $_.$ = $; + + //console.log polyfill + if(typeof window.console === "undefined") + { + window.console = { + log:function(){} + }; + } + + /** + * String trim function polyfill + */ + if(typeof String.prototype.trim === "undefined") + { + String.prototype.trim = function(){ + return this.replace(/^\s+|\s+$/g, ""); + }; + } + +}()); \ No newline at end of file diff --git a/src/event.js b/src/event.js new file mode 100644 index 0000000..7bfef6c --- /dev/null +++ b/src/event.js @@ -0,0 +1,144 @@ +/** + * Event object + * + * Event api wrapper + */ +(function (){ + + // Property name for expandos on DOM objects + var kis_expando = "KIS_0_2_0"; + + var attach, remove, add_remove, e; + + // Define the proper attach and remove functions + // based on browser support + if(typeof document.addEventListener !== "undefined") + { + attach = function (sel, event, callback) + { + if (typeof sel.addEventListener !== "undefined") + { + sel.addEventListener(event, callback, false); + } + }; + remove = function (sel, event, callback) + { + if (typeof sel.removeEventListener !== "undefined") + { + sel.removeEventListener(event, callback, false); + } + }; + } + //typeof function doesn't work in IE where attachEvent is available: brute force it + else if(typeof document.attachEvent !== "undefined") + { + attach = function (sel, event, callback) + { + function listener () { + // Internet Explorer fails to correctly set the 'this' object + // for event listeners, so we need to set it ourselves. + callback.apply(arguments); + } + + if (typeof sel.attachEvent !== "undefined") + { + remove(event, callback); // Make sure we don't have duplicate listeners + + sel.attachEvent("on" + event, listener); + // Store our listener so we can remove it later + var expando = sel[kis_expando] = sel[kis_expando] || {}; + expando.listeners = expando.listeners || {}; + expando.listeners[event] = expando.listeners[event] || []; + expando.listeners[event].push({ + callback: callback, + listener: listener + }); + } + else + { + console.log("Failed to attach event:"+event+" on "+sel); + } + }; + remove = function (sel, event, callback) + { + if(typeof sel.detachEvent !== "undefined") + { + var expando = sel[kis_expando]; + if (expando && expando.listeners + && expando.listeners[event]) + { + var listeners = expando.listeners[event]; + var len = listeners.length; + for (var i=0; i