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
text/typescript
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;