├── js
├── schema
│ ├── list-view.js
│ └── properties.js
├── vnext
│ ├── projection
│ │ ├── default-cell.jade
│ │ ├── selection-head.jade
│ │ ├── sortable-header.jade
│ │ ├── single-selection-resolver.js
│ │ ├── selection-body.jade
│ │ ├── item-index.js
│ │ ├── index.js
│ │ ├── flex-columns.js
│ │ ├── selection-resolver.js
│ │ ├── events.js
│ │ ├── multiple-selection-resolver.js
│ │ ├── buffer.js
│ │ ├── range-selection.js
│ │ └── query.js
│ ├── layout
│ │ ├── index.js
│ │ ├── editable.jade
│ │ ├── row.jade
│ │ ├── flex-head-cell.jade
│ │ ├── flex-row.jade
│ │ ├── flex-header-footer.jade
│ │ ├── header-footer.jade
│ │ ├── column-group.jade
│ │ ├── table-static.jade
│ │ ├── flex-static.jade
│ │ ├── flex-sticky.jade
│ │ ├── flex-fixed.jade
│ │ ├── table-sticky.jade
│ │ ├── table-fixed.jade
│ │ ├── flex-layout.less
│ │ ├── table-mixins.jade
│ │ ├── escape.js
│ │ ├── flex-mixins.jade
│ │ └── const.js
│ ├── data-source
│ │ ├── index.js
│ │ ├── odata.js
│ │ ├── js-data.js
│ │ └── memory.js
│ └── factory
│ │ ├── data-source-plugin.js
│ │ └── grid-view-plugin.js
├── .DS_Store
├── layout
│ ├── .DS_Store
│ ├── template
│ │ ├── editable.jade
│ │ ├── row.editable.string.jade
│ │ ├── selectable.jade
│ │ ├── row.tri-state-checked.jade
│ │ ├── table.dot
│ │ ├── table.while.jade
│ │ └── table.mobile.jade
│ ├── index.js
│ ├── renderer
│ │ └── index.js
│ ├── px.js
│ └── measure.js
├── factory
│ ├── utility.js
│ ├── renderers-plugin.js
│ ├── grid-view-plugin.js
│ └── grid-factory.js
├── model
│ ├── index.js
│ └── response.js
├── popup-editor
│ └── index.jade
├── projection
│ ├── columns.js
│ ├── memory.js
│ ├── sink.js
│ ├── a11y.js
│ ├── column-hovertext.js
│ ├── row-index.js
│ ├── mock.js
│ ├── memory-page.js
│ ├── index.js
│ ├── map.js
│ ├── memory-filter.js
│ ├── column-template.js
│ ├── aggregate-row.js
│ ├── memory-sort.js
│ ├── property-template.js
│ ├── page.js
│ ├── editable-string.js
│ ├── column-i18n.js
│ ├── memory-queryable.js
│ ├── odata.js
│ ├── jsdata.js
│ ├── row.js
│ └── column-queryable.js
├── windowContainer.js
├── index.js
├── containerBase.js
└── elementContainer.js
├── .bowerrc
├── demos
├── vnext
│ ├── events
│ │ ├── README.md
│ │ ├── demo.config.json
│ │ ├── index.less
│ │ ├── index.jade
│ │ └── index.js
│ ├── rows
│ │ ├── README.md
│ │ ├── demo.config.json
│ │ ├── index.jade
│ │ └── index.less
│ ├── columns
│ │ └── README.md
│ ├── scrolling
│ │ ├── README.md
│ │ ├── emails.jade
│ │ ├── fixed-header
│ │ │ ├── demo.config.json
│ │ │ ├── index.less
│ │ │ ├── index.js
│ │ │ └── index.jade
│ │ ├── static-header
│ │ │ ├── demo.config.json
│ │ │ ├── index.less
│ │ │ ├── index.js
│ │ │ └── index.jade
│ │ ├── sticky-header-on-element
│ │ │ ├── demo.config.json
│ │ │ ├── index.less
│ │ │ ├── index.js
│ │ │ └── index.jade
│ │ ├── sticky-header-on-window
│ │ │ ├── demo.config.json
│ │ │ ├── index.less
│ │ │ ├── index.js
│ │ │ └── index.jade
│ │ ├── sticky-header-on-element-flex
│ │ │ ├── demo.config.json
│ │ │ ├── index.less
│ │ │ ├── index.jade
│ │ │ └── index.js
│ │ ├── rows.js
│ │ └── columns.js
│ ├── sortable-header
│ │ ├── README.md
│ │ ├── demo.config.json
│ │ ├── sortable-header-number.jade
│ │ ├── sortable-header.jade
│ │ ├── sortable-header-alphabet.jade
│ │ ├── index.jade
│ │ └── index.less
│ ├── data-source
│ │ ├── README.md
│ │ ├── custom
│ │ │ ├── demo.config.json
│ │ │ ├── index.less
│ │ │ ├── index.jade
│ │ │ ├── js-data-resource.js
│ │ │ └── index.js
│ │ ├── jsdata
│ │ │ ├── demo.config.json
│ │ │ ├── index.less
│ │ │ ├── index.js
│ │ │ ├── index.jade
│ │ │ └── js-data-resource.js
│ │ ├── memory
│ │ │ ├── demo.config.json
│ │ │ ├── index.less
│ │ │ ├── index.js
│ │ │ └── index.jade
│ │ └── odata
│ │ │ ├── demo.config.json
│ │ │ ├── index.less
│ │ │ ├── index.js
│ │ │ └── index.jade
│ ├── pagination
│ │ ├── demo.config.json
│ │ ├── index.less
│ │ ├── index.jade
│ │ ├── pager-view-plugin.js
│ │ └── index.js
│ └── selection
│ │ ├── multiple
│ │ ├── demo.config.json
│ │ ├── index.jade
│ │ ├── index.less
│ │ └── index.js
│ │ └── single
│ │ ├── demo.config.json
│ │ ├── index.jade
│ │ ├── index.less
│ │ └── index.js
├── cell-view
│ ├── key-column-header.jade
│ ├── company-name.jade
│ ├── demo.config.json
│ ├── map-view.jade
│ ├── index.css
│ ├── index.jade
│ ├── pager-view-plugin.js
│ ├── index.js
│ ├── js-data-resource.js
│ └── map-view.js
├── factory
│ ├── key-column-header.jade
│ ├── company-name.jade
│ ├── demo.config.json
│ ├── index.jade
│ ├── pager-view-plugin.js
│ ├── js-data-resource.js
│ └── index.js
├── radio-selection
│ ├── key-column-header.jade
│ ├── company-name.jade
│ ├── demo.config.json
│ ├── index.jade
│ ├── index.js
│ └── js-data-resource.js
├── full
│ ├── demo.config.json
│ ├── specs
│ │ └── index.js
│ ├── example.css
│ ├── northwind-schema.js
│ └── index.jade
├── odata
│ ├── demo.config.json
│ ├── index.jade
│ └── index.js
├── js-data
│ ├── demo.config.json
│ ├── index.jade
│ ├── js-data-resource.js
│ └── index.js
├── fixed-header
│ ├── demo.config.json
│ ├── index.jade
│ ├── index.less
│ └── index.js
└── webpack.config.js
├── spec
├── integrated
│ ├── template
│ │ ├── column-address.jade
│ │ └── column-header.jade
│ ├── $speclist.js
│ ├── driver
│ │ ├── index.js
│ │ ├── protocol.js
│ │ ├── events.js
│ │ ├── action.js
│ │ └── utility.js
│ ├── styles
│ │ └── tri-state-checkbox.css
│ ├── conf
│ │ ├── karma.debug.conf.js
│ │ ├── karma.conf.js
│ │ └── webpack.config.js
│ ├── data
│ │ ├── js-data-source.js
│ │ └── js-data-expected.json
│ └── non-vnext-spec
│ │ ├── page-spec.js
│ │ ├── grid-view-api-spec.js
│ │ └── aggregate-row-spec.js
├── unit
│ ├── .eslintrc.yaml
│ ├── $speclist.js
│ ├── mock-spec.js
│ ├── page-spec.js
│ ├── conf
│ │ ├── karma.debug.conf.js
│ │ ├── karma.conf.js
│ │ └── webpack.config.js
│ ├── map-spec.js
│ ├── row-index-spec.js
│ ├── util.js
│ ├── odata-spec.js
│ ├── column-i18n-spec.js
│ ├── column-queryable-spec.js
│ ├── property-template-spec.js
│ ├── column-shifter-spec.js
│ ├── aggregate-row-spec.js
│ ├── jsdata-spec.js
│ ├── row-checkbox-spec.js
│ └── base-spec.js
└── util
│ └── index.js
├── jsdoc.json
├── .editorconfig
├── .eslintignore
├── .gitignore
├── webpack.alias.js
├── less
└── editable.less
├── wdio.conf.js
├── .travis.yml
├── .eslintrc.yaml
├── bower.json
├── LICENSE
└── webpack.config.js
/js/schema/list-view.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.bowerrc:
--------------------------------------------------------------------------------
1 | {
2 | "directory": "./lib/"
3 | }
--------------------------------------------------------------------------------
/demos/vnext/events/README.md:
--------------------------------------------------------------------------------
1 | # Handling Grid Events
2 |
--------------------------------------------------------------------------------
/demos/vnext/rows/README.md:
--------------------------------------------------------------------------------
1 | # Configuration for rows
2 |
--------------------------------------------------------------------------------
/js/vnext/projection/default-cell.jade:
--------------------------------------------------------------------------------
1 | span
2 | =value
--------------------------------------------------------------------------------
/demos/cell-view/key-column-header.jade:
--------------------------------------------------------------------------------
1 | i
2 | span=$text
3 |
--------------------------------------------------------------------------------
/demos/factory/key-column-header.jade:
--------------------------------------------------------------------------------
1 | i
2 | span=$text
3 |
--------------------------------------------------------------------------------
/demos/vnext/columns/README.md:
--------------------------------------------------------------------------------
1 | # Configuration for columns
2 |
--------------------------------------------------------------------------------
/demos/cell-view/company-name.jade:
--------------------------------------------------------------------------------
1 | i
2 | span=model[property]
3 |
--------------------------------------------------------------------------------
/demos/factory/company-name.jade:
--------------------------------------------------------------------------------
1 | i
2 | span=model[property]
3 |
--------------------------------------------------------------------------------
/demos/radio-selection/key-column-header.jade:
--------------------------------------------------------------------------------
1 | i
2 | span=$text
3 |
--------------------------------------------------------------------------------
/demos/vnext/scrolling/README.md:
--------------------------------------------------------------------------------
1 | # Configuration for scrolling
2 |
--------------------------------------------------------------------------------
/demos/vnext/sortable-header/README.md:
--------------------------------------------------------------------------------
1 | # Configuration for rows
2 |
--------------------------------------------------------------------------------
/spec/integrated/template/column-address.jade:
--------------------------------------------------------------------------------
1 | .column-address #{value}
--------------------------------------------------------------------------------
/spec/integrated/template/column-header.jade:
--------------------------------------------------------------------------------
1 | .column-header=$text
2 |
--------------------------------------------------------------------------------
/demos/full/demo.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "template": "./index.jade"
3 | }
4 |
--------------------------------------------------------------------------------
/demos/odata/demo.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "template": "./index.jade"
3 | }
4 |
--------------------------------------------------------------------------------
/demos/radio-selection/company-name.jade:
--------------------------------------------------------------------------------
1 | i
2 | span=model[property]
3 |
--------------------------------------------------------------------------------
/demos/vnext/data-source/README.md:
--------------------------------------------------------------------------------
1 | # Configuration for data-source
2 |
--------------------------------------------------------------------------------
/demos/vnext/rows/demo.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "template": "./index.jade"
3 | }
--------------------------------------------------------------------------------
/js/vnext/layout/index.js:
--------------------------------------------------------------------------------
1 | export { TableView } from './table-view';
2 |
--------------------------------------------------------------------------------
/demos/cell-view/demo.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "template": "./index.jade"
3 | }
4 |
--------------------------------------------------------------------------------
/demos/factory/demo.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "template": "./index.jade"
3 | }
4 |
--------------------------------------------------------------------------------
/demos/js-data/demo.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "template": "./index.jade"
3 | }
4 |
--------------------------------------------------------------------------------
/demos/vnext/events/demo.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "template": "./index.jade"
3 | }
--------------------------------------------------------------------------------
/spec/unit/.eslintrc.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | "rules":
3 | "no-unused-expressions": 0
4 |
--------------------------------------------------------------------------------
/demos/fixed-header/demo.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "template": "./index.jade"
3 | }
4 |
--------------------------------------------------------------------------------
/demos/radio-selection/demo.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "template": "./index.jade"
3 | }
4 |
--------------------------------------------------------------------------------
/demos/vnext/pagination/demo.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "template": "./index.jade"
3 | }
4 |
--------------------------------------------------------------------------------
/demos/vnext/sortable-header/demo.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "template": "./index.jade"
3 | }
--------------------------------------------------------------------------------
/js/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/projection-grid/HEAD/js/.DS_Store
--------------------------------------------------------------------------------
/demos/vnext/data-source/custom/demo.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "template": "./index.jade"
3 | }
4 |
--------------------------------------------------------------------------------
/demos/vnext/data-source/jsdata/demo.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "template": "./index.jade"
3 | }
4 |
--------------------------------------------------------------------------------
/demos/vnext/data-source/memory/demo.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "template": "./index.jade"
3 | }
4 |
--------------------------------------------------------------------------------
/demos/vnext/data-source/odata/demo.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "template": "./index.jade"
3 | }
4 |
--------------------------------------------------------------------------------
/demos/vnext/selection/multiple/demo.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "template": "./index.jade"
3 | }
4 |
--------------------------------------------------------------------------------
/demos/vnext/selection/single/demo.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "template": "./index.jade"
3 | }
4 |
--------------------------------------------------------------------------------
/demos/vnext/scrolling/emails.jade:
--------------------------------------------------------------------------------
1 | ul
2 | each email in value || []
3 | li=email
4 |
5 |
--------------------------------------------------------------------------------
/demos/vnext/scrolling/fixed-header/demo.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "template": "./index.jade"
3 | }
4 |
--------------------------------------------------------------------------------
/demos/vnext/scrolling/static-header/demo.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "template": "./index.jade"
3 | }
4 |
--------------------------------------------------------------------------------
/js/vnext/layout/editable.jade:
--------------------------------------------------------------------------------
1 | .grid-edit-icon(title='Edit', class=classes)
2 | !=$html
3 | =text
--------------------------------------------------------------------------------
/demos/cell-view/map-view.jade:
--------------------------------------------------------------------------------
1 | span=City
2 |  
3 | button.btn.btn-default(type='button') Show
4 |
--------------------------------------------------------------------------------
/js/layout/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/projection-grid/HEAD/js/layout/.DS_Store
--------------------------------------------------------------------------------
/js/vnext/layout/row.jade:
--------------------------------------------------------------------------------
1 | include ./table-mixins.jade
2 |
3 | +mixinRow(row, 'body', escapeAttr)
4 |
--------------------------------------------------------------------------------
/demos/vnext/scrolling/sticky-header-on-element/demo.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "template": "./index.jade"
3 | }
4 |
--------------------------------------------------------------------------------
/demos/vnext/scrolling/sticky-header-on-window/demo.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "template": "./index.jade"
3 | }
4 |
--------------------------------------------------------------------------------
/js/layout/template/editable.jade:
--------------------------------------------------------------------------------
1 | .grid-edit-icon(title=tooltipText, class=classes)
2 | !=$html
3 | =text
4 |
--------------------------------------------------------------------------------
/js/vnext/layout/flex-head-cell.jade:
--------------------------------------------------------------------------------
1 | include ./flex-mixins.jade
2 |
3 | +mixinHeadCell(cell, escapeAttr)
4 |
--------------------------------------------------------------------------------
/js/vnext/layout/flex-row.jade:
--------------------------------------------------------------------------------
1 | include ./flex-mixins.jade
2 |
3 | +mixinRow(row, 'body', escapeAttr)
4 |
--------------------------------------------------------------------------------
/demos/vnext/scrolling/sticky-header-on-element-flex/demo.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "template": "./index.jade"
3 | }
4 |
--------------------------------------------------------------------------------
/js/vnext/layout/flex-header-footer.jade:
--------------------------------------------------------------------------------
1 | include ./flex-mixins.jade
2 |
3 | +mixinRow(row, group, escapeAttr)
4 |
--------------------------------------------------------------------------------
/js/vnext/layout/header-footer.jade:
--------------------------------------------------------------------------------
1 | include ./table-mixins.jade
2 |
3 | +mixinRow(row, group, escapeAttr)
4 |
--------------------------------------------------------------------------------
/js/layout/template/row.editable.string.jade:
--------------------------------------------------------------------------------
1 | input.grid-text-input(type="text" value=defaultValue style="width:100%")
--------------------------------------------------------------------------------
/demos/cell-view/index.css:
--------------------------------------------------------------------------------
1 | .container-map {
2 | position: fixed;
3 | right: 0;
4 | top: 0;
5 | width: 500px;
6 | height: 500px;
7 | }
8 |
--------------------------------------------------------------------------------
/jsdoc.json:
--------------------------------------------------------------------------------
1 | {
2 | "opts": {
3 | "destination": "docs",
4 | "recurse": true
5 | },
6 | "plugins": ["plugins/markdown"]
7 | }
8 |
9 |
--------------------------------------------------------------------------------
/js/layout/template/selectable.jade:
--------------------------------------------------------------------------------
1 | input.column-selection(type=type, checked=checked, disabled=disabled, aria-labelledby=labelledId, aria-label=labelString)
2 |
--------------------------------------------------------------------------------
/demos/vnext/scrolling/sticky-header-on-window/index.less:
--------------------------------------------------------------------------------
1 | table.table {
2 | thead {
3 | background: lavender;
4 | }
5 | }
6 |
7 | col {
8 | width: 120px;
9 | }
10 |
--------------------------------------------------------------------------------
/js/vnext/projection/selection-head.jade:
--------------------------------------------------------------------------------
1 | if single
2 | span
3 | else
4 | input.select-all(type='checkbox' checked=checked aria-label=checkAllLabel class=partSelected ? 'part-selected' : undefined)
5 |
--------------------------------------------------------------------------------
/spec/unit/$speclist.js:
--------------------------------------------------------------------------------
1 | // require all `test/**/*.js` except for `test/**/$*.js`
2 | var testsContext = require.context('.', false, /[^\$][^\/]*\.js$/);
3 | testsContext.keys().forEach(testsContext);
4 |
--------------------------------------------------------------------------------
/js/layout/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | TableLayout: require('./table'),
3 | templates: {
4 | table: require('./template/table.jade'),
5 | },
6 | renderers: require('./renderer/index'),
7 | };
8 |
--------------------------------------------------------------------------------
/js/vnext/projection/sortable-header.jade:
--------------------------------------------------------------------------------
1 | if direction < 0
2 | span.sort-indicator.glyphicon.glyphicon-arrow-down
3 | else if direction > 0
4 | span.sort-indicator.glyphicon.glyphicon-arrow-up
5 |
6 | != html
7 |
--------------------------------------------------------------------------------
/demos/vnext/events/index.less:
--------------------------------------------------------------------------------
1 | table.table {
2 | max-width: none;
3 | word-wrap: break-word;
4 |
5 | col {
6 | width: 120px;
7 | }
8 |
9 | thead {
10 | background: lavender;
11 | }
12 | }
13 |
14 |
--------------------------------------------------------------------------------
/js/vnext/data-source/index.js:
--------------------------------------------------------------------------------
1 | export { DataSource } from './base.js';
2 | export { ODataDataSource } from './odata.js';
3 | export { JSDataDataSource } from './js-data.js';
4 | export { MemoryDataSource } from './memory.js';
5 |
6 |
--------------------------------------------------------------------------------
/js/factory/utility.js:
--------------------------------------------------------------------------------
1 | import _ from 'underscore';
2 |
3 | export function delegateEvents({ from, to, events }) {
4 | _.each(
5 | events,
6 | event => from.on(event, (...args) => to.trigger(event, ...args))
7 | );
8 | }
9 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 2
6 | charset = utf-8
7 | trim_trailing_whitespace = true
8 | insert_final_newline = true
9 |
10 | [*.md]
11 | trim_trailing_whitespace = false
12 |
--------------------------------------------------------------------------------
/js/schema/properties.js:
--------------------------------------------------------------------------------
1 | define([
2 | 'lib/underscore',
3 | ], function (_) {
4 | function from(arr) {
5 | var obj = _.first(arr);
6 |
7 | return _.keys(obj || {});
8 | }
9 |
10 | return { from: from };
11 | });
12 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | # npm packages
2 | node_modules
3 |
4 | # code coverage
5 | coverage
6 |
7 | # output folder
8 | dist
9 |
10 | # generated code for example pages
11 | examples/requirejs/require.config.js
12 | examples/webpack/dist
13 |
--------------------------------------------------------------------------------
/demos/vnext/sortable-header/sortable-header-number.jade:
--------------------------------------------------------------------------------
1 | if direction < 0
2 | span.sort-indicator.glyphicon.glyphicon-sort-by-order
3 | else if direction > 0
4 | span.sort-indicator.glyphicon.glyphicon-sort-by-order-alt
5 |
6 | != html
7 |
8 |
--------------------------------------------------------------------------------
/js/vnext/layout/column-group.jade:
--------------------------------------------------------------------------------
1 | each col in cols || []
2 | - var columnStyle = col.width ? 'width: ' + col.width + ';' : null;
3 | - var attr = escapeAttr(col.attributes);
4 | col&attributes(attr)(class=col.classes, style=columnStyle)
5 |
--------------------------------------------------------------------------------
/demos/odata/index.jade:
--------------------------------------------------------------------------------
1 | doctype html
2 | html(lang="en")
3 | head
4 | title= title
5 | body
6 | .container
7 | .row
8 | .grid-root
9 | .pager-root
10 | script(type="application/javascript", src=bundle)
11 |
--------------------------------------------------------------------------------
/demos/vnext/sortable-header/sortable-header.jade:
--------------------------------------------------------------------------------
1 | if direction < 0
2 | span.sort-indicator.glyphicon.glyphicon-sort-by-attributes
3 | else if direction > 0
4 | span.sort-indicator.glyphicon.glyphicon-sort-by-attributes-alt
5 |
6 | != html
7 |
8 |
--------------------------------------------------------------------------------
/demos/factory/index.jade:
--------------------------------------------------------------------------------
1 | doctype html
2 | html(lang="en")
3 | head
4 | title= title
5 | body
6 | .container
7 | .row
8 | .grid-root
9 | .pager-root
10 | script(type="application/javascript", src=bundle)
11 |
--------------------------------------------------------------------------------
/demos/js-data/index.jade:
--------------------------------------------------------------------------------
1 | doctype html
2 | html(lang="en")
3 | head
4 | title= title
5 | body
6 | .container
7 | .row
8 | .grid-root
9 | .pager-root
10 | script(type="application/javascript", src=bundle)
11 |
--------------------------------------------------------------------------------
/demos/vnext/sortable-header/sortable-header-alphabet.jade:
--------------------------------------------------------------------------------
1 | if direction < 0
2 | span.sort-indicator.glyphicon.glyphicon-sort-by-alphabet
3 | else if direction > 0
4 | span.sort-indicator.glyphicon.glyphicon-sort-by-alphabet-alt
5 |
6 | != html
7 |
8 |
--------------------------------------------------------------------------------
/demos/fixed-header/index.jade:
--------------------------------------------------------------------------------
1 | doctype html
2 | html(lang="en")
3 | head
4 | title= title
5 | body
6 | .container
7 | .row
8 | .grid-root
9 | .pager-root
10 | script(type="application/javascript", src=bundle)
11 |
--------------------------------------------------------------------------------
/demos/radio-selection/index.jade:
--------------------------------------------------------------------------------
1 | doctype html
2 | html(lang="en")
3 | head
4 | title= title
5 | body
6 | .container
7 | .row
8 | .grid-root
9 | .pager-root
10 | script(type="application/javascript", src=bundle)
11 |
--------------------------------------------------------------------------------
/js/layout/template/row.tri-state-checked.jade:
--------------------------------------------------------------------------------
1 | - var classes = [].concat(checkState === 'indeterminate' ? ['glyphicon', 'glyphicon-minus'] : checkState === 'checked' ? ['glyphicon', 'glyphicon-ok'] : [])
2 | .column-tri-state-checkbox
3 | span(class=classes)
4 |
--------------------------------------------------------------------------------
/js/layout/renderer/index.js:
--------------------------------------------------------------------------------
1 | import FixedHeaderRenderer from './fixed-header';
2 | import VirtualizationRenderer from './virtualization';
3 |
4 | module.exports = {
5 | FixedHeader: FixedHeaderRenderer,
6 | Virtualization: VirtualizationRenderer,
7 | };
8 |
9 |
--------------------------------------------------------------------------------
/js/model/index.js:
--------------------------------------------------------------------------------
1 | define([
2 | 'lib/underscore',
3 | 'lib/backbone',
4 | ], function (_, Backbone) {
5 | return Backbone.Model.extend({
6 | defaults: {
7 | layout: undefined,
8 | projection: undefined,
9 | },
10 | });
11 | });
12 |
--------------------------------------------------------------------------------
/js/vnext/projection/single-selection-resolver.js:
--------------------------------------------------------------------------------
1 | import { SelectionResolver } from './selection-resolver.js';
2 |
3 | export class SingleSelectionResolver extends SelectionResolver {
4 | selectRow(key) {
5 | return this.patch({ selected: [key.toString()] });
6 | }
7 | }
8 |
9 |
--------------------------------------------------------------------------------
/spec/integrated/$speclist.js:
--------------------------------------------------------------------------------
1 | // require all `test/**/*.js` except for `test/**/$*.js`
2 | // var testsContext = require.context('.', false, /[^\$][^\/]*\.js$/);
3 | var testsContext = require.context('.', true, /[^\$][^\/]*\-spec.js$/);
4 | testsContext.keys().forEach(testsContext);
5 |
--------------------------------------------------------------------------------
/js/popup-editor/index.jade:
--------------------------------------------------------------------------------
1 | form.form-inline.grid-popup-editor(role='dialog')
2 | input.form-control.editor(type='text', value=value)
3 |  
4 | button.btn.btn-primary.save(type='button')=saveButtonText
5 |  
6 | button.btn.btn-default.cancel(type='button')=cancelButtonText
7 |
--------------------------------------------------------------------------------
/js/model/response.js:
--------------------------------------------------------------------------------
1 | define([
2 | 'lib/underscore',
3 | 'lib/backbone',
4 | ], function (_, Backbone) {
5 | return Backbone.Model.extend({
6 | defaults: {
7 | value: [],
8 | select: null,
9 | count: 0,
10 | aggregate: [],
11 | },
12 | });
13 | });
14 |
--------------------------------------------------------------------------------
/spec/integrated/driver/index.js:
--------------------------------------------------------------------------------
1 | import _ from 'underscore';
2 | import protocol from './protocol';
3 | import action from './action';
4 | import utility from './utility';
5 | import events from './events';
6 |
7 | let exports = _.extend({}, protocol, action, utility, events);
8 | export default exports;
--------------------------------------------------------------------------------
/spec/integrated/driver/protocol.js:
--------------------------------------------------------------------------------
1 | import Promise from 'bluebird';
2 | import $ from 'jquery';
3 |
4 | function element(el) {
5 | return new Promise((resolve, reject) => {
6 | let $el = el instanceof $ ? el : $(el);
7 | resolve($el);
8 | });
9 | }
10 |
11 | export default {
12 | element,
13 | };
14 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # npm packages
2 | node_modules
3 |
4 | # test results
5 | test-results
6 | errorShots
7 |
8 | # code coverage
9 | coverage
10 |
11 | # npm logs
12 | node-debug.log
13 | npm-debug.log
14 |
15 | # yarn logs
16 | yarn-error.log
17 |
18 | # output folder
19 | dist
20 | docs
21 |
22 | # the npm settings
23 | .npmrc
24 |
25 |
--------------------------------------------------------------------------------
/demos/fixed-header/index.less:
--------------------------------------------------------------------------------
1 | .col-CustomerID {
2 | width: 100px;
3 | min-width: 100px;
4 | }
5 |
6 | .col-CompanyName {
7 | width: 150px;
8 | min-width: 150px;
9 | }
10 |
11 | .col-City {
12 | width: 100px;
13 | min-width: 100px;
14 | }
15 |
16 | .col-ContactName {
17 | width: 200px;
18 | min-width: 200px;
19 | }
20 |
21 |
--------------------------------------------------------------------------------
/webpack.alias.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 | // Config your webpack resolve.alias in this file
3 | module.exports = {
4 | 'lib/underscore': 'underscore',
5 | 'lib/backbone': 'backbone',
6 | 'lib/jquery': 'jquery',
7 | 'component/grid': path.resolve('./js'),
8 | 'component/popup-editor': path.resolve('./js/popup-editor'),
9 | };
10 |
--------------------------------------------------------------------------------
/spec/integrated/styles/tri-state-checkbox.css:
--------------------------------------------------------------------------------
1 | .column-tri-state-checkbox {
2 | border: 1px solid rgba(155,155,155,0.57);
3 | border-radius: 1px;
4 | height: 21px;
5 | margin: 0 1px;
6 | outline: 0;
7 | vertical-align: text-bottom;
8 | width: 21px;
9 | }
10 |
11 | .column-tri-state-checkbox > span.glyphicon {
12 | left: 3px;
13 | }
14 |
--------------------------------------------------------------------------------
/demos/vnext/scrolling/static-header/index.less:
--------------------------------------------------------------------------------
1 | .grid-container {
2 | width: 1200px;
3 | }
4 |
5 | table.table {
6 | thead {
7 | background: lavender;
8 | }
9 | }
10 |
11 | col {
12 | width: 120px;
13 | }
14 |
15 | tr.row-buffer-changed {
16 | background: LightPink;
17 | }
18 | tr.row-buffer-committed {
19 | background: LightGreen;
20 | }
21 |
--------------------------------------------------------------------------------
/js/vnext/projection/selection-body.jade:
--------------------------------------------------------------------------------
1 | if value.selectable
2 | if value.single
3 | input.select-row(type='radio', checked=value.checked, aria-label=value.ariaLabel, aria-labelledby=value.labelbyId)
4 | else
5 | input.select-row(type='checkbox', checked=value.checked, aria-label=value.ariaLabel, aria-labelledby=value.labelbyId)
6 | else
7 | span.unselectable
8 |
--------------------------------------------------------------------------------
/demos/cell-view/index.jade:
--------------------------------------------------------------------------------
1 | doctype html
2 | html(lang="en")
3 | head
4 | title= title
5 | body
6 | .container
7 | .row
8 | .grid-root
9 | .pager-root
10 | .container-map
11 | #myMap
12 | script(type="application/javascript", src="http://www.bing.com/api/maps/mapcontrol")
13 | script(type="application/javascript", src=bundle)
14 |
--------------------------------------------------------------------------------
/less/editable.less:
--------------------------------------------------------------------------------
1 | table.grid {
2 | td.grid-editable-cell {
3 | position: relative;
4 |
5 | .grid-edit-icon {
6 | display: none;;
7 | position: absolute;
8 | top: 2px;
9 | right: 2px;
10 | font-size: 10px;
11 | }
12 | }
13 |
14 | td.grid-editable-cell:hover {
15 | .grid-edit-icon {
16 | display: block;
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/demos/vnext/pagination/index.less:
--------------------------------------------------------------------------------
1 | .grid-container {
2 | width: 1200px;
3 |
4 | .viewport {
5 | height: 600px;
6 | }
7 | }
8 |
9 | table.table {
10 | thead {
11 | background: lavender;
12 | }
13 | }
14 |
15 | col {
16 | width: 120px;
17 | }
18 |
19 | tr.row-buffer-changed {
20 | background: LightPink;
21 | }
22 | tr.row-buffer-committed {
23 | background: LightGreen;
24 | }
25 |
--------------------------------------------------------------------------------
/demos/vnext/events/index.jade:
--------------------------------------------------------------------------------
1 | doctype html
2 | html(lang="en")
3 | head
4 | title= title
5 | body(style="padding-top: 70px")
6 | nav.navbar.navbar-default.navbar-fixed-top
7 | .container-fluid.navbar-container
8 | .navbar-header
9 | .navbar-brand Projection Grid vNext
10 | h1 Events
11 | .grid-container
12 | script(type="application/javascript", src=bundle)
13 |
--------------------------------------------------------------------------------
/wdio.conf.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 | var democase = require('democase');
3 |
4 | var demoSet = democase.loadSync(path.resolve(__dirname, 'demos'));
5 | var config = demoSet.wdioConfig({
6 | capabilities: [{
7 | browserName: 'firefox',
8 | }],
9 | reporters: ['dot', 'junit'],
10 | reporterOptions: { outputDir: './test-results/' },
11 | });
12 |
13 | module.exports = { config: config };
14 |
--------------------------------------------------------------------------------
/js/layout/px.js:
--------------------------------------------------------------------------------
1 | // todo [akamel] move to /component
2 | define(['lib/underscore'], function (_) {
3 | function parse(a) {
4 | return abs(parseFloat(a));
5 | }
6 |
7 | function abs(a) {
8 | return _.isFinite(a) ? a : 0;
9 | }
10 |
11 | function pixelify(a) {
12 | return abs(a) + 'px';
13 | }
14 |
15 | return {
16 | parse: parse,
17 | pixelify: pixelify,
18 | };
19 | });
20 |
--------------------------------------------------------------------------------
/demos/vnext/data-source/custom/index.less:
--------------------------------------------------------------------------------
1 | .grid-viewport {
2 | height: 600px;
3 | width: 900px;
4 | overflow-y: auto;
5 | position: relative;
6 | border-style: dashed;
7 |
8 | ul.nav.nav-tabs {
9 | position: absolute;
10 | top: 0;
11 | left: 0;
12 | }
13 | }
14 |
15 | table.table {
16 | thead {
17 | background: lavender;
18 | }
19 | }
20 |
21 | col {
22 | width: 120px;
23 | }
24 |
--------------------------------------------------------------------------------
/demos/vnext/data-source/jsdata/index.less:
--------------------------------------------------------------------------------
1 | .grid-viewport {
2 | height: 600px;
3 | width: 900px;
4 | overflow-y: auto;
5 | position: relative;
6 | border-style: dashed;
7 |
8 | ul.nav.nav-tabs {
9 | position: absolute;
10 | top: 0;
11 | left: 0;
12 | }
13 | }
14 |
15 | table.table {
16 | thead {
17 | background: lavender;
18 | }
19 | }
20 |
21 | col {
22 | width: 120px;
23 | }
24 |
--------------------------------------------------------------------------------
/demos/vnext/data-source/memory/index.less:
--------------------------------------------------------------------------------
1 | .grid-viewport {
2 | height: 600px;
3 | width: 900px;
4 | overflow-y: auto;
5 | position: relative;
6 | border-style: dashed;
7 |
8 | ul.nav.nav-tabs {
9 | position: absolute;
10 | top: 0;
11 | left: 0;
12 | }
13 | }
14 |
15 | table.table {
16 | thead {
17 | background: lavender;
18 | }
19 | }
20 |
21 | col {
22 | width: 120px;
23 | }
24 |
--------------------------------------------------------------------------------
/demos/vnext/data-source/odata/index.less:
--------------------------------------------------------------------------------
1 | .grid-viewport {
2 | height: 600px;
3 | width: 900px;
4 | overflow-y: auto;
5 | position: relative;
6 | border-style: dashed;
7 |
8 | ul.nav.nav-tabs {
9 | position: absolute;
10 | top: 0;
11 | left: 0;
12 | }
13 | }
14 |
15 | table.table {
16 | thead {
17 | background: lavender;
18 | }
19 | }
20 |
21 | col {
22 | width: 120px;
23 | }
24 |
--------------------------------------------------------------------------------
/demos/vnext/rows/index.jade:
--------------------------------------------------------------------------------
1 | doctype html
2 | html(lang="en")
3 | head
4 | title= title
5 | body(style="padding-top: 70px")
6 | nav.navbar.navbar-default.navbar-fixed-top
7 | .container-fluid.navbar-container
8 | .navbar-header
9 | .navbar-brand Projection Grid vNext
10 | h1 Row Configuartaion
11 | .grid-container
12 | script(type="application/javascript", src=bundle)
13 |
--------------------------------------------------------------------------------
/demos/vnext/scrolling/fixed-header/index.less:
--------------------------------------------------------------------------------
1 | .grid-container {
2 | width: 1200px;
3 |
4 | .viewport {
5 | height: 600px;
6 | }
7 | }
8 |
9 | table.table {
10 | thead {
11 | background: lavender;
12 | }
13 | }
14 |
15 | col {
16 | width: 120px;
17 | }
18 |
19 | tr.row-buffer-changed {
20 | background: LightPink;
21 | }
22 | tr.row-buffer-committed {
23 | background: LightGreen;
24 | }
25 |
--------------------------------------------------------------------------------
/demos/vnext/selection/single/index.jade:
--------------------------------------------------------------------------------
1 | doctype html
2 | html(lang="en")
3 | head
4 | title= title
5 | body(style="padding-top: 70px")
6 | nav.navbar.navbar-default.navbar-fixed-top
7 | .container-fluid.navbar-container
8 | .navbar-header
9 | .navbar-brand Projection Grid vNext
10 | h1 Single Selection
11 | .grid-container
12 | script(type="application/javascript", src=bundle)
13 |
--------------------------------------------------------------------------------
/demos/vnext/sortable-header/index.jade:
--------------------------------------------------------------------------------
1 | doctype html
2 | html(lang="en")
3 | head
4 | title= title
5 | body(style="padding-top: 70px")
6 | nav.navbar.navbar-default.navbar-fixed-top
7 | .container-fluid.navbar-container
8 | .navbar-header
9 | .navbar-brand Projection Grid vNext
10 | h1 Row Configuartaion
11 | .grid-container
12 | script(type="application/javascript", src=bundle)
13 |
--------------------------------------------------------------------------------
/demos/vnext/scrolling/sticky-header-on-element/index.less:
--------------------------------------------------------------------------------
1 | .grid-viewport {
2 | height: 600px;
3 | width: 900px;
4 | overflow-y: auto;
5 | position: relative;
6 | border-style: dashed;
7 |
8 | ul.nav.nav-tabs {
9 | position: absolute;
10 | top: 0;
11 | left: 0;
12 | }
13 | }
14 |
15 | table.table {
16 | thead {
17 | background: lavender;
18 | }
19 | }
20 |
21 | col {
22 | width: 120px;
23 | }
24 |
--------------------------------------------------------------------------------
/demos/vnext/selection/multiple/index.jade:
--------------------------------------------------------------------------------
1 | doctype html
2 | html(lang="en")
3 | head
4 | title= title
5 | body(style="padding-top: 70px")
6 | nav.navbar.navbar-default.navbar-fixed-top
7 | .container-fluid.navbar-container
8 | .navbar-header
9 | .navbar-brand Projection Grid vNext
10 | h1 Multiple Selection
11 | .grid-container
12 | script(type="application/javascript", src=bundle)
13 |
--------------------------------------------------------------------------------
/js/vnext/layout/table-static.jade:
--------------------------------------------------------------------------------
1 | -
2 | var tableStyle = {
3 | 'border-collapse': 'collapse',
4 | 'margin': 0,
5 | };
6 |
7 | .table-container
8 | table.data-table(style=tableStyle, class=classes, role='grid')&attributes(dataTableAttributes)
9 | colgroup.column-group
10 | thead.header
11 | tbody
12 | tr.top-filler(role='presentation')
13 | tr.bottom-filler(role='presentation')
14 | tfoot.footer
15 |
--------------------------------------------------------------------------------
/js/projection/columns.js:
--------------------------------------------------------------------------------
1 | import BaseProjection from './base';
2 |
3 | const ColumnsProjection = BaseProjection.extend({
4 | defaults: {
5 | columns: {},
6 | },
7 |
8 | name: 'columns',
9 |
10 | update(options) {
11 | if (BaseProjection.prototype.update.call(this, options)) {
12 | this.patch({
13 | columns: this.get('columns'),
14 | });
15 | }
16 | },
17 |
18 | });
19 |
20 | export default ColumnsProjection;
21 |
--------------------------------------------------------------------------------
/js/vnext/layout/flex-static.jade:
--------------------------------------------------------------------------------
1 | -
2 | var tableStyle = {
3 | 'border-collapse': 'collapse',
4 | 'margin': 0,
5 | };
6 |
7 | .table-container.flex-layout(role='grid')&attributes(dataTableAttributes)
8 | div.table.data-table(style=tableStyle, class=classes)
9 | div.thead.header
10 | div.tbody(role='rowgroup')
11 | div.tr.top-filler(role='presentation')
12 | div.tr.bottom-filler(role='presentation')
13 | div.tfoot.footer
14 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | env:
3 | - CXX=g++-4.8
4 | addons:
5 | apt:
6 | sources:
7 | - ubuntu-toolchain-r-test
8 | packages:
9 | - g++-4.8
10 | sudo: true
11 | node_js:
12 | - v5
13 | before_install:
14 | - export CHROME_BIN=chromium-browser
15 | - export DISPLAY=:99.0
16 | - sh -e /etc/init.d/xvfb start
17 | script:
18 | - gulp test
19 | - "cat ./spec/*/coverage/report-lcov/lcov.info | ./node_modules/coveralls/bin/coveralls.js"
20 |
--------------------------------------------------------------------------------
/demos/vnext/data-source/jsdata/index.js:
--------------------------------------------------------------------------------
1 | import pgrid from '../../../../js';
2 | import store from './js-data-resource.js';
3 |
4 | import './index.less';
5 | import 'bootstrap-webpack';
6 |
7 | window.gridView = pgrid.factory({ vnext: true }).create({
8 | el: '.grid-container',
9 | tableClasses: ['table', 'table-bordered'],
10 | scrolling: {
11 | virtualized: true,
12 | },
13 | dataSource: {
14 | type: 'jsdata',
15 | entity: store,
16 | },
17 | }).gridView.render();
18 |
--------------------------------------------------------------------------------
/demos/vnext/selection/multiple/index.less:
--------------------------------------------------------------------------------
1 | table.table {
2 | max-width: none;
3 | word-wrap: break-word;
4 |
5 | col {
6 | width: 120px;
7 | }
8 |
9 | thead {
10 | background: Lavender;
11 | }
12 |
13 | col.selection-column {
14 | width: 40px;
15 | }
16 |
17 | td.selection-cell {
18 | text-align: center;
19 | background: Lavender;
20 | }
21 |
22 | th.selection-header {
23 | text-align: center;
24 | background: LavenderBlush;
25 | }
26 | }
27 |
28 |
--------------------------------------------------------------------------------
/demos/vnext/selection/single/index.less:
--------------------------------------------------------------------------------
1 | table.table {
2 | max-width: none;
3 | word-wrap: break-word;
4 |
5 | col {
6 | width: 120px;
7 | }
8 |
9 | thead {
10 | background: lavender;
11 | }
12 |
13 | col.selection-column {
14 | width: 40px;
15 | }
16 |
17 | td.selection-cell {
18 | text-align: center;
19 | background: Lavender;
20 | }
21 |
22 | th.selection-header {
23 | text-align: center;
24 | background: LavenderBlush;
25 | }
26 | }
27 |
28 |
--------------------------------------------------------------------------------
/demos/vnext/pagination/index.jade:
--------------------------------------------------------------------------------
1 | doctype html
2 | html(lang="en")
3 | head
4 | title= title
5 | body(style="padding-top: 70px")
6 | nav.navbar.navbar-default.navbar-fixed-top
7 | .container-fluid.navbar-container
8 | .navbar-header
9 | .navbar-brand Pagination
10 | h1.title-window-viewport Window Viewport (data source: memory)
11 | .container-window-viewport
12 | .container-window-pagination
13 |
14 | script(type="application/javascript", src=bundle)
15 |
--------------------------------------------------------------------------------
/.eslintrc.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | "extends": "xo-space"
3 | "env":
4 | "mocha": true
5 | "amd": true
6 | "browser": true
7 | "rules":
8 | "comma-dangle":
9 | - 2
10 | - "always-multiline"
11 | "object-curly-spacing":
12 | - 2
13 | - "always"
14 | "linebreak-style": 0
15 | "no-warning-comments": 0
16 | "one-var":
17 | - 2
18 | -
19 | uninitialized: always
20 | initialized: never
21 | "no-unused-expressions":
22 | - 2
23 | -
24 | allowShortCircuit: true
25 |
--------------------------------------------------------------------------------
/spec/integrated/driver/events.js:
--------------------------------------------------------------------------------
1 | import Promise from 'bluebird';
2 | import _ from 'underscore';
3 |
4 | function once(target, event) {
5 | return new Promise((resolve, reject) => {
6 | if (!_.isEmpty(target) && _.isFunction(target.once)) {
7 | target.once(event, (...args) => {
8 | resolve(...args);
9 | })
10 | } else {
11 | reject(new Error('Target not exist or target no contains "event" function'));
12 | }
13 | });
14 | }
15 |
16 | export default {
17 | once,
18 | };
19 |
--------------------------------------------------------------------------------
/js/projection/memory.js:
--------------------------------------------------------------------------------
1 | define(['component/grid/projection/base'], function (BaseProjection) {
2 | var Model = BaseProjection.extend({
3 | defaults: {
4 | seed: [],
5 | },
6 | update: function () {
7 | this.trigger('update:beginning');
8 |
9 | var value = this.get('seed');
10 |
11 | this.data.set({
12 | value: value,
13 | count: value.length,
14 | });
15 |
16 | this.trigger('update:finished');
17 | },
18 | });
19 |
20 | return Model;
21 | });
22 |
--------------------------------------------------------------------------------
/demos/vnext/data-source/memory/index.js:
--------------------------------------------------------------------------------
1 | import pgrid from '../../../../js';
2 | import people from './people.json';
3 |
4 | import './index.less';
5 | import 'bootstrap-webpack';
6 |
7 | window.gridView = pgrid.factory({ vnext: true }).create({
8 | el: '.grid-container',
9 | tableClasses: ['table', 'table-bordered'],
10 | scrolling: {
11 | virtualized: true,
12 | },
13 | dataSource: {
14 | type: 'memory',
15 | data: people.value,
16 | primaryKey: 'UserName',
17 | },
18 | }).gridView.render();
19 |
--------------------------------------------------------------------------------
/demos/vnext/data-source/odata/index.js:
--------------------------------------------------------------------------------
1 | import pgrid from '../../../../js';
2 |
3 | import './index.less';
4 | import 'bootstrap-webpack';
5 |
6 | window.gridView = pgrid.factory({ vnext: true }).create({
7 | el: '.grid-container',
8 | tableClasses: ['table', 'table-bordered'],
9 | scrolling: {
10 | virtualized: true,
11 | },
12 | dataSource: {
13 | type: 'odata',
14 | url: 'http://services.odata.org/V4/Northwind/Northwind.svc/Orders',
15 | primaryKey: 'OrderID',
16 | },
17 | }).gridView.render();
18 |
--------------------------------------------------------------------------------
/js/factory/renderers-plugin.js:
--------------------------------------------------------------------------------
1 | import layout from '../layout/index';
2 |
3 | export default definePlugin => definePlugin('renderers', [
4 | 'config',
5 | ], function (config) {
6 | const renderers = [];
7 |
8 | if (config.scrollable) {
9 | if (config.scrollable.virtual) {
10 | renderers.push(layout.renderers.Virtualization);
11 | }
12 | if (config.scrollable.fixedHeader) {
13 | renderers.push(layout.renderers.FixedHeader);
14 | }
15 | }
16 |
17 | return renderers;
18 | });
19 |
--------------------------------------------------------------------------------
/js/vnext/projection/item-index.js:
--------------------------------------------------------------------------------
1 | import _ from 'underscore';
2 |
3 | export const itemIndex = {
4 | name: 'itemIndex',
5 | handler(state) {
6 | const { items, primaryKey } = state;
7 | const itemIndex = {};
8 |
9 | _.each(items.slice(), item => {
10 | if (!_.has(item, primaryKey)) {
11 | item[primaryKey] = _.uniqueId('grid-item-');
12 | }
13 | itemIndex[item[primaryKey]] = item;
14 | });
15 |
16 | return _.defaults({ itemIndex }, state);
17 | },
18 | defaults: {},
19 | };
20 |
21 |
--------------------------------------------------------------------------------
/js/layout/template/table.dot:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{~it.columns :column:j}}
5 | | {{=column.$text || column.property || column}} |
6 | {{~}}
7 |
8 |
9 |
10 | {{~it.value :row:i}}
11 |
12 | {{~it.columns :column:j}}
13 | |
14 | {{= row[column.property].$html || row[column.property] || '' }}
15 | |
16 | {{~}}
17 |
18 | {{~}}
19 |
20 |
--------------------------------------------------------------------------------
/spec/integrated/driver/action.js:
--------------------------------------------------------------------------------
1 | import Promise from 'bluebird';
2 | import protocol from './protocol';
3 |
4 | function click(selector) {
5 | return protocol.element.call(this, selector)
6 | .then($el => {
7 | $el.click();
8 | return null;
9 | });
10 | }
11 |
12 | function setValue(selector, value) {
13 | return protocol.element.call(this, selector)
14 | .then(($el) => {
15 | $el.val(value).change();
16 | return null;
17 | });
18 | }
19 |
20 | export default {
21 | click,
22 | setValue,
23 | };
24 |
--------------------------------------------------------------------------------
/js/windowContainer.js:
--------------------------------------------------------------------------------
1 | define([
2 | 'lib/underscore',
3 | 'lib/jquery',
4 | 'component/grid/containerBase',
5 | ], function (_, $, ContainerBase) {
6 | var WindowContainer = ContainerBase.extend({
7 | constructor: function (options) {
8 | options = _.extend({}, options, { el: window });
9 | ContainerBase.prototype.constructor.apply(this, [options].concat(_.rest(arguments)));
10 | },
11 |
12 | offset: function (element) {
13 | return $(element).offset();
14 | },
15 | });
16 |
17 | return WindowContainer;
18 | });
19 |
--------------------------------------------------------------------------------
/demos/vnext/scrolling/sticky-header-on-element-flex/index.less:
--------------------------------------------------------------------------------
1 | .grid-viewport {
2 | height: 600px;
3 | width: 900px;
4 | overflow-y: auto;
5 | position: relative;
6 | border-style: dashed;
7 | }
8 |
9 | .header .column-header {
10 | border-top: 2px solid #81868B;
11 | border-bottom: 2px solid #81868B;
12 | background-color: #fff;
13 | }
14 |
15 | .freezing-group .th, .freezing-group .td {
16 | background: #fcffe5;
17 | }
18 | .other-group .th, .other-group .td {
19 | background: #edeeff;
20 | }
21 |
22 | .flex-cell {
23 | width: 150px;
24 | }
25 |
--------------------------------------------------------------------------------
/js/vnext/layout/flex-sticky.jade:
--------------------------------------------------------------------------------
1 | -
2 | var tableStyle = {
3 | 'border-collapse': 'collapse',
4 | 'margin': 0,
5 | 'table-layout': 'fixed',
6 | };
7 |
8 | .table-container.flex-layout(role='grid')&attributes(dataTableAttributes)
9 | div.table.sticky-header(style=tableStyle, class=classes)&attributes(headerAttributes)
10 | div.thead.header
11 | .sticky-header-filler
12 | div.table.data-table(style=tableStyle, class=classes)
13 | div.tbody(role='rowgroup')
14 | div.tr.top-filler(role='presentation')
15 | div.tr.bottom-filler(role='presentation')
16 | div.tfoot.footer
17 |
18 |
--------------------------------------------------------------------------------
/js/vnext/projection/index.js:
--------------------------------------------------------------------------------
1 | export { query } from './query.js';
2 | export { buffer } from './buffer.js';
3 | export { itemIndex } from './item-index.js';
4 | export { selection, setSelectAll, setSelectRow } from './selection.js';
5 | export { rangeSelection } from './range-selection.js';
6 | export { columns } from './columns.js';
7 | export { flexColumns } from './flex-columns.js';
8 | export { rows } from './rows.js';
9 | export { columnGroup } from './column-group.js';
10 | export { cells } from './cells.js';
11 | export { sortableHeader } from './sortable-header.js';
12 | export { events } from './events.js';
13 |
--------------------------------------------------------------------------------
/demos/vnext/scrolling/fixed-header/index.js:
--------------------------------------------------------------------------------
1 | import pgrid from '../../../../js';
2 | import people from '../people.json';
3 | import columns from '../columns.js';
4 | import rows from '../rows';
5 |
6 | import './index.less';
7 | import 'bootstrap-webpack';
8 |
9 | window.gridView = pgrid.factory({ vnext: true }).create({
10 | el: '.grid-container',
11 | tableClasses: ['table', 'table-bordered'],
12 | scrolling: {
13 | virtualized: true,
14 | header: 'fixed',
15 | },
16 | dataSource: {
17 | type: 'memory',
18 | data: people.value,
19 | },
20 | rows,
21 | columns,
22 | }).gridView.render();
23 |
--------------------------------------------------------------------------------
/demos/vnext/scrolling/static-header/index.js:
--------------------------------------------------------------------------------
1 | import pgrid from '../../../../js';
2 | import people from '../people.json';
3 | import columns from '../columns.js';
4 | import rows from '../rows';
5 |
6 | import './index.less';
7 | import 'bootstrap-webpack';
8 |
9 | window.gridView = pgrid.factory({ vnext: true }).create({
10 | el: '.grid-container',
11 | tableClasses: ['table', 'table-bordered'],
12 | scrolling: {
13 | virtualized: true,
14 | header: 'static',
15 | },
16 | dataSource: {
17 | type: 'memory',
18 | data: people.value,
19 | },
20 | rows,
21 | columns,
22 | }).gridView.render();
23 |
--------------------------------------------------------------------------------
/js/vnext/layout/flex-fixed.jade:
--------------------------------------------------------------------------------
1 | -
2 | var tableStyle = {
3 | 'border-collapse': 'collapse',
4 | 'margin': 0,
5 | 'table-layout': 'fixed',
6 | };
7 |
8 | .table-container.flex-layout(role='grid')&attributes(dataTableAttributes)
9 | .fixed-header
10 | div.table.fixed-header(style=tableStyle, class=classes)&attributes(headerAttributes)
11 | div.thead.header
12 | .viewport
13 | div.table.data-table(style=tableStyle, class=classes)
14 | div.tbody(role='rowgroup')
15 | div.tr.top-filler(role='presentation')
16 | div.tr.bottom-filler(role='presentation')
17 | div.tfoot.footer
18 |
--------------------------------------------------------------------------------
/js/vnext/layout/table-sticky.jade:
--------------------------------------------------------------------------------
1 | -
2 | var tableStyle = {
3 | 'border-collapse': 'collapse',
4 | 'margin': 0,
5 | 'table-layout': 'fixed',
6 | };
7 |
8 | .table-container
9 | table.sticky-header(style=tableStyle, class=classes, role='grid')&attributes(headerAttributes)
10 | colgroup.column-group
11 | thead.header
12 | .sticky-header-filler
13 | table.data-table(style=tableStyle, class=classes, role='grid')&attributes(dataTableAttributes)
14 | colgroup.column-group
15 | tbody
16 | tr.top-filler(role='presentation')
17 | tr.bottom-filler(role='presentation')
18 | tfoot.footer
19 |
20 |
--------------------------------------------------------------------------------
/spec/unit/mock-spec.js:
--------------------------------------------------------------------------------
1 | var expect = require('chai').expect;
2 | var Mock = require('component/grid/projection/mock');
3 | var Base = require('component/grid/projection/base');
4 | var Response = require('component/grid/model/response');
5 |
6 | describe('projection Mock', function () {
7 | it('update should run normal', function () {
8 | var model = new Mock({ n: 10 });
9 |
10 | var originalData = new Base();
11 | originalData.data = new Response();
12 |
13 | originalData.pipe(model);
14 | expect(model.data.get('value').length).to.be.equal(10);
15 | expect(model.data.get('count')).to.be.equal(10);
16 | });
17 | });
18 |
--------------------------------------------------------------------------------
/js/vnext/layout/table-fixed.jade:
--------------------------------------------------------------------------------
1 | -
2 | var tableStyle = {
3 | 'border-collapse': 'collapse',
4 | 'margin': 0,
5 | 'table-layout': 'fixed',
6 | };
7 |
8 | .table-container
9 | .fixed-header
10 | table.fixed-header(style=tableStyle, class=classes, role='grid')&attributes(headerAttributes)
11 | colgroup.column-group
12 | thead.header
13 | .viewport
14 | table.data-table(style=tableStyle, class=classes, role='grid')&attributes(dataTableAttributes)
15 | colgroup.column-group
16 | tbody
17 | tr.top-filler(role='presentation')
18 | tr.bottom-filler(role='presentation')
19 | tfoot.footer
20 |
--------------------------------------------------------------------------------
/spec/unit/page-spec.js:
--------------------------------------------------------------------------------
1 | var expect = require('chai').expect;
2 | var Page = require('component/grid/projection/page');
3 | var Base = require('component/grid/projection/base');
4 | var Response = require('component/grid/model/response');
5 |
6 | describe('projection Page', function () {
7 | it('update should run normal', function () {
8 | var model = new Page({
9 | 'page.size': 200,
10 | 'page.number': 0,
11 | });
12 |
13 | var originalData = new Base();
14 | originalData.data = new Response({ count: 400 });
15 | originalData.pipe(model);
16 | expect(model.data.get('page.count')).to.be.equal(2);
17 | });
18 | });
19 |
--------------------------------------------------------------------------------
/js/index.js:
--------------------------------------------------------------------------------
1 | import factory from './factory/grid-factory';
2 | import pkg from '../package.json';
3 | import {
4 | DataSource,
5 | MemoryDataSource,
6 | ODataDataSource,
7 | JSDataDataSource,
8 | } from './vnext/data-source';
9 |
10 | module.exports = {
11 | version: pkg.version,
12 | GridView: require('./grid-view'),
13 | projections: require('./projection/index'),
14 | layout: require('./layout/index'),
15 | factory,
16 | popupEditorPrompt: require('./popup-editor/index'),
17 | dataSource: {
18 | Base: DataSource,
19 | Memory: MemoryDataSource,
20 | OData: ODataDataSource,
21 | JSData: JSDataDataSource,
22 | },
23 | };
24 |
--------------------------------------------------------------------------------
/js/containerBase.js:
--------------------------------------------------------------------------------
1 | define([
2 | 'lib/underscore',
3 | 'lib/jquery',
4 | 'lib/backbone',
5 | ], function (_, $, Backbone) {
6 | var ContainerBase = Backbone.View.extend({
7 | events: {
8 | scroll: 'onScroll',
9 | resize: 'onResize',
10 | },
11 |
12 | onScroll: function (e) {
13 | this.trigger('scroll:container', e);
14 | },
15 |
16 | onResize: function (e) {
17 | this.trigger('resize:container', e);
18 | },
19 |
20 | offset: function (/* element, scrollTop, scrollLeft */) {
21 | throw new Error("Offset function not implemented");
22 | },
23 | });
24 |
25 | return ContainerBase;
26 | });
27 |
--------------------------------------------------------------------------------
/demos/full/specs/index.js:
--------------------------------------------------------------------------------
1 | var expect = require('chai').expect;
2 |
3 | /* global browser */
4 | /* eslint no-unused-expressions: 0 */
5 | describe('webdriver.io page', function () {
6 | beforeEach(function (cb) {
7 | browser.waitForExist('.table.grid', cb);
8 | });
9 |
10 | it('should expand and collapse the group column', function () {
11 | var collpaseSpan = browser.element('.pop-collapse');
12 | expect(collpaseSpan).to.be.exist;
13 | collpaseSpan.click();
14 | var expandBtn = browser.element('.pop-expand');
15 | expect(expandBtn).to.be.exist;
16 | expandBtn.click();
17 | expect(browser.isExisting('.pop-collapse')).to.be.true;
18 | });
19 | });
20 |
--------------------------------------------------------------------------------
/js/elementContainer.js:
--------------------------------------------------------------------------------
1 | define([
2 | 'lib/underscore',
3 | 'lib/jquery',
4 | 'component/grid/containerBase',
5 | ], function (_, $, ContainerBase) {
6 | var ElementContainer = ContainerBase.extend({
7 | offset: function (element) {
8 | var position = $(element).position();
9 |
10 | return {
11 | top: position.top + this.$el.scrollTop(),
12 | left: position.left + this.$el.scrollLeft(),
13 | };
14 | },
15 | });
16 |
17 | ElementContainer.isValidContainer = function (userContainer) {
18 | return ['absolute', 'relative', 'fixed'].indexOf($(userContainer).css('position')) >= 0;
19 | };
20 |
21 | return ElementContainer;
22 | });
23 |
--------------------------------------------------------------------------------
/demos/vnext/scrolling/sticky-header-on-element/index.js:
--------------------------------------------------------------------------------
1 | import $ from 'jquery';
2 | import pgrid from '../../../../js';
3 | import people from '../people.json';
4 | import columns from '../columns.js';
5 | import rows from '../rows';
6 |
7 | import './index.less';
8 | import 'bootstrap-webpack';
9 |
10 | window.gridView = pgrid.factory({ vnext: true }).create({
11 | el: '.grid-container',
12 | tableClasses: ['table', 'table-bordered'],
13 | scrolling: {
14 | virtualized: true,
15 | viewport: $('.grid-viewport'),
16 | header: 'sticky',
17 | },
18 | dataSource: {
19 | type: 'memory',
20 | data: people.value,
21 | },
22 | rows,
23 | columns,
24 | }).gridView.render();
25 |
--------------------------------------------------------------------------------
/js/projection/sink.js:
--------------------------------------------------------------------------------
1 | define([
2 | 'lib/underscore',
3 | 'component/grid/projection/base',
4 | 'component/grid/schema/properties',
5 | ], function (_, BaseProjection, schemaProperties) {
6 | var Model = BaseProjection.extend({
7 |
8 | defaults: {
9 | seed: null,
10 | },
11 |
12 | name: 'sink',
13 |
14 | update: function () {
15 | var value;
16 |
17 | value = this.get('seed');
18 |
19 | if (value) {
20 | var select = schemaProperties.from(value);
21 | this.patch({
22 | value: value,
23 | select: select,
24 | count: _.size(value),
25 | });
26 | }
27 | },
28 |
29 | });
30 |
31 | return Model;
32 | });
33 |
--------------------------------------------------------------------------------
/demos/vnext/scrolling/rows.js:
--------------------------------------------------------------------------------
1 | import Backbone from 'backbone';
2 |
3 | class CustomView extends Backbone.View {
4 | events() {
5 | return {
6 | 'click button': () => {
7 | console.log('click');
8 | this.$el.find('.text').val('test view');
9 | },
10 | };
11 | }
12 | render() {
13 | this.$el.html(`This is a Backbone View Row.
`);
14 | return this;
15 | }
16 | }
17 |
18 | window.customView = new CustomView().render();
19 |
20 | export default {
21 | headRows: [
22 | 'column-header-rows',
23 | ],
24 | footRows: [
25 | { view: window.customView },
26 | { html: 'This a html row
' },
27 | ],
28 | };
29 |
--------------------------------------------------------------------------------
/js/vnext/layout/flex-layout.less:
--------------------------------------------------------------------------------
1 | .flex-layout {
2 | .flex-row,
3 | .flex-column-group,
4 | .flex-column-group-container,
5 | .nested-column-container,
6 | .nested-column-placeholder {
7 | display: flex;
8 | }
9 |
10 | .flex-column-group,
11 | .flex-column-group-container {
12 | flex-grow: 1;
13 | }
14 |
15 | .nested-column-container > .flex-cell {
16 | width: 100%;
17 | }
18 |
19 | .nested-column-container {
20 | flex-direction: column;
21 | }
22 |
23 | .flex-cell {
24 | display: flex;
25 | flex-grow: 1;
26 | flex-shrink: 0;
27 | align-items: center;
28 | padding: 6px 8px;
29 | border-right: 1px solid #EDEBE9;
30 | border-bottom: 1px solid #EDEBE9;
31 | }
32 | }
--------------------------------------------------------------------------------
/demos/vnext/sortable-header/index.less:
--------------------------------------------------------------------------------
1 | .grid-container {
2 | width: 1200px;
3 |
4 | .viewport {
5 | height: 600px;
6 | }
7 | }
8 |
9 | table.table {
10 | thead {
11 | background: lavender;
12 | }
13 | }
14 |
15 | col {
16 | width: 120px;
17 | }
18 |
19 | tr.row-buffer-changed {
20 | background: LightPink;
21 | }
22 | tr.row-buffer-committed {
23 | background: LightGreen;
24 | }
25 |
26 | .redColor {
27 | color: red;
28 | }
29 | .longer {
30 | color: blue;
31 | }
32 |
33 | tr.noborder {
34 | border: hidden;
35 | }
36 |
37 | table {
38 | thead {
39 | tr.separator {
40 | height: 0;
41 | padding: 0;
42 | th {
43 | height: 0;
44 | padding: 0;
45 | };
46 | };
47 | };
48 | };
49 |
--------------------------------------------------------------------------------
/spec/unit/conf/karma.debug.conf.js:
--------------------------------------------------------------------------------
1 | var _ = require('underscore');
2 | var path = require('path');
3 | var webpackConfig = require('./webpack.config');
4 |
5 | module.exports = function (config) {
6 | config.set({
7 | basePath: '../../../',
8 | frameworks: [
9 | 'mocha',
10 | ],
11 | reporters: [
12 | 'mocha',
13 | ],
14 | client: {
15 | mocha: {
16 | reporter: 'html', // change Karma's debug.html to the mocha web reporter
17 | },
18 | },
19 | webpack: webpackConfig,
20 | browsers: [
21 | 'ChromeNoSandbox',
22 | ],
23 | customLaunchers: {
24 | ChromeNoSandbox: {
25 | base: 'Chrome',
26 | flags: ['--no-sandbox'],
27 | },
28 | },
29 | });
30 | };
31 |
--------------------------------------------------------------------------------
/spec/integrated/conf/karma.debug.conf.js:
--------------------------------------------------------------------------------
1 | var _ = require('underscore');
2 | var path = require('path');
3 | var webpackConfig = require('./webpack.config');
4 |
5 | module.exports = function (config) {
6 | config.set({
7 | basePath: '../../../',
8 | frameworks: [
9 | 'mocha',
10 | ],
11 | reporters: [
12 | 'mocha',
13 | ],
14 | client: {
15 | mocha: {
16 | reporter: 'html', // change Karma's debug.html to the mocha web reporter
17 | },
18 | },
19 | webpack: webpackConfig,
20 | browsers: [
21 | 'ChromeNoSandbox',
22 | ],
23 | customLaunchers: {
24 | ChromeNoSandbox: {
25 | base: 'Chrome',
26 | flags: ['--no-sandbox'],
27 | },
28 | },
29 | });
30 | };
31 |
--------------------------------------------------------------------------------
/demos/vnext/rows/index.less:
--------------------------------------------------------------------------------
1 | .grid-container {
2 | width: 1200px;
3 |
4 | .viewport {
5 | height: 600px;
6 | }
7 | }
8 |
9 | table.table {
10 | thead {
11 | background: lavender;
12 | }
13 | max-width: none;
14 | }
15 |
16 | col {
17 | width: 120px;
18 | }
19 |
20 | tr.row-buffer-changed {
21 | background: LightPink;
22 | }
23 | tr.row-buffer-committed {
24 | background: LightGreen;
25 | }
26 |
27 | .red-color {
28 | color: red;
29 | }
30 | .longer {
31 | color: blue;
32 | }
33 |
34 | tr.noborder {
35 | border: hidden;
36 | }
37 |
38 | table {
39 | thead {
40 | tr.separator {
41 | height: 0;
42 | padding: 0;
43 | th {
44 | height: 0;
45 | padding: 0;
46 | };
47 | };
48 | };
49 | };
50 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "projection-grid",
3 | "description": "",
4 | "main": "",
5 | "authors": [
6 | "Ahmed Kamel "
7 | ],
8 | "license": "MIT",
9 | "homepage": "https://github.com/BingAds/projection-grid",
10 | "moduleType": [
11 | "amd"
12 | ],
13 | "private": true,
14 | "ignore": [
15 | "**/.*",
16 | "node_modules",
17 | "bower_components",
18 | "lib/",
19 | "test",
20 | "tests"
21 | ],
22 | "dependencies": {
23 | "backbone": "backbone#1.2.3",
24 | "bootstrap": "bootstrap#v3.3.6",
25 | "jquery": "jquery#2.2.1",
26 | "requirejs": "requirejs#latest",
27 | "underscore": "underscore#1.8.3"
28 | },
29 | "devDependencies": {
30 | "squire": "squire#v0.2.0"
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/spec/unit/map-spec.js:
--------------------------------------------------------------------------------
1 | var expect = require('chai').expect;
2 | var sinon = require('sinon');
3 | var MapProjection = require('component/grid/projection/map');
4 | var Base = require('component/grid/projection/base');
5 | var Response = require('component/grid/model/response');
6 |
7 | describe('projection map', function () {
8 | it('update should run normal', function () {
9 | var model = new MapProjection({
10 | map: function (item) {
11 | return [{ name: item.name }];
12 | },
13 | });
14 |
15 | var originalData = new Base();
16 | originalData.data = new Response({
17 | value: [{ name: 'hello' }],
18 | });
19 | sinon.spy(model, 'patch');
20 | originalData.pipe(model);
21 | expect(model.patch.called).to.be.true;
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/demos/vnext/scrolling/sticky-header-on-window/index.js:
--------------------------------------------------------------------------------
1 | import $ from 'jquery';
2 | import pgrid from '../../../../js';
3 | import people from '../people.json';
4 | import columns from '../columns.js';
5 | import rows from '../rows';
6 |
7 | import './index.less';
8 | import 'bootstrap-webpack';
9 |
10 | window.gridView = pgrid.factory({ vnext: true }).create({
11 | el: '.grid-container',
12 | tableClasses: ['table', 'table-bordered'],
13 | scrolling: {
14 | virtualized: true,
15 | viewport: window,
16 | header: {
17 | type: 'sticky',
18 | offset() {
19 | return $('.navbar-container').height();
20 | },
21 | },
22 | },
23 | dataSource: {
24 | type: 'memory',
25 | data: people.value,
26 | },
27 | rows,
28 | columns,
29 | }).gridView.render();
30 |
--------------------------------------------------------------------------------
/js/vnext/projection/flex-columns.js:
--------------------------------------------------------------------------------
1 | import _ from 'underscore';
2 | import { getDefaultColumns, extendColumns, extendWidthAttr, extendDataColumnAttr } from './common.js';
3 |
4 | function columnsProjectionHandler(state, columns) {
5 | const defaultColumns = getDefaultColumns(state, columns);
6 | const extendedColumnsWithDataColumn = extendColumns(defaultColumns, extendDataColumnAttr);
7 | const extendedColumnsWithDataColumnWithWidth = extendColumns(extendedColumnsWithDataColumn, extendWidthAttr);
8 |
9 | return _.defaults({
10 | columns: extendedColumnsWithDataColumnWithWidth,
11 | }, state);
12 | }
13 |
14 | // only pipe this projection when the layout is 'flex'
15 | export const flexColumns = {
16 | name: 'columns',
17 | handler: columnsProjectionHandler,
18 | defaults: null,
19 | };
20 |
--------------------------------------------------------------------------------
/spec/unit/row-index-spec.js:
--------------------------------------------------------------------------------
1 | var expect = require('chai').expect;
2 | var RowIndex = require('component/grid/projection/row-index');
3 | var Base = require('component/grid/projection/base');
4 | var Response = require('component/grid/model/response');
5 |
6 | describe('projection RowIndex', function () {
7 | it('update should run normal', function () {
8 | var model = new RowIndex();
9 |
10 | var originalData = new Base();
11 | originalData.data = new Response({
12 | value: [
13 | { name: 'hello', id: 'hello_id' },
14 | { name: 'world', id: 'world_id' },
15 | ],
16 | });
17 |
18 | originalData.pipe(model);
19 | expect(model.data.get('value')[0].rowIndex).to.be.equal(1);
20 | expect(model.data.get('value')[1].rowIndex).to.be.equal(2);
21 | });
22 | });
23 |
--------------------------------------------------------------------------------
/js/vnext/projection/selection-resolver.js:
--------------------------------------------------------------------------------
1 | import _ from 'underscore';
2 |
3 | export class SelectionResolver {
4 | constructor(gridView) {
5 | this.gridView = gridView;
6 | }
7 |
8 | updateItems() {
9 | return this.patch({});
10 | }
11 |
12 | selectRow() {
13 | throw new Error('Not Supported');
14 | }
15 |
16 | deselectRow() {
17 | throw new Error('Not Supported');
18 | }
19 |
20 | selectAll() {
21 | throw new Error('Not Supported');
22 | }
23 |
24 | deselectAll() {
25 | throw new Error('Not Supported');
26 | }
27 |
28 | patch(options) {
29 | const selectionCur = this.gridView.get('selection');
30 | const selection = _.isFunction(options) ? options(selectionCur) : options;
31 |
32 | return _.defaults(selection, selectionCur);
33 | }
34 | }
35 |
36 |
--------------------------------------------------------------------------------
/demos/full/example.css:
--------------------------------------------------------------------------------
1 | table > thead > tr {
2 | background-color: #FFF;
3 | }
4 |
5 | .table {
6 | /*table-layout: fixed;*/
7 | width: 1200px;
8 | overflow: hidden;
9 | }
10 |
11 | .table thead td {
12 | /*display: flex;*/
13 | }
14 |
15 | .table td, .table th {
16 | word-break: break-word;
17 | /*outline: solid gray 1px;*/
18 | }
19 |
20 | .table__row--aggregate td {
21 | background-color: #f5f5f5;
22 | }
23 |
24 | .table__row--body td:hover {
25 | background-color: #ddd;
26 | }
27 |
28 | .column-tri-state-checkbox {
29 | border: 1px solid rgba(155,155,155,0.57);
30 | border-radius: 1px;
31 | height: 21px;
32 | margin: 0 1px;
33 | outline: 0;
34 | vertical-align: text-bottom;
35 | width: 21px;
36 | }
37 |
38 | .column-tri-state-checkbox > span.glyphicon {
39 | left: 3px;
40 | }
41 |
--------------------------------------------------------------------------------
/demos/odata/index.js:
--------------------------------------------------------------------------------
1 | import pgrid from 'projection-grid';
2 |
3 | const grid = pgrid.factory().create({
4 | el: '.grid-root',
5 | dataSource: {
6 | type: 'odata',
7 | url: 'http://services.odata.org/V4/Northwind/Northwind.svc/Customers',
8 | },
9 | columns: [
10 | { name: 'CustomerID', sortable: true },
11 | { name: 'CompanyName', sortable: true },
12 | { name: 'City', sortable: true },
13 | { name: 'ContactName', sortable: true },
14 | ],
15 | });
16 |
17 | grid.gridView.on('update:beginning', function () {
18 | console.log('begin update');
19 | });
20 |
21 | grid.gridView.on('update:finished', function (error) {
22 | if (error) {
23 | console.log('fail update');
24 | } else {
25 | console.log('success update');
26 | }
27 | });
28 |
29 | grid.gridView.render({ fetch: true });
30 |
--------------------------------------------------------------------------------
/js/layout/template/table.while.jade:
--------------------------------------------------------------------------------
1 | table.table.table-striped.table-hover
2 | - var i = 0;
3 | - var j = 0;
4 | thead
5 | tr
6 | - j = 0;
7 | while j < columns.length
8 | - var column = columns[j];
9 | th(style=column.$th_style)
10 | = column.$text || column.property || column
11 | - j++
12 | tbody
13 | - i = 0;
14 | while i < value.length
15 | - var row = value[i];
16 | tr
17 | - j = 0
18 | while j < columns.length
19 | - var column = columns[j];
20 | td(class=column.$lock?'table-col-lock':undefined, style=column.$td_style)
21 | - var res = row[column.property]
22 | if (res && res.$html)
23 | != res.$html
24 | else
25 | = res
26 | - j++
27 | - i++
--------------------------------------------------------------------------------
/demos/webpack.config.js:
--------------------------------------------------------------------------------
1 | var webpack = require('webpack');
2 |
3 | var webpackConfig = require('../webpack.config');
4 |
5 | webpackConfig.plugins = webpackConfig.plugins || [];
6 | webpackConfig.plugins.push(new webpack.optimize.LimitChunkCountPlugin({ maxChunks: 1 }));
7 |
8 | webpackConfig.module.loaders = webpackConfig.module.loaders.concat([
9 | { test: /bootstrap[\\\/]js[\\\/]/, loader: 'imports?jQuery=jquery' },
10 | { test: /\.(woff|woff2)(\?v=\d+\.\d+\.\d+)?$/, loader: 'url?limit=10000&mimetype=application/font-woff' },
11 | { test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, loader: 'url?limit=10000&mimetype=application/octet-stream' },
12 | { test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, loader: 'file' },
13 | { test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: 'url?limit=10000&mimetype=image/svg+xml' },
14 | ]);
15 |
16 | module.exports = webpackConfig;
17 |
--------------------------------------------------------------------------------
/js/projection/a11y.js:
--------------------------------------------------------------------------------
1 | define([
2 | 'lib/underscore',
3 | 'lib/backbone',
4 | 'component/grid/projection/base',
5 | ], function (_, Backbone, BaseProjection /* , schemaProperties, Response */) {
6 | const defaultselectAllLabel = 'Select All';
7 | var Model = BaseProjection.extend({
8 | defaults: {
9 | 'a11y.selection.selectAllLabel': 'Select All'
10 | },
11 | name: 'a11y',
12 | update: function (options) {
13 | var selectAllLabel = this.get('a11y.selection.selectAllLabel');
14 | if (Model.__super__.update.call(this, options)) {
15 | this.patch({
16 | 'a11y.selection.uniqueId': _.uniqueId().concat('-'),
17 | 'a11y.selection.selectAllLabel': _.isString(selectAllLabel) ? selectAllLabel : defaultselectAllLabel,
18 | });
19 | }
20 | },
21 | });
22 | return Model;
23 | });
24 |
--------------------------------------------------------------------------------
/demos/cell-view/pager-view-plugin.js:
--------------------------------------------------------------------------------
1 | import _ from 'underscore';
2 | import { PaginationView } from 'pagination-control';
3 |
4 | export default definePlugin => definePlugin('pagerView', [
5 | 'config',
6 | 'projection',
7 | 'gridView',
8 | ], function (config, projection, gridView) {
9 | const pagerView = new PaginationView(_.defaults({
10 | pageSize: config.pageable.pageSize,
11 | availablePageSizes: config.pageable.pageSizes,
12 | }, config.pagerView));
13 |
14 | gridView.on('change:data', function (model) {
15 | pagerView.itemCount = model.get('count');
16 | });
17 |
18 | pagerView.on('change:page-size', function (pageSize) {
19 | projection.set('page.size', pageSize);
20 | });
21 |
22 | pagerView.on('change:page-number', function (pageNumber) {
23 | projection.set('page.number', pageNumber);
24 | });
25 |
26 | return pagerView;
27 | });
28 |
--------------------------------------------------------------------------------
/demos/factory/pager-view-plugin.js:
--------------------------------------------------------------------------------
1 | import _ from 'underscore';
2 | import { PaginationView } from 'pagination-control';
3 |
4 | export default definePlugin => definePlugin('pagerView', [
5 | 'config',
6 | 'projection',
7 | 'gridView',
8 | ], function (config, projection, gridView) {
9 | const pagerView = new PaginationView(_.defaults({
10 | pageSize: config.pageable.pageSize,
11 | availablePageSizes: config.pageable.pageSizes,
12 | }, config.pagerView));
13 |
14 | gridView.on('change:data', function (model) {
15 | pagerView.itemCount = model.get('count');
16 | });
17 |
18 | pagerView.on('change:page-size', function (pageSize) {
19 | projection.set('page.size', pageSize);
20 | });
21 |
22 | pagerView.on('change:page-number', function (pageNumber) {
23 | projection.set('page.number', pageNumber);
24 | });
25 |
26 | return pagerView;
27 | });
28 |
--------------------------------------------------------------------------------
/js/projection/column-hovertext.js:
--------------------------------------------------------------------------------
1 | define([
2 | 'lib/underscore',
3 | 'lib/backbone',
4 | 'component/grid/projection/base',
5 | ], function (_, Backbone, BaseProjection) {
6 | var Model = BaseProjection.extend({
7 | name: 'column-hovertext',
8 | update: function (options) {
9 | if (Model.__super__.update.call(this, options)) {
10 | var model = this.src.data;
11 | var columns = model.get('columns');
12 |
13 | _.each(columns, function(column) {
14 | column.$metadata = _.result(column, '$metadata', {});
15 | column.$metadata['attr.head'] = _.result(column.$metadata, 'attr.head', {});
16 | column.$metadata['attr.head'].title = _.result(column.config, 'hoverText') || _.result(column.config, 'title') || '';
17 | });
18 |
19 | this.patch({ columns: columns });
20 | }
21 | },
22 | });
23 |
24 | return Model;
25 | });
26 |
--------------------------------------------------------------------------------
/demos/fixed-header/index.js:
--------------------------------------------------------------------------------
1 | import pgrid from 'projection-grid';
2 | import 'bootstrap-webpack';
3 | import './index.less';
4 |
5 | const grid = pgrid.factory().create({
6 | el: '.grid-root',
7 | dataSource: {
8 | type: 'odata',
9 | url: 'http://services.odata.org/V4/Northwind/Northwind.svc/Customers',
10 | },
11 | columns: [
12 | { name: 'CustomerID', sortable: true },
13 | { name: 'CompanyName', sortable: true },
14 | { name: 'City', sortable: true },
15 | { name: 'ContactName', sortable: true },
16 | ],
17 | scrollable: {
18 | fixedHeader: true,
19 | },
20 | layoutOptions: {
21 | class: 'table-bordered',
22 | },
23 | });
24 |
25 | grid.gridView.on('update:beginning', function () {
26 | console.log('begin update');
27 | });
28 |
29 | grid.gridView.on('update:finished', function () {
30 | console.log('end update');
31 | });
32 |
33 | grid.gridView.render({ fetch: true });
34 |
--------------------------------------------------------------------------------
/js/vnext/layout/table-mixins.jade:
--------------------------------------------------------------------------------
1 | mixin mixinHeadCell(cell, escapeAttr)
2 | - var attr = escapeAttr(cell.attributes)
3 | th&attributes(attr)(class=cell.classes)
4 | if (cell.html)
5 | != cell.html
6 | else
7 | = cell.value
8 |
9 | mixin mixinCell(cell, escapeAttr)
10 | - var attr = escapeAttr(cell.attributes)
11 | td&attributes(attr)(class=cell.classes)
12 | if (cell.html)
13 | != cell.html
14 | else
15 | = cell.value
16 |
17 | mixin mixinRow(row, group, escapeAttr)
18 | - var attr = escapeAttr(row.attributes)
19 | tr&attributes(attr)(class=row.classes)
20 | each cell in row.cells || []
21 | case group
22 | when 'head'
23 | +mixinHeadCell(cell, escapeAttr)
24 | when 'body'
25 | when 'foot'
26 | +mixinCell(cell, escapeAttr)
27 |
28 | mixin mixinRows(rows, group, escapeAttr)
29 | each row in rows || []
30 | +mixinRow(row, group, escapeAttr)
--------------------------------------------------------------------------------
/demos/vnext/scrolling/fixed-header/index.jade:
--------------------------------------------------------------------------------
1 | doctype html
2 | html(lang="en")
3 | head
4 | title= title
5 | body(style="padding-top: 70px")
6 | nav.navbar.navbar-default.navbar-fixed-top
7 | .container-fluid.navbar-container
8 | .navbar-header
9 | .navbar-brand Projection Grid vNext
10 | h1 Fixed Header
11 | p.
12 | Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
13 | tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
14 | veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea
15 | commodo consequat. Duis aute irure dolor in reprehenderit in voluptate
16 | velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint
17 | occaecat cupidatat non proident, sunt in culpa qui officia deserunt
18 | mollit anim id est laborum.
19 | .grid-container
20 | script(type="application/javascript", src=bundle)
21 |
--------------------------------------------------------------------------------
/demos/vnext/scrolling/static-header/index.jade:
--------------------------------------------------------------------------------
1 | doctype html
2 | html(lang="en")
3 | head
4 | title= title
5 | body(style="padding-top: 70px")
6 | nav.navbar.navbar-default.navbar-fixed-top
7 | .container-fluid.navbar-container
8 | .navbar-header
9 | .navbar-brand Projection Grid vNext
10 | h1 Static Header
11 | p.
12 | Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
13 | tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
14 | veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea
15 | commodo consequat. Duis aute irure dolor in reprehenderit in voluptate
16 | velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint
17 | occaecat cupidatat non proident, sunt in culpa qui officia deserunt
18 | mollit anim id est laborum.
19 | .grid-container
20 | script(type="application/javascript", src=bundle)
21 |
--------------------------------------------------------------------------------
/demos/vnext/data-source/custom/index.jade:
--------------------------------------------------------------------------------
1 | doctype html
2 | html(lang="en")
3 | head
4 | title= title
5 | body(style="padding-top: 70px")
6 | nav.navbar.navbar-default.navbar-fixed-top
7 | .container-fluid.navbar-container
8 | .navbar-header
9 | .navbar-brand Projection Grid vNext
10 | h1 JSData Data Source
11 | p.
12 | Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
13 | tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
14 | veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea
15 | commodo consequat. Duis aute irure dolor in reprehenderit in voluptate
16 | velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint
17 | occaecat cupidatat non proident, sunt in culpa qui officia deserunt
18 | mollit anim id est laborum.
19 | .grid-container
20 |
21 | script(type="application/javascript", src=bundle)
22 |
--------------------------------------------------------------------------------
/demos/vnext/data-source/jsdata/index.jade:
--------------------------------------------------------------------------------
1 | doctype html
2 | html(lang="en")
3 | head
4 | title= title
5 | body(style="padding-top: 70px")
6 | nav.navbar.navbar-default.navbar-fixed-top
7 | .container-fluid.navbar-container
8 | .navbar-header
9 | .navbar-brand Projection Grid vNext
10 | h1 JSData Data Source
11 | p.
12 | Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
13 | tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
14 | veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea
15 | commodo consequat. Duis aute irure dolor in reprehenderit in voluptate
16 | velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint
17 | occaecat cupidatat non proident, sunt in culpa qui officia deserunt
18 | mollit anim id est laborum.
19 | .grid-container
20 |
21 | script(type="application/javascript", src=bundle)
22 |
--------------------------------------------------------------------------------
/demos/vnext/data-source/memory/index.jade:
--------------------------------------------------------------------------------
1 | doctype html
2 | html(lang="en")
3 | head
4 | title= title
5 | body(style="padding-top: 70px")
6 | nav.navbar.navbar-default.navbar-fixed-top
7 | .container-fluid.navbar-container
8 | .navbar-header
9 | .navbar-brand Projection Grid vNext
10 | h1 Memory Data Source
11 | p.
12 | Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
13 | tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
14 | veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea
15 | commodo consequat. Duis aute irure dolor in reprehenderit in voluptate
16 | velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint
17 | occaecat cupidatat non proident, sunt in culpa qui officia deserunt
18 | mollit anim id est laborum.
19 | .grid-container
20 |
21 | script(type="application/javascript", src=bundle)
22 |
--------------------------------------------------------------------------------
/demos/vnext/data-source/odata/index.jade:
--------------------------------------------------------------------------------
1 | doctype html
2 | html(lang="en")
3 | head
4 | title= title
5 | body(style="padding-top: 70px")
6 | nav.navbar.navbar-default.navbar-fixed-top
7 | .container-fluid.navbar-container
8 | .navbar-header
9 | .navbar-brand Projection Grid vNext
10 | h1 OData Data Source
11 | p.
12 | Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
13 | tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
14 | veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea
15 | commodo consequat. Duis aute irure dolor in reprehenderit in voluptate
16 | velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint
17 | occaecat cupidatat non proident, sunt in culpa qui officia deserunt
18 | mollit anim id est laborum.
19 | .grid-container
20 |
21 | script(type="application/javascript", src=bundle)
22 |
--------------------------------------------------------------------------------
/demos/vnext/scrolling/sticky-header-on-window/index.jade:
--------------------------------------------------------------------------------
1 | doctype html
2 | html(lang="en")
3 | head
4 | title= title
5 | body(style="padding-top: 70px")
6 | nav.navbar.navbar-default.navbar-fixed-top
7 | .container-fluid.navbar-container
8 | .navbar-header
9 | .navbar-brand Projection Grid vNext
10 | h1 Sticky Header - with Window Viewport
11 | p.
12 | Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
13 | tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
14 | veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea
15 | commodo consequat. Duis aute irure dolor in reprehenderit in voluptate
16 | velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint
17 | occaecat cupidatat non proident, sunt in culpa qui officia deserunt
18 | mollit anim id est laborum.
19 | .grid-container
20 | script(type="application/javascript", src=bundle)
21 |
--------------------------------------------------------------------------------
/js/projection/row-index.js:
--------------------------------------------------------------------------------
1 | define([
2 | 'lib/underscore',
3 | 'lib/backbone',
4 | 'component/grid/projection/base',
5 | 'component/grid/schema/properties',
6 | 'component/grid/model/response',
7 | ], function (_, Backbone, BaseProjection /* , schemaProperties, Response */) {
8 | var Model = BaseProjection.extend({
9 | defaults: {},
10 | name: 'row-index',
11 | update: function (options) {
12 | if (Model.__super__.update.call(this, options)) {
13 | var model = this.src.data;
14 | var skip = this.get('skip');
15 | var value = _.map(model.get('value'), function (i, idx) {
16 | var ret = _.clone(i);
17 | ret.rowIndex = idx + (_.isFinite(skip) ? skip : 0) + 1;
18 | return ret;
19 | });
20 |
21 | this.patch({ value: value });
22 | } else {
23 | // todo [akamel] unset our properties only
24 | // this.unset();
25 | }
26 | },
27 | });
28 |
29 | return Model;
30 | });
31 |
--------------------------------------------------------------------------------
/spec/integrated/data/js-data-source.js:
--------------------------------------------------------------------------------
1 | import _ from 'underscore';
2 | import JSData from 'js-data';
3 | import DSHttpAdapter from 'js-data-http';
4 |
5 | JSData.DSUtils.Promise = require('bluebird');
6 | let store = new JSData.DS();
7 |
8 | store.registerAdapter('http', new DSHttpAdapter({
9 | basePath: 'https://services.odata.org/V4/Northwind/Northwind.svc/',
10 | deserialize: (definition, response) => {
11 | return response.data.value;
12 | },
13 | queryTransform: (definition, params) => {
14 | let query = {};
15 |
16 | _.has(params, 'offset') && (query.$skip = params.offset);
17 | _.has(params, 'limit') && (query.$top = params.limit);
18 | _.has(params, 'orderBy') && (query.$orderby = params.orderBy.map((item) => {
19 | return item[0] + ' ' + item[1].toLowerCase();
20 | }).join(','));
21 |
22 | return query;
23 | },
24 | }), { default: true });
25 |
26 | module.exports = store.defineResource({
27 | name: 'Customers',
28 | idAttribute: 'CustomerID',
29 | });
30 |
--------------------------------------------------------------------------------
/demos/js-data/js-data-resource.js:
--------------------------------------------------------------------------------
1 | var _ = require('underscore');
2 | var JSData = require('js-data');
3 | var DSHttpAdapter = require('js-data-http');
4 |
5 | JSData.DSUtils.Promise = require('bluebird');
6 |
7 | var store = new JSData.DS();
8 | store.registerAdapter('http', new DSHttpAdapter({
9 | basePath: 'http://services.odata.org/V4/Northwind/Northwind.svc/',
10 | deserialize: function (definition, response) {
11 | return response.data.value;
12 | },
13 | queryTransform: function (definition, params) {
14 | var query = {};
15 |
16 | _.has(params, 'offset') && (query.$skip = params.offset);
17 | _.has(params, 'limit') && (query.$top = params.limit);
18 | _.has(params, 'orderBy') && (query.$orderby = params.orderBy.map(function (item) {
19 | return item[0] + ' ' + item[1].toLowerCase();
20 | }).join(','));
21 |
22 | return query;
23 | },
24 | }), { default: true });
25 |
26 | module.exports = store.defineResource({
27 | name: 'Customers',
28 | idAttribute: 'CustomerID',
29 | });
30 |
--------------------------------------------------------------------------------
/demos/vnext/scrolling/sticky-header-on-element/index.jade:
--------------------------------------------------------------------------------
1 | doctype html
2 | html(lang="en")
3 | head
4 | title= title
5 | body(style="padding-top: 70px")
6 | nav.navbar.navbar-default.navbar-fixed-top
7 | .container-fluid.navbar-container
8 | .navbar-header
9 | .navbar-brand Projection Grid vNext
10 | .grid-viewport
11 | h1 Sticky Header - with Element Viewport
12 | p.
13 | Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
14 | tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
15 | veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea
16 | commodo consequat. Duis aute irure dolor in reprehenderit in voluptate
17 | velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint
18 | occaecat cupidatat non proident, sunt in culpa qui officia deserunt
19 | mollit anim id est laborum.
20 | .grid-container
21 |
22 | script(type="application/javascript", src=bundle)
23 |
--------------------------------------------------------------------------------
/spec/unit/util.js:
--------------------------------------------------------------------------------
1 | import { flow, omit, isEqual } from 'lodash/fp';
2 | import sinon from 'sinon';
3 | import $ from 'lib/jquery';
4 |
5 | export var $container = $('body > #container');
6 | if ($container.length === 0) {
7 | $container = $('').appendTo('body');
8 | }
9 |
10 | export function cleanup() {
11 | $container.empty();
12 | }
13 |
14 | export function objectPartialMatch(propsToOmit, expected) {
15 | const omitProps = omit(propsToOmit);
16 | return sinon.match(
17 | flow(
18 | // function (actual) {
19 | // console.log('Actual', JSON.stringify(omitProps(actual)));
20 | // console.log('Expected', JSON.stringify(expected));
21 | // return actual;
22 | // },
23 | omitProps,
24 | isEqual(expected)
25 | // result => {
26 | // console.log('result', result);
27 | // return result;
28 | // }
29 | ), 'matching ' + JSON.stringify(expected) + ' without [' + propsToOmit.map(prop => `"${prop}"`).join(', ') + '] properties');
30 | }
31 |
--------------------------------------------------------------------------------
/demos/vnext/data-source/custom/js-data-resource.js:
--------------------------------------------------------------------------------
1 | var _ = require('underscore');
2 | var JSData = require('js-data');
3 | var DSHttpAdapter = require('js-data-http');
4 |
5 | JSData.DSUtils.Promise = require('bluebird');
6 |
7 | var store = new JSData.DS();
8 | store.registerAdapter('http', new DSHttpAdapter({
9 | basePath: 'http://services.odata.org/V4/Northwind/Northwind.svc/',
10 | deserialize: function (definition, response) {
11 | return response.data.value;
12 | },
13 | queryTransform: function (definition, params) {
14 | var query = {};
15 |
16 | _.has(params, 'offset') && (query.$skip = params.offset);
17 | _.has(params, 'limit') && (query.$top = params.limit);
18 | _.has(params, 'orderBy') && (query.$orderby = params.orderBy.map(function (item) {
19 | return item[0] + ' ' + item[1].toLowerCase();
20 | }).join(','));
21 |
22 | return query;
23 | },
24 | }), { default: true });
25 |
26 | module.exports = store.defineResource({
27 | name: 'Customers',
28 | idAttribute: 'CustomerID',
29 | });
30 |
--------------------------------------------------------------------------------
/demos/vnext/data-source/jsdata/js-data-resource.js:
--------------------------------------------------------------------------------
1 | var _ = require('underscore');
2 | var JSData = require('js-data');
3 | var DSHttpAdapter = require('js-data-http');
4 |
5 | JSData.DSUtils.Promise = require('bluebird');
6 |
7 | var store = new JSData.DS();
8 | store.registerAdapter('http', new DSHttpAdapter({
9 | basePath: 'http://services.odata.org/V4/Northwind/Northwind.svc/',
10 | deserialize: function (definition, response) {
11 | return response.data.value;
12 | },
13 | queryTransform: function (definition, params) {
14 | var query = {};
15 |
16 | _.has(params, 'offset') && (query.$skip = params.offset);
17 | _.has(params, 'limit') && (query.$top = params.limit);
18 | _.has(params, 'orderBy') && (query.$orderby = params.orderBy.map(function (item) {
19 | return item[0] + ' ' + item[1].toLowerCase();
20 | }).join(','));
21 |
22 | return query;
23 | },
24 | }), { default: true });
25 |
26 | module.exports = store.defineResource({
27 | name: 'Customers',
28 | idAttribute: 'CustomerID',
29 | });
30 |
--------------------------------------------------------------------------------
/demos/vnext/scrolling/sticky-header-on-element-flex/index.jade:
--------------------------------------------------------------------------------
1 | doctype html
2 | html(lang="en")
3 | head
4 | title= title
5 | body(style="padding-top: 70px")
6 | nav.navbar.navbar-default.navbar-fixed-top
7 | .container-fluid.navbar-container
8 | .navbar-header
9 | .navbar-brand Projection Grid vNext
10 | .grid-viewport
11 | h1 Sticky Header/Flex layout/Selection - with Element Viewport
12 | p.
13 | Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
14 | tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
15 | veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea
16 | commodo consequat. Duis aute irure dolor in reprehenderit in voluptate
17 | velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint
18 | occaecat cupidatat non proident, sunt in culpa qui officia deserunt
19 | mollit anim id est laborum.
20 | .grid-container
21 |
22 | script(type="application/javascript", src=bundle)
23 |
--------------------------------------------------------------------------------
/js/projection/mock.js:
--------------------------------------------------------------------------------
1 | define([
2 | 'lib/underscore',
3 | 'lib/backbone',
4 | 'component/grid/projection/base',
5 | ], function (_, Backbone, BaseProjection) {
6 | var wrds = ['troubles', 'kahlua', 'poncho', 'suzie', 'baheyya'];
7 | var idx = 0;
8 |
9 | function randomRow() {
10 | var wrd1 = wrds[_.random(0, wrds.length - 1)];
11 | var wrd2 = wrds[_.random(0, wrds.length - 1)];
12 |
13 | return {
14 | index: idx++,
15 | name: wrd1 + ' ' + wrd2,
16 | age: _.random(0, 22),
17 | };
18 | }
19 |
20 | var Model = BaseProjection.extend({
21 | defaults: {
22 | n: 5000,
23 | },
24 | name: 'mock',
25 | update: function () {
26 | this.trigger('update:beginning');
27 | var value = [];
28 |
29 | _(this.get('n')).times(function () {
30 | value.push(randomRow());
31 | });
32 |
33 | this.data.set({
34 | value: value,
35 | count: value.length,
36 | });
37 | this.trigger('update:finished');
38 | },
39 | });
40 |
41 | return Model;
42 | });
43 |
--------------------------------------------------------------------------------
/js/factory/grid-view-plugin.js:
--------------------------------------------------------------------------------
1 | import _ from 'underscore';
2 | import GridView from '../grid-view';
3 | import layout from '../layout/index';
4 | import { delegateEvents } from './utility';
5 |
6 | export default definePlugin => definePlugin('gridView', [
7 | 'config',
8 | 'projection',
9 | 'renderers',
10 | ], function (config, projection, renderers) {
11 | const gridView = new GridView({
12 | projection,
13 | el: config.el,
14 | container: _.chain(config)
15 | .result('scrollable')
16 | .result('fixedHeader')
17 | .result('container')
18 | .value(),
19 | schema: config.dataSource.schema,
20 | Layout: layout.TableLayout.partial({
21 | renderers,
22 | template: layout.templates.table,
23 | hideHeaders: config.hideHeaders,
24 | $metadata: config.layoutOptions || {},
25 | }),
26 | });
27 |
28 | delegateEvents({
29 | from: projection,
30 | to: gridView,
31 | events: [
32 | 'update:beginning',
33 | 'update:finished',
34 | ],
35 | });
36 |
37 | return gridView;
38 | });
39 |
--------------------------------------------------------------------------------
/spec/unit/odata-spec.js:
--------------------------------------------------------------------------------
1 | var expect = require('chai').expect;
2 | var sinon = require('sinon');
3 | var $ = require('lib/jquery');
4 | var OData = require('component/grid/projection/odata');
5 |
6 | describe('projection OData', function () {
7 | it('constructor and initialize should run normal', function () {
8 | var model = new OData({
9 | url: 'www.bing.com',
10 | skip: 10,
11 | take: 10,
12 | orderby: [{ name: 1 }],
13 | filter: 'name',
14 | });
15 | sinon.stub($, 'getJSON', function () {
16 | var obj = {
17 | success: function () {
18 | return obj;
19 | },
20 | error: function () {
21 | return obj;
22 | },
23 | complete: function () {},
24 | };
25 | return obj;
26 | });
27 | model.update();
28 | var expectRes = {
29 | $format: 'json',
30 | $count: true,
31 | $top: 10,
32 | $skip: 10,
33 | $orderby: 'name asc',
34 | };
35 | expect($.getJSON.calledWith('www.bing.com', expectRes)).to.be.true;
36 | });
37 | });
38 |
--------------------------------------------------------------------------------
/demos/js-data/index.js:
--------------------------------------------------------------------------------
1 | var pgrid = require('projection-grid');
2 | var Customer = require('./js-data-resource');
3 |
4 | var jsdata = new pgrid.projections.JSData({
5 | 'jsdata.entity': Customer,
6 | });
7 | var projection = jsdata.pipe(
8 | new pgrid.projections.ColumnI18n()
9 | ).pipe(new pgrid.projections.ColumnQueryable({
10 | 'column.take': 10,
11 | }));
12 |
13 | var grid = new pgrid.GridView({
14 | el: '.grid-root',
15 | projection: projection,
16 | Layout: pgrid.layout.TableLayout.partial({
17 | template: pgrid.layout.templates.table,
18 | renderers: [
19 | pgrid.layout.renderers.Virtualization,
20 | ],
21 | columns: {
22 | CustomerID: { sortable: true },
23 | CompanyName: { sortable: true },
24 | City: { sortable: true },
25 | ContactName: { sortable: true },
26 | },
27 | }),
28 | });
29 |
30 | jsdata.on('update:beginning', function () {
31 | console.log('begin update');
32 | });
33 |
34 | jsdata.on('update:finished', function () {
35 | console.log('end update');
36 | });
37 |
38 | grid.render({ fetch: true });
39 |
--------------------------------------------------------------------------------
/demos/vnext/scrolling/columns.js:
--------------------------------------------------------------------------------
1 | import emailsTemplate from './emails.jade';
2 |
3 | export default [{
4 | name: 'UserName',
5 | width: 520,
6 | sortable: true,
7 | }, {
8 | name: 'Name',
9 | property: item => `${item.FirstName}, ${item.LastName}`,
10 | width: 150,
11 | sortable: true,
12 | }, {
13 | name: 'Emails',
14 | template: emailsTemplate,
15 | width: 220,
16 | sortable: item => item.Emails.length,
17 | }, {
18 | name: 'AddressInfo',
19 | columns: [{
20 | name: 'Address',
21 | property: 'AddressInfo/0/Address',
22 | sortable: true,
23 | }, {
24 | name: 'City',
25 | columns: [{
26 | name: 'CityName',
27 | property: 'AddressInfo/0/City/Name',
28 | sortable: true,
29 | }, {
30 | name: 'CityCountry',
31 | property: 'AddressInfo/0/City/CountryRegion',
32 | sortable: true,
33 | }, {
34 | name: 'CityRegion',
35 | property: 'AddressInfo/0/City/Region',
36 | sortable: true,
37 | }],
38 | }],
39 | }, {
40 | name: 'Gender',
41 | sortable: true,
42 | }, {
43 | name: 'Concurrency',
44 | width: 200,
45 | sortable: true,
46 | }];
47 |
--------------------------------------------------------------------------------
/js/projection/memory-page.js:
--------------------------------------------------------------------------------
1 | define([
2 | 'lib/underscore',
3 | 'lib/backbone',
4 | 'component/grid/projection/base',
5 | 'component/grid/schema/properties',
6 | 'component/grid/model/response',
7 | ], function (_, Backbone, BaseProjection, schemaProperties /* , Response */) {
8 | var Model = BaseProjection.extend({
9 | defaults: {
10 | skip: 0,
11 | take: Number.MAX_VALUE,
12 | select: [],
13 | },
14 | name: 'memory-page',
15 | update: function (options) {
16 | if (Model.__super__.update.call(this, options)) {
17 | var model = this.src.data;
18 |
19 | var value = model.get('value');
20 |
21 | value = _.chain(value)
22 | .rest(this.get('skip'))
23 | .first(this.get('take'))
24 | .value();
25 |
26 | var select = this.get('select');
27 | if (!_.size(select)) {
28 | select = schemaProperties.from(value);
29 | }
30 |
31 | this.patch({
32 | value: value,
33 | select: select,
34 | });
35 | }
36 | },
37 | });
38 |
39 | return Model;
40 | });
41 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Bing Ads
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 |
--------------------------------------------------------------------------------
/spec/unit/column-i18n-spec.js:
--------------------------------------------------------------------------------
1 | var expect = require('chai').expect;
2 | var ColumnI18n = require('component/grid/projection/column-i18n');
3 | var Base = require('component/grid/projection/base');
4 | var Response = require('component/grid/model/response');
5 |
6 | describe('projection ColumnI18n', function () {
7 | it('update should run normal', function () {
8 | var model = new ColumnI18n({
9 | 'column.i18n': {
10 | 'name': 'Name',
11 | '': function (name) {
12 | return '$' + name;
13 | },
14 | },
15 | 'subColumn.i18n': {
16 | name: 'SubName',
17 | },
18 | });
19 | var originalData = new Base();
20 | originalData.data = new Response({
21 | columns: {
22 | name: { name: 'hello', property: 'name' },
23 | id: { id: '007', property: 'id' },
24 | },
25 | });
26 | originalData.pipe(model);
27 | expect(model.data.get('columns').name.$text).to.be.equal('Name');
28 | expect(model.data.get('columns').name.config.subColTitle).to.be.equal('SubName');
29 | expect(model.data.get('columns').id.$text).to.be.equal('$id');
30 | });
31 | });
32 |
--------------------------------------------------------------------------------
/demos/vnext/pagination/pager-view-plugin.js:
--------------------------------------------------------------------------------
1 | import _ from 'underscore';
2 | import { PaginationView } from 'pagination-control';
3 |
4 | export default definePlugin => definePlugin('pagerView', [
5 | 'gridView',
6 | ], function (gridView) {
7 | const pagerViewConfig = gridView.get('pagerView');
8 | const page = new PaginationView(pagerViewConfig).render();
9 | const initPageSize = pagerViewConfig.pageSize || pagerViewConfig.availablePageSizes[0];
10 | const initPageNumber = pagerViewConfig.pageNumber || 0;
11 | gridView.set({ query: _.defaults({ skip: initPageSize * initPageNumber, take: initPageSize }, gridView.get('query')) });
12 | gridView.on('didUpdate', () => {
13 | page.itemCount = gridView.totalCountRows;
14 | });
15 | page.on('change:page-number', pageNumber => {
16 | page.pageNumber = pageNumber;
17 | gridView.set({ query: _.defaults({ skip: page.pageSize * pageNumber, take: page.pageSize }, gridView.get('query')) });
18 | });
19 | page.on('change:page-size', pageSize => {
20 | page.pageSize = pageSize;
21 | gridView.set({ query: _.defaults({ take: pageSize }, gridView.get('query')) });
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/js/projection/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | A11y: require('./a11y'),
3 | AggregateRow: require('./aggregate-row'),
4 | Base: require('./base'),
5 | ColumnI18n: require('./column-i18n'),
6 | ColumnQueryable: require('./column-queryable'),
7 | ColumnShifter: require('./column-shifter'),
8 | ColumnTemplate: require('./column-template'),
9 | EditableString: require('./editable-string'),
10 | Editable: require('./editable'),
11 | JSData: require('./jsdata'),
12 | Map: require('./map'),
13 | MemoryQueryable: require('./memory-queryable'),
14 | Memory: require('./memory'),
15 | Mock: require('./mock'),
16 | Odata: require('./odata'),
17 | Page: require('./page'),
18 | PropertyTemplate: require('./property-template'),
19 | RowCheckbox: require('./row-checkbox'),
20 | Row: require('./row'),
21 | RowTriStateCheckboxProjection: require('./row-tri-state-checkbox'),
22 | RowIndex: require('./row-index'),
23 | Sink: require('./sink'),
24 | // Todo[hezhan]: deprecated?
25 | // ColumnGroup: require('./column-group'),
26 | ColumnHoverText: require('./column-hovertext'),
27 | Columns: require('./columns').default,
28 | };
29 |
--------------------------------------------------------------------------------
/js/vnext/projection/events.js:
--------------------------------------------------------------------------------
1 | import _ from 'underscore';
2 |
3 | function sequence(...args) {
4 | const callbacks = _.filter(args, _.isFunction);
5 | return function (...argsInner) {
6 | _.each(callbacks, cb => cb.apply(this, argsInner));
7 | };
8 | }
9 |
10 | /**
11 | * @external BackboneViewEventHash
12 | * @see {@link http://backbonejs.org/#View-events}
13 | */
14 |
15 | /**
16 | * Merge the customized events with the projection injected events.
17 | * @param {ContentChainState} state
18 | * The input state.
19 | * @param {external:BackboneViewEventHash} eventsOptions
20 | * The customized events in form of `Backbone.View#events`.
21 | */
22 | function eventsProjectionHandler(state, eventsOptions) {
23 | const pairs = _.pairs(state.events).concat(_.pairs(eventsOptions));
24 | const events = _.reduce(pairs, (memo, [key, handler]) => {
25 | return _.extend(memo, {
26 | [key]: sequence(memo[key], handler)
27 | });
28 | }, {});
29 |
30 | return _.defaults({ events }, state);
31 | }
32 |
33 | export const events = {
34 | name: 'events',
35 | handler: eventsProjectionHandler,
36 | defaults: {},
37 | };
38 |
39 |
--------------------------------------------------------------------------------
/js/projection/map.js:
--------------------------------------------------------------------------------
1 | define([
2 | 'lib/underscore',
3 | 'lib/backbone',
4 | 'component/grid/projection/base',
5 | 'component/grid/schema/properties',
6 | 'component/grid/model/response',
7 | ], function (_, Backbone, BaseProjection, schemaProperties /* , Response */) {
8 | var Model = BaseProjection.extend({
9 | defaults: {
10 | // todo [akamel] consider supporting a select on this level?
11 | map: _.identity,
12 | },
13 | name: 'map',
14 | update: function (options) {
15 | // Model.__super__.update.call(this, options);
16 |
17 | if (Model.__super__.update.call(this, options)) {
18 | var model = this.src.data;
19 | var map = this.get('map');
20 | var value = _.isFunction(map) ? _.map(model.get('value'), map) : model.get('value');
21 |
22 | value = _.flatten(value);
23 |
24 | this.patch({
25 | value: value,
26 | select: _.without(schemaProperties.from(value), '$metadata'),
27 | });
28 | } else {
29 | // todo [akamel] unset our properties only
30 | // this.unset();
31 | }
32 | },
33 | });
34 |
35 | return Model;
36 | });
37 |
--------------------------------------------------------------------------------
/demos/vnext/data-source/custom/index.js:
--------------------------------------------------------------------------------
1 | import _ from 'underscore';
2 |
3 | import pgrid from '../../../../js';
4 | import store from './js-data-resource.js';
5 |
6 | import './index.less';
7 | import 'bootstrap-webpack';
8 |
9 | // This is a decorator of the OData data source
10 | // It filter out the items with CustomerID starts with 'A'.
11 | class CustomDataSource extends pgrid.dataSource.JSData {
12 | constructor(resource, options) {
13 | super(resource, options);
14 | this._jsdataDS = new pgrid.dataSource.JSData(resource);
15 | }
16 |
17 | query(options) {
18 | return this._jsdataDS.query(options).then(({
19 | totalCount,
20 | items,
21 | }) => {
22 | const filteredItems = _.filter(items, item => item.CustomerID[0] !== 'A');
23 |
24 | return {
25 | totalCount,
26 | items: filteredItems,
27 | };
28 | });
29 | }
30 | }
31 |
32 | window.gridView = pgrid.factory({ vnext: true }).create({
33 | el: '.grid-container',
34 | tableClasses: ['table', 'table-bordered'],
35 | scrolling: {
36 | virtualized: true,
37 | },
38 | dataSource: new CustomDataSource(store),
39 | }).gridView.render();
40 |
--------------------------------------------------------------------------------
/spec/unit/conf/karma.conf.js:
--------------------------------------------------------------------------------
1 | var _ = require('underscore');
2 | var path = require('path');
3 |
4 | function getWebpackConfig() {
5 | var webpackConfig = require('./webpack.config');
6 |
7 | webpackConfig.module.preLoaders = [
8 | {
9 | test: /\.js$/,
10 | include: path.resolve('./js/'),
11 | loader: 'istanbul-instrumenter',
12 | },
13 | ].concat(webpackConfig.module.preLoaders || []);
14 | return webpackConfig;
15 | }
16 |
17 | module.exports = function (config) {
18 | config.set({
19 | basePath: '../../../',
20 | frameworks: [
21 | 'mocha',
22 | ],
23 | client: {
24 | mocha: {
25 | reporter: 'html', // change Karma's debug.html to the mocha web reporter
26 | },
27 | },
28 | reporters: ['mocha', 'coverage', 'junit'],
29 | webpack: getWebpackConfig(),
30 | coverageReporter: {
31 | dir: './spec/unit/coverage',
32 | reporters: [
33 | { type: 'html', subdir: 'report-html' },
34 | { type: 'lcov', subdir: 'report-lcov' },
35 | ],
36 | },
37 | junitReporter: {
38 | outputDir: './spec/unit/test-results/',
39 | },
40 | browsers: [
41 | 'PhantomJS',
42 | ],
43 | });
44 | };
45 |
--------------------------------------------------------------------------------
/spec/unit/conf/webpack.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 | var _ = require('underscore');
3 | var webpackAlias = require('../../../webpack.alias');
4 | _.extend(webpackAlias, {'sinon': 'sinon/pkg/sinon.js'});
5 |
6 | module.exports = {
7 | output: {
8 | path: path.resolve('./dist'),
9 | filename: 'unit-test.js',
10 | devtoolModuleFilenameTemplate: function (info) {
11 | if (path.isAbsolute(info.absoluteResourcePath)) {
12 | return 'webpack-src:///unit-test/' + path.relative('.', info.absoluteResourcePath).replace(/\\/g, '/');
13 | }
14 | return info.absoluteResourcePath;
15 | },
16 | },
17 | resolve: {
18 | alias: webpackAlias,
19 | },
20 | module: {
21 | loaders: [
22 | {
23 | test: /sinon\.js$/,
24 | loader: 'imports?define=>false,require=>false',
25 | },
26 | // jade
27 | { test: /\.jade$/, loader: 'jade-loader' },
28 | // jade-end
29 | // es2015
30 | { test: /\.js$/, exclude: /\bnode_modules\b/, loader: 'babel-loader' },
31 | // es2015-end
32 | { test: /\.less$/, loader: 'style!css!less' },
33 | ],
34 |
35 | },
36 | babel: { presets: ['es2015'] },
37 | devtool: 'inline-source-map',
38 | };
39 |
40 |
--------------------------------------------------------------------------------
/spec/unit/column-queryable-spec.js:
--------------------------------------------------------------------------------
1 | var expect = require('chai').expect;
2 | var ColumnQueryable = require('component/grid/projection/column-queryable');
3 | var Base = require('component/grid/projection/base');
4 | var Response = require('component/grid/model/response');
5 |
6 | describe('projection ColumnQueryable', function () {
7 | it('update should run normal', function () {
8 | var model = new ColumnQueryable({
9 | 'column.take': 2,
10 | 'column.skip': 1,
11 | 'column.lock': ['name'],
12 | });
13 |
14 | var originalData = new Base();
15 | originalData.data = new Response({
16 | columns: {
17 | name: { name: 'hello', property: 'name' },
18 | id: { id: '007', property: 'id' },
19 | attr1: { attr1: '007', property: 'attr1' },
20 | attr2: { attr2: '007', property: 'attr2' },
21 | },
22 | });
23 | originalData.pipe(model);
24 | expect(model.data.get('columns').name.$lock).to.be.equal(true);
25 | expect(model.data.get('select').length).to.be.equal(2);
26 |
27 | expect(model.data.get('column.skip')).to.be.equal(1);
28 | expect(model.data.get('columns.skipped')).to.be.eql(['id']);
29 | expect(model.data.get('columns.remaining')).to.be.eql(['attr2']);
30 | });
31 | });
32 |
--------------------------------------------------------------------------------
/spec/unit/property-template-spec.js:
--------------------------------------------------------------------------------
1 | var expect = require('chai').expect;
2 | var PropertyTemplate = require('component/grid/projection/property-template');
3 | var Base = require('component/grid/projection/base');
4 | var Response = require('component/grid/model/response');
5 |
6 | describe('projection PropertyTemplate', function () {
7 | it('update should run normal', function () {
8 | var model = new PropertyTemplate({
9 | 'property.template': {
10 | name: function (local) {
11 | return local.model[local.property];
12 | },
13 | id: function (local) {
14 | return local.model[local.property];
15 | },
16 | },
17 | });
18 |
19 | var originalData = new Base();
20 | originalData.data = new Response({
21 | value: [
22 | { name: 'hello', id: 'hello_id' },
23 | { name: 'world', id: 'world_id' },
24 | ],
25 | });
26 | originalData.pipe(model);
27 | expect(model.data.get('value')[0].name.$html).to.be.equal('hello');
28 | expect(model.data.get('value')[0].id.$html).to.be.equal('hello_id');
29 |
30 | expect(model.data.get('value')[1].name.$html).to.be.equal('world');
31 | expect(model.data.get('value')[1].id.$html).to.be.equal('world_id');
32 | });
33 | });
34 |
--------------------------------------------------------------------------------
/demos/vnext/selection/single/index.js:
--------------------------------------------------------------------------------
1 | import $ from 'jquery';
2 | import _ from 'underscore';
3 | import Backbone from 'backbone';
4 | import pgrid from '../../../../js';
5 |
6 | import './index.less';
7 | import 'bootstrap-webpack';
8 |
9 | window.gridView = pgrid.factory({ vnext: true }).create({
10 | el: '.grid-container',
11 | tableClasses: ['table', 'table-bordered'],
12 | scrolling: {
13 | virtualized: true,
14 | header: {
15 | type: 'sticky',
16 | offset() {
17 | return $('.navbar-container').height();
18 | },
19 | },
20 | },
21 | dataSource: {
22 | type: 'odata',
23 | url: 'http://services.odata.org/V4/Northwind/Northwind.svc/Orders',
24 | primaryKey: 'OrderID',
25 | },
26 | selection: {
27 | single: true,
28 | selectable(item) {
29 | return _.has(item, 'CustomerID') && item.CustomerID.match(/^[A-N]/);
30 | },
31 | colClasses: ['selection-column'],
32 | headClasses: ['selection-header'],
33 | bodyClasses: ['selection-cell'],
34 | },
35 | }).gridView.render();
36 |
37 | gridView.on('willSelect', (selections) => {
38 | console.log(`Will select ${selections[0]}`);
39 | });
40 |
41 | gridView.on('didSelect', (selections) => {
42 | console.log(`Did select ${selections[0]}`);
43 | });
44 |
45 |
--------------------------------------------------------------------------------
/demos/vnext/selection/multiple/index.js:
--------------------------------------------------------------------------------
1 | import $ from 'jquery';
2 | import Backbone from 'backbone';
3 | import pgrid from '../../../../js';
4 |
5 | import './index.less';
6 | import 'bootstrap-webpack';
7 |
8 | window.gridView = pgrid.factory({ vnext: true }).create({
9 | el: '.grid-container',
10 | tableClasses: ['table', 'table-bordered'],
11 | scrolling: {
12 | virtualized: true,
13 | header: {
14 | type: 'sticky',
15 | offset() {
16 | return $('.navbar-container').height();
17 | },
18 | },
19 | },
20 | dataSource: {
21 | type: 'odata',
22 | url: 'http://services.odata.org/V4/Northwind/Northwind.svc/Orders',
23 | primaryKey: 'OrderID',
24 | },
25 | selection: {
26 | a11y: {
27 | selectAllLabel: 'select all label',
28 | },
29 | selectable(item) {
30 | return item.OrderID && (item.OrderID % 2 === 0);
31 | },
32 | colClasses: ['selection-column'],
33 | headClasses: ['selection-header'],
34 | bodyClasses: ['selection-cell'],
35 | },
36 | }).gridView.render();
37 |
38 | gridView.on('willSelect', (selections) => {
39 | console.log(`Will select ${selections.length} items`);
40 | });
41 |
42 | gridView.on('didSelect', (selections) => {
43 | console.log(`Did select ${selections.length} items`);
44 | });
45 |
46 |
--------------------------------------------------------------------------------
/spec/unit/column-shifter-spec.js:
--------------------------------------------------------------------------------
1 | var expect = require('chai').expect;
2 | var sinon = require('sinon');
3 | var ColumnShifter = require('component/grid/projection/column-shifter');
4 | var Base = require('component/grid/projection/base');
5 | var Response = require('component/grid/model/response');
6 |
7 | describe('projection ColumnShifter', function () {
8 | it('update should run normal', function () {
9 | var model = new ColumnShifter();
10 |
11 | var originalData = new Base();
12 | originalData.data = new Response({
13 | columns: {
14 | name: { name: 'hello', property: 'name' },
15 | id: { id: '007', property: 'id' },
16 | },
17 | select: ['name', 'id'],
18 | });
19 | originalData.pipe(model);
20 | expect(model.data.get('select')[0]).to.be.equal('column.skip.less');
21 | expect(model.data.get('select')[3]).to.be.equal('column.skip.more');
22 | });
23 |
24 | it('thClick should run normal', function () {
25 | var model = new ColumnShifter();
26 | model.get = sinon.stub().returns(1);
27 | sinon.spy(model, 'set');
28 |
29 | model.thClick({}, {
30 | column: { $metadata: { enabled: true } },
31 | property: 'column.skip.less',
32 | });
33 | expect(model.set.calledWith({ 'column.skip': 0 })).to.be.true;
34 | });
35 | });
36 |
--------------------------------------------------------------------------------
/spec/integrated/data/js-data-expected.json:
--------------------------------------------------------------------------------
1 |
2 | {
3 | "value": [
4 | {
5 | "CustomerID": "ALFKI",
6 | "CompanyName": "Alfreds Futterkiste",
7 | "ContactName": "Maria Anders",
8 | "ContactTitle": "Sales Representative",
9 | "Address": "Obere Str. 57",
10 | "City": "Berlin",
11 | "Region": "",
12 | "PostalCode": "12209",
13 | "Country": "Germany",
14 | "Phone": "030-0074321",
15 | "Fax": "030-0076545"
16 | },
17 | {
18 | "CustomerID": "BOTTM",
19 | "CompanyName": "Bottom-Dollar Markets",
20 | "ContactName": "Elizabeth Lincoln",
21 | "ContactTitle": "Accounting Manager",
22 | "Address": "23 Tsawassen Blvd.",
23 | "City": "Tsawassen",
24 | "Region": "BC",
25 | "PostalCode": "T2F 8M4",
26 | "Country": "Canada",
27 | "Phone": "(604) 555-4729",
28 | "Fax": "(604) 555-3745"
29 | },
30 | {
31 | "CustomerID": "ERNSH",
32 | "CompanyName": "Ernst Handel",
33 | "ContactName": "Roland Mendel",
34 | "ContactTitle": "Sales Manager",
35 | "Address": "Kirchgasse 6",
36 | "City": "Graz",
37 | "Region": "",
38 | "PostalCode": "8010",
39 | "Country": "Austria",
40 | "Phone": "7675-3425",
41 | "Fax": "7675-3426"
42 | }
43 | ]
44 | }
45 |
--------------------------------------------------------------------------------
/js/projection/memory-filter.js:
--------------------------------------------------------------------------------
1 | define([
2 | 'lib/underscore',
3 | 'lib/backbone',
4 | 'component/grid/projection/base',
5 | 'component/grid/schema/properties',
6 | 'component/grid/model/response',
7 | ], function (_, Backbone, BaseProjection, schemaProperties /* , Response */) {
8 | var Model = BaseProjection.extend({
9 | defaults: {
10 | filter: function () {
11 | return true;
12 | },
13 | select: [],
14 | },
15 | name: 'memory-filter',
16 | beforeSet: function (local) {
17 | if (_.has(local, 'filter')) {
18 | if (!_.isFunction(local.filter)) {
19 | local.filter = this.defaults.filter;
20 | }
21 | }
22 | },
23 | update: function (options) {
24 | if (Model.__super__.update.call(this, options)) {
25 | var model = this.src.data;
26 |
27 | var value = model.get('value');
28 |
29 | value = _.chain(value)
30 | .filter(this.get('filter'))
31 | .value();
32 |
33 | var select = this.get('select');
34 | if (!_.size(select)) {
35 | select = schemaProperties.from(value);
36 | }
37 |
38 | this.patch({
39 | value: value,
40 | select: select,
41 | });
42 | }
43 | },
44 | });
45 |
46 | return Model;
47 | });
48 |
--------------------------------------------------------------------------------
/demos/full/northwind-schema.js:
--------------------------------------------------------------------------------
1 | define({
2 | title: 'Northwind Schema',
3 | type: 'object',
4 | properties: {
5 | CustomerID: {
6 | type: 'string',
7 | filterable: true,
8 | nullable: false,
9 | required: true,
10 | aggregateType: 'count',
11 | },
12 | EmployeeID: {
13 | type: 'number',
14 | filterable: true,
15 | nullable: false,
16 | required: true,
17 | aggregateType: 'count',
18 | },
19 | ShipName: {
20 | type: 'string',
21 | filterable: true,
22 | nullable: false,
23 | required: true,
24 | aggregateType: 'count',
25 | },
26 | OrderID: {
27 | type: 'integer',
28 | },
29 | Freight: {
30 | type: 'number',
31 | },
32 | ShipCountry: {
33 | type: 'string',
34 | enum: [
35 | 'France',
36 | 'Germany',
37 | 'Brazil',
38 | 'Belgium',
39 | 'Switzerland',
40 | 'Venezuela',
41 | 'Austria',
42 | 'Mexico',
43 | 'USA',
44 | 'Sweden',
45 | 'Finland',
46 | 'Spain',
47 | 'UK',
48 | 'Italy',
49 | 'Ireland',
50 | 'Portugal',
51 | 'Canada',
52 | 'Poland',
53 | 'Norway',
54 | 'Denmark',
55 | ],
56 | },
57 | },
58 | required: ['CustomerID'],
59 | });
60 |
--------------------------------------------------------------------------------
/spec/integrated/driver/utility.js:
--------------------------------------------------------------------------------
1 | import Promise from 'bluebird';
2 | import _ from 'underscore';
3 | import protocol from './protocol';
4 |
5 | function pause(ms) {
6 | return new Promise((resolve) => {
7 | setTimeout(() => {
8 | resolve();
9 | }, ms);
10 | });
11 | }
12 |
13 | function scroll(selector, xoffset, yoffset) {
14 | return new Promise((resolve, reject) => {
15 | if (!_.isString(selector)) {
16 | yoffset = xoffset;
17 | xoffset = selector;
18 | selector = undefined;
19 | } else if (arguments.length < 3) {
20 | xoffset = 0;
21 | yoffset = 0;
22 | }
23 |
24 | if (!(_.isUndefined(selector) || _.isString(selector)) && _.isNumber(xoffset) && _.isNumber(yoffset)) {
25 | reject(new Error('Input is invalid.'));
26 | }
27 |
28 | if (selector) {
29 | protocol.element.call(this, selector)
30 | .then(($el) => {
31 | let elOffset = $el.offset();
32 | let rectLeft = elOffset.left + xoffset;
33 | let rectTop = elOffset.top + yoffset;
34 |
35 | window.scrollTo(rectLeft, rectTop);
36 | resolve();
37 | })
38 | .catch(reject);
39 | } else {
40 | window.scrollTo(xoffset, yoffset);
41 | resolve();
42 | }
43 | });
44 | }
45 |
46 | export default {
47 | pause,
48 | scroll,
49 | };
50 |
--------------------------------------------------------------------------------
/js/vnext/projection/multiple-selection-resolver.js:
--------------------------------------------------------------------------------
1 | import _ from 'underscore';
2 | import { SelectionResolver } from './selection-resolver.js';
3 |
4 | export class MultipleSelectionResolver extends SelectionResolver {
5 |
6 | selectRow(key) {
7 | return this.patch(({ selected }) => ({
8 | selected: _.union(selected, [key.toString()]),
9 | }));
10 | }
11 |
12 | deselectRow(key) {
13 | return this.patch(({ selected }) => ({
14 | selected: _.without(selected, key.toString()),
15 | }));
16 | }
17 |
18 | // Select all selectalbe items
19 | selectAll() {
20 | return this.patch(({ selectable, selected }) => {
21 | const patch = {};
22 |
23 | patch.selected = _.chain(this.gridView.items.slice())
24 | .filter(selectable)
25 | .map(_.property(this.gridView.primaryKey))
26 | .map(String)
27 | .union(selected)
28 | .value();
29 |
30 | return patch;
31 | });
32 | }
33 |
34 | // Deselect all selectable items
35 | deselectAll() {
36 | return this.patch(({ selectable, selected }) => {
37 | const patch = {};
38 |
39 | patch.selected = _.filter(selected, key => {
40 | const item = this.gridView.itemWithKey(key);
41 |
42 | return !(item && selectable(item));
43 | });
44 |
45 | return patch;
46 | });
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/spec/unit/aggregate-row-spec.js:
--------------------------------------------------------------------------------
1 | var expect = require('chai').expect;
2 | var AggregateRow = require('component/grid/projection/aggregate-row');
3 | var Base = require('component/grid/projection/base');
4 | var Response = require('component/grid/model/response');
5 |
6 | describe('projection AggregateRow', function () {
7 | it('update should run normal', function () {
8 | var model = new AggregateRow({
9 | 'aggregate.top': function (data) {
10 | return [{
11 | name: data.get('value').length,
12 | $metadata: {},
13 | }];
14 | },
15 |
16 | 'aggregate.bottom': function (data) {
17 | return [{
18 | name: data.get('value').length,
19 | $metadata: {},
20 | }];
21 | },
22 | });
23 |
24 | var originalData = new Base();
25 | originalData.data = new Response({
26 | value: [
27 | { name: 'hello', id: 'hello_id' },
28 | { name: 'world', id: 'world_id' },
29 | ],
30 | });
31 |
32 | originalData.pipe(model);
33 | expect(model.data.get('value')[0].$metadata).to.be.eql({ type: 'aggregate' });
34 | expect(model.data.get('value')[0].name).to.be.eql(2);
35 |
36 | expect(model.data.get('value')[3].$metadata).to.be.eql({ type: 'aggregate' });
37 | expect(model.data.get('value')[3].name).to.be.eql(2);
38 | });
39 | });
40 |
--------------------------------------------------------------------------------
/js/vnext/layout/escape.js:
--------------------------------------------------------------------------------
1 | const pugMatch = /["&<>]/;
2 |
3 | function pugEscape(_html) {
4 | const html = String(_html);
5 | const regexResult = pugMatch.exec(html);
6 | if (!regexResult) {
7 | return _html;
8 | }
9 |
10 | let result = '';
11 | let i, lastIndex, escape;
12 | for (i = regexResult.index, lastIndex = 0; i < html.length; i++) {
13 | switch (html.charCodeAt(i)) {
14 | case 34: escape = '"'; break;
15 | case 38: escape = '&'; break;
16 | case 60: escape = '<'; break;
17 | case 62: escape = '>'; break;
18 | default: continue;
19 | }
20 | if (lastIndex !== i) {
21 | result += html.substring(lastIndex, i);
22 | }
23 | lastIndex = i + 1;
24 | result += escape;
25 | }
26 | if (lastIndex !== i) {
27 | return result + html.substring(lastIndex, i);
28 | }
29 |
30 | return result;
31 | }
32 |
33 | export function escapeAttr(attr) {
34 | if (Array.isArray(attr)) {
35 | return attr.map(function (val) {
36 | return escapeAttr(val);
37 | });
38 | } else if (typeof attr === 'object') {
39 | return Object.keys(attr).reduce(function (memo, key) {
40 | memo[key] = escapeAttr(attr[key]);
41 | return memo;
42 | }, {});
43 | } else if (typeof attr === 'string') {
44 | return pugEscape(attr);
45 | }
46 | return attr;
47 | }
48 |
--------------------------------------------------------------------------------
/js/projection/column-template.js:
--------------------------------------------------------------------------------
1 | define([
2 | 'lib/underscore',
3 | 'component/grid/projection/base',
4 | ], function (_, BaseProjection) {
5 | 'use strict';
6 |
7 | var Model = BaseProjection.extend({
8 | defaults: {
9 | 'column.template': {},
10 | },
11 | name: 'column-template',
12 | update: function (options) {
13 | // todo [akamel] when calling a deep update; suppress onchange event based updates
14 | // Model.__super__.update.call(this, options);
15 |
16 | // TODO [imang]: columns: ideally we should not need to read from select.
17 | if (Model.__super__.update.call(this, options)) {
18 | var model = this.src.data;
19 | var colTemplate = this.get('column.template');
20 | var columns = model.get('columns');
21 | _.each(columns, function (item, property) {
22 | var ret = _.clone(item);
23 | var templateValue = colTemplate[property];
24 |
25 | if (_.has(colTemplate, property)) {
26 | ret.$html = _.isFunction(templateValue) ? templateValue(ret) : templateValue;
27 | }
28 |
29 | columns[property] = ret;
30 | });
31 |
32 | this.patch({ columns: columns });
33 | } else {
34 | // todo [akamel] unset our properties only
35 | // this.unset();
36 | }
37 | },
38 | });
39 |
40 | return Model;
41 | });
42 |
--------------------------------------------------------------------------------
/spec/integrated/conf/karma.conf.js:
--------------------------------------------------------------------------------
1 | var _ = require('underscore');
2 | var path = require('path');
3 |
4 | function getWebpackConfig() {
5 | var webpackConfig = require('./webpack.config');
6 |
7 | webpackConfig.module.preLoaders = [
8 | {
9 | test: /\.js$/,
10 | include: path.resolve('./js/'),
11 | loader: 'isparta',
12 | },
13 | ].concat(webpackConfig.module.preLoaders || []);
14 | return webpackConfig;
15 | }
16 |
17 | module.exports = function (config) {
18 | //files fild set in gulpfile.js in the root folder.
19 | config.set({
20 | basePath: '../../../',
21 | frameworks: [
22 | 'mocha',
23 | ],
24 | client: {
25 | mocha: {
26 | reporter: 'html', // change Karma's debug.html to the mocha web reporter
27 | },
28 | },
29 | reporters: ['mocha', 'coverage', 'junit'],
30 | webpack: getWebpackConfig(),
31 | coverageReporter: {
32 | dir: './spec/integrated/coverage',
33 | reporters: [
34 | { type: 'html', subdir: 'report-html' },
35 | { type: 'lcov', subdir: 'report-lcov' },
36 | ],
37 | },
38 | junitReporter: {
39 | outputDir: './spec/integrated/test-results/',
40 | },
41 | browsers: [
42 | 'ChromeNoSandbox',
43 | ],
44 | customLaunchers: {
45 | ChromeNoSandbox: {
46 | base: 'Chrome',
47 | flags: ['--no-sandbox'],
48 | },
49 | },
50 |
51 | });
52 | };
53 |
--------------------------------------------------------------------------------
/demos/cell-view/index.js:
--------------------------------------------------------------------------------
1 | var pgrid = require('projection-grid');
2 | var Customer = require('./js-data-resource').default;
3 | var keyHeaderTemplate = require('./key-column-header.jade');
4 | var companyNameTemplate = require('./company-name.jade');
5 | var MapView = require('./map-view').default;
6 |
7 | require('bootstrap-webpack');
8 | require('style!css!./index.css');
9 |
10 | var grid = pgrid.factory().create({
11 | el: '.grid-root',
12 | dataSource: {
13 | type: 'js-data',
14 | resource: Customer,
15 | schema: { key: 'CustomerID' },
16 | },
17 | selectable: true,
18 | columns: [
19 | {
20 | name: 'CustomerID',
21 | title: 'Customer ID',
22 | sortable: true,
23 | locked: true,
24 | headerTemplate: keyHeaderTemplate,
25 | },
26 | {
27 | name: 'CompanyName',
28 | title: 'Company Name',
29 | sortable: true,
30 | template: companyNameTemplate,
31 | attributes: {
32 | class: 'company-name-cell',
33 | },
34 | headerAttributes: {
35 | class: 'company-name-header',
36 | },
37 | },
38 | {
39 | name: 'City',
40 | title: 'City',
41 | sortable: true,
42 | View: MapView,
43 | },
44 | {
45 | name: 'Contact',
46 | field: 'ContactName',
47 | value: item => `${item.ContactName} (${item.ContactTitle})`,
48 | sortable: true,
49 | },
50 | ],
51 | });
52 |
53 | grid.gridView.render({ fetch: true });
54 |
--------------------------------------------------------------------------------
/js/projection/aggregate-row.js:
--------------------------------------------------------------------------------
1 | define([
2 | 'lib/underscore',
3 | 'lib/backbone',
4 | 'component/grid/projection/base',
5 | ], function (_, Backbone, BaseProjection) {
6 | var createRows = function (fn, data) {
7 | var rows = fn(data);
8 |
9 | _.each(rows, function (row) {
10 | row.$metadata = _.extend({}, row.$metadata, { type: 'aggregate' });
11 | });
12 |
13 | return rows;
14 | };
15 |
16 | var Model = BaseProjection.extend({
17 |
18 | defaults: {
19 | 'aggregate.top': null,
20 | 'aggregate.bottom': null,
21 | },
22 |
23 | name: 'aggregate-row',
24 |
25 | update: function (options) {
26 | var value, topFn, rowTop, bottomFn, rowBottom;
27 |
28 | if (Model.__super__.update.call(this, options)) {
29 | value = this.src.data.get('value');
30 |
31 | if (value) {
32 | topFn = this.get('aggregate.top');
33 | bottomFn = this.get('aggregate.bottom');
34 | rowTop = _.isFunction(topFn) ? createRows(topFn, this.src.data) : null;
35 | rowBottom = _.isFunction(bottomFn) ? createRows(bottomFn, this.src.data) : null;
36 |
37 | if (rowTop) {
38 | value = _.flatten(rowTop).concat(value);
39 | }
40 |
41 | if (rowBottom) {
42 | value = value.concat(_.flatten(rowBottom));
43 | }
44 |
45 | this.patch({
46 | value: value,
47 | });
48 | }
49 | }
50 | },
51 |
52 | });
53 |
54 | return Model;
55 | });
56 |
--------------------------------------------------------------------------------
/demos/radio-selection/index.js:
--------------------------------------------------------------------------------
1 | var pgrid = require('projection-grid');
2 | var Customer = require('./js-data-resource');
3 | var keyHeaderTemplate = require('./key-column-header.jade');
4 | var companyNameTemplate = require('./company-name.jade');
5 | require('bootstrap-webpack');
6 |
7 | var grid = pgrid.factory().create({
8 | el: '.grid-root',
9 | dataSource: {
10 | type: 'js-data',
11 | resource: Customer,
12 | },
13 | selectable: 'single',
14 | hideHeaders: true,
15 | pageable: {
16 | pageSize: 10,
17 | pageSizes: [5, 10, 15, 20],
18 | },
19 | columns: [
20 | {
21 | name: 'CustomerID',
22 | title: 'Customer ID',
23 | sortable: true,
24 | locked: true,
25 | headerTemplate: keyHeaderTemplate,
26 | },
27 | {
28 | name: 'CompanyName',
29 | title: 'Company Name',
30 | sortable: true,
31 | template: companyNameTemplate,
32 | attributes: {
33 | class: 'company-name-cell',
34 | },
35 | headerAttributes: {
36 | class: 'company-name-header',
37 | },
38 | },
39 | {
40 | name: 'City',
41 | title: 'City',
42 | sortable: true,
43 | },
44 | {
45 | name: 'Contact',
46 | field: 'ContactName',
47 | value: item => `${item.ContactName} (${item.ContactTitle})`,
48 | sortable: true,
49 | },
50 | ],
51 | pagerView: {
52 | el: '.pager-root',
53 | availablePageSizes: [5, 10, 15, 20],
54 | },
55 | });
56 |
57 | grid.gridView.render({ fetch: true });
58 |
--------------------------------------------------------------------------------
/spec/unit/jsdata-spec.js:
--------------------------------------------------------------------------------
1 | var chai = require('chai');
2 | var sinon = require('sinon');
3 | var Promise = require('bluebird');
4 | var JSDataProjection = require('component/grid/projection/jsdata');
5 | var Base = require('component/grid/projection/base');
6 |
7 | chai.use(require('sinon-chai'));
8 | var expect = chai.expect;
9 |
10 | describe('JSData Projection', function () {
11 | var MockEntity = function () {
12 | this.findAll = sinon.spy(function () {
13 | return Promise.resolve([]);
14 | });
15 | };
16 |
17 | var entity = null;
18 | var projection = null;
19 |
20 | beforeEach(function () {
21 | entity = new MockEntity();
22 | projection = new JSDataProjection({
23 | 'jsdata.entity': entity,
24 | 'jsdata.options': {},
25 | 'jsdata.skip': 10,
26 | 'jsdata.take': 10,
27 | 'jsdata.filter': {},
28 | 'jsdata.orderby': [],
29 | 'jsdata.select': [],
30 | });
31 | });
32 |
33 | it('should be a projection', function () {
34 | expect(projection).to.be.instanceof(Base);
35 | });
36 |
37 | describe('update', function () {
38 | it('should call findAll of the entity and finish', function (done) {
39 | var onUpdateBegin = sinon.spy();
40 | projection.on('update:beginning', onUpdateBegin);
41 | projection.on('update:finished', function () {
42 | expect(onUpdateBegin).to.have.been.called;
43 | done();
44 | });
45 | projection.update();
46 | expect(entity.findAll).to.have.been.called;
47 | });
48 | });
49 | });
50 |
--------------------------------------------------------------------------------
/demos/cell-view/js-data-resource.js:
--------------------------------------------------------------------------------
1 | import _ from 'underscore';
2 | import JSData from 'js-data';
3 | import DSHttpAdapter from 'js-data-http';
4 |
5 | class ODataAdapter extends DSHttpAdapter {
6 |
7 | constructor(options) {
8 | super(_.extend(options, {
9 | queryTransform(definition, params) {
10 | const query = { $count: true };
11 |
12 | _.has(params, 'offset') && (query.$skip = params.offset);
13 | _.has(params, 'limit') && (query.$top = params.limit);
14 | _.has(params, 'orderBy') && (query.$orderby = params.orderBy.map(function (item) {
15 | return item[0] + ' ' + item[1].toLowerCase();
16 | }).join(','));
17 |
18 | return query;
19 | },
20 | }));
21 | }
22 |
23 | findAll(definition, params, options) {
24 | let totalCount = 0;
25 |
26 | options.afterFindAll = (resource, data) => {
27 | totalCount = data['@odata.count'];
28 | return data.value;
29 | };
30 | options.afterInject = (resource, instances) => {
31 | Object.defineProperty(instances, 'totalCount', { value: totalCount });
32 | };
33 |
34 | return super.findAll(definition, params, options);
35 | }
36 | }
37 |
38 | JSData.DSUtils.Promise = require('bluebird');
39 |
40 | const store = new JSData.DS();
41 | store.registerAdapter('odata', new ODataAdapter({
42 | basePath: 'http://services.odata.org/V4/Northwind/Northwind.svc/',
43 | }), { default: true });
44 |
45 | export default store.defineResource({
46 | name: 'Customers',
47 | idAttribute: 'CustomerID',
48 | });
49 |
--------------------------------------------------------------------------------
/demos/factory/js-data-resource.js:
--------------------------------------------------------------------------------
1 | var _ = require('underscore');
2 | var JSData = require('js-data');
3 | var DSHttpAdapter = require('js-data-http');
4 |
5 | class ODataAdapter extends DSHttpAdapter {
6 |
7 | constructor(options) {
8 | super(_.extend(options, {
9 | queryTransform(definition, params) {
10 | var query = { $count: true };
11 |
12 | _.has(params, 'offset') && (query.$skip = params.offset);
13 | _.has(params, 'limit') && (query.$top = params.limit);
14 | _.has(params, 'orderBy') && (query.$orderby = params.orderBy.map(function (item) {
15 | return item[0] + ' ' + item[1].toLowerCase();
16 | }).join(','));
17 |
18 | return query;
19 | },
20 | }));
21 | }
22 |
23 | findAll(definition, params, options) {
24 | let totalCount = 0;
25 |
26 | options.afterFindAll = (resource, data) => {
27 | totalCount = data['@odata.count'];
28 | return data.value;
29 | };
30 | options.afterInject = (resource, instances) => {
31 | Object.defineProperty(instances, 'totalCount', { value: totalCount });
32 | };
33 |
34 | return super.findAll(definition, params, options);
35 | }
36 | }
37 |
38 | JSData.DSUtils.Promise = require('bluebird');
39 |
40 | var store = new JSData.DS();
41 | store.registerAdapter('odata', new ODataAdapter({
42 | basePath: 'http://services.odata.org/V4/Northwind/Northwind.svc/',
43 | }), { default: true });
44 |
45 | module.exports = store.defineResource({
46 | name: 'Customers',
47 | idAttribute: 'CustomerID',
48 | });
49 |
--------------------------------------------------------------------------------
/demos/radio-selection/js-data-resource.js:
--------------------------------------------------------------------------------
1 | var _ = require('underscore');
2 | var JSData = require('js-data');
3 | var DSHttpAdapter = require('js-data-http');
4 |
5 | class ODataAdapter extends DSHttpAdapter {
6 |
7 | constructor(options) {
8 | super(_.extend(options, {
9 | queryTransform(definition, params) {
10 | var query = { $count: true };
11 |
12 | _.has(params, 'offset') && (query.$skip = params.offset);
13 | _.has(params, 'limit') && (query.$top = params.limit);
14 | _.has(params, 'orderBy') && (query.$orderby = params.orderBy.map(function (item) {
15 | return item[0] + ' ' + item[1].toLowerCase();
16 | }).join(','));
17 |
18 | return query;
19 | },
20 | }));
21 | }
22 |
23 | findAll(definition, params, options) {
24 | let totalCount = 0;
25 |
26 | options.afterFindAll = (resource, data) => {
27 | totalCount = data['@odata.count'];
28 | return data.value;
29 | };
30 | options.afterInject = (resource, instances) => {
31 | Object.defineProperty(instances, 'totalCount', { value: totalCount });
32 | };
33 |
34 | return super.findAll(definition, params, options);
35 | }
36 | }
37 |
38 | JSData.DSUtils.Promise = require('bluebird');
39 |
40 | var store = new JSData.DS();
41 | store.registerAdapter('odata', new ODataAdapter({
42 | basePath: 'http://services.odata.org/V4/Northwind/Northwind.svc/',
43 | }), { default: true });
44 |
45 | module.exports = store.defineResource({
46 | name: 'Customers',
47 | idAttribute: 'CustomerID',
48 | });
49 |
--------------------------------------------------------------------------------
/demos/vnext/events/index.js:
--------------------------------------------------------------------------------
1 | import $ from 'jquery';
2 | import _ from 'underscore';
3 | import Backbone from 'backbone';
4 | import pgrid from '../../../js';
5 |
6 | import './index.less';
7 | import 'bootstrap-webpack';
8 |
9 | window.gridView = pgrid.factory({ vnext: true }).create({
10 | el: '.grid-container',
11 | tableClasses: ['table', 'table-bordered'],
12 | scrolling: {
13 | virtualized: true,
14 | header: {
15 | type: 'sticky',
16 | offset() {
17 | return $('.navbar-container').height();
18 | },
19 | },
20 | },
21 | dataSource: {
22 | type: 'odata',
23 | url: 'http://services.odata.org/V4/Northwind/Northwind.svc/Orders',
24 | primaryKey: 'OrderID',
25 | },
26 | selection: true,
27 | }).gridView.render();
28 |
29 | gridView.on('willReload', () => {
30 | console.log('Will reload');
31 | });
32 |
33 | gridView.on('didReload', () => {
34 | console.log('Did reload');
35 | });
36 |
37 | gridView.on('willRedraw', () => {
38 | console.log('Will redraw');
39 | });
40 |
41 | gridView.on('didRedraw', () => {
42 | console.log('Did redraw');
43 | });
44 |
45 | gridView.on('willUpdate', (changes) => {
46 | console.log(`Will update with changes ${_.keys(changes)}`);
47 | });
48 |
49 | gridView.on('didUpdate', (changes) => {
50 | console.log(`Did update with changes ${_.keys(changes)}`);
51 | });
52 |
53 | gridView.on('willSelect', (selections) => {
54 | console.log(`Will select ${selections[0]}`);
55 | });
56 |
57 | gridView.on('didSelect', (selections) => {
58 | console.log(`Did select ${selections[0]}`);
59 | });
60 |
61 |
--------------------------------------------------------------------------------
/demos/cell-view/map-view.js:
--------------------------------------------------------------------------------
1 | import $ from 'jquery';
2 | import Backbone from 'backbone';
3 | import template from './map-view.jade';
4 |
5 | var mapKey = 'Aitu1GXEVDLOMeRerogqYje1eqGGfi_anQ0uPmUkIqnCgwm72G2zdJj9a-sDS0Qe';
6 | var map = null;
7 |
8 | function showCity(cityName) {
9 | $.getJSON('http://dev.virtualearth.net/REST/v1/Locations/?q=' + cityName + '&key=' + mapKey + '&jsonp=?', function (data) {
10 | var coordinates = data.resourceSets[0].resources[0].point.coordinates;
11 | var location = new window.Microsoft.Maps.Location(coordinates[0], coordinates[1]);
12 | map.setView({
13 | center: location,
14 | zoom: 10,
15 | });
16 | });
17 | }
18 |
19 | function hideMap() {
20 | $('.container-map').hide();
21 | }
22 |
23 | function showMap(cityName) {
24 | $('.container-map').show();
25 | if (!map) {
26 | map = new window.Microsoft.Maps.Map(document.getElementById('myMap'), {
27 | credentials: mapKey,
28 | });
29 | }
30 | showCity(cityName);
31 |
32 | function dismissOnClick(e) {
33 | if (!$(e.target).closest('#myMap').length) {
34 | hideMap();
35 | $('body').off('click', dismissOnClick);
36 | }
37 | }
38 | $('body').on('click', dismissOnClick);
39 | }
40 |
41 | hideMap();
42 |
43 | export default class MapView extends Backbone.View {
44 | events() {
45 | return {
46 | 'click button': e => {
47 | showMap(this.model.City);
48 | e.stopImmediatePropagation();
49 | },
50 | };
51 | }
52 |
53 | initialize() {
54 | }
55 |
56 | render() {
57 | this.$el.html(template(this.model));
58 | return this;
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/spec/integrated/conf/webpack.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 | var _ = require('underscore');
3 | var webpackAlias = require('../../../webpack.alias');
4 | _.extend(webpackAlias, {
5 | 'sinon': 'sinon/pkg/sinon.js',
6 | 'data' : path.resolve('./spec/integrated/data/'),
7 | 'driver' : path.resolve('./spec/integrated/driver/'),
8 | 'template' : path.resolve('./spec/integrated/template/'),
9 | 'util' : path.resolve('./spec/util/'),
10 | 'styles' : path.resolve('./spec/integrated/styles/'),
11 | });
12 |
13 | module.exports = {
14 | output: {
15 | path: path.resolve('./dist'),
16 | filename: 'component-test.js',
17 | devtoolModuleFilenameTemplate: function (info) {
18 | if (path.isAbsolute(info.absoluteResourcePath)) {
19 | return 'webpack-src:///component-test/' + path.relative('.', info.absoluteResourcePath).replace(/\\/g, '/');
20 | }
21 | return info.absoluteResourcePath;
22 | },
23 | },
24 | resolve: {
25 | alias: webpackAlias,
26 | },
27 | module: {
28 | loaders: [
29 | {
30 | test: /sinon\.js$/,
31 | loader: 'imports?define=>false,require=>false',
32 | },
33 | // jade
34 | { test: /\.jade$/, loader: 'jade-loader' },
35 | // jade-end
36 | // es2015
37 | { test: /\.js$/, exclude: /\bnode_modules\b/, loader: 'babel-loader' },
38 | // es2015-end
39 | { test: /\.less$/, loader: 'style!css!less' },
40 | { test: /\.css$/, loader: 'style!css!less' },
41 | { test: /\.json$/, loader: 'json' },
42 | ],
43 |
44 | },
45 | babel: { presets: ['es2015'] },
46 | devtool: 'inline-source-map',
47 | };
48 |
49 |
--------------------------------------------------------------------------------
/js/vnext/projection/buffer.js:
--------------------------------------------------------------------------------
1 | import _ from 'underscore';
2 |
3 | function updateItemState(gridView, item, state) {
4 | const buffer = gridView.get('buffer');
5 | const changed = {};
6 | const key = _.result(item, gridView.primaryKey);
7 |
8 | changed[key] = {
9 | item,
10 | state,
11 | };
12 | _.defaults(changed, buffer.changed);
13 |
14 | gridView.set({
15 | buffer: _.defaults({ changed }, buffer),
16 | });
17 | }
18 |
19 | export const buffer = {
20 | name: 'buffer',
21 | handler(state, buffer) {
22 | const { primaryKey, uniqueId, update } = state;
23 |
24 | if (buffer.uniqueId !== uniqueId) {
25 | buffer.uniqueId = uniqueId;
26 | buffer.changed = {};
27 | }
28 |
29 | const items = {
30 | length: state.items.length,
31 | slice: (begin = 0, end = state.items.length) => {
32 | return state.items.slice(begin, end).map(item => {
33 | const key = item[primaryKey];
34 |
35 | if (_.has(buffer.changed, key)) {
36 | return buffer.changed[key].item;
37 | }
38 | return item;
39 | });
40 | },
41 | };
42 |
43 | const onCommit = item => {
44 | if (item) {
45 | updateItemState(this, item, 'committed');
46 | }
47 | };
48 |
49 | const onEdit = item => {
50 | if (item) {
51 | updateItemState(this, item, 'changed');
52 | if (_.isFunction(update)) {
53 | update(item).then(onCommit);
54 | }
55 | }
56 | };
57 |
58 | const events = _.defaults({ didEdit: onEdit }, state.events);
59 |
60 | return _.defaults({ items, events }, state);
61 | },
62 | defaults: {},
63 | };
64 |
65 |
--------------------------------------------------------------------------------
/js/vnext/data-source/odata.js:
--------------------------------------------------------------------------------
1 | import _ from 'underscore';
2 | import $ from 'jquery';
3 | import Promise from 'bluebird';
4 | import { DataSource } from './base.js';
5 |
6 | function translateOrderBy(dataSource, orderByParams) {
7 | return _.chain(dataSource.normalizeOrderBy(orderByParams))
8 | .map(([key, direction]) => `${key} ${direction > 0 ? 'asc' : 'desc'}`)
9 | .join(',')
10 | .value();
11 | }
12 |
13 | function translateParams(dataSource, params) {
14 | return _.chain(params)
15 | .omit('skip', 'take', 'filter', 'orderby')
16 | .extend(_.pick({
17 | $skip: params.skip,
18 | $top: params.take,
19 | $filter: params.filter,
20 | $orderby: translateOrderBy(dataSource, params.orderby),
21 | $count: true,
22 | }, Boolean))
23 | .value();
24 | }
25 |
26 | /**
27 | * Data source from an OData service.
28 | * @class ODataDataSource
29 | * @param {string} url
30 | * The root URL of the OData entity set.
31 | * @param {string} primaryKey
32 | * The primary key of the entity set.
33 | */
34 | export class ODataDataSource extends DataSource {
35 | constructor(url, primaryKey) {
36 | super(primaryKey);
37 | this._url = url;
38 | }
39 |
40 | query(params) {
41 | return new Promise((resolve, reject) => {
42 | $.getJSON(this._url, translateParams(this, params))
43 | .success(resolve)
44 | .fail((jqXHR, textStatus, errorThrown) => {
45 | reject(new Error(errorThrown));
46 | });
47 | }).then(data => ({
48 | items: data.value || [],
49 | totalCount: data['@odata.count'] || 0,
50 | }));
51 | }
52 |
53 | get url() {
54 | return this._url;
55 | }
56 | }
57 |
58 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | var _ = require('underscore');
2 | var path = require('path');
3 | var pkg = require('./package');
4 |
5 | var webpackAlias = pkg.webpackAlias || {};
6 |
7 | try {
8 | webpackAlias = require('./webpack.alias');
9 | } catch (e) { }
10 |
11 | function getExternals() {
12 | var deps = _.keys(pkg.peerDependencies);
13 | var externals = _.object(deps, deps);
14 |
15 | return _.reduce(_.pairs(webpackAlias), function (exts, pair) {
16 | if (_.has(externals, pair[1])) {
17 | exts[pair[0]] = pair[1];
18 | }
19 | return exts;
20 | }, externals);
21 | }
22 |
23 | module.exports = {
24 | entry: path.resolve('./js/index.js'),
25 | output: {
26 | path: path.join(__dirname, 'dist'),
27 | filename: 'projection-grid.js',
28 | library: 'projection-grid',
29 | libraryTarget: 'umd',
30 | umdNamedDefine: false,
31 | devtoolModuleFilenameTemplate: function (info) {
32 | if (path.isAbsolute(info.absoluteResourcePath)) {
33 | return 'webpack-src:///projection-grid/' + path.relative('.', info.absoluteResourcePath).replace(/\\/g, '/');
34 | }
35 | return info.absoluteResourcePath;
36 | },
37 | },
38 | module: {
39 | loaders: [
40 | // jade
41 | { test: /\.jade$/, loader: 'babel!jade' },
42 | // jade-end
43 | // es2015
44 | { test: /\.js$/, exclude: /\bnode_modules\b/, loader: 'babel-loader' },
45 | // es2015-end
46 | { test: /\.less$/, loader: 'style!css!less' },
47 | { test: /\.json$/, loader: 'json' },
48 | ],
49 | },
50 | babel: { presets: ['es2015'] },
51 | externals: [getExternals()],
52 | resolve: { alias: webpackAlias },
53 | devtool: 'source-map',
54 | };
55 |
56 |
--------------------------------------------------------------------------------
/js/layout/template/table.mobile.jade:
--------------------------------------------------------------------------------
1 | mixin th(column, cls)
2 | - var cls = cls || [];
3 | if column.header
4 | - cls.push(column.header['class']);
5 | if column.sortable
6 | - cls.push('sortable');
7 | if column.$orderby
8 | - cls.push('orderby');
9 |
10 | - cls = cls.join(' ');
11 | th(class=cls)
12 | if column.$orderby
13 | if column.$orderby.dir > 0
14 | span.grid-asc
15 | else
16 | span.grid-des
17 | if (column && column.$html)
18 | != column.$html
19 | else
20 | = (typeof column.$text != 'undefined')? column.$text : (column.property || column)
21 |
22 | mixin td(row, column, cls)
23 | - var cls = cls || [];
24 | if column.cell
25 | - cls.push(column.cell['class']);
26 | - cls = cls.join(' ');
27 | td(class=cls)
28 | - var res = row[column.property]
29 | if (res && res.$html)
30 | != res.$html
31 | else
32 | = res
33 |
34 | .table-responsive
35 | table.table.table-hover.grid
36 | thead
37 | tr
38 | each column, index in locals['columns.group.lock'][true] || []
39 | +th(column, ['lock'])
40 | each column, index in locals['columns.group.lock'][false] || []
41 | +th(column)
42 | if ((locals['columns.group.lock'][false] || []).length ==- 0)
43 | th.fillter
44 | tbody
45 | each row, i in value
46 | tr
47 | each column, j in locals['columns.group.lock'][true] || []
48 | +td(row, column, ['lock'])
49 | each column, j in locals['columns.group.lock'][false] || []
50 | +td(row, column)
51 | if ((locals['columns.group.lock'][false] || []).length ==- 0)
52 | td.fillterr
53 |
--------------------------------------------------------------------------------
/demos/full/index.jade:
--------------------------------------------------------------------------------
1 | doctype html
2 | html(lang="en")
3 | head
4 | title= title
5 | body
6 | .container
7 | .row
8 | .panel.panel-info
9 | .panel-heading Commands you can run in the console
10 | .panel-body
11 | pre.
12 | -- skip and take only work when pagination control is not connected [for now]
13 | // window.grid.projection.set({ skip : 20, take : 100, filter : function(i){ return i.age < 5; } });
14 | -- select is being overriden by the column shifter's options
15 | // window.grid.projection.set({ select : ['age', 'age', 'name', 'index', 'name', 'age', 'index', 'age'] });
16 | -- call to column.take should work fine on this page
17 | // window.grid.projection.set({ 'column.take' : 4, 'column.skip' : 3 });
18 | // --- only used to change column settings / dimensions; window.grid.set('columns', [{ property : 'name', title : 'My Name' }, { property : 'age', title : 'Cat Age' }]);
19 | .panel.panel-info
20 | .panel-heading Notes
21 | .panel-body
22 | ul
23 | li Column 'ContactName' is sortable
24 | li Shift Columns right and left
25 | li Use Pager to navigate OData Source
26 | .row
27 | #grid_toolbar_host_a
28 | #pagination_host_a
29 | #grid_host_a
30 | #pager
31 |
32 | .row
33 | .panel.panel-info
34 | .panel-heading Footer Space
35 | .panel-body
36 | ul
37 | li Used to make sure that grid virtualization renders correctly at the end of page
38 | script(type='text/javascript', src=bundle)
39 |
--------------------------------------------------------------------------------
/js/vnext/factory/data-source-plugin.js:
--------------------------------------------------------------------------------
1 | import _ from 'underscore';
2 | import {
3 | DataSource,
4 | ODataDataSource,
5 | JSDataDataSource,
6 | MemoryDataSource,
7 | } from '../data-source';
8 |
9 | /**
10 | * @typedef DataSourceConfig
11 | * @type {DataSource|BuiltinDataSourceConfig}
12 | */
13 |
14 | /**
15 | * @typedef BuiltinDataSourceConfig
16 | * @type {Object}
17 | * @property {string} type
18 | * The builtin data source type, available values are 'odata', 'js-data'
19 | * and 'memory'.
20 | * @property {string?} primaryKey
21 | * The primaryKey for the data source. Required by 'odata' and 'memory'
22 | * data sources.
23 | * @property {JSDataResource?} entity
24 | * The JSDataResource representing the entity set. Required by 'js-data'
25 | * data source.
26 | * @property {string?} url
27 | * The URL for the entity set. Required by 'odata' data source.
28 | * @property {Object[]?} data
29 | * The in-memory data set. Required by 'memory' data source.
30 | *
31 | */
32 |
33 | export default definePlugin => definePlugin('dataSource', ['config'], config => {
34 | const dataSource = _.result(config, 'dataSource', {});
35 |
36 | if (dataSource instanceof DataSource) {
37 | return dataSource;
38 | }
39 |
40 | const { type, primaryKey } = dataSource;
41 |
42 | if (type === 'memory') {
43 | return new MemoryDataSource(dataSource.data, primaryKey);
44 | }
45 |
46 | if (type === 'odata') {
47 | return new ODataDataSource(dataSource.url, primaryKey);
48 | }
49 |
50 | if (_.contains(['jsdata', 'js-data'], type)) {
51 | return new JSDataDataSource(dataSource.entity, dataSource.options);
52 | }
53 |
54 | throw new Error('Unsupported data source type');
55 | });
56 |
57 |
--------------------------------------------------------------------------------
/js/projection/memory-sort.js:
--------------------------------------------------------------------------------
1 | define([
2 | 'lib/underscore',
3 | 'lib/backbone',
4 | 'component/grid/projection/base',
5 | 'component/grid/schema/properties',
6 | 'component/grid/model/response',
7 | ], function (_, Backbone, BaseProjection, schemaProperties /* , Response */) {
8 | var Model = BaseProjection.extend({
9 | defaults: {
10 | 'orderby': [],
11 | 'select': [],
12 | 'column.sortable': {},
13 | },
14 | name: 'memory-sort',
15 | update: function (options) {
16 | if (Model.__super__.update.call(this, options)) {
17 | var model = this.src.data;
18 | var order = _.chain(this.get('orderby'))
19 | .first()
20 | .pairs()
21 | .first()
22 | .value();
23 |
24 | var orderKey = _.first(order);
25 | var orderDir = _.last(order);
26 |
27 | var value = model.get('value');
28 |
29 | var sortFunc = this.get('column.sortable')[orderKey];
30 |
31 | if (orderKey) {
32 | if (_.isFunction(sortFunc)) {
33 | value = sortFunc(value, orderDir);
34 | } else {
35 | value = _.sortBy(value, orderKey);
36 | if (orderDir === -1) {
37 | value = value.reverse();
38 | }
39 | }
40 | }
41 |
42 | var select = this.get('select');
43 | if (!_.size(select)) {
44 | select = schemaProperties.from(value);
45 | }
46 |
47 | this.patch({
48 | value: value,
49 | select: select,
50 | });
51 | } else {
52 | // TODO [akamel] unset our properties only
53 | // this.unset();
54 | }
55 | },
56 | });
57 |
58 | return Model;
59 | });
60 |
--------------------------------------------------------------------------------
/js/projection/property-template.js:
--------------------------------------------------------------------------------
1 | define([
2 | 'lib/underscore',
3 | 'lib/backbone',
4 | 'component/grid/projection/base',
5 | 'component/grid/schema/properties',
6 | 'component/grid/model/response',
7 | ], function (_, Backbone, BaseProjection /* , schemaProperties, Response */) {
8 | var Model = BaseProjection.extend({
9 | defaults: {
10 | 'property.template': {},
11 | },
12 | name: 'property-template',
13 | update: function (options) {
14 | // Model.__super__.update.call(this, options);
15 |
16 | if (Model.__super__.update.call(this, options)) {
17 | var model = this.src.data;
18 | var opt = this.get('property.template');
19 | var value = _.map(model.get('value'), function (item) {
20 | var ret = _.clone(item);
21 |
22 | _.each(opt, function (value, key) {
23 | if (_.has(ret, key)) {
24 | var res = value({
25 | model: item,
26 | property: key,
27 | });
28 |
29 | if (!_.isObject(ret[key])) {
30 | var obj = new Object(ret[key]); // eslint-disable-line
31 |
32 | if (_.isUndefined(ret[key])) {
33 | obj.$undefined = true;
34 | }
35 |
36 | if (_.isNull(ret[key])) {
37 | obj.$null = true;
38 | }
39 |
40 | ret[key] = obj;
41 | }
42 |
43 | ret[key].$html = res;
44 | }
45 | });
46 |
47 | return ret;
48 | });
49 |
50 | this.patch({ value: value });
51 | } else {
52 | // todo [akamel] unset our properties only
53 | // this.unset();
54 | }
55 | },
56 | });
57 |
58 | return Model;
59 | });
60 |
--------------------------------------------------------------------------------
/demos/vnext/pagination/index.js:
--------------------------------------------------------------------------------
1 | import Backbone from 'backbone';
2 | import pgrid from '../../../js';
3 | import pagerViewPlugin from './pager-view-plugin.js';
4 |
5 | import './index.less';
6 | import 'bootstrap-webpack';
7 |
8 | class CustomView extends Backbone.View {
9 | events() {
10 | return {
11 | 'click h2': () => console.log('click'),
12 | };
13 | }
14 | render() {
15 | this.$el.html('Custom View
');
16 | return this;
17 | }
18 | }
19 |
20 | window.customView = new CustomView().render();
21 |
22 | window.gridView = pgrid.factory({ vnext: true }).use(pagerViewPlugin).create({
23 | el: '.container-window-viewport',
24 | viewport: '.container-window-viewport',
25 | stickyHeader: true,
26 | virtualized: true,
27 | dataSource: {
28 | type: 'odata',
29 | url: 'http://services.odata.org/V4/Northwind/Northwind.svc/Orders',
30 | primaryKey: 'OrderID',
31 | },
32 | selection: true,
33 | columns: [{
34 | name: 'Group 0',
35 | html: 'Group 0',
36 | columns: [{
37 | name: 'CustomerID',
38 | sortable: true,
39 | },{
40 | name: 'OrderID',
41 | sortable: -1,
42 | }, {
43 | name: 'ShipAddress',
44 | property: 'ShipAddress/length',
45 | title: 'Ship Address Length',
46 | width: 150,
47 | sortable: 'length(ShipAddress)',
48 | }, {
49 | name: 'Destination',
50 | property: item => `${item.ShipCountry} / ${item.ShipCity}`,
51 | }],
52 | },{
53 | name: 'ShipCity',
54 | sortable: true,
55 | }],
56 | events: {
57 | 'click th.column-header': (e) => console.log(e.target),
58 | },
59 | pagerView: {
60 | el: '.container-window-pagination',
61 | availablePageSizes: [5,10,20],
62 | pageSize: 20,
63 | },
64 | }).gridView.render();
65 |
--------------------------------------------------------------------------------
/js/factory/grid-factory.js:
--------------------------------------------------------------------------------
1 | import _ from 'underscore';
2 | import renderersPlugin from './renderers-plugin';
3 | import projectionPlugin from './projection-plugin';
4 | import gridViewPlugin from './grid-view-plugin';
5 |
6 | import dataSourceVNextPlugin from '../vnext/factory/data-source-plugin.js';
7 | import gridViewVNextPlugin from '../vnext/factory/grid-view-plugin.js';
8 |
9 | const configPlugin = definePlugin => definePlugin('config', [], function () {
10 | return this.config;
11 | });
12 |
13 | class GridFactory {
14 | constructor({
15 | vnext = false,
16 | } = {}) {
17 | this.pluginIndex = {};
18 | this.plugins = [];
19 | if (vnext) {
20 | this
21 | .use(configPlugin)
22 | .use(dataSourceVNextPlugin)
23 | .use(gridViewVNextPlugin);
24 | } else {
25 | this
26 | .use(configPlugin)
27 | .use(projectionPlugin)
28 | .use(renderersPlugin)
29 | .use(gridViewPlugin);
30 | }
31 | }
32 |
33 | definePlugin(name, deps, callback) {
34 | const plugin = { name, deps, callback };
35 |
36 | this.pluginIndex[name] = plugin;
37 | this.plugins.push(plugin);
38 | _.each(deps, dep => {
39 | if (!_.has(this.pluginIndex, dep)) {
40 | throw new Error(`unresolved plugin dependency ${name} -> ${dep}`);
41 | }
42 | });
43 | }
44 |
45 | use(callback) {
46 | callback(this.definePlugin.bind(this));
47 | return this;
48 | }
49 |
50 | create(config) {
51 | return _.reduce(
52 | this.plugins,
53 | (result, { name, deps, callback }) =>
54 | _.extend(result, {
55 | [name]: callback.apply(result, _.map(deps, dep => result[dep])),
56 | }),
57 | { config }
58 | );
59 | }
60 |
61 | }
62 |
63 | export default function (options) {
64 | return new GridFactory(options);
65 | }
66 |
--------------------------------------------------------------------------------
/js/vnext/data-source/js-data.js:
--------------------------------------------------------------------------------
1 | import _ from 'underscore';
2 | import { DataSource } from './base.js';
3 |
4 | function translateOrderBy(dataSource, orderByParams) {
5 | const orderBy = _.chain(dataSource.normalizeOrderBy(orderByParams))
6 | .map(([key, direction]) => [
7 | key,
8 | direction > 0 ? 'ASC' : 'DESC',
9 | ])
10 | .value();
11 |
12 | return _.isEmpty(orderBy) ? null : orderBy;
13 | }
14 |
15 | function translateParams(dataSource, params) {
16 | return _.chain(params)
17 | .omit('skip', 'take', 'filter', 'orderby')
18 | .extend(_.pick({
19 | offset: params.skip,
20 | limit: params.take,
21 | where: params.filter,
22 | orderBy: translateOrderBy(dataSource, params.orderby),
23 | }, Boolean))
24 | .value();
25 | }
26 |
27 | /**
28 | * Data source from a JSData resource
29 | * @class JSDataDataSource
30 | * @param {JSDataResource} resource
31 | * The JSData resource representing the entity set.
32 | * @param {Object} options
33 | * The query options. You can use it add JSData life cycle hooks.
34 | */
35 | export class JSDataDataSource extends DataSource {
36 | constructor(resource, options = {}) {
37 | super(resource.idAttribute);
38 | this._resource = resource;
39 | this._options = options;
40 | }
41 |
42 | query(params) {
43 | const options = _.defaults({}, params.options, this._options);
44 |
45 | return this._resource
46 | .findAll(translateParams(this, _.omit(params, 'options')), options)
47 | .then(data => {
48 | if (_.isArray(data)) {
49 | return ({
50 | items: data.slice(),
51 | totalCount: data.totalCount || 0,
52 | });
53 | } else {
54 | throw data; // propagate error out
55 | }
56 | });
57 | }
58 |
59 | get resource() {
60 | return this._resource;
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/spec/integrated/non-vnext-spec/page-spec.js:
--------------------------------------------------------------------------------
1 | import $ from 'jquery';
2 | import _ from 'underscore';
3 | import pGrid from 'component/grid';
4 | import chai from 'chai';
5 | import util from 'util';
6 | import driver from 'driver';
7 | import peopleData from 'data/people.json';
8 |
9 | let expect = chai.expect;
10 | let memoryDataSource = _.map(peopleData.value, (row) => {
11 | return _.pick(row, 'UserName', 'FirstName', 'LastName', 'Gender', 'Concurrency');
12 | });
13 | let memoryHeader = _.keys(_.first(memoryDataSource));
14 | let memoryData = util.getExpectedGridData(memoryDataSource);
15 | let gridConfig = {
16 | el: '#container',
17 | dataSource: {
18 | type: 'memory',
19 | data: memoryData,
20 | },
21 | columns: [
22 | {
23 | name: 'UserName',
24 | },
25 | {
26 | name: 'FirstName',
27 | },
28 | {
29 | name: 'LastName',
30 | },
31 | {
32 | name: 'Gender',
33 | },
34 | ],
35 | };
36 |
37 | let pgrid;
38 | let pgridFactory;
39 | let gridView;
40 |
41 | describe('pageable config for non-vnext', function () {
42 | let bodyRowSelector = '#container .grid tbody .table__row--body';
43 |
44 | beforeEach(function () {
45 | util.renderTestContainer();
46 | pgridFactory = pGrid
47 | .factory();
48 | });
49 |
50 | afterEach(() => {
51 | gridView.remove();
52 | util.cleanup();
53 | });
54 |
55 | it('pageable should works as expected for non-vnext', function (done) {
56 | let pageableConfig = {
57 | pageable: {
58 | pageSize: 5,
59 | pageSizes: [5, 10, 15, 20],
60 | },
61 | };
62 |
63 | gridView = pgridFactory
64 | .create(_.extend(pageableConfig, gridConfig))
65 | .gridView
66 | .render({fetch: true});
67 | driver.element(bodyRowSelector)
68 | .then((result) => {
69 | expect(result.length).to.equal(5);
70 | })
71 | .then(done)
72 | .catch(console.log);
73 | });
74 | });
75 |
--------------------------------------------------------------------------------
/js/projection/page.js:
--------------------------------------------------------------------------------
1 | define([
2 | 'lib/underscore',
3 | 'lib/backbone',
4 | 'component/grid/projection/base',
5 | 'component/grid/schema/properties',
6 | 'component/grid/model/response',
7 | ], function (_, Backbone, BaseProjection /* , schemaProperties, Response */) {
8 | var Model = BaseProjection.extend({
9 | defaults: {
10 | 'page.size': 20,
11 | 'page.number': 0, // zero based
12 | },
13 | name: 'page',
14 | // todo [akamel] what if we piped after the data was set?
15 | beforeSet: function (local, other) {
16 | var size = _.has(local, 'page.size') ? local['page.size'] : this.get('page.size');
17 | var number = _.has(local, 'page.number') ? local['page.number'] : this.get('page.number');
18 |
19 | // todo [akamel] sanetize size and number here
20 | size = Math.max(size, 0);
21 | number = Math.max(number, 0);
22 |
23 | _.extend(other, {
24 | take: size,
25 | skip: size * number,
26 | });
27 | },
28 | update: function (options) {
29 | var model = this.src.data;
30 | var size = Math.max(this.get('page.size'), 0);
31 | var count = Math.max(1, model.get('count'));
32 | var number = Math.max(this.get('page.number'), 0);
33 | var pageCount = Math.ceil(count / size);
34 | var pageNumber = Math.min(number, pageCount - 1);
35 |
36 | options = options || {};
37 |
38 | if (options.deep) {
39 | if (this.src) {
40 | this.src.set({
41 | take: size,
42 | skip: size * pageNumber,
43 | }, { silent: true });
44 | }
45 | }
46 |
47 | // Model.__super__.update.call(this, options);
48 |
49 | // if we came in with an update:deep
50 | if (Model.__super__.update.call(this, options)) {
51 | this.patch({ 'page.count': pageCount });
52 | } else {
53 | // todo [akamel] unset our properties only
54 | // this.unset();
55 | }
56 | },
57 | });
58 |
59 | return Model;
60 | });
61 |
--------------------------------------------------------------------------------
/spec/unit/row-checkbox-spec.js:
--------------------------------------------------------------------------------
1 | var expect = require('chai').expect;
2 | var RowCheckbox = require('component/grid/projection/row-checkbox');
3 | var Base = require('component/grid/projection/base');
4 | var Response = require('component/grid/model/response');
5 |
6 | describe('projection RowCheckbox', function () {
7 | it('update should run normal', function () {
8 | var model = new RowCheckbox({
9 | 'row.check.id': 'id',
10 | 'column.checked': 'name',
11 | });
12 |
13 | var originalData = new Base();
14 | originalData.data = new Response({
15 | value: [
16 | { name: 'hello', id: 'hello_id' },
17 | { name: 'world', id: 'world_id' },
18 | ],
19 | columns: [
20 | { name: 'hello', property: 'name' },
21 | { id: 'hello_id', property: 'id' },
22 | ],
23 | });
24 |
25 | originalData.pipe(model);
26 | expect(model.data.get('value')[0].name.$html).to.be.not.null;
27 | expect(model.data.get('value')[1].name.$html).to.be.not.null;
28 | });
29 |
30 | it('thClick should run normal', function () {
31 | var model = new RowCheckbox({
32 | 'row.check.id': 'id',
33 | 'column.checked': 'name',
34 | 'row.check.allow': true,
35 | });
36 | model.data = new Response({
37 | value: [
38 | { name: 'hello', id: 'hello_id' },
39 | { name: 'world', id: 'world_id' },
40 | ],
41 | });
42 | model.thClick({}, { property: 'name', checked: true });
43 | expect(model.get('row.check.list')).to.be.eql(['hello_id', 'world_id']);
44 | });
45 |
46 | it('tdClick should run normal', function () {
47 | var model = new RowCheckbox({
48 | 'row.check.id': 'id',
49 | 'column.checked': 'name',
50 | 'row.check.list': [],
51 | });
52 | var newModel = new Response({ name: 'hello', id: 'hello_id' });
53 | model.tdClick({}, { property: 'name', model: newModel, checked: true });
54 |
55 | expect(model.get('row.check.list')).to.be.eql(['hello_id']);
56 | });
57 | });
58 |
--------------------------------------------------------------------------------
/js/projection/editable-string.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Important!!!
4 | *
5 | * The initial check-in is for unblock the simple grid in advanced campaign creation and
6 | * may cause bugs if you try to use it with some more complex grid. Please DO NOT use this
7 | * projection until we update this and remove the comments here.
8 | *
9 | * todo [yucongj, wewei] add the model to maintail the input data.
10 | *
11 | */
12 |
13 | define([
14 | 'lib/underscore',
15 | 'lib/jquery',
16 | 'component/grid/projection/base',
17 | 'component/grid/layout/template/row.editable.string.jade',
18 | ], function (_, jquery, BaseProjection, editableTemplate) {
19 | 'use strict';
20 |
21 | var Model = BaseProjection.extend({
22 | defaults: {
23 | 'column.editable.string': {},
24 | },
25 | name: 'column-editable-string',
26 | update: function (options) {
27 | if (Model.__super__.update.call(this, options)) {
28 | var model = this.src.data;
29 | var columnEditable = this.get('column.editable.string');
30 | var value = _.map(model.get('value'), function (item) {
31 | var ret = _.clone(item);
32 |
33 | _.each(columnEditable, function (value, key) {
34 | if (_.has(ret, key)) {
35 | if (!_.isObject(ret[key])) {
36 | var obj = new Object(ret[key]); //eslint-disable-line
37 |
38 | if (_.isUndefined(ret[key])) {
39 | obj.$undefined = true;
40 | }
41 |
42 | if (_.isNull(ret[key])) {
43 | obj.$null = true;
44 | }
45 |
46 | ret[key] = obj;
47 | }
48 |
49 | var defaultValue = (ret[key].$undefined || ret[key].$null) ? value.defaultValue : ret[key];
50 |
51 | ret[key].$html = editableTemplate({ defaultValue: defaultValue });
52 | }
53 | });
54 |
55 | return ret;
56 | });
57 |
58 | this.patch({ value: value });
59 | }
60 | },
61 | });
62 |
63 | return Model;
64 | });
65 |
--------------------------------------------------------------------------------
/js/vnext/layout/flex-mixins.jade:
--------------------------------------------------------------------------------
1 | mixin cellValue(cell)
2 | if (cell.html)
3 | != cell.html
4 | else
5 | = cell.value
6 |
7 | mixin mixinHeadCell(cell, escapeAttr)
8 | - var attr = escapeAttr(cell.attributes)
9 | if attr.colspan > 1
10 | .nested-column-container(style=`flex-grow: ${attr.colspan};`)
11 | div.th.flex-cell&attributes(attr)(class=cell.classes, role='columnheader')
12 | +cellValue(cell)
13 | if (cell.hasChild)
14 | .nested-column-placeholder
15 | else
16 | div.th.flex-cell&attributes(attr)(class=cell.classes, role='columnheader')
17 | +cellValue(cell)
18 | if (cell.hasChild)
19 | .nested-column-placeholder
20 |
21 | mixin mixinCell(cell, escapeAttr)
22 | - var attr = escapeAttr(cell.attributes)
23 | div.td.flex-cell&attributes(attr)(class=cell.classes, role='gridcell')
24 | +cellValue(cell)
25 |
26 | mixin mixinSection(section, cell, escapeAttr)
27 | case section
28 | when 'head'
29 | +mixinHeadCell(cell, escapeAttr)
30 | when 'body'
31 | when 'foot'
32 | +mixinCell(cell, escapeAttr)
33 |
34 | mixin mixinRow(row, section, escapeAttr)
35 | - var attr = escapeAttr(row.attributes)
36 |
37 | div.tr.flex-row&attributes(attr)(class=row.classes, role='row')
38 | if row.html || row.view
39 | each cell in row.cells
40 | +mixinSection(section, cell, escapeAttr)
41 | else
42 | - var groups = [], cellByGroup = (row.cells || []).reduce((memo, cell) => {
43 | - var group = cell.group;
44 | - !memo[group] && (memo[group] = [], groups.push(group));
45 | - memo[group].push(cell);
46 | - return memo;
47 | - }, {});
48 |
49 | each group in groups
50 | div(class=`${group}-group-container flex-column-group-container`)
51 | div(class=`${group}-group flex-column-group`)
52 | each cell in cellByGroup[group]
53 | +mixinSection(section, cell, escapeAttr)
54 |
55 |
56 | mixin mixinRows(rows, group, escapeAttr)
57 | each row in rows || []
58 | +mixinRow(row, group, escapeAttr)
--------------------------------------------------------------------------------
/spec/integrated/non-vnext-spec/grid-view-api-spec.js:
--------------------------------------------------------------------------------
1 | import $ from 'jquery';
2 | import _ from 'underscore';
3 | import pGrid from 'component/grid';
4 | import chai from 'chai';
5 | import util from 'util';
6 | import driver from 'driver';
7 | import peopleData from 'data/people.json';
8 |
9 | let expect = chai.expect;
10 | let memoryDataSource = _.map(peopleData.value, (row) => {
11 | return _.pick(row, 'UserName', 'FirstName', 'LastName', 'Gender', 'Concurrency');
12 | });
13 | let memoryHeader = _.keys(_.first(memoryDataSource));
14 | let memoryData = util.getExpectedGridData(memoryDataSource);
15 | let gridConfig = {
16 | el: '#container',
17 | dataSource: {
18 | type: 'memory',
19 | data: memoryData,
20 | },
21 | columns: [
22 | {
23 | name: 'UserName',
24 | },
25 | {
26 | name: 'FirstName',
27 | },
28 | {
29 | name: 'LastName',
30 | },
31 | {
32 | name: 'Gender',
33 | },
34 | ],
35 | };
36 |
37 | let pgrid;
38 | let pgridFactory;
39 | let gridView;
40 |
41 | describe('grid api check for non-vnext', function () {
42 | let bodyRowSelector = '#container .grid tbody .table__row--body';
43 |
44 | beforeEach(function () {
45 | util.renderTestContainer();
46 | pgridFactory = pGrid
47 | .factory();
48 | });
49 |
50 | afterEach(() => {
51 | gridView.remove();
52 | util.cleanup();
53 | });
54 |
55 | it('verify selection function for non-vnext', function (done) {
56 | let selectionConfig = {
57 | selectable: true,
58 | };
59 |
60 | gridView = pgridFactory
61 | .create(_.extend(selectionConfig, gridConfig))
62 | .gridView
63 | .render({fetch: true});
64 | driver.element(bodyRowSelector)
65 | .then((result) => {
66 | return driver.click(util.getCheckboxElFromTbody(result, 0, 0));
67 | })
68 | .then(() => {
69 | let selectedItem = gridView.getSelection();
70 | expect(selectedItem.length).to.equal(1);
71 | expect(selectedItem[0]).to.equal(1);
72 | })
73 | .then(done)
74 | .catch(console.log);
75 | });
76 | });
77 |
--------------------------------------------------------------------------------
/demos/vnext/scrolling/sticky-header-on-element-flex/index.js:
--------------------------------------------------------------------------------
1 | import _ from 'underscore';
2 | import $ from 'jquery';
3 | import pgrid from '../../../../js';
4 | import people from '../people.json';
5 | import rows from '../rows';
6 |
7 | import './index.less';
8 | import 'bootstrap-webpack';
9 |
10 | const columns = [{
11 | name: 'UserName',
12 | width: 70,
13 | sortable: true,
14 | group: 'freezing',
15 | }, {
16 | name: 'Name',
17 | property: item => `${item.FirstName}, ${item.LastName}`,
18 | width: 70,
19 | sortable: true,
20 | group: 'freezing',
21 | }, {
22 | name: 'Emails',
23 | width: 200,
24 | sortable: item => item.Emails.length,
25 | group: 'freezing',
26 | }, {
27 | name: 'AddressInfo',
28 | width: 100,
29 | columns: [{
30 | width: 100,
31 | name: 'Address',
32 | property: 'AddressInfo/0/Address',
33 | sortable: true,
34 | }, {
35 | name: 'City',
36 | columns: [{
37 | width: 200,
38 | name: 'CityName',
39 | property: 'AddressInfo/0/City/Name',
40 | sortable: true,
41 | }, {
42 | width: 250,
43 | name: 'CityCountry',
44 | property: 'AddressInfo/0/City/CountryRegion',
45 | sortable: true,
46 | }],
47 | }],
48 | }];
49 |
50 | const gridConfigBase = {
51 | tableClasses: ['table'],
52 | layout: 'flex',
53 | selection: true,
54 | dataSource: {
55 | type: 'memory',
56 | data: people.value,
57 | },
58 | rows,
59 | columns,
60 | };
61 |
62 | const stickyGridConfig = _.defaults({
63 | el: '.grid-container',
64 | scrolling: {
65 | virtualized: true,
66 | viewport: $('.grid-viewport'),
67 | header: 'sticky',
68 | },
69 | }, gridConfigBase);
70 |
71 | window.gridView = pgrid.factory({ vnext: true }).create(stickyGridConfig).gridView;
72 |
73 | _.each([
74 | 'willRedrawHeader',
75 | 'didRedrawHeader',
76 | 'willRedrawFooter',
77 | 'didRedrawFooter',
78 | 'willRedrawBody',
79 | 'didRedrawBody',
80 | ], event => {
81 | window.gridView.on(event, () => {
82 | window.console.log(`trigger the event ${event}`);
83 | });
84 | });
85 |
86 | window.gridView.render();
87 |
--------------------------------------------------------------------------------
/js/projection/column-i18n.js:
--------------------------------------------------------------------------------
1 | define([
2 | 'lib/underscore',
3 | 'lib/backbone',
4 | 'component/grid/projection/base',
5 | 'component/grid/schema/properties',
6 | 'component/grid/model/response',
7 | ], function (_, Backbone, BaseProjection /* , schemaProperties, Response */) {
8 | var Model = BaseProjection.extend({
9 | defaults: {
10 | 'column.i18n': {
11 | '': function (name) {
12 | return name;
13 | },
14 | },
15 | 'subColumn.i18n': {},
16 | },
17 | name: 'column-i18n',
18 | beforeSet: function (local) {
19 | if (_.has(local, 'column.i18n')) {
20 | if (!_.isObject(local['column.i18n'])) {
21 | local['column.i18n'] = this.defaults['column.i18n'];
22 | }
23 | }
24 | },
25 | update: function (options) {
26 | // todo [akamel] when calling a deep update; suppress onchange event based updates
27 | // Model.__super__.update.call(this, options);
28 |
29 | if (Model.__super__.update.call(this, options)) {
30 | var model = this.src.data;
31 | var colOptions = this.get('column.i18n');
32 | var subColOptions = this.get('subColumn.i18n');
33 | var columns = model.get('columns') || {};
34 | var $default = colOptions[''];
35 |
36 | var i18nColumns = {};
37 | _.each(_.keys(columns), function (element) {
38 | var opt = colOptions[element];
39 | if (_.isUndefined(opt)) {
40 | opt = $default;
41 | }
42 |
43 | i18nColumns[element] = _.defaults({
44 | $text: _.isFunction(opt) ? opt(element) : opt,
45 | property: element,
46 | }, columns[element]);
47 |
48 | i18nColumns[element].config = i18nColumns[element].config || {};
49 | i18nColumns[element].config.subColTitle = subColOptions[element];
50 | });
51 |
52 | this.patch({
53 | columns: i18nColumns,
54 | });
55 | } else {
56 | // todo [akamel] unset our properties only
57 | // this.unset();
58 | }
59 | },
60 | });
61 |
62 | return Model;
63 | });
64 |
--------------------------------------------------------------------------------
/js/vnext/data-source/memory.js:
--------------------------------------------------------------------------------
1 | import _ from 'underscore';
2 | import { DataSource } from './base.js';
3 |
4 | const truthy = _.constant(true);
5 |
6 | /**
7 | * Data source from an in-memory array.
8 | * @class MemoryDataSource
9 | * @param {Object[]} data
10 | * The in-memory array for the data set.
11 | * @param {string} primaryKey
12 | * The primary key of the data set.
13 | */
14 | export class MemoryDataSource extends DataSource {
15 | constructor(data, primaryKey) {
16 | super(primaryKey);
17 | this.data = data;
18 |
19 | // This caches the query result without skip and take
20 | this._cache = {
21 | seed: this.data,
22 | query: null,
23 | data: this.data,
24 | };
25 | }
26 |
27 | /**
28 | * The cached items respecting the sorting and filtering,
29 | * ignoring the skip and take.
30 | * @type {Object[]}
31 | */
32 | get cachedItems() {
33 | return this._cache.data;
34 | }
35 |
36 | query(params) {
37 | const {
38 | skip = 0,
39 | take = this.data.length - skip,
40 | filter = truthy,
41 | orderby = [],
42 | } = params || {};
43 |
44 | if (this._cache.seed !== this.data || !_.isEqual(this._cache.query, { filter, orderby })) {
45 | const { key, direction } = _.first(orderby) || {};
46 | let sortIteratee = null;
47 |
48 | if (_.isFunction(key)) {
49 | sortIteratee = key;
50 | } else if (_.isString(key)) {
51 | const segs = key.split(/[\.\/]/);
52 | sortIteratee = item => _.reduce(segs, (memo, seg) => _.result(memo, seg), item);
53 | }
54 |
55 | let data = _.filter(this.data, filter);
56 |
57 | if (sortIteratee) {
58 | data = _.sortBy(data, sortIteratee);
59 |
60 | if (direction < 0) {
61 | data.reverse();
62 | }
63 | }
64 |
65 | this._cache = {
66 | seed: this.data,
67 | query: { filter, orderby },
68 | data,
69 | };
70 | }
71 |
72 | return {
73 | totalCount: this._cache.data.length,
74 | items: this._cache.data.slice(skip, skip + take),
75 | };
76 | }
77 | }
78 |
79 |
--------------------------------------------------------------------------------
/js/projection/memory-queryable.js:
--------------------------------------------------------------------------------
1 | define([
2 | 'lib/underscore',
3 | 'lib/backbone',
4 | 'component/grid/projection/base',
5 | 'component/grid/schema/properties',
6 | 'component/grid/model/response',
7 | ], function (_, Backbone, BaseProjection, schemaProperties /* , Response */) {
8 | var Model = BaseProjection.extend({
9 | defaults: {
10 | 'skip': 0,
11 | 'take': Number.MAX_VALUE,
12 | 'filter': function () {
13 | return true;
14 | },
15 | 'orderby': [],
16 | 'select': [],
17 | 'column.sortable': {},
18 | },
19 | name: 'map-queryable',
20 | beforeSet: function (local) {
21 | if (_.has(local, 'filter')) {
22 | if (!_.isFunction(local.filter)) {
23 | local.filter = this.defaults.filter;
24 | }
25 | }
26 | },
27 | update: function (options) {
28 | // Model.__super__.update.call(this, options);
29 |
30 | if (Model.__super__.update.call(this, options)) {
31 | var model = this.src.data;
32 | var order = _.chain(this.get('orderby')).first().pairs().first().value();
33 |
34 | var orderKey = _.first(order);
35 | var orderDir = _.last(order);
36 |
37 | var value = _.chain(model.get('value')).filter(this.get('filter'));
38 | var sortFunc = this.get('column.sortable')[orderKey];
39 |
40 | if (orderKey) {
41 | if (_.isFunction(sortFunc)) {
42 | value = _.chain(sortFunc(value.value(), orderDir));
43 | } else {
44 | value = value.sortBy(orderKey);
45 | if (orderDir === -1) {
46 | value = value.reverse();
47 | }
48 | }
49 | }
50 |
51 | value = value
52 | .rest(this.get('skip'))
53 | .first(this.get('take'))
54 | .value();
55 |
56 | var select = this.get('select');
57 | if (!_.size(select)) {
58 | select = schemaProperties.from(value);
59 | }
60 |
61 | this.patch({
62 | value: value,
63 | select: select,
64 | });
65 | } else {
66 | // todo [akamel] unset our properties only
67 | // this.unset();
68 | }
69 | },
70 | });
71 |
72 | return Model;
73 | });
74 |
--------------------------------------------------------------------------------
/js/vnext/projection/range-selection.js:
--------------------------------------------------------------------------------
1 | import _ from 'underscore';
2 |
3 | function changeSelectRow(e) {
4 | const key = this.keyOfElement(e.target);
5 | const selection = this.get('selection');
6 | const rangeSelection = this.get('rangeSelection');
7 |
8 | if (!e.shiftKey || selection.disableShiftKey) {
9 | rangeSelection.preSelect = key;
10 | return;
11 | }
12 |
13 | const selected = selection.selected;
14 | const selectable = selection.selectable;
15 | const preSelect = rangeSelection.preSelect;
16 | const preShiftSelect = rangeSelection.preShiftSelect;
17 | const allKey = _.chain(this.items.slice())
18 | .filter(selectable)
19 | .map(_.property(this.primaryKey))
20 | .map(String)
21 | .value();
22 | const preIndex = allKey.indexOf(preSelect);
23 | const curIndex = allKey.indexOf(key);
24 | const selectObj = _.indexBy(selected);
25 |
26 | if (preShiftSelect) {
27 | const preShiftIndex = allKey.indexOf(preShiftSelect);
28 |
29 | _.each(allKey.slice(
30 | Math.min(preIndex, preShiftIndex),
31 | Math.max(preIndex, preShiftIndex) + 1
32 | ), key => {
33 | delete selectObj[key];
34 | });
35 | }
36 |
37 | _.each(allKey.slice(
38 | Math.min(preIndex, curIndex),
39 | Math.max(preIndex, curIndex) + 1
40 | ), key => {
41 | selectObj[key] = key;
42 | });
43 |
44 | rangeSelection.preShiftSelect = key;
45 | this.set({
46 | selection: _.defaults({ selected: _.keys(selectObj) }, selection),
47 | });
48 | }
49 |
50 | function changeSelectAll() {
51 | const rangeSelection = this.get('rangeSelection');
52 |
53 | rangeSelection.preSelect = null;
54 | rangeSelection.preShiftSelect = null;
55 | }
56 |
57 | function selectionProjectionHandler(state, config, model) {
58 | if (model.selection.single) {
59 | return state;
60 | }
61 |
62 | const events = _.defaults({
63 | 'click input.select-all': changeSelectAll,
64 | 'click input.select-row': changeSelectRow,
65 | }, state.events);
66 |
67 | return _.defaults({ events }, state);
68 | }
69 |
70 | export const rangeSelection = {
71 | name: 'rangeSelection',
72 | handler: selectionProjectionHandler,
73 | defaults: {
74 | preSelect: null,
75 | preShiftSelect: null,
76 | },
77 | };
78 |
--------------------------------------------------------------------------------
/js/vnext/layout/const.js:
--------------------------------------------------------------------------------
1 | import _ from 'underscore';
2 | import { escapeAttr } from './escape';
3 |
4 | import tableStickyTemplate from './table-sticky.jade';
5 | import flexStickyTemplate from './flex-sticky.jade';
6 | import tableFixedTemplate from './table-fixed.jade';
7 | import flexFixedTemplate from './flex-fixed.jade';
8 | import tableStaticTemplate from './table-static.jade';
9 | import flexStaticTemplate from './flex-static.jade';
10 |
11 | import tableRowTemplate from './row.jade';
12 | import flexRowTemplate from './flex-row.jade';
13 |
14 | import tableHeaderFooterTemplate from './header-footer.jade';
15 | import flexHeaderFooterTemplate from './flex-header-footer.jade';
16 |
17 | export const LAYOUT = {
18 | table: 'table',
19 | flex: 'flex',
20 | };
21 |
22 | // sticky/static/fixed header type
23 | export const stickyTemplate = {
24 | [LAYOUT.table]: tableStickyTemplate,
25 | [LAYOUT.flex]: flexStickyTemplate,
26 | };
27 |
28 | export const fixedTemplate = {
29 | [LAYOUT.table]: tableFixedTemplate,
30 | [LAYOUT.flex]: flexFixedTemplate,
31 | };
32 | export const staticTemplate = {
33 | [LAYOUT.table]: tableStaticTemplate,
34 | [LAYOUT.flex]: flexStaticTemplate,
35 | };
36 |
37 | // header footer template
38 | export const headerFooterTemplate = {
39 | [LAYOUT.table]: tableHeaderFooterTemplate,
40 | [LAYOUT.flex]: flexHeaderFooterTemplate,
41 | };
42 |
43 | // row template
44 | export const rowTemplateWithEscape = {
45 | [LAYOUT.table]: row => tableRowTemplate(_.defaults({ escapeAttr }, row)),
46 | [LAYOUT.flex]: row => flexRowTemplate(_.defaults({ escapeAttr }, row)),
47 | };
48 |
49 | // selector
50 | export const colgroupSelector = {
51 | [LAYOUT.table]: 'colgroup.column-group',
52 | [LAYOUT.flex]: '.colgroup.column-group',
53 | };
54 | export const headerSelector = {
55 | [LAYOUT.table]: 'thead.header',
56 | [LAYOUT.flex]: '.thead.header',
57 | };
58 | export const footerSelector = {
59 | [LAYOUT.table]: 'tfoot.footer',
60 | [LAYOUT.flex]: '.tfoot.footer',
61 | };
62 | export const stickyHeaderFillerTableSelector = {
63 | [LAYOUT.table]: '.sticky-header-filler + table',
64 | [LAYOUT.flex]: '.sticky-header-filler + .table',
65 | };
66 | export const viewportTableSelector = {
67 | [LAYOUT.table]: '.viewport > table',
68 | [LAYOUT.flex]: '.viewport > .table',
69 | };
70 |
--------------------------------------------------------------------------------
/js/vnext/factory/grid-view-plugin.js:
--------------------------------------------------------------------------------
1 | import _ from 'underscore';
2 | import { GridView } from '../grid-view.js';
3 |
4 | const CONSTRUCTOR_OPTIONS = ['el', 'scrolling', 'tableClasses', 'tableAttributes', 'layout'];
5 | const NONE_PROJECTION_OPTIONS = CONSTRUCTOR_OPTIONS.concat(['plugins']);
6 |
7 | /**
8 | * @typedef GridViewConfig
9 | * @type {Object}
10 | * @property {string} name
11 | * Name of the grid
12 | * @property {string|HTMLElement|jQuery} el
13 | * The root element of the grid view.
14 | * @property {ScrollingConfig} scrolling
15 | * The scrolling behavior configuratoin.
16 | * @property {strings[]} tableClasses
17 | * An array of classes to be applied to `TABLE` elements.
18 | * @property {object} tableAttributes
19 | * An object of attributes to be applied to `TABLE` elements.
20 | * @property {Object.} plugins
21 | * The plugin configurations. The keys are the plugin names, and the values
22 | * are the configuation objects.
23 | * @property {RowsConfig} rows
24 | * The structure of the head/body/foot rows.
25 | * @property {ColumnConfig[]} columns
26 | * Array of the columns configurations.
27 | * @property {DataSourceConfig} dataSource
28 | * The data source configurations. Tells the grid how to fetch data.
29 | * @property {QueryConfig} query
30 | * The query parameters for data fetch.
31 | * @property {SelectionConfig} selection
32 | * Config the behavior for row selection.
33 | * @property {SortableHeaderConfig} sortableHeader
34 | * Config the state and visual of the click-to-sort column headers.
35 | * @property {external:BackboneViewEventHash} events
36 | * Customized event hash in form of `Backbone.View#events`.
37 | * It can be set to handle
38 | * * The DOM events inside the `GridView`.
39 | * * The `GridView` events, e.g. {@link GridView#willUpdate},
40 | * {@link GridView#didUpdate}.
41 | */
42 | export default definePlugin => definePlugin('gridView', [
43 | 'config',
44 | 'dataSource',
45 | ], (config, dataSource) => {
46 | const options = _.chain(config)
47 | .pick(CONSTRUCTOR_OPTIONS)
48 | .extend({ dataSource })
49 | .value();
50 | const gridView = new GridView(options);
51 |
52 | gridView.set(_.omit(config, NONE_PROJECTION_OPTIONS));
53 |
54 | return gridView;
55 | });
56 |
57 |
--------------------------------------------------------------------------------
/spec/unit/base-spec.js:
--------------------------------------------------------------------------------
1 | var expect = require('chai').expect;
2 | var sinon = require('sinon');
3 | var Base = require('component/grid/projection/base');
4 |
5 | describe('projection Base', function () {
6 | var ExtendModel, instance;
7 | beforeEach(function () {
8 | ExtendModel = Base.extend({
9 | defaults: { attr1: 'attr1' },
10 | });
11 | instance = new ExtendModel();
12 | });
13 |
14 | it('constructor and initialize should run normal', function () {
15 | expect(instance.localKeys).to.be.deep.equal(["attr1"]);
16 | expect(instance.data).to.not.be.null;
17 | });
18 |
19 | it('pipe should run normal', function () {
20 | var otherInstance = new ExtendModel();
21 | sinon.spy(instance.data, 'off');
22 | sinon.spy(instance, 'off');
23 | sinon.spy(instance.data, 'on');
24 | sinon.spy(instance, 'on');
25 | sinon.spy(otherInstance, 'update');
26 | instance.pipe(otherInstance);
27 |
28 | expect(instance.data.off.called).to.be.true;
29 | expect(instance.off.called).to.be.true;
30 | expect(instance.data.on.called).to.be.true;
31 | expect(instance.on.called).to.be.true;
32 | expect(otherInstance.update.called).to.be.true;
33 | });
34 |
35 | it('set should run normal', function () {
36 | var otherInstance = new ExtendModel();
37 | sinon.spy(otherInstance, 'beforeSet');
38 | sinon.spy(otherInstance, 'afterSet');
39 |
40 | instance.pipe(otherInstance);
41 | otherInstance.set({ attr1: 'value1', attr2: 'value2' });
42 |
43 | expect(otherInstance.beforeSet.calledWith({ attr1: 'value1' }, { attr2: 'value2' })).to.true;
44 | expect(otherInstance.afterSet.called).to.be.true;
45 | expect(otherInstance.get('attr1')).to.equal('value1');
46 | });
47 |
48 | it('bubble should run normal', function () {
49 | var otherInstance = new ExtendModel();
50 | sinon.stub(instance, 'bubble');
51 | if (!otherInstance.events) {
52 | otherInstance.events = {};
53 | }
54 | otherInstance.events['test-event'] = 'testEvent';
55 | otherInstance.testEvent = sinon.spy();
56 |
57 | instance.pipe(otherInstance);
58 | otherInstance.bubble('test-event', 'arguments');
59 | expect(otherInstance.testEvent.calledWith('arguments')).to.be.true;
60 | expect(instance.bubble.calledWith('test-event', 'arguments'));
61 | });
62 | });
63 |
--------------------------------------------------------------------------------
/js/layout/measure.js:
--------------------------------------------------------------------------------
1 | define([
2 | 'lib/jquery',
3 | 'lib/underscore',
4 | ], function ($, _) {
5 | function viewport(el, container) {
6 | var $el = el ? $(el) : this.$el;
7 |
8 | container = container || this.container;
9 | var $viewport = container.$el;
10 |
11 | var viewportTop = $viewport.scrollTop();
12 | var viewportBottom = viewportTop + $viewport.height();
13 | var viewportLeft = $viewport.scrollLeft();
14 |
15 | var boundsTop = container.offset($el).top;
16 | var boundsBottom = boundsTop + $el.innerHeight();
17 | // var boundsLeft = $el.offset().left;
18 |
19 | var visibleTop = Math.max(boundsTop, viewportTop);
20 | var visibleBottom = Math.min(boundsBottom, viewportBottom);
21 | // var visibleLeft = Math.max(boundsLeft, viewportLeft);
22 |
23 | return {
24 | boundsTop: boundsTop,
25 | viewportTop: viewportTop,
26 | top: visibleTop - boundsTop,
27 | bottom: visibleBottom - boundsTop,
28 | offsetLeft: viewportLeft,
29 | };
30 | }
31 |
32 | function dimensions(el) {
33 | var $el = el ? $(el) : this.$el;
34 |
35 | // calculate heights
36 | // a. header
37 | var ret = {
38 | rows: [],
39 | thead: $el.find('thead > tr').outerHeight(),
40 | };
41 |
42 | // b. keep row info
43 | $el.find('tbody').children('tr').each(function () {
44 | ret.rows.push($(this).outerHeight());
45 | });
46 |
47 | // c. update average row height
48 | var avg = _.reduce(ret.rows, function (memo, num) {
49 | return memo + num;
50 | }, 0) / (ret.rows.length === 0 ? 1 : ret.rows.length);
51 |
52 | ret.avgRowHeight = avg;
53 | ret.estimateHeight = (_.size(this.data.value) * avg) + ret.thead;
54 |
55 | return ret;
56 | }
57 |
58 | function sample() {
59 | // a. render test pass
60 | var $tmpEl = $('');
61 | var sample = _.first(this.data.value, 20);
62 |
63 | this.$el.append($tmpEl);
64 |
65 | $tmpEl[0].innerHTML = this.toHTML({ rows: sample });
66 |
67 | // b. take measures
68 | var ret = dimensions.call(this, $tmpEl);
69 |
70 | // c. clean-up
71 | $tmpEl.remove();
72 |
73 | return ret;
74 | }
75 |
76 | return {
77 | viewport: viewport,
78 | dimensions: dimensions,
79 | sample: sample,
80 | };
81 | });
82 |
--------------------------------------------------------------------------------
/js/vnext/projection/query.js:
--------------------------------------------------------------------------------
1 | import _ from 'underscore';
2 | import Promise from 'bluebird';
3 |
4 | /**
5 | * The data source configurations.
6 | * @typedef QueryConfig
7 | * @type {Object}
8 | * @property {OrderByConfig} orderby
9 | * The sorting parameter.
10 | * @property {Object} filter
11 | * The filter object in MangoDB format.
12 | * @property {number} skip
13 | * The number of item to skip from the query result.
14 | * @property {number} take
15 | * The number of item to take from the query result.
16 | */
17 |
18 | /**
19 | * Fetching data from the data source
20 | * @param {DataChainState} state
21 | * @param {QueryConfig} params
22 | * The data source configurations.
23 | * @return {DataChainState}
24 | */
25 | function queryProjectionHandler(state, params) {
26 | const primaryKey = this.primaryKey;
27 |
28 | /**
29 | * The `GridView` will reload data from the data source.
30 | * @event GridView#willReload
31 | */
32 | this.trigger('willReload');
33 |
34 | return Promise.resolve(this.query(params)).catch(error => ({
35 | totalCount: 0,
36 | items: [],
37 | error: error || new Error('Failed to load grid data'),
38 | })).then(({ totalCount, items, error }) => {
39 | /**
40 | * The `GridView` did reload data from the data source.
41 | * This event take 2 parameters
42 | * 1. succeeded (Boolean), true if the data fetch succeeded
43 | * 2. data|rejection (Object)
44 | * * If the data fetch succeeded, pass the fetched data in form of
45 | * `{ totalCount, items }`. Where `totalCount` is the server side total
46 | * item count, and the items is the array of data items with current
47 | * query condition
48 | * * If the data fetch failed, pass the rejection object, which is
49 | * usually an `Error` object from the data source
50 | * @event GridView#didReload
51 | */
52 | if (error) {
53 | this.trigger('didReload', false, error);
54 | } else {
55 | this.trigger('didReload', true, { totalCount, items });
56 | }
57 |
58 | return {
59 | uniqueId: _.uniqueId('grid-data-'),
60 | items,
61 | primaryKey,
62 | totalCount,
63 | };
64 | });
65 | }
66 |
67 | export const query = {
68 | name: 'query',
69 | handler: queryProjectionHandler,
70 | defaults: {},
71 | };
72 |
--------------------------------------------------------------------------------
/spec/integrated/non-vnext-spec/aggregate-row-spec.js:
--------------------------------------------------------------------------------
1 | import $ from 'jquery';
2 | import _ from 'underscore';
3 | import pGrid from 'component/grid';
4 | import chai from 'chai';
5 | import util from 'util';
6 | import driver from 'driver';
7 | import peopleData from 'data/people.json';
8 |
9 | let expect = chai.expect;
10 | let memoryDataSource = _.map(peopleData.value, (row) => {
11 | return _.pick(row, 'UserName', 'FirstName', 'LastName', 'Gender', 'Concurrency');
12 | });
13 | let memoryHeader = _.keys(_.first(memoryDataSource));
14 | let memoryData = util.getExpectedGridData(memoryDataSource);
15 | let gridConfig = {
16 | el: '#container',
17 | dataSource: {
18 | type: 'memory',
19 | data: memoryData,
20 | },
21 | columns: [
22 | {
23 | name: 'UserName',
24 | },
25 | {
26 | name: 'FirstName',
27 | },
28 | {
29 | name: 'LastName',
30 | },
31 | {
32 | name: 'Gender',
33 | },
34 | ],
35 | };
36 |
37 | let pgrid;
38 | let pgridFactory;
39 | let gridView;
40 |
41 | describe('aggregate row for non-vnext', function () {
42 | let bodyRowSelector = '#container .grid tbody .table__row--body';
43 |
44 | beforeEach(function () {
45 | util.renderTestContainer();
46 | pgridFactory = pGrid
47 | .factory();
48 | });
49 |
50 | afterEach(() => {
51 | gridView.remove();
52 | util.cleanup();
53 | });
54 |
55 | it('aggregate should works as expected for non-vnext', function (done) {
56 | let aggregateConfig= {
57 | aggregate: {
58 | top() {
59 | return [
60 | {UserName: 'Total Top'},
61 | ];
62 | },
63 | bottom() {
64 | return [
65 | {UserName: 'Total Bottom'},
66 | ];
67 | },
68 | }
69 | };
70 |
71 | gridView = pgridFactory
72 | .create(_.extend(aggregateConfig, gridConfig))
73 | .gridView
74 | .render({fetch: true});
75 | driver.element(bodyRowSelector)
76 | .then((result) => {
77 | let aggregateFirstText = result.first().find('td').first().text();
78 | let aggregateLastText = result.last().find('td').first().text();
79 |
80 | expect(aggregateFirstText).to.equal('Total Top');
81 | expect(aggregateLastText).to.equal('Total Bottom');
82 | })
83 | .then(done)
84 | .catch(console.log);
85 | });
86 | });
87 |
--------------------------------------------------------------------------------
/spec/util/index.js:
--------------------------------------------------------------------------------
1 | import _ from 'underscore';
2 | import $ from 'jquery';
3 |
4 | let $container;
5 |
6 | function renderTestContainer(id) {
7 | id = id || 'container';
8 | $container = $(id);
9 | if ($container.length === 0) {
10 | $container = $('')).appendTo('body');
11 | }
12 | return $container;
13 | }
14 |
15 | function cleanup() {
16 | $container.empty();
17 | }
18 |
19 | function createGrid(pgridFactory, config, extendedConfig) {
20 |
21 | }
22 |
23 | function validateElementArray(elementArray, expectedData) {
24 | let actualArray = _.map(elementArray, (element) => {
25 | return element.textContent;
26 | });
27 |
28 | return _.isEqual(actualArray, expectedData);
29 | }
30 |
31 | function validateElementMatrix(elementMatrix, expectedData) {
32 | if (elementMatrix.length !== expectedData.length) {
33 | return false;
34 | }
35 | let validation = true;
36 | _.forEach(elementMatrix, (elementRow, index) => {
37 | let expectedArray = _.values(expectedData[index]);
38 |
39 | expectedArray = _.map(expectedArray, (item) => {
40 | return String(item);
41 | });
42 | if (!validateElementArray(elementRow.children, expectedArray)) {
43 | validation = false;
44 | }
45 | });
46 | return validation;
47 | }
48 |
49 | function getExpectedGridData(data, columnKeys) {
50 | return _.map(data, (row) => {
51 | return columnKeys ? _.pick(row, columnKeys) : row;
52 | });
53 | }
54 |
55 | function validateClassesForElement(el, classes) {
56 | let assertion = classes.every((clazz) => {
57 | return el.hasClass(clazz);
58 | });
59 | return assertion;
60 | }
61 |
62 | function validateClassesForElementArray(elArray, classes) {
63 | let assertion = elArray.every((el) => {
64 | return validateClassesForElement(el, classes);
65 | });
66 | return assertion;
67 | }
68 |
69 | function getCheckboxElFromTable($el, selector, xindex, yindex) {
70 | let $checkbox = $el.eq(xindex).find(selector).eq(yindex).find('input');
71 | return $checkbox;
72 | }
73 |
74 | function getCheckboxElFromThead($el, xindex, yindex) {
75 | return getCheckboxElFromTable($el, 'th', xindex, yindex);
76 | }
77 |
78 | function getCheckboxElFromTbody($el, xindex, yindex) {
79 | return getCheckboxElFromTable($el, 'td', xindex, yindex);
80 | }
81 |
82 | export default {
83 | renderTestContainer,
84 | cleanup,
85 | validateElementMatrix,
86 | getExpectedGridData,
87 | validateClassesForElement,
88 | validateClassesForElementArray,
89 | getCheckboxElFromThead,
90 | getCheckboxElFromTbody,
91 | };
92 |
--------------------------------------------------------------------------------
/js/projection/odata.js:
--------------------------------------------------------------------------------
1 | define([
2 | 'bluebird',
3 | 'lib/underscore',
4 | 'lib/backbone',
5 | 'lib/jquery',
6 | 'component/grid/projection/base',
7 | 'component/grid/schema/properties',
8 | ], function (Promise, _, Backbone, $, BaseProjection, schemaProperties) {
9 | var Model = BaseProjection.extend({
10 | defaults: {
11 | verb: 'get',
12 | url: undefined,
13 | skip: undefined,
14 | take: undefined,
15 | filter: undefined,
16 | orderby: [],
17 | select: [],
18 | },
19 | name: 'odata',
20 | update: function () {
21 | this.p$fetchData || this.trigger('update:beginning');
22 |
23 | var url = this.get('url');
24 |
25 | url = _.isFunction(url) ? url() : url;
26 | var op = {
27 | url: url,
28 | $format: 'json',
29 | // todo [akamel] this is odata v3 specific
30 | $count: true,
31 | };
32 |
33 | var take = this.get('take');
34 | if (take) {
35 | op.$top = take;
36 | }
37 |
38 | var skip = this.get('skip');
39 | if (skip) {
40 | op.$skip = skip;
41 | }
42 |
43 | // todo [akamel] only supports one order column
44 | var orderby = this.get('orderby');
45 | if (_.size(orderby)) {
46 | var col = _.first(orderby);
47 | var key = _.keys(col)[0];
48 | var dir = col[key];
49 |
50 | op.$orderby = key + ' ' + (dir > 0 ? 'asc' : 'desc');
51 | }
52 |
53 | var p$fetchData = this.p$fetchData = new Promise(function (resolve, reject) {
54 | $.getJSON(op.url, _.omit(op, 'url'))
55 | .success(resolve)
56 | .error(function (jqXHR, textStatus, errorThrown) {
57 | reject(new Error(errorThrown));
58 | });
59 | }).then(function (data) {
60 | if (p$fetchData === this.p$fetchData) {
61 | var delta = {
62 | value: data.value,
63 | rawValue: data,
64 | select: schemaProperties.from(data.value),
65 | count: data['@odata.count'],
66 | error: undefined,
67 | };
68 | this.patch(delta);
69 | }
70 | }.bind(this)).catch(function (error) {
71 | if (p$fetchData === this.p$fetchData) {
72 | this.patch({ error: error });
73 | }
74 | }.bind(this)).finally(function () {
75 | if (p$fetchData === this.p$fetchData) {
76 | this.trigger('update:finished', this.data.get('error'));
77 | this.p$fetchData = null;
78 | }
79 | }.bind(this));
80 | },
81 | });
82 |
83 | return Model;
84 | });
85 |
--------------------------------------------------------------------------------
/js/projection/jsdata.js:
--------------------------------------------------------------------------------
1 | define([
2 | 'bluebird',
3 | 'lib/underscore',
4 | 'component/grid/projection/base',
5 | 'component/grid/schema/properties.js',
6 | ], function (Promise, _, BaseProjection, schemaProperties) {
7 | var Model = BaseProjection.extend({
8 | defaults: {
9 | 'jsdata.query': undefined,
10 | 'jsdata.entity': undefined,
11 | 'jsdata.options': undefined,
12 | 'skip': undefined,
13 | 'take': undefined,
14 | 'filter': undefined,
15 | 'orderby': [],
16 | 'select': [],
17 | },
18 | name: 'jsdata',
19 |
20 | update: function () {
21 | var entity = this.get('jsdata.entity');
22 | var options = _.defaults(this.get('jsdata.options'), { all: true });
23 | var op = {};
24 |
25 | this.p$fetchData || this.trigger('update:beginning');
26 |
27 | var take = this.get('take');
28 |
29 | if (take) {
30 | op.limit = take;
31 | }
32 |
33 | var skip = this.get('skip');
34 |
35 | if (skip) {
36 | op.offset = skip;
37 | }
38 |
39 | var filter = this.get('filter');
40 |
41 | if (filter) {
42 | op.where = filter;
43 | }
44 |
45 | var query = this.get('jsdata.query');
46 |
47 | if (query) {
48 | op.query = query;
49 | }
50 |
51 | var orderby = this.get('orderby');
52 |
53 | if (orderby && orderby.length) {
54 | op.orderBy = _.reduce(orderby, function (arr, obj) {
55 | _.each(obj, function (value, key) {
56 | arr.push([key, value > 0 ? 'ASC' : 'DESC']);
57 | });
58 | return arr;
59 | }, []);
60 | }
61 |
62 | var p$fetchData = this.p$fetchData = entity.findAll(op, options)
63 | .then(function (data) {
64 | if (this.p$fetchData === p$fetchData) {
65 | var delta = {
66 | value: data,
67 | count: data.totalCount,
68 | select: schemaProperties.from(data),
69 | error: undefined,
70 | };
71 | if (_.has(data, 'raw')) {
72 | delta.rawValue = data.raw;
73 | }
74 | this.patch(delta);
75 | }
76 | }.bind(this))
77 | .catch(function (error) {
78 | if (this.p$fetchData === p$fetchData) {
79 | this.patch({ error });
80 | }
81 | }.bind(this))
82 | .finally(function () {
83 | if (this.p$fetchData === p$fetchData) {
84 | this.trigger('update:finished', this.data.get('error'));
85 | this.p$fetchData = null;
86 | }
87 | }.bind(this));
88 | },
89 | });
90 |
91 | return Model;
92 | });
93 |
--------------------------------------------------------------------------------
/demos/factory/index.js:
--------------------------------------------------------------------------------
1 | var pgrid = require('projection-grid');
2 | var Customer = require('./js-data-resource');
3 | var keyHeaderTemplate = require('./key-column-header.jade');
4 | var companyNameTemplate = require('./company-name.jade');
5 | var pagerViewPlugin = require('./pager-view-plugin').default;
6 | require('bootstrap-webpack');
7 |
8 | var grid = pgrid.factory().use(pagerViewPlugin).create({
9 | el: '.grid-root',
10 | dataSource: {
11 | type: 'js-data',
12 | resource: Customer,
13 | schema: { key: 'CustomerID' },
14 | },
15 | aggregate: {
16 | top(/* data */) {
17 | return [
18 | { CustomerID: 'Total Top' },
19 | ];
20 | },
21 | bottom(/* data */) {
22 | return [
23 | { CustomerID: 'Total Bottom' },
24 | ];
25 | },
26 | },
27 | scrollable: {
28 | virtual: true,
29 | },
30 | columnShifter: {
31 | totalColumns: 3,
32 | },
33 | selectable: true,
34 | pageable: {
35 | pageSize: 10,
36 | pageSizes: [5, 10, 15, 20],
37 | },
38 | editable: {
39 | tooltipText: 'Edit Me!',
40 | iconClasses: ['glyphicon', 'glyphicon-wrench'],
41 | },
42 | columns: [
43 | {
44 | name: 'CustomerID',
45 | title: 'Customer ID',
46 | hoverText: 'This is hover text',
47 | sortable: true,
48 | locked: true,
49 | headerTemplate: keyHeaderTemplate,
50 | },
51 | {
52 | name: 'CompanyName',
53 | title: 'Company Name',
54 | sortable: true,
55 | editable: true,
56 | template: companyNameTemplate,
57 | attributes: {
58 | class: 'company-name-cell',
59 | },
60 | headerAttributes: {
61 | class: 'company-name-header',
62 | },
63 | },
64 | {
65 | name: 'City',
66 | title: 'City',
67 | sortable: true,
68 | editable: true,
69 | headerBuilder: (column => 'Great ' + column.property),
70 | },
71 | {
72 | name: 'Contact',
73 | field: 'ContactName',
74 | value: item => `${item.ContactName} (${item.ContactTitle})`,
75 | sortable: true,
76 | },
77 | ],
78 | pagerView: {
79 | el: '.pager-root',
80 | availablePageSizes: [5, 10, 15, 20],
81 | },
82 | });
83 |
84 | grid.gridView.on('update:beginning', function () {
85 | console.log('begin update');
86 | });
87 |
88 | grid.gridView.on('update:finished', function (error) {
89 | if (error) {
90 | console.log('fail update');
91 | } else {
92 | console.log('success update');
93 | }
94 | });
95 |
96 | grid.gridView.on('data:edit', model => {
97 | console.log(`[Edit] ${JSON.stringify(model)}`);
98 | });
99 |
100 | grid.gridView.render({ fetch: true });
101 | grid.pagerView.render();
102 |
--------------------------------------------------------------------------------
/js/projection/row.js:
--------------------------------------------------------------------------------
1 | define([
2 | 'lib/underscore',
3 | 'lib/backbone',
4 | 'component/grid/projection/base',
5 | ], function (_, Backbone, BaseProjection) {
6 | var Model = BaseProjection.extend({
7 | defaults: {
8 | 'row.classes': {},
9 | },
10 | name: 'row',
11 | update: function (options) {
12 | if (Model.__super__.update.call(this, options)) {
13 | var selectedMap = {};
14 |
15 | _.each(this.get('row.check.list'), function(selectedIndex) {
16 | selectedMap[selectedIndex] = true;
17 | });
18 |
19 | var model = this.src.data;
20 | var rows = model.get('value');
21 |
22 | _.each(rows, row => {
23 | var type = _.chain(row).result('$metadata').result('type').value();
24 |
25 | if (_.isUndefined(type)) {
26 | row.$metadata = _.extend({}, row.$metadata, {
27 | type: 'row',
28 | });
29 | }
30 | });
31 |
32 | _.each(rows, (row) => {
33 | var classesRule = this.get('row.classes');
34 | var checkId = this.get('row.check.id');
35 | var checkboxAllow = model.get('row.check.allow');
36 | var classArr = [];
37 |
38 | if (selectedMap[row[checkId]]) {
39 | classArr.push('row-selected');
40 | }
41 |
42 | var originClass = _.chain(row)
43 | .result('$metadata')
44 | .result('attr')
45 | .result('class')
46 | .value();
47 |
48 | if (originClass) {
49 | classArr.push(originClass);
50 | }
51 |
52 |
53 | _.each(classesRule, (func, key) => {
54 | var type = _.chain(row).result('$metadata').result('type').value();
55 |
56 | if (_.isFunction(func) && func(row, type)) {
57 | classArr.push(key);
58 | }
59 | });
60 |
61 | //attr info from meta
62 | var originId = _.chain(row)
63 | .result('$metadate')
64 | .result('attr')
65 | .result('id')
66 | .value();
67 |
68 | if (_.isFunction(checkboxAllow) ? checkboxAllow(row) : checkboxAllow) {
69 | var uniqueId = model.get('a11y.selection.uniqueId');
70 | var id = row[checkId] || originId;
71 | var a11yId = uniqueId.concat(id);
72 | var role = 'row';
73 | }
74 |
75 | _.extend(row, {
76 | $metadata: {
77 | attr: _.pick({
78 | class: _.flatten(classArr).join(' '),
79 | id: a11yId,
80 | role: role,
81 | }, Boolean),
82 | },
83 | });
84 | });
85 |
86 | this.patch({ value: rows });
87 | }
88 | },
89 | });
90 |
91 | return Model;
92 | });
93 |
--------------------------------------------------------------------------------
/js/projection/column-queryable.js:
--------------------------------------------------------------------------------
1 | define([
2 | 'lib/underscore',
3 | 'lib/backbone',
4 | 'component/grid/projection/base',
5 | 'component/grid/schema/properties',
6 | 'component/grid/model/response',
7 | ], function (_, Backbone, BaseProjection /* , schemaProperties, Response */) {
8 | var Model = BaseProjection.extend({
9 | defaults: {
10 | 'column.skip': 0,
11 | 'column.take': Number.MAX_VALUE,
12 | 'column.lock': [],
13 | 'column.filter': function () {
14 | return true;
15 | },
16 | 'column.in': undefined,
17 | },
18 | name: 'column-queryable',
19 | update: function (options) {
20 | if (Model.__super__.update.call(this, options)) {
21 | var model = this.src.data;
22 | var take = this.get('column.take');
23 | var skip = this.get('column.skip');
24 | var lock = this.get('column.lock') || [];
25 | var filter = this.get('column.filter');
26 | // todo [akamel] consider renaming to column.select
27 | var $in = this.get('column.in');
28 | var select = _.size(model.get('columns')) ? _.map(model.get('columns'), function (i) {
29 | return i.property;
30 | }) : model.get('select');
31 | var unlocked = _.isFunction(filter) ? _.filter($in || select, filter) : ($in || select);
32 | var lookup = model.get('columns');
33 | var set = _.chain(unlocked).difference(lock).value();
34 | var col = set;
35 |
36 | if (!_.isNumber(take)) {
37 | take = Number.MAX_VALUE;
38 | }
39 |
40 | take = Math.max(take - _.size(lock), 0);
41 | if (_.size(set) < skip) {
42 | skip = 0;
43 | // this.set({ 'columns.skip' : 0 }, { silent : true });
44 | }
45 |
46 | // start query
47 | var skipped = _.first(set, skip);
48 |
49 | if (skip) {
50 | col = _.rest(set, skip);
51 | }
52 |
53 | var remaining = _.rest(col, take);
54 |
55 | col = _.union(lock, _.first(col, take));
56 | // end query
57 |
58 | _.each(col, function (element) {
59 | if (!lookup[element]) {
60 | lookup[element] = { property: element };
61 | }
62 | lookup[element].$lock = _.contains(lock, element);
63 | });
64 |
65 | this.patch({
66 | 'select': col,
67 | // todo [akamel] rename to column.in???
68 | // , 'columns.select' : set
69 | 'columns.skipped': skipped,
70 | 'columns.remaining': remaining,
71 | // , 'columns.count' : _.size(res)
72 | // todo [akamel] do we still need to update skip?
73 | 'column.skip': skip,
74 | });
75 | } else {
76 | // todo [akamel] unset our properties only
77 | // this.unset();
78 | }
79 | },
80 | });
81 |
82 | return Model;
83 | });
84 |
--------------------------------------------------------------------------------