UNPKG

comindware.core.ui

Version:

Comindware Core UI provides the basic components like editors, lists, dropdowns, popups that we so desperately need while creating Marionette-based single-page applications.

307 lines (269 loc) • 10.5 kB
import _ from 'underscore'; const SelectableBehavior = { // SelectableBehavior.SingleSelect // ------------------ // A single-select mixin for Backbone.Collection, allowing a single // model to be selected within a collection. Selection of another // model within the collection causes the previous model to be // deselected. // ToDo: should update this.selected on collection reset or remove selected. SingleSelect: function(collection: Backbone.Collection) { this.selected = {}; this.collection = collection; }, // SelectableBehavior.MultiSelect // ----------------- // A mult-select mixin for Backbone.Collection, allowing a collection to // have multiple items selected, including `selectAll` and `selectNone` // capabilities. MultiSelect: function(collection: Backbone.Collection) { this.collection = collection; this.selected = {}; }, // SelectableBehavior.Selectable // ---------------- // A selectable mixin for Backbone.Model, allowing a model to be selected, // enabling it to work with SelectableBehavior.MultiSelect or on it's own Selectable: function() {} }; _.extend(SelectableBehavior.SingleSelect.prototype, { // Select a model, deselecting any previously // selected model select(model, ctrlPressed, shiftPressed, selectOnCursor, options) { if (model && this.selected && this.selected[model.cid] === model) { return; } this.deselect(undefined, options); this.selected[model.cid] = model; this.selected[model.cid].select(options); this.lastSelectedModel = model.cid; this.cursorCid = model.cid; this.trigger('select:one', model, options); }, // Deselect a model, resulting in no model // being selected deselect(model = this.selected[this.lastSelectedModel], options) { if (!this.lastSelectedModel) { return; } const lastSelectedModel = this.selected[this.lastSelectedModel]; if (!lastSelectedModel || lastSelectedModel !== model) { return; } lastSelectedModel.deselect(options); this.cursorCid = undefined; if (this.selected[this.lastSelectedModel] !== undefined) { //todo why we need to do this!? this.trigger('deselect:one', this.selected[this.lastSelectedModel], options); delete this.selected[this.lastSelectedModel]; this.lastSelectedModel = undefined; } }, getSelected() { // selectable not update selected on remove selected item const selectedModels = Object.values(this.selected).filter(function(selecteModel) { return this.has(selecteModel); }, this); const length = selectedModels.length; if (length > 1) { console.warn(`single select has ${length} selected models`); } return selectedModels[0]; } }); _.extend(SelectableBehavior.MultiSelect.prototype, { // Select a specified model, make sure the // model knows it's selected, and hold on to // the selected model. select(model, ctrlPressed, shiftPressed, selectOnCursor, options) { if (this.selected[model.cid]) { return; } this.selected[model.cid] = model; model.select(options); if (selectOnCursor === false) { this.pointOff(); model.pointTo(); this.lastPointedModel = model; this.cursorCid = model.cid; } calculateSelectedLength(this, options); }, // Select a specified model and update selection for the whole collection according to the key modifiers selectSmart(model, ctrlPressed, shiftPressed, selectOnCursor, options) { const collection = this; if (selectOnCursor === false) { collection.pointOff(); model.pointTo(); collection.lastPointedModel = model; collection.cursorCid = model.cid; } else if (!ctrlPressed && !shiftPressed) { // with no hotkeys we select this item and deselect the others // collection.selectNone(); Object.values(collection.selected).forEach(selected => { if (selected !== model) { selected.deselect(options); } }); model.select(options); collection.lastSelectedModel = model.cid; collection.cursorCid = model.cid; } else if (shiftPressed) { // if shift or ctrl+shift is pressed we select the items in range [lastSelectedItem, thisItem] and deselect the others const lastSelectedModel = collection.lastSelectedModel; if (!lastSelectedModel) { // we select this item alone if this is the first click Object.values(collection.selected).forEach(selected => { if (selected !== model) { selected.deselect(options); } }); model.select(options); collection.cursorCid = model.cid; } else { // if not, we select the range let lastSelectedIndex = 0; let thisIndex = 0; collection.forEach((m, i) => { if (m.cid === lastSelectedModel) { lastSelectedIndex = i; } if (m === model) { thisIndex = i; } }); const startIndex = Math.min(lastSelectedIndex, thisIndex); const endIndex = Math.max(lastSelectedIndex, thisIndex); const models = collection.models; Object.values(collection.selected).forEach(selected => { if (selected !== model) { selected.deselect(options); } }); for (let i = startIndex; i <= endIndex; i++) { models[i].select(options); } collection.cursorCid = models[thisIndex].cid; } } else if (ctrlPressed) { // adding this item to the multiple selection list model.select(options); collection.lastSelectedModel = model.cid; } }, // Deselect a specified model, make sure the // model knows it has been deselected, and remove // the model from the selected list. deselect(model, options) { if (!this.selected[model.cid]) { return; } delete this.selected[model.cid]; model.deselect(options); calculateSelectedLength(this, options); }, // Select all models in this collection selectAll(options) { this.forEach(model => { model.select(options); }); calculateSelectedLength(this, options); }, // Deselect all models in this collection selectNone(options) { this.forEach(model => { model.deselect(options); }); calculateSelectedLength(this, options); }, pointOff() { this.lastPointedModel && this.lastPointedModel.pointOff(); delete this.lastPointedModel; }, // Toggle select all / none. If some are selected, it // will select all. If all are selected, it will select // none. If none are selected, it will select all. toggleSelectAll(options) { if (this.selectedLength === this.length) { this.selectNone(options); } else { this.selectAll(options); } }, getSelected() { // selectable not update selected on remove selected item return Object.values(this.selected).filter(function(selecteModel) { return this.has(selecteModel); }, this); } }); _.extend(SelectableBehavior.Selectable.prototype, { // Select this model, and tell our // collection that we're selected select(options) { if (this.selected) { return; } this.selected = true; this.trigger('selected', this, options); const collection = this.selectableCollection || this.collection; if (collection && collection.select) { collection.select(this, undefined, undefined, undefined, options); } }, // Deselect this model, and tell our // collection that we're deselected deselect(options) { if (!this.selected) { return; } this.selected = false; this.trigger('deselected', this, options); const collection = this.selectableCollection || this.collection; if (collection && collection.deselect) { collection.deselect(this, undefined, undefined, undefined, options); } }, pointTo() { this.pointed = true; this.trigger('pointed', this); }, pointOff() { this.pointed = false; this.trigger('unpointed', this); }, // Change selected to the opposite of what // it currently is toggleSelected(isSelect = !this.selected, options) { if (isSelect) { this.select(options); } else { this.deselect(options); } } }); // Helper Methods // -------------- // Calculate the number of selected items in a collection // and update the collection with that length. Trigger events // from the collection based on the number of selected items. const calculateSelectedLength = _.debounce((collection, options) => { collection.selectedLength = collection.parentCollection ? collection.models.filter(model => model.selected).length : collection.filter(model => model.selected).length; const selectedLength = collection.selectedLength; const length = collection.length; if (selectedLength === length) { collection.trigger('select:all', collection, options); return; } if (selectedLength === 0) { collection.trigger('select:none', collection, options); return; } if (selectedLength > 0 && selectedLength < length) { collection.trigger('select:some', collection, options); } }, 10); export default SelectableBehavior; export var Selectable = SelectableBehavior.Selectable; export var SingleSelect = SelectableBehavior.SingleSelect; export var MultiSelect = SelectableBehavior.MultiSelect;