UNPKG

decks

Version:

JavaScript UI library for viewing collections of items.

512 lines (466 loc) 16.8 kB
var _ = require("lodash"); var binder = require("./events/binder"); /** * Contains the logic for rendering a layout of items * * Can be subclassed to implement layout methods, or can be passed * layout methods in the Layout constructor options. * * @class * @mixes binder * @param {?Object} options - layout options * @param {?Function} options.getRenders - implementation of getRenders method * @param {?Function} options.initializeRender - implementation of initializeRender method * @param {?Function} options.loadRender - implementation of loadRender method * @param {?Function} options.unloadRender - implementation of unloadRender method * @param {?Function} options.getShowAnimation - implementation of getShowAnimation method * @param {?Function} options.getHideAnimation - implementation of getHideAnimation method * @param {?Function} options.setShowAnimation - implementation of setShowAnimation method * @param {?Function} options.setHideAnimation - implementation of setHideAnimation method * @returns {Layout} */ function Layout(options) { if (!(this instanceof Layout)) { return new Layout(options); } options = _.merge({}, this.defaultOptions, options); // Allow the caller to create a Layout implementation without subclassing Layout - // just by passing in implementations of the key methods in options _.each(this.getOverridables(), function(key) { if (options[key]) { this[key] = options[key]; } }, this); } _.extend(Layout.prototype, binder, /** @lends Layout.prototype */ { /** * Default options for a Layout instance */ defaultOptions: { }, /** * List of method names that can be overridden by passing them in via * options properties in the constructor. You can also override these * properties by subclassing {@link Layout}. */ getOverridables: function() { return [ // Render-related methods "getRenders", "initializeRender", "shouldLoadRender", "shouldUnloadRender", "loadRender", "unloadRender", // Custom render-related methods "getCustomRenders", // Animation-related methods "getShowAnimation", "getHideAnimation", "setShowAnimation", "setHideAnimation", // Canvas/Gesture-related methods "getCanvasGestureOptions", "getMoveToElementOffsets", // Emitter event handlers "onViewportItemDrawing", "onViewportItemsDrawing", "onViewportRenderDrawing", "onViewportRenderErasing", "onViewportRenderDrawn", "onViewportRenderErased", "onViewportAllRendersDrawn", // TODO: add custom render event handlers? "onCanvasBoundsSet", "onFrameBoundsSet", "onItemCollectionFilterSet", "onItemCollectionSortBySet", "onItemCollectionReversedSet", "onItemCollectionIndexed" ]; }, /** * Events that all Layout instances will bind to, so the layout can receive * notifications when certain decks events occur. */ getEmitterEvents: function() { return { "viewport:item:drawing": "onViewportItemDrawing", "viewport:items:drawing": "onViewportItemsDrawing", //"viewport:render:drawing": "onViewportRenderDrawing", //"viewport:render:erasing": "onViewportRenderErasing", //"viewport:render:drawn": "onViewportRenderDrawn", //"viewport:render:erased": "onViewportRenderErased", "viewport:all:renders:drawn": "onViewportAllRendersDrawn", "canvas:bounds:set": "onCanvasBoundsSet", "frame:bounds:set": "onFrameBoundsSet", "item:collection:filter:set": "onItemCollectionFilterSet", "item:collection:sort:by:set": "onItemCollectionSortBySet", "item:collection:reversed:set": "onItemCollectionReversedSet", "item:collection:indexed": "onItemCollectionIndexed" }; }, /** * Destroys the layout (no-op by default) * * @return {undefined} */ destroy: _.noop, /** * Creates the "render" or "renders" for a given {@link Item} during a (re-)drawing cycle. * * A "render" is basically an instruction to the {@link Viewport} on where and how to draw * the item in the * {@link Canvas}. An {@link Item} can be drawn in the {@link Canvas} one * time, or multiple times, which is specified by how many render objects this method returns. * * The {@link Viewport} invokes this method for an/each {@link Item} when a drawing * cycle is happening. This method should return a "render" object (which is a set of * DOM style properties to apply to the render's element, along with animation options, * like duration, easing, delay, etc.), or an array of render objects. * * The Viewport will reconcile any existing renders/elements for the given {@link Item}, and * eventually animate an element to the property values listed in "transform", with the * animation controlled by the options in "animateOptions". * * This method is abstract at this level - it must be implemented by either passing * an options.getRenders function value into the {@link Layout} constructor, or * creating a subclass of {@link Layout} that implements this method on itself, or its * prototype. * * A render object must have "transform" and "animateOptions" properties at a minimum, * but can also have any other arbitrary properties that are needed for loading or unloading * the render at a later time (e.g. in load/unloadRender). * * A render might look like this: * * @abstract * @param {!Item} item - Item for which to create renders * @param {!Object} options - Other options provided by the {@link Viewport} * @returns {(Object|Object[])} - The render object or array of render objects for the {@link Item} * @example Example render object created by Layout#getRenders: * { * transform: { * top: 20, * left: 20, * width: 200, * height: 150, * rotateZ: 20 * }, * animateOptions: { * duration: 200, * easing: "easeInOutExpo" * }, * someLayoutSpecificProperty: "some value", * } */ getRenders: function getRenders(/*item, options*/) { throw new Error("Layout#getRenders: not implemented"); }, /** * Allows the layout to initialize a new render element, when a new render element is needed * for a render. * * @param {Object} render - The render object that was just created * @returns {undefined} */ initializeRender: function initializeRender(render, options) { // Call unloadRender for this by default, as these methods are probably similar this.unloadRender(render, options); }, /** * Returns whether the given render should be loaded at the time of invocation. * The {@link Layout} can implement this method if there are cases where a render * might not be normally loaded, but should be. * * @param {!Object} render - render object to check for loading * @param {!Object} options - hash of all deck-related objects * @return {undefined} */ shouldLoadRender: function shouldLoadRender(/*render, options*/) { return true; }, /** * Contains the logic to load the contents of a render (e.g. load an image in the render element) * * @param {Object} render - The render object to load * @returns {undefined} */ loadRender: function loadRender(/*render, options*/) { throw new Error("Layout#loadRender not implemented"); }, /** * Returns whether the given render should be unloaded at the time of invocation. * The {@link Layout} can implement this method if there are cases where a render * should be unloaded (e.g. to save on memory). * * @param {!Object} render - render object to check for unloading. * @param {!Object} options - hash of all deck-related objects * @return {undefined} */ shouldUnloadRender: function shouldUnloadRender(/*render, options*/) { return false; }, /** * Contains the logic to unload the contents of a render (e.g. remove the DOM content of a render element) * * @param {Object} render - The render object to unload * @param {Object} options * @returns {undefined} */ unloadRender: function unloadRender(/*render, options*/) { throw new Error("Layout#unloadRender not implemented"); }, /** * Event handler which informs the {@link Layout} that a render cycle is about to start for a * single {@link Item}. * * @param {DecksEvent} e - event object * @param {string} e.type - the event type * @param {Viewport} e.sender - the sender of the event (Viewport instance) * @param {Item} e.data - the {@link Item} that is about to be drawn (animated) * @return {undefined} */ onViewportItemDrawing: _.noop, /** * Event handler which informs the {@link Layout} that a render cycle is about to start for a * multiple {@link Item}s. * * @param {DecksEvent} e - event object * @param {string} e.type - the event type * @param {Viewport} e.sender - the sender of the event (Viewport instance) * @param {Item[]} e.data - the {@link Item}s that are about to be drawn (animated) * @return {undefined} */ onViewportItemsDrawing: _.noop, /** * Event handler which informs the {@link Layout} that a render is about to start drawing (animating). * The {@link Layout} can use this method to modify the render/element before the animation starts. * * @param {DecksEvent} e - event object * @param {string} e.type - the event type * @param {Viewport} e.sender - the sender of the event (Viewport instance) * @param {Object} e.data - the "render" object that is about to be drawn (animated) * @return {undefined} */ onViewportRenderDrawing: _.noop, /** * Event handler which informs the {@link Layout} that a render is about to start erasing (animating * to a hidden state before being removed).) * The {@link Layout} can use this method to modify the render/element before the animation starts. * * @param {DecksEvent} e - event object * @param {string} e.type - the event type * @param {Viewport} e.sender - the sender of the event (Viewport instance) * @param {Object} e.data - the "render" object that is about to be drawn (animated) * @return {undefined} */ onViewportRenderErasing: _.noop, /** * Event handler which informs the {@link Layout} when a single render has finished animating. * * @param {DecksEvent} e - event object * @return {undefined} */ onViewportRenderDrawn: _.noop, /** * Event handler which informs the {@link Layout} when a single render has finished its hide animation. * * @param {DecksEvent} e - event object * @return {undefined} */ onViewportRenderErased: _.noop, /** * Event handler which informs the {@link Layout} when a all renders in one drawing cycle have finished animating. * * @param {DecksEvent} e - event object * @return {undefined} */ onViewportAllRendersDrawn: _.noop, /** * Event handler which informs the {@link Layout} when the {@link Canvas} bounds have been set. * * @param {DecksEvent} e - event object * @return {undefined} */ onCanvasBoundsSet: _.noop, /** * Event handler which informs the {@link Layout} when the {@link Frame} bounds have been set. * * @param {DecksEvent} e - event object * @return {undefined} */ onFrameBoundsSet: _.noop, /** * Event handler which informs the {@link Layout} when the {@link ItemCollection} filter has been set. * * @param {DecksEvent} e - event object * @return {undefined} */ onItemCollectionFilterSet: _.noop, /** * Event handler which informs the {@link Layout} when the {@link ItemCollection} sort by function has been set. * * @param {DecksEvent} e - event object * @return {undefined} */ onItemCollectionSortBySet: _.noop, /** * Event handler which informs the {@link Layout} when the {@link ItemCollection} reversed flag has been set. * * @param {DecksEvent} e - event object * @return {undefined} */ onItemCollectionReversedSet: _.noop, /** * Event handler which informs the {@link Layout} when the {@link ItemCollection} has been (re-)indexed.. * * @param {DecksEvent} e - event object * @return {undefined} */ onItemCollectionIndexed: _.noop, /** * Gets the animation to use/merge when showing a render. * This would typically be transform properties that ensure the element * is fully visible (e.g. opacity: 1, scaleX: 1, scaleY: 1, display: auto, etc.) * * Override this method in a {@link Layout} subclass or with {@link Layout} options * to provide a custom "show" animation. */ getShowAnimation: function getShowAnimation() { return { transform: { //opacity: 1, scaleX: 1, scaleY: 1, rotateZ: 0 }, animateOptions: { duration: 400, display: "auto" } }; }, /** * Gets the base animation to use/merge when hiding (removing) a render * This is typically the opposite of the show animation transform. E.g. * if the show animation sets opacity: 1, this might set opacity: 0. * * Override this method in a {@link Layout} subclass or with {@link Layout} options * to provide a custom "hide" animation. */ getHideAnimation: function getHideAnimation() { return { transform: { //opacity: 0, scaleX: 0, scaleY: 0, rotateZ: 0 }, animateOptions: { duration: 400, display: "none" } }; }, /** * Sets the animation on a render to include the default show animation. * * @param {!Object} render - render on which to add the show animation * @returns {undefined} */ setShowAnimation: function setShowAnimation(render) { _.merge(render, this.getShowAnimation()); }, /** * Sets an animation on a render to remove the render. The implementation of this method should set * transform and animateOptions properties on the render. * * @param {!Object} render render on which to set animation * @return {undefined} */ setHideAnimation: function setHideAnimation(render) { _.merge(render, this.getHideAnimation()); }, /** * Returns an array of render objects, or a single render object, which are not associated * with items. This can be used to draw custom elements on the {@link Canvas}, like divider lines, * non-item-associated labels, etc. * * @param {Object} options - standard layout method options (viewport, frame, etc.) * @return {Object[]} - array of custom render objects */ getCustomRenders: function getCustomRenders(/*options*/) { return {}; }, /** * Gets the default gesture handler options to apply to render elements * * @param {!Object} render - render object * @param {!Object} options - standard {@link Layout} method options * @return {Object} - {@link GestureHandler} options to apply to the render */ getRenderGestureOptions: function getRenderGestureOptions() { return { gestures: { pan: { enabled: false, horizontal: true, vertical: false }, swipe: { enabled: false, horizontal: true, vertical: false } }, snapping: { toBounds: true, toNearestChildElement: false } }; }, /** * Gets the gesture handler options to use for the {@link Canvas} for this {@link Layout}. * * Each {@link Layout} might call for different {@link Canvas} gestures, like a vertical list Layout * might only allow vertical panning/swiping, whereas a horizontal list might only allow horizontal * scrolling. * * Override this method in a {@link Layout} subclass or {@link Layout} options. */ getCanvasGestureOptions: function getCanvasGestureOptions() { return {}; }, /** * Gets the {@link Layout}s preferences for how the canvas is resized when elements are added * or removed. * * @return {Object} - the resize options */ getCanvasBoundsOptions: function getCanvasBoundsOptions() { return { marginRight: 0, marginBottom: 0, smartMarginRight: true, smartMarginBottom: true, preventOverflowHorizontal: false, preventOverflowVertical: false, preventScrollbarHorizontal: false, preventScrollbarVertical: false, scrollbarSize: 20 }; }, /** * Gets extra offsets to apply when panning to an item * * @param {!Element} element - element being moved to * @return {undefined} */ getMoveToElementOffsets: function getMoveToElementOffsets(/*element*/) { return { x: 0, y: 0 }; } }); module.exports = Layout;