├── .gitattributes ├── .gitignore ├── .vscode ├── launch.json └── settings.json ├── JCalculator.js ├── JCalculator.min.js ├── LICENSE ├── README.md ├── gulpfile.js ├── index.js ├── package.json └── test ├── JCalculator.test.js └── tree.test.js /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ 3 | coverage/ 4 | .vscode/ -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // 使用 IntelliSense 了解相关属性。 3 | // 悬停以查看现有属性的描述。 4 | // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Mocha Tests", 11 | "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", 12 | "args": [ 13 | "-u", 14 | "tdd", 15 | "--timeout", 16 | "999999", 17 | "--colors", 18 | "${workspaceFolder}/test" 19 | ], 20 | "internalConsoleOptions": "openOnSessionStart" 21 | }, 22 | { 23 | "type": "node", 24 | "request": "launch", 25 | "name": "Launch Program", 26 | "program": "${workspaceFolder}\\JCalculator.js" 27 | } 28 | ] 29 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | } -------------------------------------------------------------------------------- /JCalculator.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | // 多种环境支持,以及一些零碎开头引用了underscore的代码,致敬经典。 3 | var root = typeof self == 'object' && self.self === self && self || 4 | typeof global == 'object' && global.global === global && global || this; 5 | // 保存jc 6 | var previousjc = root.jc; 7 | // 原型赋值,便于压缩 8 | var ArrayProto = Array.prototype, ObjProto = Object.prototype; 9 | var push = ArrayProto.push, 10 | slice = ArrayProto.slice, 11 | toString = ObjProto.toString, 12 | hasOwnProperty = ObjProto.hasOwnProperty; 13 | // 定义了一些ECMAScript 5方法 14 | var nativeIsArray = Array.isArray, 15 | nativeKeys = Object.keys, 16 | nativeValues = Object.values ? Object.values : function (obj) { 17 | return nativeKeys(obj).map(function (key) { 18 | return obj[key]; 19 | }) 20 | }, 21 | nativeCreate = Object.create; 22 | 23 | // 创建一个jc对象, 保留将来有拓展成支持链式的可能 24 | var jc = function (obj) { 25 | if (obj instanceof jc) return obj; 26 | if (!(this instanceof jc)) return new jc(obj); 27 | this._wrapped = obj; 28 | }; 29 | // 针对不同的环境 30 | if (typeof exports != 'undefined' && !exports.nodeType) { 31 | if (typeof module != 'undefined' && !module.nodeType && module.exports) { 32 | exports = module.exports = jc; 33 | } 34 | exports.jc = jc; 35 | } else { 36 | root.jc = jc; 37 | } 38 | // 版本 39 | jc.VERSION = '1.1.0'; 40 | 41 | /** 42 | * 遍历 43 | * 44 | * @public 45 | * @param {array} data 数据 46 | * @param {function} fn 循环每行执行函数 47 | */ 48 | jc.map = function (data, fn) { 49 | if (jc.isArray(data)) return data.map(fn); 50 | return []; 51 | }; 52 | 53 | /** 54 | * 对象键值对循环 55 | * 56 | * @public 57 | * @param {objec} obj 数据 58 | * @param {function} fn 循环每个键值对执行函数 59 | */ 60 | jc.forIn = function (obj, fn) { 61 | if (!jc.isObject(obj)) return {}; 62 | var i = 0 63 | for (var key in obj) { 64 | fn(key, obj[key], i); 65 | i++; 66 | } 67 | } 68 | 69 | /** 70 | * 过滤 71 | * 72 | * @public 73 | * @param {array} data 数据 74 | * @param {function} iteratee 过滤函数 75 | */ 76 | jc.filter = function (data, iteratee) { 77 | if (jc.isArray(data)) { 78 | return data.filter(iteratee); 79 | } else { 80 | return []; 81 | } 82 | } 83 | 84 | /** 85 | * 过滤 86 | * 87 | * @public 88 | * @param {array} data 数据 89 | * @param {object} key 过滤条件 90 | */ 91 | jc.where = function (data, key) { 92 | var result; 93 | if (jc.isFunction(key)) { 94 | result = jc.filter(data, key) 95 | } else if (jc.isObject(key)) { 96 | result = jc.filter(data, function (d) { 97 | return nativeKeys(key).every(function (k) { 98 | return key[k] === d[k]; 99 | }) 100 | }); 101 | } else { 102 | return [] 103 | } 104 | return result; 105 | } 106 | 107 | 108 | /** 109 | * limit 110 | * 111 | * @public 112 | * @param {array} val 数据 113 | * @param {object} option 检索条件 114 | */ 115 | jc.limit = function (data, option) { 116 | return sqlLimit(data, option); 117 | } 118 | 119 | /** 120 | * orderBy 121 | * 122 | * @public 123 | * @param array data 数据 124 | * @param {object||function} iteratee 排序条件 125 | */ 126 | jc.order = jc.orderBy = function (data, iteratee) { 127 | return sqlOrder(data, iteratee); 128 | } 129 | 130 | /** 131 | * tree 132 | * 生成树 133 | * 134 | * @public 135 | * @param data 数据 136 | * @param option 生成树相关配置项 137 | */ 138 | jc.tree = function (data, option) { 139 | if (!option.children) option.children = "children"; 140 | // 生成jc.sql中的col选择项 141 | var col; 142 | if (jc.isString(option.retain)) { 143 | col = [option.retain, option.id, option.parent] 144 | } else if (jc.isArray(option.retain)) { 145 | col = option.retain; 146 | col.push(option.id); 147 | col.push(option.parent); 148 | } else if (jc.isObject(option.retain)) { 149 | col = option.retain; 150 | col[option.id] = option.id; 151 | col[option.parent] = option.parent; 152 | } else { 153 | // 为了不影响原始数据 154 | data = jc.extend(true, [], data); 155 | } 156 | // 使用sql方法,生成data; 157 | if (col) { 158 | data = jc.sql({ 159 | select: { 160 | col: col 161 | }, 162 | from: data 163 | }) 164 | } 165 | 166 | // 上面都是都是为了提高灵活度,让用户能有自己的骚操作。下面才是生成树的核心 167 | var root = {}; 168 | var group = jc.groupBy(data, function (row) { 169 | if (row[option.id] == option.root) root = row; // 假如数据中根节点不是虚拟存在的 170 | return row[option.parent] 171 | }); 172 | jc.map(data, function (row) { 173 | // 行的ID相当于分组中的父ID 174 | var parentId = row[option.id] 175 | if (group[parentId]) { 176 | row[option.children] = group[parentId]; 177 | } 178 | }) 179 | 180 | if (jc.isObjEmpty(root)) { 181 | root[option.children] = group[option.root]; 182 | } 183 | return root 184 | } 185 | 186 | /** 187 | * treeDic 188 | * 生成树查询字典 189 | * 190 | * @public 191 | * @param data 数据 192 | * @param {Object} option 相关配置项 193 | */ 194 | jc.treeDic = function (data, option) { 195 | var defaultOption = { 196 | id: "id", 197 | children: "children", 198 | deleteEmptyChildren: true, 199 | } 200 | if (jc.isObject(option)) jc.extend(defaultOption, option) 201 | if (jc.isObject(data)) data = [data] 202 | var treeObj = {} 203 | 204 | function query(tree) { 205 | tree.map(function (row) { 206 | treeObj[row[defaultOption.id]] = row 207 | if ( 208 | ( 209 | !row[defaultOption.children] 210 | || row[defaultOption.children].length == 0 211 | ) 212 | && defaultOption.deleteEmptyChildren 213 | ) { 214 | delete row[defaultOption.children] 215 | } else { 216 | query(row[defaultOption.children]) 217 | } 218 | }) 219 | } 220 | 221 | query(data) 222 | return treeObj 223 | } 224 | 225 | /** 226 | * treeFilter 227 | * 生成树查询字典 228 | * 229 | * @public 230 | * @param data 数据 231 | * @param {Object} option 相关配置项 232 | */ 233 | jc.treeFilter = function (data, option) { 234 | var defaultOption = { 235 | root: "0", 236 | id: "id", 237 | parent: "pid", 238 | children: "children", 239 | deleteEmptyChildren: true, 240 | } 241 | if (jc.isObject(option)) jc.extend(defaultOption, option) 242 | if (jc.isObject(data)) data = [data] 243 | data = jc.extend([], data) 244 | var treeArr = [] 245 | // var treeObj = {} 246 | 247 | function query(tree) { 248 | tree.map(function (row) { 249 | treeArr.push(row) 250 | // treeObj[row[defaultOption.id]] = row 251 | if (row[defaultOption.children] && row[defaultOption.children].length > 0) { 252 | var children = row[defaultOption.children] 253 | query(children) 254 | } 255 | delete row[defaultOption.children] 256 | }) 257 | } 258 | 259 | query(data) 260 | 261 | var filterArr = jc.where(treeArr, defaultOption.filter) 262 | var newTree = jc.tree(filterArr, defaultOption) 263 | return newTree 264 | } 265 | 266 | /** 267 | * treeMap 268 | * 生成树查询字典 269 | * 270 | * @public 271 | * @param data 数据 272 | * @param {Object} option 相关配置项 273 | */ 274 | jc.treeMap = function (data, option) { 275 | var defaultOption = { 276 | root: "0", 277 | id: "id", 278 | parent: "pid", 279 | children: "children", 280 | deleteEmptyChildren: true, 281 | map: function (row) { 282 | return row 283 | } 284 | } 285 | if (jc.isObject(option)) jc.extend(defaultOption, option) 286 | var root = jc.isObject(data) ? [data] : data 287 | function query(tree) { 288 | tree.map(function (row) { 289 | defaultOption.map(row) 290 | if ( 291 | ( 292 | !row[defaultOption.children] 293 | || row[defaultOption.children].length == 0 294 | ) 295 | && defaultOption.deleteEmptyChildren 296 | ) { 297 | delete row[defaultOption.children] 298 | } else { 299 | query(row[defaultOption.children]) 300 | } 301 | }) 302 | } 303 | 304 | query(root) 305 | 306 | return data 307 | } 308 | 309 | 310 | /** 311 | * treeSearch 312 | * 生成树查询字典 313 | * 314 | * @public 315 | * @param data 数据 316 | * @param {Object} option 相关配置项 317 | */ 318 | jc.treeSearch = function (data, option) { 319 | var defaultOption = { 320 | // root: "0", 321 | // id: "id", 322 | // parent: "pid", 323 | children: "children", 324 | search: {} 325 | } 326 | if (jc.isObject(option)) jc.extend(defaultOption, option) 327 | var root = jc.isObject(data) ? [data] : data 328 | var searchArr = [] 329 | function query(tree) { 330 | tree.map(function (row) { 331 | var search = defaultOption.search 332 | // var flag = true 333 | var data = jc.where([row], search) 334 | if (data.length > 0) { 335 | searchArr.push (row) 336 | } 337 | // for (var key in search) { 338 | // if (search[key] != row[key]) { 339 | // flag = false 340 | // break; 341 | // } 342 | // } 343 | // if (flag) searchArr.push(row) 344 | if ( 345 | ( 346 | !row[defaultOption.children] 347 | || row[defaultOption.children].length == 0 348 | ) 349 | // && defaultOption.deleteEmptyChildren 350 | ) { 351 | // delete row[defaultOption.children] 352 | } else { 353 | query(row[defaultOption.children]) 354 | } 355 | }) 356 | } 357 | 358 | query(root) 359 | 360 | return searchArr 361 | } 362 | 363 | 364 | /** 365 | * treePath 366 | * 生成树查询字典 367 | * 368 | * @public 369 | * @param data 数据 370 | * @param {Object} option 相关配置项 371 | */ 372 | jc.treePath = function (data, option) { 373 | var defaultOption = { 374 | root: "0", 375 | id: "id", 376 | parent: "pid", 377 | children: "children", 378 | path: "" 379 | } 380 | if (jc.isObject(option)) jc.extend(defaultOption, option) 381 | var dic = jc.treeDic(data, defaultOption) 382 | var pathArr = [] 383 | function query(path) { 384 | if (dic[path]) { 385 | pathArr.unshift(dic[path]) 386 | if (dic[path][defaultOption.parent]) { 387 | query(dic[path][defaultOption.parent]) 388 | } else { 389 | return 390 | } 391 | } else { 392 | return 393 | } 394 | } 395 | 396 | query(defaultOption.path) 397 | 398 | return pathArr 399 | } 400 | 401 | 402 | /** 403 | * extend 404 | * 此段偷懒,大部分引用了jquery的extend,用法和jq一致; 405 | * 406 | * @public 407 | * @param boolean 可选,true为深拷贝,默认浅拷贝 408 | * @param {object|array} 被覆盖的对象 409 | * @param {object|array} 需要复制的对象 410 | * @return {object|array} 被覆盖的对象 411 | */ 412 | jc.extend = function () { 413 | var options, name, src, copy, copyIsArray, clone, target = arguments[0] || {}, 414 | i = 1, 415 | length = arguments.length, 416 | deep = false; 417 | if (typeof target === "boolean") { 418 | deep = target; 419 | target = arguments[1] || {}; 420 | i++; 421 | } 422 | 423 | if (typeof target !== "object" && !jc.isFunction(target)) { 424 | target = {}; 425 | } 426 | 427 | if (length === i) { 428 | target = this; --i; 429 | } 430 | for (; i < length; i++) { 431 | if ((options = arguments[i]) != null) { 432 | for (name in options) { 433 | src = target[name]; 434 | copy = options[name]; 435 | if (target === copy) { 436 | continue; 437 | } 438 | if (deep && copy && (jc.isObject(copy) || (copyIsArray = jc.isArray(copy)))) { 439 | if (copyIsArray) { 440 | copyIsArray = false; 441 | clone = src && jc.isArray(src) ? src : []; 442 | } else { 443 | clone = src && jc.isObject(src) ? src : {}; 444 | } 445 | // 递归 446 | target[name] = jc.extend(deep, clone, copy); 447 | } else if (copy !== undefined) { 448 | target[name] = copy; 449 | } 450 | } 451 | } 452 | } 453 | return target; 454 | }; 455 | 456 | /** 457 | * 去重 458 | * 我也知道es6有个更好的处理,将来有空我会试着用es6重构整个项目。当然,欢迎直接PR。 459 | * @public 460 | * @param {array} data 数据 461 | */ 462 | jc.unique = function (data) { 463 | if (!data || data.length == 0) return data; 464 | var newObj = {}, newArr = []; 465 | var arr = jc.map(data, function (d) { 466 | var item = JSON.stringify(d); 467 | newObj[item] = d; 468 | }); 469 | jc.forIn(newObj, function (key, row) { 470 | newArr.push(row); 471 | }); 472 | return newArr; 473 | } 474 | 475 | 476 | jc.spaceFix = function (data, set) { 477 | if (!data || data.length == 0) return data; 478 | var fix = []; 479 | // 补起点 480 | if (data[0][set.key] - set.start > 0) { 481 | var obj = {}; 482 | obj[set.key] = set.start; 483 | jc.map(set.zeroFill, function (d) { 484 | obj[d] = 0; 485 | }) 486 | data.unshift(obj); 487 | } 488 | // 补结束点 489 | if (data[data.length - 1][set.key] < set.end) { 490 | var obj = {}; 491 | obj[set.key] = set.end; 492 | jc.map(set.zeroFill, function (d) { 493 | obj[d] = 0; 494 | }); 495 | data.push(obj); 496 | } 497 | 498 | for (var i = 1, j = 0, len = data.length; i < len; i++) { 499 | if (i > 10000) break; 500 | var space = data[i][set.key] - data[i - 1][set.key]; 501 | // 补零 502 | if (space <= set.space) { 503 | fix.push(data[i]) 504 | } else { 505 | var t = set.space; 506 | for (var k = 0, l = space / set.space; k < (l - 1); k++) { 507 | if (k > 10000) break; 508 | var obj = {}; 509 | obj[set.key] = parseInt(data[i - 1][set.key]) + parseInt(t); 510 | jc.map(set.zeroFill, function (d) { 511 | obj[d] = 0; 512 | }); 513 | fix.push(obj); 514 | t += set.space; 515 | } 516 | fix.push(data[i]); 517 | } 518 | } 519 | fix.unshift(data[0]); 520 | return fix; 521 | } 522 | 523 | /** 524 | * keyArray 525 | * json根据键名生成数组,并存于一个对象内 526 | * 527 | * @public 528 | * @param [obj, obj, ...] data 数据 529 | * @param [keyName, keyName, ...] keyList转数组的键名 530 | */ 531 | jc.keyArray = function (data, keyList) { 532 | if (!data || data.length == 0) return data; 533 | if (jc.isString(keyList)) keyList = [keyList]; 534 | var objList = {} 535 | jc.map(data, function (d, i) { 536 | jc.map(keyList, function (e, j) { 537 | if (!objList[e]) objList[e] = []; 538 | objList[e].push(d[e]); 539 | }) 540 | }) 541 | return objList; 542 | } 543 | 544 | /** 545 | * keyBreak 546 | * json根据键名生成数组,并存于一个对象内 547 | * 548 | * @public 549 | * @param [obj, obj, ...] data 数据 550 | * @param {} option拆分数据配置项 551 | */ 552 | jc.keyBreak = function (data, option) { 553 | if (!data || data.length == 0) return data; 554 | var arr = []; 555 | var key = option.key; 556 | var value = option.value; 557 | jc.map(data, function (d) { 558 | jc.map(option.break, function (e) { 559 | var obj = {}; 560 | obj[key] = e; 561 | obj[value] = d[e]; 562 | jc.map(option.retain, function (f) { 563 | obj[f] = d[f]; 564 | }); 565 | arr.push(obj); 566 | }); 567 | }) 568 | return arr; 569 | }; 570 | 571 | /** 572 | * index 573 | * 建唯一索引 574 | * 575 | * @public 576 | * @param [obj, obj, ...] data 数据 577 | * @param string key值 578 | */ 579 | jc.index = function (data, key) { 580 | if (!data || data.length == 0) return data; 581 | var obj = {}; 582 | jc.map(data, function (row) { 583 | obj[row[key]] = row; 584 | }); 585 | return obj; 586 | }; 587 | 588 | /** 589 | * compare 590 | * 比较数字大小 591 | * 592 | * @private 593 | * @param num1 数据 594 | * @param num2 数据 595 | * @param type 取值类型 596 | */ 597 | function compare (num1, num2, type) { 598 | var result; 599 | if (jc.isNoVal(num1) || jc.isNoVal(num2)) { 600 | // 存在空值情况下(undefined和null) 601 | result = jc.isNoVal(num1) ? num2 : num1 602 | } else { 603 | type = type == "max" ? (num1 > num2) : (num1 < num2) 604 | result = type ? num1 : num2 605 | } 606 | return result 607 | }; 608 | 609 | /** 610 | * 最大值 611 | * 612 | * @param data 数据 613 | * @param iteratee 处理逻辑,纯数字数组允许为空 614 | */ 615 | jc.max = function (data, iteratee) { 616 | if (!data || data.length == 0) return data; 617 | var result; 618 | // 兼容空值 619 | if (jc.isString(iteratee)) { 620 | var key = iteratee; 621 | iteratee = function (row) { 622 | return row[key] 623 | }; 624 | } else if (jc.isUndefined(iteratee)) { 625 | iteratee = function (row) { 626 | return row 627 | }; 628 | } 629 | jc.map(data, function (row) { 630 | // num1 num2 以防空值出现 631 | var num1 = result ? iteratee(result) : result; 632 | var num2 = row ? iteratee(row) : row; 633 | if (jc.isNoVal(num1) || jc.isNoVal(num2)) { 634 | // 存在空值情况下(undefined和null) 635 | result = jc.isNoVal(num1) ? row : result 636 | } else { 637 | result = num1 > num2 ? result : row 638 | } 639 | }) 640 | return result 641 | }; 642 | 643 | /** 644 | * 最小值 645 | * 646 | * @param data 数据 647 | * @param iteratee 处理逻辑,纯数字数组允许为空 648 | */ 649 | jc.min = function (data, iteratee) { 650 | if (!data || data.length == 0) return data; 651 | var result; 652 | if (jc.isString(iteratee)) { 653 | var key = iteratee; 654 | iteratee = function (row) { 655 | return row[key] 656 | }; 657 | } else if (jc.isUndefined(iteratee)) { 658 | iteratee = function (row) { 659 | return row 660 | }; 661 | } 662 | jc.map(data, function (row) { 663 | // num1 num2 以防空值出现 664 | var num1 = result ? iteratee(result) : result; 665 | var num2 = row ? iteratee(row) : row; 666 | if (jc.isNoVal(num1) || jc.isNoVal(num2)) { 667 | // 存在空值情况下,选择非空,两个都空我就随意了。 668 | result = jc.isNoVal(num1) ? row : result 669 | } else { 670 | result = num1 < num2 ? result : row; 671 | } 672 | }); 673 | return result 674 | }; 675 | 676 | /** 677 | * 分组 678 | * 679 | * @public 680 | * @param {table} val 数据 681 | * @param {object|array|string} key 分组条件 682 | */ 683 | jc.group = jc.groupBy = function (val, key) { 684 | if (!val || val.length == 0) return val; 685 | var groups = {}; 686 | jc.map(val, function (row, i) { 687 | var k = []; 688 | if (jc.isArray(key)) { 689 | jc.map(key, function (a, i) { 690 | if (jc.isFunction(a)) { 691 | k.push(a(row)); 692 | } else { 693 | k.push(row[a]); 694 | } 695 | }) 696 | } else if (jc.isString(key)) { 697 | k.push(row[key]) 698 | } else if (jc.isFunction(key)) { 699 | k = key(row, i); 700 | } 701 | if (!groups[k]) groups[k] = []; 702 | groups[k].push(row); 703 | }) 704 | return groups; 705 | } 706 | 707 | jc.sql = function (query) { 708 | errorCheck(query); 709 | if (query.from.length == 0 || !jc.isArray(query.from)) return []; 710 | var table = query.from; 711 | var whereData, groupData, havingData, selectData, orderData, limitData; 712 | whereData = sqlWhere(table, query.where); 713 | groupData = sqlGroup(whereData, query); 714 | havingData = sqlhaving(groupData, query.having) 715 | selectData = sqlSelect(havingData, query.select); 716 | orderData = sqlOrder(selectData, query.orderBy); 717 | limitData = sqlLimit(orderData, query.limit); 718 | return limitData; 719 | } 720 | 721 | function errorCheck (query) { 722 | if (!query.from) throw new Error("From is not defined", "Error from"); 723 | if (!query.select) throw new Error("Select is not defined", "Error select"); 724 | groupCheck(query); 725 | } 726 | 727 | // 检查group和col之间的关系是否合理 728 | function groupCheck (query) { 729 | var select = query.select, groupBy = query.groupBy; 730 | if (!(select.sum || select.avg || select.count || select.max || select.min)) return; 731 | if (select.col && !query.groupBy) return; 732 | var col = selectType("", select.col); 733 | col = nativeValues(col); 734 | var group = []; 735 | var flag = false; 736 | if (jc.isArray(groupBy)) { 737 | jc.map(groupBy, function (a, i) { 738 | group.push(a); 739 | }) 740 | } else if (jc.isString(groupBy)) { 741 | group.push(groupBy) 742 | } else if (jc.isFunction(groupBy)) { 743 | group = [groupBy]; 744 | } 745 | 746 | for (var i = 0, len = col.length; i < len; i++) { 747 | for (var j = 0; j < len; j++) { 748 | if (col[i] == group[j]) { 749 | flag = true; 750 | break; 751 | } else if (jc.isObject(col[i]) && jc.isObject(group[j])) { 752 | if (String(col[i]) === String(group[j])) { 753 | flag = true; 754 | break; 755 | } 756 | } 757 | } 758 | if (!flag) { 759 | throw new Error("groupBy should contain select.col", "Error groupBy"); 760 | } else { 761 | flag = false; 762 | } 763 | } 764 | } 765 | 766 | function sqlWhere (table, where) { 767 | if (!where) return table; 768 | return jc.where(table, where); 769 | } 770 | 771 | function sqlGroup (table, query) { 772 | var flag; 773 | jc.forIn(query.select, function (key, val) { 774 | val = key == "col" ? false : true; 775 | flag = !!val || flag; 776 | }) 777 | // 只有col选项 778 | if (!query.groupBy && !flag) return table; 779 | // 没有group和col 780 | if (!query.groupBy && !query.select.col) return {table: table}; 781 | // 有group 782 | return jc.group(table, query.groupBy); 783 | } 784 | 785 | /** 786 | * having 787 | * 788 | * @private 789 | * @param {table} table 数据 790 | * @param {object} having 组筛选配置 791 | */ 792 | function sqlhaving (table, having) { 793 | if (!having) return table; 794 | var operation = { 795 | sumObj: {}, 796 | avgObj: {}, 797 | maxObj: {}, 798 | minObj: {}, 799 | countObj: {} 800 | }; 801 | var havingData = {}; 802 | jc.forIn(having, function (key, val) { 803 | var reg = /[1-9a-zA-z_\$\@]+/g; 804 | var splitKey = key.split("_"); 805 | // 首个_前面部分为聚合运算类型 806 | var type = splitKey.shift(); 807 | var formula = splitKey.join("_"); 808 | // 根据运算符号拆分出字段,在前后加上对应内容变成row["key"]这样的字符串 809 | formula = formula.replace(reg, function (match) { 810 | return "row['" + match + "']"; 811 | }); 812 | 813 | 814 | // 下面都是字符串拼成算式,通过new Function执行,返回后计算返回是true的分组, 自己review都差点忘记,尴尬! 815 | // 生成对应的操作operation,都是一个function, 相当于每个分组都进行了一次sql查询了 816 | switch (type) { 817 | case "sum": 818 | operation.sumObj[key] = function (row) { 819 | return (new Function("row", "return " + formula))(row); 820 | }; 821 | break; 822 | case "avg": 823 | operation.avgObj[key] = function (row) { 824 | return (new Function("row", "return " + formula))(row); 825 | }; 826 | break; 827 | case "max": 828 | operation.maxObj[key] = function (row) { 829 | return (new Function("row", "return " + formula))(row); 830 | }; 831 | break; 832 | case "min": 833 | operation.minObj[key] = function (row) { 834 | return (new Function("row", "return " + formula))(row); 835 | }; 836 | break; 837 | case "count": 838 | operation.countObj[key] = function (row) { 839 | return (new Function("row", "return " + formula))(row); 840 | }; 841 | break; 842 | } 843 | }) 844 | jc.forIn(table, function (groupKey, groupItem, obj, i) { 845 | var row = groupCal(groupItem, operation); 846 | var flag; 847 | for (var key in having) { 848 | foo = new Function("return " + row[key] + having[key]); 849 | flag = foo(); 850 | if (!flag) break; 851 | } 852 | if (flag) havingData[groupKey] = groupItem; 853 | }); 854 | return havingData; 855 | } 856 | 857 | // select部分代码开始 858 | 859 | /** 860 | * select的查询计算方式 861 | * 生成计算键对象 862 | * 863 | * @private 864 | * @param {table} table 数据 865 | * @param {object} select 查询计算配置 866 | * @param {object|array|string|undefined} select.col 列查询计算配置 867 | * @param {object|array|string|undefined} select.sum 列总和查询计算配置 868 | * @param {object|array|string|undefined} select.avg 列平均数查询计算配置 869 | * @param {object|array|string|undefined} select.max 列最大值查询计算配置 870 | * @param {object|array|string|undefined} select.min 列最小值查询计算配置 871 | * @param {object|array|string|undefined} select.count 列计数查询计算配置 872 | */ 873 | function sqlSelect (table, select) { 874 | if (!select) throw new Error("Select is not defined", "Error select"); 875 | var selectData = []; 876 | var operation = {}; 877 | if (select.col) operation.colObj = selectType("", select.col); 878 | if (select.sum) operation.sumObj = selectType("sum_", select.sum); 879 | if (select.avg) operation.avgObj = selectType("avg_", select.avg); 880 | if (select.max) operation.maxObj = selectType("max_", select.max); 881 | if (select.min) operation.minObj = selectType("min_", select.min); 882 | if (select.count) operation.countObj = selectType("count_", select.count); 883 | if (!jc.isArray(table)) { 884 | jc.forIn(table, function (groupKey, groupItem, obj, i) { 885 | var row = groupCal(groupItem, operation); 886 | selectData.push(row); 887 | }); 888 | } else { 889 | jc.map(table, function (groupItem, i) { 890 | var row = groupCal([groupItem], operation); 891 | selectData.push(row); 892 | }); 893 | } 894 | return selectData; 895 | } 896 | 897 | 898 | /** 899 | * select的查询计算方式 900 | * 生成计算键对象 901 | * 902 | * @private 903 | * @param {string} prefix 前缀 904 | * @param {object|array|string} option 查询计算配置 905 | */ 906 | function selectType (prefix, option) { 907 | var obj = {}; 908 | if (jc.isArray(option)) { 909 | jc.map(option, function (key) { 910 | obj[prefix + key] = key; 911 | }) 912 | } else if (jc.isObject(option)) { 913 | jc.forIn(option, function (key, val) { 914 | obj[key] = val; 915 | }) 916 | } else if (jc.isString(option)) { 917 | obj[prefix + option] = option; 918 | } 919 | return obj 920 | } 921 | 922 | /** 923 | * 每个分组计算 924 | * 925 | * @private 926 | * @param {groupItem} groupItem 分组过后的一组数据 927 | * @param {object} operation 选择列的对象键信息,储存各种操作的信息 928 | */ 929 | function groupCal (groupItem, operation) { 930 | var newRow = {}; 931 | var itemLength = groupItem.length, validLength = {}; 932 | jc.map(groupItem, function (row) { 933 | if (!jc.isObjEmpty(operation.colObj)) { 934 | jc.forIn(operation.colObj, function (key, val) { 935 | var rowVal = jc.isFunction(val) ? val(row) : row[val]; 936 | newRow[key] = rowVal; 937 | }) 938 | } 939 | 940 | if (!jc.isObjEmpty(operation.sumObj)) { 941 | jc.forIn(operation.sumObj, function (key, val) { 942 | var rowVal = jc.isFunction(val) ? val(row) : row[val]; 943 | newRow[key] ? newRow[key] += rowVal || 0 : newRow[key] = rowVal || 0; 944 | }) 945 | } 946 | 947 | if (!jc.isObjEmpty(operation.avgObj)) { 948 | jc.forIn(operation.avgObj, function (key, val) { 949 | if (!validLength[key]) validLength[key] = 0; 950 | var rowVal = jc.isFunction(val) ? val(row) : row[val]; 951 | newRow[key] ? newRow[key] += rowVal || 0 : newRow[key] = rowVal || 0; 952 | if (rowVal || rowVal == 0) validLength[key]++; 953 | }) 954 | } 955 | 956 | if (!jc.isObjEmpty(operation.maxObj)) { 957 | jc.forIn(operation.maxObj, function (key, val) { 958 | var rowVal = jc.isFunction(val) ? val(row) : row[val]; 959 | newRow[key] = compare(newRow[key], rowVal, "max"); 960 | }) 961 | } 962 | 963 | if (!jc.isObjEmpty(operation.minObj)) { 964 | jc.forIn(operation.minObj, function (key, val) { 965 | var rowVal = jc.isFunction(val) ? val(row) : row[val]; 966 | newRow[key] = compare(newRow[key], rowVal, "min"); 967 | }) 968 | } 969 | 970 | if (!jc.isObjEmpty(operation.countObj)) { 971 | jc.forIn(operation.countObj, function (key, val) { 972 | var rowVal = jc.isFunction(val) ? val(row) : row[val]; 973 | if (jc.isUndefined(newRow[key])) newRow[key] = 0; 974 | switch (key) { 975 | case val == "*" ? key : "count_*": 976 | newRow[key] = itemLength; 977 | break; 978 | default : 979 | if (!jc.isUndefined(rowVal) && rowVal != null) { 980 | newRow[key]++ 981 | } 982 | } 983 | }) 984 | } 985 | }) 986 | 987 | jc.forIn(operation.avgObj, function (key, val) { 988 | newRow[key] = newRow[key] / validLength[key]; 989 | }) 990 | return newRow; 991 | } 992 | // select部分代码结束 993 | 994 | 995 | 996 | /** 997 | * 排序 998 | * 999 | * @private 1000 | * @param {array} table 数据 1001 | * @param {object|function} order 排序配置 1002 | ***/ 1003 | function sqlOrder (table, order) { 1004 | if (!order) return table; 1005 | if (jc.isObject(order) && !jc.isFunction(order)) { 1006 | var keys = nativeKeys(order); 1007 | var vals = nativeValues(order); 1008 | table.sort(function (left, right) { 1009 | var len = keys.length, key, val; 1010 | for (var i = 0; i < len; i++) { 1011 | key = keys[i]; 1012 | val = vals[i]; 1013 | if (left[key] !== right[key]) break; 1014 | } 1015 | if (left[key] !== right[key]) { 1016 | if (left[key] > right[key] || right[key] === void 0) return val === "desc" || val === "DESC" ? -1 : 1; 1017 | if (left[key] < right[key] || left[key] === void 0) return val === "desc" || val === "DESC" ? 1 : -1; 1018 | } 1019 | return val === "desc" || val === "DESC" ? right[key] - left[key] : left[key] - right[key]; 1020 | }) 1021 | } else if (jc.isFunction(order)) { 1022 | table.sort(order) 1023 | } 1024 | return table; 1025 | } 1026 | // orderBy 部分代码结束 1027 | 1028 | /** 1029 | * 检索记录行 1030 | * 1031 | * @private 1032 | * @param {array} table 数据 1033 | * @param {number|array} limit 检索行 1034 | */ 1035 | function sqlLimit (table, limit) { 1036 | if (!limit && limit != 0) return table; 1037 | var limitData = []; 1038 | var len = table.length; 1039 | var i = 0; 1040 | if (jc.isNumber(limit)) { 1041 | len = limit > len ? len : limit; 1042 | } else if (jc.isArray(limit)) { 1043 | if (limit[0] > len) { 1044 | return []; 1045 | } else if (limit[0] < len) { 1046 | i = limit[0]; 1047 | } 1048 | len = limit[0] + limit[1] < len ? limit[0] + limit[1] : len; 1049 | } 1050 | for (; i < len; i++) { 1051 | limitData.push(table[i]); 1052 | } 1053 | return limitData; 1054 | } 1055 | 1056 | // 类型判断 1057 | 1058 | // 空对象 1059 | jc.isObjEmpty = function (obj) { 1060 | for (var name in obj) { 1061 | return false; 1062 | } 1063 | return true; 1064 | }; 1065 | 1066 | // 值是undefined, NaN或者null 1067 | jc.isNoVal = function (val) { 1068 | return jc.isUndefined(val) || val == null || val != val 1069 | }; 1070 | 1071 | // 类型判断, 偷懒引用了underscore的代码. 1072 | jc.isArray = nativeIsArray || function (val) { 1073 | return toString.call(val) === '[object Array]'; 1074 | }; 1075 | 1076 | jc.isObject = function (val) { 1077 | var type = typeof val; 1078 | return type === 'object' && !jc.isArray(val) && !!val; 1079 | }; 1080 | 1081 | jc.map(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp', 'Error', 'Symbol', 'Map', 'WeakMap', 'Set', 'WeakSet'], function (name) { 1082 | jc['is' + name] = function (val) { 1083 | return {}.toString.call(val) === '[object ' + name + ']'; 1084 | }; 1085 | }); 1086 | 1087 | // 判断undefined 1088 | jc.isUndefined = function (val) { 1089 | return val === void 0; 1090 | }; 1091 | 1092 | // 对AMD支持的一些处理 1093 | if (typeof define == 'function' && define.amd) { 1094 | define('jc', [], function () { 1095 | return jc; 1096 | }); 1097 | } 1098 | }()); -------------------------------------------------------------------------------- /JCalculator.min.js: -------------------------------------------------------------------------------- 1 | !function(){var n="object"==typeof self&&self.self===self&&self||"object"==typeof global&&global.global===global&&global||this,r=(n.jc,Array.prototype),e=Object.prototype,t=(r.push,r.slice,e.toString),i=(e.hasOwnProperty,Array.isArray),o=Object.keys,f=Object.values?Object.values:function(r){return o(r).map(function(n){return r[n]})},s=(Object.create,function(n){return n instanceof s?n:this instanceof s?void(this._wrapped=n):new s(n)});function c(n,r,e){return s.isNoVal(n)||s.isNoVal(r)?s.isNoVal(n)?r:n:(e="max"==e?rr[e]||void 0===r[e])return"desc"===t||"DESC"===t?-1:1;if(n[e]t)return[];r[0] 2 36 | }, 37 | groupBy: "name" 38 | }) 39 | console.log(test); 40 | /* output: [ 41 | {"name":"apple","sum_sell":21}, 42 | {"name":"banana","sum_sell":9} 43 | ]*/ 44 | ``` -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require("gulp"); 2 | var eslint = require("gulp-eslint"); 3 | var uglify = require('gulp-uglify'); 4 | var rename = require('gulp-rename'); 5 | 6 | gulp.task('script', function () { 7 | return gulp.src('./JCalculator.js') // 指明源文件路径、并进行文件匹配 8 | .pipe(uglify({})) // 使用uglify进行压缩,并保留部分注释 9 | .pipe(rename('JCalculator.min.js')) 10 | .pipe(gulp.dest('./')); // 输出路径 11 | }); 12 | 13 | gulp.task("lint", function () { 14 | // ESLint ignores files with "node_modules" paths. 15 | // So, it"s best to have gulp ignore the directory as well. 16 | // Also, Be sure to return the stream from the task; 17 | // Otherwise, the task may end before the stream has finished. 18 | console.log("\n\n\nstart lint"); 19 | return gulp.src(["**/JCalculator.js", "!node_modules/**"]) 20 | // eslint() attaches the lint output to the "eslint" property 21 | // of the file object so it can be used by other modules. 22 | .pipe(eslint({ 23 | rules:{ 24 | "no-dupe-args":1, 25 | "use-isnan":1, 26 | "curly":[1, "multi-line"], 27 | // "no-useless-escape":1, 28 | "no-unused-vars":1, 29 | "array-bracket-spacing":1, 30 | "block-spacing":1, 31 | "brace-style":1, 32 | "comma-style":1, 33 | "no-unused-vars":0, 34 | "computed-property-spacing":1, 35 | "indent": ["error", 2], // 缩进控制 2空格 36 | "no-mixed-spaces-and-tabs": "error", // 禁止使用 空格 和 tab 混合缩进 37 | "no-regex-spaces": "error", // 禁止正则表达式字面量中出现多个空格 38 | "no-multi-spaces": "error", // 禁止出现多个空格而且不是用来作缩进的 39 | "array-bracket-spacing": ["error", "never"], // 数组紧贴括号部分不允许包含空格。 40 | "object-curly-spacing": ["error", "never"], // 对象紧贴花括号部分不允许包含空格。 41 | "block-spacing": ["error", "never"], // 单行代码块中紧贴括号部分不允许包含空格。 42 | "comma-spacing": ["error", {"before": false, "after": true}], // 在变量声明、数组字面量、对象字面量、函数参数 和 序列中禁止在逗号前使用空格,要求在逗号后使用一个或多个空格 43 | "semi-spacing": ["error", {"before": false, "after": true}], // 禁止分号周围的空格 44 | "computed-property-spacing": ["error", "never"], // 禁止括号和其内部值之间的空格 45 | "keyword-spacing": ["error", {"before": true, "after": true}], // 该规则强制关键字和类似关键字的符号周围空格的一致性:as、break、case、catch、class、const、continue、debugger、default、delete、do、else、export、extends、finally、for、from、function、get、if、import、in、instanceof、let、new、of、return、set、static、super、switch、this、throw、try、typeof、var、void、while、with 和 yield。 46 | "no-trailing-spaces": "error", // 禁用行尾空格 47 | "no-spaced-func": "error", // 禁止 function 标识符和圆括号之间有空格 48 | "space-before-function-paren": "error", // 禁止函数圆括号之前有一个空格 49 | "space-before-blocks": ["error", "always"], // 禁止语句块之前的空格 50 | "space-in-parens": ["error", "never"], // 禁止圆括号内的空格 51 | "space-infix-ops": ["error", {"int32Hint": false}], // 要求中缀操作符周围有空格,设置 int32Hint 选项为 true (默认 false) 允许 a|0 不带空格。 52 | "space-unary-ops": "error", // 要求或禁止在一元操作符之前或之后存在空格,new、delete、typeof、void、yield要求有空格,-、+、--、++、!、!!要求无空格。 53 | "spaced-comment": ["error", "always"], // 要求在注释前有空白 54 | "arrow-spacing": "error", // 要求箭头函数的箭头之前和之后有空格 55 | "generator-star-spacing": ["error", {"before": false, "after": true}], // 强制 generator 函数中 * 号前有空格,后无空格。 56 | "yield-star-spacing": ["error", {"before": true, "after": false}], // 强制 yield* 表达式中 * 号前有空格,后无空格。 57 | "no-irregular-whitespace": "error", // 禁止不规则的空白。 58 | "template-curly-spacing": ["error", "never"], // 强制模板字符串中花括号内不能出现空格 59 | "linebreak-style": [1, "windows"], 60 | //"new-cap":1 61 | } 62 | })) 63 | .pipe(eslint.format()) 64 | // To have the process exit with an error code (1) on 65 | // lint error, return the stream and pipe to failAfterError last. 66 | .pipe(eslint.failAfterError()); 67 | }); 68 | 69 | // 监听任务 70 | gulp.task("watch", function () { 71 | gulp.watch(["**/*.js", "!node_modules/**"], ["lint"]); 72 | }); 73 | 74 | gulp.task("default", ["lint", "script"], function () { 75 | console.log("lint successful") 76 | }); -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var jc = require('./JCalculator'); 2 | 3 | 4 | var table = [ 5 | { time: "10月1日", inPerson: 20, outPerson: 1000, region: "广州", fly: 12 }, 6 | { time: "10月1日", inPerson: 13, outPerson: 900, region: "深圳", fly: 2 }, 7 | { time: "10月2日", inPerson: 15, outPerson: 900, region: "广州", fly: null }, 8 | { time: "10月2日", inPerson: 15, outPerson: 1000, region: "深圳", fly: 0 }, 9 | { time: "10月3日", inPerson: 15, outPerson: 100, region: "广州" }, 10 | { time: "10月3日", inPerson: 15, outPerson: 100, region: "深圳" }, 11 | { time: "10月4日", inPerson: 90, outPerson: 60, region: "广州" }, 12 | { time: "10月4日", inPerson: 70, outPerson: 50, region: "深圳" }, 13 | { time: "10月5日", inPerson: 500, outPerson: 39, region: "广州" }, 14 | { time: "10月5日", inPerson: 350, outPerson: 30, region: "深圳" }, 15 | { time: "10月6日", inPerson: 900, outPerson: 15, region: "广州" }, 16 | { time: "10月6日", inPerson: 1000, outPerson: 15, region: "深圳" }, 17 | { time: "10月7日", inPerson: 900, outPerson: 10, region: "广州" }, 18 | { time: "10月7日", inPerson: 1200, outPerson: 7, region: "深圳" } 19 | ] 20 | 21 | var data = jc.sql({ 22 | select: { 23 | count: "fly" 24 | }, 25 | from: table 26 | }); 27 | console.log(data); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jcalculator", 3 | "version": "1.3.2", 4 | "description": "json calculator for javascript", 5 | "main": "JCalculator.min.js", 6 | "scripts": { 7 | "test": "istanbul cover ./node_modules/mocha/bin/_mocha" 8 | }, 9 | "keywords": [ 10 | "jcalculator", 11 | "sql for javascript", 12 | "sql\u001b[D\u001b[D\u001b[Djson", 13 | "\u001b[D\u001b[D\u001b[D\u001b[D\u001b[D\u001b[D\u001b[D\u001b[D\u001b[D\u001b[D", 14 | "\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[C\u001b[D\u001b[D\u001b[D\u001b[D", 15 | "calculator" 16 | ], 17 | "author": "Colin Chen/陈立林", 18 | "email": [ 19 | "chenlilin1993@gmail.com", 20 | "245839124@qq.com" 21 | ], 22 | "license": "MIT", 23 | "files": [ 24 | "JCalculator.js", 25 | "JCalculator.min.js" 26 | ], 27 | "dependencies": {}, 28 | "devDependencies": { 29 | "gulp": "^3.9.1", 30 | "gulp-eslint": "^4.0.0", 31 | "gulp-livereload": "^3.8.1", 32 | "gulp-rename": "^1.2.2", 33 | "gulp-uglify": "^3.0.0", 34 | "gulp-webserver": "^0.9.1", 35 | "istanbul": "^0.4.5", 36 | "mocha": "^4.0.1", 37 | "should": "^13.1.3" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /test/JCalculator.test.js: -------------------------------------------------------------------------------- 1 | var jc = require("../JCalculator"); 2 | var should = require("should"); 3 | 4 | var table = [ 5 | { time: "10月1日", inPerson: 20, outPerson: 1000, region: "广州", fly: 12 }, 6 | { time: "10月1日", inPerson: 13, outPerson: 900, region: "深圳", fly: 2 }, 7 | { time: "10月2日", inPerson: 15, outPerson: 900, region: "广州", fly: null }, 8 | { time: "10月2日", inPerson: 15, outPerson: 1000, region: "深圳", fly: 0 }, 9 | { time: "10月3日", inPerson: 15, outPerson: 100, region: "广州" }, 10 | { time: "10月3日", inPerson: 15, outPerson: 100, region: "深圳" }, 11 | { time: "10月4日", inPerson: 90, outPerson: 60, region: "广州" }, 12 | { time: "10月4日", inPerson: 70, outPerson: 50, region: "深圳" }, 13 | { time: "10月5日", inPerson: 500, outPerson: 39, region: "广州" }, 14 | { time: "10月5日", inPerson: 350, outPerson: 30, region: "深圳" }, 15 | { time: "10月6日", inPerson: 900, outPerson: 15, region: "广州" }, 16 | { time: "10月6日", inPerson: 1000, outPerson: 15, region: "深圳" }, 17 | { time: "10月7日", inPerson: 900, outPerson: 10, region: "广州" }, 18 | { time: "10月7日", inPerson: 1200, outPerson: 7, region: "深圳" } 19 | ] 20 | 21 | describe("test/JCalculator.test.js", function () { 22 | it("test count obj type", function () { 23 | var data = jc.sql({ 24 | select: { 25 | count:{ 26 | fly:"fly", 27 | all:"*", 28 | in: function (row) { 29 | return row.inPerson 30 | } 31 | } 32 | }, 33 | from: table 34 | }); 35 | data.should.deepEqual([{ "fly": 3,"all": 14, in: 14 }]); 36 | }); 37 | 38 | it("test count array type", function () { 39 | var data = jc.sql({ 40 | select: { 41 | count: ["fly", "*", "time"] 42 | }, 43 | from: table 44 | }); 45 | data.should.deepEqual([{ "count_fly": 3, "count_*": 14, "count_time": 14 }]); 46 | }); 47 | 48 | it("test sum obj type", function () { 49 | var data = jc.sql({ 50 | select: { 51 | sum: { 52 | fly: "fly", 53 | in: function (row) { 54 | return row.inPerson 55 | } 56 | } 57 | }, 58 | from: table 59 | }); 60 | var inSum=0; 61 | for (var i=0, len=table.length; i1000"} 359 | }); 360 | var json = [ 361 | { time: "10月1日", in: 33 }, 362 | { time: "10月2日", in: 30 }, 363 | { time: "10月6日", in: 1900 }, 364 | { time: "10月7日", in: 2100 } 365 | ]; 366 | data.should.deepEqual(json); 367 | }); 368 | 369 | it("test having count", function () { 370 | var data = jc.sql({ 371 | select: { 372 | col: ["time"], 373 | sum: { 374 | in: "inPerson" 375 | } 376 | }, 377 | from: table, 378 | groupBy: "time", 379 | having: { "count_fly": ">= 2" } 380 | }); 381 | var json = [ 382 | { time: "10月1日", in: 33 } 383 | ]; 384 | data.should.deepEqual(json); 385 | }); 386 | 387 | it("test having max", function () { 388 | var data = jc.sql({ 389 | select: { 390 | col: ["time"], 391 | sum: { 392 | in: "inPerson" 393 | } 394 | }, 395 | from: table, 396 | groupBy: "time", 397 | having: { "max_fly": ">= 2" } 398 | }); 399 | var json = [ 400 | { time: "10月1日", in: 33 } 401 | ]; 402 | data.should.deepEqual(json); 403 | }); 404 | 405 | it("test having min", function () { 406 | var data = jc.sql({ 407 | select: { 408 | col: ["time"], 409 | sum: { 410 | in: "inPerson" 411 | } 412 | }, 413 | from: table, 414 | groupBy: "time", 415 | having: { "min_fly": "== 2" } 416 | }); 417 | var json = [ 418 | { time: "10月1日", in: 33 } 419 | ]; 420 | data.should.deepEqual(json); 421 | }); 422 | 423 | it("test having avg", function () { 424 | var data = jc.sql({ 425 | select: { 426 | col: ["time"], 427 | sum: { 428 | in: "inPerson" 429 | } 430 | }, 431 | from: table, 432 | groupBy: "time", 433 | having: { "avg_fly": "== 7" } 434 | }); 435 | var json = [ 436 | { time: "10月1日", in: 33 } 437 | ]; 438 | data.should.deepEqual(json); 439 | }); 440 | 441 | it("test orderBy ASC", function () { 442 | var data = jc.sql({ 443 | select: { 444 | col: ["inPerson"], 445 | }, 446 | from: table, 447 | orderBy:{ inPerson: "ASC" } 448 | }); 449 | 450 | var json = [ 451 | { inPerson: 13 }, 452 | { inPerson: 15 }, 453 | { inPerson: 15 }, 454 | { inPerson: 15 }, 455 | { inPerson: 15 }, 456 | { inPerson: 20 }, 457 | { inPerson: 70 }, 458 | { inPerson: 90 }, 459 | { inPerson: 350 }, 460 | { inPerson: 500 }, 461 | { inPerson: 900 }, 462 | { inPerson: 900 }, 463 | { inPerson: 1000 }, 464 | { inPerson: 1200 } 465 | ] 466 | data.should.deepEqual(json); 467 | }); 468 | 469 | 470 | it("test orderBy desc", function () { 471 | var data = jc.sql({ 472 | select: { 473 | col: ["inPerson"], 474 | }, 475 | from: table, 476 | orderBy: { inPerson: "desc" } 477 | }); 478 | 479 | var json = [ 480 | { inPerson: 1200 }, 481 | { inPerson: 1000 }, 482 | { inPerson: 900 }, 483 | { inPerson: 900 }, 484 | { inPerson: 500 }, 485 | { inPerson: 350 }, 486 | { inPerson: 90 }, 487 | { inPerson: 70 }, 488 | { inPerson: 20 }, 489 | { inPerson: 15 }, 490 | { inPerson: 15 }, 491 | { inPerson: 15 }, 492 | { inPerson: 15 }, 493 | { inPerson: 13 } 494 | ] 495 | data.should.deepEqual(json); 496 | }); 497 | 498 | it("test orderBy function type", function () { 499 | var data = jc.sql({ 500 | select: { 501 | col: ["inPerson"], 502 | }, 503 | from: table, 504 | orderBy: function (left, right) { 505 | return right.inPerson - left.inPerson 506 | } 507 | }); 508 | 509 | var json = [ 510 | { inPerson: 1200 }, 511 | { inPerson: 1000 }, 512 | { inPerson: 900 }, 513 | { inPerson: 900 }, 514 | { inPerson: 500 }, 515 | { inPerson: 350 }, 516 | { inPerson: 90 }, 517 | { inPerson: 70 }, 518 | { inPerson: 20 }, 519 | { inPerson: 15 }, 520 | { inPerson: 15 }, 521 | { inPerson: 15 }, 522 | { inPerson: 15 }, 523 | { inPerson: 13 } 524 | ] 525 | data.should.deepEqual(json); 526 | }); 527 | 528 | it("test limit number type", function () { 529 | var data = jc.sql({ 530 | select: { 531 | col: ["time", "inPerson"], 532 | }, 533 | from: table, 534 | limit: 2 535 | 536 | }); 537 | 538 | var json = [ 539 | { time: "10月1日", inPerson: 20 }, 540 | { time: "10月1日", inPerson: 13 } 541 | ] 542 | data.should.deepEqual(json); 543 | }); 544 | 545 | it("test limit number > table.length type", function () { 546 | var data = jc.sql({ 547 | select: { 548 | col: ["time", "inPerson"], 549 | }, 550 | from: table, 551 | limit: 20 552 | }); 553 | data.length.should.deepEqual(14); 554 | }); 555 | 556 | it("test limit array type", function () { 557 | var data = jc.sql({ 558 | select: { 559 | col: ["time", "inPerson"], 560 | }, 561 | from: table, 562 | limit: [1,2] 563 | 564 | }); 565 | 566 | var json = [ 567 | { time: "10月1日", inPerson: 13}, 568 | { time: "10月2日", inPerson: 15} 569 | ] 570 | data.should.deepEqual(json); 571 | }); 572 | 573 | it("test limit array type, data.length between limit[0] and limit[1]", function () { 574 | var data = jc.sql({ 575 | select: { 576 | col: ["time", "inPerson"], 577 | }, 578 | from: table, 579 | limit: [12, 3] 580 | 581 | }); 582 | 583 | var json = [ 584 | { time: "10月7日", inPerson: 900}, 585 | { time: "10月7日", inPerson: 1200} 586 | ] 587 | data.should.deepEqual(json); 588 | }); 589 | it("test limit over", function () { 590 | var data = jc.sql({ 591 | select: { 592 | col: ["time", "inPerson"], 593 | }, 594 | from: table, 595 | limit: [15,20] 596 | 597 | }); 598 | data.should.deepEqual([]); 599 | }); 600 | 601 | it("test miss from", function () { 602 | (function () { 603 | jc.sql({ 604 | select: { 605 | col: ["time", "inPerson"], 606 | } 607 | }) 608 | }).should.throw("From is not defined"); 609 | }); 610 | 611 | it("test error groupBy", function () { 612 | (function () { 613 | var data = jc.sql({ 614 | select: { 615 | col:["outPerson"], 616 | sum: ["inPerson"], 617 | }, 618 | from: table, 619 | groupBy:"fly" 620 | }) 621 | }).should.throw("groupBy should contain select.col"); 622 | }); 623 | 624 | it("test miss select", function () { 625 | (function () { 626 | var data = jc.sql({ 627 | from: table, 628 | groupBy: "fly" 629 | }) 630 | }).should.throw("Select is not defined"); 631 | }); 632 | 633 | it("test index", function () { 634 | var table = [ 635 | { key: "1", mac: 10, win: 20 }, 636 | { key: "2", mac: 30, win: 20 }, 637 | { key: "3", mac: 45, win: 20 }, 638 | { key: "4", mac: 20, win: 20 } 639 | ] 640 | var data = jc.index(table, "key"); 641 | data.should.deepEqual({ 642 | "1": { key: "1", mac: 10, win: 20 }, 643 | "2": { key: "2", mac: 30, win: 20 }, 644 | "3": { key: "3", mac: 45, win: 20 }, 645 | "4": { key: "4", mac: 20, win: 20 } 646 | }); 647 | }); 648 | 649 | it("test keyArray", function () { 650 | var table = [ 651 | { time: "10/1", mac: 10, win: 20 }, 652 | { time: "10/1", mac: 30, win: 20 }, 653 | { time: "10/1", mac: 45, win: 20 }, 654 | { time: "10/1", mac: 20, win: 20 } 655 | ] 656 | var data = jc.keyArray(table, ["time", "mac"]); 657 | data.should.deepEqual({ 658 | time: ["10/1", "10/1", "10/1", "10/1"], 659 | mac: [10, 30, 45, 20] 660 | }); 661 | }); 662 | 663 | it("test keyArray,string", function () { 664 | var table = [ 665 | { time: "10/1", mac: 10, win: 20 }, 666 | { time: "10/1", mac: 30, win: 20 }, 667 | { time: "10/1", mac: 45, win: 20 }, 668 | { time: "10/1", mac: 20, win: 20 } 669 | ] 670 | var data = jc.keyArray(table, "time"); 671 | data.should.deepEqual({ 672 | time: ["10/1", "10/1", "10/1", "10/1"] 673 | }); 674 | }); 675 | 676 | it("test isNoVal", function () { 677 | 678 | [jc.isNoVal(null), jc.isNoVal(undefined), jc.isNoVal(NaN), jc.isNoVal(0), jc.isNoVal({})].should.deepEqual([ 679 | true, 680 | true, 681 | true, 682 | false, 683 | false 684 | ]); 685 | }); 686 | 687 | it("test jc.max, arr", function () { 688 | var table = [5, NaN, undefined, null, 1] 689 | var data = jc.max(table); 690 | data.should.deepEqual(5); 691 | }); 692 | 693 | 694 | it("test jc.max, arr-obj", function () { 695 | var table = [ 696 | { time: "10/1", mac: 10, win: 20 }, 697 | { time: "10/1", mac: 30, win: 20 }, 698 | { time: "10/1", mac: 45, win: 20 }, 699 | { time: "10/1", mac: 20, win: 20 } 700 | ] 701 | var data = jc.max(table, function (row) { 702 | return row.mac 703 | }); 704 | [data].should.deepEqual([{ time: "10/1", mac: 45, win: 20 }]); 705 | }); 706 | 707 | it("test jc.max, string type", function () { 708 | var table = [ 709 | { time: "10/1", mac: 10, win: 20 }, 710 | { time: "10/1", mac: 30, win: 20 }, 711 | { time: "10/1", mac: 45, win: 20 }, 712 | { time: "10/1", mac: 20, win: 20 } 713 | ] 714 | var data = jc.max(table, "mac"); 715 | [data].should.deepEqual([{ time: "10/1", mac: 45, win: 20 }]); 716 | }); 717 | 718 | it("test jc.min, arr", function () { 719 | var table = [5, NaN, undefined, null, 1] 720 | var result = jc.min(table); 721 | result.should.deepEqual(1); 722 | }); 723 | 724 | 725 | it("test jc.min, string", function () { 726 | var table = [ 727 | { time: "10/1", mac: 10, win: 20 }, 728 | { time: "10/1", mac: 30, win: 20 }, 729 | { time: "10/1", mac: 45, win: 2 } 730 | ] 731 | var data = jc.min(table, "mac"); 732 | [data].should.deepEqual([{ time: "10/1", mac: 10, win: 20 }]); 733 | }); 734 | 735 | it("test jc.min, arr-obj", function () { 736 | var table = [ 737 | { time: "10/1", mac: 10, win: 20 }, 738 | { time: "10/1", mac: 30, win: 20 }, 739 | { time: "10/1", mac: 45, win: 20 } 740 | ] 741 | var data = jc.min(table, function (row) { 742 | return row.mac 743 | }); 744 | [data].should.deepEqual([{ time: "10/1", mac: 10, win: 20 }]); 745 | }); 746 | 747 | it("test jc.extend,deep", function () { 748 | var obj = { 749 | title: "for test", 750 | array:["name",{ 751 | name: "extend" 752 | }], 753 | obj:{ 754 | name: "obj-test", 755 | other:"other" 756 | } 757 | } 758 | var data = jc.extend(true, obj, { 759 | title: "success", 760 | array: [123], 761 | obj:{ 762 | name:"success" 763 | } 764 | }); 765 | data.should.deepEqual({ 766 | title: "success", 767 | array: [123, { 768 | name: "extend" 769 | }], 770 | obj: { 771 | name: "success", 772 | other: "other" 773 | } 774 | }); 775 | }); 776 | 777 | it("test jc.extend", function () { 778 | var obj = { 779 | title: "for test", 780 | array: ["name", { 781 | name: "extend" 782 | }], 783 | obj: { 784 | name: "obj-test", 785 | other: "other" 786 | } 787 | } 788 | var data = jc.extend(obj, { 789 | title: "success", 790 | array: [123], 791 | obj: { 792 | name: "success" 793 | }, 794 | fn: function (params) { 795 | return params 796 | } 797 | }); 798 | data.should.deepEqual({ 799 | title: "success", 800 | array: [123], 801 | obj: { 802 | name: "success" 803 | }, 804 | fn: function (params) { 805 | return params 806 | } 807 | }); 808 | }); 809 | 810 | it("test jc.where, object type", function () { 811 | var table = [ 812 | { time: "10/1", mac: 10, win: 20 }, 813 | { time: "10/1", mac: 30, win: 20 }, 814 | { time: "10/1", mac: 45, win: 20 }, 815 | { time: "10/2", mac: 30, win: 3 }, 816 | ]; 817 | var data = jc.where(table, { mac: 30 }); 818 | data.should.deepEqual([ 819 | { time: "10/1", mac: 30, win: 20 }, 820 | { time: "10/2", mac: 30, win: 3 }, 821 | ]); 822 | }); 823 | 824 | it("test jc.where, function type", function () { 825 | var table = [ 826 | { time: "10/1", mac: 10, win: 20 }, 827 | { time: "10/1", mac: 30, win: 20 }, 828 | { time: "10/1", mac: 45, win: 20 }, 829 | { time: "10/2", mac: 30, win: 3 }, 830 | ]; 831 | var data = jc.where(table, function(row) { 832 | return row.time == "10/2" && row.mac == 30 833 | }); 834 | data.should.deepEqual([ 835 | { time: "10/2", mac: 30, win: 3 } 836 | ]); 837 | }); 838 | 839 | it("test jc.keyBreak", function () { 840 | var table = [{ time: "10/1", mac: 10, win: 20 }] 841 | var data = jc.keyBreak(table, { 842 | break: ["mac", "win"], 843 | key: "key", 844 | value: "value", 845 | retain: ["time"] 846 | }); 847 | data.should.deepEqual([ 848 | { time: "10/1", key: "mac", value: 10 }, 849 | { time: "10/1", key: "win", value: 20 } 850 | ]); 851 | }); 852 | 853 | it("test jc.unique", function () { 854 | var table = [ 855 | { number: 1, name: "Colin" }, 856 | { number: 1, name: "Colin" }, 857 | { number: 2, name: "Colin" }, 858 | { number: 3, name: "Mr Chen" }, 859 | { number: 3, name: "Mr Chen" } 860 | ] 861 | var data = jc.unique(table); 862 | [ 863 | { number: 1, name: "Colin" }, 864 | { number: 2, name: "Colin" }, 865 | { number: 3, name: "Mr Chen" } 866 | ].should.deepEqual(data); 867 | }); 868 | 869 | it("test jc.spaceFix", function () { 870 | var time = [ 871 | { TIME: 1, IN: 10, OUT: 10 }, 872 | { TIME: 2, IN: 20, OUT: 20 }, 873 | { TIME: 3, IN: 30, OUT: 30 }, 874 | { TIME: 7, IN: 20, OUT: 40 }, 875 | { TIME: 8, IN: 30, OUT: 50 }, 876 | { TIME: 11, IN: 40, OUT: 60 } 877 | ]; 878 | var fix = jc.spaceFix(time, { 879 | key: "TIME", 880 | start: 0, 881 | end: 12, 882 | space: 1, 883 | zeroFill: ["IN", "OUT"] 884 | }); 885 | fix.should.deepEqual([ 886 | { TIME: 0, IN: 0, OUT: 0 }, 887 | { TIME: 1, IN: 10, OUT: 10 }, 888 | { TIME: 2, IN: 20, OUT: 20 }, 889 | { TIME: 3, IN: 30, OUT: 30 }, 890 | { TIME: 4, IN: 0, OUT: 0 }, 891 | { TIME: 5, IN: 0, OUT: 0 }, 892 | { TIME: 6, IN: 0, OUT: 0 }, 893 | { TIME: 7, IN: 20, OUT: 40 }, 894 | { TIME: 8, IN: 30, OUT: 50 }, 895 | { TIME: 9, IN: 0, OUT: 0 }, 896 | { TIME: 10, IN: 0, OUT: 0 }, 897 | { TIME: 11, IN: 40, OUT: 60 }, 898 | { TIME: 12, IN: 0, OUT: 0 } 899 | ]); 900 | }); 901 | 902 | it("test jc.spaceFix, test time", function () { 903 | var time = [ 904 | { TIME: 1514736000, IN: 10, OUT: 10 }, 905 | { TIME: 1514822400, IN: 20, OUT: 20 }, 906 | { TIME: 1514908800, IN: 30, OUT: 30 }, 907 | { TIME: 1515254400, IN: 20, OUT: 40 }, 908 | { TIME: 1515340800, IN: 30, OUT: 50 }, 909 | { TIME: 1515600000, IN: 40, OUT: 60 } 910 | ]; 911 | var fix = jc.spaceFix(time, { 912 | key: "TIME", 913 | start: 1514649600, 914 | end: 1515686400, 915 | space: 86400, 916 | zeroFill: ["IN", "OUT"] 917 | }); 918 | fix.should.deepEqual([ 919 | { TIME: 1514649600, IN: 0, OUT: 0 }, 920 | { TIME: 1514736000, IN: 10, OUT: 10 }, 921 | { TIME: 1514822400, IN: 20, OUT: 20 }, 922 | { TIME: 1514908800, IN: 30, OUT: 30 }, 923 | { TIME: 1514995200, IN: 0, OUT: 0 }, 924 | { TIME: 1515081600, IN: 0, OUT: 0 }, 925 | { TIME: 1515168000, IN: 0, OUT: 0 }, 926 | { TIME: 1515254400, IN: 20, OUT: 40 }, 927 | { TIME: 1515340800, IN: 30, OUT: 50 }, 928 | { TIME: 1515427200, IN: 0, OUT: 0 }, 929 | { TIME: 1515513600, IN: 0, OUT: 0 }, 930 | { TIME: 1515600000, IN: 40, OUT: 60 }, 931 | { TIME: 1515686400, IN: 0, OUT: 0 } 932 | ]); 933 | }); 934 | 935 | it("test jc.sapceFix, empty", function () { 936 | var time = []; 937 | var fix = jc.spaceFix(time, { 938 | key: "TIME", 939 | start: 0, 940 | end: 12, 941 | space: 1, 942 | zeroFill: ["IN", "OUT"] 943 | }); 944 | fix.should.deepEqual([]); 945 | }); 946 | 947 | it("test jc.orderBy", function () { 948 | var table = [ 949 | { TIME: 1, IN: 10, OUT: 10 }, 950 | { TIME: 2, IN: 20, OUT: 20 }, 951 | { TIME: 3, IN: 30, OUT: 30 }, 952 | { TIME: 4, IN: 30, OUT: 40 } 953 | ]; 954 | var order = jc.orderBy(table, {IN:"DESC", OUT:"ASC"}); 955 | order.should.deepEqual([ 956 | { TIME: 3, IN: 30, OUT: 30 }, 957 | { TIME: 4, IN: 30, OUT: 40 }, 958 | { TIME: 2, IN: 20, OUT: 20 }, 959 | { TIME: 1, IN: 10, OUT: 10 } 960 | ]); 961 | }); 962 | 963 | it("test jc.order", function () { 964 | var table = [ 965 | { TIME: 1, IN: 10, OUT: 10 }, 966 | { TIME: 2, IN: 20, OUT: 20 }, 967 | { TIME: 3, IN: 30, OUT: 30 }, 968 | { TIME: 4, IN: 30, OUT: 40 } 969 | ]; 970 | var order = jc.order(table, { IN: "DESC", OUT: "ASC" }); 971 | order.should.deepEqual([ 972 | { TIME: 3, IN: 30, OUT: 30 }, 973 | { TIME: 4, IN: 30, OUT: 40 }, 974 | { TIME: 2, IN: 20, OUT: 20 }, 975 | { TIME: 1, IN: 10, OUT: 10 } 976 | ]); 977 | }); 978 | 979 | it("test jc.limit", function () { 980 | var data = [ 981 | { TIME: 1, IN: 10, OUT: 10 }, 982 | { TIME: 2, IN: 20, OUT: 20 }, 983 | { TIME: 3, IN: 30, OUT: 30 }, 984 | { TIME: 7, IN: 20, OUT: 40 }, 985 | { TIME: 8, IN: 30, OUT: 50 }, 986 | { TIME: 11, IN: 40, OUT: 60 } 987 | ]; 988 | var limit = jc.limit(data, 2); 989 | limit.should.deepEqual([ 990 | { TIME: 1, IN: 10, OUT: 10 }, 991 | { TIME: 2, IN: 20, OUT: 20 } 992 | ]); 993 | }); 994 | 995 | it("test jc.group array type", function () { 996 | var group = jc.group(table, function(row) { 997 | return row.time 998 | }) 999 | group.should.deepEqual({ 1000 | "10月1日": [{ time: "10月1日", inPerson: 20, outPerson: 1000, region: "广州", fly: 12 }, 1001 | { time: "10月1日", inPerson: 13, outPerson: 900, region: "深圳", fly: 2 } 1002 | ], 1003 | "10月2日": [ 1004 | { time: "10月2日", inPerson: 15, outPerson: 900, region: "广州", fly: null }, 1005 | { time: "10月2日", inPerson: 15, outPerson: 1000, region: "深圳", fly: 0 } 1006 | ], 1007 | "10月3日": [ 1008 | { time: "10月3日", inPerson: 15, outPerson: 100, region: "广州" }, 1009 | { time: "10月3日", inPerson: 15, outPerson: 100, region: "深圳" } 1010 | ], 1011 | "10月4日": [ 1012 | { time: "10月4日", inPerson: 90, outPerson: 60, region: "广州" }, 1013 | { time: "10月4日", inPerson: 70, outPerson: 50, region: "深圳" } 1014 | ], 1015 | "10月5日": [ 1016 | { time: "10月5日", inPerson: 500, outPerson: 39, region: "广州" }, 1017 | { time: "10月5日", inPerson: 350, outPerson: 30, region: "深圳" } 1018 | ], 1019 | "10月6日": [ 1020 | { time: "10月6日", inPerson: 900, outPerson: 15, region: "广州" }, 1021 | { time: "10月6日", inPerson: 1000, outPerson: 15, region: "深圳" } 1022 | ], 1023 | "10月7日": [ 1024 | { time: "10月7日", inPerson: 900, outPerson: 10, region: "广州" }, 1025 | { time: "10月7日", inPerson: 1200, outPerson: 7, region: "深圳" } 1026 | ] 1027 | }); 1028 | }); 1029 | 1030 | 1031 | it("test jc.groupBy", function () { 1032 | var data = [ 1033 | { name: "apple", sell: 15, week: 2 }, 1034 | { name: "apple", sell: 5, week: 3 }, 1035 | { name: "apple", sell: 13, week: 4 }, 1036 | { name: "apple", sell: 3, week: 5 }, 1037 | { name: "banana", sell: 4, week: 2 }, 1038 | { name: "banana", sell: 5, week: 3 }, 1039 | { name: "banana", sell: 2, week: 4 }, 1040 | { name: "banana", sell: 2, week: 5 } 1041 | ]; 1042 | 1043 | var group = jc.groupBy(data, "name") 1044 | group.should.deepEqual({ 1045 | apple:[ 1046 | { name: "apple", sell: 15, week: 2 }, 1047 | { name: "apple", sell: 5, week: 3 }, 1048 | { name: "apple", sell: 13, week: 4 }, 1049 | { name: "apple", sell: 3, week: 5 }, 1050 | ], 1051 | banana:[ 1052 | { name: "banana", sell: 4, week: 2 }, 1053 | { name: "banana", sell: 5, week: 3 }, 1054 | { name: "banana", sell: 2, week: 4 }, 1055 | { name: "banana", sell: 2, week: 5 } 1056 | ] 1057 | }); 1058 | }); 1059 | 1060 | it("test jc.map", function () { 1061 | var table; 1062 | var data = jc.map(table, function () { 1063 | 1064 | }); 1065 | data.should.deepEqual([]); 1066 | }); 1067 | 1068 | it("test jc.forIn", function () { 1069 | var table; 1070 | var data = jc.forIn(table, function () { 1071 | return []; 1072 | }); 1073 | data.should.deepEqual({}); 1074 | }); 1075 | }); -------------------------------------------------------------------------------- /test/tree.test.js: -------------------------------------------------------------------------------- 1 | var jc = require("../JCalculator"); 2 | var should = require("should"); 3 | describe("test tree", function () { 4 | it("test jc.treeDic", function () { 5 | var json = [{ "id": 1, "pid": 0, "name": "china", "children": [{ "id": 2, "pid": 1, "name": "guangdong", "children": [{ "id": 3, "pid": 2, "name": "shenzhen" }, { "id": 4, "pid": 2, "name": "guangzhou" }] }] }, { "id": 5, "pid": 0, "name": "USA", "children": [{ "id": 6, "pid": 5, "name": "AK" }] }]; 6 | var data = jc.treeDic(json); 7 | data.should.deepEqual({ 8 | "1": { 9 | "id": 1, 10 | "pid": 0, 11 | "name": "china", 12 | "children": [ 13 | { 14 | "id": 2, 15 | "pid": 1, 16 | "name": "guangdong", 17 | "children": [ 18 | { 19 | "id": 3, 20 | "pid": 2, 21 | "name": "shenzhen" 22 | }, 23 | { 24 | "id": 4, 25 | "pid": 2, 26 | "name": "guangzhou" 27 | } 28 | ] 29 | } 30 | ] 31 | }, 32 | "2": { 33 | "id": 2, 34 | "pid": 1, 35 | "name": "guangdong", 36 | "children": [ 37 | { 38 | "id": 3, 39 | "pid": 2, 40 | "name": "shenzhen" 41 | }, 42 | { 43 | "id": 4, 44 | "pid": 2, 45 | "name": "guangzhou" 46 | } 47 | ] 48 | }, 49 | "3": { 50 | "id": 3, 51 | "pid": 2, 52 | "name": "shenzhen" 53 | }, 54 | "4": { 55 | "id": 4, 56 | "pid": 2, 57 | "name": "guangzhou" 58 | }, 59 | "5": { 60 | "id": 5, 61 | "pid": 0, 62 | "name": "USA", 63 | "children": [ 64 | { 65 | "id": 6, 66 | "pid": 5, 67 | "name": "AK" 68 | } 69 | ] 70 | }, 71 | "6": { 72 | "id": 6, 73 | "pid": 5, 74 | "name": "AK" 75 | } 76 | }); 77 | }); 78 | 79 | it("test jc.treeDic, root is Object", function () { 80 | var json = { 81 | "id": 1, 82 | "pid": 0, 83 | "name": "china", 84 | "children": [ 85 | { 86 | "id": 2, 87 | "pid": 1, 88 | "name": "guangdong", 89 | "children": [ 90 | { 91 | "id": 3, 92 | "pid": 2, 93 | "name": "shenzhen" 94 | }, 95 | { 96 | "id": 4, 97 | "pid": 2, 98 | "name": "guangzhou" 99 | } 100 | ] 101 | } 102 | ] 103 | } 104 | var data = jc.treeDic(json); 105 | data.should.deepEqual({ 106 | "1": { 107 | "id": 1, 108 | "pid": 0, 109 | "name": "china", 110 | "children": [ 111 | { 112 | "id": 2, 113 | "pid": 1, 114 | "name": "guangdong", 115 | "children": [ 116 | { 117 | "id": 3, 118 | "pid": 2, 119 | "name": "shenzhen" 120 | }, 121 | { 122 | "id": 4, 123 | "pid": 2, 124 | "name": "guangzhou" 125 | } 126 | ] 127 | } 128 | ] 129 | }, 130 | "2": { 131 | "id": 2, 132 | "pid": 1, 133 | "name": "guangdong", 134 | "children": [ 135 | { 136 | "id": 3, 137 | "pid": 2, 138 | "name": "shenzhen" 139 | }, 140 | { 141 | "id": 4, 142 | "pid": 2, 143 | "name": "guangzhou" 144 | } 145 | ] 146 | }, 147 | "3": { 148 | "id": 3, 149 | "pid": 2, 150 | "name": "shenzhen" 151 | }, 152 | "4": { 153 | "id": 4, 154 | "pid": 2, 155 | "name": "guangzhou" 156 | } 157 | }); 158 | }); 159 | 160 | it("test jc.tree", function () { 161 | var table = [ 162 | { id: 1, pid: 0, name: "china" }, 163 | { id: 2, pid: 1, name: "guangdong" }, 164 | { id: 3, pid: 2, name: "shenzhen" }, 165 | { id: 4, pid: 2, name: "guangzhou" }, 166 | { id: 5, pid: 0, name: "USA" }, 167 | { id: 6, pid: 5, name: "AK" } 168 | ]; 169 | var data = jc.tree(table, { 170 | root: 0, 171 | id: "id", 172 | parent: "pid", 173 | children: "children" 174 | }); 175 | data.should.deepEqual({ "children": [{ "id": 1, "pid": 0, "name": "china", "children": [{ "id": 2, "pid": 1, "name": "guangdong", "children": [{ "id": 3, "pid": 2, "name": "shenzhen" }, { "id": 4, "pid": 2, "name": "guangzhou" }] }] }, { "id": 5, "pid": 0, "name": "USA", "children": [{ "id": 6, "pid": 5, "name": "AK" }] }] }); 176 | }); 177 | 178 | it("test jc.tree, without enter children", function () { 179 | var table = [ 180 | { id: 1, pid: 0, name: "china" }, 181 | { id: 2, pid: 1, name: "guangdong" }, 182 | { id: 3, pid: 2, name: "shenzhen" }, 183 | { id: 4, pid: 2, name: "guangzhou" }, 184 | { id: 5, pid: 0, name: "USA" }, 185 | { id: 6, pid: 5, name: "AK" } 186 | ]; 187 | var data = jc.tree(table, { 188 | root: 0, 189 | id: "id", 190 | parent: "pid" 191 | }); 192 | data.should.deepEqual({ "children": [{ "id": 1, "pid": 0, "name": "china", "children": [{ "id": 2, "pid": 1, "name": "guangdong", "children": [{ "id": 3, "pid": 2, "name": "shenzhen" }, { "id": 4, "pid": 2, "name": "guangzhou" }] }] }, { "id": 5, "pid": 0, "name": "USA", "children": [{ "id": 6, "pid": 5, "name": "AK" }] }] }); 193 | }); 194 | 195 | it("test jc.tree, retain is string", function () { 196 | var table = [ 197 | { id: 1, pid: 0, name: "china", value: 1231 }, 198 | { id: 2, pid: 1, name: "guangdong", value: 4321 }, 199 | { id: 3, pid: 2, name: "shenzhen", value: 461 }, 200 | { id: 4, pid: 2, name: "guangzhou", value: 512 }, 201 | { id: 5, pid: 0, name: "USA", value: 911 }, 202 | { id: 6, pid: 5, name: "AK", value: 654 } 203 | ]; 204 | var data = jc.tree(table, { 205 | root: 0, 206 | id: "id", 207 | parent: "pid", 208 | retain: "name" 209 | }); 210 | data.should.deepEqual({ "children": [{ "id": 1, "pid": 0, "name": "china", "children": [{ "id": 2, "pid": 1, "name": "guangdong", "children": [{ "id": 3, "pid": 2, "name": "shenzhen" }, { "id": 4, "pid": 2, "name": "guangzhou" }] }] }, { "id": 5, "pid": 0, "name": "USA", "children": [{ "id": 6, "pid": 5, "name": "AK" }] }] }); 211 | }); 212 | 213 | it("test jc.tree, retain is array", function () { 214 | var table = [ 215 | { id: 1, pid: 0, name: "china", value: 1231 }, 216 | { id: 2, pid: 1, name: "guangdong", value: 4321 }, 217 | { id: 3, pid: 2, name: "shenzhen", value: 461 }, 218 | { id: 4, pid: 2, name: "guangzhou", value: 512 }, 219 | { id: 5, pid: 0, name: "USA", value: 911 }, 220 | { id: 6, pid: 5, name: "AK", value: 654 } 221 | ]; 222 | var data = jc.tree(table, { 223 | root: 0, 224 | id: "id", 225 | parent: "pid", 226 | retain: ["name", "value"] 227 | }); 228 | data.should.deepEqual({ 229 | "children": [ 230 | { 231 | "id": 1, 232 | "pid": 0, 233 | "name": "china", 234 | "value": 1231, 235 | "children": [ 236 | { 237 | "id": 2, 238 | "pid": 1, 239 | "name": "guangdong", 240 | "value": 4321, 241 | "children": [ 242 | { 243 | "id": 3, 244 | "pid": 2, 245 | "name": "shenzhen", 246 | "value": 461 247 | }, 248 | { 249 | "id": 4, 250 | "pid": 2, 251 | "name": "guangzhou", 252 | "value": 512 253 | } 254 | ] 255 | } 256 | ] 257 | }, 258 | { 259 | "id": 5, 260 | "pid": 0, 261 | "name": "USA", 262 | "value": 911, 263 | "children": [ 264 | { 265 | "id": 6, 266 | "pid": 5, 267 | "name": "AK", 268 | "value": 654 269 | } 270 | ] 271 | } 272 | ] 273 | }); 274 | }); 275 | 276 | it("test jc.tree, has root row", function () { 277 | var table = [ 278 | { id: 0, pid: null, name: "world", value: 1231 }, 279 | { id: 1, pid: 0, name: "china", value: 1231 }, 280 | { id: 2, pid: 1, name: "guangdong", value: 4321 }, 281 | { id: 3, pid: 2, name: "shenzhen", value: 461 }, 282 | { id: 4, pid: 2, name: "guangzhou", value: 512 }, 283 | { id: 5, pid: 0, name: "USA", value: 911 }, 284 | { id: 6, pid: 5, name: "AK", value: 654 } 285 | ]; 286 | var data = jc.tree(table, { 287 | root: 0, 288 | id: "id", 289 | parent: "pid", 290 | retain: { 291 | palce: "name", 292 | value: function (row) { 293 | return "$" + row.value 294 | } 295 | } 296 | }); 297 | data.should.deepEqual({ 298 | "palce": "world", 299 | "value": "$1231", 300 | "id": 0, 301 | "pid": null, 302 | "children": [ 303 | { 304 | "palce": "china", 305 | "value": "$1231", 306 | "id": 1, 307 | "pid": 0, 308 | "children": [ 309 | { 310 | "palce": "guangdong", 311 | "value": "$4321", 312 | "id": 2, 313 | "pid": 1, 314 | "children": [ 315 | { 316 | "palce": "shenzhen", 317 | "value": "$461", 318 | "id": 3, 319 | "pid": 2 320 | }, 321 | { 322 | "palce": "guangzhou", 323 | "value": "$512", 324 | "id": 4, 325 | "pid": 2 326 | } 327 | ] 328 | } 329 | ] 330 | }, 331 | { 332 | "palce": "USA", 333 | "value": "$911", 334 | "id": 5, 335 | "pid": 0, 336 | "children": [ 337 | { 338 | "palce": "AK", 339 | "value": "$654", 340 | "id": 6, 341 | "pid": 5 342 | } 343 | ] 344 | } 345 | ] 346 | }); 347 | }); 348 | 349 | it("test jc.tree, retain is object", function () { 350 | var table = [ 351 | { id: 1, pid: 0, name: "china", value: 1231 }, 352 | { id: 2, pid: 1, name: "guangdong", value: 4321 }, 353 | { id: 3, pid: 2, name: "shenzhen", value: 461 }, 354 | { id: 4, pid: 2, name: "guangzhou", value: 512 }, 355 | { id: 5, pid: 0, name: "USA", value: 911 }, 356 | { id: 6, pid: 5, name: "AK", value: 654 } 357 | ]; 358 | var data = jc.tree(table, { 359 | root: 0, 360 | id: "id", 361 | parent: "pid", 362 | retain: { 363 | palce: "name", 364 | value: function (row) { 365 | return "$" + row.value 366 | } 367 | } 368 | }); 369 | data.should.deepEqual({ 370 | "children": [ 371 | { 372 | "id": 1, 373 | "pid": 0, 374 | "palce": "china", 375 | "value": "$1231", 376 | "children": [ 377 | { 378 | "id": 2, 379 | "pid": 1, 380 | "palce": "guangdong", 381 | "value": "$4321", 382 | "children": [ 383 | { 384 | "id": 3, 385 | "pid": 2, 386 | "palce": "shenzhen", 387 | "value": "$461" 388 | }, 389 | { 390 | "id": 4, 391 | "pid": 2, 392 | "palce": "guangzhou", 393 | "value": "$512" 394 | } 395 | ] 396 | } 397 | ] 398 | }, 399 | { 400 | "id": 5, 401 | "pid": 0, 402 | "palce": "USA", 403 | "value": "$911", 404 | "children": [ 405 | { 406 | "id": 6, 407 | "pid": 5, 408 | "palce": "AK", 409 | "value": "$654" 410 | } 411 | ] 412 | } 413 | ] 414 | }); 415 | }); 416 | 417 | it("test jc.tree, dont change table", function () { 418 | var table = [ 419 | { id: 1, pid: 0, name: "china" }, 420 | { id: 2, pid: 1, name: "guangdong" }, 421 | { id: 3, pid: 2, name: "shenzhen" }, 422 | { id: 4, pid: 2, name: "guangzhou" }, 423 | { id: 5, pid: 0, name: "USA" }, 424 | { id: 6, pid: 5, name: "AK" } 425 | ]; 426 | var data = jc.tree(table, { 427 | root: 0, 428 | id: "id", 429 | parent: "pid", 430 | children: "children" 431 | }); 432 | table.should.deepEqual([ 433 | { id: 1, pid: 0, name: "china" }, 434 | { id: 2, pid: 1, name: "guangdong" }, 435 | { id: 3, pid: 2, name: "shenzhen" }, 436 | { id: 4, pid: 2, name: "guangzhou" }, 437 | { id: 5, pid: 0, name: "USA" }, 438 | { id: 6, pid: 5, name: "AK" } 439 | ]); 440 | }); 441 | 442 | it("test jc.treeFilter", function () { 443 | var json = [{ "id": 1, "pid": 0, "name": "china", "children": [{ "id": 2, "pid": 1, "name": "guangdong", "children": [{ "id": 3, "pid": 2, "name": "shenzhen" }, { "id": 4, "pid": 2, "name": "guangzhou" }] }] }, { "id": 5, "pid": 0, "name": "USA", "children": [{ "id": 6, "pid": 5, "name": "AK" }] }]; 444 | var data = jc.treeFilter(json, { 445 | filter (row) { 446 | return parseInt(row.id) < 3 447 | } 448 | }); 449 | data.should.deepEqual({ 450 | "children": [{ 451 | "id": 1, 452 | "pid": 0, 453 | "name": "china", 454 | "children": [ 455 | { 456 | "id": 2, 457 | "pid": 1, 458 | "name": "guangdong" 459 | } 460 | ] 461 | }] 462 | }); 463 | }); 464 | 465 | 466 | it("test jc.treeFilter, only one children node", function () { 467 | var json = [ 468 | { 469 | "folderId": "1", 470 | "label": "用户目录", 471 | "pid": "0", 472 | "children": [ 473 | { 474 | "folderId": "10089", 475 | "label": "admin", 476 | "pid": "1", 477 | "children": [ 478 | { 479 | "folderId": "10094", 480 | "label": "333111", 481 | "pid": "10089", 482 | "children": null, 483 | }, 484 | { 485 | "folderId": "10116", 486 | "label": "A", 487 | "pid": "10089", 488 | "children": [ 489 | { 490 | "folderId": "10117", 491 | "label": "B", 492 | "pid": "10116", 493 | "children": [ 494 | { 495 | "folderId": "10118", 496 | "label": "C", 497 | "pid": "10117", 498 | } 499 | ] 500 | } 501 | ] 502 | } 503 | ] 504 | } 505 | ] 506 | } 507 | ] 508 | 509 | 510 | var data = jc.treeFilter(json, { 511 | id: "folderId", 512 | filter (row) { 513 | return row.folderId != "10117" 514 | } 515 | }); 516 | data.children.should.deepEqual([ 517 | { 518 | "folderId": "1", 519 | "label": "用户目录", 520 | "pid": "0", 521 | "children": [ 522 | { 523 | "folderId": "10089", 524 | "label": "admin", 525 | "pid": "1", 526 | "children": [ 527 | { 528 | "folderId": "10094", 529 | "label": "333111", 530 | "pid": "10089", 531 | }, 532 | { 533 | "folderId": "10116", 534 | "label": "A", 535 | "pid": "10089" 536 | } 537 | ] 538 | } 539 | ] 540 | } 541 | ]); 542 | }); 543 | 544 | it("test jc.treeMap, root is array", function () { 545 | var json = [{ "id": 1, "pid": 0, "name": "china", "children": [{ "id": 2, "pid": 1, "name": "guangdong", "children": [{ "id": 3, "pid": 2, "name": "shenzhen" }, { "id": 4, "pid": 2, "name": "guangzhou" }] }] }]; 546 | var data = jc.treeMap(json, { 547 | map(row) { 548 | row.test = "test" 549 | return row 550 | } 551 | }); 552 | data.should.deepEqual([{ 553 | "id": 1, 554 | "pid": 0, 555 | "name": "china", 556 | "test": "test", 557 | "children": [ 558 | { 559 | "id": 2, 560 | "pid": 1, 561 | "name": "guangdong", 562 | "test": "test", 563 | "children": [ 564 | { 565 | "id": 3, 566 | "pid": 2, 567 | "name": "shenzhen", 568 | "test": "test", 569 | }, 570 | { 571 | "id": 4, 572 | "pid": 2, 573 | "name": "guangzhou", 574 | "test": "test", 575 | } 576 | ] 577 | } 578 | ] 579 | }]); 580 | }); 581 | 582 | it("test jc.treeMap, root is object", function () { 583 | var json = { 584 | "id": 1, 585 | "pid": 0, 586 | "name": "china", 587 | "children": [ 588 | { 589 | "id": 2, 590 | "pid": 1, 591 | "name": "guangdong", 592 | "children": [ 593 | { 594 | "id": 3, 595 | "pid": 2, 596 | "name": "shenzhen" 597 | }, 598 | { 599 | "id": 4, 600 | "pid": 2, 601 | "name": "guangzhou" 602 | } 603 | ] 604 | } 605 | ] 606 | }; 607 | var data = jc.treeMap(json, { 608 | map(row) { 609 | row.test = "test" 610 | return row 611 | } 612 | }); 613 | data.should.deepEqual({ 614 | "id": 1, 615 | "pid": 0, 616 | "name": "china", 617 | "test": "test", 618 | "children": [ 619 | { 620 | "id": 2, 621 | "pid": 1, 622 | "name": "guangdong", 623 | "test": "test", 624 | "children": [ 625 | { 626 | "id": 3, 627 | "pid": 2, 628 | "name": "shenzhen", 629 | "test": "test", 630 | }, 631 | { 632 | "id": 4, 633 | "pid": 2, 634 | "name": "guangzhou", 635 | "test": "test", 636 | } 637 | ] 638 | } 639 | ] 640 | }); 641 | }); 642 | 643 | it("test jc.treeSearch, root is object and search is object", function () { 644 | var json = { 645 | "id": 1, 646 | "pid": 0, 647 | "name": "china", 648 | "children": [ 649 | { 650 | "id": 2, 651 | "pid": 1, 652 | "name": "guangdong", 653 | "children": [ 654 | { 655 | "id": 3, 656 | "pid": 2, 657 | "name": "shenzhen" 658 | }, 659 | { 660 | "id": 4, 661 | "pid": 2, 662 | "name": "guangzhou" 663 | } 664 | ] 665 | } 666 | ] 667 | }; 668 | var data = jc.treeSearch(json, { 669 | search: {id: 4}, 670 | }); 671 | data.should.deepEqual([{ 672 | "id": 4, 673 | "pid": 2, 674 | "name": "guangzhou" 675 | }]); 676 | }); 677 | 678 | it("test jc.treeSearch, search is function", function () { 679 | var json = [{ 680 | "id": 1, 681 | "pid": 0, 682 | "name": "china", 683 | "children": [ 684 | { 685 | "id": 2, 686 | "pid": 1, 687 | "name": "guangdong", 688 | "children": [ 689 | { 690 | "id": 3, 691 | "pid": 2, 692 | "name": "shenzhen" 693 | }, 694 | { 695 | "id": 4, 696 | "pid": 2, 697 | "name": "guangzhou" 698 | } 699 | ] 700 | } 701 | ] 702 | }]; 703 | var data = jc.treeSearch(json, { 704 | search: function (row) { 705 | return row.id >= 2 706 | } 707 | }); 708 | data.should.deepEqual([{ 709 | "id": 2, 710 | "pid": 1, 711 | "name": "guangdong", 712 | "children": [ 713 | { 714 | "id": 3, 715 | "pid": 2, 716 | "name": "shenzhen" 717 | }, 718 | { 719 | "id": 4, 720 | "pid": 2, 721 | "name": "guangzhou" 722 | } 723 | ]}, 724 | { 725 | "id": 3, 726 | "pid": 2, 727 | "name": "shenzhen" 728 | }, 729 | { 730 | "id": 4, 731 | "pid": 2, 732 | "name": "guangzhou" 733 | }]); 734 | }); 735 | 736 | it("test jc.treePath", function () { 737 | var json = [{ 738 | "id": 1, 739 | "pid": 0, 740 | "name": "china", 741 | "children": [ 742 | { 743 | "id": 2, 744 | "pid": 1, 745 | "name": "guangdong", 746 | "children": [ 747 | { 748 | "id": 3, 749 | "pid": 2, 750 | "name": "shenzhen" 751 | }, 752 | { 753 | "id": 4, 754 | "pid": 2, 755 | "name": "guangzhou" 756 | } 757 | ] 758 | } 759 | ] 760 | }]; 761 | var data = jc.treePath(json, { 762 | path: 4 763 | }); 764 | data.should.deepEqual([ 765 | { 766 | "id": 1, 767 | "pid": 0, 768 | "name": "china", 769 | "children": [ 770 | { 771 | "id": 2, 772 | "pid": 1, 773 | "name": "guangdong", 774 | "children": [ 775 | { 776 | "id": 3, 777 | "pid": 2, 778 | "name": "shenzhen" 779 | }, 780 | { 781 | "id": 4, 782 | "pid": 2, 783 | "name": "guangzhou" 784 | } 785 | ] 786 | } 787 | ] 788 | }, 789 | { 790 | "id": 2, 791 | "pid": 1, 792 | "name": "guangdong", 793 | "children": [ 794 | { 795 | "id": 3, 796 | "pid": 2, 797 | "name": "shenzhen" 798 | }, 799 | { 800 | "id": 4, 801 | "pid": 2, 802 | "name": "guangzhou" 803 | } 804 | ] 805 | }, 806 | { 807 | "id": 4, 808 | "pid": 2, 809 | "name": "guangzhou" 810 | } 811 | ]); 812 | }); 813 | 814 | 815 | }) --------------------------------------------------------------------------------