comindware.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.
333 lines (289 loc) • 12.4 kB
JavaScript
/**
* Developer: Ksenia Kartvelishvili
* Date: 15.04.2015
* Copyright: 2009-2016 Comindware®
* All Rights Reserved
* Published under the MIT license
*/
'use strict';
import { Handlebars } from 'lib';
import dropdown from 'dropdown';
import { helpers, comparators } from 'utils';
import template from './templates/membersBubbleEditor.hbs';
import BaseLayoutEditorView from './base/BaseLayoutEditorView';
import MemberModel from './impl/common/members/models/MemberModel';
import FakeInputModel from './impl/membersBubble/models/FakeInputModel';
import MembersCollection from './impl/common/members/collections/MembersCollection';
import ButtonView from './impl/membersBubble/views/ButtonView';
import factory from './impl/common/members/services/factory';
import formRepository from '../formRepository';
const defaultOptions = {
exclude: [],
maxQuantitySelected: null,
canDeleteMember: true
};
/**
* @name MembersBubbleEditorView
* @memberof module:core.form.editors
* @class Редактор для выбора коллекции пользователей. Поддерживаемый тип данных: массив идентификаторов пользователей
* (<code>String[]</code>). Например, <code>[ 'user.1', 'user.2', 'user.3' ]</code>. Список доступных пользователей
* береться из <code>core.services.CacheService</code>.
* @extends module:core.form.editors.base.BaseEditorView
* @param {Object} options Options object. All the properties of {@link module:core.form.editors.base.BaseEditorView BaseEditorView} class are also supported.
* @param {Boolean} [options.canDeleteMember=true] Возможно ли удалять добавленных пользователей.
* @param {String[]} [options.exclude] Массив идентификаторов пользователей, которые будут скрыты из списка доступных для выбора.
* @param {Number} [options.maxQuantitySelected] Максимальное количество пользователей, которое можно выбрать.
* */
formRepository.editors.MembersBubble = BaseLayoutEditorView.extend(/** @lends module:core.form.editors.MembersBubbleEditorView.prototype */{
initialize(options) {
if (options.schema) {
_.extend(this.options, defaultOptions, _.pick(options.schema, _.keys(defaultOptions)));
} else {
_.extend(this.options, defaultOptions, _.pick(options || {}, _.keys(defaultOptions)));
}
_.bindAll(this, '__onDropdownOpen');
this.__bindReqres();
this.__createViewModel();
this.__updateViewModel(this.getValue());
this.__updateFakeInputModel();
this.textFilterDelay = this.options.textFilterDelay || 0;
this.fetchDelayId = _.uniqueId('fetch-delay-id-');
this.value = this.getValue() || [];
},
__bindReqres() {
this.reqres = new Backbone.Wreqr.RequestResponse();
this.reqres.setHandler('member:select', this.__onMemberSelect, this);
this.reqres.setHandler('bubble:delete', this.__onBubbleDelete, this);
this.reqres.setHandler('bubble:delete:last', this.__onBubbleDeleteLast, this);
this.reqres.setHandler('input:search', this.__onInputSearch, this);
this.reqres.setHandler('input:up', this.__onInputUp, this);
this.reqres.setHandler('input:down', this.__onInputDown, this);
this.reqres.setHandler('button:click', this.__onButtonClick, this);
},
template: Handlebars.compile(template),
className: 'editor editor_bubble',
regions: {
dropdownRegion: '.js-dropdown-region'
},
onRender() {
if (this.dropdownView) {
this.stopListening(this.dropdownView);
}
this.dropdownView = dropdown.factory.createDropdown({
buttonView: ButtonView,
buttonViewOptions: {
model: this.viewModel,
reqres: this.reqres,
enabled: this.getEnabled() && !this.getReadonly() && this.options.canDeleteMember
},
panelView: factory.getMembersListView(),
panelViewOptions: {
collection: this.viewModel.get('available')
},
autoOpen: false
});
this.dropdownRegion.show(this.dropdownView);
this.listenTo(this.dropdownView, 'before:open', this.__onBeforeDropdownOpen);
this.listenTo(this.dropdownView, 'open', this.__onDropdownOpen);
this.listenTo(this.dropdownView, 'close', this.__onDropdownClose);
this.listenTo(this.dropdownView, 'panel:member:select', this.__onMemberSelect);
},
setValue(value) {
if (_.isUndefined(value) || value === null) {
value = [];
}
if ((this.value === value) || (JSON.stringify(this.value) === JSON.stringify(value))) {
return;
}
this.value = value;
this.__updateViewModel(value);
this.__triggerChange();
},
__applyFilter(value, immediate) {
const applyFilter = () => this.viewModel.get('available').applyTextFilter(value);
if (immediate) {
applyFilter();
} else {
helpers.setUniqueTimeout(this.fetchDelayId, applyFilter, this.textFilterDelay);
}
},
__onInputSearch(value) {
this.__applyFilter(value);
this.__onButtonClick();
},
__onInputUp() {
const collection = this.viewModel.get('available');
if (collection.models[0].selected) {
this.dropdownView.close();
this.__focusButton();
} else {
this.__sendPanelCommand('up');
}
},
__onInputDown() {
if (!this.dropdownView.isOpen) {
this.dropdownView.open();
} else {
this.__sendPanelCommand('down');
}
},
__onBeforeDropdownOpen() {
this.__applyFilter(undefined, true);
},
__onDropdownOpen() {
this.viewModel.get('available').selectFirst();
this.__focusButton();
this.onFocus();
},
__onDropdownClose() {
this.onBlur();
},
__onButtonClick() {
if (this.__canAddMember()) {
this.dropdownView.open();
}
},
__sendPanelCommand(command, options) {
if (this.dropdownView.isOpen) {
this.dropdownView.panelView.handleCommand(command, options);
}
},
__createViewModel() {
this.viewModel = new Backbone.Model();
const membersCollection = factory.createMembersCollection();
const members = membersCollection.reduce((memo, model) => {
memo[model.id] = model.toJSON();
return memo;
}, {});
this.viewModel.set('members', members);
const availableModels = new MembersCollection(new Backbone.Collection([], {
model: MemberModel
}), {
comparator: helpers.comparatorFor(comparators.stringComparator2Asc, 'name')
});
this.viewModel.set('available', availableModels);
const selectedModels = new Backbone.Collection([], {
model: MemberModel,
comparator: helpers.comparatorFor(comparators.stringComparator2Asc, 'name')
});
this.viewModel.set('selected', selectedModels);
},
__updateViewModel(selectedValues) {
const members = _.clone(this.viewModel.get('members'));
_.each(this.options.exclude, id => {
if (members[id]) {
delete members[id];
}
});
const selectedMembers = _.map(selectedValues, id => {
const model = members[id];
delete members[id];
return model;
});
const availableMembers = _.values(members);
const availableModels = this.viewModel.get('available');
availableModels.reset(availableMembers);
const selectedModels = this.viewModel.get('selected');
selectedModels.reset(selectedMembers);
},
__updateFakeInputModel() {
const selectedModels = this.viewModel.get('selected');
if (this.__canAddMember() && !this.fakeInputModel) {
this.fakeInputModel = new FakeInputModel();
selectedModels.add(this.fakeInputModel, { at: selectedModels.length });
}
if (!this.__canAddMember() && this.fakeInputModel) {
selectedModels.remove(this.fakeInputModel);
delete this.fakeInputModel;
}
if (this.fakeInputModel) {
this.fakeInputModel.updateEmpty();
}
},
__onMemberSelect() {
const canAddMemberOldValue = this.__canAddMember();
const selectedModels = this.viewModel.get('selected');
const availableModels = this.viewModel.get('available');
const selectedModel = availableModels.selected;
if (!selectedModel) {
return;
}
this.__applyFilter();
availableModels.deselect();
availableModels.remove(selectedModel);
selectedModels.add(selectedModel, { at: selectedModels.length - 1 });
availableModels.selectFirst();
this.value = this.getValue().concat(selectedModel.get('id'));
this.__triggerChange();
const stopAddMembers = canAddMemberOldValue !== this.__canAddMember();
this.__updateFakeInputModel();
if (stopAddMembers) {
this.dropdownView.close();
} else {
this.__updateButtonInput();
this.__focusButton();
}
},
__canAddMember() {
const selectedMembers = _.filter(
this.viewModel.get('selected').models,
model => model !== this.fakeInputModel);
return this.getEnabled() && !this.getReadonly() &&
(!this.options.maxQuantitySelected || (this.options.maxQuantitySelected !== selectedMembers.length)) &&
this.viewModel.get('available').length > 0;
},
__onBubbleDelete(model) {
if (!model) {
return;
}
if (this.dropdownView) {
this.dropdownView.close();
}
const selectedModels = this.viewModel.get('selected');
const availableModels = this.viewModel.get('available');
selectedModels.remove(model);
availableModels.add(model, { delayed: false });
const selected = [].concat(this.getValue());
selected.splice(selected.indexOf(model.get('id')), 1);
this.value = selected;
this.__triggerChange();
this.__updateFakeInputModel();
this.__focusButton();
},
__updateButtonInput() {
if (this.dropdownView.button) {
this.dropdownView.button.updateInput();
}
},
__focusButton() {
if (this.dropdownView.button) {
this.dropdownView.button.focus();
}
},
__onBubbleDeleteLast() {
const selectedModels = this.viewModel.get('selected');
const model = selectedModels.models[selectedModels.models.length - 2];
this.__onBubbleDelete(model);
},
__setEnabled(enabled) {
BaseLayoutEditorView.prototype.__setEnabled.call(this, enabled);
const isEnabled = this.getEnabled() && !this.getReadonly() && this.options.canDeleteMember;
this.dropdownView.options.buttonViewOptions.enabled = isEnabled;
this.dropdownView.button.updateEnabled(isEnabled);
},
__setReadonly(readonly) {
BaseLayoutEditorView.prototype.__setReadonly.call(this, readonly);
const isEnabled = this.getEnabled() && !this.getReadonly() && this.options.canDeleteMember;
this.dropdownView.options.buttonViewOptions.enabled = isEnabled;
this.dropdownView.button.updateEnabled(isEnabled);
},
focus() {
if (this.__canAddMember()) {
this.dropdownView.open();
}
},
blur() {
this.dropdownView.close();
}
});
export default formRepository.editors.MembersBubble;