├── .gitignore ├── Gruntfile.js ├── README.md ├── demo ├── index.html └── ractive-tooltip.js ├── package.json ├── ractive-datatable.js ├── ractive-datatable.min.js ├── src ├── datatable.js ├── partials │ └── default.html ├── styles.styl ├── template.html └── util │ └── sortBy.js └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | node_modules 3 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | /*global module:false*/ 2 | module.exports = function(grunt) { 3 | 4 | //handles plugins 5 | require('jit-grunt')(grunt); 6 | 7 | var webpack = require('webpack'); 8 | 9 | grunt.initConfig({ 10 | 11 | webpack: { 12 | options: require('./webpack.config.js'), 13 | development: { 14 | debug: true, 15 | }, 16 | production: { 17 | debug: false, 18 | production: true, 19 | devtool: 'none', 20 | output: { 21 | pathinfo: false, 22 | filename: 'ractive-datatable.min.js', 23 | }, 24 | plugins: [ 25 | new webpack.DefinePlugin({ 26 | DEBUG: false, 27 | PRODUCTION: true 28 | }), 29 | new webpack.optimize.UglifyJsPlugin({ 30 | output: { 31 | comments: false, 32 | } 33 | }), 34 | new webpack.optimize.AggressiveMergingPlugin(), 35 | new webpack.optimize.OccurenceOrderPlugin(true), 36 | ] 37 | }, 38 | }, 39 | 40 | }); 41 | 42 | grunt.registerTask('default', ['webpack:development', 'webpack:production']); 43 | 44 | }; 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ractive-datatable 2 | 3 | 4 | ## Demo 5 | 6 | [Live Demo](http://jondum.github.com/ractive-datatable/demo/) 7 | 8 | ## Install 9 | 10 | ``` 11 | npm install ractive-datatable --save 12 | ``` 13 | 14 | ## Features 15 | 16 | * Minimal (3.45kb gzipped **including** styles, 1.71kb without) 17 | * Well designed default styling 18 | * Cell Editing 19 | * Pagination (incl. navigation buttons) 20 | * Filtering (with sexy highlights) 21 | * Sorting 22 | * Per-cell partials (new!) 23 | * Selection (new!) (wip!) 24 | 25 | ## Usage 26 | 27 | Add the datatable to your Ractive instance: 28 | 29 | ``` 30 | Ractive.extend({ 31 | ... 32 | components: { 33 | datatable: require('ractive-datatable') 34 | }, 35 | ... 36 | }); 37 | ``` 38 | 39 | Use it 40 | 41 | ``` 42 | 43 | ``` 44 | 45 | Includes minimal styling under the class `.ractive-datatable`. Styles are included in the javascript and added to the page on load. If you don't want these styles in the javascript, `require()` `src/datatable.js` and handle the styles as needed. 46 | 47 | To use a specific partial for a cell, create an inline partial expression inside the tag of the component: 48 | 49 | ``` 50 | 51 | {{#partial timestamp}} 52 | {{ moment(this).fromNow() }} 53 | {{/partial}} 54 | 55 | ``` 56 | 57 | Will render every row in the "timestamp" column with the passed in partial (in this case formatting the timestamp using moment.js). 58 | 59 | ## API 60 | 61 | ### Properties 62 | 63 | ##### `data` 64 | Array of Objects where each key is a column 65 | 66 | ##### `editable` 67 | globally allow/disallow editing 68 | 69 | ##### `filter` 70 | A string to filter the rows on. Searches through all cells with a case-insensitive RegEx and displays only rows that match. Cells with matches are highlighted. 71 | 72 | ##### `dynamicColumns` 73 | If `true` (default), searches the entire `data` array looking for columns. 74 | If `false`, columns must be explicitly provided through the `columns` property. 75 | If you have a large number of rows this should be turned off for performance, but you will have to explicitly provide columns via the `columns` object. 76 | 77 | ##### `columns` 78 | Determines the ordering of the columns and configuration for specific columns. 79 | 80 | Each key on this object refers to column names. Configurable properties are `edit`, 81 | `display`, `header` & `order`. Key/column names are case-sensitive. 82 | 83 | Example: 84 | 85 | ``` 86 | columns: { 87 | 'name': {order: 0}, // `order` "bumps" the column, lowest value is left most. 88 | 'created': {edit: false}, 89 | 'someAnnoyinglyNamedThingFromThatCoworkerThatDrivesYouNuts': {header: 'Nicer Name'}, 90 | 'id': {edit: false}, 91 | 'hiddenField': {display: false}, 92 | 'anotherHidden': false, //shorthand for { display: false } 93 | 'someOtherColumn': {order: 3}, 94 | } 95 | ``` 96 | 97 | If `dynamicColumns` is `false`, only columns configured here will display. 98 | 99 | ##### `selectionMode` 100 | Either `row` or `cell` (WIP). Allows for rows to be selected on click 101 | 102 | ##### `selection` 103 | An array of the currently selected objects from `data` 104 | 105 | ##### `page` 106 | The current page 107 | 108 | ##### `lastPage` (readonly) 109 | The last page or total number of pages 110 | 111 | ##### `sortOn` 112 | Name of column to sort 113 | 114 | ##### `sortMode` 115 | Either 'asc' or 'desc' 116 | 117 | #### Methods 118 | 119 | 120 | ##### `previousPage()` 121 | Go to the previous page 122 | 123 | ##### `nextPage()` 124 | Go to the next page 125 | 126 | ##### `gotoPage(page)` 127 | Go to the specified page 128 | 129 | 130 | ### Events 131 | 132 | `edit`: Dispatched when an edit is made. Sends the entire row and the field that is edited (useful for extracting specific information from the row that changed). 133 | 134 | ``` 135 | this.on('dataedited', function(row, field) { 136 | 137 | var change = {}; 138 | change.id = row.id; 139 | change[field] = row[field]; 140 | 141 | changes.push(change); 142 | 143 | }); 144 | ``` 145 | 146 | For other events you probably would be better off using Ractive's observers on your datatable instance and the property you want. 147 | 148 | Open to PRs and stuff. I'm around. 149 | 150 | 151 | -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Ractive-Datatable Demo 5 | 6 | 7 | 8 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 93 | 94 | 149 | 150 | 151 | 152 | 153 | -------------------------------------------------------------------------------- /demo/ractive-tooltip.js: -------------------------------------------------------------------------------- 1 | !function(e, t) { 2 | "object" == typeof exports && "object" == typeof module ? module.exports = t() : "function" == typeof define && define.amd ? define(t) : "object" == typeof exports ? 3 | exports.RactiveTooltip = t() : e.RactiveTooltip = t() 4 | }(this, function() { 5 | return function(e) { 6 | function t(r) { 7 | if (n[r]) return n[r].exports; 8 | var o = n[r] = { 9 | exports: {}, 10 | id: r, 11 | loaded: !1 12 | }; 13 | return e[r].call(o.exports, o, o.exports, t), o.loaded = !0, o.exports 14 | } 15 | var n = {}; 16 | return t.m = e, t.c = n, t.p = "", t(0) 17 | }([function(e, t, n) { 18 | function r(e, t, n) { 19 | var r = { 20 | x: e.pageX, 21 | y: e.pageY 22 | }, 23 | o = r.y - n.offsetHeight - 5, 24 | a = r.x + n.offsetWidth - i.innerWidth, 25 | s = e.pageY - n.offsetHeight - 5, 26 | f = e.pageX - 5; 27 | a > 0 && (f -= a - 5), 0 > o && (s += 2 * n.offsetHeight - 5), n.style.left = f + "px", n.style.top = s + "px" 28 | } 29 | 30 | function o(e, t) { 31 | var n, o, i; 32 | o = { 33 | mouseenter: function(o) { 34 | t && 0 !== t.length && (n || (n = a.createElement("div"), n.className = "ractive-tooltip", n.textContent = t), r(o, 35 | e, n), a.body.appendChild(n)) 36 | }, 37 | mousemove: function(t) { 38 | n && r(t, e, n) 39 | }, 40 | mouseleave: function() { 41 | n && n.parentNode && n.parentNode.removeChild(n) 42 | } 43 | }; 44 | for (i in o) o.hasOwnProperty(i) && e.addEventListener(i, o[i], !1); 45 | return { 46 | update: function(e) { 47 | t = e, n && (n.textContent = t), t && 0 !== t.length || !n || !n.parentNode || n.parentNode.removeChild(n) 48 | }, 49 | teardown: function() { 50 | n && n.parentNode && (n.parentNode.removeChild(n), n = null); 51 | for (i in o) o.hasOwnProperty(i) && e.removeEventListener(i, o[i], !1) 52 | } 53 | } 54 | } 55 | n(1); 56 | var i = window, 57 | a = i.document; 58 | e.exports = o 59 | }, function(e, t, n) { 60 | var r = n(2); 61 | "string" == typeof r && (r = [[e.id, r, ""]]); 62 | n(4)(r, {}); 63 | r.placeholders && (e.exports = r.placeholders) 64 | }, function(e, t, n) { 65 | t = e.exports = n(3)(), t.push([e.id, 66 | ".ractive-tooltip{position:absolute;display:table;padding:.5em;color:#fff;background:#000;box-shadow:0 2px 2px rgba(0,0,0,.1);border-radius:5px;white-space:nowrap;z-index:99999;font-style:normal;text-transform:none;pointer-events:none}", 67 | ""]) 68 | }, function(e, t) { 69 | e.exports = function() { 70 | var e = []; 71 | return e.toString = function() { 72 | for (var e = [], t = 0; t < this.length; t++) { 73 | var n = this[t]; 74 | n[2] ? e.push("@media " + n[2] + "{" + n[1] + "}") : e.push(n[1]) 75 | } 76 | return e.join("") 77 | }, e.i = function(t, n) { 78 | "string" == typeof t && (t = [[null, t, ""]]); 79 | for (var r = {}, o = 0; o < this.length; o++) { 80 | var i = this[o][0]; 81 | "number" == typeof i && (r[i] = !0) 82 | } 83 | for (var o = 0; o < t.length; o++) { 84 | var a = t[o]; 85 | "number" == typeof a[0] && r[a[0]] || (n && !a[2] ? a[2] = n : n && (a[2] = "(" + a[2] + ") and (" + n + ")"), e.push( 86 | a)) 87 | } 88 | }, e 89 | } 90 | }, function(e, t, n) { 91 | function r(e, t) { 92 | for (var n = 0; n < e.length; n++) { 93 | var r = e[n], 94 | o = d[r.id]; 95 | if (o) { 96 | o.refs++; 97 | for (var i = 0; i < o.parts.length; i++) o.parts[i](r.parts[i]); 98 | for (; i < r.parts.length; i++) o.parts.push(s(r.parts[i], t)) 99 | } else { 100 | for (var a = [], i = 0; i < r.parts.length; i++) a.push(s(r.parts[i], t)); 101 | d[r.id] = { 102 | id: r.id, 103 | refs: 1, 104 | parts: a 105 | } 106 | } 107 | } 108 | } 109 | 110 | function o(e) { 111 | for (var t = [], n = {}, r = 0; r < e.length; r++) { 112 | var o = e[r], 113 | i = o[0], 114 | a = o[1], 115 | s = o[2], 116 | f = o[3], 117 | p = { 118 | css: a, 119 | media: s, 120 | sourceMap: f 121 | }; 122 | n[i] ? n[i].parts.push(p) : t.push(n[i] = { 123 | id: i, 124 | parts: [p] 125 | }) 126 | } 127 | return t 128 | } 129 | 130 | function i() { 131 | var e = document.createElement("style"), 132 | t = h(); 133 | return e.type = "text/css", t.appendChild(e), e 134 | } 135 | 136 | function a() { 137 | var e = document.createElement("link"), 138 | t = h(); 139 | return e.rel = "stylesheet", t.appendChild(e), e 140 | } 141 | 142 | function s(e, t) { 143 | var n, r, o; 144 | if (t.singleton) { 145 | var s = m++; 146 | n = v || (v = i()), r = f.bind(null, n, s, !1), o = f.bind(null, n, s, !0) 147 | } else e.sourceMap && "function" == typeof URL && "function" == typeof URL.createObjectURL && "function" == typeof URL.revokeObjectURL && 148 | "function" == typeof Blob && "function" == typeof btoa ? (n = a(), r = u.bind(null, n), o = function() { 149 | n.parentNode.removeChild(n), n.href && URL.revokeObjectURL(n.href) 150 | }) : (n = i(), r = p.bind(null, n), o = function() { 151 | n.parentNode.removeChild(n) 152 | }); 153 | return r(e), 154 | function(t) { 155 | if (t) { 156 | if (t.css === e.css && t.media === e.media && t.sourceMap === e.sourceMap) return; 157 | r(e = t) 158 | } else o() 159 | } 160 | } 161 | 162 | function f(e, t, n, r) { 163 | var o = n ? "" : r.css; 164 | if (e.styleSheet) e.styleSheet.cssText = g(t, o); 165 | else { 166 | var i = document.createTextNode(o), 167 | a = e.childNodes; 168 | a[t] && e.removeChild(a[t]), a.length ? e.insertBefore(i, a[t]) : e.appendChild(i) 169 | } 170 | } 171 | 172 | function p(e, t) { 173 | var n = t.css, 174 | r = t.media; 175 | t.sourceMap; 176 | if (r && e.setAttribute("media", r), e.styleSheet) e.styleSheet.cssText = n; 177 | else { 178 | for (; e.firstChild;) e.removeChild(e.firstChild); 179 | e.appendChild(document.createTextNode(n)) 180 | } 181 | } 182 | 183 | function u(e, t) { 184 | var n = t.css, 185 | r = (t.media, t.sourceMap); 186 | r && (n += "\n/*# sourceMappingURL=data:application/json;base64," + btoa(JSON.stringify(r)) + " */"); 187 | var o = new Blob([n], { 188 | type: "text/css" 189 | }), 190 | i = e.href; 191 | e.href = URL.createObjectURL(o), i && URL.revokeObjectURL(i) 192 | } 193 | var d = {}, 194 | c = function(e) { 195 | var t; 196 | return function() { 197 | return "undefined" == typeof t && (t = e.apply(this, arguments)), t 198 | } 199 | }, 200 | l = c(function() { 201 | return /msie [6-9]\b/.test(window.navigator.userAgent.toLowerCase()) 202 | }), 203 | h = c(function() { 204 | return document.head || document.getElementsByTagName("head")[0] 205 | }), 206 | v = null, 207 | m = 0; 208 | e.exports = function(e, t) { 209 | t = t || {}, "undefined" == typeof t.singleton && (t.singleton = l()); 210 | var n = o(e); 211 | return r(n, t), 212 | function(e) { 213 | for (var i = [], a = 0; a < n.length; a++) { 214 | var s = n[a], 215 | f = d[s.id]; 216 | f.refs--, i.push(f) 217 | } 218 | if (e) { 219 | var p = o(e); 220 | r(p, t) 221 | } 222 | for (var a = 0; a < i.length; a++) { 223 | var f = i[a]; 224 | if (0 === f.refs) { 225 | for (var u = 0; u < f.parts.length; u++) f.parts[u](); 226 | delete d[f.id] 227 | } 228 | } 229 | } 230 | }; 231 | var g = function() { 232 | var e = []; 233 | return function(t, n) { 234 | return e[t] = n, e.filter(Boolean).join("\n") 235 | } 236 | }() 237 | }]) 238 | }); 239 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ractive-datatable", 3 | "version": "1.7.0", 4 | "description": "Interactive datatable for ractivejs", 5 | "main": "src/datatable.js", 6 | "scripts": { 7 | "test": "" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "http://github.com/jondum/ractive-datatable" 12 | }, 13 | "keywords": [ 14 | "ractivejs", 15 | "ractive", 16 | "component", 17 | "datatable", 18 | "ractive-datatable" 19 | ], 20 | "author": "Jonathan Dumaine", 21 | "license": "ISC", 22 | "bugs": { 23 | "url": "https://github.com/jondum/ractive-datatable/issues" 24 | }, 25 | "homepage": "https://github.com/jondum/ractive-datatable", 26 | "devDependencies": { 27 | "css-loader": "^0.26.1", 28 | "grunt": "^1.0.1", 29 | "grunt-webpack": "^2.0.1", 30 | "jit-grunt": "^0.10.0", 31 | "nib": "^1.1.2", 32 | "ractive": "^0.8.10", 33 | "ractive-loader": "^0.5.6", 34 | "style-loader": "^0.13.1", 35 | "stylus-loader": "^2.4.0", 36 | "webpack": "^2.2.1" 37 | }, 38 | "dependencies": { 39 | "css-loader": "^0.26.1", 40 | "lodash": "^4.17.4", 41 | "nib": "^1.1.2", 42 | "ractive": "^0.8.10", 43 | "style-loader": "^0.13.1", 44 | "stylus-loader": "^2.4.0" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /ractive-datatable.js: -------------------------------------------------------------------------------- 1 | (function webpackUniversalModuleDefinition(root, factory) { 2 | if(typeof exports === 'object' && typeof module === 'object') 3 | module.exports = factory(); 4 | else if(typeof define === 'function' && define.amd) 5 | define([], factory); 6 | else if(typeof exports === 'object') 7 | exports["RactiveDatatable"] = factory(); 8 | else 9 | root["RactiveDatatable"] = factory(); 10 | })(this, function() { 11 | return /******/ (function(modules) { // webpackBootstrap 12 | /******/ // The module cache 13 | /******/ var installedModules = {}; 14 | 15 | /******/ // The require function 16 | /******/ function __webpack_require__(moduleId) { 17 | 18 | /******/ // Check if module is in cache 19 | /******/ if(installedModules[moduleId]) 20 | /******/ return installedModules[moduleId].exports; 21 | 22 | /******/ // Create a new module (and put it into the cache) 23 | /******/ var module = installedModules[moduleId] = { 24 | /******/ exports: {}, 25 | /******/ id: moduleId, 26 | /******/ loaded: false 27 | /******/ }; 28 | 29 | /******/ // Execute the module function 30 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 31 | 32 | /******/ // Flag the module as loaded 33 | /******/ module.loaded = true; 34 | 35 | /******/ // Return the exports of the module 36 | /******/ return module.exports; 37 | /******/ } 38 | 39 | 40 | /******/ // expose the modules object (__webpack_modules__) 41 | /******/ __webpack_require__.m = modules; 42 | 43 | /******/ // expose the module cache 44 | /******/ __webpack_require__.c = installedModules; 45 | 46 | /******/ // __webpack_public_path__ 47 | /******/ __webpack_require__.p = ""; 48 | 49 | /******/ // Load entry module and return exports 50 | /******/ return __webpack_require__(0); 51 | /******/ }) 52 | /************************************************************************/ 53 | /******/ ([ 54 | /* 0 */ 55 | /***/ function(module, exports, __webpack_require__) { 56 | 57 | module.exports = __webpack_require__(1); 58 | 59 | 60 | /***/ }, 61 | /* 1 */ 62 | /***/ function(module, exports, __webpack_require__) { 63 | 64 | 65 | __webpack_require__(2); 66 | 67 | var sortBy = __webpack_require__(6); 68 | var uniq = __webpack_require__(7); 69 | var isUndefined = __webpack_require__(61); 70 | var isObject= __webpack_require__(24); 71 | var isNumber = __webpack_require__(62); 72 | 73 | var DataTable = Ractive.extend({ 74 | 75 | template: __webpack_require__(64), 76 | 77 | data: function() { 78 | return { 79 | 80 | filter: '', 81 | 82 | perpage: 30, 83 | 84 | page: 1, 85 | 86 | editable: true, 87 | 88 | sortable: true, 89 | 90 | sortOn: '', 91 | 92 | _selection: [], 93 | 94 | selectionMode: '', // "row" or "cell" 95 | 96 | 97 | /** 98 | * @name dynamicColumns 99 | * @type Boolean 100 | * @default true 101 | * If `true`, searches the entire `data` array looking for columns. If you have a large number of rows this should be turned off. 102 | * If `false`, columns must be explicitly provided through the `columns` property. 103 | */ 104 | dynamicColumns: true, 105 | 106 | /** 107 | * 108 | * @name columns 109 | * @type Object 110 | * @default null 111 | * 112 | * Determines the ordering of the columns and configuration for specific columns. 113 | * 114 | * Each key on this object refers to column names. Configurable properties are `edit`, 115 | * `display` & `order`. Keys and column names are case-sensitive. 116 | * 117 | * Example: 118 | * 119 | * ``` 120 | * columns: { 121 | * 'name': {order: 0}, // `order` "bumps" the column, lowest value is left most. 122 | * 'created': {edit: false}, 123 | * 'id': {edit: false}, 124 | * 'hiddenField': {display: false}, 125 | * 'anotherHidden': false, //shorthand for { display: false } 126 | * 'someOtherColumn': {order: 3}, 127 | * } 128 | * ``` 129 | * 130 | * If `dynamicColumns` is `false`, only columns configured here will display. 131 | * 132 | */ 133 | columns: undefined, 134 | 135 | can: function(action, field, row) { 136 | 137 | var config = this.get('columns'); 138 | 139 | // don't edit fields that don't exit on the row 140 | if(!row.hasOwnProperty(field) && action == 'edit') 141 | return; 142 | 143 | if(!config) 144 | return true; 145 | 146 | if(isUndefined(config[field])) 147 | return true 148 | 149 | if(config[field] && isUndefined(config[field][action])) 150 | return true; 151 | 152 | return config[field][action]; 153 | }, 154 | 155 | highlight: function(text) { 156 | 157 | var self = this; 158 | var filter = self.get('filter'); 159 | 160 | // columns without a corresponding key on the row get passed the whole row 161 | // avoids putting in [object Object] casts 162 | if(typeof text === 'object') 163 | return ''; 164 | 165 | if(!filter || !text) 166 | return text; 167 | 168 | text = String(text); 169 | 170 | if(text.indexOf(filter) > -1) { 171 | return text.split(filter).join('' + filter + ''); 172 | } 173 | 174 | return text; 175 | }, 176 | 177 | cellFor: function(column) { 178 | 179 | if(this.partials[column]) 180 | return column; 181 | 182 | return '__default__'; 183 | 184 | }, 185 | 186 | } 187 | }, 188 | 189 | computed: { 190 | 191 | 192 | // `data` set publicly 193 | // `_data` is internal, includes any filters, sorted 194 | _data: function() { 195 | 196 | var self = this; 197 | 198 | var data = self.get('data'); 199 | 200 | var filter = self.get('filter'); 201 | 202 | var sortOn = self.get('sortOn'); 203 | var sortMode = self.get('sortMode'); 204 | 205 | if(filter && filter.length > 0) { 206 | var re = new RegExp(filter, 'i'); 207 | data = data.filter(function(d) { 208 | for(var p in d) 209 | if(d.hasOwnProperty(p) && re.test(d[p])) 210 | return true; 211 | }); 212 | } 213 | 214 | if(sortOn) { 215 | data = data.slice().sort(sortBy(sortOn, (sortMode == 'desc'))); 216 | } 217 | 218 | return data 219 | .map(function(v, i) { 220 | return {item: v, index: i}; 221 | }); 222 | }, 223 | 224 | rows: function() { 225 | 226 | var self = this; 227 | 228 | var page = self.get('page') - 1; 229 | var _data = self.get('_data'); 230 | var perpage = self.get('perpage'); 231 | var total = self.get('total'); 232 | 233 | // the original data, unfiltered 234 | var data = self.get('data'); 235 | 236 | return _data.slice(page * perpage, Math.min(page * perpage + perpage, total)); 237 | }, 238 | 239 | cols: function() { 240 | 241 | var self = this; 242 | 243 | var data = self.get('data'); //use data instead of _data 244 | var config = self.get('columns'); 245 | var dynamicColumns = self.get('dynamicColumns'); 246 | 247 | var _columns = []; 248 | 249 | if(dynamicColumns) { 250 | 251 | data.forEach( function(row) { 252 | Object.keys(row).forEach(function(key) { 253 | if(_columns.indexOf(key) === -1) 254 | _columns.push(key); 255 | }); 256 | }); 257 | 258 | } else { 259 | 260 | _columns = Object.keys(config); 261 | 262 | } 263 | 264 | if(isObject(config)) { 265 | 266 | var order = []; 267 | 268 | _columns = _columns.filter( function(col) { 269 | 270 | var colConfig = config[col]; 271 | 272 | if( isUndefined(colConfig) || colConfig === true ) 273 | return true; 274 | 275 | // if display is undefined we still want to show the col 276 | if( colConfig.display === false ) 277 | return; 278 | 279 | if( !isUndefined(colConfig.order) && isNumber(colConfig.order) ) { 280 | order.splice(colConfig.order, 0, col); 281 | return; 282 | } 283 | 284 | if( colConfig === false ) 285 | return; 286 | 287 | return true; 288 | }); 289 | 290 | var length = order.length; 291 | 292 | // push to the beginning of _columns 293 | if(order && length > 0) { 294 | while(length--) 295 | _columns.unshift(order[length]); 296 | } 297 | } 298 | 299 | return _columns; 300 | 301 | }, 302 | 303 | total: function() { 304 | var data = this.get('_data'); 305 | return data ? this.get('_data').length : 0; 306 | }, 307 | 308 | current: function() { 309 | var page = this.get('page'); 310 | var perpage = this.get('perpage'); 311 | var total = this.get('total'); 312 | var ppp = (page - 1) * perpage; 313 | return (page == 1 ? 1 : ppp) + '-' + Math.min(ppp + perpage, total) 314 | }, 315 | 316 | pages: function() { 317 | 318 | var total = this.get('total'); 319 | var page = this.get('page'); 320 | var perpage = this.get('perpage'); 321 | 322 | var onFirstPage = this.get('onFirstPage'); 323 | var lastPage = this.get('lastPage'); 324 | 325 | if(perpage > total) 326 | return null; 327 | 328 | var ret = []; 329 | 330 | var n = Math.min(lastPage, 7); 331 | var p = page > lastPage - 4 ? lastPage - n : Math.max(page - 4, 0); 332 | var c = p + n; 333 | while(p++ < c) 334 | ret.push(p); 335 | 336 | //first page 337 | if(page > n) { 338 | ret[0] = 1; 339 | } 340 | 341 | // last page 342 | if(p < lastPage - 4) 343 | ret[ret.length - 1] = lastPage; 344 | 345 | return ret; 346 | }, 347 | 348 | lastPage: function() { 349 | var total = this.get('total'); 350 | var perpage = this.get('perpage'); 351 | 352 | return Math.ceil(total / perpage); 353 | }, 354 | 355 | onFirstPage: function() { 356 | return this.get('page') == 1; 357 | }, 358 | 359 | 360 | onLastPage: function() { 361 | 362 | var page = this.get('page'); 363 | var lastPage = this.get('lastPage'); 364 | 365 | return page == lastPage; 366 | }, 367 | 368 | 369 | }, 370 | 371 | partials: { 372 | __default__: __webpack_require__(65) 373 | }, 374 | 375 | oninit: function() { 376 | 377 | var self = this; 378 | // autofocus editing inputs 379 | self.observe('editing', function(value) { 380 | if(value) { 381 | var node = self.find('td input'); 382 | if(node) 383 | node.focus(); 384 | } 385 | }, { 386 | defer: true 387 | }); 388 | 389 | // reset page when perpage changes 390 | self.observe('perpage filter data', function() { 391 | self.set('page', 1); 392 | }); 393 | 394 | self.observe('perpage', function(value) { 395 | if(typeof value !== 'number') { 396 | self.set('perpage', parseInt(value, 10)); 397 | } 398 | }); 399 | 400 | self.observe('page', function(value) { 401 | if(typeof value !== 'number') { 402 | self.set('perpage', parseInt(value, 10)); 403 | } 404 | }); 405 | 406 | self.observe('_selection', function(_selection) { 407 | 408 | var data = self.get('data'); 409 | if(!_selection) 410 | return; 411 | 412 | self.set('selection', _selection.map(function(v) { 413 | return data[v]; 414 | })); 415 | 416 | }); 417 | 418 | }, 419 | 420 | fieldedited: function(context) { 421 | 422 | var self = this; 423 | var event = context.original; 424 | 425 | if(event.type == 'keyup' && event.keyCode !== 13) 426 | return false; 427 | 428 | var field = context.get(); 429 | var index = context.get('index') + (self.get('page') - 1) * self.get('perpage'); 430 | var row = self.get('_data.' + index + '.item'); 431 | 432 | // don't duplicate 433 | if(context.node.value !== row[field]) { 434 | 435 | // get the real position of index 436 | index = self.get('data').indexOf(row); 437 | 438 | var keypath = 'data.' + index + '.' + field; 439 | var value = context.node.value; 440 | 441 | self.set(keypath, value); 442 | 443 | self.fire('edit', row, field, value, index); 444 | 445 | } 446 | 447 | self.set('editing', null); 448 | 449 | }, 450 | 451 | selectRow: function(details) { 452 | 453 | var mode = this.get('selectionMode'); 454 | var event = details.original; 455 | var row; 456 | 457 | if(mode == 'cell') 458 | return; 459 | 460 | var _selection = this.get('_selection'); 461 | var _lastSelected = this.get('_lastSelected'); 462 | 463 | if(details.context) 464 | row = details.context.index; 465 | else 466 | row = details.index.r; 467 | 468 | 469 | // if for some reason the details.context is undef 470 | // and we can't the index through other means then prevent 471 | // an error and do nothing 472 | if(!isNumber(row)) 473 | return; 474 | 475 | if(event.shiftKey && _lastSelected) { 476 | 477 | var min = Math.min(_lastSelected, row); 478 | var max = Math.max(_lastSelected, row); 479 | 480 | for(var c = max; c <= max && c >= min; c--) { 481 | _selection.push(c); 482 | } 483 | 484 | } else if(event.ctrlKey || event.metaKey || event.altKey || 485 | (_selection.length === 1 && _selection[0] === row)) { 486 | 487 | var index = _selection.indexOf(row); 488 | 489 | if(index > -1) 490 | _selection.splice(index, 1); 491 | else 492 | _selection.push(row); 493 | 494 | } else { 495 | 496 | _selection = [row]; 497 | 498 | } 499 | 500 | _lastSelected = row; 501 | 502 | this.set('_lastSelected', _lastSelected); 503 | this.set('_selection', _selection); 504 | 505 | clearSelection(); 506 | }, 507 | 508 | selectCell: function(details) { 509 | 510 | var mode = this.get('selectionMode'); 511 | 512 | if(mode == 'row') 513 | return; 514 | 515 | var event = details.original; 516 | event.stopImmediatePropagation(); 517 | 518 | clearSelection(); 519 | 520 | //TODO 521 | }, 522 | 523 | setSort: function(column) { 524 | 525 | var self = this; 526 | 527 | if(!column || !self.get('sortable')) 528 | return 529 | 530 | var sortMode = self.get('sortMode'); 531 | var sortOn = self.get('sortOn'); 532 | 533 | // toggle sortMode 534 | if(sortOn == column || !sortMode) { 535 | 536 | if(sortMode == 'asc') 537 | self.set('sortMode', 'desc') 538 | else 539 | self.set('sortMode', 'asc'); 540 | 541 | } 542 | 543 | self.set('sortOn', column); 544 | }, 545 | 546 | previousPage: function() { 547 | this.set('page', Math.max(this.get('page') - 1, 1)); 548 | }, 549 | 550 | nextPage: function() { 551 | this.set('page', Math.min(this.get('page') + 1, this.get('lastPage'))); 552 | }, 553 | 554 | gotoPage: function(page) 555 | { 556 | this.set('page', page); 557 | } 558 | 559 | 560 | 561 | }); 562 | 563 | function clearSelection() { 564 | if (window.getSelection) { 565 | if (window.getSelection().empty) { // Chrome 566 | window.getSelection().empty(); 567 | } else if (window.getSelection().removeAllRanges) { // Firefox 568 | window.getSelection().removeAllRanges(); 569 | } 570 | } else if (document.selection) { // IE? 571 | document.selection.empty(); 572 | } 573 | } 574 | 575 | module.exports = DataTable; 576 | 577 | 578 | /***/ }, 579 | /* 2 */ 580 | /***/ function(module, exports, __webpack_require__) { 581 | 582 | // style-loader: Adds some css to the DOM by adding a