├── .gitignore
├── LICENSE
├── README.md
├── bower.json
├── build.sh
├── dist
├── vue-table.js
└── vue-table.min.js
├── examples
├── bootstrap.html
├── img
│ └── loading.gif
└── semantic.html
├── package.json
└── src
├── components
├── Vuetable.vue
├── VuetablePagination.vue
├── VuetablePaginationBootstrap.vue
├── VuetablePaginationDropdown.vue
└── VuetablePaginationMixin.vue
└── vue-table.js
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Rati Wannapanop
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://www.npmjs.com/package/vuetable)
2 | []()
3 |
4 | ----
5 |
6 | ## Please Note!
7 |
8 | This is the previous version that works with Vue 1.x. The most up-to-date version is the [Vuetable-2](https://github.com/ratiw/vuetable-2). If you like it, please *star* the [Vuetable-2 repo](https://github.com/ratiw/vuetable-2) instead, or make a small [donation](https://www.paypal.me/ratiw) to support it.
9 |
10 | ### This version is *"no longer supported"* as I do not have time to maintain different version.
11 |
12 | ----
13 |
14 | vuetable - data table simplify!
15 | ========
16 |
17 | - No need to render the table yourself
18 | - One simple `vuetable` tag
19 | - Display data retrieved from server with sort options
20 | - Support multi-column sorting (v1.2.0) by @balping
21 | - Pagination component included, swap-able and extensible
22 | - Define fields to map your JSON data structure
23 | - Define row actions and capture the click event to do whatever you want
24 | - Field display customizable via callback function inside Vue.js instance
25 | - Programmatically show/hide any field via reactivity of fields definition
26 | - Use your favorite CSS framework classes to nicely format your table and displayed data
27 | - Events to allow control from Vue.js instance programmatically
28 | - Capture events from `vuetable` to manipulate your table and your data
29 | - Should work with any pre-defined JSON data structure
30 | - Should work with any CSS Framework, e.g. Semantic UI, Twitter's Bootstrap
31 | - Optional detail row to display additional data (v.1.2.0)
32 |
33 | ---
34 | ## vuetable is only working for Vue 1.x, vuetable-2 is for Vue 2.x
35 | If you're looking for the version that's working with Vue 2.x, please go to [`vuetable-2`](https://github.com/ratiw/vuetable-2) repo. However, I still have no time to work on the documentation. But if you're familiar enough with `vuetable`, it shouldn't be any much different. Look at the [what's changed](https://github.com/ratiw/vuetable-2/blob/master/changes.md) for info on changes from version 1 and the [upgrade guide](https://github.com/ratiw/vuetable-2/blob/master/upgrade-guide.md) on how you could upgrade from version 1.
36 |
37 | ---
38 |
39 | ## Note on vue-resource version
40 | vuetable internally uses vue-resource to request data from the `api-url`. Prior to v1.5.3, vuetable uses vue-resource v0.7.4 and it retrieves the returned data from `response.data` object. However, since [v0.9.0](https://github.com/vuejs/vue-resource/releases/tag/0.9.0) the `response.data` has been renamed to `response.body`. vuetable v1.5.3 onward has been updated to use vue-resource v1.0.2.
41 |
42 | This will cause problem with vuetable to display no data because the expected object key is no longer existed and some other related problems as discussed in [#100](https://github.com/ratiw/vue-table/issues/100).
43 |
44 | **If you're using vue-resource in your project and the version is 0.9+, please upgrade to use vuetable v1.5.3.**
45 |
46 | ## Breaking Changes
47 | #### v1.5.0
48 | - deprecated props
49 | + `detail-row-callback`: use `row-detail-component` instead
50 |
51 | #### v1.3.0
52 | - deprecated props
53 | - `paginateConfig`: use `paginateConfigCallback` instead
54 | - `detail-row`: use `detail-row-callback` instead
55 |
56 |
57 | #### v1.2.0
58 | - `sort-order` option type was changed from `Object` to `Array` to support `multi-sort`, therefore it should be declared as array. [#36](https://github.com/ratiw/vue-table/pull/36)
59 |
60 | ```
61 |
65 | ```
66 |
67 | ---
68 |
69 | ##Live Demo
70 | - [JSON data structure](http://vuetable.ratiw.net/api/users)
71 | - [Semantic UI example](http://vuetable.ratiw.net/examples/semantic.html)
72 | - [Bootstrap UI example](http://vuetable.ratiw.net/examples/bootstrap.html)
73 |
74 | ---
75 |
76 | ## What is `vuetable`?
77 | `vuetable` is a Vue.js component that will automatically request (JSON) data
78 | from the server and display them nicely in html table with swappable/extensible
79 | pagination sub-component. You can also add buttons to each row and hook an event
80 | to it
81 |
82 | 
83 |
84 | > Please note that all the examples show in here are styling using Semantic UI CSS Framework,
85 | > but `vuetable` should be able to work with any CSS framwork including Twitter's Bootstrap.
86 | > Please read through and see more info [below](#bootstrap).
87 |
88 | You do this:
89 | ```html
90 |
91 |
92 |
93 |
List of Users
94 |
100 |
101 |
102 |
103 | ```
104 |
105 | ```javascript
106 |
142 | ```
143 |
144 | And you get this!
145 | 
146 |
147 | Since I'm mainly using [Semantic UI](http://semantic-ui.com) as my default CSS Framework, all the css
148 | styles in `vuetable` are based on Semantic UI. If you're using Twitter's [Bootstrap](http://getbootstrap.com)
149 | css framework, please see [documentation in the Wiki pages](https://github.com/ratiw/vue-table/wiki/Using-%60vuetable%60-with-Twitter's-Bootstrap).
150 |
151 | ## Usage
152 |
153 | ### Javascript
154 | ```javascript
155 | //vue-table dependencies (vue and vue-resource)
156 |
157 |
158 |
159 |
160 |
161 | //or
162 |
163 | ```
164 |
165 | ### Bower
166 |
167 | ```
168 | $ bower install vuetable
169 | ```
170 |
171 | ### NPM
172 |
173 | ```
174 | $ npm install vuetable
175 | ```
176 |
177 | ### Vueify version for Browserify and Webpack
178 |
179 | Just `import` or `require` like so,
180 |
181 | ```javascript
182 | //
183 | // firstly, require or import vue and vue-resource
184 | //
185 | var Vue = require('vue');
186 | var VueResource = require('vue-resource');
187 | Vue.use(VueResource);
188 |
189 | //
190 | // secondly, require or import Vuetable and optional VuetablePagination component
191 | //
192 | import Vuetable from 'vuetable/src/components/Vuetable.vue';
193 | import VuetablePagination from 'vuetable/src/components/VuetablePagination.vue';
194 | import VuetablePaginationDropdown from 'vuetable/src/components/VuetablePaginationDropdown.vue';
195 |
196 | //
197 | // thirdly, register components to Vue
198 | //
199 | Vue.component('vuetable', Vuetable);
200 | Vue.component('vuetable-pagination', VuetablePagination)
201 | Vue.component('vuetable-pagination-dropdown', VuetablePaginationDropdown)
202 |
203 | ```
204 | You can combine the second and third steps into one if you like.
205 |
206 | You need to explicitly register the pagination components using `Vue.component()` (instead of just declaring them through the `components:` section); otherwise, the pagination component will not work or swappable or extensible. I *guess* this is because it is embedded inside `vuetable` component.
207 |
208 | ### Direct include
209 |
210 | Just import the `vue-table.js` after `vue.js` and `vue-resource.js` library in your page like so.
211 | ```html
212 |
213 |
214 |
215 | ```
216 |
217 | Then, reference the vuetable via `` tag as following
218 |
219 | ```html
220 |
221 |
225 |
226 |
227 |
244 | ```
245 | - `api-url` is the url of the api that `vuetable` should request data from.
246 | The returned data must be in the form of JSON formatted with at least the number of fields
247 | defined in `fields` property.
248 | - `fields` is the fields mapping that will be used to display data in the table.
249 | You can provide only the name of the fields to be used. But if you would like to get
250 | the true power of `vuetable`, you must provide some more information.
251 | Please see [Field Definition](https://github.com/ratiw/vue-table/wiki/Fields-Definition)
252 | section for more detail.
253 |
254 | For more detail, please see [documentation in the Wiki pages](https://github.com/ratiw/vue-table/wiki).
255 |
256 |
257 | ## Browser Compatability
258 | As I use **Chrome** almost exclusively, it is gaurantee to work on this browser and it SHOULD also work for other **WebKit** based browsers as well. But I can't really gaurantee that since I don't use them regularly.
259 |
260 | However, `vuetable` will NOT WORK on **Internet Explorer** (even IE11) due to the use of `` tag inside `
` according to [this](https://github.com/ratiw/vue-table/issues/25#issuecomment-220920656). In order to make it work with CSS framework table styling, I have to preserve the use of `
` and `` tag inside it.
261 |
262 | It seems to work just fine in **Microsoft Edge** though. Anyway, if you find that it does not work on any other browser, you can let me know by posting in the [Issues](https://github.com/ratiw/vue-table/issues). Or if you are able to make it work on those browser, please let me know or create a pull request.
263 |
264 |
265 | ## Contributions
266 | Any contribution to the code (via pull request would be nice) or any part of the documentation (the Wiki always need some love and care) and any idea and/or suggestion are very welcome.
267 |
268 | However, please do not feel bad if your pull requests or contributions do not get merged or implemented into `vuetable`.
269 |
270 | Your contributions can, not only help make `vuetable` better, but also push it away from what I intend to use it for. I just hope that you find it useful for your use or learn something useful from its source code. But remember, you can always fork it to make it work the way you want.
271 |
272 | ### Building
273 |
274 | Run `npm install`
275 |
276 | Then make sure, you have installed browserify:
277 |
278 | ```
279 | # npm install browserify -g
280 | ```
281 |
282 | You might need root access for running the above command.
283 |
284 | Then you can simply run the build script included in the root folder:
285 |
286 | ```
287 | $ ./build.sh
288 | ```
289 |
290 | This will compile the vue components in the `src` directory to one file in the `dist` folder.
291 |
292 | You might want to get a minified version, in this case run this:
293 |
294 | ```
295 | $ ./build.sh production
296 | ```
297 |
298 | For developement it's useful when it's not needed to recompile manually each time you make a change. If you want this convenience first install watchify globally:
299 |
300 | ```
301 | # npm install watchify -g
302 | ```
303 |
304 | then run
305 |
306 | ```
307 | $ ./build.sh watch
308 | ```
309 |
310 | Now each time you make a change, the source will be recompiled automatically.
311 |
312 |
313 |
314 |
315 | ## License
316 | `vuetable` is open-sourced software licensed under the [MIT license](http://opensource.org/licenses/MIT).
317 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vuetable",
3 | "homepage": "https://github.com/ratiw/vue-table",
4 | "description": "vue.js table component that will automatically request JSON data from server and display them nicely in HTML table with swap-able/extensible pagination component",
5 | "main": "src/vue-table.js",
6 | "dependencies": {
7 | "vue": "~1.0.26",
8 | "vue-resource": "~1.0.2"
9 | },
10 | "keywords": [
11 | "vue",
12 | "vuejs",
13 | "table",
14 | "component",
15 | "json",
16 | "pagination"
17 | ],
18 | "authors": [
19 | "Rati Wannapanop"
20 | ],
21 | "license": "MIT",
22 | "ignore": [
23 | "**/.*",
24 | "node_modules",
25 | "bower_components",
26 | "test",
27 | "tests"
28 | ]
29 | }
30 |
--------------------------------------------------------------------------------
/build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 |
4 | if [[ "$1" == "production" ]]; then
5 | export NODE_ENV=production
6 | browserify -x vue -x vue-resource src/vue-table.js \
7 | | node_modules/uglify-js/bin/uglifyjs -c --screw-ie8 > dist/vue-table.min.js
8 | elif [[ "$1" == "watch" ]]; then
9 |
10 | watchify -x vue -x vue-resource src/vue-table.js -o dist/vue-table.js
11 | else
12 | browserify -x vue -x vue-resource src/vue-table.js -o dist/vue-table.js
13 | fi
--------------------------------------------------------------------------------
/dist/vue-table.js:
--------------------------------------------------------------------------------
1 | (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o hot reload shim applied.')
38 | }
39 | }
40 |
41 | /**
42 | * Shim the view directive (component or router-view).
43 | *
44 | * @param {Object} View
45 | */
46 |
47 | function patchView (View) {
48 | var unbuild = View.unbuild
49 | View.unbuild = function (defer) {
50 | if (!this.hotUpdating) {
51 | var prevComponent = this.childVM && this.childVM.constructor
52 | removeView(prevComponent, this)
53 | // defer = true means we are transitioning to a new
54 | // Component. Register this new component to the list.
55 | if (defer) {
56 | addView(this.Component, this)
57 | }
58 | }
59 | // call original
60 | return unbuild.call(this, defer)
61 | }
62 | }
63 |
64 | /**
65 | * Add a component view to a Component's hot list
66 | *
67 | * @param {Function} Component
68 | * @param {Directive} view - view directive instance
69 | */
70 |
71 | function addView (Component, view) {
72 | var id = Component && Component.options.hotID
73 | if (id) {
74 | if (!map[id]) {
75 | map[id] = {
76 | Component: Component,
77 | views: [],
78 | instances: []
79 | }
80 | }
81 | map[id].views.push(view)
82 | }
83 | }
84 |
85 | /**
86 | * Remove a component view from a Component's hot list
87 | *
88 | * @param {Function} Component
89 | * @param {Directive} view - view directive instance
90 | */
91 |
92 | function removeView (Component, view) {
93 | var id = Component && Component.options.hotID
94 | if (id) {
95 | map[id].views.$remove(view)
96 | }
97 | }
98 |
99 | /**
100 | * Create a record for a hot module, which keeps track of its construcotr,
101 | * instnaces and views (component directives or router-views).
102 | *
103 | * @param {String} id
104 | * @param {Object} options
105 | */
106 |
107 | exports.createRecord = function (id, options) {
108 | if (typeof options === 'function') {
109 | options = options.options
110 | }
111 | if (typeof options.el !== 'string' && typeof options.data !== 'object') {
112 | makeOptionsHot(id, options)
113 | map[id] = {
114 | Component: null,
115 | views: [],
116 | instances: []
117 | }
118 | }
119 | }
120 |
121 | /**
122 | * Make a Component options object hot.
123 | *
124 | * @param {String} id
125 | * @param {Object} options
126 | */
127 |
128 | function makeOptionsHot (id, options) {
129 | options.hotID = id
130 | injectHook(options, 'created', function () {
131 | var record = map[id]
132 | if (!record.Component) {
133 | record.Component = this.constructor
134 | }
135 | record.instances.push(this)
136 | })
137 | injectHook(options, 'beforeDestroy', function () {
138 | map[id].instances.$remove(this)
139 | })
140 | }
141 |
142 | /**
143 | * Inject a hook to a hot reloadable component so that
144 | * we can keep track of it.
145 | *
146 | * @param {Object} options
147 | * @param {String} name
148 | * @param {Function} hook
149 | */
150 |
151 | function injectHook (options, name, hook) {
152 | var existing = options[name]
153 | options[name] = existing
154 | ? Array.isArray(existing)
155 | ? existing.concat(hook)
156 | : [existing, hook]
157 | : [hook]
158 | }
159 |
160 | /**
161 | * Update a hot component.
162 | *
163 | * @param {String} id
164 | * @param {Object|null} newOptions
165 | * @param {String|null} newTemplate
166 | */
167 |
168 | exports.update = function (id, newOptions, newTemplate) {
169 | var record = map[id]
170 | // force full-reload if an instance of the component is active but is not
171 | // managed by a view
172 | if (!record || (record.instances.length && !record.views.length)) {
173 | console.log('[HMR] Root or manually-mounted instance modified. Full reload may be required.')
174 | if (!isBrowserify) {
175 | window.location.reload()
176 | } else {
177 | // browserify-hmr somehow sends incomplete bundle if we reload here
178 | return
179 | }
180 | }
181 | if (!isBrowserify) {
182 | // browserify-hmr already logs this
183 | console.log('[HMR] Updating component: ' + format(id))
184 | }
185 | var Component = record.Component
186 | // update constructor
187 | if (newOptions) {
188 | // in case the user exports a constructor
189 | Component = record.Component = typeof newOptions === 'function'
190 | ? newOptions
191 | : Vue.extend(newOptions)
192 | makeOptionsHot(id, Component.options)
193 | }
194 | if (newTemplate) {
195 | Component.options.template = newTemplate
196 | }
197 | // handle recursive lookup
198 | if (Component.options.name) {
199 | Component.options.components[Component.options.name] = Component
200 | }
201 | // reset constructor cached linker
202 | Component.linker = null
203 | // reload all views
204 | record.views.forEach(function (view) {
205 | updateView(view, Component)
206 | })
207 | // flush devtools
208 | if (window.__VUE_DEVTOOLS_GLOBAL_HOOK__) {
209 | window.__VUE_DEVTOOLS_GLOBAL_HOOK__.emit('flush')
210 | }
211 | }
212 |
213 | /**
214 | * Update a component view instance
215 | *
216 | * @param {Directive} view
217 | * @param {Function} Component
218 | */
219 |
220 | function updateView (view, Component) {
221 | if (!view._bound) {
222 | return
223 | }
224 | view.Component = Component
225 | view.hotUpdating = true
226 | // disable transitions
227 | view.vm._isCompiled = false
228 | // save state
229 | var state = extractState(view.childVM)
230 | // remount, make sure to disable keep-alive
231 | var keepAlive = view.keepAlive
232 | view.keepAlive = false
233 | view.mountComponent()
234 | view.keepAlive = keepAlive
235 | // restore state
236 | restoreState(view.childVM, state, true)
237 | // re-eanble transitions
238 | view.vm._isCompiled = true
239 | view.hotUpdating = false
240 | }
241 |
242 | /**
243 | * Extract state from a Vue instance.
244 | *
245 | * @param {Vue} vm
246 | * @return {Object}
247 | */
248 |
249 | function extractState (vm) {
250 | return {
251 | cid: vm.constructor.cid,
252 | data: vm.$data,
253 | children: vm.$children.map(extractState)
254 | }
255 | }
256 |
257 | /**
258 | * Restore state to a reloaded Vue instance.
259 | *
260 | * @param {Vue} vm
261 | * @param {Object} state
262 | */
263 |
264 | function restoreState (vm, state, isRoot) {
265 | var oldAsyncConfig
266 | if (isRoot) {
267 | // set Vue into sync mode during state rehydration
268 | oldAsyncConfig = Vue.config.async
269 | Vue.config.async = false
270 | }
271 | // actual restore
272 | if (isRoot || !vm._props) {
273 | vm.$data = state.data
274 | } else {
275 | Object.keys(state.data).forEach(function (key) {
276 | if (!vm._props[key]) {
277 | // for non-root, only restore non-props fields
278 | vm.$data[key] = state.data[key]
279 | }
280 | })
281 | }
282 | // verify child consistency
283 | var hasSameChildren = vm.$children.every(function (c, i) {
284 | return state.children[i] && state.children[i].cid === c.constructor.cid
285 | })
286 | if (hasSameChildren) {
287 | // rehydrate children
288 | vm.$children.forEach(function (c, i) {
289 | restoreState(c, state.children[i])
290 | })
291 | }
292 | if (isRoot) {
293 | Vue.config.async = oldAsyncConfig
294 | }
295 | }
296 |
297 | function format (id) {
298 | var match = id.match(/[^\/]+\.vue$/)
299 | return match ? match[0] : id
300 | }
301 |
302 | },{}],2:[function(require,module,exports){
303 | var inserted = exports.cache = {}
304 |
305 | exports.insert = function (css) {
306 | if (inserted[css]) return
307 | inserted[css] = true
308 |
309 | var elem = document.createElement('style')
310 | elem.setAttribute('type', 'text/css')
311 |
312 | if ('textContent' in elem) {
313 | elem.textContent = css
314 | } else {
315 | elem.styleSheet.cssText = css
316 | }
317 |
318 | document.getElementsByTagName('head')[0].appendChild(elem)
319 | return elem
320 | }
321 |
322 | },{}],3:[function(require,module,exports){
323 | var __vueify_insert__ = require("vueify/lib/insert-css")
324 | var __vueify_style__ = __vueify_insert__.insert("\n.vuetable th.sortable:hover {\n color: #2185d0;\n cursor: pointer;\n}\n.vuetable-actions {\n width: 15%;\n padding: 12px 0px;\n text-align: center;\n}\n.vuetable-pagination {\n background: #f9fafb !important;\n}\n.vuetable-pagination-info {\n margin-top: auto;\n margin-bottom: auto;\n}\n")
325 | 'use strict';
326 |
327 | Object.defineProperty(exports, "__esModule", {
328 | value: true
329 | });
330 | exports.default = {
331 | props: {
332 | wrapperClass: {
333 | type: String,
334 | default: function _default() {
335 | return null;
336 | }
337 | },
338 | tableWrapper: {
339 | type: String,
340 | default: function _default() {
341 | return null;
342 | }
343 | },
344 | tableClass: {
345 | type: String,
346 | default: function _default() {
347 | return 'ui blue striped selectable celled stackable attached table';
348 | }
349 | },
350 | loadingClass: {
351 | type: String,
352 | default: function _default() {
353 | return 'loading';
354 | }
355 | },
356 | dataPath: {
357 | type: String,
358 | default: function _default() {
359 | return 'data';
360 | }
361 | },
362 | paginationPath: {
363 | type: String,
364 | default: function _default() {
365 | return 'links.pagination';
366 | }
367 | },
368 | fields: {
369 | type: Array,
370 | required: true
371 | },
372 | apiUrl: {
373 | type: String,
374 | required: true
375 | },
376 | sortOrder: {
377 | type: Array,
378 | default: function _default() {
379 | return [];
380 | }
381 | },
382 | multiSort: {
383 | type: Boolean,
384 | default: function _default() {
385 | return false;
386 | }
387 | },
388 | /*
389 | * physical key that will trigger multi-sort option
390 | * possible values: 'alt', 'ctrl', 'meta', 'shift'
391 | * 'ctrl' might not work as expected on Mac
392 | */
393 | multiSortKey: {
394 | type: String,
395 | default: 'alt'
396 | },
397 | perPage: {
398 | type: Number,
399 | coerce: function coerce(val) {
400 | return parseInt(val);
401 | },
402 | default: function _default() {
403 | return 10;
404 | }
405 | },
406 | ascendingIcon: {
407 | type: String,
408 | default: function _default() {
409 | return 'blue chevron up icon';
410 | }
411 | },
412 | descendingIcon: {
413 | type: String,
414 | default: function _default() {
415 | return 'blue chevron down icon';
416 | }
417 | },
418 | appendParams: {
419 | type: Array,
420 | default: function _default() {
421 | return [];
422 | }
423 | },
424 | showPagination: {
425 | type: Boolean,
426 | default: function _default() {
427 | return true;
428 | }
429 | },
430 | paginationComponent: {
431 | type: String,
432 | default: function _default() {
433 | return 'vuetable-pagination';
434 | }
435 | },
436 | paginationInfoTemplate: {
437 | type: String,
438 | default: function _default() {
439 | return "Displaying {from} to {to} of {total} items";
440 | }
441 | },
442 | paginationInfoNoDataTemplate: {
443 | type: String,
444 | default: function _default() {
445 | return 'No relevant data';
446 | }
447 | },
448 | paginationClass: {
449 | type: String,
450 | default: function _default() {
451 | return 'ui bottom attached segment grid';
452 | }
453 | },
454 | paginationInfoClass: {
455 | type: String,
456 | default: function _default() {
457 | return 'left floated left aligned six wide column';
458 | }
459 | },
460 | paginationComponentClass: {
461 | type: String,
462 | default: function _default() {
463 | return 'right floated right aligned six wide column';
464 | }
465 | },
466 | paginationConfig: {
467 | type: String,
468 | default: function _default() {
469 | return 'paginationConfig';
470 | }
471 | },
472 | paginationConfigCallback: {
473 | type: String,
474 | default: function _default() {
475 | return 'paginationConfig';
476 | }
477 | },
478 | itemActions: {
479 | type: Array,
480 | default: function _default() {
481 | return [];
482 | }
483 | },
484 | queryParams: {
485 | type: Object,
486 | default: function _default() {
487 | return {
488 | sort: 'sort',
489 | page: 'page',
490 | perPage: 'per_page'
491 | };
492 | }
493 | },
494 | loadOnStart: {
495 | type: Boolean,
496 | default: function _default() {
497 | return true;
498 | }
499 | },
500 | selectedTo: {
501 | type: Array,
502 | default: function _default() {
503 | return [];
504 | }
505 | },
506 | httpOptions: {
507 | type: Object,
508 | default: function _default() {
509 | return {};
510 | }
511 | },
512 | detailRow: {
513 | type: String,
514 | default: ''
515 | },
516 | detailRowCallback: {
517 | type: String,
518 | default: ''
519 | },
520 | detailRowId: {
521 | type: String,
522 | default: 'id'
523 | },
524 | detailRowTransition: {
525 | type: String,
526 | default: ''
527 | },
528 | detailRowClass: {
529 | type: String,
530 | default: 'vuetable-detail-row'
531 | },
532 | detailRowComponent: {
533 | type: String,
534 | default: ''
535 | },
536 | rowClassCallback: {
537 | type: String,
538 | default: ''
539 | }
540 | },
541 | data: function data() {
542 | return {
543 | eventPrefix: 'vuetable:',
544 | tableData: null,
545 | tablePagination: null,
546 | currentPage: 1,
547 | visibleDetailRows: []
548 | };
549 | },
550 | directives: {
551 | 'attr': {
552 | update: function update(value) {
553 | for (var i in value) {
554 | this.el.setAttribute(i, value[i]);
555 | }
556 | }
557 | }
558 | },
559 | computed: {
560 | paginationInfo: function paginationInfo() {
561 | if (this.tablePagination == null || this.tablePagination.total == 0) {
562 | return this.paginationInfoNoDataTemplate;
563 | }
564 |
565 | return this.paginationInfoTemplate.replace('{from}', this.tablePagination.from || 0).replace('{to}', this.tablePagination.to || 0).replace('{total}', this.tablePagination.total || 0);
566 | },
567 | useDetailRow: function useDetailRow() {
568 | if (this.tableData && typeof this.tableData[0][this.detailRowId] === 'undefined') {
569 | console.warn('You need to define "detail-row-id" in order for detail-row feature to work!');
570 | return false;
571 | }
572 |
573 | return this.detailRowCallback.trim() !== '' || this.detailRowComponent !== '';
574 | },
575 | useDetailRowComponent: function useDetailRowComponent() {
576 | return this.detailRowComponent !== '';
577 | },
578 | countVisibleFields: function countVisibleFields() {
579 | return this.fields.filter(function (field) {
580 | return field.visible;
581 | }).length;
582 | }
583 | },
584 | methods: {
585 | normalizeFields: function normalizeFields() {
586 | var self = this;
587 | var obj;
588 | this.fields.forEach(function (field, i) {
589 | if (typeof field === 'string') {
590 | obj = {
591 | name: field,
592 | title: self.setTitle(field),
593 | titleClass: '',
594 | dataClass: '',
595 | callback: null,
596 | visible: true
597 | };
598 | } else {
599 | obj = {
600 | name: field.name,
601 | title: field.title === undefined ? self.setTitle(field.name) : field.title,
602 | sortField: field.sortField,
603 | titleClass: field.titleClass === undefined ? '' : field.titleClass,
604 | dataClass: field.dataClass === undefined ? '' : field.dataClass,
605 | callback: field.callback === undefined ? '' : field.callback,
606 | visible: field.visible === undefined ? true : field.visible
607 | };
608 | }
609 | self.fields.$set(i, obj);
610 | });
611 | },
612 | setTitle: function setTitle(str) {
613 | if (this.isSpecialField(str)) {
614 | return '';
615 | }
616 |
617 | return this.titleCase(str);
618 | },
619 | titleCase: function titleCase(str) {
620 | return str.replace(/\w+/g, function (txt) {
621 | return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
622 | });
623 | },
624 | notIn: function notIn(str, arr) {
625 | return arr.indexOf(str) === -1;
626 | },
627 | loadData: function loadData() {
628 | var self = this;
629 |
630 | var wrapper = document.querySelector(this.tableWrapper);
631 | this.showLoadingAnimation(wrapper);
632 |
633 | var url = this.apiUrl + '?' + this.getAllQueryParams();
634 | this.$http.get(url, this.httpOptions).then(function (response) {
635 | var body = this.transform(response.body);
636 | self.tableData = self.getObjectValue(body, self.dataPath, null);
637 | self.tablePagination = self.getObjectValue(body, self.paginationPath, null);
638 | if (self.tablePagination === null) {
639 | console.warn('vuetable: pagination-path "' + self.paginationPath + '" not found. ' + 'It looks like the data returned from the sever does not have pagination information ' + 'or you may have set it incorrectly.');
640 | }
641 |
642 | self.$nextTick(function () {
643 | self.dispatchEvent('load-success', response);
644 | self.broadcastEvent('load-success', self.tablePagination);
645 |
646 | self.hideLoadingAnimation(wrapper);
647 | });
648 | }, function (response) {
649 | self.dispatchEvent('load-error', response);
650 | self.broadcastEvent('load-error', response);
651 |
652 | self.hideLoadingAnimation(wrapper);
653 | });
654 | },
655 | getAllQueryParams: function getAllQueryParams() {
656 | var params = [this.queryParams.sort + '=' + this.getSortParam(), this.queryParams.page + '=' + this.currentPage, this.queryParams.perPage + '=' + this.perPage].join('&');
657 |
658 | if (this.appendParams.length > 0) {
659 | params += '&' + this.appendParams.join('&');
660 | }
661 |
662 | return params;
663 | },
664 | showLoadingAnimation: function showLoadingAnimation(wrapper) {
665 | if (wrapper !== null) {
666 | this.addClass(wrapper, this.loadingClass);
667 | }
668 | this.dispatchEvent('loading');
669 | },
670 | hideLoadingAnimation: function hideLoadingAnimation(wrapper) {
671 | if (wrapper !== null) {
672 | this.removeClass(wrapper, this.loadingClass);
673 | }
674 | this.dispatchEvent('loaded');
675 | },
676 | transform: function transform(data) {
677 | var func = 'transform';
678 |
679 | if (this.parentFunctionExists(func)) {
680 | return this.$parent[func].call(this.$parent, data);
681 | }
682 |
683 | return data;
684 | },
685 | parentFunctionExists: function parentFunctionExists(func) {
686 | return func !== '' && typeof this.$parent[func] === 'function';
687 | },
688 | getTitle: function getTitle(field) {
689 | if (typeof field.title === 'undefined') {
690 | return field.name.replace('.', ' ');
691 | }
692 | return field.title;
693 | },
694 | getSortParam: function getSortParam() {
695 | if (!this.sortOrder || this.sortOrder.field == '') {
696 | return '';
697 | }
698 |
699 | if (typeof this.$parent['getSortParam'] == 'function') {
700 | return this.$parent['getSortParam'].call(this.$parent, this.sortOrder);
701 | }
702 |
703 | return this.getDefaultSortParam();
704 | },
705 | getDefaultSortParam: function getDefaultSortParam() {
706 | var result = '';
707 |
708 | for (var i = 0; i < this.sortOrder.length; i++) {
709 | var fieldName = typeof this.sortOrder[i].sortField === 'undefined' ? this.sortOrder[i].field : this.sortOrder[i].sortField;
710 |
711 | result += fieldName + '|' + this.sortOrder[i].direction + (i + 1 < this.sortOrder.length ? ',' : '');
712 | }
713 |
714 | return result;
715 | },
716 | addClass: function addClass(el, className) {
717 | if (el.classList) el.classList.add(className);else el.className += ' ' + className;
718 | },
719 | removeClass: function removeClass(el, className) {
720 | if (el.classList) el.classList.remove(className);else el.className = el.className.replace(new RegExp('(^|\\b)' + className.split(' ').join('|') + '(\\b|$)', 'gi'), ' ');
721 | },
722 | dispatchEvent: function dispatchEvent(eventName, args) {
723 | this.$dispatch(this.eventPrefix + eventName, args);
724 | },
725 | broadcastEvent: function broadcastEvent(eventName, args) {
726 | this.$broadcast(this.eventPrefix + eventName, args);
727 | },
728 | orderBy: function orderBy(field, event) {
729 | if (!this.isSortable(field)) {
730 | return;
731 | }
732 |
733 | var key = this.multiSortKey.toLowerCase() + 'Key';
734 |
735 | if (this.multiSort && event[key]) {
736 | //adding column to multisort
737 | var i = this.currentSortOrder(field);
738 |
739 | if (i === false) {
740 | //this field is not in the sort array yet
741 | this.sortOrder.push({
742 | field: field.sortField,
743 | direction: 'asc'
744 | });
745 | } else {
746 | //this field is in the sort array, now we change its state
747 | if (this.sortOrder[i].direction == 'asc') {
748 | // switch direction
749 | this.sortOrder[i].direction = 'desc';
750 | } else {
751 | //remove sort condition
752 | this.sortOrder.splice(i, 1);
753 | }
754 | }
755 | } else {
756 | //no multisort, or resetting sort
757 | if (this.sortOrder.length == 0) {
758 | this.sortOrder.push({
759 | field: '',
760 | direction: 'asc'
761 | });
762 | }
763 |
764 | this.sortOrder.splice(1); //removes additional columns
765 |
766 | if (this.sortOrder[0].field == field.sortField) {
767 | // change sort direction
768 | this.sortOrder[0].direction = this.sortOrder[0].direction == 'asc' ? 'desc' : 'asc';
769 | } else {
770 | // reset sort direction
771 | this.sortOrder[0].direction = 'asc';
772 | }
773 | this.sortOrder[0].field = field.sortField;
774 | }
775 |
776 | this.currentPage = 1; // reset page index
777 | this.loadData();
778 | },
779 | isSortable: function isSortable(field) {
780 | return !(typeof field.sortField == 'undefined');
781 | },
782 | isCurrentSortField: function isCurrentSortField(field) {
783 | return this.currentSortOrder(field) !== false;
784 | },
785 | currentSortOrder: function currentSortOrder(field) {
786 | if (!this.isSortable(field)) {
787 | return false;
788 | }
789 |
790 | for (var i = 0; i < this.sortOrder.length; i++) {
791 | if (this.sortOrder[i].field == field.sortField) {
792 | return i;
793 | }
794 | }
795 |
796 | return false;
797 | },
798 | sortIcon: function sortIcon(field) {
799 | var i = this.currentSortOrder(field);
800 | if (i !== false) {
801 | return this.sortOrder[i].direction == 'asc' ? this.ascendingIcon : this.descendingIcon;
802 | } else {
803 | return '';
804 | }
805 | },
806 | sortIconOpacity: function sortIconOpacity(field) {
807 | //fields with stronger precedence have darker color
808 |
809 | //if there are few fields, we go down by 0.3
810 | //ex. 2 fields are selected: 1.0, 0.7
811 |
812 | //if there are more we go down evenly on the given spectrum
813 | //ex. 6 fields are selected: 1.0, 0.86, 0.72, 0.58, 0.44, 0.3
814 |
815 | var max = 1.0;
816 | var min = 0.3;
817 | var step = 0.3;
818 |
819 | var count = this.sortOrder.length;
820 | var current = this.currentSortOrder(field);
821 |
822 | if (max - count * step < min) {
823 | step = (max - min) / (count - 1);
824 | }
825 |
826 | var opacity = max - current * step;
827 |
828 | return opacity;
829 | },
830 | gotoPreviousPage: function gotoPreviousPage() {
831 | if (this.currentPage > 1) {
832 | this.currentPage--;
833 | this.loadData();
834 | }
835 | },
836 | gotoNextPage: function gotoNextPage() {
837 | if (this.currentPage < this.tablePagination.last_page) {
838 | this.currentPage++;
839 | this.loadData();
840 | }
841 | },
842 | gotoPage: function gotoPage(page) {
843 | if (page != this.currentPage && page > 0 && page <= this.tablePagination.last_page) {
844 | this.currentPage = page;
845 | this.loadData();
846 | }
847 | },
848 | isSpecialField: function isSpecialField(fieldName) {
849 | // return fieldName.startsWith('__')
850 | return fieldName.slice(0, 2) === '__';
851 | },
852 | hasCallback: function hasCallback(item) {
853 | return item.callback ? true : false;
854 | },
855 | callCallback: function callCallback(field, item) {
856 | if (!this.hasCallback(field)) return;
857 |
858 | var args = field.callback.split('|');
859 | var func = args.shift();
860 |
861 | if (typeof this.$parent[func] == 'function') {
862 | return args.length > 0 ? this.$parent[func].apply(this.$parent, [this.getObjectValue(item, field.name)].concat(args)) : this.$parent[func].call(this.$parent, this.getObjectValue(item, field.name));
863 | }
864 |
865 | return null;
866 | },
867 | getObjectValue: function getObjectValue(object, path, defaultValue) {
868 | defaultValue = typeof defaultValue == 'undefined' ? null : defaultValue;
869 |
870 | var obj = object;
871 | if (path.trim() != '') {
872 | var keys = path.split('.');
873 | keys.forEach(function (key) {
874 | if (obj !== null && typeof obj[key] != 'undefined' && obj[key] !== null) {
875 | obj = obj[key];
876 | } else {
877 | obj = defaultValue;
878 | return;
879 | }
880 | });
881 | }
882 | return obj;
883 | },
884 | callAction: function callAction(action, data) {
885 | this.$dispatch(this.eventPrefix + 'action', action, data);
886 | },
887 | addParam: function addParam(param) {
888 | this.appendParams.push(param);
889 | },
890 | toggleCheckbox: function toggleCheckbox(isChecked, dataItem, fieldName) {
891 | var idColumn = this.extractArgs(fieldName);
892 | if (idColumn === undefined) {
893 | console.warn('You did not provide reference id column with "__checkbox:" field!');
894 | return;
895 | }
896 |
897 | var key = dataItem[idColumn];
898 | if (isChecked) {
899 | this.selectId(key);
900 | } else {
901 | this.unselectId(key);
902 | }
903 | },
904 | toggleAllCheckboxes: function toggleAllCheckboxes(isChecked, fieldName) {
905 | var self = this;
906 | var idColumn = this.extractArgs(fieldName);
907 |
908 | if (isChecked) {
909 | this.tableData.forEach(function (dataItem) {
910 | self.selectId(dataItem[idColumn]);
911 | });
912 | } else {
913 | this.tableData.forEach(function (dataItem) {
914 | self.unselectId(dataItem[idColumn]);
915 | });
916 | }
917 | },
918 | selectId: function selectId(key) {
919 | if (!this.isSelectedRow(key)) {
920 | this.selectedTo.push(key);
921 | }
922 | },
923 | unselectId: function unselectId(key) {
924 | this.selectedTo.$remove(key);
925 | // this.selectedTo = this.selectedTo.filter(function(item) {
926 | // return item !== key
927 | // })
928 | },
929 | isSelectedRow: function isSelectedRow(key) {
930 | return this.selectedTo.indexOf(key) >= 0;
931 | },
932 | rowSelected: function rowSelected(dataItem, fieldName) {
933 | var idColumn = this.extractArgs(fieldName);
934 | // var key = dataItem[idColumn]
935 |
936 | return this.isSelectedRow(dataItem[idColumn]);
937 | },
938 | checkCheckboxesState: function checkCheckboxesState(fieldName) {
939 | var self = this;
940 | var idColumn = this.extractArgs(fieldName);
941 | var selector = 'th.checkbox_' + idColumn + ' input[type=checkbox]';
942 | var els = document.querySelectorAll(selector);
943 |
944 | // count how many checkbox row in the current page has been checked
945 | var selected = this.tableData.filter(function (item) {
946 | return self.selectedTo.indexOf(item[idColumn]) >= 0;
947 | });
948 |
949 | // count == 0, clear the checkbox
950 | if (selected.length <= 0) {
951 | els.forEach(function (el) {
952 | el.indeterminate = false;
953 | });
954 | return false;
955 | }
956 | // count > 0 and count < perPage, set checkbox state to 'indeterminate'
957 | else if (selected.length < this.perPage) {
958 | els.forEach(function (el) {
959 | el.indeterminate = true;
960 | });
961 | return true;
962 | }
963 | // count == perPage, set checkbox state to 'checked'
964 | else {
965 | els.forEach(function (el) {
966 | el.indeterminate = false;
967 | });
968 | return true;
969 | }
970 | },
971 | extractName: function extractName(string) {
972 | return string.split(':')[0].trim();
973 | },
974 | extractArgs: function extractArgs(string) {
975 | return string.split(':')[1];
976 | },
977 | callDetailRowCallback: function callDetailRowCallback(item) {
978 | var func = this.detailRowCallback.trim();
979 | if (func === '') {
980 | return '';
981 | }
982 |
983 | if (typeof this.$parent[func] == 'function') {
984 | return this.$parent[func].call(this.$parent, item);
985 | } else {
986 | console.error('Function "' + func + '()" does not exist!');
987 | }
988 | },
989 | isVisibleDetailRow: function isVisibleDetailRow(rowId) {
990 | return this.visibleDetailRows.indexOf(rowId) >= 0;
991 | },
992 | showDetailRow: function showDetailRow(rowId) {
993 | if (!this.isVisibleDetailRow(rowId)) {
994 | this.visibleDetailRows.push(rowId);
995 | }
996 | },
997 | hideDetailRow: function hideDetailRow(rowId) {
998 | if (this.isVisibleDetailRow(rowId)) {
999 | this.visibleDetailRows.$remove(rowId);
1000 | }
1001 | },
1002 | toggleDetailRow: function toggleDetailRow(rowId) {
1003 | if (this.isVisibleDetailRow(rowId)) {
1004 | this.hideDetailRow(rowId);
1005 | } else {
1006 | this.showDetailRow(rowId);
1007 | }
1008 | },
1009 | onRowClass: function onRowClass(dataItem, index) {
1010 | var func = this.rowClassCallback.trim();
1011 |
1012 | if (func !== '' && typeof this.$parent[func] === 'function') {
1013 | return this.$parent[func].call(this.$parent, dataItem, index);
1014 | }
1015 | return '';
1016 | },
1017 | onRowChanged: function onRowChanged(dataItem) {
1018 | this.dispatchEvent('row-changed', dataItem);
1019 | return true;
1020 | },
1021 | onRowClicked: function onRowClicked(dataItem, event) {
1022 | this.$dispatch(this.eventPrefix + 'row-clicked', dataItem, event);
1023 | return true;
1024 | },
1025 | onRowDoubleClicked: function onRowDoubleClicked(dataItem, event) {
1026 | this.$dispatch(this.eventPrefix + 'row-dblclicked', dataItem, event);
1027 | },
1028 | onCellClicked: function onCellClicked(dataItem, field, event) {
1029 | this.$dispatch(this.eventPrefix + 'cell-clicked', dataItem, field, event);
1030 | },
1031 | onCellDoubleClicked: function onCellDoubleClicked(dataItem, field, event) {
1032 | this.$dispatch(this.eventPrefix + 'cell-dblclicked', dataItem, field, event);
1033 | },
1034 | onDetailRowClick: function onDetailRowClick(dataItem, event) {
1035 | this.$dispatch(this.eventPrefix + 'detail-row-clicked', dataItem, event);
1036 | },
1037 | callPaginationConfig: function callPaginationConfig() {
1038 | if (typeof this.$parent[this.paginationConfigCallback] === 'function') {
1039 | this.$parent[this.paginationConfigCallback].call(this.$parent, this.$refs.pagination.$options.name);
1040 | }
1041 | },
1042 | logDeprecatedMessage: function logDeprecatedMessage(name, replacer) {
1043 | var msg = '"{name}" prop is being deprecated and will be removed in the future. Please use "{replacer}" instead.';
1044 | console.warn(msg.replace('{name}', name).replace('{replacer}', replacer));
1045 | },
1046 | checkForDeprecatedProps: function checkForDeprecatedProps() {
1047 | if (this.paginationConfig !== 'paginationConfig') {
1048 | this.logDeprecatedMessage('paginationConfig', 'paginationConfigCallback');
1049 | }
1050 | if (this.detailRow !== '') {
1051 | this.logDeprecatedMessage('detail-row', 'detail-row-callback');
1052 | }
1053 | if (this.detailRowCallback !== '') {
1054 | this.logDeprecatedMessage('detail-row-callback', 'detail-row-component');
1055 | }
1056 | }
1057 | },
1058 | watch: {
1059 | 'multiSort': function multiSort(newVal, oldVal) {
1060 | if (newVal === false && this.sortOrder.length > 1) {
1061 | this.sortOrder.splice(1);
1062 | this.loadData();
1063 | }
1064 | }
1065 | },
1066 | events: {
1067 | 'vuetable-pagination:change-page': function vuetablePaginationChangePage(page) {
1068 | if (page == 'prev') {
1069 | this.gotoPreviousPage();
1070 | } else if (page == 'next') {
1071 | this.gotoNextPage();
1072 | } else {
1073 | this.gotoPage(page);
1074 | }
1075 | },
1076 | 'vuetable:reload': function vuetableReload() {
1077 | this.loadData();
1078 | },
1079 | 'vuetable:refresh': function vuetableRefresh() {
1080 | this.currentPage = 1;
1081 | this.loadData();
1082 | },
1083 | 'vuetable:goto-page': function vuetableGotoPage(page) {
1084 | this.$emit('vuetable-pagination:change-page', page);
1085 | },
1086 | 'vuetable:set-options': function vuetableSetOptions(options) {
1087 | for (var n in options) {
1088 | this.$set(n, options[n]);
1089 | }
1090 | },
1091 | 'vuetable:toggle-detail': function vuetableToggleDetail(dataItem) {
1092 | this.toggleDetailRow(dataItem);
1093 | },
1094 | 'vuetable:show-detail': function vuetableShowDetail(dataItem) {
1095 | this.showDetailRow(dataItem);
1096 | },
1097 | 'vuetable:hide-detail': function vuetableHideDetail(dataItem) {
1098 | this.hideDetailRow(dataItem);
1099 | },
1100 | 'vuetable:normalize-fields': function vuetableNormalizeFields() {
1101 | this.normalizeFields();
1102 | }
1103 | },
1104 | created: function created() {
1105 | this.checkForDeprecatedProps();
1106 | this.normalizeFields();
1107 | if (this.loadOnStart) {
1108 | this.loadData();
1109 | }
1110 | this.$nextTick(function () {
1111 | this.callPaginationConfig();
1112 | });
1113 | }
1114 | };
1115 | if (module.exports.__esModule) module.exports = module.exports.default
1116 | ;(typeof module.exports === "function"? module.exports.options: module.exports).template = "\n