1/** 2 * Bunch of useful filters for angularJS 3 * @version v0.4.7 - 2014-10-03 * @link https://github.com/a8m/angular-filter 4 * @author Ariel Mashraki <[email protected]> 5 * @license MIT License, http://www.opensource.org/licenses/MIT 6 */ 7(function ( window, angular, undefined ) { 8/*jshint globalstrict:true*/ 9'use strict'; 10 11var isDefined = angular.isDefined, 12 isUndefined = angular.isUndefined, 13 isFunction = angular.isFunction, 14 isString = angular.isString, 15 isNumber = angular.isNumber, 16 isObject = angular.isObject, 17 isArray = angular.isArray, 18 forEach = angular.forEach, 19 extend = angular.extend, 20 copy = angular.copy, 21 equals = angular.equals; 22 23 24/** 25 * get an object and return array of values 26 * @param object 27 * @returns {Array} 28 */ 29function toArray(object) { 30 return isArray(object) ? object : 31 Object.keys(object).map(function(key) { 32 return object[key]; 33 }); 34} 35 36/** 37 * 38 * @param value 39 * @returns {boolean} 40 */ 41function isNull(value) { 42 return value === null; 43} 44 45/** 46 * return if object contains partial object 47 * @param partial{object} 48 * @param object{object} 49 * @returns {boolean} 50 */ 51function objectContains(partial, object) { 52 var keys = Object.keys(partial); 53 54 return keys.map(function(el) { 55 return !(!object[el] || (object[el] != partial[el])); 56 }).indexOf(false) == -1; 57 58} 59 60/** 61 * search for approximate pattern in string 62 * @param word 63 * @param pattern 64 * @returns {*} 65 */ 66function hasApproxPattern(word, pattern) { 67 if(pattern === '') 68 return word; 69 70 var index = word.indexOf(pattern.charAt(0)); 71 72 if(index === -1) 73 return false; 74 75 return hasApproxPattern(word.substr(index+1), pattern.substr(1)) 76} 77 78/** 79 * return the first n element of an array, 80 * if expression provided, is returns as long the expression return truthy 81 * @param array 82 * @param n {number} 83 * @param expression {$parse} 84 * @return array or single object 85 */ 86function getFirstMatches(array, n, expression) { 87 var count = 0; 88 89 return array.filter(function(elm) { 90 var rest = isDefined(expression) ? (count < n && expression(elm)) : count < n; 91 count = rest ? count+1 : count; 92 93 return rest; 94 }); 95} 96/** 97 * Polyfill to ECMA6 String.prototype.contains 98 */ 99if (!String.prototype.contains) { 100 String.prototype.contains = function() { 101 return String.prototype.indexOf.apply(this, arguments) !== -1; 102 }; 103} 104 105/** 106 * 107 * @param num {Number} 108 * @param decimal {Number} 109 * @param $math 110 * @returns {Number} 111 */ 112function convertToDecimal(num, decimal, $math){ 113 return $math.round(num * $math.pow(10,decimal)) / ($math.pow(10,decimal)); 114} 115/** 116 * @ngdoc filter 117 * @name a8m.angular 118 * @kind function 119 * 120 * @description 121 * reference to angular function 122 */ 123 124angular.module('a8m.angular', []) 125 126 .filter('isUndefined', function () { 127 return function (input) { 128 return angular.isUndefined(input); 129 } 130 }) 131 .filter('isDefined', function() { 132 return function (input) { 133 return angular.isDefined(input); 134 } 135 }) 136 .filter('isFunction', function() { 137 return function (input) { 138 return angular.isFunction(input); 139 } 140 }) 141 .filter('isString', function() { 142 return function (input) { 143 return angular.isString(input) 144 } 145 }) 146 .filter('isNumber', function() { 147 return function (input) { 148 return angular.isNumber(input); 149 } 150 }) 151 .filter('isArray', function() { 152 return function (input) { 153 return angular.isArray(input); 154 } 155 }) 156 .filter('isObject', function() { 157 return function (input) { 158 return angular.isObject(input); 159 } 160 }) 161 .filter('isEqual', function() { 162 return function (o1, o2) { 163 return angular.equals(o1, o2); 164 } 165 }); 166 167/** 168 * @ngdoc filter 169 * @name a8m.conditions 170 * @kind function 171 * 172 * @description 173 * reference to math conditions 174 */ 175 angular.module('a8m.conditions', []) 176 177 .filter({ 178 isGreaterThan : isGreaterThanFilter, 179 '>' : isGreaterThanFilter, 180 181 isGreaterThanOrEqualTo : isGreaterThanOrEqualToFilter, 182 '>=' : isGreaterThanOrEqualToFilter, 183 184 isLessThan : isLessThanFilter, 185 '<' : isLessThanFilter, 186 187 isLessThanOrEqualTo : isLessThanOrEqualToFilter, 188 '<=' : isLessThanOrEqualToFilter, 189 190 isEqualTo : isEqualToFilter, 191 '==' : isEqualToFilter, 192 193 isNotEqualTo : isNotEqualToFilter, 194 '!=' : isNotEqualToFilter, 195 196 isIdenticalTo : isIdenticalToFilter, 197 '===' : isIdenticalToFilter, 198 199 isNotIdenticalTo : isNotIdenticalToFilter, 200 '!==' : isNotIdenticalToFilter 201 }); 202 203 function isGreaterThanFilter() { 204 return function (input, check) { 205 return input > check; 206 }; 207 } 208 209 function isGreaterThanOrEqualToFilter() { 210 return function (input, check) { 211 return input >= check; 212 }; 213 } 214 215 function isLessThanFilter() { 216 return function (input, check) { 217 return input < check; 218 }; 219 } 220 221 function isLessThanOrEqualToFilter() { 222 return function (input, check) { 223 return input <= check; 224 }; 225 } 226 227 function isEqualToFilter() { 228 return function (input, check) { 229 return input == check; 230 }; 231 } 232 233 function isNotEqualToFilter() { 234 return function (input, check) { 235 return input != check; 236 }; 237 } 238 239 function isIdenticalToFilter() { 240 return function (input, check) { 241 return input === check; 242 }; 243 } 244 245 function isNotIdenticalToFilter() { 246 return function (input, check) { 247 return input !== check; 248 }; 249 } 250/** 251 * @ngdoc filter 252 * @name isNull 253 * @kind function 254 * 255 * @description 256 * checks if value is null or not 257 * @return Boolean 258 */ 259 260angular.module('a8m.is-null', []) 261 262 .filter('isNull', function () { 263 return function(input) { 264 return isNull(input); 265 } 266 }); 267 268/** 269 * @ngdoc filter 270 * @name after-where 271 * @kind function 272 * 273 * @description 274 * get a collection and properties object, and returns all of the items 275 * in the collection after the first that found with the given properties. 276 * 277 */ 278 279angular.module('a8m.after-where', []) 280 .filter('afterWhere', function() { 281 return function (collection, object) { 282 283 collection = (isObject(collection)) ? 284 toArray(collection) : 285 collection; 286 287 if(!isArray(collection) || isUndefined(object)) 288 return collection; 289 290 var index = collection.map( function( elm ) { 291 return objectContains(object, elm); 292 }).indexOf( true ); 293 294 return collection.slice((index === -1) ? 0 : index); 295 } 296 }); 297 298/** 299 * @ngdoc filter 300 * @name after 301 * @kind function 302 * 303 * @description 304 * get a collection and specified count, and returns all of the items 305 * in the collection after the specified count. 306 * 307 */ 308 309angular.module('a8m.after', []) 310 .filter('after', function() { 311 return function (collection, count) { 312 313 collection = (isObject(collection)) ? 314 toArray(collection) : 315 collection; 316 317 return (isArray(collection)) ? 318 collection.slice(count) : 319 collection; 320 321 } 322 }); 323 324/** 325 * @ngdoc filter 326 * @name before-where 327 * @kind function 328 * 329 * @description 330 * get a collection and properties object, and returns all of the items 331 * in the collection before the first that found with the given properties. 332 * 333 */ 334 335angular.module('a8m.before-where', []) 336 .filter('beforeWhere', function() { 337 return function (collection, object) { 338 339 collection = (isObject(collection)) ? 340 toArray(collection) : 341 collection; 342 343 if(!isArray(collection) || isUndefined(object)) 344 return collection; 345 346 var index = collection.map( function( elm ) { 347 return objectContains(object, elm); 348 }).indexOf( true ); 349 350 return collection.slice(0, (index === -1) ? collection.length : ++index); 351 } 352 }); 353 354/** 355 * @ngdoc filter 356 * @name before 357 * @kind function 358 * 359 * @description 360 * get a collection and specified count, and returns all of the items 361 * in the collection before the specified count. 362 * 363 */ 364 365angular.module('a8m.before', []) 366 .filter('before', function() { 367 return function (collection, count) { 368 369 collection = (isObject(collection)) ? 370 toArray(collection) : 371 collection; 372 373 return (isArray(collection)) ? 374 collection.slice(0, (!count) ? count : --count) : 375 collection; 376 377 } 378 }); 379 380/** 381 * @ngdoc filter 382 * @name concat 383 * @kind function 384 * 385 * @description 386 * get (array/object, object/array) and return merged collection 387 * 388 */ 389 390angular.module('a8m.concat', []) 391 //TODO:unique option ? or use unique filter to filter result 392 .filter('concat', [function () { 393 return function (collection, joined) { 394 395 if (isUndefined(joined)) { 396 return collection; 397 } 398 if (isArray(collection)) { 399 return (isObject(joined)) ? 400 collection.concat(toArray(joined)) : 401 collection.concat(joined); 402 } 403 404 if (isObject(collection)) { 405 var array = toArray(collection); 406 return (isObject(joined)) ?
407 array.concat(toArray(joined)) : 408 array.concat(joined); 409 } 410 return collection; 411 }; 412 } 413]); 414 415/** 416 * @ngdoc filter 417 * @name contains 418 * @kind function 419 * 420 * @description 421 * Checks if given expression is present in one or more object in the collection 422 */ 423 424angular.module('a8m.contains', []) 425 .filter({ 426 contains: ['$parse', containsFilter], 427 some: ['$parse', containsFilter] 428 }); 429 430function containsFilter( $parse ) { 431 return function (collection, expression) { 432 433 collection = (isObject(collection)) ? toArray(collection) : collection; 434 435 if(!isArray(collection) || isUndefined(expression)) { 436 return true; 437 } 438 439 return collection.some( function(elm) { 440 441 return (isObject(elm) || isFunction(expression)) ? 442 $parse(expression)(elm) : 443 elm === expression; 444 445 }); 446 447 } 448 } 449 450/** 451 * @ngdoc filter 452 * @name countBy 453 * @kind function 454 * 455 * @description 456 * Sorts a list into groups and returns a count for the number of objects in each group. 457 */ 458 459angular.module('a8m.count-by', []) 460 461 .filter('countBy', [ '$parse', function ( $parse ) { 462 return function (collection, property) { 463 464 var result = {}, 465 get = $parse(property), 466 prop; 467 468 collection = (isObject(collection)) ? toArray(collection) : collection; 469 470 if(!isArray(collection) || isUndefined(property)) { 471 return collection; 472 } 473 474 collection.forEach( function( elm ) { 475 prop = get(elm); 476 477 if(!result[prop]) { 478 result[prop] = 0; 479 } 480 481 result[prop]++; 482 }); 483 484 return result; 485 } 486 }]); 487 488/** 489 * @ngdoc filter 490 * @name every 491 * @kind function 492 * 493 * @description 494 * Checks if given expression is present in all members in the collection 495 * 496 */ 497 498angular.module('a8m.every', []) 499 .filter('every', ['$parse', function($parse) { 500 return function (collection, expression) { 501 502 collection = (isObject(collection)) ? toArray(collection) : collection; 503 504 if(!isArray(collection) || isUndefined(expression)) { 505 return true; 506 } 507 508 return collection.every( function(elm) { 509 510 return (isObject(elm) || isFunction(expression)) ? 511 $parse(expression)(elm) : 512 elm === expression; 513 }); 514 515 } 516 }]); 517 518/** 519 * @ngdoc filter 520 * @name filterBy 521 * @kind function 522 * 523 * @description 524 * filter by specific properties, avoid the rest 525 */ 526angular.module('a8m.filter-by', []) 527 528 .filter('filterBy', ['$parse', function( $parse ) { 529 return function(collection, properties, search) { 530 531 var comparator; 532 533 search = (isString(search) || isNumber(search)) ? 534 String(search).toLowerCase() : undefined; 535 536 collection = (isObject(collection)) ? toArray(collection) : collection; 537 538 if(!isArray(collection) || isUndefined(search)) { 539 return collection; 540 } 541 542 return collection.filter(function(elm) { 543 544 return properties.some(function(prop) { 545 546 /** 547 * check if there is concatenate properties 548 * example: 549 * object: { first: 'foo', last:'bar' } 550 * filterBy: ['first + last'] => search by full name(i.e 'foo bar') 551 */ 552 if(!~prop.indexOf('+')) { 553 comparator = $parse(prop)(elm) 554 } else { 555 var propList = prop.replace(new RegExp('\\s', 'g'), '').split('+'); 556 comparator = propList.reduce(function(prev, cur, index) { 557 return (index === 1) ? $parse(prev)(elm) + ' ' + $parse(cur)(elm) : 558 prev + ' ' + $parse(cur)(elm); 559 }); 560 } 561 562 return (isString(comparator) || isNumber(comparator)) ? 563 String(comparator).toLowerCase().contains(search) : 564 false; 565 }) 566 567 }); 568 569 } 570 }]); 571 572/** 573 * @ngdoc filter 574 * @name first 575 * @kind function 576 * 577 * @description 578 * Gets the first element or first n elements of an array 579 * if callback is provided, is returns as long the callback return truthy 580 */ 581angular.module('a8m.first', []) 582 583 .filter('first', ['$parse', function( $parse ) { 584 return function(collection) { 585 586 var n, 587 getter, 588 args; 589 590 collection = (isObject(collection)) ? toArray(collection) : 591 collection; 592 593 if(!isArray(collection)) { 594 return collection; 595 } 596 597 args = Array.prototype.slice.call(arguments, 1); 598 n = (isNumber(args[0])) ? args[0] : 1; 599 getter = (!isNumber(args[0])) ? args[0] : (!isNumber(args[1])) ? args[1] : undefined; 600 601 return (args.length) ? getFirstMatches(collection, n,(getter) ? $parse(getter) : getter) : 602 collection[0]; 603 } 604 }]); 605 606/** 607 * @ngdoc filter 608 * @name flatten 609 * @kind function 610 * 611 * @description 612 * Flattens a nested array (the nesting can be to any depth). 613 * If you pass shallow, the array will only be flattened a single level 614 */ 615 616angular.module('a8m.flatten', []) 617 .filter('flatten', function () { 618 return function(collection, shallow) { 619 620 shallow = shallow || false; 621 collection = (isObject(collection)) ? toArray(collection) 622 : collection; 623 624 if(!isArray(collection)) { 625 return collection; 626 } 627 628 return (!shallow) ? flatten(collection, 0) : 629 [].concat.apply([], collection); 630 } 631 }); 632 633/** 634 * flatten nested array (the nesting can be to any depth). 635 * @param array {Array} 636 * @param i {int} 637 * @returns {Array} 638 * @private 639 */ 640function flatten(array, i) { 641 i = i || 0; 642 643 if(i >= array.length) 644 return array; 645 646 if(isArray(array[i])) { 647 return flatten(array.slice(0,i) 648 .concat(array[i], array.slice(i+1)), i); 649 } 650 return flatten(array, i+1); 651} 652 653/** 654 * @ngdoc filter 655 * @name fuzzyByKey 656 * @kind function 657 * 658 * @description 659 * fuzzy string searching by key 660 */ 661 662angular.module('a8m.fuzzy-by', []) 663 .filter('fuzzyBy', ['$parse', function ( $parse ) { 664 return function (collection, property, search, csensitive) { 665 666 var sensitive = csensitive || false, 667 prop, getter; 668 669 collection = (isObject(collection)) ? toArray(collection) : collection; 670 671 if(!isArray(collection) || isUndefined(property) 672 || isUndefined(search)) { 673 return collection; 674 } 675 676 getter = $parse(property); 677 678 return collection.filter(function(elm) { 679 680 prop = getter(elm); 681 if(!isString(prop)) { 682 return false;
683 } 684 685 prop = (sensitive) ? prop : prop.toLowerCase(); 686 search = (sensitive) ? search : search.toLowerCase(); 687 688 return hasApproxPattern(prop, search) !== false 689 }) 690 } 691 692 }]); 693/** 694 * @ngdoc filter 695 * @name fuzzy 696 * @kind function 697 * 698 * @description 699 * fuzzy string searching for array of strings, objects 700 */ 701 702angular.module('a8m.fuzzy', []) 703 .filter('fuzzy', function () { 704 return function (collection, search, csensitive) { 705 706 var sensitive = csensitive || false; 707 708 collection = (isObject(collection)) ? toArray(collection) : collection; 709 710 if(!isArray(collection) || isUndefined(search)) { 711 return collection; 712 } 713 714 search = (sensitive) ? search : search.toLowerCase(); 715 716 return collection.filter(function(elm) { 717 718 if(isString(elm)) { 719 elm = (sensitive) ? elm : elm.toLowerCase(); 720 return hasApproxPattern(elm, search) !== false 721 } 722 723 return (isObject(elm)) ? _hasApproximateKey(elm, search) : false; 724 725 }); 726 727 /** 728 * checks if object has key{string} that match 729 * to fuzzy search pattern 730 * @param object 731 * @param search 732 * @returns {boolean} 733 * @private 734 */ 735 function _hasApproximateKey(object, search) { 736 var properties = Object.keys(object), 737 prop, flag; 738 return 0 < properties.filter(function (elm) { 739 prop = object[elm]; 740 741 //avoid iteration if we found some key that equal[performance] 742 if(flag) return true; 743 744 if (isString(prop)) { 745 prop = (sensitive) ? prop : prop.toLowerCase(); 746 return flag = (hasApproxPattern(prop, search) !== false); 747 } 748 749 return false; 750 751 }).length; 752 } 753 754 } 755 }); 756 757/** 758 * @ngdoc filter 759 * @name groupBy 760 * @kind function 761 * 762 * @description 763 * Create an object composed of keys generated from the result of running each element of a collection, 764 * each key is an array of the elements. 765 */ 766 767angular.module('a8m.group-by', [ 'a8m.filter-watcher' ]) 768 769 .filter('groupBy', [ '$parse', 'filterWatcher', function ( $parse, filterWatcher ) { 770 return function (collection, property) { 771 772 var result, 773 get = $parse(property), 774 prop; 775 776 if(!isObject(collection) || isUndefined(property)) { 777 return collection; 778 } 779 780 //Add collection instance to watch list 781 result = filterWatcher.$watch('groupBy', collection); 782 783 forEach( collection, function( elm ) { 784 prop = get(elm); 785 786 if(!result[prop]) { 787 result[prop] = []; 788 } 789 790 if(result[prop].indexOf( elm ) === -1) { 791 result[prop].push(elm); 792 } 793 794 }); 795 796 //kill instance 797 filterWatcher.$destroy('groupBy', collection); 798 799 return result; 800 } 801 }]); 802 803/** 804 * @ngdoc filter 805 * @name isEmpty 806 * @kind function 807 * 808 * @description 809 * get collection or string and return if it empty 810 */ 811 812angular.module('a8m.is-empty', []) 813 .filter('isEmpty', function () { 814 return function(collection) { 815 return (isObject(collection)) ? 816 !toArray(collection).length : 817 !collection.length; 818 } 819 }); 820 821/** 822 * @ngdoc filter 823 * @name last 824 * @kind function 825 * 826 * @description 827 * Gets the last element or last n elements of an array 828 * if callback is provided, is returns as long the callback return truthy 829 */ 830angular.module('a8m.last', []) 831 832 .filter('last', ['$parse', function( $parse ) { 833 return function(collection) { 834 835 var n, 836 getter, 837 args, 838 //cuz reverse change our src collection 839 //and we don't want side effects 840 reversed = copy(collection); 841 842 reversed = (isObject(reversed)) ? toArray(reversed) : 843 reversed; 844 845 if(!isArray(reversed)) { 846 return reversed; 847 } 848 849 args = Array.prototype.slice.call(arguments, 1); 850 n = (isNumber(args[0])) ? args[0] : 1; 851 getter = (!isNumber(args[0])) ? args[0] : (!isNumber(args[1])) ? args[1] : undefined; 852 853 return (args.length) ? 854 //send reversed collection as arguments, and reverse it back as result 855 getFirstMatches(reversed.reverse(), n,(getter) ? $parse(getter) : getter).reverse() : 856 //get the last element 857 reversed[reversed.length-1]; 858 } 859 }]); 860 861/** 862 * @ngdoc filter 863 * @name map 864 * @kind function 865 * 866 * @description 867 * Returns a new collection of the results of each expression execution. 868 */ 869 870angular.module('a8m.map', []) 871 872 .filter('map', ['$parse', function($parse) { 873 return function (collection, expression) { 874 875 collection = (isObject(collection)) ? 876 toArray(collection) : collection; 877 878 if(!isArray(collection) || isUndefined(expression)) { 879 return collection; 880 } 881 882 return collection.map(function (elm) { 883 884 return $parse(expression)(elm); 885 }); 886 } 887 }]); 888 889/** 890 * @ngdoc filter 891 * @name omit 892 * @kind function 893 * 894 * @description 895 * filter collection by expression 896 */ 897 898angular.module('a8m.omit', []) 899 900 .filter('omit', ['$parse', function($parse) { 901 return function (collection, expression) { 902 903 collection = (isObject(collection)) ? 904 toArray(collection) : collection; 905 906 if(!isArray(collection) || isUndefined(expression)) { 907 return collection; 908 } 909 910 return collection.filter(function (elm) { 911 912 return !($parse(expression)(elm)); 913 }); 914 } 915 }]); 916 917/** 918 * @ngdoc filter 919 * @name omit 920 * @kind function 921 * 922 * @description 923 * filter collection by expression 924 */ 925 926angular.module('a8m.pick', []) 927 928 .filter('pick', ['$parse', function($parse) { 929 return function (collection, expression) { 930 931 collection = (isObject(collection)) ? 932 toArray(collection) : collection; 933 934 if(!isArray(collection) || isUndefined(expression)) { 935 return collection; 936 } 937 938 return collection.filter(function (elm) { 939 940 return $parse(expression)(elm); 941 }); 942 } 943 }]); 944 945/** 946 * @ngdoc filter 947 * @name removeWith 948 * @kind function 949 * 950 * @description 951 * get collection and properties object, and removed elements 952 * with this properties 953 */ 954 955angular.module('a8m.remove-with', []) 956 .filter('removeWith', function() { 957 return function (collection, object) { 958 959 if(isUndefined(object)) { 960 return collection; 961 } 962 collection = (isObject(collection)) ? 963 toArray(collection) : collection; 964 965 return collection.filter(function (elm) { 966 return !objectContains(object, elm); 967 }); 968 } 969 }); 970 971 972/** 973 * @ngdoc filter 974 * @name remove 975 * @kind function 976 * 977 * @description 978 * remove specific members from collection 979 */ 980 981angular.module('a8m.remove', []) 982 983 .filter('remove', function () { 984 return function (collection) { 985 986 collection = (isObject(collection)) ? toArray(collection) : collection; 987 988 var args = Array.prototype.slice.call(arguments, 1); 989 990 if(!isArray(collection)) { 991 return collection; 992 } 993 994 return collection.filter( function( member ) { 995 return !args.some(function(nest) {
996 return equals(nest, member); 997 }) 998 }); 999 1000 } 1001 }); 1002 1003/** 1004 * @ngdoc filter 1005 * @name reverse 1006 * @kind function 1007 * 1008 * @description 1009 * Reverses a string or collection 1010 */ 1011 1012angular.module('a8m.reverse', []) 1013 1014 .filter('reverse',[ function () { 1015 return function (input) { 1016 1017 input = (isObject(input)) ? toArray(input) : input; 1018 1019 if(isString(input)) { 1020 return input.split('').reverse().join(''); 1021 } 1022 1023 return (isArray(input)) ? input.reverse() : input; 1024 } 1025 }]); 1026 1027 1028/** 1029 * @ngdoc filter 1030 * @name searchField 1031 * @kind function 1032 * 1033 * @description 1034 * for each member, join several strings field and add them to 1035 * new field called 'searchField' (use for search filtering) 1036 */ 1037 1038angular.module('a8m.search-field', []) 1039 1040 .filter('searchField', ['$parse', function ($parse) { 1041 return function (collection) { 1042 1043 var get, field; 1044 1045 collection = (isObject(collection)) ? toArray(collection) : collection; 1046 1047 var args = Array.prototype.slice.call(arguments, 1); 1048 1049 if(!isArray(collection) || !args.length) { 1050 return collection; 1051 } 1052 1053 return collection.map(function(member) { 1054 1055 field = args.map(function(field) { 1056 get = $parse(field); 1057 return get(member); 1058 }).join(' '); 1059 1060 return extend(member, { searchField: field }); 1061 }); 1062 } 1063 }]); 1064 1065/** 1066 * @ngdoc filter 1067 * @name toArray 1068 * @kind function 1069 * 1070 * @description 1071 * Convert objects into stable arrays. 1072 * if addKey set to true,the filter also attaches a new property 1073 * $key to the value containing the original key that was used in 1074 * the object we are iterating over to reference the property 1075 */ 1076 1077angular.module('a8m.to-array', []) 1078 1079 .filter('toArray', function() { 1080 return function (collection, addKey) { 1081 1082 if(!isObject(collection)) { 1083 return collection; 1084 } 1085 1086 return (!addKey) ? toArray(collection) : 1087 1088 Object.keys(collection).map(function (key) { 1089 return extend(collection[key], { $key: key }); 1090 }); 1091 } 1092 }); 1093 1094/** 1095 * @ngdoc filter 1096 * @name unique/uniq 1097 * @kind function 1098 * 1099 * @description 1100 * get collection and filter duplicate members 1101 * if uniqueFilter get a property(nested to) as argument it's 1102 * filter by this property as unique identifier 1103 */ 1104 1105angular.module('a8m.unique', []) 1106 .filter({ 1107 unique: ['$parse', uniqFilter], 1108 uniq: ['$parse', uniqFilter] 1109 }); 1110 1111function uniqFilter($parse) { 1112 return function (collection, property) { 1113 1114 collection = (isObject(collection)) ? toArray(collection) : collection; 1115 1116 if (!isArray(collection)) { 1117 return collection; 1118 } 1119 1120 //store all unique identifiers 1121 var uniqueItems = [], 1122 get = $parse(property); 1123 1124 return (isUndefined(property)) ? 1125 //if it's kind of primitive array 1126 collection.filter(function (elm, pos, self) { 1127 return self.indexOf(elm) === pos; 1128 }) : 1129 //else compare with equals 1130 collection.filter(function (elm) { 1131 var prop = get(elm); 1132 if(some(uniqueItems, prop)) { 1133 return false; 1134 } 1135 uniqueItems.push(prop); 1136 return true; 1137 }); 1138 1139 //checked if the unique identifier is already exist 1140 function some(array, member) { 1141 if(isUndefined(member)) { 1142 return false; 1143 } 1144 return array.some(function(el) { 1145 return equals(el, member); 1146 }); 1147 } 1148 1149 } 1150} 1151 1152/** 1153 * @ngdoc filter 1154 * @name where 1155 * @kind function 1156 * 1157 * @description 1158 * of each element in a collection to the given properties object, 1159 * returning an array of all elements that have equivalent property values. 1160 * 1161 */ 1162 1163angular.module('a8m.where', []) 1164 .filter('where', function() { 1165 return function (collection, object) { 1166 1167 if(isUndefined(object)) { 1168 return collection; 1169 } 1170 collection = (isObject(collection)) ? 1171 toArray(collection) : collection; 1172 1173 return collection.filter(function (elm) { 1174 return objectContains(object, elm); 1175 }); 1176 } 1177 }); 1178 1179/** 1180 * @ngdoc filter 1181 * @name xor 1182 * @kind function 1183 * 1184 * @description 1185 * Exclusive or filter by expression 1186 */ 1187 1188angular.module('a8m.xor', []) 1189 1190 .filter('xor', ['$parse', function($parse) { 1191 return function (col1, col2, expression) { 1192 1193 expression = expression || false; 1194 1195 col1 = (isObject(col1)) ? toArray(col1) : col1; 1196 col2 = (isObject(col2)) ? toArray(col2) : col2; 1197 1198 if(!isArray(col1) || !isArray(col2)) return col1; 1199 1200 return col1.concat(col2) 1201 .filter(function(elm) { 1202 return !(some(elm, col1) && some(elm, col2)); 1203 }); 1204 1205 function some(el, col) { 1206 var getter = $parse(expression); 1207 return col.some(function(dElm) { 1208 return expression ? 1209 equals(getter(dElm), getter(el)) : 1210 equals(dElm, el); 1211 }) 1212 } 1213 } 1214 }]); 1215 1216/** 1217 * @ngdoc filter 1218 * @name formatBytes 1219 * @kind function 1220 * 1221 * @description 1222 * Convert bytes into appropriate display 1223 * 1024 bytes => 1 KB 1224 */ 1225 1226angular.module('a8m.math.byteFmt', ['a8m.math']) 1227 1228 .filter('byteFmt', ['$math', function ($math) { 1229 return function (bytes, decimal) { 1230 1231 if(isNumber(decimal) && isFinite(decimal) && decimal%1===0 && decimal >= 0 && 1232 isNumber(bytes) && isFinite(bytes)) { 1233 1234 if(bytes < 1024) { // within 1 KB so B 1235 return convertToDecimal(bytes, decimal, $math) + ' B'; 1236 } else if(bytes < 1048576) { // within 1 MB so KB 1237 return convertToDecimal((bytes / 1024), decimal, $math) + ' KB'; 1238 } else if(bytes < 1073741824){ // within 1 GB so MB 1239 return convertToDecimal((bytes / 1048576), decimal, $math) + ' MB'; 1240 } else { // GB or more 1241 return convertToDecimal((bytes / 1073741824), decimal, $math) + ' GB'; 1242 } 1243 1244 } else { 1245 return "NaN"; 1246 } 1247 } 1248 }]); 1249/** 1250 * @ngdoc filter 1251 * @name degrees 1252 * @kind function 1253 * 1254 * @description 1255 * Convert angle from radians to degrees 1256 * 1257 */ 1258 1259angular.module('a8m.math.degrees', ['a8m.math']) 1260 1261 .filter('degrees', ['$math', function ($math) { 1262 return function (radians, decimal) { 1263 // if decimal is not an integer greater than -1, we cannot do. quit with error "NaN" 1264 // if degrees is not a real number, we cannot do also. quit with error "NaN" 1265 if(isNumber(decimal) && isFinite(decimal) && decimal%1===0 && decimal >= 0 && 1266 isNumber(radians) && isFinite(radians)) { 1267 var degrees = (radians * 180) / $math.PI; 1268 return $math.round(degrees * $math.pow(10,decimal)) / ($math.pow(10,decimal)); 1269 } else { 1270 return "NaN"; 1271 } 1272 } 1273}]); 1274 1275 1276 1277/** 1278 * @ngdoc filter 1279 * @name formatBytes 1280 * @kind function 1281 * 1282 * @description 1283 * Convert bytes into appropriate display 1284 * 1024 kilobytes => 1 MB 1285 */ 1286 1287angular.module('a8m.math.kbFmt', ['a8m.math']) 1288 1289 .filter('kbFmt', ['$math', function ($math) { 1290 return function (bytes, decimal) { 1291 1292 if(isNumber(decimal) && isFinite(decimal) && decimal%1===0 && decimal >= 0 && 1293 isNumber(bytes) && isFinite(bytes)) { 1294 1295 if(bytes < 1024) { // within 1 MB so KB 1296 return convertToDecimal(bytes, decimal, $math) + ' KB'; 1297 } else if(bytes < 1048576) { // within 1 GB so MB 1298 return convertToDecimal((bytes / 1024), decimal, $math) + ' MB'; 1299 } else { 1300 return convertToDecimal((bytes / 1048576), decimal, $math) + ' GB'; 1301 } 1302 1303 } else { 1304 return "NaN"; 1305 } 1306 } 1307}]); 1308/** 1309 * @ngdoc module 1310 * @name math 1311 * @description 1312 * reference to global Math object 1313 */ 1314 1315angular.module('a8m.math', []) 1316 .factory('$math', ['$window', function ($window) { 1317 1318 return $window.Math; 1319 1320 }]); 1321 1322/** 1323 * @ngdoc filter 1324 * @name max 1325 * @kind function 1326 * 1327 * @description 1328 * Math.max 1329 * 1330 */ 1331 1332angular.module('a8m.math.max', ['a8m.math']) 1333 1334 .filter('max', ['$math', function ($math) { 1335 return function (input) { 1336 1337 return (isArray(input)) ? 1338 $math.max.apply($math, input) : 1339 input; 1340 } 1341 1342 }]); 1343 1344/** 1345 * @ngdoc filter 1346 * @name min 1347 * @kind function 1348 * 1349 * @description 1350 * Math.min 1351 * 1352 */ 1353 1354angular.module('a8m.math.min', ['a8m.math']) 1355 1356 .filter('min', ['$math', function ($math) { 1357 return function (input) { 1358 1359 return (isArray(input)) ? 1360 $math.min.apply($math, input) : 1361 input; 1362 } 1363 1364 }]); 1365 1366/** 1367 * @ngdoc filter 1368 * @name Percent 1369 * @kind function 1370 * 1371 * @description 1372 * percentage between two numbers 1373 * 1374 */ 1375 1376angular.module('a8m.math.percent', ['a8m.math']) 1377 1378 .filter('percent', ['$math', '$window', function ($math, $window) { 1379
1380 return function (input, divided, round) { 1381 1382 var divider = (isString(input)) ? $window.Number(input) : input; 1383 divided = divided || 100; 1384 round = round || false; 1385 1386 if (!isNumber(divider) || $window.isNaN(divider)) return input; 1387 1388 return (round) ? $math.round((divider / divided) * 100) : 1389 ((divider / divided) * 100); 1390 } 1391 1392 }]); 1393 1394/** 1395 * @ngdoc filter 1396 * @name toRadians 1397 * @kind function 1398 * 1399 * @description 1400 * Convert angle from degrees to radians 1401 * 1402 */ 1403 1404angular.module('a8m.math.radians', ['a8m.math']) 1405 1406 .filter('radians', ['$math', function ($math) { 1407 return function (degrees, decimal) { 1408 // if decimal is not an integer greater than -1, we cannot do. quit with error "NaN" 1409 // if degrees is not a real number, we cannot do also. quit with error "NaN" 1410 if(isNumber(decimal) && isFinite(decimal) && decimal%1===0 && decimal >= 0 && 1411 isNumber(degrees) && isFinite(degrees)) { 1412 var radians = (degrees * 3.14159265359) / 180; 1413 return $math.round(radians * $math.pow(10,decimal)) / ($math.pow(10,decimal)); 1414 } else { 1415 return "NaN"; 1416 } 1417 } 1418}]); 1419 1420 1421 1422/** 1423 * @ngdoc filter 1424 * @name Radix 1425 * @kind function 1426 * 1427 * @description 1428 * converting decimal numbers to different bases(radix) 1429 */ 1430 1431angular.module('a8m.math.radix', []) 1432 1433 .filter('radix', function () { 1434 1435 return function (input, radix) { 1436 1437 var RANGE = /^[2-9]$|^[1-2]\d$|^3[0-6]$/; 1438 1439 if(!isNumber(input) || !RANGE.test(radix)) { 1440 return input; 1441 } 1442 1443 return input.toString(radix).toUpperCase(); 1444 1445 } 1446 1447 }); 1448 1449/** 1450 * @ngdoc filter 1451 * @name formatBytes 1452 * @kind function 1453 * 1454 * @description 1455 * Convert number into abbreviations. 1456 * i.e: K for one thousand, M for Million, B for billion 1457 * e.g: number of users:235,221, decimal:1 => 235.2 K 1458 */ 1459 1460angular.module('a8m.math.shortFmt', ['a8m.math']) 1461 1462 .filter('shortFmt', ['$math', function ($math) { 1463 return function (number, decimal) { 1464 if(isNumber(decimal) && isFinite(decimal) && decimal%1===0 && decimal >= 0 && 1465 isNumber(number) && isFinite(number)){ 1466 1467 if(number < 1e3) { 1468 return number; 1469 } else if(number < 1e6) { 1470 return convertToDecimal((number / 1e3), decimal, $math) + ' K'; 1471 } else if(number < 1e9){ 1472 return convertToDecimal((number / 1e6), decimal, $math) + ' M'; 1473 } else { 1474 return convertToDecimal((number / 1e9), decimal, $math) + ' B'; 1475 } 1476 1477 }else{ 1478 return "NaN"; 1479 } 1480 } 1481}]); 1482/** 1483 * @ngdoc filter 1484 * @name sum 1485 * @kind function 1486 * 1487 * @description 1488 * Sum up all values within an array 1489 * 1490 */ 1491 1492angular.module('a8m.math.sum', []) 1493 1494 .filter('sum', function () { 1495 return function (input, initial) { 1496 1497 return (!isArray(input)) ? input : 1498 input.reduce(function(prev, curr) { 1499 return prev + curr; 1500 }, initial || 0); 1501 1502 } 1503 1504 }); 1505 1506/** 1507 * @ngdoc filter 1508 * @name endsWith 1509 * @kind function 1510 * 1511 * @description 1512 * checks whether string ends with the ends parameter. 1513 */ 1514 1515angular.module('a8m.ends-with', []) 1516 1517 .filter('endsWith', function () { 1518 return function (input, ends, csensitive) { 1519 1520 var sensitive = csensitive || false, 1521 position; 1522 1523 if(!isString(input) || isUndefined(ends)) { 1524 return input; 1525 } 1526 1527 input = (sensitive) ? input : input.toLowerCase(); 1528 position = input.length - ends.length; 1529 1530 return input.indexOf((sensitive) ? ends : ends.toLowerCase(), position) !== -1; 1531 } 1532 }); 1533 1534/** 1535 * @ngdoc filter 1536 * @name ltrim 1537 * @kind function 1538 * 1539 * @description 1540 * Left trim. Similar to trimFilter, but only for left side. 1541 */ 1542 1543angular.module('a8m.ltrim', []) 1544 1545 .filter('ltrim', function () { 1546 return function(input, chars) { 1547 1548 var trim = chars || '\\s'; 1549 1550 if(!isString(input)) { 1551 return input; 1552 } 1553 1554 return input.replace(new RegExp('^' + trim + '+'), ''); 1555 } 1556 }); 1557 1558/** 1559 * @ngdoc filter 1560 * @name repeat 1561 * @kind function 1562 * 1563 * @description 1564 * Repeats a string n times 1565 */ 1566 1567angular.module('a8m.repeat', []) 1568 1569 .filter('repeat',[ function () { 1570 return function (input, n, separator) { 1571 1572 var times = ~~n; 1573 1574 if(!isString(input)) { 1575 return input; 1576 } 1577 1578 return (!times) ? input : strRepeat(input, --n, separator || ''); 1579 } 1580 }]); 1581 1582/** 1583 * Repeats a string n times with given separator 1584 * @param str string to repeat 1585 * @param n number of times 1586 * @param sep separator 1587 * @returns {*} 1588 */ 1589function strRepeat(str, n, sep) { 1590 if(!n) { 1591 return str; 1592 } 1593 return str + sep + strRepeat(str, --n, sep); 1594} 1595/** 1596* @ngdoc filter 1597* @name rtrim 1598* @kind function 1599* 1600* @description 1601* Right trim. Similar to trimFilter, but only for right side. 1602*/ 1603 1604angular.module('a8m.rtrim', []) 1605 1606 .filter('rtrim', function () { 1607 return function(input, chars) { 1608 1609 var trim = chars || '\\s'; 1610 1611 if(!isString(input)) { 1612 return input; 1613 } 1614 1615 return input.replace(new RegExp(trim + '+$'), ''); 1616 } 1617 }); 1618 1619/** 1620 * @ngdoc filter 1621 * @name slugify 1622 * @kind function 1623 * 1624 * @description 1625 * remove spaces from string, replace with "-" or given argument 1626 */ 1627 1628angular.module('a8m.slugify', []) 1629 1630 .filter('slugify',[ function () { 1631 return function (input, sub) { 1632 1633 var replace = sub || '-'; 1634 1635 if(isString(input)) { 1636 return input.toLowerCase() 1637 .replace(/\s+/g, replace); 1638 } 1639 1640 return input; 1641 } 1642 }]); 1643 1644/** 1645 * @ngdoc filter 1646 * @name startWith 1647 * @kind function 1648 * 1649 * @description 1650 * checks whether string starts with the starts parameter. 1651 */ 1652 1653angular.module('a8m.starts-with', []) 1654 1655 .filter('startsWith', function () { 1656 return function (input, start, csensitive) { 1657 1658 var sensitive = csensitive || false; 1659 1660 if(!isString(input) || isUndefined(start)) { 1661 return input; 1662 } 1663 1664 input = (sensitive) ? input : input.toLowerCase(); 1665 1666 return !input.indexOf((sensitive) ? start : start.toLowerCase()); 1667 } 1668 }); 1669 1670/** 1671 * @ngdoc filter 1672 * @name stringular 1673 * @kind function 1674 * 1675 * @description 1676 * get string with {n} and replace match with enumeration values 1677 */ 1678 1679angular.module('a8m.stringular', []) 1680 .filter('stringular', function () { 1681 return function(input) { 1682 1683 var args = Array.prototype.slice.call(arguments, 1); 1684 1685 return input.replace(/{(\d+)}/g, function (match, number) { 1686 return isUndefined(args[number]) ? match : args[number]; 1687 }); 1688 1689 } 1690 }); 1691 1692/** 1693 * @ngdoc filter 1694 * @name stripTags 1695 * @kind function 1696 * 1697 * @description 1698 * strip html tags from string 1699 */ 1700 1701angular.module('a8m.strip-tags', []) 1702 .filter('stripTags', function () { 1703 return function(input) { 1704 if(isString(input)) { 1705 return input.replace(/<\S[^><]*>/g, ''); 1706 } 1707 return input; 1708 } 1709 }); 1710 1711/** 1712 * @ngdoc filter 1713 * @name trim 1714 * @kind function 1715 * 1716 * @description 1717 * Strip whitespace (or other characters) from the beginning and end of a string 1718 */ 1719 1720angular.module('a8m.trim', []) 1721 1722 .filter('trim', function () { 1723 return function(input, chars) { 1724 1725 var trim = chars || '\\s'; 1726 1727 if(!isString(input)) { 1728 return input; 1729 } 1730 1731 return input.replace(new RegExp('^' + trim + '+|' + trim + '+$', 'g'), ''); 1732 } 1733 }); 1734 1735/** 1736 * @ngdoc filter 1737 * @name truncate 1738 * @kind function 1739 * 1740 * @description 1741 * truncates a string given a specified length, providing a custom string to denote an omission. 1742 */ 1743 1744angular.module('a8m.truncate', []) 1745 .filter('truncate', function () { 1746 return function(input, length, suffix, preserve) { 1747 1748 length = isUndefined(length) ? input.length : length; 1749 preserve = preserve || false; 1750 suffix = suffix || ''; 1751 1752 if(!isString(input) || (input.length <= length)) return input; 1753 1754 return input.substring(0, (preserve) ? 1755 ((input.indexOf(' ', length) === -1) ? input.length : input.indexOf(' ', length)) : 1756 length) + suffix; 1757 1758 }; 1759 }); 1760 1761/** 1762 * @ngdoc filter 1763 * @name ucfirst 1764 * @kind function 1765 * 1766 * @description 1767 * ucfirst 1768 * 1769 */ 1770 1771angular.module('a8m.ucfirst', []) 1772 1773 .filter('ucfirst', [function() { 1774 return function(input) { 1775 return angular.isString(input) ? input.split(' ') 1776 .map(function (char) { 1777 return char.charAt(0).toUpperCase() + char.substring(1); 1778 }).join(' ') : input; 1779 } 1780 1781 }]); 1782 1783/** 1784 * @ngdoc filter 1785 * @name uriEncode 1786 * @kind function 1787 * 1788 * @description 1789 * get string as parameter and return encoded string 1790 */ 1791 1792angular.module('a8m.uri-encode', []) 1793 1794 .filter('uriEncode',['$window', function ($window) { 1795 return function (input) { 1796 1797 if(isString(input)) { 1798 return $window.encodeURI(input); 1799 } 1800 1801 return input; 1802 } 1803 }]); 1804 1805/** 1806 * @ngdoc filter 1807 * @name wrap 1808 * @kind function 1809 * 1810 * @description 1811 * Wrap a string with another string 1812 */ 1813 1814angular.module('a8m.wrap', []) 1815 1816 .filter('wrap', function () { 1817 return function(input, wrap, ends) { 1818 1819 if(!isString(input) || isUndefined(wrap)) { 1820 return input; 1821 } 1822 1823 return [wrap, input, ends || wrap].join(''); 1824 1825 } 1826 }); 1827 1828/** 1829 * @ngdoc provider 1830 * @name filterWatcher 1831 * @kind function 1832 * 1833 * @description 1834 * filterWatchers is a _privateProvider 1835 * It's created to solve the problem of $rootScope:infdig(Infinite $digest loop) when using 1836 * some filters on the view. 1837 */ 1838 1839angular.module('a8m.filter-watcher', []) 1840 .provider('filterWatcher', function() { 1841 1842 var filterPrefix = '_$$'; 1843 1844 /**
1845 * @description 1846 * change the prefix name for filters on watch phase 1847 * @param prefix 1848 * @returns {filterWatcher} 1849 */ 1850 this.setPrefix = function(prefix) { 1851 filterPrefix = prefix; 1852 return this; 1853 }; 1854 1855 this.$get = ['$window', function($window) { 1856 1857 var $$timeout = $window.setTimeout; 1858 1859 /** 1860 * @description 1861 * return the filter full name 1862 * @param name 1863 * @returns {string} 1864 * @private 1865 */ 1866 function _getFullName(name) { 1867 return filterPrefix + name; 1868 } 1869 1870 /** 1871 * @description 1872 * return whether or not this object is watched in current phase 1873 * @param fName 1874 * @param object 1875 * @returns {boolean} 1876 * @private 1877 */ 1878 function _isWatched(fName, object) { 1879 return isDefined(object[fName]); 1880 } 1881 1882 /** 1883 * @description 1884 * return the object.$$filterName instance in current phase 1885 * @param name 1886 * @param object 1887 * @private 1888 */ 1889 function _watch(name, object) { 1890 var fName = _getFullName(name); 1891 1892 if(!_isWatched(fName, object)) { 1893 //Create new instance 1894 Object.defineProperty(object, fName, { 1895 enumerable: false, 1896 configurable: true, 1897 value: {} 1898 }); 1899 } 1900 return object[fName]; 1901 } 1902 1903 /** 1904 * @description 1905 * destroy/delete current watch instance 1906 * @param name 1907 * @param object 1908 * @private 1909 */ 1910 function _destroy(name, object) { 1911 return $$timeout(function() { 1912 delete object[_getFullName(name)]; 1913 }); 1914 } 1915 1916 return { 1917 $watch: _watch, 1918 $destroy: _destroy 1919 } 1920 1921 }]; 1922 1923 }); 1924/** 1925 * @ngdoc module 1926 * @name angular.filters 1927 * @description 1928 * Bunch of useful filters for angularJS 1929 */ 1930 1931angular.module('angular.filter', [ 1932 1933 'a8m.ucfirst', 1934 'a8m.uri-encode', 1935 'a8m.slugify', 1936 'a8m.strip-tags', 1937 'a8m.stringular', 1938 'a8m.truncate', 1939 'a8m.starts-with', 1940 'a8m.ends-with', 1941 'a8m.wrap', 1942 'a8m.trim', 1943 'a8m.ltrim', 1944 'a8m.rtrim', 1945 'a8m.repeat', 1946 1947 'a8m.to-array', 1948 'a8m.concat', 1949 'a8m.contains', 1950 'a8m.unique', 1951 'a8m.is-empty', 1952 'a8m.after', 1953 'a8m.after-where', 1954 'a8m.before', 1955 'a8m.before-where', 1956 'a8m.where', 1957 'a8m.reverse', 1958 'a8m.remove', 1959 'a8m.remove-with', 1960 'a8m.group-by', 1961 'a8m.count-by', 1962 'a8m.search-field', 1963 'a8m.fuzzy-by', 1964 'a8m.fuzzy', 1965 'a8m.omit', 1966 'a8m.pick', 1967 'a8m.every', 1968 'a8m.filter-by', 1969 'a8m.xor', 1970 'a8m.map', 1971 'a8m.first', 1972 'a8m.last', 1973 'a8m.flatten', 1974 1975 'a8m.math', 1976 'a8m.math.max', 1977 'a8m.math.min', 1978 'a8m.math.percent', 1979 'a8m.math.radix', 1980 'a8m.math.sum', 1981 'a8m.math.degrees', 1982 'a8m.math.radians', 1983 'a8m.math.byteFmt', 1984 'a8m.math.kbFmt', 1985 'a8m.math.shortFmt', 1986 1987 'a8m.angular', 1988 'a8m.conditions', 1989 'a8m.is-null', 1990 1991 'a8m.filter-watcher' 1992]); 1993})( window, window.angular );
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.