Comparing version 17.7.7 to 17.7.8
@@ -743,2 +743,5 @@ module.exports = { | ||
}, | ||
"util/isNonEmptyArray": { | ||
"js": "src/util/isNonEmptyArray.js" | ||
}, | ||
"widgets/Dropdown": { | ||
@@ -745,0 +748,0 @@ "js": "src/widgets/overlay/Dropdown.js", |
@@ -1156,2 +1156,6 @@ var Console = { | ||
export { Console, debug, Debug, renderFlag, prepareFlag, processDataFlag, cleanupFlag, menuFlag, focusFlag, internalFlag, shouldUpdateFlag, appDataFlag, tooltipsFlag, deprecatedFlag, destroyFlag, findFirst, findFirstChild, closest, closestParent, isFocused, isFocusedDeep, isFocusable, getFocusedElement, isDescendant, isSelfOrDescendant, Format, resolveMinMaxFractionDigits, trimFractionZeros, expandFatArrows, GlobalCacheIdentifier, innerTextTrim, isDigit, isPromise, isTouchDevice, KeyCode, parseStyle, quoteStr, scrollElementIntoView, shallowEquals, appLoopFlag, vdomRenderFlag, now, Timing, dateDiff, zeroTime, monthStart, lowerBoundCheck, upperBoundCheck, maxDate, minDate, sameDate, hslToRgb, parseColor, parseHexColor, parseRgbColor, parseHslColor, rgbToHex, rgbToHsl, getVendorPrefix, stopPropagation, preventDefault, getSearchQueryPredicate, escapeSpecialRegexCharacters, browserSupportsPassiveEventHandlers, enableTouchEventDetection, isTouchEvent, debounce, throttle, SubscriberList, findScrollableParent, getScrollerBoundingClientRect }; | ||
function isNonEmptyArray(x) { | ||
return Array.isArray(x) && x.length > 0; | ||
} | ||
export { Console, debug, Debug, renderFlag, prepareFlag, processDataFlag, cleanupFlag, menuFlag, focusFlag, internalFlag, shouldUpdateFlag, appDataFlag, tooltipsFlag, deprecatedFlag, destroyFlag, findFirst, findFirstChild, closest, closestParent, isFocused, isFocusedDeep, isFocusable, getFocusedElement, isDescendant, isSelfOrDescendant, Format, resolveMinMaxFractionDigits, trimFractionZeros, expandFatArrows, GlobalCacheIdentifier, innerTextTrim, isDigit, isPromise, isTouchDevice, KeyCode, parseStyle, quoteStr, scrollElementIntoView, shallowEquals, appLoopFlag, vdomRenderFlag, now, Timing, dateDiff, zeroTime, monthStart, lowerBoundCheck, upperBoundCheck, maxDate, minDate, sameDate, hslToRgb, parseColor, parseHexColor, parseRgbColor, parseHslColor, rgbToHex, rgbToHsl, getVendorPrefix, stopPropagation, preventDefault, getSearchQueryPredicate, escapeSpecialRegexCharacters, browserSupportsPassiveEventHandlers, enableTouchEventDetection, isTouchEvent, debounce, throttle, SubscriberList, findScrollableParent, getScrollerBoundingClientRect, isNonEmptyArray }; |
@@ -0,0 +0,0 @@ # Third-Party Software Licenses |
{ | ||
"name": "cx", | ||
"version": "17.7.7", | ||
"version": "17.7.8", | ||
"description": "Advanced JavaScript UI framework for admin and dashboard applications with ready to use grid, form and chart components.", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
@@ -0,0 +0,0 @@ export function isSelector(config) { |
@@ -18,4 +18,3 @@ import {DataAdapter} from './DataAdapter'; | ||
mapRecords(context, instance, records, parentStore, recordsBinding) { | ||
var result = []; | ||
var writable = parentStore && recordsBinding; | ||
let result = []; | ||
@@ -27,45 +26,6 @@ if (Array.isArray(records)) | ||
return; | ||
let record = this.mapRecord(context, instance, data, parentStore, recordsBinding, index); | ||
var recordStore = this.map.get(data); | ||
if (writable) { | ||
if (!recordStore) | ||
recordStore = new ExposedRecordView({ | ||
store: parentStore, | ||
collectionBinding: recordsBinding, | ||
itemIndex: index, | ||
recordName: this.recordName, | ||
indexName: this.indexName, | ||
immutable: this.immutable | ||
}); | ||
else { | ||
recordStore.setStore(parentStore); | ||
recordStore.setIndex(index); | ||
} | ||
} else { | ||
if (!recordStore) | ||
recordStore = new ReadOnlyDataView({ | ||
store: parentStore, | ||
data: { | ||
[this.recordName]: data, | ||
[this.indexName]: index | ||
}, | ||
immutable: this.immutable | ||
}); | ||
else { | ||
recordStore.setStore(parentStore); | ||
} | ||
} | ||
if (typeof data == 'object') | ||
this.map.set(data, recordStore); | ||
//TODO: filter | ||
result.push({ | ||
store: recordStore, | ||
index: index, | ||
data: data, | ||
type: 'data', | ||
key: this.keyField ? data[this.keyField] : index | ||
}); | ||
result.push(record); | ||
}); | ||
@@ -79,2 +39,46 @@ | ||
mapRecord(context, instance, data, parentStore, recordsBinding, index) { | ||
let recordStore = this.map.get(data); | ||
let writable = parentStore && recordsBinding; | ||
if (writable) { | ||
if (!recordStore) | ||
recordStore = new ExposedRecordView({ | ||
store: parentStore, | ||
collectionBinding: recordsBinding, | ||
itemIndex: index, | ||
recordName: this.recordName, | ||
indexName: this.indexName, | ||
immutable: this.immutable | ||
}); | ||
else { | ||
recordStore.setStore(parentStore); | ||
recordStore.setIndex(index); | ||
} | ||
} else { | ||
if (!recordStore) | ||
recordStore = new ReadOnlyDataView({ | ||
store: parentStore, | ||
data: { | ||
[this.recordName]: data, | ||
[this.indexName]: index | ||
}, | ||
immutable: this.immutable | ||
}); | ||
else { | ||
recordStore.setStore(parentStore); | ||
} | ||
} | ||
if (typeof data == 'object') | ||
this.map.set(data, recordStore); | ||
return { | ||
store: recordStore, | ||
index: index, | ||
data: data, | ||
type: 'data', | ||
key: this.keyField ? data[this.keyField] : index | ||
}; | ||
} | ||
setFilter(filterFn) { | ||
@@ -81,0 +85,0 @@ this.filterFn = filterFn; |
@@ -0,0 +0,0 @@ var cssHelperCache = {}; |
@@ -0,0 +0,0 @@ import {dateDiff} from './dateDiff'; |
@@ -0,0 +0,0 @@ import {dateDiff} from './dateDiff'; |
@@ -0,0 +0,0 @@ import {dateDiff} from './dateDiff'; |
@@ -29,2 +29,3 @@ export * from './Console'; | ||
export * from './findScrollableParent'; | ||
export * from './getScrollerBoundingClientRect'; | ||
export * from './getScrollerBoundingClientRect'; | ||
export * from './isNonEmptyArray'; |
@@ -29,2 +29,3 @@ export * from './Console'; | ||
export * from './findScrollableParent'; | ||
export * from './getScrollerBoundingClientRect'; | ||
export * from './getScrollerBoundingClientRect'; | ||
export * from './isNonEmptyArray'; |
@@ -85,2 +85,17 @@ import * as Cx from '../../core'; | ||
/** Set to `true` to enable row caching. This greatly improves grid performance | ||
on subsequent render operations, however, only changes on `records` | ||
are allowed. If grid rows display any data outside `records`, changes on that | ||
data will be ignored. */ | ||
cached?: boolean, | ||
/** Render only rows visible on the screen. */ | ||
buffered?: boolean, | ||
/** Specifies how many rows should be visible on the screen */ | ||
bufferSize?: number, | ||
/** Specifies how many rows needs to be scrolled in order to recalculate buffer */ | ||
bufferStep?: number, | ||
/** | ||
@@ -87,0 +102,0 @@ * Set to true to lock column widths after the first render. |
@@ -19,8 +19,4 @@ import {Widget, VDOM, getContent} from '../../ui/Widget'; | ||
import { | ||
ddMouseDown, | ||
ddMouseUp, | ||
ddDetect, | ||
initiateDragDrop, | ||
registerDropZone, | ||
isDragHandleEvent | ||
} from '../drag-drop/ops'; | ||
@@ -32,2 +28,4 @@ | ||
import {SubscriberList} from '../../util/SubscriberList'; | ||
import {RenderingContext} from '../../ui/RenderingContext'; | ||
import {isNonEmptyArray} from '../../util/isNonEmptyArray'; | ||
@@ -58,2 +56,5 @@ export class Grid extends Widget { | ||
if (this.buffered) | ||
this.scrollable = true; | ||
if (this.records && this.records.bind) | ||
@@ -126,2 +127,5 @@ this.recordsBinding = Binding.get(this.records.bind); | ||
if (!Array.isArray(data.records)) | ||
data.records = []; | ||
if (state.sorters && !data.sorters) | ||
@@ -208,4 +212,4 @@ data.sorters = state.sorters; | ||
//do not process rows in cached mode if nothing has changed; | ||
if (!this.cached || instance.shouldUpdate) { | ||
//do not process rows in buffered mode or cached mode if nothing has changed; | ||
if (!this.buffered && (!this.cached || instance.shouldUpdate)) { | ||
let dragHandles = context.dragHandles, | ||
@@ -238,3 +242,3 @@ record, | ||
prepare(context, instance) { | ||
if (!this.cached || instance.shouldUpdate) { | ||
if (!this.buffered && (!this.cached || instance.shouldUpdate)) { | ||
for (let i = 0; i < instance.records.length; i++) { | ||
@@ -251,3 +255,3 @@ let record = instance.records[i]; | ||
cleanup(context, instance) { | ||
if (!this.cached || instance.shouldUpdate) { | ||
if (!this.buffered && (!this.cached || instance.shouldUpdate)) { | ||
for (let i = 0; i < instance.records.length; i++) { | ||
@@ -310,3 +314,4 @@ let record = instance.records[i]; | ||
this.renderRows(context, instance); | ||
if (!this.buffered) | ||
this.renderRows(context, instance); | ||
@@ -536,7 +541,17 @@ refs.header = {}; | ||
let sorters = !this.remoteSort && data.sorters; | ||
this.dataAdapter.setFilter(filter); | ||
this.dataAdapter.sort(!this.remoteSort && data.sorters); | ||
this.dataAdapter.sort(sorters); | ||
//if no filtering or sorting applied, let the component maps records on demand | ||
if (this.buffered && !filter && !isNonEmptyArray(sorters)) | ||
return null; | ||
return this.dataAdapter.getRecords(context, instance, data.records, store); | ||
} | ||
mapRecord(context, instance, data, index) { | ||
return this.dataAdapter.mapRecord(context, instance, data, instance.store, this.recordsBinding, index); | ||
} | ||
} | ||
@@ -555,2 +570,5 @@ | ||
Grid.prototype.cached = false; | ||
Grid.prototype.buffered = false; | ||
Grid.prototype.bufferStep = 15; | ||
Grid.prototype.bufferSize = 60; | ||
@@ -568,4 +586,6 @@ Widget.alias('grid', Grid); | ||
focused: widget.focused, | ||
dragInsertionIndex: null | ||
} | ||
dragInsertionIndex: null, | ||
start: 0, | ||
end: widget.bufferSize | ||
}; | ||
} | ||
@@ -578,39 +598,62 @@ | ||
let {dragSource} = data; | ||
let {dragged, start, end, cursor} = this.state; | ||
let children = []; | ||
instance.records.forEach((record, i) => { | ||
if (record.type == 'data') { | ||
let {data, store, index, key, row} = record; | ||
let dragged = this.state.dragged && (row.selected || record == this.state.dragged); | ||
let mod = { | ||
selected: row.selected, | ||
dragged: dragged, | ||
draggable: dragSource && row.dragHandles.length == 0, | ||
cursor: i == this.state.cursor | ||
}; | ||
children.push( | ||
<GridRowComponent | ||
key={key} | ||
className={CSS.expand(row.data.classNames, CSS.state(mod))} | ||
store={store} | ||
dragSource={dragSource} | ||
instance={row} | ||
grid={instance} | ||
record={record} | ||
parent={this} | ||
cursorIndex={i} | ||
selected={row.selected} | ||
isBeingDragged={dragged} | ||
cursor={mod.cursor} | ||
shouldUpdate={row.shouldUpdate} | ||
> | ||
{record.vdom} | ||
</GridRowComponent> | ||
); | ||
} | ||
else | ||
children.push(record.vdom); | ||
}); | ||
let addRow = (record, i) => { | ||
let {store, key, row} = record; | ||
let isDragged = dragged && (row.selected || record == dragged); | ||
let mod = { | ||
selected: row.selected, | ||
dragged: isDragged, | ||
draggable: dragSource && row.dragHandles.length == 0, | ||
cursor: i == cursor | ||
}; | ||
children.push( | ||
<GridRowComponent | ||
key={key} | ||
className={CSS.expand(row.data.classNames, CSS.state(mod))} | ||
store={store} | ||
dragSource={dragSource} | ||
instance={row} | ||
grid={instance} | ||
record={record} | ||
parent={this} | ||
cursorIndex={i} | ||
selected={row.selected} | ||
isBeingDragged={dragged} | ||
cursor={mod.cursor} | ||
shouldUpdate={row.shouldUpdate} | ||
> | ||
{record.vdom} | ||
</GridRowComponent> | ||
); | ||
}; | ||
if (widget.scrollable && widget.buffered) { | ||
let source = instance.records || data.records; | ||
let context = new RenderingContext(); | ||
let isRecordSelected = widget.selection.getIsSelectedDelegate(instance.store); | ||
source.slice(start, end).forEach((r, i) => { | ||
let record = source == instance.records ? r : widget.mapRecord(context, instance, r, start + i); | ||
let row = record.row = instance.getChild(context, widget.row, record.key, record.store); | ||
let wasSelected = row.selected; | ||
record.vdom = row.vdom = row.vdom && widget.cached && row.cacheBuster === record.data ? row.vdom : Widget.renderInstance(row); | ||
row.selected = isRecordSelected(record.data, record.index); | ||
row.cacheBuster = record.data; | ||
if (row.selected != wasSelected) | ||
row.shouldUpdate = true; | ||
addRow(record, start + i); | ||
}); | ||
} | ||
else { | ||
instance.records.forEach((record, i) => { | ||
if (record.type == 'data') { | ||
addRow(record, i); | ||
} | ||
else | ||
children.push(record.vdom); | ||
}); | ||
} | ||
if (this.state.dragInsertionIndex != null) { | ||
@@ -635,3 +678,3 @@ let dragInsertionRow = ( | ||
if (instance.records.length == 0 && data.emptyText) { | ||
if (children.length == 0 && data.emptyText) { | ||
children.push( | ||
@@ -651,2 +694,8 @@ <tbody | ||
let marginTop = -(this.headerHeight || 0), marginBottom = null; | ||
if (this.rowHeight > 0) { | ||
marginTop += this.rowHeight * start; | ||
marginBottom = (data.records.length - (start + children.length)) * this.rowHeight; | ||
} | ||
content.push( | ||
@@ -658,5 +707,8 @@ <div | ||
}} | ||
style={{ | ||
marginTop: this.headerHeight | ||
}} | ||
tabIndex={widget.selectable ? 0 : null} | ||
onScroll={::this.onScroll} | ||
className={CSS.element(baseClass, 'scroll-area')} | ||
className={CSS.element(baseClass, 'scroll-area', { buffered: !!this.dom.fixedHeader })} | ||
onKeyDown={::this.handleKeyDown} | ||
@@ -671,2 +723,6 @@ onMouseLeave={::this.handleMouseLeave} | ||
}} | ||
style={{ | ||
marginTop, | ||
marginBottom | ||
}} | ||
> | ||
@@ -680,3 +736,4 @@ {this.props.header} | ||
if (this.props.fixedHeader) | ||
content.push(<div key="fh" | ||
content.push(<div | ||
key="fh" | ||
ref={el => { | ||
@@ -686,3 +743,4 @@ this.dom.fixedHeader = el | ||
className={CSS.element(baseClass, 'fixed-header')} | ||
style={{display: this.scrollWidth > 0 ? 'block' : 'none'}}> | ||
style={{display: this.scrollWidth > 0 ? 'block' : 'none'}} | ||
> | ||
<table> | ||
@@ -704,4 +762,24 @@ {this.props.fixedHeader} | ||
} | ||
this.checkBuffer(); | ||
} | ||
checkBuffer() { | ||
let {widget, data} = this.props.instance; | ||
if (widget.buffered && this.rowHeight > 0 && !this.pending) { | ||
let start = Math.round(this.dom.scroller.scrollTop / this.rowHeight - widget.bufferStep); | ||
start = Math.round(start / widget.bufferStep) * widget.bufferStep; | ||
start = Math.max(0, start); | ||
start = Math.min(start, data.records.length - widget.bufferSize); | ||
let end = start + widget.bufferSize; | ||
if (this.state.end != end) { | ||
this.pending = true; | ||
this.setState({start, end}, () => { | ||
this.pending = false; | ||
setTimeout(::this.checkBuffer, 0); | ||
}); | ||
} | ||
} | ||
} | ||
shouldComponentUpdate(props, state) { | ||
@@ -713,2 +791,7 @@ return props.shouldUpdate !== false || state != this.state; | ||
this.componentDidUpdate(); | ||
if (this.headerHeight) { | ||
this.dom.table.style.marginTop = `${-this.headerHeight}px`; | ||
this.dom.scroller.style.marginTop = `${this.headerHeight}px`; | ||
} | ||
let {instance} = this.props; | ||
@@ -840,5 +923,5 @@ let {widget} = instance; | ||
componentWillReceiveProps(props) { | ||
let {records, widget} = props.instance; | ||
let {data, widget} = props.instance; | ||
this.setState({ | ||
cursor: Math.max(Math.min(this.state.cursor, records.length - 1), widget.selectable && this.state.focused ? 0 : -1) | ||
cursor: Math.max(Math.min(this.state.cursor, data.records.length - 1), widget.selectable && this.state.focused ? 0 : -1) | ||
}); | ||
@@ -875,3 +958,3 @@ } | ||
let resized = false; | ||
let resized = false, headerHeight = 0; | ||
@@ -893,7 +976,17 @@ if (this.dom.fixedHeader) { | ||
let headerHeight = this.dom.fixedHeader.offsetHeight; | ||
this.dom.table.style.marginTop = `${-headerHeight}px`; | ||
this.dom.scroller.style.marginTop = `${headerHeight}px`; | ||
headerHeight = this.dom.fixedHeader.offsetHeight; | ||
} | ||
if (widget.buffered) { | ||
let count = Math.min(data.records.length, this.state.end - this.state.start); | ||
if (count == 0) { | ||
delete this.rowHeight; | ||
} else { | ||
this.rowHeight = Math.round((this.dom.table.offsetHeight - headerHeight) / count); | ||
this.checkBuffer(); | ||
} | ||
} | ||
this.headerHeight = headerHeight; | ||
if (resized) | ||
@@ -900,0 +993,0 @@ instance.fixedHeaderResizeEvent.notify(); |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
2021938
651
50050