UNPKG

ldx-widgets

Version:

widgets

343 lines (321 loc) 12.3 kB
/* Instructions for implementing this mixin Requires 2 properties to be defined in the component it is mixed into: @ROW_HEIGHT - defaults to 30, the static height of each list item @SPACE_ABOVE_CONTENT - defaults to 68, The space in the app above of the list (for calculating the list height) **OPTIONAL @SPACE_BELOW_CONTENT - defaults to 0, The space in the app below the list (for calculating the list height) Require the following props to be passed to the component it is mixed into: @props.records - the array of objects being rendered into the list @props.loadNextRecordset - a method that triggers the server GET for the next 'page' of records Additional info After a records GET is complete, the doneWithFetch method must be called EG, if the component with this mixin is the 'results' ref, you would call the following from it's parent @refs.results.doneWithFetch({recordsExhausted, newSearch}) The options object has two boolean attributes: recordsExhausted - means there are no more records to fetch from the server newSearch - means a new list is being rendered, and the scroll states should be reset For examples of what the rende method of the results component should look like, look at one of the components that implements this mixin. Public Methods to call from within render method calculateRange returns an object with start and end properties, which is the range of indexes from the records array that should be rendered buildLoadingRow returns an li component with a spinner for the end of the list buildScrollBar build the verical scrollbar, can pass optionaly className and key props */ (function() { var Animation, DOWN_ARROW, END, HEADER_HEIGHT, HOME, PAGE_DOWN, PAGE_UP, React, SCROLL_THUMB_MIN, Spinner, UP_ARROW, button, div, easing, li, ref, ref1; React = require('react'); Animation = require('ainojs-animation'); easing = require('ainojs-easing'); Spinner = React.createFactory(require('../components/spinner')); ref = require('../constants/keyboard'), HOME = ref.HOME, END = ref.END, PAGE_UP = ref.PAGE_UP, PAGE_DOWN = ref.PAGE_DOWN, UP_ARROW = ref.UP_ARROW, DOWN_ARROW = ref.DOWN_ARROW; ref1 = require('react-dom-factories'), div = ref1.div, button = ref1.button, li = ref1.li; SCROLL_THUMB_MIN = 25; HEADER_HEIGHT = 0; module.exports = { numOfScreens: 1, translate: 0, height: 0, getInitialState: function() { return { scrollY: 0, contentAreaHeight: 0, maxY: 0, noResults: false, recordsExhausted: false }; }, componentWillReceiveProps: function(nextProps) { if (!nextProps.records.length) { return this.setState({ noResults: true }); } }, getContentAreaHeight: function() { return window.innerHeight - this.SPACE_ABOVE_CONTENT - this.SPACE_BELOW_CONTENT; }, calculateContentAreaHeight: function() { return this.setState({ contentAreaHeight: this.getContentAreaHeight() }); }, calculateMaxY: function(visibleRowCount, recordsExhausted) { var buffer, contentAreaHeight, contentHeight; if (recordsExhausted == null) { recordsExhausted = false; } if (visibleRowCount != null) { buffer = recordsExhausted ? 0 : this.ROW_HEIGHT; contentHeight = (visibleRowCount * this.ROW_HEIGHT) + buffer; } contentAreaHeight = this.getContentAreaHeight(); if (contentHeight < contentAreaHeight) { return null; } else { return (0 - contentHeight) + contentAreaHeight; } }, componentWillMount: function() { if (this.ROW_HEIGHT == null) { this.ROW_HEIGHT = 30; } if (this.SPACE_ABOVE_CONTENT == null) { this.SPACE_ABOVE_CONTENT = 68; } if (this.SPACE_BELOW_CONTENT == null) { this.SPACE_BELOW_CONTENT = 0; } return this.oldLength = null; }, componentDidMount: function() { this.calculateContentAreaHeight(); window.addEventListener('resize', this.calculateContentAreaHeight); return window.addEventListener('keydown', this.handleScrollKeys); }, componentWillUnmount: function() { var ref2; window.removeEventListener('resize', this.calculateContentAreaHeight); window.removeEventListener('keydown', this.handleScrollKeys); if ((ref2 = this.animation) != null ? ref2.isAnimating() : void 0) { return this.animation.end(); } }, checkContentPosition: function(e) { var maxY, newLength, records, recordsExhausted, ref2, scrollY; if (!this.props.records.length) { return; } if (e != null) { this.handleScrollY(e); } ref2 = this.state, scrollY = ref2.scrollY, maxY = ref2.maxY, recordsExhausted = ref2.recordsExhausted; records = this.props.records; if (scrollY - maxY <= 250) { newLength = records.length; if (newLength !== this.oldLength && !recordsExhausted) { this.oldLength = newLength; return this.props.loadNextRecordset(); } } }, doneWithFetch: function(options) { var newSearch, recordsExhausted, state; recordsExhausted = options.recordsExhausted, newSearch = options.newSearch; state = { maxY: this.calculateMaxY(this.props.records.length, recordsExhausted), recordsExhausted: recordsExhausted }; if (newSearch) { state.scrollY = 0; this.oldLength = null; } return this.setState(state); }, calculateRange: function(records, buffer) { var contentAreaHeight, end, ref2, scrollY, start; if (buffer == null) { buffer = 1; } ref2 = this.state, contentAreaHeight = ref2.contentAreaHeight, scrollY = ref2.scrollY; start = Math.floor(-scrollY / this.ROW_HEIGHT) - buffer; end = Math.ceil((-(scrollY - contentAreaHeight)) / this.ROW_HEIGHT) + buffer; return { start: start >= 0 ? start : 0, end: end <= records.length ? end : records.length }; }, buildLoadingRow: function(index) { return li({ key: 'loadingRow', className: 'grid grid-pad full result-row loading-row', style: { height: this.ROW_HEIGHT, top: this.ROW_HEIGHT * index } }, Spinner({ length: 5 })); }, handleScrollKeys: function(e) { var ctrlKey, keyCode, metaKey; keyCode = e.keyCode, metaKey = e.metaKey, ctrlKey = e.ctrlKey; if (keyCode !== HOME && keyCode !== END && keyCode !== PAGE_UP && keyCode !== PAGE_DOWN && keyCode !== UP_ARROW && keyCode !== DOWN_ARROW) { return; } switch (keyCode) { case HOME: return this.animateScrollTo(0); case END: return this.animateScrollTo(this.state.maxY); case PAGE_UP: return this.page(1); case PAGE_DOWN: return this.page(-1); case UP_ARROW: if (metaKey || ctrlKey) { return this.page(1); } else { return this.handleScrollY({ deltaY: -this.ROW_HEIGHT }, this.checkContentPosition); } break; case DOWN_ARROW: if (metaKey || ctrlKey) { return this.page(-1); } else { return this.handleScrollY({ deltaY: this.ROW_HEIGHT }, this.checkContentPosition); } } }, page: function(direction) { var maxY, newScrollPos, pageDistance, ref2, scrollY; ref2 = this.state, scrollY = ref2.scrollY, maxY = ref2.maxY; pageDistance = window.innerHeight - this.SPACE_ABOVE_CONTENT - this.SPACE_BELOW_CONTENT; newScrollPos = scrollY + (pageDistance * direction); newScrollPos = newScrollPos > 0 ? 0 : newScrollPos; newScrollPos = newScrollPos < maxY ? maxY : newScrollPos; return this.animateScrollTo(newScrollPos); }, animateScrollTo: function(newScrollPos) { var ref2, scrollY; if ((ref2 = this.animation) != null ? ref2.isAnimating() : void 0) { this.animation.end(); } scrollY = this.state.scrollY; return this.animation = new Animation({ duration: 300, easing: easing('easeOutCirc') }).init({ scrollY: scrollY }).on('frame', this.onFrame).on('complete', this.checkContentPosition).animateTo({ scrollY: newScrollPos }); }, onFrame: function(e) { return this.setState(e.values); }, clickScroll: function(e) { this.scrollPos = e.clientY; document.addEventListener('mousemove', this.handleMouseMoveY, false); return document.addEventListener('mouseup', this.handleMouseUpY, false); }, handleMouseUpY: function(e) { document.removeEventListener('mousemove', this.handleMouseMoveY, false); document.removeEventListener('mouseup', this.handleMouseUpY, false); this.handleScrollY({ deltaY: 0 - (this.scrollPos - e.clientY) * this.numOfScreens }); return this.checkContentPosition(); }, handleMouseMoveY: function(e) { this.handleScrollY({ deltaY: 0 - (this.scrollPos - e.clientY) * this.numOfScreens }); return this.scrollPos = e.clientY; }, handleScrollY: function(e, cb) { var deltaY, maxY, newState, newY, ref2, scrollY, y; ref2 = this.state, scrollY = ref2.scrollY, maxY = ref2.maxY; deltaY = e.deltaY; newState = {}; if (deltaY !== 0) { y = scrollY - deltaY; newY = y < 0 ? y : 0; newY = newY > maxY ? newY : maxY; newState.scrollY = newY; } return this.setState(newState, function() { return typeof cb === "function" ? cb() : void 0; }); }, handleTrackClick: function(e) { var clientY, deltaY, maxY, ref2, scrollY; ref2 = this.state, scrollY = ref2.scrollY, maxY = ref2.maxY; clientY = e.clientY; deltaY = (function() { if (clientY - this.SPACE_ABOVE_CONTENT - this.SPACE_BELOW_CONTENT > this.height + this.translate) { return this.height; } else if (clientY - this.SPACE_ABOVE_CONTENT - this.SPACE_BELOW_CONTENT < this.translate) { return -this.height; } else { return null; } })(); if (deltaY == null) { return; } this.handleScrollY({ deltaY: deltaY * 2 }); return this.scrollPos = clientY; }, buildScrollBar: function(props) { var availableHeight, availableTrackSpace, className, contentAreaHeight, finalClass, key, maxY, ref2, scrollPercent, scrollY, visibleClass; if (props == null) { props = {}; } className = props.className, key = props.key; ref2 = this.state, scrollY = ref2.scrollY, maxY = ref2.maxY, contentAreaHeight = ref2.contentAreaHeight; finalClass = "thumb-btn-vertical-track"; if (className != null) { finalClass += " " + className; } availableHeight = contentAreaHeight; if (availableHeight) { this.numOfScreens = (-maxY + contentAreaHeight) / availableHeight; } visibleClass = this.numOfScreens <= 1 ? ' is-hidden' : ''; this.height = availableHeight / this.numOfScreens; if (this.height < SCROLL_THUMB_MIN) { this.height = SCROLL_THUMB_MIN; } availableTrackSpace = availableHeight - this.height; scrollPercent = scrollY / maxY; this.translate = scrollPercent * availableTrackSpace; return div({ key: key || 'vScroll', className: finalClass, onClick: this.handleTrackClick }, button({ key: 'thumb-btn-vertical', ref: 'scrollButton', className: 'thumb-btn-vertical' + visibleClass, type: 'button', style: { transform: "translateY(" + this.translate + "px)", WebkitTransform: "translateY(" + this.translate + "px)", msTransform: "translateY(" + this.translate + "px)", top: HEADER_HEIGHT + 1, height: this.height }, onMouseDown: this.clickScroll })); } }; }).call(this);