import {$, _, key} from 'common'; import ko from 'ko'; import {EventKeyCode} from 'Common/Enums'; import Utils from 'Common/Utils'; class Selector { /** * @constructor * @param {koProperty} koList * @param {koProperty} koSelectedItem * @param {koProperty} koFocusedItem * @param {string} sItemSelector * @param {string} sItemSelectedSelector * @param {string} sItemCheckedSelector * @param {string} sItemFocusedSelector */ constructor(koList, koSelectedItem, koFocusedItem, sItemSelector, sItemSelectedSelector, sItemCheckedSelector, sItemFocusedSelector) { this.list = koList; this.listChecked = ko.computed(() => { return _.filter(this.list(), (item) => item.checked()); }, this).extend({'rateLimit': 0}); this.isListChecked = ko.computed(() => 0 < this.listChecked().length); this.focusedItem = koFocusedItem || ko.observable(null); this.selectedItem = koSelectedItem || ko.observable(null); this.selectedItemUseCallback = true; this.itemSelectedThrottle = _.debounce(_.bind(this.itemSelected, this), 300); this.listChecked.subscribe((items) => { if (0 < items.length) { if (null === this.selectedItem()) { if (this.selectedItem.valueHasMutated) { this.selectedItem.valueHasMutated(); } } else { this.selectedItem(null); } } else if (this.autoSelect() && this.focusedItem()) { this.selectedItem(this.focusedItem()); } }, this); this.selectedItem.subscribe((item) => { if (item) { if (this.isListChecked()) { _.each(this.listChecked(), (subItem) => { subItem.checked(false); }); } if (this.selectedItemUseCallback) { this.itemSelectedThrottle(item); } } else if (this.selectedItemUseCallback) { this.itemSelected(null); } }, this); this.selectedItem = this.selectedItem.extend({'toggleSubscribe': [null, (prev) => { if (prev) { prev.selected(false); } }, (next) => { if (next) { next.selected(true); } } ]}); this.focusedItem = this.focusedItem.extend({'toggleSubscribe': [null, (prev) => { if (prev) { prev.focused(false); } }, (next) => { if (next) { next.focused(true); } } ]}); this.iSelectNextHelper = 0; this.iFocusedNextHelper = 0; this.oContentVisible = null; this.oContentScrollable = null; this.sItemSelector = sItemSelector; this.sItemSelectedSelector = sItemSelectedSelector; this.sItemCheckedSelector = sItemCheckedSelector; this.sItemFocusedSelector = sItemFocusedSelector; this.sLastUid = ''; this.oCallbacks = {}; this.emptyFunction = () => {}; this.emptyTrueFunction = () => true; this.focusedItem.subscribe((item) => { if (item) { this.sLastUid = this.getItemUid(item); } }, this); let aCache = [], aCheckedCache = [], mFocused = null, mSelected = null ; this.list.subscribe((items) => { if (Utils.isArray(items)) { _.each(items, (item) => { if (item) { const uid = this.getItemUid(item); aCache.push(uid); if (item.checked()) { aCheckedCache.push(uid); } if (null === mFocused && item.focused()) { mFocused = uid; } if (null === mSelected && item.selected()) { mSelected = uid; } } }); } }, this, 'beforeChange'); this.list.subscribe((aItems) => { var oTemp = null, bGetNext = false, aUids = [], mNextFocused = mFocused, bChecked = false, bSelected = false, iLen = 0 ; this.selectedItemUseCallback = false; this.focusedItem(null); this.selectedItem(null); if (Utils.isArray(aItems)) { iLen = aCheckedCache.length; _.each(aItems, (oItem) => { var sUid = this.getItemUid(oItem); aUids.push(sUid); if (null !== mFocused && mFocused === sUid) { this.focusedItem(oItem); mFocused = null; } if (0 < iLen && -1 < Utils.inArray(sUid, aCheckedCache)) { bChecked = true; oItem.checked(true); iLen--; } if (!bChecked && null !== mSelected && mSelected === sUid) { bSelected = true; this.selectedItem(oItem); mSelected = null; } }); this.selectedItemUseCallback = true; if (!bChecked && !bSelected && this.autoSelect()) { if (this.focusedItem()) { this.selectedItem(this.focusedItem()); } else if (0 < aItems.length) { if (null !== mNextFocused) { bGetNext = false; mNextFocused = _.find(aCache, (sUid) => { if (bGetNext && -1 < Utils.inArray(sUid, aUids)) { return sUid; } else if (mNextFocused === sUid) { bGetNext = true; } return false; }); if (mNextFocused) { oTemp = _.find(aItems, (oItem) => mNextFocused === this.getItemUid(oItem)); } } this.selectedItem(oTemp || null); this.focusedItem(this.selectedItem()); } } if ((0 !== this.iSelectNextHelper || 0 !== this.iFocusedNextHelper) && 0 < aItems.length && !this.focusedItem()) { oTemp = null; if (0 !== this.iFocusedNextHelper) { oTemp = aItems[-1 === this.iFocusedNextHelper ? aItems.length - 1 : 0] || null; } if (!oTemp && 0 !== this.iSelectNextHelper) { oTemp = aItems[-1 === this.iSelectNextHelper ? aItems.length - 1 : 0] || null; } if (oTemp) { if (0 !== this.iSelectNextHelper) { this.selectedItem(oTemp || null); } this.focusedItem(oTemp || null); this.scrollToFocused(); _.delay(() => this.scrollToFocused(), 100); } this.iSelectNextHelper = 0; this.iFocusedNextHelper = 0; } } aCache = []; aCheckedCache = []; mFocused = null; mSelected = null; }, this); } itemSelected(item) { if (this.isListChecked()) { if (!item) { (this.oCallbacks['onItemSelect'] || this.emptyFunction)(item || null); } } else { if (item) { (this.oCallbacks['onItemSelect'] || this.emptyFunction)(item); } } } /** * @param {boolean} forceSelect */ goDown(forceSelect) { this.newSelectPosition(EventKeyCode.Down, false, forceSelect); } /** * @param {boolean} forceSelect */ goUp(forceSelect) { this.newSelectPosition(EventKeyCode.Up, false, forceSelect); } unselect() { this.selectedItem(null); this.focusedItem(null); } init(contentVisible, contentScrollable, keyScope = 'all') { this.oContentVisible = contentVisible; this.oContentScrollable = contentScrollable; if (this.oContentVisible && this.oContentScrollable) { $(this.oContentVisible) .on('selectstart', (event) => { if (event && event.preventDefault) { event.preventDefault(); } }) .on('click', this.sItemSelector, (event) => { this.actionClick(ko.dataFor(event.currentTarget), event); }) .on('click', this.sItemCheckedSelector, (event) => { const item = ko.dataFor(event.currentTarget); if (item) { if (event && event.shiftKey) { this.actionClick(item, event); } else { this.focusedItem(item); item.checked(!item.checked()); } } }) ; key('enter', keyScope, () => { if (this.focusedItem() && !this.focusedItem().selected()) { this.actionClick(this.focusedItem()); return false; } return true; }); key('ctrl+up, command+up, ctrl+down, command+down', keyScope, () => false); key('up, shift+up, down, shift+down, home, end, pageup, pagedown, insert, space', keyScope, (event, handler) => { if (event && handler && handler.shortcut) { let eventKey = 0; switch (handler.shortcut) { case 'up': case 'shift+up': eventKey = EventKeyCode.Up; break; case 'down': case 'shift+down': eventKey = EventKeyCode.Down; break; case 'insert': eventKey = EventKeyCode.Insert; break; case 'space': eventKey = EventKeyCode.Space; break; case 'home': eventKey = EventKeyCode.Home; break; case 'end': eventKey = EventKeyCode.End; break; case 'pageup': eventKey = EventKeyCode.PageUp; break; case 'pagedown': eventKey = EventKeyCode.PageDown; break; } if (0 < eventKey) { this.newSelectPosition(eventKey, key.shift); return false; } } }); } } /** * @return {boolean} */ autoSelect() { return !!(this.oCallbacks['onAutoSelect'] || this.emptyTrueFunction)(); } /** * @param {boolean} up */ doUpUpOrDownDown(up) { (this.oCallbacks['onUpUpOrDownDown'] || this.emptyTrueFunction)(!!up); } /** * @param {Object} oItem * @return {string} */ getItemUid(item) { let uid = ''; const getItemUidCallback = this.oCallbacks['onItemGetUid'] || null; if (getItemUidCallback && item) { uid = getItemUidCallback(item); } return uid.toString(); } /** * @param {number} iEventKeyCode * @param {boolean} bShiftKey * @param {boolean=} bForceSelect = false */ newSelectPosition(iEventKeyCode, bShiftKey, bForceSelect) { var iIndex = 0, iPageStep = 10, bNext = false, bStop = false, oResult = null, aList = this.list(), iListLen = aList ? aList.length : 0, oFocused = this.focusedItem() ; if (0 < iListLen) { if (!oFocused) { if (EventKeyCode.Down === iEventKeyCode || EventKeyCode.Insert === iEventKeyCode || EventKeyCode.Space === iEventKeyCode || EventKeyCode.Home === iEventKeyCode || EventKeyCode.PageUp === iEventKeyCode) { oResult = aList[0]; } else if (EventKeyCode.Up === iEventKeyCode || EventKeyCode.End === iEventKeyCode || EventKeyCode.PageDown === iEventKeyCode) { oResult = aList[aList.length - 1]; } } else if (oFocused) { if (EventKeyCode.Down === iEventKeyCode || EventKeyCode.Up === iEventKeyCode || EventKeyCode.Insert === iEventKeyCode || EventKeyCode.Space === iEventKeyCode) { _.each(aList, (item) => { if (!bStop) { switch (iEventKeyCode) { case EventKeyCode.Up: if (oFocused === item) { bStop = true; } else { oResult = item; } break; case EventKeyCode.Down: case EventKeyCode.Insert: if (bNext) { oResult = item; bStop = true; } else if (oFocused === item) { bNext = true; } break; } } }); if (!oResult && (EventKeyCode.Down === iEventKeyCode || EventKeyCode.Up === iEventKeyCode)) { this.doUpUpOrDownDown(EventKeyCode.Up === iEventKeyCode); } } else if (EventKeyCode.Home === iEventKeyCode || EventKeyCode.End === iEventKeyCode) { if (EventKeyCode.Home === iEventKeyCode) { oResult = aList[0]; } else if (EventKeyCode.End === iEventKeyCode) { oResult = aList[aList.length - 1]; } } else if (EventKeyCode.PageDown === iEventKeyCode) { for (; iIndex < iListLen; iIndex++) { if (oFocused === aList[iIndex]) { iIndex += iPageStep; iIndex = iListLen - 1 < iIndex ? iListLen - 1 : iIndex; oResult = aList[iIndex]; break; } } } else if (EventKeyCode.PageUp === iEventKeyCode) { for (iIndex = iListLen; iIndex >= 0; iIndex--) { if (oFocused === aList[iIndex]) { iIndex -= iPageStep; iIndex = 0 > iIndex ? 0 : iIndex; oResult = aList[iIndex]; break; } } } } } if (oResult) { this.focusedItem(oResult); if (oFocused) { if (bShiftKey) { if (EventKeyCode.Up === iEventKeyCode || EventKeyCode.Down === iEventKeyCode) { oFocused.checked(!oFocused.checked()); } } else if (EventKeyCode.Insert === iEventKeyCode || EventKeyCode.Space === iEventKeyCode) { oFocused.checked(!oFocused.checked()); } } if ((this.autoSelect() || !!bForceSelect) && !this.isListChecked() && EventKeyCode.Space !== iEventKeyCode) { this.selectedItem(oResult); } this.scrollToFocused(); } else if (oFocused) { if (bShiftKey && (EventKeyCode.Up === iEventKeyCode || EventKeyCode.Down === iEventKeyCode)) { oFocused.checked(!oFocused.checked()); } else if (EventKeyCode.Insert === iEventKeyCode || EventKeyCode.Space === iEventKeyCode) { oFocused.checked(!oFocused.checked()); } this.focusedItem(oFocused); } } /** * @return {boolean} */ scrollToFocused() { if (!this.oContentVisible || !this.oContentScrollable) { return false; } const offset = 20, list = this.list(), $focused = $(this.sItemFocusedSelector, this.oContentScrollable), pos = $focused.position(), visibleHeight = this.oContentVisible.height(), focusedHeight = $focused.outerHeight() ; if (list && list[0] && list[0].focused()) { this.oContentScrollable.scrollTop(0); return true; } else if (pos && (pos.top < 0 || pos.top + focusedHeight > visibleHeight)) { this.oContentScrollable.scrollTop(pos.top < 0 ? this.oContentScrollable.scrollTop() + pos.top - offset : this.oContentScrollable.scrollTop() + pos.top - visibleHeight + focusedHeight + offset ); return true; } return false; } /** * @param {boolean=} fast = false * @return {boolean} */ scrollToTop(fast = false) { if (!this.oContentVisible || !this.oContentScrollable) { return false; } if (fast || 50 > this.oContentScrollable.scrollTop()) { this.oContentScrollable.scrollTop(0); } else { this.oContentScrollable.stop().animate({'scrollTop': 0}, 200); } return true; } eventClickFunction(item, event) { let index = 0, length = 0, changeRange = false, isInRange = false, list = [], checked = false, listItem = null, lineUid = '' ; const uid = this.getItemUid(item); if (event && event.shiftKey) { if ('' !== uid && '' !== this.sLastUid && uid !== this.sLastUid) { list = this.list(); checked = item.checked(); for (index = 0, length = list.length; index < length; index++) { listItem = list[index]; lineUid = this.getItemUid(listItem); changeRange = false; if (lineUid === this.sLastUid || lineUid === uid) { changeRange = true; } if (changeRange) { isInRange = !isInRange; } if (isInRange || changeRange) { listItem.checked(checked); } } } } this.sLastUid = '' === uid ? '' : uid; } /** * @param {Object} item * @param {Object=} event */ actionClick = function (item, event = null) { if (item) { let click = true; if (event) { if (event.shiftKey && !(event.ctrlKey || event.metaKey) && !event.altKey) { click = false; if ('' === this.sLastUid) { this.sLastUid = this.getItemUid(item); } item.checked(!item.checked()); this.eventClickFunction(item, event); this.focusedItem(item); } else if ((event.ctrlKey || event.metaKey) && !event.shiftKey && !event.altKey) { click = false; this.focusedItem(item); if (this.selectedItem() && item !== this.selectedItem()) { this.selectedItem().checked(true); } item.checked(!item.checked()); } } if (click) { this.selectMessageItem(item); } } } on(eventName, callback) { this.oCallbacks[eventName] = callback; } selectMessageItem(messageItem) { this.focusedItem(messageItem); this.selectedItem(messageItem); this.scrollToFocused(); } } export {Selector, Selector as default}; module.exports = Selector;