-/**\r
- * This component provides a grid holding selected items from a second store of potential\r
- * members. The `store` of this component represents the selected items. The "search store"\r
- * represents the potentially selected items.\r
- *\r
- * While this component is a grid and so you can configure `columns`, it is best to leave\r
- * that to this class in its `initComponent` method. That allows this class to create the\r
- * extra column that allows the user to remove rows. Instead use `{@link #fieldName}` and\r
- * `{@link #fieldTitle}` to configure the primary column's `dataIndex` and column `text`,\r
- * respectively.\r
- *\r
- * @since 5.0.0\r
- */\r
-Ext.define('Ext.view.MultiSelector', {\r
- extend: 'Ext.grid.Panel',\r
-\r
- xtype: 'multiselector',\r
-\r
- config: {\r
- /**\r
- * @cfg {Object} search\r
- * This object configures the search popup component. By default this contains the\r
- * `xtype` for a `Ext.view.MultiSelectorSearch` component and specifies `autoLoad`\r
- * for its `store`.\r
- */\r
- search: {\r
- xtype: 'multiselector-search',\r
- width: 200,\r
- height: 200,\r
- store: {\r
- autoLoad: true\r
- }\r
- }\r
- },\r
-\r
- /**\r
- * @cfg {String} [fieldName="name"]\r
- * The name of the data field to display in the primary column of the grid.\r
- * @since 5.0.0\r
- */\r
- fieldName: 'name',\r
-\r
- /**\r
- * @cfg {String} [fieldTitle]\r
- * The text to display in the column header for the primary column of the grid.\r
- * @since 5.0.0\r
- */\r
- fieldTitle: null,\r
-\r
- /**\r
- * @cfg {String} removeRowText\r
- * The text to display in the "remove this row" column. By default this is a Unicode\r
- * "X" looking glyph.\r
- * @since 5.0.0\r
- */\r
- removeRowText: '\u2716',\r
-\r
- /**\r
- * @cfg {String} removeRowTip\r
- * The tooltip to display when the user hovers over the remove cell.\r
- * @since 5.0.0\r
- */\r
- removeRowTip: 'Remove this item',\r
-\r
- emptyText: 'Nothing selected',\r
-\r
- /**\r
- * @cfg {String} addToolText\r
- * The tooltip to display when the user hovers over the "+" tool in the panel header.\r
- * @since 5.0.0\r
- */\r
- addToolText: 'Search for items to add',\r
-\r
- initComponent: function () {\r
- var me = this,\r
- emptyText = me.emptyText,\r
- store = me.getStore(),\r
- search = me.getSearch(),\r
- fieldTitle = me.fieldTitle,\r
- searchStore, model;\r
-\r
- //<debug>\r
- if (!search) {\r
- Ext.raise('The search configuration is required for the multi selector');\r
- }\r
- //</debug>\r
-\r
- searchStore = search.store;\r
- if (searchStore.isStore) {\r
- model = searchStore.getModel();\r
- } else {\r
- model = searchStore.model;\r
- }\r
-\r
- if (!store) {\r
- me.store = {\r
- model: model\r
- };\r
- }\r
-\r
- if (emptyText && !me.viewConfig) {\r
- me.viewConfig = {\r
- deferEmptyText: false,\r
- emptyText: emptyText\r
- };\r
- }\r
-\r
- if (!me.columns) {\r
- me.hideHeaders = !fieldTitle;\r
- me.columns = [\r
- { text: fieldTitle, dataIndex: me.fieldName, flex: 1 },\r
- me.makeRemoveRowColumn()\r
- ];\r
- }\r
-\r
- me.callParent();\r
- },\r
-\r
- addTools: function () {\r
- this.addTool({\r
- type: 'plus',\r
- tooltip: this.addToolText,\r
- callback: 'onShowSearch',\r
- scope: this\r
- });\r
- },\r
-\r
- convertSearchRecord: Ext.identityFn,\r
-\r
- convertSelectionRecord: Ext.identityFn,\r
-\r
- makeRemoveRowColumn: function () {\r
- var me = this;\r
-\r
- return {\r
- width: 22,\r
- menuDisabled: true,\r
- tdCls: Ext.baseCSSPrefix + 'multiselector-remove',\r
- processEvent: me.processRowEvent.bind(me),\r
- renderer: me.renderRemoveRow,\r
- updater: Ext.emptyFn,\r
- scope: me\r
- };\r
- },\r
-\r
- processRowEvent: function (type, view, cell, recordIndex, cellIndex, e, record, row) {\r
- if (e.type !== 'click') {\r
- return;\r
- }\r
-\r
- if (Ext.fly(cell).hasCls(Ext.baseCSSPrefix + 'multiselector-remove')) {\r
- this.store.remove(record);\r
- if (this.searchPopup) {\r
- this.searchPopup.deselectRecords(record);\r
- }\r
- }\r
- },\r
-\r
- renderRemoveRow: function () {\r
- return '<span data-qtip="'+ this.removeRowTip + '" role="button">' +\r
- this.removeRowText + '</span>';\r
- },\r
-\r
- beforeDestroy: function() {\r
- Ext.un({\r
- mousedown: 'onDismissSearch',\r
- scope: this\r
- });\r
- this.callParent();\r
- },\r
-\r
- privates: {\r
- onDismissSearch: function (e) {\r
- var searchPopup = this.searchPopup;\r
-\r
- if (searchPopup && !(searchPopup.owns(e.getTarget()) || this.owns(e.getTarget()))) {\r
- Ext.un({\r
- mousedown: 'onDismissSearch',\r
- scope: this\r
- });\r
- searchPopup.hide();\r
- }\r
- },\r
-\r
- onShowSearch: function (panel, tool) {\r
- var me = this,\r
- searchPopup = me.searchPopup,\r
- store = me.getStore();\r
-\r
- if (!searchPopup) {\r
- searchPopup = Ext.merge({\r
- owner: me,\r
- field: me.fieldName,\r
- floating: true\r
- }, me.getSearch());\r
- me.searchPopup = searchPopup = me.add(searchPopup);\r
-\r
- // If we were configured with records prior to the UI requesting the popup,\r
- // ensure that the records are selected in the popup.\r
- if (store.getCount()) {\r
- searchPopup.selectRecords(store.getRange());\r
- }\r
- }\r
-\r
- searchPopup.showBy(me, 'tl-tr?');\r
- Ext.on({\r
- mousedown: 'onDismissSearch',\r
- scope: me\r
- });\r
- }\r
- }\r
-});\r
+/**
+ * This component provides a grid holding selected items from a second store of potential
+ * members. The `store` of this component represents the selected items. The "search store"
+ * represents the potentially selected items.
+ *
+ * While this component is a grid and so you can configure `columns`, it is best to leave
+ * that to this class in its `initComponent` method. That allows this class to create the
+ * extra column that allows the user to remove rows. Instead use `{@link #fieldName}` and
+ * `{@link #fieldTitle}` to configure the primary column's `dataIndex` and column `text`,
+ * respectively.
+ *
+ * @since 5.0.0
+ */
+Ext.define('Ext.view.MultiSelector', {
+ extend: 'Ext.grid.Panel',
+
+ xtype: 'multiselector',
+
+ config: {
+ /**
+ * @cfg {Object} search
+ * This object configures the search popup component. By default this contains the
+ * `xtype` for a `Ext.view.MultiSelectorSearch` component and specifies `autoLoad`
+ * for its `store`.
+ */
+ search: {
+ xtype: 'multiselector-search',
+ width: 200,
+ height: 200,
+ store: {
+ autoLoad: true
+ }
+ }
+ },
+
+ /**
+ * @cfg {String} [fieldName="name"]
+ * The name of the data field to display in the primary column of the grid.
+ * @since 5.0.0
+ */
+ fieldName: 'name',
+
+ /**
+ * @cfg {String} [fieldTitle]
+ * The text to display in the column header for the primary column of the grid.
+ * @since 5.0.0
+ */
+ fieldTitle: null,
+
+ /**
+ * @cfg {String} removeRowText
+ * The text to display in the "remove this row" column. By default this is a Unicode
+ * "X" looking glyph.
+ * @since 5.0.0
+ */
+ removeRowText: '\u2716',
+
+ /**
+ * @cfg {String} removeRowTip
+ * The tooltip to display when the user hovers over the remove cell.
+ * @since 5.0.0
+ */
+ removeRowTip: 'Remove this item',
+
+ emptyText: 'Nothing selected',
+
+ /**
+ * @cfg {String} addToolText
+ * The tooltip to display when the user hovers over the "+" tool in the panel header.
+ * @since 5.0.0
+ */
+ addToolText: 'Search for items to add',
+
+ initComponent: function() {
+ var me = this,
+ emptyText = me.emptyText,
+ store = me.getStore(),
+ search = me.getSearch(),
+ fieldTitle = me.fieldTitle,
+ searchStore, model;
+
+ //<debug>
+ if (!search) {
+ Ext.raise('The search configuration is required for the multi selector');
+ }
+ //</debug>
+
+ searchStore = search.store;
+
+ if (searchStore.isStore) {
+ model = searchStore.getModel();
+ }
+ else {
+ model = searchStore.model;
+ }
+
+ if (!store) {
+ me.store = {
+ model: model
+ };
+ }
+
+ if (emptyText && !me.viewConfig) {
+ me.viewConfig = {
+ deferEmptyText: false,
+ emptyText: emptyText
+ };
+ }
+
+ if (!me.columns) {
+ me.hideHeaders = !fieldTitle;
+ me.columns = [
+ { text: fieldTitle, dataIndex: me.fieldName, flex: 1 },
+ me.makeRemoveRowColumn()
+ ];
+ }
+
+ me.callParent();
+ },
+
+ addTools: function() {
+ var me = this;
+
+ me.addTool({
+ type: 'plus',
+ tooltip: me.addToolText,
+ callback: 'onShowSearch',
+ scope: me
+ });
+ me.searchTool = me.tools[me.tools.length - 1];
+ },
+
+ convertSearchRecord: Ext.identityFn,
+
+ convertSelectionRecord: Ext.identityFn,
+
+ makeRemoveRowColumn: function() {
+ var me = this;
+
+ return {
+ width: 32,
+ align: 'center',
+ menuDisabled: true,
+ tdCls: Ext.baseCSSPrefix + 'multiselector-remove',
+ processEvent: me.processRowEvent.bind(me),
+ renderer: me.renderRemoveRow,
+ updater: Ext.emptyFn,
+ scope: me
+ };
+ },
+
+ processRowEvent: function(type, view, cell, recordIndex, cellIndex, e, record, row) {
+ var body = Ext.getBody();
+
+ if (e.type === 'click' ||
+ (e.type === 'keydown' && (e.keyCode === e.SPACE || e.keyCode === e.ENTER))) {
+ // Deleting the focused row will momentarily focusLeave
+ // That would dismiss the popup, so disable that.
+ body.suspendFocusEvents();
+ this.store.remove(record);
+ body.resumeFocusEvents();
+
+ if (this.searchPopup) {
+ this.searchPopup.deselectRecords(record);
+ }
+ }
+ },
+
+ renderRemoveRow: function() {
+ return '<span data-qtip="' + this.removeRowTip + '" role="button" tabIndex="0">' +
+ this.removeRowText + '</span>';
+ },
+
+ onFocusLeave: function(e) {
+ this.onDismissSearch();
+ this.callParent([e]);
+ },
+
+ afterComponentLayout: function(width, height, prevWidth, prevHeight) {
+ var me = this,
+ popup = me.searchPopup;
+
+ me.callParent([width, height, prevWidth, prevHeight]);
+
+ if (popup && popup.isVisible()) {
+ popup.showBy(me, me.popupAlign);
+ }
+ },
+
+ privates: {
+ popupAlign: 'tl-tr?',
+
+ onGlobalScroll: function(scroller) {
+ // Collapse if the scroll is anywhere but inside this selector or the popup
+ if (!this.owns(scroller.getElement())) {
+ this.onDismissSearch();
+ }
+ },
+
+ onDismissSearch: function(e) {
+ var searchPopup = this.searchPopup;
+
+ if (searchPopup &&
+ (!e || !(searchPopup.owns(e.getTarget()) || this.owns(e.getTarget())))) {
+ this.scrollListeners.destroy();
+ this.touchListeners.destroy();
+ searchPopup.hide();
+ }
+ },
+
+ onShowSearch: function(panel, tool, event) {
+ var me = this,
+ searchPopup = me.searchPopup,
+ store = me.getStore();
+
+ if (!searchPopup) {
+ searchPopup = Ext.merge({
+ owner: me,
+ field: me.fieldName,
+ floating: true,
+ alignOnScroll: false
+ }, me.getSearch());
+ me.searchPopup = searchPopup = me.add(searchPopup);
+
+ // If we were configured with records prior to the UI requesting the popup,
+ // ensure that the records are selected in the popup.
+ if (store.getCount()) {
+ searchPopup.selectRecords(store.getRange());
+ }
+ }
+
+ searchPopup.invocationEvent = event;
+ searchPopup.showBy(me, me.popupAlign);
+
+ // It only autofocuses its defaultFocus target if it was hidden.
+ // If they're reactivating the show tool, they'll expect to focus the search.
+ if (!event || event.pointerType !== 'touch') {
+ searchPopup.lookupReference('searchField').focus();
+ }
+
+ me.scrollListeners = Ext.on({
+ scroll: 'onGlobalScroll',
+ scope: me,
+ destroyable: true
+ });
+
+ // Dismiss on touch outside this component tree.
+ // Because touch platforms do not focus document.body on touch
+ // so no focusleave would occur to trigger a collapse.
+ me.touchListeners = Ext.getDoc().on({
+ // Do not translate on non-touch platforms.
+ // mousedown will blur the field.
+ translate: false,
+ touchstart: me.onDismissSearch,
+ scope: me,
+ delegated: false,
+ destroyable: true
+ });
+ }
+ }
+});