├── .bowerrc ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── bower.json ├── build ├── object-table-style.css ├── object-table.js └── themes │ ├── blue-dust.css │ └── dark-sky.css ├── dev ├── data │ ├── data-page1.json │ ├── data-page2.json │ └── data-page3.json ├── example.js └── index.html ├── example ├── css │ └── style.css ├── index.html ├── js │ ├── codemirror │ │ ├── ambiance.css │ │ ├── codemirror.css │ │ ├── codemirror.js │ │ ├── htmlmixed.js │ │ ├── javascript.js │ │ └── xml.js │ ├── ctrl │ │ └── serverPagingCtrl.js │ ├── example.js │ ├── router.js │ └── ui-codemirror.js ├── partials │ ├── additional_filters.html │ ├── attributes.html │ ├── basic.html │ ├── column_highlighting.html │ ├── compound_resize.html │ ├── compound_sorting.html │ ├── custom_headers.html │ ├── custom_rows.html │ ├── custom_rows_headers.html │ ├── draggable_headers.html │ ├── editable_cells.html │ ├── external.html │ ├── footer_expression.html │ ├── other.html │ ├── pagination.html │ ├── select_multiply.html │ ├── separate_search.html │ ├── server_paging.html │ └── themes.html └── test-data │ ├── data-page1.json │ ├── data-page2.json │ └── data-page3.json ├── gulp └── build.js ├── gulpfile.js ├── package-lock.json ├── package.json ├── src ├── css │ ├── object-table-style.less │ └── themes │ │ ├── blue-dust.less │ │ └── dark-sky.less ├── js │ ├── contenteditableDirective.js │ ├── directive.js │ ├── draggableDirective.js │ ├── objectTableCtrl.js │ ├── objectTableFilters.js │ ├── objectTablePagingCtrl.js │ ├── objectTableSortingCtrl.js │ ├── objectTableUtilService.js │ └── pagingDirective.js └── templates │ ├── common.html │ └── paging.html └── tests ├── karma.conf.js └── specs └── objectTable.spec.js /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "app/bower_components", 3 | "scripts": { 4 | "postinstall": "wiredep -s app/index.html" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .tmp 2 | node_modules 3 | bower_components 4 | npm-debug.log 5 | *.rar 6 | .idea 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 10.0.0 4 | 5 | script: 6 | - gulp build 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013, esvit. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 5 | 6 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 8 | Neither the name of the esvit nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 9 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Display JSON data in table with AngularJS 2 | 3 | ================= 4 | [![Build Status](https://travis-ci.org/ekokotov/object-table.svg)](https://travis-ci.org/ekokotov/object-table) 5 | 6 | ## Desription 7 | This Angular directive enable data representation via tables. It makes possible search, filtering, pagination, compound sorting, editable cells, row templates, etc... 8 | Exhibits high performance. Without any dependencies - just pure Angular. 9 | 10 | Code licensed under BSD license. 11 | 12 | Requirements 13 | ================= 14 | Angular v. 1.3.x 15 | Bootstrap 3 CSS 16 | 17 | Feature List 18 | ------------ 19 | - loading data from URL 20 | - column-specific filtering 21 | - column sorting 22 | - compound sorting (sorting by few fields) 23 | - live editing (editable cells) 24 | - custom row templates 25 | - custom header templates 26 | - column highlighting 27 | - column resizing 28 | - draggable columns 29 | - multiply selection 30 | - external filtering 31 | - pagination 32 | - search (by all fields) 33 | - column searching (by each column) 34 | - display aggregate information in footer (like, total ,sum) 35 | - support server pagination 36 | - themes! 37 | 38 | ## Installing: 39 | ``` 40 | bower install angular-object-table 41 | npm i angular-object-table --save 42 | yarn add angular-object-table 43 | ``` 44 | 45 | 46 | ## Run examples 47 | Just run http server form root folder and open in browser example directory: http://localhost:8080/example/ 48 | 49 | ## Using 50 | 51 | ``` 52 | 53 | 54 | 55 | 56 | 57 | ``` 58 | Add dependency: 59 | ``` 60 | angular.module('yourModule', ['objectTable']) 61 | ``` 62 | 63 | Example of HTML-markup : 64 | ``` 65 |
74 | ``` 75 | ## Examples 76 | http://ekokotov.github.io/object-table/ 77 | 78 | ## Atributes 79 | 80 | Here is a file list for possible attributes for stable version: 81 | 82 | Atribute | required | Description 83 | ---------------------|----------|------------------------- 84 | data | yes | Data source (array of objects) in your Controller. Note: If 'fromUrl' is present, 'data' attribute contains controller link to empty array (will be fill up after receiving data). Example: data="exportDataVariable" 85 | fromUrl | no | Load data from external URL. 86 | display | no | *default: 5*. Displays count. Using with 'paging' displays items per page 87 | search | no | *default: true*. Display search input. Value search="separate" is allows you search by columns. 88 | paging | no | *default: true*. Use paging to present data. 89 | headers | yes | Table header names array. Example: headers = "HeaderName1,HeaderName2". 90 | fields | yes | Array of displayed properties of object. This option allows to display only certain fields of the object. Number of fields must be equal number of headers. Example: fields=propertyName1,propertyName1". 91 | sorting | no | *default: simple*. Value "simple" is used to sort by single column, "compound" - to order by multiple fields. 92 | editable | no | Allows to edit content inside cells. Editing updates your angular model. 93 | select | no | select="multiply" allows to select more than one row. Selected rows are accessible. 94 | selected-model | no | It exports selected model to controller variable. selected-model="yourModel" 95 | resize | no | *default: true*. Use column resizing. 96 | drag-columns | no | *default: false*. It allows to reorder your columns using drag-n-drop. 97 | 98 | ## Themes 99 | Please check new 'Dark-sky' and 'Blue-dust' themes: 100 | http://ekokotov.github.io/object-table/samples.html#/themes 101 | 102 | ## Tests (Karma + Jasmine) 103 | ``` 104 | karma start tests/karma.conf.js 105 | ``` 106 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-object-table", 3 | "version": "0.2.6", 4 | "authors": [ 5 | "Evgeny Kokotov" 6 | ], 7 | "description": "angular smart table data grid", 8 | "main": [ 9 | "build/object-table.js", 10 | "build/object-table-style.css" 11 | ], 12 | "keywords": [ 13 | "angular", 14 | "table", 15 | "data-grid", 16 | "datagrid", 17 | "object-table", 18 | "angular-object-table", 19 | "json-table", 20 | "angular json table" 21 | ], 22 | "license": "MIT", 23 | "homepage": "https://github.com/ekokotov/", 24 | "ignore": [ 25 | "**/.*", 26 | "node_modules", 27 | "bower_components", 28 | "test", 29 | "tests" 30 | ], 31 | "dependencies": { 32 | "angular": "~1.3.15" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /build/object-table-style.css: -------------------------------------------------------------------------------- 1 | .object-table-module .back-cover{background-color:#fff}.object-table-module .count{font-size:15px;float:right;margin-top:20px;color:#2165a3;background-color:rgba(188,188,188,.35)}.object-table-module .count .records{color:#888;font-weight:400}.object-table-module .search{max-width:250px;padding-right:25px}.object-table-module .search.separate{width:100%;max-width:100%;padding-left:22px}.object-table-module .search_icon{position:absolute;right:8px;top:10px;color:gray}.object-table-module .search_icon.separate{left:15px;top:20px;font-size:10px;width:10px}.object-table-module .sorting-badge{background-color:#fff;position:relative;font-weight:600;font-size:10px;padding:5px 5px 5px 10px;display:inline-block;border-radius:4px;margin-left:5px;color:#337ab7;border:1px solid rgba(12,75,140,.43);margin-bottom:3px}.object-table-module .sorting-container{margin:10px 0}.object-table-module .sorting-container .sorting-badge:hover{background-color:#337ab7;color:#fff}.object-table-module .sorting-container .sorting-badge:hover .close{opacity:1;cursor:pointer;color:#002655;text-shadow:none}.object-table-module .sorting-container .sorting-badge .close{margin-left:5px;float:right;font-size:12px;line-height:1;color:#000;filter:alpha(opacity=20);opacity:.2}.object-table-module .object-table{margin-bottom:0;color:gray}.object-table-module .object-table tfoot tr,.object-table-module .object-table thead>tr{background-color:#e6f1f6;color:#2165a3;cursor:pointer;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.object-table-module .object-table thead>tr th{border-bottom:0;padding-right:25px;position:relative;will-change:width;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.object-table-module .object-table thead>tr th[draggable] a{width:100%;display:block}.object-table-module .object-table thead>tr th.dragged div.resize,.object-table-module .object-table thead>tr th.draggedOver div.resize{display:none}.object-table-module .object-table thead>tr th.dragged{background-color:rgba(255,255,255,.64);border:2px dashed #427998}.object-table-module .object-table thead>tr th.draggedOver{background-color:rgba(128,128,128,.16);border:1px solid #93999c}.object-table-module .object-table thead>tr th.sortable{position:relative}.object-table-module .object-table thead>tr th.sortable:after,.object-table-module .object-table thead>tr th.sortable:before{content:"\e113";font-size:9px;font-family:'Glyphicons Halflings';visibility:visible;right:8px;top:40%;position:absolute;opacity:.25;margin-top:-4px}.object-table-module .object-table thead>tr th.sortable:after{content:"\e114";margin-top:5px}.object-table-module .object-table thead>tr th.sortable.table-sort-down:after,.object-table-module .object-table thead>tr th.sortable.table-sort-up:before,.object-table-module .object-table thead>tr th.sortable:hover:not(.table-sort-up):not(.table-sort-down):after,.object-table-module .object-table thead>tr th.sortable:hover:not(.table-sort-up):not(.table-sort-down):before{opacity:1}.object-table-module .object-table thead>tr th.separate{position:relative;padding-right:7px;text-shadow:none!important;outline:0}.object-table-module .object-table thead>tr th.separate:focus{outline:0}.object-table-module .object-table thead>tr th div.resize{width:5px;border-right:2px dotted #949494;position:absolute;right:-2px;visibility:hidden;top:0;z-index:999;height:100%;cursor:ew-resize}.object-table-module .object-table thead>tr th:hover div.resize{visibility:visible}.object-table-module .object-table tfoot tr{cursor:auto}.object-table-module .object-table tbody tr.selected-row td{background-color:#d7f1a0}.object-table-module .object-table tbody tr:hover{background-color:rgba(103,103,103,.08);color:#000}.object-table-module .object-table tbody td:hover{cursor:pointer}.object-table-module .object-table tbody td[editable] div[contenteditable]{padding:3px}.object-table-module .object-table tbody td[editable] div[contenteditable]:focus{margin:-3px;border:2px dotted #738395;background-color:#F5F5F5;vertical-align:middle;color:#0c4b8c;display:block;outline:0}.object-table-module.hover-column table{overflow:hidden!important}.object-table-module.hover-column table thead tr th{z-index:3;position:relative}.object-table-module.hover-column table tbody tr td{position:relative;outline:0;z-index:2}.object-table-module.hover-column table tbody tr td:hover{z-index:1}.object-table-module.hover-column table tbody tr td:hover::before{content:'';height:10000px;left:0;position:absolute;top:-5000px;width:100%;z-index:-1;background-color:rgba(103,103,103,.08)}.loading{padding:5px;text-align:center;color:#337ab9}.loading .glyphicon-refresh-animate{-animation:spin .7s infinite linear;-webkit-animation:spin2 .7s infinite linear}@-webkit-keyframes spin2{from{-webkit-transform:rotate(0)}to{-webkit-transform:rotate(360deg)}}@keyframes spin{from{transform:scale(1) rotate(0)}to{transform:scale(1) rotate(360deg)}} -------------------------------------------------------------------------------- /build/object-table.js: -------------------------------------------------------------------------------- 1 | /** 2 | * angular-object-table - angular smart table directive 3 | * @version v0.2.6 4 | * @author Yauheni Kokatau 5 | * @license MIT 6 | */ 7 | "use strict";angular.module("objectTable",[]).directive("contenteditable",function(){return{restrict:"A",require:["ngModel","^objectTable"],link:function(r,n,e,t){var a=t[0],o=t[1];a.$render=function(){n.html(a.$viewValue||"")},n.bind("change blur",function(){var e=a.$viewValue.toString(),t=n.text();e!==t&&(r.$apply(function(){a.$setViewValue(t)}),o.onEdit&&"function"==typeof o.onEdit&&o.onEdit({$oldValue:e,$newValue:t}))})}}}),angular.module("objectTable").directive("objectTable",["$compile","$interpolate",function(e,t){return{restrict:"A",replace:!0,templateUrl:"/src/templates/common.html",controller:"objectTableCtrl",controllerAs:"ctrl",transclude:!0,scope:{data:"=",display:"=?",resize:"=?",paging:"=?",fromUrl:"@",sortingType:"@?sorting",editable:"&?",select:"@?",selectedModel:"=?",dragColumns:"=?",onEdit:"&?",onDrag:"&?"},compile:function(e,t){var o="",i="";return t.addFilter&&(o+=t.addFilter),"false"!==t.sorting&&(o+="| orderBy:sortingArray"),t.dragColumns&&e.find("th").attr("allow-drag",""),"separate"===t.search?t.fields.split(",").forEach(function(e,t){o+="| filter:{'"+e.trim()+"':columnSearch['"+e+"']}"}):void 0!==t.search&&"true"!==t.search||(o+="| filter:globalSearch"),i+=" | offset: currentPage:display |limitTo: display",e[0].querySelector("#rowTr").setAttribute("ng-repeat","item in $parent.$filtered = (data"+o+")"+i),e.find("paging").attr("count","$filtered.length"),function(n,e,t,a,r){a._init(),r(n,function(e,t){for(var r in n.$owner=t.$parent,e)if(e.hasOwnProperty(r))switch(e[r].tagName){case"THEAD":a._addHeaderPattern(e[r]);break;case"TBODY":n.findBody=!0,a._addRowPattern(e[r],o,i);break;case"TFOOT":a._addFooterPattern(e[r])}})}}}}]),angular.module("objectTable").directive("allowDrag",function(){return{restrict:"A",controller:function(){},compile:function(e,t){function i(e,t){var r=e[0].parentNode.querySelector("."+t);r&&r.classList.remove(t)}return function(e,a,t,o){a.attr("draggable",!0),a.bind("dragstart",function(e){(o.target=this).classList.add("dragged"),(e.originalEvent||e).dataTransfer.setData("text",o.target.cellIndex)}),a.bind("dragover",function(e){e.preventDefault()}),a.bind("dragenter",function(e){(o.toTarget=this).classList.contains("draggedOver")||this.classList.contains("dragged")||this.classList.add("draggedOver"),e.preventDefault(),e.stopPropagation()}),a.bind("dragend",function(e){this.classList.contains("dragged")&&this.classList.remove("dragged"),e.preventDefault()}),a.bind("dragleave",function(e){this.classList.remove("draggedOver")}),a.bind("drop",function(e){var t=o.toTarget.cellIndex,r=e.originalEvent||e,n=parseInt(r.dataTransfer.getData("text"),10);i(a,"dragged"),i(a,"draggedOver"),a.parent().controller("objectTable").changeColumnsOrder(t,n),e.preventDefault()})}}}}),angular.module("objectTable").controller("objectTableCtrl",["$scope","$timeout","$element","$attrs","$http","$compile","$controller","objectTableUtilService",function(u,e,g,t,r,p,n,a){n("objectTableSortingCtrl",{$scope:u});var f=this;this._init=function(){u.headers=[],u.fields=[],u.display=u.display||5,u.paging=!angular.isDefined(u.paging)||u.paging,u.sortingType=u.sortingType||"simple",u.currentPage=0,u.customHeader=!1,"separate"==t.search?(u.search="separate",u.columnSearch=[]):u.search=void 0===t.search||"true"===t.search,u.headers=a.getArrayFromParams(t.headers,"headers"),u.fields=a.getArrayFromParams(t.fields,"fields"),t.fromUrl&&this._loadExternalData(t.fromUrl),u.onEdit&&(this.onEdit=u.onEdit),u.selectedModel="multiply"===u.select?[]:{}},this.onEdit=u.onEdit,this._loadExternalData=function(e){u.dataIsLoading=!0,r.get(e).then(function(e){u.data=e.data,u.dataIsLoading=!1})},this._addHeaderPattern=function(e){u.customHeader=!0,Array.prototype.forEach.call(e.querySelectorAll("[allow-drag]"),function(e,t){e.setAttribute("index",t)}),e.removeAttribute("ng-non-bindable"),g.find("table").prepend(e)},this._addFooterPattern=function(e){g.find("table").prepend(e)},this._addRowPattern=function(e,t,r){this._checkEditableContent(e),this._addRepeatToRow(e,t,r),e.removeAttribute("ng-non-bindable"),g.find("table").append(e.outerHTML),this.bodyTemplate=e.innerHTML,p(g.find("tbody"))(u)},this._addRepeatToRow=function(e,t,r){var n=angular.element(e).find("tr");n.attr("ng-repeat","item in $filtered = (data"+t+")"+r),n.attr("ng-click")||n.attr("ng-click","setSelected(item)"),n.attr("ng-class","{'selected-row':ifSelected(item)}")},this._checkEditableContent=function(e){var t,r=/\{\{:*:*(.*?)\}\}/g;Array.prototype.forEach.call(e.querySelectorAll("[editable]"),function(e){t=e.innerHTML.replace(r,"$1"),e.innerHTML="
{{"+t+"}}
"})},this.setCurrentPage=function(e){u.currentPage=e},u.setSelected=function(e){"multiply"===u.select?f._containsInSelectArray(e)?u.selectedModel.splice(u.selectedModel.indexOf(e),1):u.selectedModel.push(e):u.selectedModel=e},this._containsInSelectArray=function(t){if(u.selectedModel.length)return 0a.pageCount()-e&&(r=a.pageCount()-e+1);for(var n=r;n=this.length)for(var r=e-this.length;1+r--;)this.push(void 0);return this.splice(e,0,this.splice(t,1)[0]),this},{getArrayFromParams:function(e,t){if(!e)throw"Required '"+t+"' attribute is not found!";for(var r=[],n=e.split(","),a=0,o=n.length;a
Sorting:
{{::sortField}}
{{head}}
{{item[field]}}
{{item[field]}}
Loading Data...
'); 9 | $templateCache.put('/src/templates/paging.html','
{{count}} records
');}]); -------------------------------------------------------------------------------- /build/themes/blue-dust.css: -------------------------------------------------------------------------------- 1 | .object-table-module.blue-dust .back-cover{background-color:#bac9d1}.object-table-module.blue-dust .back-cover table{color:#02658C}.object-table-module.blue-dust .back-cover table.table{border:1px solid #005576;border-bottom:5px solid #545761}.object-table-module.blue-dust .back-cover table.object-table thead tr{background-color:#545761;color:#90dfff}.object-table-module.blue-dust .back-cover table.object-table thead tr th{border:1px solid rgba(143,153,162,.39);text-shadow:1px 0 1px rgba(0,0,0,.64)}.object-table-module.blue-dust .back-cover table.object-table tbody tr:hover{background-color:rgba(136,223,255,.19);color:#000;text-shadow:0 0 1px rgba(0,0,0,.29)}.object-table-module.blue-dust .back-cover table.object-table tbody tr.selected-row td{background-color:#88dfff;color:#000;border-bottom:2px solid #4abee8!important;border-top:2px solid #4abee8!important}.object-table-module.blue-dust .back-cover table.object-table tbody tr td{border:1px solid rgba(84,87,97,.23)}.object-table-module.blue-dust .back-cover table.object-table tbody tr td[editable] span:focus{padding:3px;margin:-5px;font-weight:700;border:2px dotted #009AD5;background-color:#D7D7D7}.object-table-module.blue-dust .sorting-badge{color:#333742;background-color:#bac9d1}.object-table-module.blue-dust .sorting-badge:hover{background-color:#545761;color:#bac9d1}.object-table-module.blue-dust.hover-column table tbody td:hover::before{background-color:rgba(136,223,255,.19)}.object-table-module.blue-dust .search{color:#333742;border:3px solid rgba(0,137,189,.23)}.object-table-module.blue-dust .search:focus{border:2px solid rgba(84,87,97,.77)}.object-table-module.blue-dust .search.separate{border:none}.object-table-module.blue-dust .count{background-color:#BAC9D1;border:1px solid rgba(43,51,74,.45);color:#545761}.object-table-module.blue-dust .count .records{color:#333742}.object-table-module.blue-dust .pagination>li.active a{color:#88DFFF;background-color:#545761}.object-table-module.blue-dust .pagination>li a{padding:3px 12px;color:#2B334A;background-color:#BAC9D1;border:1px solid rgba(43,51,74,.45)}.object-table-module.blue-dust .pagination>li a:hover{background-color:#DFDFDF} -------------------------------------------------------------------------------- /build/themes/dark-sky.css: -------------------------------------------------------------------------------- 1 | .object-table-module.dark-sky .back-cover{background-color:#333742}.object-table-module.dark-sky .back-cover table{color:#9ca0a9}.object-table-module.dark-sky .back-cover table.table{border:1px #24272f;border-bottom:5px solid #009AD5}.object-table-module.dark-sky .back-cover table.table-bordered>tbody>tr>td{border:1px solid #24272f}.object-table-module.dark-sky .back-cover table.object-table thead tr{background-color:#009AD5;color:#333742}.object-table-module.dark-sky .back-cover table.object-table thead tr th{border:1px solid #333742}.object-table-module.dark-sky .back-cover table tbody tr.selected-row td{background-color:rgba(156,156,159,.56);color:#00b1f5}.object-table-module.dark-sky .back-cover table tbody tr:hover{background-color:rgba(0,154,213,.19);color:#fff}.object-table-module.dark-sky .back-cover table tbody tr td[editable] span:focus{padding:3px;margin:-5px;font-weight:700;border:2px dotted #009AD5;background-color:#D7D7D7}.object-table-module.dark-sky .sorting-badge{color:#333742;background-color:#009AD5;cursor:pointer}.object-table-module.dark-sky .sorting-badge:hover{background-color:rgba(0,154,213,.7);color:#333742}.object-table-module.dark-sky.hover-column table tbody td:hover::before{background-color:rgba(0,154,213,.19)}.object-table-module.dark-sky .search{background-color:rgba(239,239,239,.9);color:#333742;border:2px solid #009ad5;box-shadow:inset 0 1px 5px rgba(51,55,66,.67)}.object-table-module.dark-sky .search:focus{box-shadow:none}.object-table-module.dark-sky .search.separate{color:#333742;border:1px solid #0076a3;box-shadow:none}.object-table-module.dark-sky .count{background-color:#5D7187;color:#6fd7ff}.object-table-module.dark-sky .count .records{color:#333742}.object-table-module.dark-sky .pagination>li.active>a{color:#000;background-color:#009AD5;top:-3px;height:35px;padding-top:6px}.object-table-module.dark-sky .pagination>li a{padding:3px 12px;color:#2B334A;background-color:#5D7187;border:1px solid #2B334A} -------------------------------------------------------------------------------- /dev/data/data-page1.json: -------------------------------------------------------------------------------- 1 | { 2 | "data":[{"name": "Moroni", "age": 50, "money": -10}, 3 | {"name": "Tiancum", "age": 43,"money": 120}, 4 | {"name": "Jacob", "age": 27, "money": 5.5}, 5 | {"name": "Nephi", "age": 29,"money": -54}, 6 | {"name": "Enos", "age": 34,"money": 110}, 7 | {"name": "Tiancum", "age": 43, "money": 1000}, 8 | {"name": "Jacob", "age": 27,"money": -201}, 9 | {"name": "Nephi", "age": 29, "money": 100}, 10 | {"name": "Enos", "age": 34, "money": -52.5}, 11 | {"name": "Tiancum", "age": 43, "money": 52.1}], 12 | "page":1, 13 | "limit":10, 14 | "total":25 15 | } -------------------------------------------------------------------------------- /dev/data/data-page2.json: -------------------------------------------------------------------------------- 1 | { 2 | "data":[{"name": "2Moroni", "age": 50, "money": -10}, 3 | {"name": "2Tiancum", "age": 43,"money": 120}, 4 | {"name": "2Jacob", "age": 27, "money": 5.5}, 5 | {"name": "Nephi", "age": 29,"money": -54}, 6 | {"name": "Enos", "age": 34,"money": 110}, 7 | {"name": "Tiancum", "age": 43, "money": 1000}, 8 | {"name": "Jacob", "age": 27,"money": -201}, 9 | {"name": "Nephi", "age": 29, "money": 100}, 10 | {"name": "Enos", "age": 34, "money": -52.5}, 11 | {"name": "Tiancum", "age": 43, "money": 52.1}], 12 | "page":2, 13 | "limit":10, 14 | "total":25 15 | } -------------------------------------------------------------------------------- /dev/data/data-page3.json: -------------------------------------------------------------------------------- 1 | { 2 | "data":[{"name": "Tiancum", "age": 43, "money": 1000}, 3 | {"name": "Jacob", "age": 27,"money": -201}, 4 | {"name": "Nephi", "age": 29, "money": 100}, 5 | {"name": "Enos", "age": 34, "money": -52.5}, 6 | {"name": "Tiancum", "age": 43, "money": 52.1}], 7 | "page":3, 8 | "limit":5, 9 | "total":25 10 | } -------------------------------------------------------------------------------- /dev/example.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module 3 | * 4 | * gTableTest Description 5 | */ 6 | angular.module('test', ['objectTable']) 7 | .controller('mainController', function($scope, $http, $timeout, $q) { 8 | 9 | $scope.data = [{name: 'Moroni', age: 50, money: -10}, 10 | {name: 'Tiancum', age: 43,money: 120}, 11 | {name: 'Jacob', age: 27, money: 5.5}, 12 | {name: 'Nephi', age: 29,money: -54}, 13 | {name: 'Enos', age: 34,money: 110}, 14 | {name: 'Tiancum', age: 43, money: 1000}, 15 | {name: 'Jacob', age: 27,money: -201}, 16 | {name: 'Nephi', age: 29, money: 100}, 17 | {name: 'Enos', age: 34, money: -52.5}, 18 | {name: 'Tiancum', age: 43, money: 52.1}, 19 | {name: 'Jacob', age: 27, money: 110}, 20 | {name: 'Nephi', age: 29, money: -55}, 21 | {name: 'Enos', age: 34, money: 551}, 22 | {name: 'Tiancum', age: 43, money: -1410}, 23 | {name: 'Jacob', age: 27, money: 410}, 24 | {name: 'Nephi', age: 29, money: 100}, 25 | {name: 'Enos', age: 34, money: -100}]; 26 | 27 | $scope.test = function(e) { 28 | alert('Alert from controller method!'); 29 | }; 30 | 31 | $scope.logChange = function(oldValue, newValue) { 32 | console.log('OLD value:', oldValue, '\nNEW value:', newValue); 33 | }; 34 | 35 | $scope.logDrag = function(oldOrder, newOrder) { 36 | console.log(oldOrder, newOrder) 37 | }; 38 | 39 | $scope.dataTeacherSearch = [{'employeeNo': '4433', 40 | 'name': {'firstName': 'kuldeep','middleName': 'dsf','lastName': 'gfdkjh'}, 41 | 'department': [{'dept': 'Computer Science','status': true}, 42 | {'dept': 'science','status': false}, 43 | {'dept': 'sports','status': false}, 44 | {'dept': 'sdlkf','status': false}], 45 | 'designation': [{'post': 'director','status': false}, 46 | {'post': 'principal','status': false}, 47 | {'post': 'teaching','status': true}, 48 | {'post': 'nonteaching','status': false}]}]; 49 | 50 | $scope.report = { 51 | selectedUser: null 52 | } 53 | 54 | $scope.pagingExample = { 55 | exData: null, 56 | limit: 0, 57 | currentPage: 0, 58 | total: 0, 59 | pages: [] 60 | }; 61 | 62 | var ctrl = this, 63 | initialLoaded = false; 64 | 65 | $scope.loadData = function(n) { 66 | 67 | //don't load if n==0 or n>pages 68 | if ($scope.pagingExample.pages.length) { 69 | if (n == 0 || n > $scope.pagingExample.pages.length) return; 70 | }; 71 | 72 | //load data 73 | $http.get('data/data-page' + n + '.json').then(function(response) { 74 | $scope.pagingExample.exData = response.data.data; 75 | $scope.pagingExample.limit = response.data.limit; 76 | $scope.pagingExample.currentPage = response.data.page; 77 | $scope.pagingExample.total = response.data.total; 78 | 79 | //calculate pages just once - after first loading 80 | if (!initialLoaded) { 81 | ctrl.getTotalPages(); 82 | initialLoaded = true; 83 | }; 84 | }); 85 | }; 86 | 87 | // load first page 88 | $scope.loadData($scope.pagingExample.currentPage + 1); 89 | 90 | // calculate totals and return page range ([1,2,3]) 91 | this.getTotalPages = function() { 92 | var count = Math.round($scope.pagingExample.total / $scope.pagingExample.limit); 93 | for (var i = 0; i < count; i++) { 94 | $scope.pagingExample.pages.push(i); 95 | }; 96 | }; 97 | 98 | $scope.getTotalBalance = function(data) { 99 | if (!data || !data.length) return; 100 | var totalNumber = 0; 101 | for (var i = 0; i < data.length; i++) { 102 | totalNumber = totalNumber + parseFloat(data[i].money); 103 | } 104 | 105 | return Math.round(totalNumber); 106 | 107 | }; 108 | 109 | }) 110 | -------------------------------------------------------------------------------- /dev/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | jTable Example 7 | 8 | 9 | 10 | 11 | 12 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 |
45 |

Row custom

46 | 47 | 48 | 49 | 50 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 |
{{::item.name}} 51 |
52 | {{::item.eyeColor}} 53 |
{{::item.age}}{{::item.balance}}{{::item.company}}{{::item.address}}{{::item.favoriteFruit}}
62 |

Patel

-- 63 | 64 | 65 | 66 | 67 | 70 | 73 | 74 | 75 |
{{item.name.firstName}} {{item.name.middleName}} {{item.name.lastName}} 68 | {{items.dept}} 69 | 71 | {{designation.status}} 72 |
76 |
77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 95 | 96 | 97 | 98 | 99 | 100 | 101 |
NameEye colorAgeBalanceCompanyFavorite Fruit
{{::item.name}} 92 |
93 | {{::item.eyeColor}} 94 |
{{::item.age}}{{::item.balance}}{{::item.balance}}{{::item.favoriteFruit}}
102 |

Excell selection

103 |
104 | 416 |
417 | 418 | 419 | 420 | -------------------------------------------------------------------------------- /example/css/style.css: -------------------------------------------------------------------------------- 1 | ._personal_inf{ 2 | background-color: rgb(212, 255, 227); 3 | color: green; 4 | text-align: center; 5 | } 6 | .additional_inf{ 7 | background-color: rgb(230, 241, 246); 8 | text-align: center; 9 | } 10 | body{ 11 | margin-top: 70px; 12 | } 13 | .eye{ 14 | width: 30px; 15 | height: 10px; 16 | border-radius: 5px; 17 | float: left; 18 | margin: 5px; 19 | box-shadow: 0px 0px 3px gray; 20 | border: 1px solid gray; 21 | } 22 | 23 | .eye.green{ 24 | background-color: rgb(92, 197, 23); 25 | } 26 | .eye.brown{ 27 | background-color: rgb(208, 92, 92); 28 | } 29 | 30 | .eye.blue{ 31 | background-color: rgb(49, 191, 255); 32 | } 33 | .CodeMirror{ 34 | height: auto !important; 35 | border: 1px dashed rgb(167, 167, 167); 36 | } 37 | h2{ 38 | margin-top: 0; 39 | } 40 | p{ 41 | color: gray; 42 | } 43 | 44 | /*callout*/ 45 | .bs-callout { 46 | padding: 20px; 47 | margin: 20px 0; 48 | border: 1px solid #eee; 49 | border-left-width: 5px; 50 | border-radius: 3px; 51 | color: gray; 52 | } 53 | .bs-callout h4 { 54 | margin-top: 0; 55 | margin-bottom: 5px; 56 | } 57 | .bs-callout p:last-child { 58 | margin-bottom: 0; 59 | } 60 | .bs-callout code { 61 | border-radius: 3px; 62 | } 63 | .bs-callout+.bs-callout { 64 | margin-top: -5px; 65 | } 66 | .bs-callout-default { 67 | border-left-color: #777; 68 | } 69 | .bs-callout-default h4 { 70 | color: #777; 71 | } 72 | .bs-callout-primary { 73 | border-left-color: #428bca; 74 | } 75 | .bs-callout-primary h4 { 76 | color: #428bca; 77 | } 78 | .bs-callout-success { 79 | border-left-color: #5cb85c; 80 | } 81 | .bs-callout-success h4 { 82 | color: #5cb85c; 83 | } 84 | .bs-callout-danger { 85 | border-left-color: #d9534f; 86 | } 87 | .bs-callout-danger h4 { 88 | color: #d9534f; 89 | } 90 | .bs-callout-warning { 91 | border-left-color: #f0ad4e; 92 | } 93 | .bs-callout-warning h4 { 94 | color: #f0ad4e; 95 | } 96 | .bs-callout-info { 97 | border-left-color: #5bc0de; 98 | } 99 | .bs-callout-info h4 { 100 | color: #5bc0de; 101 | } 102 | 103 | .label{ 104 | font-size: 12px; 105 | font-weight: 100; 106 | font-family: Arial; 107 | } 108 | .label.friend{ 109 | margin-right: 10px; 110 | } -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | jTable Example 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 50 | 51 | 80 | 81 |
82 |
83 |
84 | 85 | 86 | -------------------------------------------------------------------------------- /example/js/codemirror/ambiance.css: -------------------------------------------------------------------------------- 1 | /* ambiance theme for codemirror */ 2 | 3 | /* Color scheme */ 4 | 5 | .cm-s-ambiance .cm-keyword { color: #cda869; } 6 | .cm-s-ambiance .cm-atom { color: #CF7EA9; } 7 | .cm-s-ambiance .cm-number { color: #78CF8A; } 8 | .cm-s-ambiance .cm-def { color: #aac6e3; } 9 | .cm-s-ambiance .cm-variable { color: #ffb795; } 10 | .cm-s-ambiance .cm-variable-2 { color: #eed1b3; } 11 | .cm-s-ambiance .cm-variable-3 { color: #faded3; } 12 | .cm-s-ambiance .cm-property { color: #eed1b3; } 13 | .cm-s-ambiance .cm-operator {color: #fa8d6a;} 14 | .cm-s-ambiance .cm-comment { color: #555; font-style:italic; } 15 | .cm-s-ambiance .cm-string { color: #8f9d6a; } 16 | .cm-s-ambiance .cm-string-2 { color: #9d937c; } 17 | .cm-s-ambiance .cm-meta { color: #D2A8A1; } 18 | .cm-s-ambiance .cm-qualifier { color: yellow; } 19 | .cm-s-ambiance .cm-builtin { color: #9999cc; } 20 | .cm-s-ambiance .cm-bracket { color: #24C2C7; } 21 | .cm-s-ambiance .cm-tag { color: #fee4ff } 22 | .cm-s-ambiance .cm-attribute { color: #9B859D; } 23 | .cm-s-ambiance .cm-header {color: blue;} 24 | .cm-s-ambiance .cm-quote { color: #24C2C7; } 25 | .cm-s-ambiance .cm-hr { color: pink; } 26 | .cm-s-ambiance .cm-link { color: #F4C20B; } 27 | .cm-s-ambiance .cm-special { color: #FF9D00; } 28 | .cm-s-ambiance .cm-error { color: #AF2018; } 29 | 30 | .cm-s-ambiance .CodeMirror-matchingbracket { color: #0f0; } 31 | .cm-s-ambiance .CodeMirror-nonmatchingbracket { color: #f22; } 32 | 33 | .cm-s-ambiance .CodeMirror-selected { background: rgba(255, 255, 255, 0.15); } 34 | .cm-s-ambiance.CodeMirror-focused .CodeMirror-selected { background: rgba(255, 255, 255, 0.10); } 35 | .cm-s-ambiance.CodeMirror ::selection { background: rgba(255, 255, 255, 0.10); } 36 | .cm-s-ambiance.CodeMirror ::-moz-selection { background: rgba(255, 255, 255, 0.10); } 37 | 38 | /* Editor styling */ 39 | 40 | .cm-s-ambiance.CodeMirror { 41 | line-height: 1.40em; 42 | color: #E6E1DC; 43 | background-color: #202020; 44 | -webkit-box-shadow: inset 0 0 10px black; 45 | -moz-box-shadow: inset 0 0 10px black; 46 | box-shadow: inset 0 0 10px black; 47 | } 48 | 49 | .cm-s-ambiance .CodeMirror-gutters { 50 | background: #3D3D3D; 51 | border-right: 1px solid #4D4D4D; 52 | box-shadow: 0 10px 20px black; 53 | } 54 | 55 | .cm-s-ambiance .CodeMirror-linenumber { 56 | text-shadow: 0px 1px 1px #4d4d4d; 57 | color: #111; 58 | padding: 0 5px; 59 | } 60 | 61 | .cm-s-ambiance .CodeMirror-guttermarker { color: #aaa; } 62 | .cm-s-ambiance .CodeMirror-guttermarker-subtle { color: #111; } 63 | 64 | .cm-s-ambiance .CodeMirror-lines .CodeMirror-cursor { 65 | border-left: 1px solid #7991E8; 66 | } 67 | 68 | .cm-s-ambiance .CodeMirror-activeline-background { 69 | background: none repeat scroll 0% 0% rgba(255, 255, 255, 0.031); 70 | } 71 | 72 | .cm-s-ambiance.CodeMirror, 73 | .cm-s-ambiance .CodeMirror-gutters { 74 | background-image: url("data:image/png;base64,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"); 75 | } 76 | -------------------------------------------------------------------------------- /example/js/codemirror/codemirror.css: -------------------------------------------------------------------------------- 1 | /* BASICS */ 2 | 3 | .CodeMirror { 4 | /* Set height, width, borders, and global font properties here */ 5 | font-family: monospace; 6 | height: 300px; 7 | color: black; 8 | } 9 | 10 | /* PADDING */ 11 | 12 | .CodeMirror-lines { 13 | padding: 4px 0; /* Vertical padding around content */ 14 | } 15 | .CodeMirror pre { 16 | padding: 0 4px; /* Horizontal padding of content */ 17 | } 18 | 19 | .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { 20 | background-color: white; /* The little square between H and V scrollbars */ 21 | } 22 | 23 | /* GUTTER */ 24 | 25 | .CodeMirror-gutters { 26 | border-right: 1px solid #ddd; 27 | background-color: #f7f7f7; 28 | white-space: nowrap; 29 | } 30 | .CodeMirror-linenumbers {} 31 | .CodeMirror-linenumber { 32 | padding: 0 3px 0 5px; 33 | min-width: 20px; 34 | text-align: right; 35 | color: #999; 36 | white-space: nowrap; 37 | } 38 | 39 | .CodeMirror-guttermarker { color: black; } 40 | .CodeMirror-guttermarker-subtle { color: #999; } 41 | 42 | /* CURSOR */ 43 | 44 | .CodeMirror div.CodeMirror-cursor { 45 | border-left: 1px solid black; 46 | } 47 | /* Shown when moving in bi-directional text */ 48 | .CodeMirror div.CodeMirror-secondarycursor { 49 | border-left: 1px solid silver; 50 | } 51 | .CodeMirror.cm-fat-cursor div.CodeMirror-cursor { 52 | width: auto; 53 | border: 0; 54 | background: #7e7; 55 | } 56 | .CodeMirror.cm-fat-cursor div.CodeMirror-cursors { 57 | z-index: 1; 58 | } 59 | 60 | .cm-animate-fat-cursor { 61 | width: auto; 62 | border: 0; 63 | -webkit-animation: blink 1.06s steps(1) infinite; 64 | -moz-animation: blink 1.06s steps(1) infinite; 65 | animation: blink 1.06s steps(1) infinite; 66 | } 67 | @-moz-keyframes blink { 68 | 0% { background: #7e7; } 69 | 50% { background: none; } 70 | 100% { background: #7e7; } 71 | } 72 | @-webkit-keyframes blink { 73 | 0% { background: #7e7; } 74 | 50% { background: none; } 75 | 100% { background: #7e7; } 76 | } 77 | @keyframes blink { 78 | 0% { background: #7e7; } 79 | 50% { background: none; } 80 | 100% { background: #7e7; } 81 | } 82 | 83 | /* Can style cursor different in overwrite (non-insert) mode */ 84 | div.CodeMirror-overwrite div.CodeMirror-cursor {} 85 | 86 | .cm-tab { display: inline-block; text-decoration: inherit; } 87 | 88 | .CodeMirror-ruler { 89 | border-left: 1px solid #ccc; 90 | position: absolute; 91 | } 92 | 93 | /* DEFAULT THEME */ 94 | 95 | .cm-s-default .cm-keyword {color: #708;} 96 | .cm-s-default .cm-atom {color: #219;} 97 | .cm-s-default .cm-number {color: #164;} 98 | .cm-s-default .cm-def {color: #00f;} 99 | .cm-s-default .cm-variable, 100 | .cm-s-default .cm-punctuation, 101 | .cm-s-default .cm-property, 102 | .cm-s-default .cm-operator {} 103 | .cm-s-default .cm-variable-2 {color: #05a;} 104 | .cm-s-default .cm-variable-3 {color: #085;} 105 | .cm-s-default .cm-comment {color: #a50;} 106 | .cm-s-default .cm-string {color: #a11;} 107 | .cm-s-default .cm-string-2 {color: #f50;} 108 | .cm-s-default .cm-meta {color: #555;} 109 | .cm-s-default .cm-qualifier {color: #555;} 110 | .cm-s-default .cm-builtin {color: #30a;} 111 | .cm-s-default .cm-bracket {color: #997;} 112 | .cm-s-default .cm-tag {color: #170;} 113 | .cm-s-default .cm-attribute {color: #00c;} 114 | .cm-s-default .cm-header {color: blue;} 115 | .cm-s-default .cm-quote {color: #090;} 116 | .cm-s-default .cm-hr {color: #999;} 117 | .cm-s-default .cm-link {color: #00c;} 118 | 119 | .cm-negative {color: #d44;} 120 | .cm-positive {color: #292;} 121 | .cm-header, .cm-strong {font-weight: bold;} 122 | .cm-em {font-style: italic;} 123 | .cm-link {text-decoration: underline;} 124 | .cm-strikethrough {text-decoration: line-through;} 125 | 126 | .cm-s-default .cm-error {color: #f00;} 127 | .cm-invalidchar {color: #f00;} 128 | 129 | .CodeMirror-composing { border-bottom: 2px solid; } 130 | 131 | /* Default styles for common addons */ 132 | 133 | div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;} 134 | div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} 135 | .CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); } 136 | .CodeMirror-activeline-background {background: #e8f2ff;} 137 | 138 | /* STOP */ 139 | 140 | /* The rest of this file contains styles related to the mechanics of 141 | the editor. You probably shouldn't touch them. */ 142 | 143 | .CodeMirror { 144 | position: relative; 145 | overflow: hidden; 146 | background: white; 147 | } 148 | 149 | .CodeMirror-scroll { 150 | overflow: scroll !important; /* Things will break if this is overridden */ 151 | /* 30px is the magic margin used to hide the element's real scrollbars */ 152 | /* See overflow: hidden in .CodeMirror */ 153 | margin-bottom: -30px; margin-right: -30px; 154 | padding-bottom: 30px; 155 | height: 100%; 156 | outline: none; /* Prevent dragging from highlighting the element */ 157 | position: relative; 158 | } 159 | .CodeMirror-sizer { 160 | position: relative; 161 | border-right: 30px solid transparent; 162 | } 163 | 164 | /* The fake, visible scrollbars. Used to force redraw during scrolling 165 | before actuall scrolling happens, thus preventing shaking and 166 | flickering artifacts. */ 167 | .CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { 168 | position: absolute; 169 | z-index: 6; 170 | display: none; 171 | } 172 | .CodeMirror-vscrollbar { 173 | right: 0; top: 0; 174 | overflow-x: hidden; 175 | overflow-y: scroll; 176 | } 177 | .CodeMirror-hscrollbar { 178 | bottom: 0; left: 0; 179 | overflow-y: hidden; 180 | overflow-x: scroll; 181 | } 182 | .CodeMirror-scrollbar-filler { 183 | right: 0; bottom: 0; 184 | } 185 | .CodeMirror-gutter-filler { 186 | left: 0; bottom: 0; 187 | } 188 | 189 | .CodeMirror-gutters { 190 | position: absolute; left: 0; top: 0; 191 | z-index: 3; 192 | } 193 | .CodeMirror-gutter { 194 | white-space: normal; 195 | height: 100%; 196 | display: inline-block; 197 | margin-bottom: -30px; 198 | /* Hack to make IE7 behave */ 199 | *zoom:1; 200 | *display:inline; 201 | } 202 | .CodeMirror-gutter-wrapper { 203 | position: absolute; 204 | z-index: 4; 205 | height: 100%; 206 | } 207 | .CodeMirror-gutter-elt { 208 | position: absolute; 209 | cursor: default; 210 | z-index: 4; 211 | } 212 | .CodeMirror-gutter-wrapper { 213 | -webkit-user-select: none; 214 | -moz-user-select: none; 215 | user-select: none; 216 | } 217 | 218 | .CodeMirror-lines { 219 | cursor: text; 220 | min-height: 1px; /* prevents collapsing before first draw */ 221 | } 222 | .CodeMirror pre { 223 | /* Reset some styles that the rest of the page might have set */ 224 | -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0; 225 | border-width: 0; 226 | background: transparent; 227 | font-family: inherit; 228 | font-size: inherit; 229 | margin: 0; 230 | white-space: pre; 231 | word-wrap: normal; 232 | line-height: inherit; 233 | color: inherit; 234 | z-index: 2; 235 | position: relative; 236 | overflow: visible; 237 | -webkit-tap-highlight-color: transparent; 238 | } 239 | .CodeMirror-wrap pre { 240 | word-wrap: break-word; 241 | white-space: pre-wrap; 242 | word-break: normal; 243 | } 244 | 245 | .CodeMirror-linebackground { 246 | position: absolute; 247 | left: 0; right: 0; top: 0; bottom: 0; 248 | z-index: 0; 249 | } 250 | 251 | .CodeMirror-linewidget { 252 | position: relative; 253 | z-index: 2; 254 | overflow: auto; 255 | } 256 | 257 | .CodeMirror-widget {} 258 | 259 | .CodeMirror-code { 260 | outline: none; 261 | } 262 | 263 | /* Force content-box sizing for the elements where we expect it */ 264 | .CodeMirror-scroll, 265 | .CodeMirror-sizer, 266 | .CodeMirror-gutter, 267 | .CodeMirror-gutters, 268 | .CodeMirror-linenumber { 269 | -moz-box-sizing: content-box; 270 | box-sizing: content-box; 271 | } 272 | 273 | .CodeMirror-measure { 274 | position: absolute; 275 | width: 100%; 276 | height: 0; 277 | overflow: hidden; 278 | visibility: hidden; 279 | } 280 | .CodeMirror-measure pre { position: static; } 281 | 282 | .CodeMirror div.CodeMirror-cursor { 283 | position: absolute; 284 | border-right: none; 285 | width: 0; 286 | } 287 | 288 | div.CodeMirror-cursors { 289 | visibility: hidden; 290 | position: relative; 291 | z-index: 3; 292 | } 293 | .CodeMirror-focused div.CodeMirror-cursors { 294 | visibility: visible; 295 | } 296 | 297 | .CodeMirror-selected { background: #d9d9d9; } 298 | .CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; } 299 | .CodeMirror-crosshair { cursor: crosshair; } 300 | .CodeMirror ::selection { background: #d7d4f0; } 301 | .CodeMirror ::-moz-selection { background: #d7d4f0; } 302 | 303 | .cm-searching { 304 | background: #ffa; 305 | background: rgba(255, 255, 0, .4); 306 | } 307 | 308 | /* IE7 hack to prevent it from returning funny offsetTops on the spans */ 309 | .CodeMirror span { *vertical-align: text-bottom; } 310 | 311 | /* Used to force a border model for a node */ 312 | .cm-force-border { padding-right: .1px; } 313 | 314 | @media print { 315 | /* Hide the cursor when printing */ 316 | .CodeMirror div.CodeMirror-cursors { 317 | visibility: hidden; 318 | } 319 | } 320 | 321 | /* See issue #2901 */ 322 | .cm-tab-wrap-hack:after { content: ''; } 323 | 324 | /* Help users use markselection to safely style text background */ 325 | span.CodeMirror-selectedtext { background: none; } 326 | -------------------------------------------------------------------------------- /example/js/codemirror/htmlmixed.js: -------------------------------------------------------------------------------- 1 | // CodeMirror, copyright (c) by Marijn Haverbeke and others 2 | // Distributed under an MIT license: http://codemirror.net/LICENSE 3 | 4 | (function(mod) { 5 | if (typeof exports == "object" && typeof module == "object") // CommonJS 6 | mod(require("../../lib/codemirror"), require("../xml/xml"), require("../javascript/javascript"), require("../css/css")); 7 | else if (typeof define == "function" && define.amd) // AMD 8 | define(["../../lib/codemirror", "../xml/xml", "../javascript/javascript", "../css/css"], mod); 9 | else // Plain browser env 10 | mod(CodeMirror); 11 | })(function(CodeMirror) { 12 | "use strict"; 13 | 14 | CodeMirror.defineMode("htmlmixed", function(config, parserConfig) { 15 | var htmlMode = CodeMirror.getMode(config, {name: "xml", 16 | htmlMode: true, 17 | multilineTagIndentFactor: parserConfig.multilineTagIndentFactor, 18 | multilineTagIndentPastTag: parserConfig.multilineTagIndentPastTag}); 19 | var cssMode = CodeMirror.getMode(config, "css"); 20 | 21 | var scriptTypes = [], scriptTypesConf = parserConfig && parserConfig.scriptTypes; 22 | scriptTypes.push({matches: /^(?:text|application)\/(?:x-)?(?:java|ecma)script$|^$/i, 23 | mode: CodeMirror.getMode(config, "javascript")}); 24 | if (scriptTypesConf) for (var i = 0; i < scriptTypesConf.length; ++i) { 25 | var conf = scriptTypesConf[i]; 26 | scriptTypes.push({matches: conf.matches, mode: conf.mode && CodeMirror.getMode(config, conf.mode)}); 27 | } 28 | scriptTypes.push({matches: /./, 29 | mode: CodeMirror.getMode(config, "text/plain")}); 30 | 31 | function html(stream, state) { 32 | var tagName = state.htmlState.tagName; 33 | if (tagName) tagName = tagName.toLowerCase(); 34 | var style = htmlMode.token(stream, state.htmlState); 35 | if (tagName == "script" && /\btag\b/.test(style) && stream.current() == ">") { 36 | // Script block: mode to change to depends on type attribute 37 | var scriptType = stream.string.slice(Math.max(0, stream.pos - 100), stream.pos).match(/\btype\s*=\s*("[^"]+"|'[^']+'|\S+)[^<]*$/i); 38 | scriptType = scriptType ? scriptType[1] : ""; 39 | if (scriptType && /[\"\']/.test(scriptType.charAt(0))) scriptType = scriptType.slice(1, scriptType.length - 1); 40 | for (var i = 0; i < scriptTypes.length; ++i) { 41 | var tp = scriptTypes[i]; 42 | if (typeof tp.matches == "string" ? scriptType == tp.matches : tp.matches.test(scriptType)) { 43 | if (tp.mode) { 44 | state.token = script; 45 | state.localMode = tp.mode; 46 | state.localState = tp.mode.startState && tp.mode.startState(htmlMode.indent(state.htmlState, "")); 47 | } 48 | break; 49 | } 50 | } 51 | } else if (tagName == "style" && /\btag\b/.test(style) && stream.current() == ">") { 52 | state.token = css; 53 | state.localMode = cssMode; 54 | state.localState = cssMode.startState(htmlMode.indent(state.htmlState, "")); 55 | } 56 | return style; 57 | } 58 | function maybeBackup(stream, pat, style) { 59 | var cur = stream.current(); 60 | var close = cur.search(pat), m; 61 | if (close > -1) stream.backUp(cur.length - close); 62 | else if (m = cur.match(/<\/?$/)) { 63 | stream.backUp(cur.length); 64 | if (!stream.match(pat, false)) stream.match(cur); 65 | } 66 | return style; 67 | } 68 | function script(stream, state) { 69 | if (stream.match(/^<\/\s*script\s*>/i, false)) { 70 | state.token = html; 71 | state.localState = state.localMode = null; 72 | return null; 73 | } 74 | return maybeBackup(stream, /<\/\s*script\s*>/, 75 | state.localMode.token(stream, state.localState)); 76 | } 77 | function css(stream, state) { 78 | if (stream.match(/^<\/\s*style\s*>/i, false)) { 79 | state.token = html; 80 | state.localState = state.localMode = null; 81 | return null; 82 | } 83 | return maybeBackup(stream, /<\/\s*style\s*>/, 84 | cssMode.token(stream, state.localState)); 85 | } 86 | 87 | return { 88 | startState: function() { 89 | var state = htmlMode.startState(); 90 | return {token: html, localMode: null, localState: null, htmlState: state}; 91 | }, 92 | 93 | copyState: function(state) { 94 | if (state.localState) 95 | var local = CodeMirror.copyState(state.localMode, state.localState); 96 | return {token: state.token, localMode: state.localMode, localState: local, 97 | htmlState: CodeMirror.copyState(htmlMode, state.htmlState)}; 98 | }, 99 | 100 | token: function(stream, state) { 101 | return state.token(stream, state); 102 | }, 103 | 104 | indent: function(state, textAfter) { 105 | if (!state.localMode || /^\s*<\//.test(textAfter)) 106 | return htmlMode.indent(state.htmlState, textAfter); 107 | else if (state.localMode.indent) 108 | return state.localMode.indent(state.localState, textAfter); 109 | else 110 | return CodeMirror.Pass; 111 | }, 112 | 113 | innerMode: function(state) { 114 | return {state: state.localState || state.htmlState, mode: state.localMode || htmlMode}; 115 | } 116 | }; 117 | }, "xml", "javascript", "css"); 118 | 119 | CodeMirror.defineMIME("text/html", "htmlmixed"); 120 | 121 | }); 122 | -------------------------------------------------------------------------------- /example/js/codemirror/xml.js: -------------------------------------------------------------------------------- 1 | // CodeMirror, copyright (c) by Marijn Haverbeke and others 2 | // Distributed under an MIT license: http://codemirror.net/LICENSE 3 | 4 | (function(mod) { 5 | if (typeof exports == "object" && typeof module == "object") // CommonJS 6 | mod(require("../../lib/codemirror")); 7 | else if (typeof define == "function" && define.amd) // AMD 8 | define(["../../lib/codemirror"], mod); 9 | else // Plain browser env 10 | mod(CodeMirror); 11 | })(function(CodeMirror) { 12 | "use strict"; 13 | 14 | CodeMirror.defineMode("xml", function(config, parserConfig) { 15 | var indentUnit = config.indentUnit; 16 | var multilineTagIndentFactor = parserConfig.multilineTagIndentFactor || 1; 17 | var multilineTagIndentPastTag = parserConfig.multilineTagIndentPastTag; 18 | if (multilineTagIndentPastTag == null) multilineTagIndentPastTag = true; 19 | 20 | var Kludges = parserConfig.htmlMode ? { 21 | autoSelfClosers: {'area': true, 'base': true, 'br': true, 'col': true, 'command': true, 22 | 'embed': true, 'frame': true, 'hr': true, 'img': true, 'input': true, 23 | 'keygen': true, 'link': true, 'meta': true, 'param': true, 'source': true, 24 | 'track': true, 'wbr': true, 'menuitem': true}, 25 | implicitlyClosed: {'dd': true, 'li': true, 'optgroup': true, 'option': true, 'p': true, 26 | 'rp': true, 'rt': true, 'tbody': true, 'td': true, 'tfoot': true, 27 | 'th': true, 'tr': true}, 28 | contextGrabbers: { 29 | 'dd': {'dd': true, 'dt': true}, 30 | 'dt': {'dd': true, 'dt': true}, 31 | 'li': {'li': true}, 32 | 'option': {'option': true, 'optgroup': true}, 33 | 'optgroup': {'optgroup': true}, 34 | 'p': {'address': true, 'article': true, 'aside': true, 'blockquote': true, 'dir': true, 35 | 'div': true, 'dl': true, 'fieldset': true, 'footer': true, 'form': true, 36 | 'h1': true, 'h2': true, 'h3': true, 'h4': true, 'h5': true, 'h6': true, 37 | 'header': true, 'hgroup': true, 'hr': true, 'menu': true, 'nav': true, 'ol': true, 38 | 'p': true, 'pre': true, 'section': true, 'table': true, 'ul': true}, 39 | 'rp': {'rp': true, 'rt': true}, 40 | 'rt': {'rp': true, 'rt': true}, 41 | 'tbody': {'tbody': true, 'tfoot': true}, 42 | 'td': {'td': true, 'th': true}, 43 | 'tfoot': {'tbody': true}, 44 | 'th': {'td': true, 'th': true}, 45 | 'thead': {'tbody': true, 'tfoot': true}, 46 | 'tr': {'tr': true} 47 | }, 48 | doNotIndent: {"pre": true}, 49 | allowUnquoted: true, 50 | allowMissing: true, 51 | caseFold: true 52 | } : { 53 | autoSelfClosers: {}, 54 | implicitlyClosed: {}, 55 | contextGrabbers: {}, 56 | doNotIndent: {}, 57 | allowUnquoted: false, 58 | allowMissing: false, 59 | caseFold: false 60 | }; 61 | var alignCDATA = parserConfig.alignCDATA; 62 | 63 | // Return variables for tokenizers 64 | var type, setStyle; 65 | 66 | function inText(stream, state) { 67 | function chain(parser) { 68 | state.tokenize = parser; 69 | return parser(stream, state); 70 | } 71 | 72 | var ch = stream.next(); 73 | if (ch == "<") { 74 | if (stream.eat("!")) { 75 | if (stream.eat("[")) { 76 | if (stream.match("CDATA[")) return chain(inBlock("atom", "]]>")); 77 | else return null; 78 | } else if (stream.match("--")) { 79 | return chain(inBlock("comment", "-->")); 80 | } else if (stream.match("DOCTYPE", true, true)) { 81 | stream.eatWhile(/[\w\._\-]/); 82 | return chain(doctype(1)); 83 | } else { 84 | return null; 85 | } 86 | } else if (stream.eat("?")) { 87 | stream.eatWhile(/[\w\._\-]/); 88 | state.tokenize = inBlock("meta", "?>"); 89 | return "meta"; 90 | } else { 91 | type = stream.eat("/") ? "closeTag" : "openTag"; 92 | state.tokenize = inTag; 93 | return "tag bracket"; 94 | } 95 | } else if (ch == "&") { 96 | var ok; 97 | if (stream.eat("#")) { 98 | if (stream.eat("x")) { 99 | ok = stream.eatWhile(/[a-fA-F\d]/) && stream.eat(";"); 100 | } else { 101 | ok = stream.eatWhile(/[\d]/) && stream.eat(";"); 102 | } 103 | } else { 104 | ok = stream.eatWhile(/[\w\.\-:]/) && stream.eat(";"); 105 | } 106 | return ok ? "atom" : "error"; 107 | } else { 108 | stream.eatWhile(/[^&<]/); 109 | return null; 110 | } 111 | } 112 | 113 | function inTag(stream, state) { 114 | var ch = stream.next(); 115 | if (ch == ">" || (ch == "/" && stream.eat(">"))) { 116 | state.tokenize = inText; 117 | type = ch == ">" ? "endTag" : "selfcloseTag"; 118 | return "tag bracket"; 119 | } else if (ch == "=") { 120 | type = "equals"; 121 | return null; 122 | } else if (ch == "<") { 123 | state.tokenize = inText; 124 | state.state = baseState; 125 | state.tagName = state.tagStart = null; 126 | var next = state.tokenize(stream, state); 127 | return next ? next + " tag error" : "tag error"; 128 | } else if (/[\'\"]/.test(ch)) { 129 | state.tokenize = inAttribute(ch); 130 | state.stringStartCol = stream.column(); 131 | return state.tokenize(stream, state); 132 | } else { 133 | stream.match(/^[^\s\u00a0=<>\"\']*[^\s\u00a0=<>\"\'\/]/); 134 | return "word"; 135 | } 136 | } 137 | 138 | function inAttribute(quote) { 139 | var closure = function(stream, state) { 140 | while (!stream.eol()) { 141 | if (stream.next() == quote) { 142 | state.tokenize = inTag; 143 | break; 144 | } 145 | } 146 | return "string"; 147 | }; 148 | closure.isInAttribute = true; 149 | return closure; 150 | } 151 | 152 | function inBlock(style, terminator) { 153 | return function(stream, state) { 154 | while (!stream.eol()) { 155 | if (stream.match(terminator)) { 156 | state.tokenize = inText; 157 | break; 158 | } 159 | stream.next(); 160 | } 161 | return style; 162 | }; 163 | } 164 | function doctype(depth) { 165 | return function(stream, state) { 166 | var ch; 167 | while ((ch = stream.next()) != null) { 168 | if (ch == "<") { 169 | state.tokenize = doctype(depth + 1); 170 | return state.tokenize(stream, state); 171 | } else if (ch == ">") { 172 | if (depth == 1) { 173 | state.tokenize = inText; 174 | break; 175 | } else { 176 | state.tokenize = doctype(depth - 1); 177 | return state.tokenize(stream, state); 178 | } 179 | } 180 | } 181 | return "meta"; 182 | }; 183 | } 184 | 185 | function Context(state, tagName, startOfLine) { 186 | this.prev = state.context; 187 | this.tagName = tagName; 188 | this.indent = state.indented; 189 | this.startOfLine = startOfLine; 190 | if (Kludges.doNotIndent.hasOwnProperty(tagName) || (state.context && state.context.noIndent)) 191 | this.noIndent = true; 192 | } 193 | function popContext(state) { 194 | if (state.context) state.context = state.context.prev; 195 | } 196 | function maybePopContext(state, nextTagName) { 197 | var parentTagName; 198 | while (true) { 199 | if (!state.context) { 200 | return; 201 | } 202 | parentTagName = state.context.tagName; 203 | if (!Kludges.contextGrabbers.hasOwnProperty(parentTagName) || 204 | !Kludges.contextGrabbers[parentTagName].hasOwnProperty(nextTagName)) { 205 | return; 206 | } 207 | popContext(state); 208 | } 209 | } 210 | 211 | function baseState(type, stream, state) { 212 | if (type == "openTag") { 213 | state.tagStart = stream.column(); 214 | return tagNameState; 215 | } else if (type == "closeTag") { 216 | return closeTagNameState; 217 | } else { 218 | return baseState; 219 | } 220 | } 221 | function tagNameState(type, stream, state) { 222 | if (type == "word") { 223 | state.tagName = stream.current(); 224 | setStyle = "tag"; 225 | return attrState; 226 | } else { 227 | setStyle = "error"; 228 | return tagNameState; 229 | } 230 | } 231 | function closeTagNameState(type, stream, state) { 232 | if (type == "word") { 233 | var tagName = stream.current(); 234 | if (state.context && state.context.tagName != tagName && 235 | Kludges.implicitlyClosed.hasOwnProperty(state.context.tagName)) 236 | popContext(state); 237 | if (state.context && state.context.tagName == tagName) { 238 | setStyle = "tag"; 239 | return closeState; 240 | } else { 241 | setStyle = "tag error"; 242 | return closeStateErr; 243 | } 244 | } else { 245 | setStyle = "error"; 246 | return closeStateErr; 247 | } 248 | } 249 | 250 | function closeState(type, _stream, state) { 251 | if (type != "endTag") { 252 | setStyle = "error"; 253 | return closeState; 254 | } 255 | popContext(state); 256 | return baseState; 257 | } 258 | function closeStateErr(type, stream, state) { 259 | setStyle = "error"; 260 | return closeState(type, stream, state); 261 | } 262 | 263 | function attrState(type, _stream, state) { 264 | if (type == "word") { 265 | setStyle = "attribute"; 266 | return attrEqState; 267 | } else if (type == "endTag" || type == "selfcloseTag") { 268 | var tagName = state.tagName, tagStart = state.tagStart; 269 | state.tagName = state.tagStart = null; 270 | if (type == "selfcloseTag" || 271 | Kludges.autoSelfClosers.hasOwnProperty(tagName)) { 272 | maybePopContext(state, tagName); 273 | } else { 274 | maybePopContext(state, tagName); 275 | state.context = new Context(state, tagName, tagStart == state.indented); 276 | } 277 | return baseState; 278 | } 279 | setStyle = "error"; 280 | return attrState; 281 | } 282 | function attrEqState(type, stream, state) { 283 | if (type == "equals") return attrValueState; 284 | if (!Kludges.allowMissing) setStyle = "error"; 285 | return attrState(type, stream, state); 286 | } 287 | function attrValueState(type, stream, state) { 288 | if (type == "string") return attrContinuedState; 289 | if (type == "word" && Kludges.allowUnquoted) {setStyle = "string"; return attrState;} 290 | setStyle = "error"; 291 | return attrState(type, stream, state); 292 | } 293 | function attrContinuedState(type, stream, state) { 294 | if (type == "string") return attrContinuedState; 295 | return attrState(type, stream, state); 296 | } 297 | 298 | return { 299 | startState: function() { 300 | return {tokenize: inText, 301 | state: baseState, 302 | indented: 0, 303 | tagName: null, tagStart: null, 304 | context: null}; 305 | }, 306 | 307 | token: function(stream, state) { 308 | if (!state.tagName && stream.sol()) 309 | state.indented = stream.indentation(); 310 | 311 | if (stream.eatSpace()) return null; 312 | type = null; 313 | var style = state.tokenize(stream, state); 314 | if ((style || type) && style != "comment") { 315 | setStyle = null; 316 | state.state = state.state(type || style, stream, state); 317 | if (setStyle) 318 | style = setStyle == "error" ? style + " error" : setStyle; 319 | } 320 | return style; 321 | }, 322 | 323 | indent: function(state, textAfter, fullLine) { 324 | var context = state.context; 325 | // Indent multi-line strings (e.g. css). 326 | if (state.tokenize.isInAttribute) { 327 | if (state.tagStart == state.indented) 328 | return state.stringStartCol + 1; 329 | else 330 | return state.indented + indentUnit; 331 | } 332 | if (context && context.noIndent) return CodeMirror.Pass; 333 | if (state.tokenize != inTag && state.tokenize != inText) 334 | return fullLine ? fullLine.match(/^(\s*)/)[0].length : 0; 335 | // Indent the starts of attribute names. 336 | if (state.tagName) { 337 | if (multilineTagIndentPastTag) 338 | return state.tagStart + state.tagName.length + 2; 339 | else 340 | return state.tagStart + indentUnit * multilineTagIndentFactor; 341 | } 342 | if (alignCDATA && /$/, 371 | blockCommentStart: "", 373 | 374 | configuration: parserConfig.htmlMode ? "html" : "xml", 375 | helperType: parserConfig.htmlMode ? "html" : "xml" 376 | }; 377 | }); 378 | 379 | CodeMirror.defineMIME("text/xml", "xml"); 380 | CodeMirror.defineMIME("application/xml", "xml"); 381 | if (!CodeMirror.mimeModes.hasOwnProperty("text/html")) 382 | CodeMirror.defineMIME("text/html", {name: "xml", htmlMode: true}); 383 | 384 | }); 385 | -------------------------------------------------------------------------------- /example/js/ctrl/serverPagingCtrl.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module 3 | * 4 | * gTableTest Description 5 | */ 6 | angular.module('test') 7 | .controller('serverPagingController', function ($scope, $http,$timeout) { 8 | 9 | 10 | /* ### Simple server paging ### */ 11 | $scope.simplePaging = { 12 | data:null, 13 | display:0, 14 | currentPage:0, 15 | total:0, 16 | pages:0 17 | }; 18 | 19 | $scope.loadSimpleData = function(n){ 20 | 21 | //don't load if n==0 or n>pages 22 | if($scope.simplePaging.pages){ 23 | if(n==0 || n > $scope.simplePaging.pages) return; 24 | }; 25 | 26 | //load data 27 | $http.get('test-data/data-page'+ n +'.json').then(function(response){ 28 | $scope.simplePaging.data = response.data.data; 29 | $scope.simplePaging.display = response.data.limit; 30 | $scope.simplePaging.currentPage = response.data.page; 31 | $scope.simplePaging.total = response.data.total; 32 | 33 | //calculate page count 34 | if(!$scope.simplePaging.pages){ 35 | $scope.simplePaging.pages = Math.round(response.data.total / response.data.limit); 36 | }; 37 | 38 | }); 39 | 40 | }; 41 | 42 | // load first page 43 | $scope.loadSimpleData(1); 44 | 45 | /* [END] Simple server paging ### */ 46 | 47 | 48 | /* ### Advanced server paging ### */ 49 | $scope.advPaging = { 50 | data:null, 51 | display:0, 52 | currentPage:0, 53 | total:0, 54 | pages:[] 55 | }; 56 | 57 | var ctrl = this; 58 | 59 | $scope.loadData = function(n){ 60 | 61 | //don't load if n==0 or n>pages 62 | if($scope.advPaging.pages.length){ 63 | if(n==0 || n > $scope.advPaging.pages.length) return; 64 | }; 65 | 66 | //load data ($timeout to emulate delay) 67 | $scope.advPaging.data=[]; 68 | $timeout(function(){ 69 | $http.get('test-data/data-page'+ n +'.json').then(function(response){ 70 | $scope.advPaging.data = response.data.data; 71 | $scope.advPaging.display = response.data.limit; 72 | $scope.advPaging.currentPage = response.data.page; 73 | $scope.advPaging.total = response.data.total; 74 | 75 | //calculate pages just once - after first loading 76 | if(!$scope.advPaging.pages.length){ 77 | ctrl.getTotalPages(); 78 | }; 79 | }); 80 | },2000); 81 | 82 | }; 83 | 84 | // load first page 85 | $scope.loadData(1); 86 | 87 | // calculate totals and return page range ([1,2,3]) 88 | this.getTotalPages = function(){ 89 | var count = Math.round($scope.advPaging.total / $scope.advPaging.display); 90 | for (var i = 0; i < count; i++) { 91 | $scope.advPaging.pages.push(i); 92 | }; 93 | }; 94 | 95 | /* [END] Advanced server paging ### */ 96 | 97 | 98 | /*codemirror*/ 99 | $scope.editorOptions = { 100 | lineNumbers: true, 101 | readOnly: 'nocursor' 102 | }; 103 | 104 | $scope.editorOptionsJS = { 105 | lineNumbers: true, 106 | readOnly: 'nocursor', 107 | mode:"javascript" 108 | }; 109 | 110 | 111 | }); -------------------------------------------------------------------------------- /example/js/example.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module 3 | * 4 | * gTableTest Description 5 | */ 6 | angular.module('test', ['objectTable','ngRoute','ui.codemirror']) 7 | .controller('mainController', function ($scope, $routeParams,$location,$timeout) { 8 | 9 | $scope.state = $routeParams.template; 10 | 11 | $scope.data = [{name: "Moroni", age: 50, money: -10}, 12 | {name: "Tiancum", age: 43,money: 120}, 13 | {name: "Jacob", age: 27, money: 5.5}, 14 | {name: "Nephi", age: 29,money: -54}, 15 | {name: "Enos", age: 34,money: 110}, 16 | {name: "Tiancum", age: 43, money: 1000}, 17 | {name: "Jacob", age: 27,money: -201}, 18 | {name: "Nephi", age: 29, money: 100}, 19 | {name: "Enos", age: 34, money: -52.5}, 20 | {name: "Tiancum", age: 43, money: 52.1}, 21 | {name: "Jacob", age: 27, money: 110}, 22 | {name: "Nephi", age: 29, money: -55}, 23 | {name: "Enos", age: 34, money: 551}, 24 | {name: "Tiancum", age: 43, money: -1410}, 25 | {name: "Jacob", age: 27, money: 410}, 26 | {name: "Nephi", age: 29, money: 100}, 27 | {name: "Enos", age: 34, money: -100}]; 28 | 29 | $scope.report = { 30 | selectedPerson:null 31 | }; 32 | 33 | $scope.test = function(e) { 34 | alert('Alert from controller method!'); 35 | }; 36 | 37 | $scope.showItem = function(item) { 38 | alert(JSON.stringify(item)); 39 | }; 40 | 41 | $scope.getTotalBalance = function(data){ 42 | //return if empty or not ready 43 | if(!data || !data.length) return; 44 | 45 | var totalNumber = 0; 46 | for(var i=0; i element. 5 | */ 6 | angular.module('ui.codemirror', []) 7 | .constant('uiCodemirrorConfig', {}) 8 | .directive('uiCodemirror', uiCodemirrorDirective); 9 | 10 | /** 11 | * @ngInject 12 | */ 13 | function uiCodemirrorDirective($timeout, uiCodemirrorConfig) { 14 | 15 | return { 16 | restrict: 'EA', 17 | require: '?ngModel', 18 | compile: function compile() { 19 | 20 | // Require CodeMirror 21 | if (angular.isUndefined(window.CodeMirror)) { 22 | throw new Error('ui-codemirror needs CodeMirror to work... (o rly?)'); 23 | } 24 | 25 | return postLink; 26 | } 27 | }; 28 | 29 | function postLink(scope, iElement, iAttrs, ngModel) { 30 | 31 | var codemirrorOptions = angular.extend( 32 | { value: iElement.text() }, 33 | uiCodemirrorConfig.codemirror || {}, 34 | scope.$eval(iAttrs.uiCodemirror), 35 | scope.$eval(iAttrs.uiCodemirrorOpts) 36 | ); 37 | 38 | var codemirror = newCodemirrorEditor(iElement, codemirrorOptions); 39 | 40 | configOptionsWatcher( 41 | codemirror, 42 | iAttrs.uiCodemirror || iAttrs.uiCodemirrorOpts, 43 | scope 44 | ); 45 | 46 | configNgModelLink(codemirror, ngModel, scope); 47 | 48 | configUiRefreshAttribute(codemirror, iAttrs.uiRefresh, scope); 49 | 50 | // Allow access to the CodeMirror instance through a broadcasted event 51 | // eg: $broadcast('CodeMirror', function(cm){...}); 52 | scope.$on('CodeMirror', function(event, callback) { 53 | if (angular.isFunction(callback)) { 54 | callback(codemirror); 55 | } else { 56 | throw new Error('the CodeMirror event requires a callback function'); 57 | } 58 | }); 59 | 60 | // onLoad callback 61 | if (angular.isFunction(codemirrorOptions.onLoad)) { 62 | codemirrorOptions.onLoad(codemirror); 63 | } 64 | } 65 | 66 | function newCodemirrorEditor(iElement, codemirrorOptions) { 67 | var codemirrot; 68 | 69 | if (iElement[0].tagName === 'TEXTAREA') { 70 | // Might bug but still ... 71 | codemirrot = window.CodeMirror.fromTextArea(iElement[0], codemirrorOptions); 72 | } else { 73 | iElement.html(''); 74 | codemirrot = new window.CodeMirror(function(cm_el) { 75 | iElement.append(cm_el); 76 | }, codemirrorOptions); 77 | } 78 | 79 | return codemirrot; 80 | } 81 | 82 | function configOptionsWatcher(codemirrot, uiCodemirrorAttr, scope) { 83 | if (!uiCodemirrorAttr) { return; } 84 | 85 | var codemirrorDefaultsKeys = Object.keys(window.CodeMirror.defaults); 86 | scope.$watch(uiCodemirrorAttr, updateOptions, true); 87 | function updateOptions(newValues, oldValue) { 88 | if (!angular.isObject(newValues)) { return; } 89 | codemirrorDefaultsKeys.forEach(function(key) { 90 | if (newValues.hasOwnProperty(key)) { 91 | 92 | if (oldValue && newValues[key] === oldValue[key]) { 93 | return; 94 | } 95 | 96 | codemirrot.setOption(key, newValues[key]); 97 | } 98 | }); 99 | } 100 | } 101 | 102 | function configNgModelLink(codemirror, ngModel, scope) { 103 | if (!ngModel) { return; } 104 | // CodeMirror expects a string, so make sure it gets one. 105 | // This does not change the model. 106 | ngModel.$formatters.push(function(value) { 107 | if (angular.isUndefined(value) || value === null) { 108 | return ''; 109 | } else if (angular.isObject(value) || angular.isArray(value)) { 110 | throw new Error('ui-codemirror cannot use an object or an array as a model'); 111 | } 112 | return value; 113 | }); 114 | 115 | 116 | // Override the ngModelController $render method, which is what gets called when the model is updated. 117 | // This takes care of the synchronizing the codeMirror element with the underlying model, in the case that it is changed by something else. 118 | ngModel.$render = function() { 119 | //Code mirror expects a string so make sure it gets one 120 | //Although the formatter have already done this, it can be possible that another formatter returns undefined (for example the required directive) 121 | var safeViewValue = ngModel.$viewValue || ''; 122 | codemirror.setValue(safeViewValue); 123 | }; 124 | 125 | 126 | // Keep the ngModel in sync with changes from CodeMirror 127 | codemirror.on('change', function(instance) { 128 | var newValue = instance.getValue(); 129 | if (newValue !== ngModel.$viewValue) { 130 | scope.$evalAsync(function() { 131 | ngModel.$setViewValue(newValue); 132 | }); 133 | } 134 | }); 135 | } 136 | 137 | function configUiRefreshAttribute(codeMirror, uiRefreshAttr, scope) { 138 | if (!uiRefreshAttr) { return; } 139 | 140 | scope.$watch(uiRefreshAttr, function(newVal, oldVal) { 141 | // Skip the initial watch firing 142 | if (newVal !== oldVal) { 143 | $timeout(function() { 144 | codeMirror.refresh(); 145 | }); 146 | } 147 | }); 148 | } 149 | 150 | } 151 | -------------------------------------------------------------------------------- /example/partials/additional_filters.html: -------------------------------------------------------------------------------- 1 |

Additional filters

2 |

add filtering by controller variables

3 |
4 |
5 | 11 | 12 |
13 |
14 | 15 |
16 |
17 | 18 |
19 |

20 |
29 |
30 | 31 | 32 |

In template:

33 | 63 |
64 |

Notice:

65 | $owner is a link to Controller 66 |
67 | 68 | -------------------------------------------------------------------------------- /example/partials/attributes.html: -------------------------------------------------------------------------------- 1 |

Possible attrributes:

2 |
3 |

Required:

4 |

5 |
    6 |
  • 7 | data="controllerArray" 8 | Data source (array of objects) in your Controller. But if 'fromUrl' is present 'data' atribute will contain controller link to empty array( will be fill up after receiving data ). 9 |
  • 10 |
  • 11 | headers="headerName1,headerName1,headerName3" 12 | Array of table header names 13 |
  • 14 |
  • 15 | fields="Property1, Property2" 16 | Array of displayed properties of object. This option allows you to display only certain fields of the object. Number of fields must be equal number of headers. 17 |
  • 18 |
19 | 20 |

Optional:

21 |

22 | 23 |
    24 |
  • 25 | fromUrl="URL" 26 | Load data from external URL. 27 |
  • 28 |
  • 29 | display="N" 30 | Display N records per page. 31 |
  • 32 |
  • 33 | search="true | false | separate" 34 | Display search input. Value "separate" is allows you search by columns. 35 |
  • 36 |
  • 37 | paging="true | false" 38 | Use paging to present data. 39 |
  • 40 |
  • 41 | sorting="simple | compound | false" 42 | Use sorting. 'simple' - by single column (property), 'compound' - by few columns. 43 |
  • 44 |
  • 45 | select="multiply" 46 | Allows to select more than one row. Selected rows are accessible. 47 |
  • 48 |
  • 49 | editable="all" 50 | Allows to edit content inside cells. Edit uptates your angular model. 51 |
  • 52 |
  • 53 | selected-model="model" 54 | It exports selected model to controller variable. 55 |
  • 56 |
  • 57 | resize="true" 58 | Use column resizing. 59 |
  • 60 |
  • 61 | drag-columns="true" 62 | It allows to reorder your columns using drag-n-drop. 63 |
  • 64 |
65 | 66 |

Default values:

67 |

68 | 69 |
    70 |
  • 71 | display="5" 72 |
  • 73 |
  • 74 | paging="true" 75 |
  • 76 |
  • 77 | sorting="simple" 78 |
  • 79 | 80 |
  • 81 | resize="false" 82 |
  • 83 |
  • 84 | drag-columns="false" 85 |
  • 86 | 87 |
-------------------------------------------------------------------------------- /example/partials/basic.html: -------------------------------------------------------------------------------- 1 |

Bacis

2 | 3 |
12 |
13 | 14 | 15 |

In template:

16 | 27 | 28 |

In controller:

29 | 30 | 49 | 50 | -------------------------------------------------------------------------------- /example/partials/column_highlighting.html: -------------------------------------------------------------------------------- 1 |

Column highlighting

2 | 3 | 12 |
13 | 14 |
15 | 16 | 17 |

In template:

18 | 27 | 28 |
29 |

Notice:

30 | just add class class="hover-column" to add CSS column highlighting 31 |
32 | -------------------------------------------------------------------------------- /example/partials/compound_resize.html: -------------------------------------------------------------------------------- 1 |

Resizable table columns

2 | 3 | 9 |
10 | 11 |
12 | 13 |
14 |

Example

15 | Just add attribute resize to the table 16 |
17 | 18 | 19 |

In template:

20 | 29 | 30 |
31 |

Notice

32 | If you want to change in the size of the cell was changed and the length of the table, just add a CSS class with style width:auto to the table. Example: 33 | .object-table-module table{ width:auto; } 34 |
35 | 36 |

If you are using custom header:

37 |

1) Add ng-mousedown="resizeStart($event)" and ng-mouseup="resizeEnd($event)" to your <thead>tag.

38 |

2) Add <div class="resize"></div> to resized headers (<td> tag)

39 | 40 | 46 | 47 | 48 | 52 | 55 | 58 | 59 | 60 |
49 | 50 | Name of Person
51 |
53 | 54 | Eye
56 | 57 | Age
61 | 62 | 63 |

In template:

64 | 88 | -------------------------------------------------------------------------------- /example/partials/compound_sorting.html: -------------------------------------------------------------------------------- 1 |

Compound sorting

2 | 3 |
10 |
11 |
12 |

Example

13 | First try sort by 'Full Name' , then by 'Money' 14 |
15 | 24 |
-------------------------------------------------------------------------------- /example/partials/custom_headers.html: -------------------------------------------------------------------------------- 1 |

Custom headers pattern

2 | 3 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
Personal informationAdditional information
NameEye colorAgeBalanceCompanyAddressFavorite Fruit
26 | 27 |
28 | 29 | 30 | 31 | 32 |

In template:

33 | 78 | 79 |
80 |

Notice:

81 | If you want to allow sorting you need to add attributes to header (th): 82 | ng-click="sortBy('object.property')" and 83 | ng-class="headerIsSortedClass('Header Name')" 84 |
85 | -------------------------------------------------------------------------------- /example/partials/custom_rows.html: -------------------------------------------------------------------------------- 1 |

Rows pattern

2 | 3 | 11 | 12 | 13 | 14 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |
{{::item.name}}
15 | {{::item.eyeColor}} 16 |
{{::item.age}}{{::item.balance}}{{::item.company}}{{::item.address}}{{::item.favoriteFruit}}
25 |
26 | 27 | 28 |

In template:

29 | 54 | 55 |
56 |

Warning:

57 | If you want to use ng-attributes (like ng-if, ng-repeat,...) inside your template - please add to your <body> ng-non-bindable attribute to prevent Angular engine to compile attributes before table render. 58 |
59 | 60 |

Example:

61 | 62 | 67 | 68 | 69 | 70 | 73 | 74 | 79 | 80 | 81 |
{{::item.name}}
71 | {{::item.eyeColor}} 72 |
{{::item.address}} 75 | 76 | {{::friend.name}} 77 | 78 |
82 | 83 | 84 |

In template:

85 | 107 | -------------------------------------------------------------------------------- /example/partials/custom_rows_headers.html: -------------------------------------------------------------------------------- 1 |

Custom rows and headers pattern:

2 | 3 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
Name..(click me)AgeMoney $
{{::item.name}}{{::item.age}}{{::item.money}} USD
26 |
27 | 28 | 29 |

In template:

30 | 56 | 57 |
58 |

Notice:

59 | $owner is a link to Controller 60 |
-------------------------------------------------------------------------------- /example/partials/draggable_headers.html: -------------------------------------------------------------------------------- 1 |

Draggable headers

2 | 3 | 11 |
12 | 13 | 14 |

In template:

15 | 26 |
27 |

Example

28 | Working perfect with resize resizing columns 29 |
30 | 31 |

If you are using custom templates:

32 |

add allow-drag attribute to draggable header

33 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 55 | 56 | 57 | 58 | 59 | 60 | 61 |
NameEye colorAgeBalanceCompanyFavorite Fruit
{{::item.name}}
53 | {{::item.eyeColor}} 54 |
{{::item.age}}{{::item.balance}}{{::item.balance}}{{::item.favoriteFruit}}
62 | 63 | 64 |

In template:

65 | 96 | -------------------------------------------------------------------------------- /example/partials/editable_cells.html: -------------------------------------------------------------------------------- 1 |

Editable cells (all)

2 | 3 | 12 |
13 |
14 | 15 | 16 |

In template:

17 | 28 | 29 |
30 |

Notice:

31 | Add attribute editable = "true" to allow to edit all cells. 32 |
33 | 34 |

Editable single column (Eye color)

35 | 36 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 |
{{::item.name}}{{item.eyeColor}}{{::item.age}}{{::item.balance}}{{::item.company}}{{::item.address}}{{::item.favoriteFruit}}
56 |
57 | 58 | 59 |

In template:

60 | 81 | 82 |
83 |

Notice:

84 | Add attribute editable to cell for editing. 85 |
86 | 87 | -------------------------------------------------------------------------------- /example/partials/external.html: -------------------------------------------------------------------------------- 1 |

External resource

2 | 3 |
10 |
11 | 12 | 13 |

In template:

14 | 23 | 24 |
25 |

Notice

26 | Directive exports all data to controller variable. 27 |
28 | 29 |

In controller:

30 | 34 | -------------------------------------------------------------------------------- /example/partials/footer_expression.html: -------------------------------------------------------------------------------- 1 |

Footer expressions

2 |

aggregate functions in footer

3 | 4 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
Total users:{{$filtered.length}}Total balance:{{ $owner.getTotalBalance($filtered) }} $
{{::item.name}}{{::item.age}}{{item.money}}
26 | 27 |
28 |

Please try to edit money column

29 |
30 | 31 | 32 |

In template:

33 | 56 | 57 |
58 |

Notice:

59 |

variable $filtered contains displayed array (It depends on the filters like search or your custom filters);

60 |

If you want to apply your aggregate function for all data array despite filters, please use getTotalBalance(data). In this case returned of function value won't be changed during search.

61 |
62 | 63 |
64 |

Notice:

65 |

The <tfoot> tag must be used in the following context: As a child of a <table> element, after any <caption>, <colgroup>, and <thead> elements and before any <tbody> and <tr> elements.

66 |
67 | 68 | 69 | 70 | 71 |

Method getTotalBalance in controller:

72 | -------------------------------------------------------------------------------- /example/partials/other.html: -------------------------------------------------------------------------------- 1 |

Without header

2 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
{{::item.name}}{{item.eyeColor}}{{::item.age}}{{::item.balance}}{{::item.company}}{{::item.address}}{{::item.favoriteFruit}}
20 |
21 | 22 | 23 |

In template:

24 | 44 | -------------------------------------------------------------------------------- /example/partials/pagination.html: -------------------------------------------------------------------------------- 1 |

Pagination

2 | 3 |
11 |
12 |
13 |

Notice

14 | paging ="true" by defaults. 15 |
16 | 17 | 18 |

In template:

19 | -------------------------------------------------------------------------------- /example/partials/select_multiply.html: -------------------------------------------------------------------------------- 1 |

Multiply selection

2 | 3 |
14 | 15 |

Selected:

16 |
 17 | 		{{report.selectedPerson}}
 18 | 	
19 |
20 | 21 | 22 |

In template:

23 | 40 | 41 |
42 |

Controller rows click event:

43 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 |
{{item.eyeColor}}{{::item.age}}{{::item.balance}}{{::item.company}}{{::item.address}}{{::item.favoriteFruit}}
61 | 62 | 63 |

In template:

64 | 84 | 85 | 86 | 87 |
88 |

Notice

89 | If you don't want to disable row selection, please add setSelected(item) to your ng-click.
90 | $owner is a link to Controller. 91 |
92 | 93 | 100 | -------------------------------------------------------------------------------- /example/partials/separate_search.html: -------------------------------------------------------------------------------- 1 |

Separate Search

2 |

Searching by each column

3 |
9 |
10 | 11 | 12 |

In template:

13 | -------------------------------------------------------------------------------- /example/partials/server_paging.html: -------------------------------------------------------------------------------- 1 |

Server Paging (Simple)

2 | 3 | 4 |
5 | Just imagine, server returns JSON, that contains such information:
6 | data: array of objects
7 | page: current number page
8 | limit: the amount of items on the page
9 | total: the total amount of objects (to calculate the number of pages).
10 |

Example


11 | 30 |
31 | 32 | 37 |
38 | 39 | 49 | 50 | 51 |

In template:

52 | 71 | 72 | 73 |

In controller:

74 | 109 |
110 | 111 |

Server Paging (Advanced)

112 | 113 | 118 |
119 | 120 | 121 |
122 | Loading Data... 123 |
124 | 125 | 126 | 143 | 144 | 145 |

In template:

146 | 178 | 179 | 180 |

In controller:

181 | -------------------------------------------------------------------------------- /example/partials/themes.html: -------------------------------------------------------------------------------- 1 |

Themes

2 | 3 |

Default theme:

4 |

Without additional classes

5 |
13 |
14 | 15 |
16 |

Notice

17 | Add class to table to apply theme (class="blue-dust") 18 |
19 | 20 |

In template:

21 | 30 |
31 | 32 |

Blue Dust

33 |

class="blue-dust"

34 | 35 |
43 |
44 | 45 |

Dark sky

46 |

class="dark-sky"

47 | 48 |
56 | -------------------------------------------------------------------------------- /example/test-data/data-page1.json: -------------------------------------------------------------------------------- 1 | { 2 | "data":[{"name": "Moroni", "age": 50, "money": -10}, 3 | {"name": "Tiancum", "age": 43,"money": 120}, 4 | {"name": "Jacob", "age": 27, "money": 5.5}, 5 | {"name": "Nephi", "age": 29,"money": -54}, 6 | {"name": "Enos", "age": 34,"money": 110}, 7 | {"name": "Tiancum", "age": 43, "money": 1000}, 8 | {"name": "Jacob", "age": 27,"money": -201}, 9 | {"name": "Nephi", "age": 29, "money": 100}, 10 | {"name": "Enos", "age": 34, "money": -52.5}, 11 | {"name": "Tiancum", "age": 43, "money": 52.1}], 12 | "page":1, 13 | "limit":10, 14 | "total":25 15 | } -------------------------------------------------------------------------------- /example/test-data/data-page2.json: -------------------------------------------------------------------------------- 1 | { 2 | "data":[{"name": "2Moroni", "age": 50, "money": -10}, 3 | {"name": "2Tiancum", "age": 43,"money": 120}, 4 | {"name": "2Jacob", "age": 27, "money": 5.5}, 5 | {"name": "Nephi", "age": 29,"money": -54}, 6 | {"name": "Enos", "age": 34,"money": 110}, 7 | {"name": "Tiancum", "age": 43, "money": 1000}, 8 | {"name": "Jacob", "age": 27,"money": -201}, 9 | {"name": "Nephi", "age": 29, "money": 100}, 10 | {"name": "Enos", "age": 34, "money": -52.5}, 11 | {"name": "Tiancum", "age": 43, "money": 52.1}], 12 | "page":2, 13 | "limit":10, 14 | "total":25 15 | } -------------------------------------------------------------------------------- /example/test-data/data-page3.json: -------------------------------------------------------------------------------- 1 | { 2 | "data":[{"name": "Tiancum", "age": 43, "money": 1000}, 3 | {"name": "Jacob", "age": 27,"money": -201}, 4 | {"name": "Nephi", "age": 29, "money": 100}, 5 | {"name": "Enos", "age": 34, "money": -52.5}, 6 | {"name": "Tiancum", "age": 43, "money": 52.1}], 7 | "page":3, 8 | "limit":5, 9 | "total":25 10 | } -------------------------------------------------------------------------------- /gulp/build.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var gulp = require('gulp'), 4 | gulpLoadPlugins = require('gulp-load-plugins'), 5 | minifyCSS = require('gulp-minify-css'), 6 | path = require('path'), 7 | gutil = require('gulp-util'), 8 | plugins = gulpLoadPlugins(), 9 | header = require('gulp-header'), 10 | 11 | 12 | TEMP_FOLDER = ".tmp", 13 | SRC_FOLDER = "src", 14 | TEMPLATES_FOLDER = "templates", 15 | BUILD_FOLDER = "build", 16 | OUTPUT_FILE = "object-table.js", 17 | 18 | /* add HEADER */ 19 | pkg = require('../package.json'); 20 | var banner = ['/**', 21 | ' * <%= pkg.name %> - <%= pkg.description %>', 22 | ' * @version v<%= pkg.version %>', 23 | ' * @author <%= pkg.author %>', 24 | ' * @license <%= pkg.license %>', 25 | ' */', 26 | ''].join('\n'); 27 | 28 | 29 | gulp.task('less', function () { 30 | return gulp.src('src/css/**/*.less') 31 | .pipe(plugins.less({ 32 | paths: [path.join(__dirname, 'less', 'includes')] 33 | }).on('error', function (err) { 34 | gutil.log(err); 35 | this.emit('end'); 36 | })) 37 | .pipe(minifyCSS()) 38 | 39 | .pipe(gulp.dest(BUILD_FOLDER)); 40 | }); 41 | 42 | gulp.task('watch', function () { 43 | return plugins.watch('src/css/**/*.less', function () { 44 | gulp.start('less') 45 | .on('error', gutil.log); 46 | }) 47 | }); 48 | 49 | gulp.task('js-min', function () { 50 | return gulp.src(SRC_FOLDER + '/js/**/*.js') 51 | .pipe(plugins.concat(OUTPUT_FILE)) 52 | .pipe(plugins.ngAnnotate({add: true})) 53 | .pipe(plugins.uglify({mangle: true})) 54 | .pipe(header(banner, {pkg: pkg})) 55 | .pipe(gulp.dest(TEMP_FOLDER)) 56 | }); 57 | 58 | gulp.task('js-templates', function () { 59 | return gulp.src(SRC_FOLDER + '/' + TEMPLATES_FOLDER + '/**/*.html') 60 | .pipe(plugins.htmlmin({collapseWhitespace: true})) 61 | .pipe(plugins.angularTemplatecache('templates.js', { 62 | module: 'objectTable', 63 | root: '/' + SRC_FOLDER + '/' + TEMPLATES_FOLDER + '/' 64 | })) 65 | .pipe(gulp.dest(TEMP_FOLDER)) 66 | }); 67 | 68 | /*gulp.task('css', function() { 69 | return gulp.src('src/css/object-table-style.css') 70 | .pipe(minifyCSS()) 71 | .pipe(gulp.dest(BUILD_FOLDER)); 72 | });*/ 73 | 74 | gulp.task('js-concat', function () { 75 | return gulp.src(TEMP_FOLDER + '/**/*.js') 76 | .pipe(plugins.concat(OUTPUT_FILE)) 77 | .pipe(gulp.dest('./build')) 78 | }); 79 | 80 | gulp.task('build', gulp.series('js-templates', 'js-min', 'less', 'js-concat')); 81 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var gulp = require('gulp'); 4 | 5 | require('require-dir')('./gulp'); 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-object-table", 3 | "version": "0.2.6", 4 | "description": "angular smart table directive", 5 | "main": "build/angular-object-table.js", 6 | "scripts": { 7 | "test": "karma start tests/karma.conf.js", 8 | "build": "gulp build" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/ekokotov/object-table.git" 13 | }, 14 | "keywords": [ 15 | "angular", 16 | "gtable", 17 | "dablegrid", 18 | "grid" 19 | ], 20 | "author": "Yauheni Kokatau", 21 | "license": "MIT", 22 | "dependencies": {}, 23 | "engine": { 24 | "node": "10.15.3", 25 | "npm": "6.9.0" 26 | }, 27 | "devDependencies": { 28 | "angular": "^1.4.1", 29 | "angular-mocks": "^1.4.1", 30 | "bower": "^1.8.8", 31 | "gulp": "^4.0.2", 32 | "gulp-angular-templatecache": "^3.0.0", 33 | "gulp-clean": "^0.4.0", 34 | "gulp-concat": "^2.6.1", 35 | "gulp-header": "^2.0.7", 36 | "gulp-htmlmin": "^5.0.1", 37 | "gulp-less": "^4.0.1", 38 | "gulp-load": "^0.1.1", 39 | "gulp-load-plugins": "^1.5.0", 40 | "gulp-minify-css": "^1.2.4", 41 | "gulp-ng-annotate": "^2.1.0", 42 | "gulp-uglify": "^3.0.2", 43 | "gulp-util": "^3.0.8", 44 | "gulp-watch": "^5.0.1", 45 | "jasmine-core": "^3.4.0", 46 | "karma": "^4.1.0", 47 | "karma-jasmine": "^2.0.1", 48 | "karma-phantomjs-launcher": "^1.0.4", 49 | "plugins": "^0.4.2", 50 | "require-dir": "^1.2.0" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/css/object-table-style.less: -------------------------------------------------------------------------------- 1 | .object-table-module { 2 | 3 | .back-cover{ 4 | background-color: white; 5 | } 6 | 7 | .count{ 8 | font-size: 15px; 9 | float: right; 10 | margin-top: 20px; 11 | color: rgb(33, 101, 163); 12 | background-color: rgba(188, 188, 188, 0.35); 13 | .records{ 14 | color: rgb(136, 136, 136); 15 | font-weight: normal; 16 | } 17 | } 18 | 19 | /*## Search */ 20 | .search{ 21 | max-width:250px; 22 | padding-right: 25px; 23 | &.separate{ 24 | width: 100%; 25 | max-width: 100%; 26 | padding-left: 22px; 27 | } 28 | } 29 | .search_icon{ 30 | position: absolute; 31 | right: 8px; 32 | top: 10px; 33 | color: gray; 34 | &.separate{ 35 | left: 15px; 36 | top: 20px; 37 | font-size: 10px; 38 | width: 10px; 39 | } 40 | } 41 | 42 | /*## Sorting */ 43 | .sorting-badge{ 44 | background-color: white; 45 | position: relative; 46 | font-weight: 600; 47 | font-size: 10px; 48 | padding: 5px 5px 5px 10px; 49 | display: inline-block; 50 | border-radius: 4px; 51 | margin-left: 5px; 52 | color: rgb(51, 122, 183); 53 | border: 1px solid rgba(12, 75, 140, 0.43); 54 | margin-bottom: 3px; 55 | } 56 | 57 | .sorting-container{ 58 | margin: 10px 0; 59 | 60 | .sorting-badge{ 61 | &:hover{ 62 | background-color: rgb(51, 122, 183); 63 | color: white; 64 | .close{ 65 | opacity:1; 66 | cursor: pointer; 67 | color: rgb(0, 38, 85); 68 | text-shadow:none; 69 | } 70 | } 71 | .close{ 72 | margin-left: 5px; 73 | float: right; 74 | font-size: 12px; 75 | line-height: 1; 76 | color: #000; 77 | filter: alpha(opacity=20); 78 | opacity: .2; 79 | } 80 | /*.up,.down{ 81 | &:extend(.object-table-module .object-table thead>tr>th.sortable:after) 82 | }*/ 83 | } 84 | } 85 | 86 | .object-table{ 87 | margin-bottom: 0px; 88 | color: gray; 89 | 90 | thead{ 91 | >tr{ 92 | background-color: #e6f1f6; 93 | color: rgb(33, 101, 163); 94 | cursor: pointer; 95 | -webkit-touch-callout: none; 96 | -webkit-user-select: none; 97 | -khtml-user-select: none; 98 | -moz-user-select: none; 99 | -ms-user-select: none; 100 | user-select: none; 101 | th{ 102 | border-bottom: 0; 103 | padding-right: 25px; 104 | position: relative; 105 | will-change:width; 106 | -webkit-touch-callout: none; 107 | -webkit-user-select: none; 108 | -khtml-user-select: none; 109 | -moz-user-select: none; 110 | -ms-user-select: none; 111 | user-select: none; 112 | &[draggable]{ 113 | a{ 114 | width: 100%; 115 | display: block; 116 | } 117 | } 118 | &.dragged{ 119 | background-color: rgba(255, 255, 255, 0.64); 120 | border: 2px dashed rgb(66, 121, 152); 121 | div.resize{ 122 | display: none; 123 | } 124 | } 125 | &.draggedOver{ 126 | background-color: rgba(128, 128, 128, 0.16); 127 | border: 1px solid rgb(147, 153, 156); 128 | div.resize{ 129 | display: none; 130 | } 131 | } 132 | &.sortable{ 133 | position: relative; 134 | &:before { 135 | margin-top: 2px; 136 | content: "\e113"; 137 | font-size: 9px; 138 | font-family: 'Glyphicons Halflings'; 139 | visibility: visible; 140 | right: 8px; 141 | top: 40%; 142 | position: absolute; 143 | opacity: .25; 144 | margin-top: -4px; 145 | } 146 | &:after { 147 | &:extend(.object-table-module .object-table thead > tr th.sortable:before); 148 | content: "\e114"; 149 | margin-top: 5px; 150 | } 151 | &:hover:not(.table-sort-up):not(.table-sort-down):after, 152 | &:hover:not(.table-sort-up):not(.table-sort-down):before{ 153 | opacity: 1; 154 | } 155 | &.table-sort-up:before,&.table-sort-down:after{ 156 | opacity: 1; 157 | } 158 | } 159 | 160 | &.separate{ 161 | position: relative; 162 | padding-right: 7px; 163 | text-shadow: none !important; 164 | outline: none; 165 | &:focus{ 166 | outline: none; 167 | } 168 | } 169 | div.resize{ 170 | width: 5px; 171 | border-right: 2px dotted rgb(148, 148, 148); 172 | position: absolute; 173 | right: -2px; 174 | visibility: hidden; 175 | top: 0; 176 | z-index: 999; 177 | height: 100%; 178 | cursor: ew-resize; 179 | } 180 | &:hover{ 181 | div.resize{ 182 | visibility: visible; 183 | } 184 | } 185 | } 186 | } 187 | 188 | } 189 | 190 | tfoot{ 191 | tr{ 192 | &:extend(.object-table-module .object-table thead>tr); 193 | cursor: auto; 194 | } 195 | } 196 | tbody{ 197 | tr{ 198 | &.selected-row td{ 199 | background-color: rgb(215, 241, 160); 200 | } 201 | &:hover{ 202 | background-color:rgba(103, 103, 103, 0.08); 203 | color: black; 204 | } 205 | } 206 | td{ 207 | //will-change:width; 208 | &:hover{ 209 | cursor: pointer; 210 | } 211 | &[editable]{ 212 | div[contenteditable]{ 213 | padding: 3px; 214 | &:focus{ 215 | margin: -3px; 216 | border: 2px dotted #738395; 217 | background-color: #F5F5F5; 218 | vertical-align: middle; 219 | color: rgb(12, 75, 140); 220 | display: block; 221 | outline: none; 222 | } 223 | } 224 | } 225 | } 226 | 227 | } 228 | /*&.resize{ 229 | width: auto; 230 | }*/ 231 | } 232 | 233 | &.hover-column table{ 234 | overflow: hidden !important; 235 | 236 | thead{ 237 | tr{ 238 | th{ 239 | z-index: 3; 240 | position: relative; 241 | } 242 | } 243 | } 244 | tbody{ 245 | tr{ 246 | td{ 247 | position: relative; 248 | outline: 0; 249 | z-index: 2; 250 | &:hover{ 251 | z-index: 1; 252 | &::before{ 253 | content: ''; 254 | height: 10000px; 255 | left: 0; 256 | position: absolute; 257 | top: -5000px; 258 | width: 100%; 259 | z-index: -1; 260 | background-color: rgba(103, 103, 103, 0.08); 261 | } 262 | } 263 | 264 | } 265 | } 266 | } 267 | } 268 | 269 | } 270 | /* global loading */ 271 | .loading{ 272 | padding: 5px; 273 | text-align: center; 274 | color: rgb(51, 122, 185); 275 | } 276 | 277 | .loading .glyphicon-refresh-animate { 278 | -animation: spin .7s infinite linear; 279 | -webkit-animation: spin2 .7s infinite linear; 280 | } 281 | 282 | 283 | @-webkit-keyframes spin2 { 284 | from { -webkit-transform: rotate(0deg);} 285 | to { -webkit-transform: rotate(360deg);} 286 | } 287 | 288 | @keyframes spin { 289 | from { transform: scale(1) rotate(0deg);} 290 | to { transform: scale(1) rotate(360deg);} 291 | } 292 | -------------------------------------------------------------------------------- /src/css/themes/blue-dust.less: -------------------------------------------------------------------------------- 1 | @table-background-color:#bac9d1; 2 | @table-font-color:#02658C; 3 | @table-border:rgb(0, 85, 118); 4 | @head-background-color:#545761; 5 | @head-color:rgb(144, 223, 255); 6 | @row-selected-background:rgba(136, 223, 255, 1); 7 | @row-selected-border:2px solid rgb(74, 190, 232) !important; 8 | @row-selected-color:black; 9 | @row-hover-background:rgba(136, 223, 255, 0.19); 10 | @editable-background:#D7D7D7; 11 | @editable-border:2px dotted #009AD5; 12 | @counts-background:#BAC9D1; 13 | @counts-border:1px solid rgba(43, 51, 74, 0.45); 14 | @paging-color:#2B334A; 15 | 16 | /* table background */ 17 | .object-table-module.blue-dust { 18 | 19 | .back-cover { 20 | background-color: @table-background-color; 21 | table{ 22 | color: @table-font-color; 23 | &.table{ 24 | border: 1px solid @table-border; 25 | border-bottom: 5px solid @head-background-color; 26 | } 27 | &.object-table{ 28 | thead{ 29 | tr{ 30 | background-color: @head-background-color; 31 | color: @head-color; 32 | th{ 33 | border: 1px solid rgba(143, 153, 162, 0.39); 34 | text-shadow: 1px 0 1px rgba(0, 0, 0, 0.64); 35 | } 36 | } 37 | } 38 | 39 | tbody{ 40 | tr{ 41 | &:hover{ 42 | background-color: @row-hover-background; 43 | color: @row-selected-color; 44 | text-shadow: 0px 0px 1px rgba(0, 0, 0, 0.29); 45 | } 46 | &.selected-row{ 47 | td{ 48 | background-color: @row-selected-background; 49 | color: @row-selected-color; 50 | border-bottom: @row-selected-border; 51 | border-top: @row-selected-border; 52 | } 53 | } 54 | td{ 55 | border:1px solid rgba(84, 87, 97, 0.23); 56 | &[editable]{ 57 | span:focus { 58 | padding: 3px; 59 | margin: -5px; 60 | font-weight: bold; 61 | border: @editable-border; 62 | background-color: @editable-background; 63 | } 64 | } 65 | } 66 | } 67 | } 68 | 69 | } 70 | } 71 | } 72 | 73 | .sorting-badge{ 74 | color: rgb(51, 55, 66); 75 | background-color: rgb(186, 201, 209); 76 | &:hover{ 77 | background-color: @head-background-color; 78 | color: rgb(186, 201, 209); 79 | } 80 | } 81 | 82 | 83 | 84 | /* column hover*/ 85 | &.hover-column table tbody td:hover::before { 86 | background-color: @row-hover-background; 87 | } 88 | 89 | /*search*/ 90 | .search{ 91 | color: rgb(51, 55, 66); 92 | border: 3px solid rgba(0, 137, 189, 0.23); 93 | &:focus{ 94 | border: 2px solid rgba(84, 87, 97, 0.77); 95 | } 96 | &.separate{ 97 | border: none; 98 | } 99 | } 100 | 101 | 102 | /*barge*/ 103 | .count { 104 | background-color: @counts-background; 105 | border: @counts-border; 106 | color: @head-background-color; 107 | .records { 108 | color: rgb(51, 55, 66); 109 | } 110 | } 111 | 112 | .pagination>li{ 113 | &.active{ 114 | a{ 115 | color: #88DFFF; 116 | background-color: #545761; 117 | } 118 | } 119 | a { 120 | padding: 3px 12px; 121 | color: @paging-color; 122 | background-color: @counts-background; 123 | border: @counts-border; 124 | &:hover{ 125 | background-color: #DFDFDF; 126 | } 127 | } 128 | } 129 | 130 | } -------------------------------------------------------------------------------- /src/css/themes/dark-sky.less: -------------------------------------------------------------------------------- 1 | @table-background-color:#333742; 2 | @table-font-color:#9ca0a9; 3 | @table-border:#24272f; 4 | @head-background-color:#009AD5; 5 | @head-color:rgb(51, 55, 66); 6 | @row-selected-background:rgba(156, 156, 159, 0.56); 7 | @row-selected-color:rgb(0, 177, 245); 8 | @row-hover-background:rgba(0, 154, 213, 0.19); 9 | @editable-background:#D7D7D7; 10 | @editable-border:2px dotted #009AD5; 11 | @counts-background:#5D7187; 12 | @counts-border:#2B334A; 13 | @counts-color:#2B334A; 14 | 15 | .object-table-module.dark-sky{ 16 | /* table background */ 17 | .back-cover { 18 | background-color: @table-background-color; 19 | /* cell font color */ 20 | table{ 21 | color: @table-font-color; 22 | /* cell border */ 23 | &.table{ 24 | border: 1px @table-border; 25 | border-bottom: 5px solid @head-background-color; 26 | } 27 | /* table border */ 28 | &.table-bordered>tbody>tr>td{ 29 | border: 1px solid @table-border; 30 | } 31 | /* table head cell border*/ 32 | &.object-table thead{ 33 | tr{ 34 | background-color: @head-background-color; 35 | color: @head-color; 36 | th { 37 | border: 1px solid @table-background-color; 38 | } 39 | } 40 | } 41 | tbody{ 42 | tr{ 43 | /* table row selected*/ 44 | &.selected-row td { 45 | background-color: @row-selected-background; 46 | color: @row-selected-color; 47 | } 48 | /* table row hover*/ 49 | &:hover { 50 | background-color: @row-hover-background; 51 | color: white; 52 | } 53 | td{ 54 | /*editable*/ 55 | &[editable] span:focus { 56 | padding: 3px; 57 | margin: -5px; 58 | font-weight: bold; 59 | border: @editable-border; 60 | background-color: @editable-background; 61 | } 62 | } 63 | } 64 | } 65 | } 66 | } 67 | 68 | .sorting-badge{ 69 | color: @head-color; 70 | background-color: @head-background-color; 71 | cursor: pointer; 72 | &:hover{ 73 | background-color: fade(@head-background-color, 70%); 74 | color: @head-color; 75 | } 76 | } 77 | 78 | 79 | /* column hover*/ 80 | &.hover-column table tbody td:hover::before { 81 | background-color: @row-hover-background; 82 | } 83 | 84 | /*search*/ 85 | .search{ 86 | background-color: rgba(239, 239, 239, 0.9); 87 | color: @head-color; 88 | border: 2px solid rgba(0, 154, 213, 1); 89 | box-shadow: inset 0 1px 5px rgba(51, 55, 66, 0.67); 90 | &:focus{ 91 | box-shadow: none; 92 | } 93 | &.separate{ 94 | color: #333742; 95 | border: 1px solid rgba(0, 118, 163, 1); 96 | box-shadow: none; 97 | } 98 | } 99 | 100 | 101 | 102 | .count { 103 | background-color: @counts-background; 104 | color: lighten(@head-background-color,30%); 105 | .records { 106 | color: @head-color; 107 | } 108 | } 109 | /*paggination*/ 110 | .pagination>li{ 111 | &.active>a{ 112 | color: #000000; 113 | background-color: @head-background-color; 114 | top: -3px; 115 | height: 35px; 116 | padding-top: 6px; 117 | } 118 | a { 119 | padding: 3px 12px; 120 | color: @counts-color; 121 | background-color: @counts-background; 122 | border: 1px solid @counts-border; 123 | } 124 | } 125 | } -------------------------------------------------------------------------------- /src/js/contenteditableDirective.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | angular.module('objectTable',[]).directive('contenteditable', function() { 3 | return { 4 | restrict: 'A', 5 | require: ['ngModel', '^objectTable'], 6 | link: function(scope, element, attrs, ctrls) { 7 | var ngModel = ctrls[0], objectTableCtrl = ctrls[1]; 8 | ngModel.$render = function() { 9 | element.html(ngModel.$viewValue || ''); 10 | }; 11 | 12 | element.bind('change blur', function() { 13 | var oldValue = ngModel.$viewValue.toString(); 14 | var newValue = element.text(); 15 | if (oldValue !== newValue) { 16 | scope.$apply(function() { 17 | ngModel.$setViewValue(newValue); 18 | }); 19 | if (!!objectTableCtrl.onEdit && typeof objectTableCtrl.onEdit === 'function') objectTableCtrl.onEdit({$oldValue: oldValue, $newValue: newValue}); 20 | } 21 | }) 22 | } 23 | } 24 | }); 25 | -------------------------------------------------------------------------------- /src/js/directive.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | angular.module('objectTable').directive('objectTable', ['$compile','$interpolate',function($compile, $interpolate) { 3 | return { 4 | restrict: 'A', 5 | replace: true, 6 | templateUrl: '/src/templates/common.html', 7 | controller: 'objectTableCtrl', 8 | controllerAs: 'ctrl', 9 | transclude: true, 10 | scope: { 11 | data: '=', 12 | display: '=?', 13 | resize: '=?', 14 | paging: '=?', 15 | fromUrl: '@', 16 | //search:"@?", 17 | //headers:"@", 18 | //fields:"@", 19 | sortingType: '@?sorting', 20 | editable: '&?', 21 | select: '@?', 22 | selectedModel: '=?', 23 | dragColumns: '=?', 24 | //events 25 | onEdit: '&?', 26 | onDrag: '&?', 27 | 28 | }, 29 | compile: function(tElement, tAttributes) { 30 | 31 | //collect filters 32 | var rowFilter = '', 33 | pagingFilter = ''; 34 | 35 | // additional user filters 36 | if (!!tAttributes.addFilter) { 37 | rowFilter += tAttributes.addFilter; 38 | } 39 | 40 | //If SORTING allowed 41 | if (tAttributes.sorting !== 'false') { 42 | rowFilter += '| orderBy:sortingArray'; 43 | } 44 | 45 | // add 'allow-drag' attribute to header is just cistom tbody present 46 | if (tAttributes.dragColumns) { 47 | tElement.find('th').attr('allow-drag',''); 48 | } 49 | 50 | //If SEARCH allowed 51 | if (tAttributes.search === 'separate') { 52 | tAttributes.fields.split(',').forEach(function(item, index) { 53 | rowFilter += '| filter:{\'' + item.trim() + '\':columnSearch[\'' + item + '\']}'; 54 | }); 55 | 56 | } else if (typeof(tAttributes.search) === 'undefined' || tAttributes.search === 'true') { 57 | rowFilter += '| filter:globalSearch'; 58 | } 59 | 60 | //pagingFilter = rowFilter; 61 | pagingFilter += ' | offset: currentPage:display |limitTo: display'; 62 | 63 | tElement[0].querySelector('#rowTr').setAttribute('ng-repeat','item in $parent.$filtered = (data' + rowFilter + ')' + pagingFilter); 64 | //add paging 65 | tElement.find('paging').attr('count','$filtered.length'); 66 | 67 | return function preLink(scope, element, attrs, ctrl, transclude) { 68 | ctrl._init(); 69 | transclude(scope, function(clone, innerScope) { 70 | scope.$owner = innerScope.$parent; 71 | for (var key in clone) { 72 | if (clone.hasOwnProperty(key)) { 73 | switch (clone[key].tagName) { 74 | case 'THEAD': 75 | ctrl._addHeaderPattern(clone[key]); 76 | break; 77 | case 'TBODY': 78 | scope.findBody = true; 79 | ctrl._addRowPattern(clone[key],rowFilter,pagingFilter); 80 | break; 81 | case 'TFOOT': 82 | ctrl._addFooterPattern(clone[key]); 83 | break; 84 | } 85 | } 86 | } 87 | }); 88 | 89 | }; //[END transclude] 90 | 91 | }, 92 | 93 | }; 94 | 95 | }]); 96 | -------------------------------------------------------------------------------- /src/js/draggableDirective.js: -------------------------------------------------------------------------------- 1 | angular.module('objectTable').directive('allowDrag', function() { 2 | return { 3 | restrict: 'A', 4 | controller: function() {}, 5 | compile: function(el, attr) { 6 | 7 | function removeDragClass(element, className) { 8 | var elm = element[0].parentNode.querySelector('.' + className); 9 | if (!!elm) 10 | elm.classList.remove(className); 11 | } 12 | 13 | return function pre(scope, element, attrs, ctrl) { 14 | element.attr('draggable',true); 15 | 16 | element.bind('dragstart', function(e) { 17 | ctrl.target = this; 18 | this.classList.add('dragged'); 19 | var ev = e.originalEvent || e; // override event for touch events 20 | ev.dataTransfer.setData('text', ctrl.target.cellIndex); 21 | }); 22 | 23 | element.bind('dragover', function(e) { 24 | e.preventDefault(); 25 | }); 26 | 27 | element.bind('dragenter', function(e) { 28 | ctrl.toTarget = this; 29 | if (!this.classList.contains('draggedOver') && !this.classList.contains('dragged')) 30 | this.classList.add('draggedOver'); 31 | e.preventDefault(); 32 | e.stopPropagation(); 33 | }); 34 | 35 | element.bind('dragend', function(e) { 36 | if (this.classList.contains('dragged')) 37 | this.classList.remove('dragged'); 38 | e.preventDefault(); 39 | }); 40 | 41 | element.bind('dragleave', function(e) { 42 | this.classList.remove('draggedOver'); 43 | }); 44 | 45 | element.bind('drop', function(e) { 46 | var currentIndex = ctrl.toTarget.cellIndex, 47 | ev = e.originalEvent || e, 48 | draggedIndex = parseInt(ev.dataTransfer.getData('text'),10); 49 | removeDragClass(element, 'dragged'); 50 | removeDragClass(element, 'draggedOver'); 51 | element.parent().controller('objectTable').changeColumnsOrder(currentIndex,draggedIndex); 52 | e.preventDefault(); 53 | }); 54 | }; 55 | } 56 | 57 | }; 58 | }); 59 | -------------------------------------------------------------------------------- /src/js/objectTableCtrl.js: -------------------------------------------------------------------------------- 1 | angular.module('objectTable').controller('objectTableCtrl', ['$scope', '$timeout','$element', '$attrs','$http', '$compile', '$controller', 'objectTableUtilService', 2 | function angTableCtrl($scope, $timeout, $element, $attrs, $http, $compile, $controller, Util) { 3 | 4 | $controller('objectTableSortingCtrl', {$scope: $scope}); 5 | var ctrl = this; 6 | 7 | this._init = function() { 8 | $scope.headers = []; 9 | $scope.fields = []; 10 | $scope.display = $scope.display || 5; 11 | $scope.paging = angular.isDefined($scope.paging) ? $scope.paging : true; 12 | $scope.sortingType = $scope.sortingType || 'simple'; 13 | $scope.currentPage = 0; 14 | $scope.customHeader = false; 15 | 16 | if ($attrs.search == 'separate') { 17 | $scope.search = 'separate'; 18 | $scope.columnSearch = []; 19 | 20 | /* ## after changing search model - clear currentPage ##*/ 21 | } else { 22 | /* 'separate' or 'true' or 'false '*/ 23 | $scope.search = typeof($attrs.search) === 'undefined' || $attrs.search === 'true'; 24 | } 25 | 26 | /* GET HEADERS */ 27 | $scope.headers = Util.getArrayFromParams($attrs.headers,'headers'); 28 | 29 | /* GET FIELDS */ 30 | $scope.fields = Util.getArrayFromParams($attrs.fields,'fields'); 31 | 32 | //LOAD FROM EXTERNAL URL 33 | if (!!$attrs.fromUrl) { 34 | this._loadExternalData($attrs.fromUrl); 35 | } 36 | 37 | if (!!$scope.onEdit) { 38 | this.onEdit = $scope.onEdit; 39 | } 40 | 41 | //reinitialize selected model~` 42 | $scope.selectedModel = $scope.select === 'multiply' ? [] : {}; 43 | 44 | }; 45 | 46 | this.onEdit = $scope.onEdit; 47 | 48 | this._loadExternalData = function(url) { 49 | $scope.dataIsLoading = true; 50 | $http.get(url).then(function(response) { 51 | $scope.data = response.data; 52 | $scope.dataIsLoading = false; 53 | }); 54 | 55 | }; 56 | 57 | this._addHeaderPattern = function(node) { 58 | $scope.customHeader = true; 59 | //add Index to drag 60 | Array.prototype.forEach.call(node.querySelectorAll('[allow-drag]'), function(th, index) { 61 | th.setAttribute('index',index); 62 | }); 63 | node.removeAttribute('ng-non-bindable'); 64 | $element.find('table').prepend(node); 65 | }; 66 | 67 | this._addFooterPattern = function(node) { 68 | $element.find('table').prepend(node); 69 | }; 70 | 71 | this._addRowPattern = function(node, rowFilter, paggingFilter) { 72 | this._checkEditableContent(node); 73 | this._addRepeatToRow(node, rowFilter, paggingFilter); 74 | node.removeAttribute('ng-non-bindable'); 75 | //compile TBODY 76 | $element.find('table').append(node.outerHTML); 77 | this.bodyTemplate = node.innerHTML; 78 | $compile($element.find('tbody'))($scope); 79 | }; 80 | 81 | this._addRepeatToRow = function(node, rowFilter, paggingFilter) { 82 | var tr = angular.element(node).find('tr'); 83 | 84 | tr.attr('ng-repeat','item in $filtered = (data' + rowFilter + ')' + paggingFilter); 85 | if (!tr.attr('ng-click')) { 86 | tr.attr('ng-click','setSelected(item)'); 87 | } 88 | 89 | tr.attr('ng-class','{\'selected-row\':ifSelected(item)}'); 90 | }; 91 | 92 | this._checkEditableContent = function(node) { 93 | var innerModel, findModelRegex = /\{\{:*:*(.*?)\}\}/g; 94 | Array.prototype.forEach.call(node.querySelectorAll('[editable]'), function(td) { 95 | innerModel = td.innerHTML.replace(findModelRegex,'$1'); 96 | td.innerHTML = '
{{' + innerModel + '}}
'; 97 | }); 98 | }; 99 | 100 | this.setCurrentPage = function(_currentPage) { 101 | $scope.currentPage = _currentPage; 102 | }; 103 | 104 | $scope.setSelected = function(item) { 105 | if ($scope.select === 'multiply') { 106 | if (!ctrl._containsInSelectArray(item)) { 107 | $scope.selectedModel.push(item); 108 | } else { 109 | $scope.selectedModel.splice($scope.selectedModel.indexOf(item),1); 110 | } 111 | }else { 112 | $scope.selectedModel = item; 113 | } 114 | }; 115 | 116 | this._containsInSelectArray = function(obj) { 117 | if ($scope.selectedModel.length) 118 | return $scope.selectedModel.filter(function(listItem) { 119 | return angular.equals(listItem, obj); 120 | }).length > 0; 121 | }; 122 | 123 | $scope.ifSelected = function(item) { 124 | if (!!$scope.selectedModel ) { 125 | if ($scope.select === 'multiply') { 126 | return ctrl._containsInSelectArray(item); 127 | } else { 128 | return item.$$hashKey == $scope.selectedModel.$$hashKey; 129 | } 130 | } 131 | return false; 132 | }; 133 | 134 | /* Drag-n-Drop columns exchange*/ 135 | this.changeColumnsOrder = function(from, to) { 136 | $scope.$apply(function() { 137 | $scope.fields.swap(from,to); 138 | var headersBackup = $scope.headers.slice(); 139 | $scope.headers.swap(from,to); 140 | if (!!$scope.onDrag && typeof $scope.onDrag === 'function') 141 | $scope.onDrag({$oldOrder: headersBackup, $newOrder: $scope.headers}); 142 | headersBackup = null; 143 | if (!!$scope.columnSearch) { 144 | $scope.columnSearch.swap(from,to); 145 | } 146 | if (!!ctrl.bodyTemplate) { 147 | var tds = angular.element(ctrl.bodyTemplate).children(), 148 | html = '', 149 | tr = document.createElement('tr'), 150 | tbody = document.createElement('tbody'), 151 | attributes = $element.find('tbody').find('tr')[0].attributes; 152 | Array.prototype.swap.apply(tds,[from,to]); 153 | 154 | [].forEach.call(attributes, function(attr, index) { 155 | tr.setAttribute(attr.name, attr.value); 156 | }); 157 | 158 | for (var i = 0,length = tds.length; i < length; i++) { 159 | tr.appendChild(tds[i]); 160 | } 161 | 162 | tbody.appendChild(tr); 163 | 164 | $element.find('tbody').replaceWith(tbody); 165 | ctrl.bodyTemplate = tbody.innerHTML; 166 | $compile($element.find('tbody'))($scope); 167 | } 168 | if ($scope.customHeader) { 169 | var ths = $element.find('th'), 170 | tr = document.createElement('tr'), 171 | thead = document.createElement('thead'); 172 | 173 | Array.prototype.swap.apply(ths,[from,to]); 174 | 175 | for (var i = 0, length = ths.length; i < length; i++) { 176 | tr.appendChild(ths[i]); 177 | }; 178 | thead.appendChild(tr); 179 | $element.find('thead').replaceWith(thead); 180 | 181 | } 182 | if (!!ctrl.pageCtrl) 183 | ctrl.pageCtrl.setPage(0); 184 | }); 185 | }; 186 | 187 | $scope.setCurrentPageForPageCtrl = function(_currentPage) { 188 | if (!!ctrl.pageCtrl) ctrl.pageCtrl.setPage(_currentPage); 189 | } 190 | 191 | }]); 192 | -------------------------------------------------------------------------------- /src/js/objectTableFilters.js: -------------------------------------------------------------------------------- 1 | angular.module('objectTable').filter('offset', function() { 2 | return function(input, start, display) { 3 | if (!input) return; 4 | start = parseInt(start, 10); 5 | //if (start == 1) return input.slice(0, display); 6 | display = parseInt(display, 10); 7 | var offset = start* display; 8 | return input.slice(offset, offset + display); 9 | }; 10 | }); -------------------------------------------------------------------------------- /src/js/objectTablePagingCtrl.js: -------------------------------------------------------------------------------- 1 | angular.module('objectTable').controller('pagingTableCtrl', ['$scope', '$element', '$attrs', 2 | function ($scope, $element, $attrs) { 3 | 4 | $scope.currentPage = 0; 5 | 6 | 7 | $scope.prevPage = function () { 8 | if ($scope.currentPage > 0) { 9 | $scope.currentPage--; 10 | } 11 | $scope.setCurrentPageToTable(); 12 | }; 13 | 14 | $scope.nextPage = function () { 15 | if ($scope.currentPage < $scope.pageCount()) { 16 | $scope.currentPage++; 17 | } 18 | $scope.setCurrentPageToTable(); 19 | }; 20 | 21 | $scope.setCurrentPageToTable = function (){ 22 | $scope.objectTableCtrl.setCurrentPage($scope.currentPage); 23 | }; 24 | 25 | $scope.prevPageDisabled = function () { 26 | return $scope.currentPage === 0 ? "disabled" : ""; 27 | }; 28 | 29 | $scope.pageCount = function () { 30 | return $scope.count>0 ?Math.ceil($scope.count/$scope.display)-1: 0; 31 | }; 32 | 33 | $scope.nextPageDisabled = function () { 34 | return $scope.currentPage === $scope.pageCount() ? "disabled" : ""; 35 | }; 36 | 37 | $scope.setPage = function(n) { 38 | $scope.currentPage = n; 39 | $scope.setCurrentPageToTable(); 40 | }; 41 | 42 | 43 | $scope.range = function () { 44 | var rangeSize = $scope.pageCount()+1 <5 ?$scope.pageCount()+1 :5; 45 | 46 | var ret = []; 47 | var start = $scope.currentPage; 48 | 49 | if ( start > $scope.pageCount()-rangeSize ) { 50 | start = $scope.pageCount()-rangeSize+1; 51 | } 52 | 53 | for (var i=start; i -1) { 69 | $scope.sort.fields.splice(index, 1); 70 | $scope.sort.reverse.splice(index, 1); 71 | $scope.sortingArray.splice(index, 1); 72 | } 73 | index = null; 74 | }; 75 | /* sorting [END]*/ 76 | 77 | 78 | /* column resizing*/ 79 | var resizePressed = false, 80 | resizePressedEnd = false, 81 | start,startX, startWidth; 82 | 83 | $scope.resizeStart = function(e) { 84 | var target = e.target ? e.target : e.srcElement; 85 | if (target.classList.contains("resize")) { 86 | start = target.parentNode; 87 | resizePressed = true; 88 | startX = e.pageX; 89 | startWidth = target.parentNode.offsetWidth; 90 | document.addEventListener('mousemove', drag); 91 | e.stopPropagation(); 92 | e.preventDefault(); 93 | } 94 | }; 95 | 96 | function drag(e) { 97 | if (resizePressed) { 98 | start.width = startWidth + (e.pageX - startX); 99 | } 100 | } 101 | 102 | $scope.resizeEnd = function(e) { 103 | if (resizePressed) { 104 | document.removeEventListener('mousemove', drag); 105 | e.stopPropagation(); 106 | e.preventDefault(); 107 | resizePressed = false; 108 | resizePressedEnd = true; 109 | } 110 | }; 111 | 112 | } 113 | ]); 114 | -------------------------------------------------------------------------------- /src/js/objectTableUtilService.js: -------------------------------------------------------------------------------- 1 | angular.module('objectTable').service('objectTableUtilService', [function () { 2 | //extend Array [+swap] 3 | Array.prototype.swap = function (new_index,old_index) { 4 | 5 | if (new_index >= this.length) { 6 | var k = new_index - this.length; 7 | while ((k--) + 1) { 8 | this.push(undefined); 9 | } 10 | } 11 | this.splice(new_index, 0, this.splice(old_index, 1)[0]); 12 | return this; // for testing purposes 13 | }; 14 | return { 15 | getArrayFromParams : function (string,attrName){ 16 | if(!string) throw "Required '" + attrName + "' attribute is not found!"; 17 | var tempArray=[]; 18 | var preArray = string.split(','); 19 | for (var i = 0,length=preArray.length; i 2 |
3 |
4 | Sorting: 5 |
6 | 7 | {{::sortField}} 8 | 9 |
10 |
11 |
12 |
13 | 14 | 15 |
16 | 17 |
18 |
19 | 20 | 21 | 22 | 27 | 28 | 29 | 30 | 31 | 32 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 46 | 47 | 48 | 49 | 50 |
{{head}}
26 |
33 | 34 | 35 |
{{item[field]}} 44 |
{{item[field]}}
45 |
51 |
52 | 53 |
54 | Loading Data... 55 |
56 | 57 | 58 | 59 |
60 | 61 | -------------------------------------------------------------------------------- /src/templates/paging.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 17 |
18 |
19 | {{count}} records 20 |
21 |
22 | 23 | -------------------------------------------------------------------------------- /tests/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // Generated on Tue Jun 23 2015 12:36:13 GMT+0300 (Arab Standard Time) 3 | 4 | module.exports = function(config) { 5 | config.set({ 6 | 7 | // base path that will be used to resolve all patterns (eg. files, exclude) 8 | basePath: '', 9 | 10 | 11 | // frameworks to use 12 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter 13 | frameworks: ['jasmine'], 14 | 15 | 16 | // list of files / patterns to load in the browser 17 | files: [ 18 | '../node_modules/angular/angular.js', 19 | '../node_modules/angular-mocks/angular-mocks.js', 20 | '../build/**/*.js', 21 | 'specs/**/*.spec.js' 22 | ], 23 | 24 | 25 | // list of files to exclude 26 | exclude: [ 27 | ], 28 | 29 | 30 | // preprocess matching files before serving them to the browser 31 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor 32 | preprocessors: { 33 | }, 34 | 35 | 36 | // test results reporter to use 37 | // possible values: 'dots', 'progress' 38 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 39 | reporters: ['progress'], 40 | 41 | 42 | // web server port 43 | port: 9876, 44 | 45 | 46 | // enable / disable colors in the output (reporters and logs) 47 | colors: true, 48 | 49 | 50 | // level of logging 51 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 52 | logLevel: config.LOG_INFO, 53 | 54 | 55 | // enable / disable watching file and executing tests whenever any file changes 56 | autoWatch: false, 57 | 58 | 59 | // start these browsers 60 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 61 | browsers: ['PhantomJS'], 62 | 63 | 64 | // Continuous Integration mode 65 | // if true, Karma captures browsers, runs the tests and exits 66 | singleRun: true 67 | }); 68 | }; 69 | -------------------------------------------------------------------------------- /tests/specs/objectTable.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | describe('Unit: ObjectTableController', function() { 3 | // Load the module with MainController 4 | var scope,elm, data = [{name: "Moroni", age: 50, money: -10}, 5 | {name: "Tiancum", age: 43,money: 120}, 6 | {name: "Jacob", age: 27, money: 5.5}, 7 | {name: "Nephi", age: 29,money: -54}, 8 | {name: "Enos", age: 34,money: 110}, 9 | {name: "Tiancum", age: 43, money: 1000}, 10 | {name: "Jacob", age: 27,money: -201}, 11 | {name: "Nephi", age: 29, money: 100}, 12 | {name: "Enos", age: 34, money: -52.5}, 13 | {name: "Tiancum", age: 43, money: 52.1}, 14 | {name: "Jacob", age: 27, money: 110}, 15 | {name: "Nephi", age: 29, money: -55}, 16 | {name: "Enos", age: 34, money: 551}, 17 | {name: "Tiancum", age: 43, money: -1410}, 18 | {name: "Jacob", age: 27, money: 410}, 19 | {name: "Nephi", age: 29, money: 100}, 20 | {name: "Enos", age: 34, money: -100}], 21 | fields=['age','name'], 22 | headers=['Age','Full Name']; 23 | 24 | beforeEach(module('objectTable')); 25 | 26 | beforeEach( 27 | inject(function ($rootScope, $compile) { 28 | elm = angular.element('
'); 29 | scope = $rootScope.$new(); 30 | scope.users = data; 31 | scope.fields = fields; 32 | $rootScope.$digest(); 33 | $compile(elm)(scope); 34 | scope.$digest(); 35 | })); 36 | 37 | it('is compiled into table', function() { 38 | expect(elm.find('table').length).toBe(1); 39 | }); 40 | 41 | it('found head', function() { 42 | expect(elm.find('thead').length).toBe(1); 43 | }); 44 | 45 | it('found body', function() { 46 | expect(elm.find('tbody').length).toBe(1); 47 | }); 48 | 49 | it('found few headers', function() { 50 | expect(elm.find('th').length).toBe(2); 51 | }); 52 | 53 | it('found few cells', function() { 54 | expect(elm.find('td').length).toBeGreaterThan(1); 55 | }); 56 | 57 | it('check compilled header', function() { 58 | expect(elm.find('thead').find('th')[0].innerText).toEqual(headers[0]); 59 | expect(elm.find('thead').find('th')[1].innerText).toEqual(headers[1]); 60 | }); 61 | 62 | it('compare first row value with initial data', function() { 63 | expect(elm.find('tbody').find('td')[0].innerText).toEqual(""+scope.users[0][fields[0]]); 64 | expect(elm.find('tbody').find('td')[1].innerText).toEqual(""+scope.users[0][fields[1]]); 65 | }); 66 | 67 | describe('ifSelected', function(){ 68 | it('should return false if no row selected', function(){ 69 | var isolatedScope = elm.children().scope() 70 | isolatedScope.selectedModel = null; 71 | expect(isolatedScope.ifSelected({})).toBeFalsy() 72 | }) 73 | }) 74 | 75 | }); --------------------------------------------------------------------------------