├── .gitignore ├── HISTORY.md ├── LICENSE.md ├── README.md ├── build.xml ├── build ├── jsql-min.js ├── jsql.js └── kissy-wrapper │ └── index.js ├── console ├── jdump.js ├── jquery-1.8.0.min.js ├── jsql-console.css ├── jsql-console.html ├── jsql-sample-data.js └── print_r.js ├── docs └── doc.html ├── jsql.html ├── package.json ├── src ├── events.js ├── jsql.js ├── kissy-wrapper │ ├── index.post.js │ └── index.pre.js └── reqwest.js ├── test.html └── test ├── struct.js └── test-data.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .swap 3 | .svn 4 | *.swp 5 | .sw* 6 | .idea/* 7 | node_modules 8 | npm-debug.log 9 | -------------------------------------------------------------------------------- /HISTORY.md: -------------------------------------------------------------------------------- 1 | jSQL Change History 2 | =================== 3 | 4 | 5 | 0.7.1 6 | ----- 7 | 8 | * Remove buffer-auto-rebase while using `count()`, `findAll()`, `find()`, `listAll()`, `update()`, `insert()`, `append()` and `remove()` methods. 9 | 10 | * Added `HISTORY.md` to list all changes. 11 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Guo Kai, http://jsql.us/ 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## jSQL `数据的异想世界` 2 | 3 | ------------------------------- 4 | 5 | ### Why Should I Use jSQL? 6 | 7 | 使用jSQL可以让你更加专注于业务需求和逻辑,通过jSQL可以将排序、筛选等数据查询的底层操作同业务逻辑完美分离。从而提高代码的可维护性和开发效率。jSQL不仅可以用于PC端,也可以在移动手持设备的应用中使用。除了JavaScript版本的jSQL,如果你喜欢jSQL的使用方式和API,同时你又是一个Python后端开发人员且不喜欢庞大的ORM,同样有短小精干的Python-jSQL可以让你在Python框架中方便的对MySQL进行操作。jSQL不是一个库,它是一个使得数据的查询操作变得更方便快捷高效的解决思路和思想、方法的抽象。jSQL内置IO和事件中心,天然的支持远程数据源,并且所有数据的操作行为最终都会通过事件中心广播出去,从而使得数据操作和业务逻辑解耦分离。 8 | 9 | ### API Document 10 | 11 | jSQL是一个用SQL的思想和API实现JavaScript数据操作(查询,过滤)的方法库(同时有基于Tornado DB封装的类似API的后端SQL查询封装),它通过链式调用的方式简化数据查询和操作,同时支持多种形式的数据源。 12 | 13 | - `Object` 14 | - `Array` 15 | - `URI` 16 | 17 | Object类型: 18 | 19 | { 20 | key: {column: value}, 21 | key: {column: value} 22 | } 23 | 24 | Object的key对应数据表的唯一索引,cloumn映射为数据表的字段,value对应数据表相应字段的值。 25 | 26 | Array类型: 27 | 28 | [ 29 | {column: value}, 30 | {column: value} 31 | ] 32 | 33 | Array的每个item对应数据表的一个记录,cloumn对应字段名,value对应字段值,同Object类型不同的是,当前类型的索引key是根据指定的条件或者自动生成的。 34 | 35 | `create(dbname, db /*, indexList */)` 36 | 37 | `create(dbname, db, /*, scope|function */)` 38 | 39 | 在内存中创建一个数据库(指定indexList则根据指定字段生成唯一键) 40 | 41 | 用法示例1: 42 | 43 | jsql.create('local', {}) 44 | 45 | 用法示例2: 46 | 47 | jsql.create('local', 'http://uri?callback=?', 'data.result') 48 | 49 | 用法示例3: 50 | 51 | jsql.create('local', 'http://uri', function(resp) { 52 | return resp.data.result; 53 | }) 54 | 55 | `use(dbname)` 56 | 57 | 使用指定数据库作为当前操作的数据库,并重置缓冲区(buffer) 58 | 59 | local = jsql.use('local').listAll(); 60 | remote = jsql.use('remote').listAll(); 61 | 62 | `drop(dbname)` 63 | 64 | 丢弃指定数据库,同时从内存中抹除 65 | 66 | jsql.drop('local'); 67 | 68 | `dbs()` 69 | 70 | 获取当前内存中存在的所有数据库名称 71 | 72 | dbs = jsql.dbs(); // ['local', 'remote', 'test'] 73 | 74 | `db()` 75 | 76 | 获取当前操作的数据库名称 77 | 78 | cur = jsql.db(); // local 79 | 80 | `select(key)` 81 | 82 | 指定获取的数据字段名称 83 | 84 | local = jsql.use('local').select('*').listAll(); 85 | remote = jsql.use('remote').select('name, age').listAll(); 86 | 87 | `count()` 88 | 89 | 获取当前操作缓冲区的数据条数 90 | 91 | count = jsql.use('local').count(); 92 | 93 | `total(scope|function)` 94 | 95 | 返回当前操作数据库中含有某字段(或者符合传入条件)的数据总条数。 96 | 97 | total = db.total('data.name'); 98 | total = db.total(function(item) { 99 | return item.data.age === 24; 100 | }); 101 | 102 | `orderby(field, /* callback, */ order)` 103 | 104 | 根据指定字段的值(或者处理后的值)和排序规则对当前缓冲区的数据进行排序。 105 | 106 | db = jsql.use('local'); 107 | db.select('*').orderby('data.time', buildDate, 'asc'); 108 | result = db.listAll(); 109 | 110 | `where(fn|[fn, …])` 111 | 112 | 返回当前缓冲区中符合指定条件的数据集合(如果传入为一组fn,则为并集)。 113 | 114 | db = jsql.use('local'); 115 | db.where([function(item) { 116 | return item.data.age < 24; 117 | }, function(item) { 118 | return item.data.sex === 'male'; 119 | }]); 120 | result = db.listAll(); 121 | 122 | `iterate(fn)` 123 | 124 | 根据指定的方法对当前缓冲区中的数据进行遍历。 125 | 126 | db = jsql.use('local'); 127 | db.where(function(item) { 128 | return item.data.age < 24; 129 | }).iterate(function(item) { 130 | item.data.checked = true; 131 | }); 132 | result = db.listAll(); 133 | 134 | `findAll(fn)` 135 | 136 | 以Object类型返回当前缓冲区中的数据集合 137 | 138 | `find(key)` 139 | 140 | 获取缓冲区中指定key的数据。 141 | 142 | `listAll()` 143 | 144 | 以Array类型返回当前缓冲区中的数据集合。 145 | 146 | `update(key, data)` 147 | 148 | 根据指定数据对指定key的数据进行更新操作。 149 | 150 | `insert(item, key /*, from index */)` 151 | 152 | (在指定位置)插入一条数据。 153 | 154 | `append(/* database, */ data)` 155 | 156 | 向当前操作的数据库中追加数据。 157 | 158 | jsql.append('local', {}); 159 | 160 | `remove()` 161 | 162 | 从当前操作的数据库中删除缓冲区中的数据。 163 | 164 | db = jsql.use('local'); 165 | db.where(function(item) { 166 | return item.data.age < 24; 167 | }).remove(); 168 | 169 | `limit(start, end)` 170 | 171 | 返回当前缓冲区指定区域的数据(也可以使用`start:end`)。 172 | 173 | db = jsql.use('local'); 174 | db.limit('1:10'); 175 | result = db.listAll(); 176 | 177 | `keys()` 178 | 179 | 返回当前缓冲区中所有数据的键名集合。 180 | 181 | `first(fn)` 182 | 183 | 返回满足条件的第一条数据。 184 | 185 | `last(fn)` 186 | 187 | 返回满足条件的最后一条数据。 188 | 189 | db = jsql.use('local'); 190 | db.last(function(item) { 191 | return item.data.age < 24; 192 | }); 193 | result = db.find(); 194 | 195 | `distinct()` 196 | 197 | 以Array类型返回当前缓冲区中去重后的数组集合。 198 | 199 | db = jsql.use('local'); 200 | result = db.select('airLineCode').distinct(); // ['CA', 'MU', 'CZ'] 201 | 202 | `rebase()` 203 | 204 | 重置缓冲区数据。 205 | 206 | `noConflict()` 207 | 208 | 避免命名空间冲突。 209 | 210 | _jSQL = jsql.noConflict(); 211 | 212 | #### IO 213 | 214 | `io` 215 | 216 | 数据接口异步请求。 217 | 218 | - `io.ajax(uri, data, success, error)` 219 | - `io.jsonp(uri, data, success, error)` 220 | 221 | - uri: 请求接口的地址 222 | - data: 请求接口的附加数据 223 | - success: 调用成功的回调函数 224 | - error: 调用出错的回调函数 225 | 226 | #### Event 227 | 228 | jSQL内置的事件中心不仅用于广播/订阅数据库自身的操作,同时它是一个完全独立的事件中心,跟数据无关的操作也可以完全通过它进行广播/订阅,且天然支持广播域,多个域各自拥有不同的事件池,互不干扰。 229 | 230 | `on(database, event, callback)` 231 | 232 | 监听数据库事件。 233 | 234 | - database: 被监听的数据库名,取`global`则为全局事件,也可自定义自己的广播域 235 | - event: 被监听的事件名 236 | - create 237 | - update 238 | - drop 239 | - io:start 240 | - io:complete 241 | - io:success 242 | - io:error 243 | - callback: 事件发生后触发的回调函数 244 | 245 | `off(database[, event])` 246 | 247 | 取消对数据库事件的监听。 248 | 249 | `trigger(database, event)` 250 | 251 | 手动触发指定数据库的指定事件。 252 | 253 | 举例,两个不同模块之间的广播监听示例: 254 | 255 | Module A: 256 | 257 | jsql.on('global', 'moduleBChange', function(arg1, arg2) { 258 | // do something 259 | }); 260 | 261 | Module B: 262 | 263 | jsql.trigger('global', 'moduleBChange', arg1, arg2); 264 | 265 | 举例,监听某个数据库的创建: 266 | 267 | jsql.on('local', 'create', function() { 268 | // do something 269 | }); 270 | 271 | jsql.create('local', {}); 272 | 273 | ### 闭合操作 274 | 275 | 所谓闭合操作即执行到此将重置缓冲区,是链式查询的最终状态。 276 | 277 | - `count()` 278 | - `find()` 279 | - `findAll()` 280 | - `listAll()` 281 | - `insert()` 282 | - `save()` 283 | - `saveAll()` 284 | - `update()` 285 | 286 | 除此之外,`use()`也会触发缓冲区重置的操作。 287 | -------------------------------------------------------------------------------- /build.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | /*! 18 | ${license} 19 | */ 20 | 21 | 22 | 23 | 24 | 25 | concatenate complete! 26 | 27 | 28 | 29 | 30 | 31 | version replace complete! 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | minify complete! 46 | 47 | 48 | 49 | 50 | 51 | 52 | /*! 53 | ${license} 54 | */ 55 | 56 | 57 | 58 | 59 | 60 | concatenate complete! 61 | 62 | 63 | 64 | 65 | 66 | version replace complete! 67 | 68 | 69 | -------------------------------------------------------------------------------- /build/jsql-min.js: -------------------------------------------------------------------------------- 1 | !function(){var slice=Array.prototype.slice,toString=Object.prototype.toString,hasOwnProperty=Object.prototype.hasOwnProperty;var nativeForEach=Array.prototype.forEach,nativeIsArray=Array.isArray,nativeKeys=Object.keys,nativeIndexOf=Array.prototype.indexOf,nativeLastIndexOf=Array.prototype.lastIndexOf;var utils={};var jSQL_KEY_NAME="jSQL_Key";var jSQL,_jSQL,_jsql,_DB={},_DBIndexMap={},_protected={},_events={};var interpolation=function(str){var args=[].slice.call(arguments,1);return str.replace(/%s/gim,function(){return args.shift()||""})};var logcat={error:function(error){error=interpolation.apply(this,arguments);if(typeof console!=="undefined"){if(console.warn){console.warn(error);return}if(console.log){console.log(error);return}}throw error},info:function(info){info=interpolation.apply(this,arguments);if(typeof console!=="undefined"){if(console.info){console.info(info);return}if(console.log){console.log(info);return}}}};if(typeof this.jSQL!=="undefined"){_jSQL=this.jSQL}if(typeof this.jsql!=="undefined"){_jsql=this.jsql}jSQL=function(){this.init.apply(this,arguments)};jSQL.prototype={version:"0.7.1-dev",init:function(){this._jSQL=_jSQL;this._jsql=_jsql;this._DB=_DB;this._currentDB=null;this._buffer=null;this._currentDBName=null;this._DBIndexMap=_DBIndexMap;this._protected=_protected;this._indexList=null;this._events=_events;this.utils=utils},create:function(dbname,db){var indexList;var that=this;if(this._DB.hasOwnProperty(dbname)){logcat.error("DB Already Exist.")}if(utils.isArray(db)){indexList=utils.listSlice(arguments,"2:");utils.appendKey(db,indexList);_DBIndexMap[dbname]=utils.arrayToObject(db);this._indexList=indexList||null}if(utils.isPlainObject(db)){_DBIndexMap[dbname]=utils.clone(db);db=utils.objectToArray(db)}if(typeof db==="string"&&db.match(/^http(s)?:\/\//gim)){var scope=arguments[2]||"*";var proxyCallback=function(data){db=typeof scope==="function"?scope.call(this,data):scope==="*"?data:utils.deep(data,scope);that._DB[dbname]=utils.clone(db);that._events[dbname]=that._events[dbname]||new that.Events;that.trigger(dbname,"create");that.trigger(dbname,"io:success");that.trigger(dbname,"io:complete")};var proxyFallback=function(){that._events[dbname]=that._events[dbname]||new that.Events;that.trigger(dbname,"io:error")};if(db.match("callback=")){this.io.jsonp(db,{},proxyCallback,proxyFallback)}else{this.io.ajax(db,{},proxyCallback,proxyFallback)}this._events[dbname]=this._events[dbname]||new this.Events;this.trigger(dbname,"io:start");return this}this._DB[dbname]=utils.clone(db);this._events[dbname]=this._events[dbname]||new this.Events;this.trigger(dbname,"create");return this},use:function(dbname){if(!this._DB.hasOwnProperty(dbname)){throw"Database Not Exist."}this._currentDB=this._DB[dbname];this._currentDBName=dbname;this.rebase();return this},drop:function(dbname){if(this._DB.hasOwnProperty(dbname)){delete this._DB[dbname];this.trigger(dbname,"drop")}return this},dbs:function(){return utils.keys(this._DB)},db:function(){return this._currentDBName},select:function(key){if(!this._currentDB){throw"Please Select Database First."}this._protected["field"]=[].slice.call(utils.isArray(key)?key:arguments);if(key==="*"){return this}return this},count:function(){var result;result=this._buffer.length;return result},total:function(scope){var rs=0,_tmp;for(var _key in this._currentDB){if(this._currentDB.hasOwnProperty(_key)){_tmp=scope==="*"?this._currentDB[_key]:typeof scope==="function"?scope.call(this,this._currentDB[_key],_key)===true?true:undefined:utils.deep(this._currentDB[_key],scope);if(typeof _tmp!=="undefined"){rs++}}}return rs},orderby:function(field,callback,order){var _array=this._buffer;var _this=this;if(typeof callback!=="function"){callback=[order,order=callback][0]}_array.sort(function(a,b){a=utils.deep(a,field);b=utils.deep(b,field);if(callback){a=callback(a);b=callback(b)}return order&&order.toLowerCase()==="asc"?a-b:b-a});this._buffer=_array;this._protected["sort"]=true;return this},where:function(fn){var _tmp=[],_swap;this._buffer=this._buffer||this._currentDB;fn=utils.parseFn(fn);for(var i in this._buffer){if(this._buffer.hasOwnProperty(i)){if(typeof fn==="function"){_swap=fn.call(this,utils.clone(this._buffer[i]),i)}if(utils.isArray(fn)){_swap=false;for(var f in fn){if(fn.hasOwnProperty(f)){if(fn[f].call(this,utils.clone(this._buffer[i]),i)){_swap=true}}}}if(_swap){_tmp.push(this._buffer[i])}}}this._buffer=_tmp;return this},iterate:function(fn){this._buffer=this._buffer||this._currentDB;for(var i in this._buffer){if(this._buffer.hasOwnProperty(i)){fn.call(this,this._buffer[i])}}return this},findAll:function(){var result;result=utils.clone(utils.arrayToObject(this._select()));return result},find:function(key){var result;var _tmp=this._DBIndexMap[this._currentDBName];if(!key){for(var i in _tmp){if(_tmp.hasOwnProperty(i)){if(key){break}if(this._buffer.hasOwnProperty(i)){key=i}}}}result=utils.clone(_tmp[key]);return result},listAll:function(){var result;result=utils.clone(this._select());return result},update:function(fn){var _swap=this.utils.arrayToObject(this._currentDB);var _tmp;this._buffer=this._buffer||this._currentDB;if(!this._currentDB){throw"Please Select Database First."}for(var i in this._buffer){if(this._buffer.hasOwnProperty(i)){_tmp=fn.call(this,utils.clone(this._buffer[i]));if(_tmp){_swap[this._buffer[i][jSQL_KEY_NAME]]=_tmp}}}this._currentDB=this.utils.objectToArray(_swap);this._DB[this._currentDBName]=this.utils.objectToArray(_swap);this.trigger(this._currentDBName,"update");return this},insert:function(item,key){var item=utils.clone(item);var fromIndex=arguments[2];item[jSQL_KEY_NAME]=item.key||key;fromIndex?this._currentDB.splice(fromIndex,0,item):this._currentDB.push(item);this.trigger(this._currentDBName,"update");return this},append:function(database,data){if(arguments.length>1){this.use(database)}else{data=arguments[0]}data=utils.clone(data);if(utils.isArray(data)){utils.appendKey(data,this._indexList);this._currentDB=this._currentDB.concat(data)}if(utils.isPlainObject(data)){this._currentDB=this._currentDB.concat(utils.objectToArray(data))}this._DB[this._currentDBName]=this.utils.objectToArray(this._currentDB);this.trigger(this._currentDBName,"update");return this},remove:function(){var that=this;var _swap=this.utils.arrayToObject(this._currentDB);this._buffer=this._buffer||this._currentDB;for(var i in this._buffer){if(this._buffer.hasOwnProperty(i)){delete _swap[this._buffer[i][jSQL_KEY_NAME]]}}this._currentDB=this.utils.objectToArray(_swap);this._DB[this._currentDBName]=this.utils.objectToArray(_swap);return this},limit:function(start,end){var _tmp=this._buffer;var limit;if(!end){start=[0,end=start][0]}limit=start+":"+(start+end);this._buffer=utils.listSlice(_tmp,limit);return this},keys:function(){return utils.keys(this.findAll())},first:function(fn){if(fn){return this.where(fn).first()}return utils.listSlice(this._select(),":1")},last:function(fn){if(fn){return this.where(fn).last()}return utils.listSlice(this._select(),"-1:")},distinct:function(field){return utils.distinct(this.listAll())},rebase:function(){this._protected={};this.select("*");this._resetBuffer();this._updateIndexMap();return this},noConflict:function(){if(window.jSQL===jSQL){window.jSQL=this._jSQL}if(window.jsql===jsql){window.jsql=this._jsql}return this},io:new function(){var that=this;this.ajax=function(uri,data,success,error){var args=[].slice.call(arguments);if(args.length<4){args.splice(1,0,{})}uri=args[0],data=args[1],success=args[2],error=args[3];data._t=utils.uuid();this.reqwest({url:uri,type:"json",method:"get",data:data,success:success,error:error})},this.jsonp=function(uri,data,success,error){var args=[].slice.call(arguments);if(args.length<4){args.splice(1,0,{})}uri=args[0],data=args[1],success=args[2],error=args[3];if(!uri.match("callback=")){if(uri.match(/\?/gim)){if(uri.lastIndexOf("&")===uri.length-1){uri+="callback=?&_t="+utils.uuid()}else{uri+="&callback=?&_t="+utils.uuid()}}else{uri+="?callback=?&_t="+utils.uuid()}}this.reqwest({url:uri,type:"jsonp",data:data,success:success,error:error})}},on:function(database,event,callback){this._events[database]=this._events[database]||new this.Events;return this._events[database].on(event,callback)},off:function(database,event,callback){return this._events[database].off(event,callback)},trigger:function(database,event){var args=[].slice.call(arguments,1);if(!this._events.hasOwnProperty(database)){return false}logcat.info("%s: trigger - %s",database,event);return this._events[database].trigger.apply(this._events[database],args)},alias:function(name){window[name]=this;return this},_select:function(field){var tmp,result=[];field=field||this._protected["field"];if(this._protected["sort"]===true){this.trigger(this._currentDBName,"sort")}if(field==="*"||field.join&&field.join("")==="*"){return this._buffer}if(typeof field==="string"){field=field.split(",")}utils.each(this._buffer,function(o,i,r){tmp={};tmp[jSQL_KEY_NAME]=utils.deep(o,jSQL_KEY_NAME);if(field.length===1){result.push(utils.deep(o,field[0]));return}utils.each(field,function(_o,_i,_r){if(o.hasOwnProperty(_o)){tmp[_o.split(".").pop()]=utils.deep(o,_o)}});result.push(tmp)});return result},_updateIndexMap:function(){_DBIndexMap[this._currentDBName]=utils.arrayToObject(this._currentDB)},_resetBuffer:function(){this._buffer=this._currentDB}};utils={deep:function(data,scope){var _tmp=data,scope=scope.split(".");for(var i=0;i1?range[1]||list.length:list.length;return[].slice.call(list,start,end)},appendKey:function(list,indexList){var that=this;that.each(list,function(o,i,r){o[jSQL_KEY_NAME]=that.keygen(o,indexList)||i})},keys:nativeKeys||function(){var hasDontEnumBug=true,dontEnums=["toString","toLocaleString","valueOf","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","constructor"],dontEnumsLength=dontEnums.length;for(var key in{toString:null}){hasDontEnumBug=false}return function keys(object){if(typeof object!="object"&&typeof object!="function"||object===null){throw new TypeError("Object.keys called on a non-object")}var keys=[];for(var name in object){if(object.hasOwnProperty(name)){keys.push(name)}}if(hasDontEnumBug){for(var i=0,ii=dontEnumsLength;i>>0;if(!length){return-1}var i=0;if(arguments.length>2){i=arguments[2]}i=i>=0?i:Math.max(0,length+i);for(;i>>0;if(!length){return-1}var i=length-1;if(arguments.length>1){i=Math.min(i,toInteger(arguments[1]))}i=i>=0?i:length-Math.abs(i);for(;i>=0;i--){if(i in self&&sought===self[i]){return i}}return-1},uuid:function(){return(new Date).getTime()+"_"+parseInt(Math.random()*1e3)},distinct:function(arr){var tmp=[];for(var i=0;i0){self._completeHandlers.shift()(resp)}}function success(resp){var filteredResponse=globalSetupOptions.dataFilter(resp.responseText,type),r=resp.responseText=filteredResponse;if(r){switch(type){case"json":try{resp=win.JSON?win.JSON.parse(r):eval("("+r+")")}catch(err){return error(resp,"Could not parse JSON in response",err)}break;case"js":resp=eval(r);break;case"html":resp=r;break;case"xml":resp=resp.responseXML&&resp.responseXML.parseError&&resp.responseXML.parseError.errorCode&&resp.responseXML.parseError.reason?null:resp.responseXML;break}}self._responseArgs.resp=resp;self._fulfilled=true;fn(resp);while(self._fulfillmentHandlers.length>0){self._fulfillmentHandlers.shift()(resp)}complete(resp)}function error(resp,msg,t){self._responseArgs.resp=resp;self._responseArgs.msg=msg;self._responseArgs.t=t;self._erred=true;while(self._errorHandlers.length>0){self._errorHandlers.shift()(resp,msg,t)}complete(resp)}this.request=getRequest.call(this,success,error)}Reqwest.prototype={abort:function(){this._aborted=true;this.request.abort()},retry:function(){init.call(this,this.o,this.fn)},then:function(success,fail){if(this._fulfilled){success(this._responseArgs.resp)}else if(this._erred){fail(this._responseArgs.resp,this._responseArgs.msg,this._responseArgs.t)}else{this._fulfillmentHandlers.push(success);this._errorHandlers.push(fail)}return this},always:function(fn){if(this._fulfilled||this._erred){fn(this._responseArgs.resp)}else{this._completeHandlers.push(fn)}return this},fail:function(fn){if(this._erred){fn(this._responseArgs.resp,this._responseArgs.msg,this._responseArgs.t)}else{this._errorHandlers.push(fn)}return this}};function reqwest(o,fn){return new Reqwest(o,fn)}function normalize(s){return s?s.replace(/\r?\n/g,"\r\n"):""}function serial(el,cb){var n=el.name,t=el.tagName.toLowerCase(),optCb=function(o){if(o&&!o.disabled)cb(n,normalize(o.attributes.value&&o.attributes.value.specified?o.value:o.text))},ch,ra,val,i;if(el.disabled||!n)return;switch(t){case"input":if(!/reset|button|image|file/i.test(el.type)){ch=/checkbox/i.test(el.type);ra=/radio/i.test(el.type);val=el.value;(!(ch||ra)||el.checked)&&cb(n,normalize(ch&&val===""?"on":val))}break;case"textarea":cb(n,normalize(el.value));break;case"select":if(el.type.toLowerCase()==="select-one"){optCb(el.selectedIndex>=0?el.options[el.selectedIndex]:null)}else{for(i=0;el.length&&i=0;i-=2){if(!(callback&&list[i]!==callback||context&&list[i+1]!==context)){list.splice(i,2)}}}return this};Events.prototype.trigger=function(events){var cache,event,all,list,i,len,rest=[],args,returned={status:true};if(!(cache=this.__events))return this;events=events.split(eventSplitter);for(i=1,len=arguments.length;i 1) { 392 | this.use(database); 393 | } else { 394 | data = arguments[0]; 395 | } 396 | 397 | data = utils.clone(data); 398 | 399 | if(utils.isArray(data)) { 400 | utils.appendKey(data, this._indexList); 401 | this._currentDB = this._currentDB.concat(data); 402 | } 403 | 404 | if(utils.isPlainObject(data)) { 405 | this._currentDB = this._currentDB.concat(utils.objectToArray(data)); 406 | } 407 | 408 | this._DB[this._currentDBName] = this.utils.objectToArray(this._currentDB); 409 | this.trigger(this._currentDBName, 'update'); 410 | return this; 411 | }, 412 | 413 | remove: function() { 414 | var that = this; 415 | var _swap = this.utils.arrayToObject(this._currentDB); 416 | this._buffer = this._buffer || this._currentDB; 417 | 418 | for(var i in this._buffer) { 419 | if(this._buffer.hasOwnProperty(i)) { 420 | delete _swap[this._buffer[i][jSQL_KEY_NAME]]; 421 | } 422 | } 423 | 424 | this._currentDB = this.utils.objectToArray(_swap); 425 | this._DB[this._currentDBName] = this.utils.objectToArray(_swap); 426 | return this; 427 | }, 428 | 429 | limit: function(start, end) { 430 | var _tmp = this._buffer; 431 | var limit; 432 | 433 | if(!end) { 434 | start = [0, end = start][0]; 435 | } 436 | 437 | limit = start + ':' + (start + end); 438 | 439 | this._buffer = utils.listSlice(_tmp, limit); 440 | return this; 441 | }, 442 | 443 | keys: function() { 444 | return utils.keys(this.findAll()); 445 | }, 446 | 447 | first: function(fn) { 448 | if(fn) { 449 | return this.where(fn).first(); 450 | } 451 | 452 | return utils.listSlice(this._select(), ':1'); 453 | }, 454 | 455 | last: function(fn) { 456 | if(fn) { 457 | return this.where(fn).last(); 458 | } 459 | 460 | return utils.listSlice(this._select(), '-1:'); 461 | }, 462 | 463 | distinct: function(field) { 464 | return utils.distinct(this.listAll()); 465 | }, 466 | 467 | rebase: function() { 468 | this._protected = {}; 469 | this.select('*'); 470 | this._resetBuffer(); 471 | this._updateIndexMap(); 472 | return this; 473 | }, 474 | 475 | noConflict: function() { 476 | if(window.jSQL === jSQL) { 477 | window.jSQL = this._jSQL; 478 | } 479 | 480 | if(window.jsql === jsql) { 481 | window.jsql = this._jsql; 482 | } 483 | 484 | return this; 485 | }, 486 | 487 | io: new function() { 488 | var that = this; 489 | 490 | this.ajax = function(uri, data, success, error) { 491 | var args = [].slice.call(arguments); 492 | 493 | if(args.length < 4) { 494 | args.splice(1, 0, {}); 495 | } 496 | 497 | uri = args[0], 498 | data = args[1], 499 | success = args[2], 500 | error = args[3]; 501 | 502 | data._t = utils.uuid(); 503 | 504 | this.reqwest({ 505 | url: uri, 506 | type: 'json', 507 | method: 'get', 508 | data: data, 509 | success: success, 510 | error: error 511 | }); 512 | }, 513 | 514 | this.jsonp = function(uri, data, success, error) { 515 | var args = [].slice.call(arguments); 516 | 517 | if(args.length < 4) { 518 | args.splice(1, 0, {}); 519 | } 520 | 521 | uri = args[0], 522 | data = args[1], 523 | success = args[2], 524 | error = args[3]; 525 | 526 | if(!uri.match('callback=')) { 527 | if(uri.match(/\?/igm)) { 528 | if(uri.lastIndexOf('&') === uri.length - 1) { 529 | uri += 'callback=?&_t=' + utils.uuid(); 530 | } else { 531 | uri += '&callback=?&_t=' + utils.uuid(); 532 | } 533 | } else { 534 | uri += '?callback=?&_t=' + utils.uuid(); 535 | } 536 | } 537 | 538 | this.reqwest({ 539 | url: uri, 540 | type: 'jsonp', 541 | data: data, 542 | success: success, 543 | error: error 544 | }); 545 | } 546 | }, 547 | 548 | on: function(database, event, callback) { 549 | this._events[database] = this._events[database] || new this.Events(); 550 | return this._events[database].on(event, callback); 551 | }, 552 | 553 | off: function(database, event, callback) { 554 | return this._events[database].off(event, callback); 555 | }, 556 | 557 | trigger: function(database, event) { 558 | var args = [].slice.call(arguments, 1); 559 | 560 | if(!this._events.hasOwnProperty(database)) { 561 | return false; 562 | } 563 | 564 | logcat.info('%s: trigger - %s', database, event); 565 | return this._events[database].trigger.apply(this._events[database], args); 566 | }, 567 | 568 | alias: function(name) { 569 | window[name] = this; 570 | return this; 571 | }, 572 | 573 | _select: function(field) { 574 | var tmp, result = []; 575 | 576 | field = field || this._protected['field']; 577 | 578 | if(this._protected['sort'] === true) { 579 | this.trigger(this._currentDBName, 'sort'); 580 | } 581 | 582 | if(field === '*' || (field.join && field.join('') === '*')) { 583 | return this._buffer; 584 | } 585 | 586 | if(typeof(field) === 'string') { 587 | field = field.split(','); 588 | } 589 | 590 | utils.each(this._buffer, function(o, i, r) { 591 | tmp = {}; 592 | tmp[jSQL_KEY_NAME] = utils.deep(o, jSQL_KEY_NAME); 593 | 594 | if(field.length === 1) { 595 | result.push(utils.deep(o, field[0])); 596 | return; 597 | } 598 | 599 | utils.each(field, function(_o, _i, _r) { 600 | if(o.hasOwnProperty(_o)) { 601 | tmp[_o.split('.').pop()] = utils.deep(o, _o); 602 | } 603 | }); 604 | result.push(tmp); 605 | }); 606 | 607 | return result; 608 | }, 609 | 610 | _updateIndexMap: function() { 611 | _DBIndexMap[this._currentDBName] = utils.arrayToObject(this._currentDB); 612 | }, 613 | 614 | _resetBuffer: function() { 615 | this._buffer = this._currentDB; //reset the _buffer 616 | } 617 | }; 618 | 619 | utils = { 620 | deep: function(data, scope) { 621 | var _tmp = data, scope = scope.split('.'); 622 | for(var i = 0; i < scope.length; i++) { 623 | _tmp = _tmp[scope[i]]; 624 | } 625 | return _tmp; 626 | }, 627 | 628 | isArray: nativeIsArray || function(obj) { 629 | return toString.call(obj) === '[object Array]'; 630 | }, 631 | 632 | isObject: function(obj) { 633 | return obj === Object(obj); 634 | }, 635 | 636 | isPlainObject: function(obj) { 637 | return this.isObject(obj) && obj.constructor === Object; 638 | }, 639 | 640 | clone: function (obj) { 641 | if(obj == null || typeof(obj) != 'object') { 642 | return obj; 643 | } 644 | 645 | var temp = new obj.constructor(); 646 | for(var key in obj) { 647 | temp[key] = arguments.callee(obj[key]); 648 | } 649 | 650 | return temp; 651 | }, 652 | 653 | objectToArray: function(object) { 654 | var array = [], object = this.clone(object); 655 | 656 | for(var i in object) { 657 | if(object.hasOwnProperty(i)) { 658 | object[i][jSQL_KEY_NAME] = i; 659 | array.push(object[i]); 660 | } 661 | } 662 | 663 | return array; 664 | }, 665 | 666 | arrayToObject: function(array, key) { 667 | var object = {}; 668 | 669 | for(var i = 0; i < array.length; i++) { 670 | object[array[i][key || jSQL_KEY_NAME]] = this.clone(array[i]); 671 | delete object[array[i][key || jSQL_KEY_NAME]][key || jSQL_KEY_NAME]; 672 | }; 673 | 674 | return object; 675 | }, 676 | 677 | each: function(list, fn) { 678 | if(nativeForEach) { 679 | list.forEach(fn); 680 | return; 681 | } 682 | 683 | for(var i = 0; i < list.length; i++) { 684 | fn(list[i], i, list); 685 | } 686 | }, 687 | 688 | keygen: function(object, indexList) { 689 | var that = this; 690 | var baseRef = [].slice.call(arguments, 1); 691 | var key = ''; 692 | 693 | if(that.isArray(indexList)) { 694 | baseRef = indexList; 695 | } 696 | 697 | that.each(baseRef, function(o, i, r) { 698 | key += utils.deep(object, o); 699 | }); 700 | 701 | return key; 702 | }, 703 | 704 | listSlice: function(list, range) { 705 | var start, end; 706 | 707 | list = [].slice.call(list); 708 | range = range.split(':'); 709 | start = range[0] || 0; 710 | end = range.length > 1 ? range[1] || list.length : list.length; 711 | return [].slice.call(list, start, end); 712 | }, 713 | 714 | appendKey: function(list, indexList) { 715 | var that = this; 716 | 717 | that.each(list, function(o, i, r) { 718 | o[jSQL_KEY_NAME] = that.keygen(o, indexList) || i; 719 | }); 720 | }, 721 | 722 | keys: nativeKeys || (function() { 723 | var hasDontEnumBug = true, 724 | dontEnums = [ 725 | 'toString', 726 | 'toLocaleString', 727 | 'valueOf', 728 | 'hasOwnProperty', 729 | 'isPrototypeOf', 730 | 'propertyIsEnumerable', 731 | 'constructor' 732 | ], 733 | dontEnumsLength = dontEnums.length; 734 | 735 | for (var key in {'toString': null}) { 736 | hasDontEnumBug = false; 737 | } 738 | 739 | return function keys(object) { 740 | if ((typeof object != 'object' && typeof object != 'function') || object === null) { 741 | throw new TypeError('Object.keys called on a non-object'); 742 | } 743 | 744 | var keys = []; 745 | for (var name in object) { 746 | if (object.hasOwnProperty(name)) { 747 | keys.push(name); 748 | } 749 | } 750 | 751 | if (hasDontEnumBug) { 752 | for (var i = 0, ii = dontEnumsLength; i < ii; i++) { 753 | var dontEnum = dontEnums[i]; 754 | if (object.hasOwnProperty(dontEnum)) { 755 | keys.push(dontEnum); 756 | } 757 | } 758 | } 759 | return keys; 760 | }; 761 | })(), 762 | 763 | parseFn: function(fn) { 764 | if(typeof(fn) === 'string') { 765 | fn = fn || true; 766 | fn = new Function('data', 'with(data) { return ' + fn + '; }'); 767 | } 768 | 769 | return fn; 770 | }, 771 | 772 | indexOf: function(list, sought /*, fromIndex */ ) { 773 | if(nativeIndexOf) { 774 | return nativeIndexOf.apply(list, this.listSlice(arguments, '1:')); 775 | } 776 | 777 | var self = list, 778 | length = self.length >>> 0; 779 | 780 | if (!length) { 781 | return -1; 782 | } 783 | 784 | var i = 0; 785 | if (arguments.length > 2) { 786 | i = arguments[2]; 787 | } 788 | 789 | // handle negative indices 790 | i = i >= 0 ? i : Math.max(0, length + i); 791 | for (; i < length; i++) { 792 | if (i in self && self[i] === sought) { 793 | return i; 794 | } 795 | } 796 | return -1; 797 | }, 798 | 799 | lastIndexOf: function lastIndexOf(list, sought /*, fromIndex */) { 800 | if(nativeLastIndexOf) { 801 | return nativeLastIndexOf.apply(list, this.listSlice(arguments, '1:')); 802 | } 803 | 804 | var self = list, 805 | length = self.length >>> 0; 806 | 807 | if (!length) { 808 | return -1; 809 | } 810 | var i = length - 1; 811 | if (arguments.length > 1) { 812 | i = Math.min(i, toInteger(arguments[1])); 813 | } 814 | // handle negative indices 815 | i = i >= 0 ? i : length - Math.abs(i); 816 | for (; i >= 0; i--) { 817 | if (i in self && sought === self[i]) { 818 | return i; 819 | } 820 | } 821 | return -1; 822 | }, 823 | 824 | uuid: function() { 825 | return new Date().getTime() + '_' + parseInt(Math.random() * 1000); 826 | }, 827 | 828 | distinct: function(arr) { 829 | var tmp = []; 830 | 831 | for(var i = 0; i < arr.length; i++) { 832 | if(tmp.indexOf(arr[i]) === -1) { 833 | tmp.push(arr[i]); 834 | } 835 | } 836 | 837 | return tmp; 838 | } 839 | }; 840 | 841 | jSQL = new jSQL(); 842 | 843 | typeof(module) !== 'undefined' && module.exports ? module.exports = jSQL : this.jsql = this.jSQL = jSQL; 844 | })(); 845 | /*! 846 | * Reqwest! A general purpose XHR connection manager 847 | * (c) Dustin Diaz 2013 848 | * https://github.com/ded/reqwest 849 | * license MIT 850 | */ 851 | !function (name, context, definition) { 852 | if (typeof module != 'undefined' && module.exports) module.exports = definition() 853 | else if (typeof define == 'function' && define.amd) define(definition) 854 | else context[name] = definition() 855 | }('reqwest', jsql.io, function () { 856 | 857 | var win = window 858 | , doc = document 859 | , twoHundo = /^20\d$/ 860 | , byTag = 'getElementsByTagName' 861 | , readyState = 'readyState' 862 | , contentType = 'Content-Type' 863 | , requestedWith = 'X-Requested-With' 864 | , head = doc[byTag]('head')[0] 865 | , uniqid = 0 866 | , callbackPrefix = 'reqwest_' + (+new Date()) 867 | , lastValue // data stored by the most recent JSONP callback 868 | , xmlHttpRequest = 'XMLHttpRequest' 869 | , noop = function () {} 870 | 871 | , isArray = typeof Array.isArray == 'function' 872 | ? Array.isArray 873 | : function (a) { 874 | return a instanceof Array 875 | } 876 | 877 | , defaultHeaders = { 878 | contentType: 'application/x-www-form-urlencoded' 879 | , requestedWith: xmlHttpRequest 880 | , accept: { 881 | '*': 'text/javascript, text/html, application/xml, text/xml, */*' 882 | , xml: 'application/xml, text/xml' 883 | , html: 'text/html' 884 | , text: 'text/plain' 885 | , json: 'application/json, text/javascript' 886 | , js: 'application/javascript, text/javascript' 887 | } 888 | } 889 | 890 | , xhr = win[xmlHttpRequest] 891 | ? function () { 892 | return new XMLHttpRequest() 893 | } 894 | : function () { 895 | return new ActiveXObject('Microsoft.XMLHTTP') 896 | } 897 | , globalSetupOptions = { 898 | dataFilter: function (data) { 899 | return data 900 | } 901 | } 902 | 903 | function handleReadyState(r, success, error) { 904 | return function () { 905 | // use _aborted to mitigate against IE err c00c023f 906 | // (can't read props on aborted request objects) 907 | if (r._aborted) return error(r.request) 908 | if (r.request && r.request[readyState] == 4) { 909 | r.request.onreadystatechange = noop 910 | if (twoHundo.test(r.request.status)) 911 | success(r.request) 912 | else 913 | error(r.request) 914 | } 915 | } 916 | } 917 | 918 | function setHeaders(http, o) { 919 | var headers = o.headers || {} 920 | , h 921 | 922 | headers.Accept = headers.Accept 923 | || defaultHeaders.accept[o.type] 924 | || defaultHeaders.accept['*'] 925 | 926 | // breaks cross-origin requests with legacy browsers 927 | if (!o.crossOrigin && !headers[requestedWith]) headers[requestedWith] = defaultHeaders.requestedWith 928 | if (!headers[contentType]) headers[contentType] = o.contentType || defaultHeaders.contentType 929 | for (h in headers) 930 | headers.hasOwnProperty(h) && http.setRequestHeader(h, headers[h]) 931 | } 932 | 933 | function setCredentials(http, o) { 934 | if (typeof o.withCredentials !== 'undefined' && typeof http.withCredentials !== 'undefined') { 935 | http.withCredentials = !!o.withCredentials 936 | } 937 | } 938 | 939 | function generalCallback(data) { 940 | lastValue = data 941 | } 942 | 943 | function urlappend (url, s) { 944 | return url + (/\?/.test(url) ? '&' : '?') + s 945 | } 946 | 947 | function handleJsonp(o, fn, err, url) { 948 | var reqId = uniqid++ 949 | , cbkey = o.jsonpCallback || 'callback' // the 'callback' key 950 | , cbval = o.jsonpCallbackName || reqwest.getcallbackPrefix(reqId) 951 | // , cbval = o.jsonpCallbackName || ('reqwest_' + reqId) // the 'callback' value 952 | , cbreg = new RegExp('((^|\\?|&)' + cbkey + ')=([^&]+)') 953 | , match = url.match(cbreg) 954 | , script = doc.createElement('script') 955 | , loaded = 0 956 | , isIE10 = navigator.userAgent.indexOf('MSIE 10.0') !== -1 957 | 958 | if (match) { 959 | if (match[3] === '?') { 960 | url = url.replace(cbreg, '$1=' + cbval) // wildcard callback func name 961 | } else { 962 | cbval = match[3] // provided callback func name 963 | } 964 | } else { 965 | url = urlappend(url, cbkey + '=' + cbval) // no callback details, add 'em 966 | } 967 | 968 | win[cbval] = generalCallback 969 | 970 | script.type = 'text/javascript' 971 | script.src = url 972 | script.async = true 973 | if (typeof script.onreadystatechange !== 'undefined' && !isIE10) { 974 | // need this for IE due to out-of-order onreadystatechange(), binding script 975 | // execution to an event listener gives us control over when the script 976 | // is executed. See http://jaubourg.net/2010/07/loading-script-as-onclick-handler-of.html 977 | // 978 | // if this hack is used in IE10 jsonp callback are never called 979 | script.event = 'onclick' 980 | script.htmlFor = script.id = '_reqwest_' + reqId 981 | } 982 | 983 | script.onload = script.onreadystatechange = function () { 984 | if ((script[readyState] && script[readyState] !== 'complete' && script[readyState] !== 'loaded') || loaded) { 985 | return false 986 | } 987 | script.onload = script.onreadystatechange = null 988 | script.onclick && script.onclick() 989 | // Call the user callback with the last value stored and clean up values and scripts. 990 | o.success && o.success(lastValue) 991 | lastValue = undefined 992 | head.removeChild(script) 993 | loaded = 1 994 | } 995 | 996 | // Add the script to the DOM head 997 | head.appendChild(script) 998 | 999 | // Enable JSONP timeout 1000 | return { 1001 | abort: function () { 1002 | script.onload = script.onreadystatechange = null 1003 | o.error && o.error({}, 'Request is aborted: timeout', {}) 1004 | lastValue = undefined 1005 | head.removeChild(script) 1006 | loaded = 1 1007 | } 1008 | } 1009 | } 1010 | 1011 | function getRequest(fn, err) { 1012 | var o = this.o 1013 | , method = (o.method || 'GET').toUpperCase() 1014 | , url = typeof o === 'string' ? o : o.url 1015 | // convert non-string objects to query-string form unless o.processData is false 1016 | , data = (o.processData !== false && o.data && typeof o.data !== 'string') 1017 | ? reqwest.toQueryString(o.data) 1018 | : (o.data || null) 1019 | , http 1020 | 1021 | // if we're working on a GET request and we have data then we should append 1022 | // query string to end of URL and not post data 1023 | if ((o.type == 'jsonp' || method == 'GET') && data) { 1024 | url = urlappend(url, data) 1025 | data = null 1026 | } 1027 | 1028 | if (o.type == 'jsonp') return handleJsonp(o, fn, err, url) 1029 | 1030 | http = xhr() 1031 | http.open(method, url, true) 1032 | setHeaders(http, o) 1033 | setCredentials(http, o) 1034 | http.onreadystatechange = handleReadyState(this, fn, err) 1035 | o.before && o.before(http) 1036 | http.send(data) 1037 | return http 1038 | } 1039 | 1040 | function Reqwest(o, fn) { 1041 | this.o = o 1042 | this.fn = fn 1043 | 1044 | init.apply(this, arguments) 1045 | } 1046 | 1047 | function setType(url) { 1048 | var m = url.match(/\.(json|jsonp|html|xml)(\?|$)/) 1049 | return m ? m[1] : 'js' 1050 | } 1051 | 1052 | function init(o, fn) { 1053 | 1054 | this.url = typeof o == 'string' ? o : o.url 1055 | this.timeout = null 1056 | 1057 | // whether request has been fulfilled for purpose 1058 | // of tracking the Promises 1059 | this._fulfilled = false 1060 | // success handlers 1061 | this._fulfillmentHandlers = [] 1062 | // error handlers 1063 | this._errorHandlers = [] 1064 | // complete (both success and fail) handlers 1065 | this._completeHandlers = [] 1066 | this._erred = false 1067 | this._responseArgs = {} 1068 | 1069 | var self = this 1070 | , type = o.type || setType(this.url) 1071 | 1072 | fn = fn || function () {} 1073 | 1074 | if (o.timeout) { 1075 | this.timeout = setTimeout(function () { 1076 | self.abort() 1077 | }, o.timeout) 1078 | } 1079 | 1080 | if (o.success) { 1081 | this._fulfillmentHandlers.push(function () { 1082 | o.success.apply(o, arguments) 1083 | }) 1084 | } 1085 | 1086 | if (o.error) { 1087 | this._errorHandlers.push(function () { 1088 | o.error.apply(o, arguments) 1089 | }) 1090 | } 1091 | 1092 | if (o.complete) { 1093 | this._completeHandlers.push(function () { 1094 | o.complete.apply(o, arguments) 1095 | }) 1096 | } 1097 | 1098 | function complete (resp) { 1099 | o.timeout && clearTimeout(self.timeout) 1100 | self.timeout = null 1101 | while (self._completeHandlers.length > 0) { 1102 | self._completeHandlers.shift()(resp) 1103 | } 1104 | } 1105 | 1106 | function success (resp) { 1107 | // use global data filter on response text 1108 | var filteredResponse = globalSetupOptions.dataFilter(resp.responseText, type) 1109 | , r = resp.responseText = filteredResponse 1110 | if (r) { 1111 | switch (type) { 1112 | case 'json': 1113 | try { 1114 | resp = win.JSON ? win.JSON.parse(r) : eval('(' + r + ')') 1115 | } catch (err) { 1116 | return error(resp, 'Could not parse JSON in response', err) 1117 | } 1118 | break 1119 | case 'js': 1120 | resp = eval(r) 1121 | break 1122 | case 'html': 1123 | resp = r 1124 | break 1125 | case 'xml': 1126 | resp = resp.responseXML 1127 | && resp.responseXML.parseError // IE trololo 1128 | && resp.responseXML.parseError.errorCode 1129 | && resp.responseXML.parseError.reason 1130 | ? null 1131 | : resp.responseXML 1132 | break 1133 | } 1134 | } 1135 | 1136 | self._responseArgs.resp = resp 1137 | self._fulfilled = true 1138 | fn(resp) 1139 | while (self._fulfillmentHandlers.length > 0) { 1140 | self._fulfillmentHandlers.shift()(resp) 1141 | } 1142 | 1143 | complete(resp) 1144 | } 1145 | 1146 | function error(resp, msg, t) { 1147 | self._responseArgs.resp = resp 1148 | self._responseArgs.msg = msg 1149 | self._responseArgs.t = t 1150 | self._erred = true 1151 | while (self._errorHandlers.length > 0) { 1152 | self._errorHandlers.shift()(resp, msg, t) 1153 | } 1154 | complete(resp) 1155 | } 1156 | 1157 | this.request = getRequest.call(this, success, error) 1158 | } 1159 | 1160 | Reqwest.prototype = { 1161 | abort: function () { 1162 | this._aborted = true 1163 | this.request.abort() 1164 | } 1165 | 1166 | , retry: function () { 1167 | init.call(this, this.o, this.fn) 1168 | } 1169 | 1170 | /** 1171 | * Small deviation from the Promises A CommonJs specification 1172 | * http://wiki.commonjs.org/wiki/Promises/A 1173 | */ 1174 | 1175 | /** 1176 | * `then` will execute upon successful requests 1177 | */ 1178 | , then: function (success, fail) { 1179 | if (this._fulfilled) { 1180 | success(this._responseArgs.resp) 1181 | } else if (this._erred) { 1182 | fail(this._responseArgs.resp, this._responseArgs.msg, this._responseArgs.t) 1183 | } else { 1184 | this._fulfillmentHandlers.push(success) 1185 | this._errorHandlers.push(fail) 1186 | } 1187 | return this 1188 | } 1189 | 1190 | /** 1191 | * `always` will execute whether the request succeeds or fails 1192 | */ 1193 | , always: function (fn) { 1194 | if (this._fulfilled || this._erred) { 1195 | fn(this._responseArgs.resp) 1196 | } else { 1197 | this._completeHandlers.push(fn) 1198 | } 1199 | return this 1200 | } 1201 | 1202 | /** 1203 | * `fail` will execute when the request fails 1204 | */ 1205 | , fail: function (fn) { 1206 | if (this._erred) { 1207 | fn(this._responseArgs.resp, this._responseArgs.msg, this._responseArgs.t) 1208 | } else { 1209 | this._errorHandlers.push(fn) 1210 | } 1211 | return this 1212 | } 1213 | } 1214 | 1215 | function reqwest(o, fn) { 1216 | return new Reqwest(o, fn) 1217 | } 1218 | 1219 | // normalize newline variants according to spec -> CRLF 1220 | function normalize(s) { 1221 | return s ? s.replace(/\r?\n/g, '\r\n') : '' 1222 | } 1223 | 1224 | function serial(el, cb) { 1225 | var n = el.name 1226 | , t = el.tagName.toLowerCase() 1227 | , optCb = function (o) { 1228 | // IE gives value="" even where there is no value attribute 1229 | // 'specified' ref: http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-862529273 1230 | if (o && !o.disabled) 1231 | cb(n, normalize(o.attributes.value && o.attributes.value.specified ? o.value : o.text)) 1232 | } 1233 | , ch, ra, val, i 1234 | 1235 | // don't serialize elements that are disabled or without a name 1236 | if (el.disabled || !n) return 1237 | 1238 | switch (t) { 1239 | case 'input': 1240 | if (!/reset|button|image|file/i.test(el.type)) { 1241 | ch = /checkbox/i.test(el.type) 1242 | ra = /radio/i.test(el.type) 1243 | val = el.value 1244 | // WebKit gives us "" instead of "on" if a checkbox has no value, so correct it here 1245 | ;(!(ch || ra) || el.checked) && cb(n, normalize(ch && val === '' ? 'on' : val)) 1246 | } 1247 | break 1248 | case 'textarea': 1249 | cb(n, normalize(el.value)) 1250 | break 1251 | case 'select': 1252 | if (el.type.toLowerCase() === 'select-one') { 1253 | optCb(el.selectedIndex >= 0 ? el.options[el.selectedIndex] : null) 1254 | } else { 1255 | for (i = 0; el.length && i < el.length; i++) { 1256 | el.options[i].selected && optCb(el.options[i]) 1257 | } 1258 | } 1259 | break 1260 | } 1261 | } 1262 | 1263 | // collect up all form elements found from the passed argument elements all 1264 | // the way down to child elements; pass a '' or form fields. 1265 | // called with 'this'=callback to use for serial() on each element 1266 | function eachFormElement() { 1267 | var cb = this 1268 | , e, i 1269 | , serializeSubtags = function (e, tags) { 1270 | var i, j, fa 1271 | for (i = 0; i < tags.length; i++) { 1272 | fa = e[byTag](tags[i]) 1273 | for (j = 0; j < fa.length; j++) serial(fa[j], cb) 1274 | } 1275 | } 1276 | 1277 | for (i = 0; i < arguments.length; i++) { 1278 | e = arguments[i] 1279 | if (/input|select|textarea/i.test(e.tagName)) serial(e, cb) 1280 | serializeSubtags(e, [ 'input', 'select', 'textarea' ]) 1281 | } 1282 | } 1283 | 1284 | // standard query string style serialization 1285 | function serializeQueryString() { 1286 | return reqwest.toQueryString(reqwest.serializeArray.apply(null, arguments)) 1287 | } 1288 | 1289 | // { 'name': 'value', ... } style serialization 1290 | function serializeHash() { 1291 | var hash = {} 1292 | eachFormElement.apply(function (name, value) { 1293 | if (name in hash) { 1294 | hash[name] && !isArray(hash[name]) && (hash[name] = [hash[name]]) 1295 | hash[name].push(value) 1296 | } else hash[name] = value 1297 | }, arguments) 1298 | return hash 1299 | } 1300 | 1301 | // [ { name: 'name', value: 'value' }, ... ] style serialization 1302 | reqwest.serializeArray = function () { 1303 | var arr = [] 1304 | eachFormElement.apply(function (name, value) { 1305 | arr.push({name: name, value: value}) 1306 | }, arguments) 1307 | return arr 1308 | } 1309 | 1310 | reqwest.serialize = function () { 1311 | if (arguments.length === 0) return '' 1312 | var opt, fn 1313 | , args = Array.prototype.slice.call(arguments, 0) 1314 | 1315 | opt = args.pop() 1316 | opt && opt.nodeType && args.push(opt) && (opt = null) 1317 | opt && (opt = opt.type) 1318 | 1319 | if (opt == 'map') fn = serializeHash 1320 | else if (opt == 'array') fn = reqwest.serializeArray 1321 | else fn = serializeQueryString 1322 | 1323 | return fn.apply(null, args) 1324 | } 1325 | 1326 | reqwest.toQueryString = function (o) { 1327 | var qs = '', i 1328 | , enc = encodeURIComponent 1329 | , push = function (k, v) { 1330 | qs += enc(k) + '=' + enc(v) + '&' 1331 | } 1332 | , k, v 1333 | 1334 | if (isArray(o)) { 1335 | for (i = 0; o && i < o.length; i++) push(o[i].name, o[i].value) 1336 | } else { 1337 | for (k in o) { 1338 | if (!Object.hasOwnProperty.call(o, k)) continue 1339 | v = o[k] 1340 | if (isArray(v)) { 1341 | for (i = 0; i < v.length; i++) push(k, v[i]) 1342 | } else push(k, o[k]) 1343 | } 1344 | } 1345 | 1346 | // spaces should be + according to spec 1347 | return qs.replace(/&$/, '').replace(/%20/g, '+') 1348 | } 1349 | 1350 | reqwest.getcallbackPrefix = function () { 1351 | return callbackPrefix + parseInt(Math.random() * 10000) 1352 | } 1353 | 1354 | // jQuery and Zepto compatibility, differences can be remapped here so you can call 1355 | // .ajax.compat(options, callback) 1356 | reqwest.compat = function (o, fn) { 1357 | if (o) { 1358 | o.type && (o.method = o.type) && delete o.type 1359 | o.dataType && (o.type = o.dataType) 1360 | o.jsonpCallback && (o.jsonpCallbackName = o.jsonpCallback) && delete o.jsonpCallback 1361 | o.jsonp && (o.jsonpCallback = o.jsonp) 1362 | } 1363 | return new Reqwest(o, fn) 1364 | } 1365 | 1366 | reqwest.ajaxSetup = function (options) { 1367 | options = options || {} 1368 | for (var k in options) { 1369 | globalSetupOptions[k] = options[k] 1370 | } 1371 | } 1372 | 1373 | return reqwest 1374 | }); 1375 | jsql.Events = (function() { 1376 | 1377 | // Events 1378 | // ----------------- 1379 | // Thanks to: 1380 | // - https://github.com/documentcloud/backbone/blob/master/backbone.js 1381 | // - https://github.com/joyent/node/blob/master/lib/events.js 1382 | 1383 | 1384 | // Regular expression used to split event strings 1385 | var eventSplitter = /\s+/ 1386 | 1387 | 1388 | // A module that can be mixed in to *any object* in order to provide it 1389 | // with custom events. You may bind with `on` or remove with `off` callback 1390 | // functions to an event; `trigger`-ing an event fires all callbacks in 1391 | // succession. 1392 | // 1393 | // var object = new Events(); 1394 | // object.on('expand', function(){ alert('expanded'); }); 1395 | // object.trigger('expand'); 1396 | // 1397 | function Events() { 1398 | } 1399 | 1400 | 1401 | // Bind one or more space separated events, `events`, to a `callback` 1402 | // function. Passing `"all"` will bind the callback to all events fired. 1403 | Events.prototype.on = function(events, callback, context) { 1404 | var cache, event, list 1405 | if (!callback) return this 1406 | 1407 | cache = this.__events || (this.__events = {}) 1408 | events = events.split(eventSplitter) 1409 | 1410 | while (event = events.shift()) { 1411 | list = cache[event] || (cache[event] = []) 1412 | list.push(callback, context) 1413 | } 1414 | 1415 | return this 1416 | } 1417 | 1418 | 1419 | // Remove one or many callbacks. If `context` is null, removes all callbacks 1420 | // with that function. If `callback` is null, removes all callbacks for the 1421 | // event. If `events` is null, removes all bound callbacks for all events. 1422 | Events.prototype.off = function(events, callback, context) { 1423 | var cache, event, list, i 1424 | 1425 | // No events, or removing *all* events. 1426 | if (!(cache = this.__events)) return this 1427 | if (!(events || callback || context)) { 1428 | delete this.__events 1429 | return this 1430 | } 1431 | 1432 | events = events ? events.split(eventSplitter) : keys(cache) 1433 | 1434 | // Loop through the callback list, splicing where appropriate. 1435 | while (event = events.shift()) { 1436 | list = cache[event] 1437 | if (!list) continue 1438 | 1439 | if (!(callback || context)) { 1440 | delete cache[event] 1441 | continue 1442 | } 1443 | 1444 | for (i = list.length - 2; i >= 0; i -= 2) { 1445 | if (!(callback && list[i] !== callback || 1446 | context && list[i + 1] !== context)) { 1447 | list.splice(i, 2) 1448 | } 1449 | } 1450 | } 1451 | 1452 | return this 1453 | } 1454 | 1455 | 1456 | // Trigger one or many events, firing all bound callbacks. Callbacks are 1457 | // passed the same arguments as `trigger` is, apart from the event name 1458 | // (unless you're listening on `"all"`, which will cause your callback to 1459 | // receive the true name of the event as the first argument). 1460 | Events.prototype.trigger = function(events) { 1461 | var cache, event, all, list, i, len, rest = [], args, returned = {status: true} 1462 | if (!(cache = this.__events)) return this 1463 | 1464 | events = events.split(eventSplitter) 1465 | 1466 | // Fill up `rest` with the callback arguments. Since we're only copying 1467 | // the tail of `arguments`, a loop is much faster than Array#slice. 1468 | for (i = 1, len = arguments.length; i < len; i++) { 1469 | rest[i - 1] = arguments[i] 1470 | } 1471 | 1472 | // For each event, walk through the list of callbacks twice, first to 1473 | // trigger the event, then to trigger any `"all"` callbacks. 1474 | while (event = events.shift()) { 1475 | // Copy callback lists to prevent modification. 1476 | if (all = cache.all) all = all.slice() 1477 | if (list = cache[event]) list = list.slice() 1478 | 1479 | // Execute event callbacks. 1480 | callEach(list, rest, this, returned) 1481 | 1482 | // Execute "all" callbacks. 1483 | callEach(all, [event].concat(rest), this, returned) 1484 | } 1485 | 1486 | return returned.status 1487 | } 1488 | 1489 | 1490 | // Mix `Events` to object instance or Class function. 1491 | Events.mixTo = function(receiver) { 1492 | receiver = receiver.prototype || receiver 1493 | var proto = Events.prototype 1494 | 1495 | for (var p in proto) { 1496 | if (proto.hasOwnProperty(p)) { 1497 | receiver[p] = proto[p] 1498 | } 1499 | } 1500 | } 1501 | 1502 | 1503 | // Helpers 1504 | // ------- 1505 | 1506 | var keys = Object.keys 1507 | 1508 | if (!keys) { 1509 | keys = function(o) { 1510 | var result = [] 1511 | 1512 | for (var name in o) { 1513 | if (o.hasOwnProperty(name)) { 1514 | result.push(name) 1515 | } 1516 | } 1517 | return result 1518 | } 1519 | } 1520 | 1521 | // Execute callbacks 1522 | function callEach(list, args, context, returned) { 1523 | var r 1524 | if (list) { 1525 | for (var i = 0, len = list.length; i < len; i += 2) { 1526 | try { 1527 | r = list[i].apply(list[i + 1] || context, args) 1528 | } catch(e) { 1529 | if (window.console && console.error && 1530 | Object.prototype.toString.call(console.error) === '[object Function]') { 1531 | console.error(e.stack || e) 1532 | } 1533 | // go next with error 1534 | continue 1535 | } 1536 | 1537 | // trigger will return false if one of the callbacks return false 1538 | r === false && returned.status && (returned.status = false) 1539 | } 1540 | } 1541 | } 1542 | 1543 | return Events 1544 | })(); 1545 | /* Build Time: October 11, 2013 03:23:47 */ 1546 | -------------------------------------------------------------------------------- /console/jdump.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | { 4 | '0': { 5 | name: 'John', 6 | age: 15, 7 | website: '' 8 | }, 9 | 10 | '1': { 11 | name: 'Mic Smith', 12 | age: 23, 13 | website: 'benben.cc' 14 | } 15 | } 16 | 17 | --------------------------------------- 18 | | INDEX | NAME | AGE | WEBSITE | 19 | --------------------------------------- 20 | | 0 | John | 15 | | 21 | --------------------------------------- 22 | | 1 | Mic Smith | 23 | benben.cc | 23 | --------------------------------------- 24 | 25 | */ 26 | 27 | (function() { 28 | var jdump, utils; 29 | 30 | jdump = function() { 31 | this.init.apply(this, arguments); 32 | }; 33 | 34 | jdump.prototype = { 35 | version: '0.1.0-dev', 36 | 37 | init: function() { 38 | this.meta = {}; 39 | this.output = ''; 40 | this.utils = utils; 41 | }, 42 | 43 | lineLen: function() { 44 | var len = 0, num = 0; 45 | utils.each(this.meta, function(o, i, r) { 46 | num++; 47 | len += 2 + o; 48 | }); 49 | 50 | len += num + 1; 51 | return len; 52 | }, 53 | 54 | genMeta: function(data) { 55 | var that = this; 56 | utils.each(data, function(o, i, r) { 57 | utils.each(o, function(o, i, r) { 58 | var len = utils.getLength(o); 59 | var titleLen = utils.getLength(i); 60 | len = len > titleLen ? len : titleLen; 61 | if(!that.meta[i] || len > that.meta[i]) { 62 | that.meta[i] = len; 63 | } 64 | }); 65 | }); 66 | }, 67 | 68 | print: function(data) { 69 | var that = this; 70 | var lineLen, lineStr; 71 | var tmpStr; 72 | var titleStr; 73 | 74 | data = utils.listLike(data) ? data : [data]; 75 | that.init(); 76 | that.genMeta(data); 77 | lineLen = that.lineLen(); 78 | lineStr = utils.fill('', lineLen, '-'); 79 | 80 | utils.each(data, function(o, i, r) { 81 | if(!titleStr) { 82 | that.echo(lineStr + '\r\n'); 83 | utils.each(o, function(o, i, r) { 84 | tmpStr = '| ' + utils.fill(i, that.meta[i]) + ' '; 85 | that.echo(tmpStr); 86 | }); 87 | that.echo('|\r\n'); 88 | titleStr = true; 89 | that.echo(lineStr + '\r\n'); 90 | } 91 | utils.each(o, function(o, i, r) { 92 | tmpStr = '| ' + utils.fill(o, that.meta[i]) + ' '; 93 | that.echo(tmpStr); 94 | }); 95 | that.echo('|\r\n'); 96 | }); 97 | that.echo(lineStr); 98 | 99 | return that.output; 100 | }, 101 | 102 | echo: function(str) { 103 | this.output += str; 104 | } 105 | }; 106 | 107 | utils = { 108 | isArray: Array.isArray || function(obj) { 109 | return Object.prototype.toString.call(obj) === '[object Array]'; 110 | }, 111 | 112 | isObject: function(obj) { 113 | return obj === Object(obj); 114 | }, 115 | 116 | isPlainObject: function(obj) { 117 | return this.isObject(obj) && obj.constructor === Object; 118 | }, 119 | 120 | each: function(list, fn) { 121 | if(this.isPlainObject(list)) { 122 | for(var i in list) { 123 | if(list.hasOwnProperty(i)) { 124 | fn(list[i], i, list); 125 | } 126 | } 127 | return; 128 | } 129 | 130 | if(!this.isArray(list)) { 131 | return; 132 | } 133 | 134 | if(Array.prototype.forEach) { 135 | list.forEach(fn); 136 | return; 137 | } 138 | 139 | for(var i = 0; i < list.length; i++) { 140 | fn(list[i], i, list); 141 | } 142 | }, 143 | 144 | getLength: function(str) { 145 | str = String(str); 146 | return str.match(/[^ -~]/g) == null ? str.length : str.length + str.match(/[^ -~]/g).length; 147 | }, 148 | 149 | fill: function(str, len, char) { 150 | var cur_len = this.getLength(str); 151 | var fill_len = len - cur_len; 152 | char = char || ' '; 153 | if(this.getLength(str) < len) { 154 | str += char; 155 | str = arguments.callee.call(this, str, len, char); 156 | } 157 | return str; 158 | }, 159 | 160 | listLike: function(data) { 161 | var that = this; 162 | var flag = true; 163 | 164 | this.each(data, function(o, i, r) { 165 | if(!that.isPlainObject(o)) { 166 | flag = false; 167 | } 168 | }); 169 | 170 | return flag; 171 | } 172 | }; 173 | 174 | jdump = new jdump(); 175 | 176 | typeof(module) !== 'undefined' && module.exports ? module.exports = jdump : this.jdump = jdump; 177 | })(); 178 | -------------------------------------------------------------------------------- /console/jsql-console.css: -------------------------------------------------------------------------------- 1 | .wrapper { 2 | padding: 0 10px; 3 | font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Droid Sans Mono', 'Consolas', monospace; 4 | background-color: #141414; 5 | border: 15px solid #ccc; 6 | -webkit-border-radius: 3px; 7 | -moz-border-radius: 3px; 8 | border-radius: 3px; 9 | } 10 | 11 | #screen { 12 | width: 100%; 13 | height: 550px; 14 | font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Droid Sans Mono', 'Consolas', monospace; 15 | background-color: #141414; 16 | color: #fff; 17 | border: 0; 18 | padding: 10px 5px 10px; 19 | font-size: 13px; 20 | overflow: hidden; 21 | } 22 | 23 | #screen:focus { 24 | outline: 0; 25 | } 26 | 27 | #screen::-webkit-scrollbar { 28 | display: none; 29 | } 30 | 31 | @media screen and (-webkit-min-device-pixel-ratio: 0) { 32 | #screen { 33 | overflow: scroll; 34 | } 35 | } 36 | 37 | #command { 38 | width: 85%; 39 | font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Droid Sans Mono', 'Consolas', monospace; 40 | background-color: #141414; 41 | color: #4de; 42 | border: 0; 43 | } 44 | 45 | #command:focus { 46 | outline: 0; 47 | } 48 | 49 | .shell-title { 50 | font-size: 13px; 51 | color: #4de; 52 | } 53 | 54 | .console { 55 | position: relative; 56 | } 57 | 58 | .shell { 59 | position: relative; 60 | bottom: 5px; 61 | left: 0; 62 | width: 100%; 63 | } 64 | -------------------------------------------------------------------------------- /console/jsql-console.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | Welcome to jSQL. 19 | version 0.4.0-dev. 20 | type `help` for more informations. 21 | 22 | 23 | jsql > 24 | 25 | 26 | 27 | 28 | 29 | 202 | 203 | 204 | 205 | -------------------------------------------------------------------------------- /console/jsql-sample-data.js: -------------------------------------------------------------------------------- 1 | var data = { 2 | 3 | // a list of user profiles 4 | users:[ 5 | { first: "John", last: "Smith", age: 45, admin: false, locationId:2, gender: "m", lastLogin: new Date("1/3/2007 1:05 PM"), permissions: ["read"] }, 6 | { first: "Margret", last: "Lynn", age: 32, admin: true, locationId:1, gender: "f", lastLogin: new Date("12/11/2006 3:25 PM"), permissions: ["read", "write"] }, 7 | { first: "Ryan", last: "Aston", age: 22, admin: false, locationId:3, gender: "M", lastLogin: new Date("3/7/2008 1:31 AM"), permissions: ["read", "write"] }, 8 | { first: "Zoe", last: "Brown", age: 21, admin: true, locationId:2, gender: "f", lastLogin: new Date("6/5/2008 2:02 PM"), permissions: ["read"] }, 9 | { first: "Haley", last: "Baker", age: 14, admin: true, locationId:3, gender: "f", lastLogin: new Date("9/28/2007 1:01 AM"), permissions: ["read", "write", "delete"] }, 10 | { first: "Chase", last: "Herbert", age: 17, admin: false, locationId:7, gender: "m", lastLogin: new Date("6/22/2008 3:12 PM"), permissions: ["read"] }, 11 | { first: "Mary", last: "Godard", age: 65, admin: true, locationId:2, gender: "f", lastLogin: new Date("7/9/2008 1:52 AM"), permissions: ["read"] }, 12 | { first: "Oliva", last: "Sellick", age: 44, admin: true, locationId:6, gender: "f", lastLogin: new Date("3/5/2006 1:55 PM"), permissions: ["read"] }, 13 | { first: "Justin", last: "Stanwood", age: 21, admin: true, locationId:5, gender: "M", lastLogin: new Date("12/18/2008 7:31 AM"), permissions: ["READ"] }, 14 | { first: "Adam", last: "Smith", age: 45, admin: false, locationId:5, gender: "m", lastLogin: new Date("5/14/2008 8:28 AM"), permissions: ["read"] }, 15 | { first: "Audrey", last: "Worth", age: 32, admin: false, locationId:2, gender: "f", lastLogin: new Date("2/23/2008 1:24 PM"), permissions: ["read"] }, 16 | { first: "Owen", last: "Walter", age: 61, admin: false, locationId:1, gender: "m", lastLogin: new Date("3/14/2007 9:23 AM"), permissions: ["read"] }, 17 | { first: "Seth", last: "Morgan", age: 45, admin: false, locationId:2, gender: "m", lastLogin: new Date("7/2/2008 11:52 PM"), permissions: ["read", "write"] }, 18 | { first: "Carter", last: "Fry", age: 38, admin: false, locationId:3, gender: "m", lastLogin: new Date("2/7/2008 1:34 AM"), permissions: ["read", "write"] }, 19 | { first: "Justin", last: "Cromwell", age: 25, admin: true, locationId:4, gender: "M", lastLogin: new Date("1/17/2006 1:33 PM"), permissions: ["read"] }, 20 | { first: "Brian", last: "Martin", age: 32, admin: false, locationId:7, gender: "m", lastLogin: new Date("7/11/2005 1:32 AM"), permissions: ["read"] }, 21 | { first: "Ava", last: "Barton", age: 14, admin: false, locationId:7, gender: "f", lastLogin: new Date("9/6/2008 12:41 PM"), permissions: ["READ", "WRITE", "delete"] }, 22 | { first: "David", last: "Baum", age: 18, admin: false, locationId:2, gender: "m", lastLogin: new Date("11/3/2008 3:54 AM"), permissions: ["read"] }, 23 | { first: "Daniel", last: "Ashford", age: 56, admin: false, locationId:2, gender: "m", lastLogin: new Date("10/12/2008 2:23 AM"), permissions: ["read", "write", "delete"] }, 24 | { first: "Lilly", last: "Edwin", age: 73, admin: true, locationId:3, gender: "f", lastLogin: new Date("4/22/2007 1:41 AM"), permissions: ["read", "write"] }, 25 | { first: "Logan", last: "Lee", age: 23, admin: false, locationId:4, gender: "m", lastLogin: new Date("7/25/2006 7:43 PM"), permissions: ["read", "write"] }, 26 | { first: "James", last: "Haff", age: 24, admin: false, locationId:4, gender: "m", lastLogin: new Date("8/21/2008 8:21 AM"), permissions: ["read"] }, 27 | { first: "Zach", last: "Lane", age: 67, admin: false, locationId:1, gender: "m", lastLogin: new Date("3/11/2007 1:45 PM"), permissions: ["read"] }, 28 | { first: "Abby", last: "Bartin", age: 73, admin: false, locationId:7, gender: "F", lastLogin: new Date("5/12/2008 6:45 AM"), permissions: ["read"] }, 29 | { first: "Paige", last: "Darrow", age: 38, admin: true, locationId:6, gender: "f", lastLogin: new Date("9/17/2006 11:21 PM"), permissions: ["read"] }, 30 | { first: "Matt", last: "Raymond", age: 12, admin: false, locationId:7, gender: "m", lastLogin: new Date("11/14/2008 4:33 AM"), permissions: ["read", "write"] }, 31 | { first: "Daniel", last: "Oswald", age: 29, admin: true, locationId:4, gender: "m", lastLogin: new Date("4/4/2007 8:55 PM"), permissions: ["READ", "WRITE"] }, 32 | { first: "Abigail", last: "Weller", age: 40, admin: false, locationId:5, gender: "f", lastLogin: new Date("10/12/2008 2:12 AM"), permissions: ["READ", "WRITE", "DELETE"] }, 33 | { first: "Madison", last: "Smith", age: 21, admin: false, locationId:3, gender: "F", lastLogin: new Date("1/22/2005 9:34 AM"), permissions: ["read"] }, 34 | { first: "Emma", last: "Yager", age: 20, admin: true, locationId:1, gender: "f", lastLogin: new Date("10/4/2008 1:21 PM"), permissions: ["read", "write"] }, 35 | { first: "Luke", last: "Heaton", age: 16, admin: false, locationId:2, gender: "m", lastLogin: new Date("3/2/2008 10:56 PM"), permissions: ["read"] }, 36 | { first: "Chris", last: "Everard", age: 34, admin: false, locationId:4, gender: "m", lastLogin: new Date("6/23/2006 12:32 PM"), permissions: ["read"] } 37 | ] 38 | 39 | }; 40 | -------------------------------------------------------------------------------- /console/print_r.js: -------------------------------------------------------------------------------- 1 | /** 2 | * print_r() 3 | * string print_r (mixed object [, boolean view = false]) 4 | * Prints human-readable information about the object 5 | * @author: Alexander Guinness 6 | * @version: 1.1 7 | * @params: {mixed} data - the Object to be printed 8 | * @params: {boolean} view - optional boolean parameter to set an alternative view 9 | * @return String 10 | * @license: MIT 11 | * @date: 2/27/12 9:28 PM 12 | **/ 13 | 14 | var print_r = function(data, view) { 15 | 'use strict' 16 | 17 | /* 18 | - string build (mixed data [, string indent = '']) 19 | */ 20 | return function build(data, indent) { 21 | return { 22 | init: function() { 23 | if (this.type(data) === 'object') 24 | this.depth(0); 25 | 26 | else if (this.type(data) === 'array') 27 | this.depth(1); 28 | 29 | else 30 | this.output.push({ 31 | 'string': '"' + data + '"', 32 | 'function': data && data.toString().replace(/\b.*\n|\}$/g, '\t$&').replace(/^\t/, ' ') 33 | }[this.type(data)] || data); 34 | 35 | return this.output.join(''); 36 | }, 37 | 38 | output : [], 39 | 40 | /* 41 | - string type (object type) 42 | */ 43 | type: function(type) { 44 | if(typeof(type) === 'undefined') return 'string'; 45 | return Object.prototype.toString.call(type).replace(/object|[\[\]\s]/g, '').toLowerCase(); 46 | }, 47 | 48 | /* 49 | - string type (mixed key, boolean array) 50 | */ 51 | get_view: function(key, array) { 52 | return (view || array ? ['\t[', key, '] => '] : ['\t', key, ': ']).join(''); 53 | }, 54 | 55 | /* 56 | - void depth (number array) 57 | */ 58 | depth: function(array) { 59 | indent = indent || ''; 60 | 61 | var brace = [['{', '}'], ['[', ']']][array], 62 | block = [brace[0], '\n']; 63 | 64 | for (var i in data) { 65 | if (data.hasOwnProperty(i)) 66 | block.push(indent, this.get_view(i, array), build(data[i], indent + '\t'), ',', '\n'); 67 | } 68 | block.splice(-2, 1); 69 | this.output.push(block.join(''), indent, brace[1]); 70 | } 71 | }.init(); 72 | }(data); 73 | }; 74 | 75 | //exports.print_r = print_r; 76 | -------------------------------------------------------------------------------- /docs/doc.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 147 | 148 | 193 | 194 | jSQL `数据的异想世界` 195 | 196 | 197 | 198 | jSQL 数据的异想世界 199 | 200 | 201 | 202 | Why Should I Use jSQL? 203 | 204 | 使用jSQL可以让你更加专注于业务需求和逻辑,通过jSQL可以将排序、筛选等数据查询的底层操作同业务逻辑完美分离。从而提高代码的可维护性和开发效率。jSQL不仅可以用于PC端,也可以在移动手持设备的应用中使用。除了JavaScript版本的jSQL,如果你喜欢jSQL的使用方式和API,同时你又是一个Python后端开发人员且不喜欢庞大的ORM,同样有短小精干的Python-jSQL可以让你在Python框架中方便的对MySQL进行操作。jSQL不是一个库,它是一个使得数据的查询操作变得更方便快捷高效的解决思路和思想、方法的抽象。jSQL内置IO和事件中心,天然的支持远程数据源,并且所有数据的操作行为最终都会通过事件中心广播出去,从而使得数据操作和业务逻辑解耦分离。 205 | 206 | 207 | 208 | jsql.js v0.7.1 (for development) 209 | 210 | 211 | jsql-min.js v0.7.1 (for production) 212 | 213 | 214 | 215 | API Document 216 | 217 | jSQL是一个用SQL的思想和API实现JavaScript数据操作(查询,过滤)的方法库(同时有基于Tornado DB封装的类似API的后端SQL查询封装),它通过链式调用的方式简化数据查询和操作,同时支持多种形式的数据源。 218 | 219 | 220 | Object 221 | Array 222 | URI 223 | 224 | 225 | 226 | Object类型: 227 | 228 | { 229 | key: {column: value}, 230 | key: {column: value} 231 | } 232 | 233 | 234 | Object的key对应数据表的唯一索引,cloumn映射为数据表的字段,value对应数据表相应字段的值。 235 | 236 | Array类型: 237 | 238 | [ 239 | {column: value}, 240 | {column: value} 241 | ] 242 | 243 | 244 | Array的每个item对应数据表的一个记录,cloumn对应字段名,value对应字段值,同Object类型不同的是,当前类型的索引key是根据指定的条件或者自动生成的。 245 | 246 | create(dbname, db /*, indexList */) 247 | 248 | create(dbname, db, /*, scope|function */) 249 | 250 | 在内存中创建一个数据库(指定indexList则根据指定字段生成唯一键) 251 | 252 | 用法示例1: 253 | 254 | jsql.create('local', {}) 255 | 256 | 用法示例2: 257 | 258 | jsql.create('local', 'http://uri?callback=?', 'data.result') 259 | 260 | 用法示例3: 261 | 262 | jsql.create('local', 'http://uri', function(resp) { 263 | return resp.data.result; 264 | }) 265 | 266 | 267 | use(dbname) 268 | 269 | 使用指定数据库作为当前操作的数据库,并重置缓冲区(buffer) 270 | 271 | local = jsql.use('local').listAll(); 272 | remote = jsql.use('remote').listAll(); 273 | 274 | 275 | drop(dbname) 276 | 277 | 丢弃指定数据库,同时从内存中抹除 278 | 279 | jsql.drop('local'); 280 | 281 | 282 | dbs() 283 | 284 | 获取当前内存中存在的所有数据库名称 285 | 286 | dbs = jsql.dbs(); // ['local', 'remote', 'test'] 287 | 288 | 289 | db() 290 | 291 | 获取当前操作的数据库名称 292 | 293 | cur = jsql.db(); // local 294 | 295 | 296 | select(key) 297 | 298 | 指定获取的数据字段名称 299 | 300 | local = jsql.use('local').select('*').listAll(); 301 | remote = jsql.use('remote').select('name, age').listAll(); 302 | 303 | 304 | count() 305 | 306 | 获取当前操作缓冲区的数据条数 307 | 308 | count = jsql.use('local').count(); 309 | 310 | 311 | total(scope|function) 312 | 313 | 返回当前操作数据库中含有某字段(或者符合传入条件)的数据总条数。 314 | 315 | total = db.total('data.name'); 316 | total = db.total(function(item) { 317 | return item.data.age === 24; 318 | }); 319 | 320 | 321 | orderby(field, /* callback, */ order) 322 | 323 | 根据指定字段的值(或者处理后的值)和排序规则对当前缓冲区的数据进行排序。 324 | 325 | db = jsql.use('local'); 326 | db.select('*').orderby('data.time', buildDate, 'asc'); 327 | result = db.listAll(); 328 | 329 | 330 | where(fn|[fn, …]) 331 | 332 | 返回当前缓冲区中符合指定条件的数据集合(如果传入为一组fn,则为并集)。 333 | 334 | db = jsql.use('local'); 335 | db.where([function(item) { 336 | return item.data.age < 24; 337 | }, function(item) { 338 | return item.data.sex === 'male'; 339 | }]); 340 | result = db.listAll(); 341 | 342 | 343 | iterate(fn) 344 | 345 | 根据指定的方法对当前缓冲区中的数据进行遍历。 346 | 347 | db = jsql.use('local'); 348 | db.where(function(item) { 349 | return item.data.age < 24; 350 | }).iterate(function(item) { 351 | item.data.checked = true; 352 | }); 353 | result = db.listAll(); 354 | 355 | 356 | findAll(fn) 357 | 358 | 以Object类型返回当前缓冲区中的数据集合 359 | 360 | find(key) 361 | 362 | 获取缓冲区中指定key的数据。 363 | 364 | listAll() 365 | 366 | 以Array类型返回当前缓冲区中的数据集合。 367 | 368 | update(key, data) 369 | 370 | 根据指定数据对指定key的数据进行更新操作。 371 | 372 | insert(item, key /*, from index */) 373 | 374 | (在指定位置)插入一条数据。 375 | 376 | append(/* database, */ data) 377 | 378 | 向当前操作的数据库中追加数据。 379 | 380 | jsql.append('local', {}); 381 | 382 | 383 | remove() 384 | 385 | 从当前操作的数据库中删除缓冲区中的数据。 386 | 387 | db = jsql.use('local'); 388 | db.where(function(item) { 389 | return item.data.age < 24; 390 | }).remove(); 391 | 392 | 393 | limit(start, end) 394 | 395 | 返回当前缓冲区指定区域的数据(也可以使用start:end)。 396 | 397 | db = jsql.use('local'); 398 | db.limit('1:10'); 399 | result = db.listAll(); 400 | 401 | 402 | keys() 403 | 404 | 返回当前缓冲区中所有数据的键名集合。 405 | 406 | first(fn) 407 | 408 | 返回满足条件的第一条数据。 409 | 410 | last(fn) 411 | 412 | 返回满足条件的最后一条数据。 413 | 414 | db = jsql.use('local'); 415 | db.last(function(item) { 416 | return item.data.age < 24; 417 | }); 418 | result = db.find(); 419 | 420 | 421 | distinct() 422 | 423 | 以Array类型返回当前缓冲区中去重后的数组集合。 424 | 425 | db = jsql.use('local'); 426 | result = db.select('airLineCode').distinct(); // ['CA', 'MU', 'CZ'] 427 | 428 | 429 | rebase() 430 | 431 | 重置缓冲区数据。 432 | 433 | noConflict() 434 | 435 | 避免命名空间冲突。 436 | 437 | _jSQL = jsql.noConflict(); 438 | 439 | 440 | IO 441 | 442 | io 443 | 444 | 数据接口异步请求。 445 | 446 | 447 | io.ajax(uri, data, success, error) 448 | io.jsonp(uri, data, success, error) 449 | 450 | 451 | uri: 请求接口的地址 452 | data: 请求接口的附加数据 453 | success: 调用成功的回调函数 454 | error: 调用出错的回调函数 455 | 456 | 457 | 458 | 459 | 460 | Event 461 | 462 | jSQL内置的事件中心不仅用于广播/订阅数据库自身的操作,同时它是一个完全独立的事件中心,跟数据无关的操作也可以完全通过它进行广播/订阅,且天然支持广播域,多个域各自拥有不同的事件池,互不干扰。 463 | 464 | on(database, event, callback) 465 | 466 | 监听数据库事件。 467 | 468 | 469 | database: 被监听的数据库名,取global则为全局事件,也可自定义自己的广播域 470 | event: 被监听的事件名 471 | 472 | 473 | create 474 | update 475 | drop 476 | io:start 477 | io:complete 478 | io:success 479 | io:error 480 | 481 | 482 | callback: 事件发生后触发的回调函数 483 | 484 | 485 | 486 | off(database[, event]) 487 | 488 | 取消对数据库事件的监听。 489 | 490 | trigger(database, event) 491 | 492 | 手动触发指定数据库的指定事件。 493 | 494 | 举例,两个不同模块之间的广播监听示例: 495 | 496 | Module A: 497 | 498 | jsql.on('global', 'moduleBChange', function(arg1, arg2) { 499 | // do something 500 | }); 501 | 502 | Module B: 503 | 504 | jsql.trigger('global', 'moduleBChange', arg1, arg2); 505 | 506 | 507 | 举例,监听某个数据库的创建: 508 | 509 | jsql.on('local', 'create', function() { 510 | // do something 511 | }); 512 | 513 | jsql.create('local', {}); 514 | 515 | 516 | 闭合操作 517 | 518 | 所谓闭合操作即执行到此将重置缓冲区,是链式查询的最终状态。 519 | 520 | 521 | count() 522 | find() 523 | findAll() 524 | listAll() 525 | insert() 526 | save() 527 | saveAll() 528 | update() 529 | 530 | 531 | 532 | 除此之外,use()也会触发缓冲区重置的操作。 533 | 534 | 535 | -------------------------------------------------------------------------------- /jsql.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 14 | 20 | 21 | 22 | 241 | 242 | 243 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name":"jsql", 3 | "version":"0.3.0-dev", 4 | "description":"a SQL like database using javascript", 5 | "homepage":"http://jsql.us", 6 | "author":"jSQL.js authors {badkaikai@gmail.com | http://benben.cc}", 7 | "keywords":["jSQL","SQL Like","db","database","jsdb","javascript"], 8 | "repository":{ 9 | "type":"git", 10 | "url":"git@github.com:PaulGuo/jSQL.git" 11 | }, 12 | "main":"./jsql" 13 | } 14 | -------------------------------------------------------------------------------- /src/events.js: -------------------------------------------------------------------------------- 1 | jsql.Events = (function() { 2 | 3 | // Events 4 | // ----------------- 5 | // Thanks to: 6 | // - https://github.com/documentcloud/backbone/blob/master/backbone.js 7 | // - https://github.com/joyent/node/blob/master/lib/events.js 8 | 9 | 10 | // Regular expression used to split event strings 11 | var eventSplitter = /\s+/ 12 | 13 | 14 | // A module that can be mixed in to *any object* in order to provide it 15 | // with custom events. You may bind with `on` or remove with `off` callback 16 | // functions to an event; `trigger`-ing an event fires all callbacks in 17 | // succession. 18 | // 19 | // var object = new Events(); 20 | // object.on('expand', function(){ alert('expanded'); }); 21 | // object.trigger('expand'); 22 | // 23 | function Events() { 24 | } 25 | 26 | 27 | // Bind one or more space separated events, `events`, to a `callback` 28 | // function. Passing `"all"` will bind the callback to all events fired. 29 | Events.prototype.on = function(events, callback, context) { 30 | var cache, event, list 31 | if (!callback) return this 32 | 33 | cache = this.__events || (this.__events = {}) 34 | events = events.split(eventSplitter) 35 | 36 | while (event = events.shift()) { 37 | list = cache[event] || (cache[event] = []) 38 | list.push(callback, context) 39 | } 40 | 41 | return this 42 | } 43 | 44 | 45 | // Remove one or many callbacks. If `context` is null, removes all callbacks 46 | // with that function. If `callback` is null, removes all callbacks for the 47 | // event. If `events` is null, removes all bound callbacks for all events. 48 | Events.prototype.off = function(events, callback, context) { 49 | var cache, event, list, i 50 | 51 | // No events, or removing *all* events. 52 | if (!(cache = this.__events)) return this 53 | if (!(events || callback || context)) { 54 | delete this.__events 55 | return this 56 | } 57 | 58 | events = events ? events.split(eventSplitter) : keys(cache) 59 | 60 | // Loop through the callback list, splicing where appropriate. 61 | while (event = events.shift()) { 62 | list = cache[event] 63 | if (!list) continue 64 | 65 | if (!(callback || context)) { 66 | delete cache[event] 67 | continue 68 | } 69 | 70 | for (i = list.length - 2; i >= 0; i -= 2) { 71 | if (!(callback && list[i] !== callback || 72 | context && list[i + 1] !== context)) { 73 | list.splice(i, 2) 74 | } 75 | } 76 | } 77 | 78 | return this 79 | } 80 | 81 | 82 | // Trigger one or many events, firing all bound callbacks. Callbacks are 83 | // passed the same arguments as `trigger` is, apart from the event name 84 | // (unless you're listening on `"all"`, which will cause your callback to 85 | // receive the true name of the event as the first argument). 86 | Events.prototype.trigger = function(events) { 87 | var cache, event, all, list, i, len, rest = [], args, returned = {status: true} 88 | if (!(cache = this.__events)) return this 89 | 90 | events = events.split(eventSplitter) 91 | 92 | // Fill up `rest` with the callback arguments. Since we're only copying 93 | // the tail of `arguments`, a loop is much faster than Array#slice. 94 | for (i = 1, len = arguments.length; i < len; i++) { 95 | rest[i - 1] = arguments[i] 96 | } 97 | 98 | // For each event, walk through the list of callbacks twice, first to 99 | // trigger the event, then to trigger any `"all"` callbacks. 100 | while (event = events.shift()) { 101 | // Copy callback lists to prevent modification. 102 | if (all = cache.all) all = all.slice() 103 | if (list = cache[event]) list = list.slice() 104 | 105 | // Execute event callbacks. 106 | callEach(list, rest, this, returned) 107 | 108 | // Execute "all" callbacks. 109 | callEach(all, [event].concat(rest), this, returned) 110 | } 111 | 112 | return returned.status 113 | } 114 | 115 | 116 | // Mix `Events` to object instance or Class function. 117 | Events.mixTo = function(receiver) { 118 | receiver = receiver.prototype || receiver 119 | var proto = Events.prototype 120 | 121 | for (var p in proto) { 122 | if (proto.hasOwnProperty(p)) { 123 | receiver[p] = proto[p] 124 | } 125 | } 126 | } 127 | 128 | 129 | // Helpers 130 | // ------- 131 | 132 | var keys = Object.keys 133 | 134 | if (!keys) { 135 | keys = function(o) { 136 | var result = [] 137 | 138 | for (var name in o) { 139 | if (o.hasOwnProperty(name)) { 140 | result.push(name) 141 | } 142 | } 143 | return result 144 | } 145 | } 146 | 147 | // Execute callbacks 148 | function callEach(list, args, context, returned) { 149 | var r 150 | if (list) { 151 | for (var i = 0, len = list.length; i < len; i += 2) { 152 | try { 153 | r = list[i].apply(list[i + 1] || context, args) 154 | } catch(e) { 155 | if (window.console && console.error && 156 | Object.prototype.toString.call(console.error) === '[object Function]') { 157 | console.error(e.stack || e) 158 | } 159 | // go next with error 160 | continue 161 | } 162 | 163 | // trigger will return false if one of the callbacks return false 164 | r === false && returned.status && (returned.status = false) 165 | } 166 | } 167 | } 168 | 169 | return Events 170 | })(); 171 | -------------------------------------------------------------------------------- /src/jsql.js: -------------------------------------------------------------------------------- 1 | /* 2 | --------------- jSQL --------------- 3 | a SQL like database using javascript 4 | website: http://jsql.us 5 | licence: MIT Licence 6 | version: @VERSION@-dev 7 | 8 | description: using jSQL to process the data easily. 9 | */ 10 | 11 | (function() { 12 | var slice = Array.prototype.slice, 13 | toString = Object.prototype.toString, 14 | hasOwnProperty = Object.prototype.hasOwnProperty; 15 | 16 | var nativeForEach = Array.prototype.forEach, 17 | nativeIsArray = Array.isArray, 18 | nativeKeys = Object.keys, 19 | nativeIndexOf = Array.prototype.indexOf, 20 | nativeLastIndexOf = Array.prototype.lastIndexOf; 21 | 22 | var utils = {}; 23 | var jSQL_KEY_NAME = 'jSQL_Key'; 24 | var jSQL, _jSQL, _jsql, 25 | _DB = {}, 26 | _DBIndexMap = {}, 27 | _protected = {}, 28 | _events = {}; 29 | 30 | var interpolation = function(str) { 31 | var args = [].slice.call(arguments, 1); 32 | 33 | return str.replace(/%s/igm, function() { 34 | return args.shift() || ''; 35 | }); 36 | }; 37 | 38 | var logcat = { 39 | error: function(error) { 40 | error = interpolation.apply(this, arguments); 41 | 42 | if(typeof(console) !== 'undefined') { 43 | if(console.warn) { 44 | console.warn(error); 45 | return; 46 | } 47 | 48 | if(console.log) { 49 | console.log(error); 50 | return; 51 | } 52 | } 53 | 54 | throw(error); 55 | }, 56 | 57 | info: function(info) { 58 | info = interpolation.apply(this, arguments); 59 | 60 | if(typeof(console) !== 'undefined') { 61 | if(console.info) { 62 | console.info(info); 63 | return; 64 | } 65 | 66 | if(console.log) { 67 | console.log(info); 68 | return; 69 | } 70 | } 71 | } 72 | }; 73 | 74 | if(typeof(this.jSQL) !== 'undefined') { 75 | _jSQL = this.jSQL; 76 | } 77 | 78 | if(typeof(this.jsql) !== 'undefined') { 79 | _jsql = this.jsql; 80 | } 81 | 82 | jSQL = function() { 83 | this.init.apply(this, arguments); 84 | }; 85 | 86 | jSQL.prototype = { 87 | version: '@VERSION@-dev', 88 | 89 | init: function() { 90 | this._jSQL = _jSQL; 91 | this._jsql = _jsql; 92 | this._DB = _DB; 93 | this._currentDB = null; 94 | this._buffer = null; 95 | this._currentDBName = null; 96 | this._DBIndexMap = _DBIndexMap; 97 | this._protected = _protected; 98 | this._indexList = null; 99 | this._events = _events; 100 | this.utils = utils; 101 | }, 102 | 103 | create: function(dbname, db /*, scope */) { 104 | var indexList; 105 | var that = this; 106 | 107 | if(this._DB.hasOwnProperty(dbname)) { 108 | logcat.error('DB Already Exist.'); 109 | } 110 | 111 | if(utils.isArray(db)) { 112 | indexList = utils.listSlice(arguments, '2:'); 113 | utils.appendKey(db, indexList); 114 | _DBIndexMap[dbname] = utils.arrayToObject(db); 115 | this._indexList = indexList || null; //remember indexList for insert/save 116 | } 117 | 118 | if(utils.isPlainObject(db)) { 119 | _DBIndexMap[dbname] = utils.clone(db); 120 | db = utils.objectToArray(db); 121 | } 122 | 123 | if(typeof(db) === 'string' && db.match(/^http(s)?:\/\//igm)) { 124 | var scope = arguments[2] || '*'; 125 | var proxyCallback = function(data) { 126 | db = typeof(scope) === 'function' ? 127 | scope.call(this, data) : 128 | scope === '*' ? 129 | data : utils.deep(data, scope); 130 | 131 | that._DB[dbname] = utils.clone(db); 132 | that._events[dbname] = that._events[dbname] || new that.Events(); 133 | that.trigger(dbname, 'create'); 134 | that.trigger(dbname, 'io:success'); 135 | that.trigger(dbname, 'io:complete'); 136 | }; 137 | 138 | var proxyFallback = function() { 139 | that._events[dbname] = that._events[dbname] || new that.Events(); 140 | that.trigger(dbname, 'io:error'); 141 | }; 142 | 143 | if(db.match('callback=')) { 144 | this.io.jsonp(db, {}, proxyCallback, proxyFallback); 145 | } else { 146 | this.io.ajax(db, {}, proxyCallback, proxyFallback); 147 | } 148 | 149 | this._events[dbname] = this._events[dbname] || new this.Events(); 150 | this.trigger(dbname, 'io:start'); 151 | 152 | return this; 153 | } 154 | 155 | this._DB[dbname] = utils.clone(db); 156 | this._events[dbname] = this._events[dbname] || new this.Events(); 157 | this.trigger(dbname, 'create'); 158 | return this; 159 | }, 160 | 161 | use: function(dbname) { 162 | if(!this._DB.hasOwnProperty(dbname)) { 163 | throw('Database Not Exist.'); 164 | } 165 | 166 | this._currentDB = this._DB[dbname]; 167 | this._currentDBName = dbname; 168 | this.rebase(); 169 | return this; 170 | }, 171 | 172 | drop: function(dbname) { 173 | if(this._DB.hasOwnProperty(dbname)) { 174 | delete this._DB[dbname]; 175 | this.trigger(dbname, 'drop'); 176 | } 177 | 178 | return this; 179 | }, 180 | 181 | dbs: function() { 182 | return utils.keys(this._DB); 183 | }, 184 | 185 | db: function() { 186 | return this._currentDBName; 187 | }, 188 | 189 | select: function(key) { 190 | if(!this._currentDB) { 191 | throw('Please Select Database First.'); 192 | } 193 | 194 | this._protected['field'] = [].slice.call(utils.isArray(key) ? key : arguments); 195 | 196 | if(key === '*') { 197 | return this; 198 | } 199 | 200 | return this; 201 | }, 202 | 203 | count: function() { 204 | var result; 205 | result = this._buffer.length; 206 | return result; 207 | }, 208 | 209 | total: function(scope) { 210 | var rs = 0, _tmp; 211 | 212 | for(var _key in this._currentDB) { 213 | if(this._currentDB.hasOwnProperty(_key)) { 214 | _tmp = scope === '*' ? 215 | this._currentDB[_key] : 216 | typeof(scope) === 'function' ? 217 | scope.call(this, this._currentDB[_key], _key) === true ? true : undefined : 218 | utils.deep(this._currentDB[_key], scope); 219 | 220 | if(typeof(_tmp) !== 'undefined') { 221 | rs++; 222 | } 223 | } 224 | } 225 | 226 | return rs; 227 | }, 228 | 229 | orderby: function(field, callback, order) { 230 | var _array = this._buffer; 231 | var _this = this; 232 | 233 | if(typeof(callback) !== 'function') { 234 | callback = [order, order = callback][0]; 235 | } 236 | 237 | _array.sort(function(a, b) { 238 | a = utils.deep(a, field); 239 | b = utils.deep(b, field); 240 | if(callback) { 241 | a = callback(a); 242 | b = callback(b); 243 | } 244 | return order && order.toLowerCase() === 'asc' ? a - b : b - a; 245 | }); 246 | 247 | this._buffer = _array; 248 | this._protected['sort'] = true; 249 | return this; 250 | }, 251 | 252 | where: function(fn) { 253 | var _tmp = [], _swap; 254 | this._buffer = this._buffer || this._currentDB; 255 | fn = utils.parseFn(fn); 256 | 257 | for(var i in this._buffer) { 258 | if(this._buffer.hasOwnProperty(i)) { 259 | if(typeof(fn) === 'function') { 260 | _swap = fn.call(this, utils.clone(this._buffer[i]), i); 261 | } 262 | 263 | if(utils.isArray(fn)) { 264 | _swap = false; 265 | 266 | for(var f in fn) { 267 | if(fn.hasOwnProperty(f)) { 268 | if(fn[f].call(this, utils.clone(this._buffer[i]), i)) { 269 | _swap = true; 270 | } 271 | } 272 | } 273 | } 274 | 275 | if(_swap) { 276 | _tmp.push(this._buffer[i]); 277 | } 278 | } 279 | } 280 | this._buffer = _tmp; 281 | return this; 282 | }, 283 | 284 | iterate: function(fn) { 285 | this._buffer = this._buffer || this._currentDB; 286 | 287 | for(var i in this._buffer) { 288 | if(this._buffer.hasOwnProperty(i)) { 289 | fn.call(this, this._buffer[i]); 290 | } 291 | } 292 | return this; 293 | }, 294 | 295 | findAll: function() { 296 | var result; 297 | result = utils.clone(utils.arrayToObject(this._select())); 298 | return result; 299 | }, 300 | 301 | find: function(key) { 302 | var result; 303 | var _tmp = this._DBIndexMap[this._currentDBName]; 304 | 305 | if(!key) { 306 | for(var i in _tmp) { 307 | if(_tmp.hasOwnProperty(i)) { 308 | if(key) { 309 | break; 310 | } 311 | 312 | if(this._buffer.hasOwnProperty(i)) { 313 | key = i; 314 | } 315 | } 316 | } 317 | } 318 | 319 | result = utils.clone(_tmp[key]); 320 | return result; 321 | }, 322 | 323 | listAll: function() { 324 | var result; 325 | result = utils.clone(this._select()); 326 | return result; 327 | }, 328 | 329 | update: function(fn) { 330 | var _swap = this.utils.arrayToObject(this._currentDB); 331 | var _tmp; 332 | this._buffer = this._buffer || this._currentDB; 333 | 334 | if(!this._currentDB) { 335 | throw('Please Select Database First.'); 336 | } 337 | 338 | for(var i in this._buffer) { 339 | if(this._buffer.hasOwnProperty(i)) { 340 | _tmp = fn.call(this, utils.clone(this._buffer[i])); 341 | 342 | if(_tmp) { 343 | _swap[this._buffer[i][jSQL_KEY_NAME]] = _tmp; 344 | } 345 | } 346 | } 347 | 348 | this._currentDB = this.utils.objectToArray(_swap); 349 | this._DB[this._currentDBName] = this.utils.objectToArray(_swap); 350 | this.trigger(this._currentDBName, 'update'); 351 | return this; 352 | }, 353 | 354 | insert: function(item, key /*, fromIndex */) { 355 | var item = utils.clone(item); 356 | var fromIndex = arguments[2]; 357 | 358 | item[jSQL_KEY_NAME] = item.key || key; 359 | fromIndex ? 360 | this._currentDB.splice(fromIndex, 0, item) : 361 | this._currentDB.push(item); 362 | this.trigger(this._currentDBName, 'update'); 363 | return this; 364 | }, 365 | 366 | append: function(database, data) { 367 | if(arguments.length > 1) { 368 | this.use(database); 369 | } else { 370 | data = arguments[0]; 371 | } 372 | 373 | data = utils.clone(data); 374 | 375 | if(utils.isArray(data)) { 376 | utils.appendKey(data, this._indexList); 377 | this._currentDB = this._currentDB.concat(data); 378 | } 379 | 380 | if(utils.isPlainObject(data)) { 381 | this._currentDB = this._currentDB.concat(utils.objectToArray(data)); 382 | } 383 | 384 | this._DB[this._currentDBName] = this.utils.objectToArray(this._currentDB); 385 | this.trigger(this._currentDBName, 'update'); 386 | return this; 387 | }, 388 | 389 | remove: function() { 390 | var that = this; 391 | var _swap = this.utils.arrayToObject(this._currentDB); 392 | this._buffer = this._buffer || this._currentDB; 393 | 394 | for(var i in this._buffer) { 395 | if(this._buffer.hasOwnProperty(i)) { 396 | delete _swap[this._buffer[i][jSQL_KEY_NAME]]; 397 | } 398 | } 399 | 400 | this._currentDB = this.utils.objectToArray(_swap); 401 | this._DB[this._currentDBName] = this.utils.objectToArray(_swap); 402 | return this; 403 | }, 404 | 405 | limit: function(start, end) { 406 | var _tmp = this._buffer; 407 | var limit; 408 | 409 | if(!end) { 410 | start = [0, end = start][0]; 411 | } 412 | 413 | limit = start + ':' + (start + end); 414 | 415 | this._buffer = utils.listSlice(_tmp, limit); 416 | return this; 417 | }, 418 | 419 | keys: function() { 420 | return utils.keys(this.findAll()); 421 | }, 422 | 423 | first: function(fn) { 424 | if(fn) { 425 | return this.where(fn).first(); 426 | } 427 | 428 | return utils.listSlice(this._select(), ':1'); 429 | }, 430 | 431 | last: function(fn) { 432 | if(fn) { 433 | return this.where(fn).last(); 434 | } 435 | 436 | return utils.listSlice(this._select(), '-1:'); 437 | }, 438 | 439 | distinct: function(field) { 440 | return utils.distinct(this.listAll()); 441 | }, 442 | 443 | rebase: function() { 444 | this._protected = {}; 445 | this.select('*'); 446 | this._resetBuffer(); 447 | this._updateIndexMap(); 448 | return this; 449 | }, 450 | 451 | noConflict: function() { 452 | if(window.jSQL === jSQL) { 453 | window.jSQL = this._jSQL; 454 | } 455 | 456 | if(window.jsql === jsql) { 457 | window.jsql = this._jsql; 458 | } 459 | 460 | return this; 461 | }, 462 | 463 | io: new function() { 464 | var that = this; 465 | 466 | this.ajax = function(uri, data, success, error) { 467 | var args = [].slice.call(arguments); 468 | 469 | if(args.length < 4) { 470 | args.splice(1, 0, {}); 471 | } 472 | 473 | uri = args[0], 474 | data = args[1], 475 | success = args[2], 476 | error = args[3]; 477 | 478 | data._t = utils.uuid(); 479 | 480 | this.reqwest({ 481 | url: uri, 482 | type: 'json', 483 | method: 'get', 484 | data: data, 485 | success: success, 486 | error: error 487 | }); 488 | }, 489 | 490 | this.jsonp = function(uri, data, success, error) { 491 | var args = [].slice.call(arguments); 492 | 493 | if(args.length < 4) { 494 | args.splice(1, 0, {}); 495 | } 496 | 497 | uri = args[0], 498 | data = args[1], 499 | success = args[2], 500 | error = args[3]; 501 | 502 | if(!uri.match('callback=')) { 503 | if(uri.match(/\?/igm)) { 504 | if(uri.lastIndexOf('&') === uri.length - 1) { 505 | uri += 'callback=?&_t=' + utils.uuid(); 506 | } else { 507 | uri += '&callback=?&_t=' + utils.uuid(); 508 | } 509 | } else { 510 | uri += '?callback=?&_t=' + utils.uuid(); 511 | } 512 | } 513 | 514 | this.reqwest({ 515 | url: uri, 516 | type: 'jsonp', 517 | data: data, 518 | success: success, 519 | error: error 520 | }); 521 | } 522 | }, 523 | 524 | on: function(database, event, callback) { 525 | this._events[database] = this._events[database] || new this.Events(); 526 | return this._events[database].on(event, callback); 527 | }, 528 | 529 | off: function(database, event, callback) { 530 | return this._events[database].off(event, callback); 531 | }, 532 | 533 | trigger: function(database, event) { 534 | var args = [].slice.call(arguments, 1); 535 | 536 | if(!this._events.hasOwnProperty(database)) { 537 | return false; 538 | } 539 | 540 | logcat.info('%s: trigger - %s', database, event); 541 | return this._events[database].trigger.apply(this._events[database], args); 542 | }, 543 | 544 | alias: function(name) { 545 | window[name] = this; 546 | return this; 547 | }, 548 | 549 | _select: function(field) { 550 | var tmp, result = []; 551 | 552 | field = field || this._protected['field']; 553 | 554 | if(this._protected['sort'] === true) { 555 | this.trigger(this._currentDBName, 'sort'); 556 | } 557 | 558 | if(field === '*' || (field.join && field.join('') === '*')) { 559 | return this._buffer; 560 | } 561 | 562 | if(typeof(field) === 'string') { 563 | field = field.split(','); 564 | } 565 | 566 | utils.each(this._buffer, function(o, i, r) { 567 | tmp = {}; 568 | tmp[jSQL_KEY_NAME] = utils.deep(o, jSQL_KEY_NAME); 569 | 570 | if(field.length === 1) { 571 | result.push(utils.deep(o, field[0])); 572 | return; 573 | } 574 | 575 | utils.each(field, function(_o, _i, _r) { 576 | if(o.hasOwnProperty(_o)) { 577 | tmp[_o.split('.').pop()] = utils.deep(o, _o); 578 | } 579 | }); 580 | result.push(tmp); 581 | }); 582 | 583 | return result; 584 | }, 585 | 586 | _updateIndexMap: function() { 587 | _DBIndexMap[this._currentDBName] = utils.arrayToObject(this._currentDB); 588 | }, 589 | 590 | _resetBuffer: function() { 591 | this._buffer = this._currentDB; //reset the _buffer 592 | } 593 | }; 594 | 595 | utils = { 596 | deep: function(data, scope) { 597 | var _tmp = data, scope = scope.split('.'); 598 | for(var i = 0; i < scope.length; i++) { 599 | _tmp = _tmp[scope[i]]; 600 | } 601 | return _tmp; 602 | }, 603 | 604 | isArray: nativeIsArray || function(obj) { 605 | return toString.call(obj) === '[object Array]'; 606 | }, 607 | 608 | isObject: function(obj) { 609 | return obj === Object(obj); 610 | }, 611 | 612 | isPlainObject: function(obj) { 613 | return this.isObject(obj) && obj.constructor === Object; 614 | }, 615 | 616 | clone: function (obj) { 617 | if(obj == null || typeof(obj) != 'object') { 618 | return obj; 619 | } 620 | 621 | var temp = new obj.constructor(); 622 | for(var key in obj) { 623 | temp[key] = arguments.callee(obj[key]); 624 | } 625 | 626 | return temp; 627 | }, 628 | 629 | objectToArray: function(object) { 630 | var array = [], object = this.clone(object); 631 | 632 | for(var i in object) { 633 | if(object.hasOwnProperty(i)) { 634 | object[i][jSQL_KEY_NAME] = i; 635 | array.push(object[i]); 636 | } 637 | } 638 | 639 | return array; 640 | }, 641 | 642 | arrayToObject: function(array, key) { 643 | var object = {}; 644 | 645 | for(var i = 0; i < array.length; i++) { 646 | object[array[i][key || jSQL_KEY_NAME]] = this.clone(array[i]); 647 | delete object[array[i][key || jSQL_KEY_NAME]][key || jSQL_KEY_NAME]; 648 | }; 649 | 650 | return object; 651 | }, 652 | 653 | each: function(list, fn) { 654 | if(nativeForEach) { 655 | list.forEach(fn); 656 | return; 657 | } 658 | 659 | for(var i = 0; i < list.length; i++) { 660 | fn(list[i], i, list); 661 | } 662 | }, 663 | 664 | keygen: function(object, indexList) { 665 | var that = this; 666 | var baseRef = [].slice.call(arguments, 1); 667 | var key = ''; 668 | 669 | if(that.isArray(indexList)) { 670 | baseRef = indexList; 671 | } 672 | 673 | that.each(baseRef, function(o, i, r) { 674 | key += utils.deep(object, o); 675 | }); 676 | 677 | return key; 678 | }, 679 | 680 | listSlice: function(list, range) { 681 | var start, end; 682 | 683 | list = [].slice.call(list); 684 | range = range.split(':'); 685 | start = range[0] || 0; 686 | end = range.length > 1 ? range[1] || list.length : list.length; 687 | return [].slice.call(list, start, end); 688 | }, 689 | 690 | appendKey: function(list, indexList) { 691 | var that = this; 692 | 693 | that.each(list, function(o, i, r) { 694 | o[jSQL_KEY_NAME] = that.keygen(o, indexList) || i; 695 | }); 696 | }, 697 | 698 | keys: nativeKeys || (function() { 699 | var hasDontEnumBug = true, 700 | dontEnums = [ 701 | 'toString', 702 | 'toLocaleString', 703 | 'valueOf', 704 | 'hasOwnProperty', 705 | 'isPrototypeOf', 706 | 'propertyIsEnumerable', 707 | 'constructor' 708 | ], 709 | dontEnumsLength = dontEnums.length; 710 | 711 | for (var key in {'toString': null}) { 712 | hasDontEnumBug = false; 713 | } 714 | 715 | return function keys(object) { 716 | if ((typeof object != 'object' && typeof object != 'function') || object === null) { 717 | throw new TypeError('Object.keys called on a non-object'); 718 | } 719 | 720 | var keys = []; 721 | for (var name in object) { 722 | if (object.hasOwnProperty(name)) { 723 | keys.push(name); 724 | } 725 | } 726 | 727 | if (hasDontEnumBug) { 728 | for (var i = 0, ii = dontEnumsLength; i < ii; i++) { 729 | var dontEnum = dontEnums[i]; 730 | if (object.hasOwnProperty(dontEnum)) { 731 | keys.push(dontEnum); 732 | } 733 | } 734 | } 735 | return keys; 736 | }; 737 | })(), 738 | 739 | parseFn: function(fn) { 740 | if(typeof(fn) === 'string') { 741 | fn = fn || true; 742 | fn = new Function('data', 'with(data) { return ' + fn + '; }'); 743 | } 744 | 745 | return fn; 746 | }, 747 | 748 | indexOf: function(list, sought /*, fromIndex */ ) { 749 | if(nativeIndexOf) { 750 | return nativeIndexOf.apply(list, this.listSlice(arguments, '1:')); 751 | } 752 | 753 | var self = list, 754 | length = self.length >>> 0; 755 | 756 | if (!length) { 757 | return -1; 758 | } 759 | 760 | var i = 0; 761 | if (arguments.length > 2) { 762 | i = arguments[2]; 763 | } 764 | 765 | // handle negative indices 766 | i = i >= 0 ? i : Math.max(0, length + i); 767 | for (; i < length; i++) { 768 | if (i in self && self[i] === sought) { 769 | return i; 770 | } 771 | } 772 | return -1; 773 | }, 774 | 775 | lastIndexOf: function lastIndexOf(list, sought /*, fromIndex */) { 776 | if(nativeLastIndexOf) { 777 | return nativeLastIndexOf.apply(list, this.listSlice(arguments, '1:')); 778 | } 779 | 780 | var self = list, 781 | length = self.length >>> 0; 782 | 783 | if (!length) { 784 | return -1; 785 | } 786 | var i = length - 1; 787 | if (arguments.length > 1) { 788 | i = Math.min(i, toInteger(arguments[1])); 789 | } 790 | // handle negative indices 791 | i = i >= 0 ? i : length - Math.abs(i); 792 | for (; i >= 0; i--) { 793 | if (i in self && sought === self[i]) { 794 | return i; 795 | } 796 | } 797 | return -1; 798 | }, 799 | 800 | uuid: function() { 801 | return new Date().getTime() + '_' + parseInt(Math.random() * 1000); 802 | }, 803 | 804 | distinct: function(arr) { 805 | var tmp = []; 806 | 807 | for(var i = 0; i < arr.length; i++) { 808 | if(tmp.indexOf(arr[i]) === -1) { 809 | tmp.push(arr[i]); 810 | } 811 | } 812 | 813 | return tmp; 814 | } 815 | }; 816 | 817 | jSQL = new jSQL(); 818 | 819 | typeof(module) !== 'undefined' && module.exports ? module.exports = jSQL : this.jsql = this.jSQL = jSQL; 820 | })(); 821 | -------------------------------------------------------------------------------- /src/kissy-wrapper/index.post.js: -------------------------------------------------------------------------------- 1 | return jSQL; 2 | }); 3 | -------------------------------------------------------------------------------- /src/kissy-wrapper/index.pre.js: -------------------------------------------------------------------------------- 1 | KISSY.add(function(S) { 2 | -------------------------------------------------------------------------------- /src/reqwest.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Reqwest! A general purpose XHR connection manager 3 | * (c) Dustin Diaz 2013 4 | * https://github.com/ded/reqwest 5 | * license MIT 6 | */ 7 | !function (name, context, definition) { 8 | if (typeof module != 'undefined' && module.exports) module.exports = definition() 9 | else if (typeof define == 'function' && define.amd) define(definition) 10 | else context[name] = definition() 11 | }('reqwest', jsql.io, function () { 12 | 13 | var win = window 14 | , doc = document 15 | , twoHundo = /^20\d$/ 16 | , byTag = 'getElementsByTagName' 17 | , readyState = 'readyState' 18 | , contentType = 'Content-Type' 19 | , requestedWith = 'X-Requested-With' 20 | , head = doc[byTag]('head')[0] 21 | , uniqid = 0 22 | , callbackPrefix = 'reqwest_' + (+new Date()) 23 | , lastValue // data stored by the most recent JSONP callback 24 | , xmlHttpRequest = 'XMLHttpRequest' 25 | , noop = function () {} 26 | 27 | , isArray = typeof Array.isArray == 'function' 28 | ? Array.isArray 29 | : function (a) { 30 | return a instanceof Array 31 | } 32 | 33 | , defaultHeaders = { 34 | contentType: 'application/x-www-form-urlencoded' 35 | , requestedWith: xmlHttpRequest 36 | , accept: { 37 | '*': 'text/javascript, text/html, application/xml, text/xml, */*' 38 | , xml: 'application/xml, text/xml' 39 | , html: 'text/html' 40 | , text: 'text/plain' 41 | , json: 'application/json, text/javascript' 42 | , js: 'application/javascript, text/javascript' 43 | } 44 | } 45 | 46 | , xhr = win[xmlHttpRequest] 47 | ? function () { 48 | return new XMLHttpRequest() 49 | } 50 | : function () { 51 | return new ActiveXObject('Microsoft.XMLHTTP') 52 | } 53 | , globalSetupOptions = { 54 | dataFilter: function (data) { 55 | return data 56 | } 57 | } 58 | 59 | function handleReadyState(r, success, error) { 60 | return function () { 61 | // use _aborted to mitigate against IE err c00c023f 62 | // (can't read props on aborted request objects) 63 | if (r._aborted) return error(r.request) 64 | if (r.request && r.request[readyState] == 4) { 65 | r.request.onreadystatechange = noop 66 | if (twoHundo.test(r.request.status)) 67 | success(r.request) 68 | else 69 | error(r.request) 70 | } 71 | } 72 | } 73 | 74 | function setHeaders(http, o) { 75 | var headers = o.headers || {} 76 | , h 77 | 78 | headers.Accept = headers.Accept 79 | || defaultHeaders.accept[o.type] 80 | || defaultHeaders.accept['*'] 81 | 82 | // breaks cross-origin requests with legacy browsers 83 | if (!o.crossOrigin && !headers[requestedWith]) headers[requestedWith] = defaultHeaders.requestedWith 84 | if (!headers[contentType]) headers[contentType] = o.contentType || defaultHeaders.contentType 85 | for (h in headers) 86 | headers.hasOwnProperty(h) && http.setRequestHeader(h, headers[h]) 87 | } 88 | 89 | function setCredentials(http, o) { 90 | if (typeof o.withCredentials !== 'undefined' && typeof http.withCredentials !== 'undefined') { 91 | http.withCredentials = !!o.withCredentials 92 | } 93 | } 94 | 95 | function generalCallback(data) { 96 | lastValue = data 97 | } 98 | 99 | function urlappend (url, s) { 100 | return url + (/\?/.test(url) ? '&' : '?') + s 101 | } 102 | 103 | function handleJsonp(o, fn, err, url) { 104 | var reqId = uniqid++ 105 | , cbkey = o.jsonpCallback || 'callback' // the 'callback' key 106 | , cbval = o.jsonpCallbackName || reqwest.getcallbackPrefix(reqId) 107 | // , cbval = o.jsonpCallbackName || ('reqwest_' + reqId) // the 'callback' value 108 | , cbreg = new RegExp('((^|\\?|&)' + cbkey + ')=([^&]+)') 109 | , match = url.match(cbreg) 110 | , script = doc.createElement('script') 111 | , loaded = 0 112 | , isIE10 = navigator.userAgent.indexOf('MSIE 10.0') !== -1 113 | 114 | if (match) { 115 | if (match[3] === '?') { 116 | url = url.replace(cbreg, '$1=' + cbval) // wildcard callback func name 117 | } else { 118 | cbval = match[3] // provided callback func name 119 | } 120 | } else { 121 | url = urlappend(url, cbkey + '=' + cbval) // no callback details, add 'em 122 | } 123 | 124 | win[cbval] = generalCallback 125 | 126 | script.type = 'text/javascript' 127 | script.src = url 128 | script.async = true 129 | if (typeof script.onreadystatechange !== 'undefined' && !isIE10) { 130 | // need this for IE due to out-of-order onreadystatechange(), binding script 131 | // execution to an event listener gives us control over when the script 132 | // is executed. See http://jaubourg.net/2010/07/loading-script-as-onclick-handler-of.html 133 | // 134 | // if this hack is used in IE10 jsonp callback are never called 135 | script.event = 'onclick' 136 | script.htmlFor = script.id = '_reqwest_' + reqId 137 | } 138 | 139 | script.onload = script.onreadystatechange = function () { 140 | if ((script[readyState] && script[readyState] !== 'complete' && script[readyState] !== 'loaded') || loaded) { 141 | return false 142 | } 143 | script.onload = script.onreadystatechange = null 144 | script.onclick && script.onclick() 145 | // Call the user callback with the last value stored and clean up values and scripts. 146 | o.success && o.success(lastValue) 147 | lastValue = undefined 148 | head.removeChild(script) 149 | loaded = 1 150 | } 151 | 152 | // Add the script to the DOM head 153 | head.appendChild(script) 154 | 155 | // Enable JSONP timeout 156 | return { 157 | abort: function () { 158 | script.onload = script.onreadystatechange = null 159 | o.error && o.error({}, 'Request is aborted: timeout', {}) 160 | lastValue = undefined 161 | head.removeChild(script) 162 | loaded = 1 163 | } 164 | } 165 | } 166 | 167 | function getRequest(fn, err) { 168 | var o = this.o 169 | , method = (o.method || 'GET').toUpperCase() 170 | , url = typeof o === 'string' ? o : o.url 171 | // convert non-string objects to query-string form unless o.processData is false 172 | , data = (o.processData !== false && o.data && typeof o.data !== 'string') 173 | ? reqwest.toQueryString(o.data) 174 | : (o.data || null) 175 | , http 176 | 177 | // if we're working on a GET request and we have data then we should append 178 | // query string to end of URL and not post data 179 | if ((o.type == 'jsonp' || method == 'GET') && data) { 180 | url = urlappend(url, data) 181 | data = null 182 | } 183 | 184 | if (o.type == 'jsonp') return handleJsonp(o, fn, err, url) 185 | 186 | http = xhr() 187 | http.open(method, url, true) 188 | setHeaders(http, o) 189 | setCredentials(http, o) 190 | http.onreadystatechange = handleReadyState(this, fn, err) 191 | o.before && o.before(http) 192 | http.send(data) 193 | return http 194 | } 195 | 196 | function Reqwest(o, fn) { 197 | this.o = o 198 | this.fn = fn 199 | 200 | init.apply(this, arguments) 201 | } 202 | 203 | function setType(url) { 204 | var m = url.match(/\.(json|jsonp|html|xml)(\?|$)/) 205 | return m ? m[1] : 'js' 206 | } 207 | 208 | function init(o, fn) { 209 | 210 | this.url = typeof o == 'string' ? o : o.url 211 | this.timeout = null 212 | 213 | // whether request has been fulfilled for purpose 214 | // of tracking the Promises 215 | this._fulfilled = false 216 | // success handlers 217 | this._fulfillmentHandlers = [] 218 | // error handlers 219 | this._errorHandlers = [] 220 | // complete (both success and fail) handlers 221 | this._completeHandlers = [] 222 | this._erred = false 223 | this._responseArgs = {} 224 | 225 | var self = this 226 | , type = o.type || setType(this.url) 227 | 228 | fn = fn || function () {} 229 | 230 | if (o.timeout) { 231 | this.timeout = setTimeout(function () { 232 | self.abort() 233 | }, o.timeout) 234 | } 235 | 236 | if (o.success) { 237 | this._fulfillmentHandlers.push(function () { 238 | o.success.apply(o, arguments) 239 | }) 240 | } 241 | 242 | if (o.error) { 243 | this._errorHandlers.push(function () { 244 | o.error.apply(o, arguments) 245 | }) 246 | } 247 | 248 | if (o.complete) { 249 | this._completeHandlers.push(function () { 250 | o.complete.apply(o, arguments) 251 | }) 252 | } 253 | 254 | function complete (resp) { 255 | o.timeout && clearTimeout(self.timeout) 256 | self.timeout = null 257 | while (self._completeHandlers.length > 0) { 258 | self._completeHandlers.shift()(resp) 259 | } 260 | } 261 | 262 | function success (resp) { 263 | // use global data filter on response text 264 | var filteredResponse = globalSetupOptions.dataFilter(resp.responseText, type) 265 | , r = resp.responseText = filteredResponse 266 | if (r) { 267 | switch (type) { 268 | case 'json': 269 | try { 270 | resp = win.JSON ? win.JSON.parse(r) : eval('(' + r + ')') 271 | } catch (err) { 272 | return error(resp, 'Could not parse JSON in response', err) 273 | } 274 | break 275 | case 'js': 276 | resp = eval(r) 277 | break 278 | case 'html': 279 | resp = r 280 | break 281 | case 'xml': 282 | resp = resp.responseXML 283 | && resp.responseXML.parseError // IE trololo 284 | && resp.responseXML.parseError.errorCode 285 | && resp.responseXML.parseError.reason 286 | ? null 287 | : resp.responseXML 288 | break 289 | } 290 | } 291 | 292 | self._responseArgs.resp = resp 293 | self._fulfilled = true 294 | fn(resp) 295 | while (self._fulfillmentHandlers.length > 0) { 296 | self._fulfillmentHandlers.shift()(resp) 297 | } 298 | 299 | complete(resp) 300 | } 301 | 302 | function error(resp, msg, t) { 303 | self._responseArgs.resp = resp 304 | self._responseArgs.msg = msg 305 | self._responseArgs.t = t 306 | self._erred = true 307 | while (self._errorHandlers.length > 0) { 308 | self._errorHandlers.shift()(resp, msg, t) 309 | } 310 | complete(resp) 311 | } 312 | 313 | this.request = getRequest.call(this, success, error) 314 | } 315 | 316 | Reqwest.prototype = { 317 | abort: function () { 318 | this._aborted = true 319 | this.request.abort() 320 | } 321 | 322 | , retry: function () { 323 | init.call(this, this.o, this.fn) 324 | } 325 | 326 | /** 327 | * Small deviation from the Promises A CommonJs specification 328 | * http://wiki.commonjs.org/wiki/Promises/A 329 | */ 330 | 331 | /** 332 | * `then` will execute upon successful requests 333 | */ 334 | , then: function (success, fail) { 335 | if (this._fulfilled) { 336 | success(this._responseArgs.resp) 337 | } else if (this._erred) { 338 | fail(this._responseArgs.resp, this._responseArgs.msg, this._responseArgs.t) 339 | } else { 340 | this._fulfillmentHandlers.push(success) 341 | this._errorHandlers.push(fail) 342 | } 343 | return this 344 | } 345 | 346 | /** 347 | * `always` will execute whether the request succeeds or fails 348 | */ 349 | , always: function (fn) { 350 | if (this._fulfilled || this._erred) { 351 | fn(this._responseArgs.resp) 352 | } else { 353 | this._completeHandlers.push(fn) 354 | } 355 | return this 356 | } 357 | 358 | /** 359 | * `fail` will execute when the request fails 360 | */ 361 | , fail: function (fn) { 362 | if (this._erred) { 363 | fn(this._responseArgs.resp, this._responseArgs.msg, this._responseArgs.t) 364 | } else { 365 | this._errorHandlers.push(fn) 366 | } 367 | return this 368 | } 369 | } 370 | 371 | function reqwest(o, fn) { 372 | return new Reqwest(o, fn) 373 | } 374 | 375 | // normalize newline variants according to spec -> CRLF 376 | function normalize(s) { 377 | return s ? s.replace(/\r?\n/g, '\r\n') : '' 378 | } 379 | 380 | function serial(el, cb) { 381 | var n = el.name 382 | , t = el.tagName.toLowerCase() 383 | , optCb = function (o) { 384 | // IE gives value="" even where there is no value attribute 385 | // 'specified' ref: http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-862529273 386 | if (o && !o.disabled) 387 | cb(n, normalize(o.attributes.value && o.attributes.value.specified ? o.value : o.text)) 388 | } 389 | , ch, ra, val, i 390 | 391 | // don't serialize elements that are disabled or without a name 392 | if (el.disabled || !n) return 393 | 394 | switch (t) { 395 | case 'input': 396 | if (!/reset|button|image|file/i.test(el.type)) { 397 | ch = /checkbox/i.test(el.type) 398 | ra = /radio/i.test(el.type) 399 | val = el.value 400 | // WebKit gives us "" instead of "on" if a checkbox has no value, so correct it here 401 | ;(!(ch || ra) || el.checked) && cb(n, normalize(ch && val === '' ? 'on' : val)) 402 | } 403 | break 404 | case 'textarea': 405 | cb(n, normalize(el.value)) 406 | break 407 | case 'select': 408 | if (el.type.toLowerCase() === 'select-one') { 409 | optCb(el.selectedIndex >= 0 ? el.options[el.selectedIndex] : null) 410 | } else { 411 | for (i = 0; el.length && i < el.length; i++) { 412 | el.options[i].selected && optCb(el.options[i]) 413 | } 414 | } 415 | break 416 | } 417 | } 418 | 419 | // collect up all form elements found from the passed argument elements all 420 | // the way down to child elements; pass a '' or form fields. 421 | // called with 'this'=callback to use for serial() on each element 422 | function eachFormElement() { 423 | var cb = this 424 | , e, i 425 | , serializeSubtags = function (e, tags) { 426 | var i, j, fa 427 | for (i = 0; i < tags.length; i++) { 428 | fa = e[byTag](tags[i]) 429 | for (j = 0; j < fa.length; j++) serial(fa[j], cb) 430 | } 431 | } 432 | 433 | for (i = 0; i < arguments.length; i++) { 434 | e = arguments[i] 435 | if (/input|select|textarea/i.test(e.tagName)) serial(e, cb) 436 | serializeSubtags(e, [ 'input', 'select', 'textarea' ]) 437 | } 438 | } 439 | 440 | // standard query string style serialization 441 | function serializeQueryString() { 442 | return reqwest.toQueryString(reqwest.serializeArray.apply(null, arguments)) 443 | } 444 | 445 | // { 'name': 'value', ... } style serialization 446 | function serializeHash() { 447 | var hash = {} 448 | eachFormElement.apply(function (name, value) { 449 | if (name in hash) { 450 | hash[name] && !isArray(hash[name]) && (hash[name] = [hash[name]]) 451 | hash[name].push(value) 452 | } else hash[name] = value 453 | }, arguments) 454 | return hash 455 | } 456 | 457 | // [ { name: 'name', value: 'value' }, ... ] style serialization 458 | reqwest.serializeArray = function () { 459 | var arr = [] 460 | eachFormElement.apply(function (name, value) { 461 | arr.push({name: name, value: value}) 462 | }, arguments) 463 | return arr 464 | } 465 | 466 | reqwest.serialize = function () { 467 | if (arguments.length === 0) return '' 468 | var opt, fn 469 | , args = Array.prototype.slice.call(arguments, 0) 470 | 471 | opt = args.pop() 472 | opt && opt.nodeType && args.push(opt) && (opt = null) 473 | opt && (opt = opt.type) 474 | 475 | if (opt == 'map') fn = serializeHash 476 | else if (opt == 'array') fn = reqwest.serializeArray 477 | else fn = serializeQueryString 478 | 479 | return fn.apply(null, args) 480 | } 481 | 482 | reqwest.toQueryString = function (o) { 483 | var qs = '', i 484 | , enc = encodeURIComponent 485 | , push = function (k, v) { 486 | qs += enc(k) + '=' + enc(v) + '&' 487 | } 488 | , k, v 489 | 490 | if (isArray(o)) { 491 | for (i = 0; o && i < o.length; i++) push(o[i].name, o[i].value) 492 | } else { 493 | for (k in o) { 494 | if (!Object.hasOwnProperty.call(o, k)) continue 495 | v = o[k] 496 | if (isArray(v)) { 497 | for (i = 0; i < v.length; i++) push(k, v[i]) 498 | } else push(k, o[k]) 499 | } 500 | } 501 | 502 | // spaces should be + according to spec 503 | return qs.replace(/&$/, '').replace(/%20/g, '+') 504 | } 505 | 506 | reqwest.getcallbackPrefix = function () { 507 | return callbackPrefix + parseInt(Math.random() * 10000) 508 | } 509 | 510 | // jQuery and Zepto compatibility, differences can be remapped here so you can call 511 | // .ajax.compat(options, callback) 512 | reqwest.compat = function (o, fn) { 513 | if (o) { 514 | o.type && (o.method = o.type) && delete o.type 515 | o.dataType && (o.type = o.dataType) 516 | o.jsonpCallback && (o.jsonpCallbackName = o.jsonpCallback) && delete o.jsonpCallback 517 | o.jsonp && (o.jsonpCallback = o.jsonp) 518 | } 519 | return new Reqwest(o, fn) 520 | } 521 | 522 | reqwest.ajaxSetup = function (options) { 523 | options = options || {} 524 | for (var k in options) { 525 | globalSetupOptions[k] = options[k] 526 | } 527 | } 528 | 529 | return reqwest 530 | }); 531 | -------------------------------------------------------------------------------- /test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 18 | 19 | 20 | 230 | 231 | 232 | -------------------------------------------------------------------------------- /test/struct.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaulGuo/jSQL/715f1a34c656e9817a2a8c9ba240770c5bc5db44/test/struct.js -------------------------------------------------------------------------------- /test/test-data.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PaulGuo/jSQL/715f1a34c656e9817a2a8c9ba240770c5bc5db44/test/test-data.js --------------------------------------------------------------------------------
数据的异想世界
使用jSQL可以让你更加专注于业务需求和逻辑,通过jSQL可以将排序、筛选等数据查询的底层操作同业务逻辑完美分离。从而提高代码的可维护性和开发效率。jSQL不仅可以用于PC端,也可以在移动手持设备的应用中使用。除了JavaScript版本的jSQL,如果你喜欢jSQL的使用方式和API,同时你又是一个Python后端开发人员且不喜欢庞大的ORM,同样有短小精干的Python-jSQL可以让你在Python框架中方便的对MySQL进行操作。jSQL不是一个库,它是一个使得数据的查询操作变得更方便快捷高效的解决思路和思想、方法的抽象。jSQL内置IO和事件中心,天然的支持远程数据源,并且所有数据的操作行为最终都会通过事件中心广播出去,从而使得数据操作和业务逻辑解耦分离。
jSQL是一个用SQL的思想和API实现JavaScript数据操作(查询,过滤)的方法库(同时有基于Tornado DB封装的类似API的后端SQL查询封装),它通过链式调用的方式简化数据查询和操作,同时支持多种形式的数据源。
Object
Array
URI
Object类型:
{ 229 | key: {column: value}, 230 | key: {column: value} 231 | } 232 |
Object的key对应数据表的唯一索引,cloumn映射为数据表的字段,value对应数据表相应字段的值。
Array类型:
[ 239 | {column: value}, 240 | {column: value} 241 | ] 242 |
Array的每个item对应数据表的一个记录,cloumn对应字段名,value对应字段值,同Object类型不同的是,当前类型的索引key是根据指定的条件或者自动生成的。
create(dbname, db /*, indexList */)
create(dbname, db, /*, scope|function */)
在内存中创建一个数据库(指定indexList则根据指定字段生成唯一键)
用法示例1: 253 | 254 | jsql.create('local', {}) 255 | 256 | 用法示例2: 257 | 258 | jsql.create('local', 'http://uri?callback=?', 'data.result') 259 | 260 | 用法示例3: 261 | 262 | jsql.create('local', 'http://uri', function(resp) { 263 | return resp.data.result; 264 | }) 265 |
use(dbname)
使用指定数据库作为当前操作的数据库,并重置缓冲区(buffer)
local = jsql.use('local').listAll(); 272 | remote = jsql.use('remote').listAll(); 273 |
drop(dbname)
丢弃指定数据库,同时从内存中抹除
jsql.drop('local'); 280 |
dbs()
获取当前内存中存在的所有数据库名称
dbs = jsql.dbs(); // ['local', 'remote', 'test'] 287 |
db()
获取当前操作的数据库名称
cur = jsql.db(); // local 294 |
select(key)
指定获取的数据字段名称
local = jsql.use('local').select('*').listAll(); 301 | remote = jsql.use('remote').select('name, age').listAll(); 302 |
count()
获取当前操作缓冲区的数据条数
count = jsql.use('local').count(); 309 |
total(scope|function)
返回当前操作数据库中含有某字段(或者符合传入条件)的数据总条数。
total = db.total('data.name'); 316 | total = db.total(function(item) { 317 | return item.data.age === 24; 318 | }); 319 |
orderby(field, /* callback, */ order)
根据指定字段的值(或者处理后的值)和排序规则对当前缓冲区的数据进行排序。
db = jsql.use('local'); 326 | db.select('*').orderby('data.time', buildDate, 'asc'); 327 | result = db.listAll(); 328 |
where(fn|[fn, …])
返回当前缓冲区中符合指定条件的数据集合(如果传入为一组fn,则为并集)。
db = jsql.use('local'); 335 | db.where([function(item) { 336 | return item.data.age < 24; 337 | }, function(item) { 338 | return item.data.sex === 'male'; 339 | }]); 340 | result = db.listAll(); 341 |
iterate(fn)
根据指定的方法对当前缓冲区中的数据进行遍历。
db = jsql.use('local'); 348 | db.where(function(item) { 349 | return item.data.age < 24; 350 | }).iterate(function(item) { 351 | item.data.checked = true; 352 | }); 353 | result = db.listAll(); 354 |
findAll(fn)
以Object类型返回当前缓冲区中的数据集合
find(key)
获取缓冲区中指定key的数据。
listAll()
以Array类型返回当前缓冲区中的数据集合。
update(key, data)
根据指定数据对指定key的数据进行更新操作。
insert(item, key /*, from index */)
(在指定位置)插入一条数据。
append(/* database, */ data)
向当前操作的数据库中追加数据。
jsql.append('local', {}); 381 |
remove()
从当前操作的数据库中删除缓冲区中的数据。
db = jsql.use('local'); 388 | db.where(function(item) { 389 | return item.data.age < 24; 390 | }).remove(); 391 |
limit(start, end)
返回当前缓冲区指定区域的数据(也可以使用start:end)。
start:end
db = jsql.use('local'); 398 | db.limit('1:10'); 399 | result = db.listAll(); 400 |
keys()
返回当前缓冲区中所有数据的键名集合。
first(fn)
返回满足条件的第一条数据。
last(fn)
返回满足条件的最后一条数据。
db = jsql.use('local'); 415 | db.last(function(item) { 416 | return item.data.age < 24; 417 | }); 418 | result = db.find(); 419 |
distinct()
以Array类型返回当前缓冲区中去重后的数组集合。
db = jsql.use('local'); 426 | result = db.select('airLineCode').distinct(); // ['CA', 'MU', 'CZ'] 427 |
rebase()
重置缓冲区数据。
noConflict()
避免命名空间冲突。
_jSQL = jsql.noConflict(); 438 |
io
数据接口异步请求。
io.ajax(uri, data, success, error)
io.jsonp(uri, data, success, error)
jSQL内置的事件中心不仅用于广播/订阅数据库自身的操作,同时它是一个完全独立的事件中心,跟数据无关的操作也可以完全通过它进行广播/订阅,且天然支持广播域,多个域各自拥有不同的事件池,互不干扰。
on(database, event, callback)
监听数据库事件。
global
off(database[, event])
取消对数据库事件的监听。
trigger(database, event)
手动触发指定数据库的指定事件。
举例,两个不同模块之间的广播监听示例:
Module A: 497 | 498 | jsql.on('global', 'moduleBChange', function(arg1, arg2) { 499 | // do something 500 | }); 501 | 502 | Module B: 503 | 504 | jsql.trigger('global', 'moduleBChange', arg1, arg2); 505 |
举例,监听某个数据库的创建:
jsql.on('local', 'create', function() { 510 | // do something 511 | }); 512 | 513 | jsql.create('local', {}); 514 |
所谓闭合操作即执行到此将重置缓冲区,是链式查询的最终状态。
find()
findAll()
insert()
save()
saveAll()
update()
除此之外,use()也会触发缓冲区重置的操作。
use()