snappymail/dev/Common/Selector.js

731 lines
15 KiB
JavaScript
Raw Normal View History

2014-09-05 06:49:03 +08:00
(function () {
2014-08-25 23:49:01 +08:00
'use strict';
2014-08-20 23:03:12 +08:00
var
2014-08-25 23:49:01 +08:00
_ = require('_'),
$ = require('$'),
2014-08-25 23:49:01 +08:00
ko = require('ko'),
key = require('key'),
2014-09-05 06:49:03 +08:00
Enums = require('Common/Enums'),
Utils = require('Common/Utils')
2014-08-20 23:03:12 +08:00
;
2014-04-10 00:01:41 +08:00
2014-08-20 23:03:12 +08:00
/**
* @constructor
* @param {koProperty} oKoList
* @param {koProperty} oKoSelectedItem
* @param {string} sItemSelector
* @param {string} sItemSelectedSelector
* @param {string} sItemCheckedSelector
* @param {string} sItemFocusedSelector
*/
function Selector(oKoList, oKoSelectedItem,
sItemSelector, sItemSelectedSelector, sItemCheckedSelector, sItemFocusedSelector)
{
this.list = oKoList;
this.listChecked = ko.computed(function () {
return _.filter(this.list(), function (oItem) {
return oItem.checked();
});
}, this).extend({'rateLimit': 0});
this.isListChecked = ko.computed(function () {
return 0 < this.listChecked().length;
}, this);
this.focusedItem = ko.observable(null);
this.selectedItem = oKoSelectedItem;
this.selectedItemUseCallback = true;
2014-04-10 00:01:41 +08:00
2014-08-20 23:03:12 +08:00
this.itemSelectedThrottle = _.debounce(_.bind(this.itemSelected, this), 300);
this.listChecked.subscribe(function (aItems) {
if (0 < aItems.length)
{
2014-08-20 23:03:12 +08:00
if (null === this.selectedItem())
{
if (this.selectedItem.valueHasMutated)
{
this.selectedItem.valueHasMutated();
}
2014-08-20 23:03:12 +08:00
}
else
{
this.selectedItem(null);
}
}
2014-08-20 23:03:12 +08:00
else if (this.bAutoSelect && this.focusedItem())
{
2014-08-20 23:03:12 +08:00
this.selectedItem(this.focusedItem());
}
2014-08-20 23:03:12 +08:00
}, this);
this.selectedItem.subscribe(function (oItem) {
2014-08-20 23:03:12 +08:00
if (oItem)
{
2014-08-20 23:03:12 +08:00
if (this.isListChecked())
{
_.each(this.listChecked(), function (oSubItem) {
oSubItem.checked(false);
});
}
if (this.selectedItemUseCallback)
{
this.itemSelectedThrottle(oItem);
}
}
2014-08-20 23:03:12 +08:00
else if (this.selectedItemUseCallback)
{
2014-08-20 23:03:12 +08:00
this.itemSelected(null);
}
2014-08-20 23:03:12 +08:00
}, this);
this.selectedItem.extend({'toggleSubscribe': [null,
function (oPrev) {
if (oPrev)
{
oPrev.selected(false);
}
}, function (oNext) {
if (oNext)
{
oNext.selected(true);
}
}
]});
this.focusedItem.extend({'toggleSubscribe': [null,
function (oPrev) {
if (oPrev)
{
oPrev.focused(false);
}
}, function (oNext) {
if (oNext)
{
oNext.focused(true);
}
}
]});
2014-08-20 23:03:12 +08:00
this.oContentVisible = null;
this.oContentScrollable = null;
2014-08-20 23:03:12 +08:00
this.sItemSelector = sItemSelector;
this.sItemSelectedSelector = sItemSelectedSelector;
this.sItemCheckedSelector = sItemCheckedSelector;
this.sItemFocusedSelector = sItemFocusedSelector;
2014-08-20 23:03:12 +08:00
this.sLastUid = '';
this.bAutoSelect = true;
this.oCallbacks = {};
2014-08-20 23:03:12 +08:00
this.emptyFunction = function () {};
2014-08-20 23:03:12 +08:00
this.focusedItem.subscribe(function (oItem) {
if (oItem)
{
this.sLastUid = this.getItemUid(oItem);
}
}, this);
2014-08-20 23:03:12 +08:00
var
aCache = [],
aCheckedCache = [],
mFocused = null,
mSelected = null
;
2014-04-10 00:01:41 +08:00
2014-08-20 23:03:12 +08:00
this.list.subscribe(function (aItems) {
2014-08-20 23:03:12 +08:00
var self = this;
if (Utils.isArray(aItems))
{
_.each(aItems, function (oItem) {
if (oItem)
{
2014-08-20 23:03:12 +08:00
var sUid = self.getItemUid(oItem);
aCache.push(sUid);
if (oItem.checked())
{
aCheckedCache.push(sUid);
}
if (null === mFocused && oItem.focused())
{
mFocused = sUid;
}
if (null === mSelected && oItem.selected())
{
mSelected = sUid;
}
}
2014-08-20 23:03:12 +08:00
});
}
}, this, 'beforeChange');
2014-08-20 23:03:12 +08:00
this.list.subscribe(function (aItems) {
2014-08-20 23:03:12 +08:00
var
self = this,
oTemp = null,
bGetNext = false,
aUids = [],
mNextFocused = mFocused,
bChecked = false,
bSelected = false,
iLen = 0
;
2014-08-20 23:03:12 +08:00
this.selectedItemUseCallback = false;
2014-08-20 23:03:12 +08:00
this.focusedItem(null);
this.selectedItem(null);
2014-08-20 23:03:12 +08:00
if (Utils.isArray(aItems))
{
iLen = aCheckedCache.length;
2014-08-20 23:03:12 +08:00
_.each(aItems, function (oItem) {
2014-08-20 23:03:12 +08:00
var sUid = self.getItemUid(oItem);
aUids.push(sUid);
2014-08-20 23:03:12 +08:00
if (null !== mFocused && mFocused === sUid)
{
self.focusedItem(oItem);
mFocused = null;
}
2014-08-20 23:03:12 +08:00
if (0 < iLen && -1 < Utils.inArray(sUid, aCheckedCache))
{
bChecked = true;
oItem.checked(true);
iLen--;
}
2014-08-20 23:03:12 +08:00
if (!bChecked && null !== mSelected && mSelected === sUid)
{
bSelected = true;
self.selectedItem(oItem);
mSelected = null;
}
});
2014-08-20 23:03:12 +08:00
this.selectedItemUseCallback = true;
2014-08-20 23:03:12 +08:00
if (!bChecked && !bSelected && this.bAutoSelect)
{
2014-08-20 23:03:12 +08:00
if (self.focusedItem())
{
2014-08-20 23:03:12 +08:00
self.selectedItem(self.focusedItem());
}
else if (0 < aItems.length)
{
if (null !== mNextFocused)
{
2014-08-20 23:03:12 +08:00
bGetNext = false;
mNextFocused = _.find(aCache, function (sUid) {
if (bGetNext && -1 < Utils.inArray(sUid, aUids))
{
return sUid;
}
else if (mNextFocused === sUid)
{
bGetNext = true;
}
return false;
});
2014-08-20 23:03:12 +08:00
if (mNextFocused)
{
oTemp = _.find(aItems, function (oItem) {
return mNextFocused === self.getItemUid(oItem);
});
}
}
2014-08-20 23:03:12 +08:00
self.selectedItem(oTemp || null);
self.focusedItem(self.selectedItem());
}
}
}
2014-08-20 23:03:12 +08:00
aCache = [];
aCheckedCache = [];
mFocused = null;
mSelected = null;
2014-08-20 23:03:12 +08:00
}, this);
}
2014-08-20 23:03:12 +08:00
Selector.prototype.itemSelected = function (oItem)
{
2014-08-20 23:03:12 +08:00
if (this.isListChecked())
{
2014-08-20 23:03:12 +08:00
if (!oItem)
{
(this.oCallbacks['onItemSelect'] || this.emptyFunction)(oItem || null);
}
}
2014-08-20 23:03:12 +08:00
else
{
2014-08-20 23:03:12 +08:00
if (oItem)
{
(this.oCallbacks['onItemSelect'] || this.emptyFunction)(oItem);
}
}
2014-08-20 23:03:12 +08:00
};
2014-08-20 23:03:12 +08:00
Selector.prototype.goDown = function (bForceSelect)
{
this.newSelectPosition(Enums.EventKeyCode.Down, false, bForceSelect);
};
2013-12-13 07:23:47 +08:00
2014-08-20 23:03:12 +08:00
Selector.prototype.goUp = function (bForceSelect)
{
this.newSelectPosition(Enums.EventKeyCode.Up, false, bForceSelect);
};
2013-12-13 07:23:47 +08:00
2014-08-20 23:03:12 +08:00
Selector.prototype.init = function (oContentVisible, oContentScrollable, sKeyScope)
{
this.oContentVisible = oContentVisible;
this.oContentScrollable = oContentScrollable;
2014-08-20 23:03:12 +08:00
sKeyScope = sKeyScope || 'all';
2014-08-20 23:03:12 +08:00
if (this.oContentVisible && this.oContentScrollable)
{
var
self = this
;
2014-08-20 23:03:12 +08:00
$(this.oContentVisible)
.on('selectstart', function (oEvent) {
if (oEvent && oEvent.preventDefault)
{
2014-08-20 23:03:12 +08:00
oEvent.preventDefault();
}
2014-08-20 23:03:12 +08:00
})
.on('click', this.sItemSelector, function (oEvent) {
self.actionClick(ko.dataFor(this), oEvent);
})
.on('click', this.sItemCheckedSelector, function (oEvent) {
var oItem = ko.dataFor(this);
if (oItem)
{
2014-08-20 23:03:12 +08:00
if (oEvent && oEvent.shiftKey)
{
self.actionClick(oItem, oEvent);
}
else
{
self.focusedItem(oItem);
oItem.checked(!oItem.checked());
}
}
2014-08-20 23:03:12 +08:00
})
;
2014-08-20 23:03:12 +08:00
key('enter', sKeyScope, function () {
if (self.focusedItem() && !self.focusedItem().selected())
{
self.actionClick(self.focusedItem());
return false;
}
2014-08-20 23:03:12 +08:00
return true;
});
2014-08-20 23:03:12 +08:00
key('ctrl+up, command+up, ctrl+down, command+down', sKeyScope, function () {
return false;
});
2014-08-20 23:03:12 +08:00
key('up, shift+up, down, shift+down, home, end, pageup, pagedown, insert, space', sKeyScope, function (event, handler) {
if (event && handler && handler.shortcut)
{
2014-08-20 23:03:12 +08:00
var iKey = 0;
switch (handler.shortcut)
{
case 'up':
case 'shift+up':
iKey = Enums.EventKeyCode.Up;
break;
case 'down':
case 'shift+down':
iKey = Enums.EventKeyCode.Down;
break;
case 'insert':
iKey = Enums.EventKeyCode.Insert;
break;
case 'space':
iKey = Enums.EventKeyCode.Space;
break;
case 'home':
iKey = Enums.EventKeyCode.Home;
break;
case 'end':
iKey = Enums.EventKeyCode.End;
break;
case 'pageup':
iKey = Enums.EventKeyCode.PageUp;
break;
case 'pagedown':
iKey = Enums.EventKeyCode.PageDown;
break;
}
2014-08-20 23:03:12 +08:00
if (0 < iKey)
{
self.newSelectPosition(iKey, key.shift);
return false;
}
}
2014-08-20 23:03:12 +08:00
});
}
};
2014-08-20 23:03:12 +08:00
Selector.prototype.autoSelect = function (bValue)
{
2014-08-20 23:03:12 +08:00
this.bAutoSelect = !!bValue;
};
/**
* @param {Object} oItem
* @returns {string}
*/
Selector.prototype.getItemUid = function (oItem)
{
var
sUid = '',
fGetItemUidCallback = this.oCallbacks['onItemGetUid'] || null
;
2014-08-20 23:03:12 +08:00
if (fGetItemUidCallback && oItem)
{
sUid = fGetItemUidCallback(oItem);
}
2014-08-20 23:03:12 +08:00
return sUid.toString();
};
2014-08-20 23:03:12 +08:00
/**
* @param {number} iEventKeyCode
* @param {boolean} bShiftKey
* @param {boolean=} bForceSelect = false
*/
Selector.prototype.newSelectPosition = function (iEventKeyCode, bShiftKey, bForceSelect)
{
2014-08-20 23:03:12 +08:00
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)
{
2014-08-20 23:03:12 +08:00
if (!oFocused)
{
2014-08-20 23:03:12 +08:00
if (Enums.EventKeyCode.Down === iEventKeyCode || Enums.EventKeyCode.Insert === iEventKeyCode || Enums.EventKeyCode.Space === iEventKeyCode || Enums.EventKeyCode.Home === iEventKeyCode || Enums.EventKeyCode.PageUp === iEventKeyCode)
{
oResult = aList[0];
}
2014-08-20 23:03:12 +08:00
else if (Enums.EventKeyCode.Up === iEventKeyCode || Enums.EventKeyCode.End === iEventKeyCode || Enums.EventKeyCode.PageDown === iEventKeyCode)
{
oResult = aList[aList.length - 1];
}
}
2014-08-20 23:03:12 +08:00
else if (oFocused)
{
2014-08-20 23:03:12 +08:00
if (Enums.EventKeyCode.Down === iEventKeyCode || Enums.EventKeyCode.Up === iEventKeyCode || Enums.EventKeyCode.Insert === iEventKeyCode || Enums.EventKeyCode.Space === iEventKeyCode)
{
_.each(aList, function (oItem) {
if (!bStop)
{
switch (iEventKeyCode) {
case Enums.EventKeyCode.Up:
if (oFocused === oItem)
{
bStop = true;
}
else
{
oResult = oItem;
}
break;
case Enums.EventKeyCode.Down:
case Enums.EventKeyCode.Insert:
if (bNext)
{
oResult = oItem;
bStop = true;
}
else if (oFocused === oItem)
{
bNext = true;
}
break;
}
}
});
}
else if (Enums.EventKeyCode.Home === iEventKeyCode || Enums.EventKeyCode.End === iEventKeyCode)
{
2014-08-20 23:03:12 +08:00
if (Enums.EventKeyCode.Home === iEventKeyCode)
{
oResult = aList[0];
}
else if (Enums.EventKeyCode.End === iEventKeyCode)
{
2014-08-20 23:03:12 +08:00
oResult = aList[aList.length - 1];
}
}
2014-08-20 23:03:12 +08:00
else if (Enums.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 (Enums.EventKeyCode.PageUp === iEventKeyCode)
{
2014-08-20 23:03:12 +08:00
for (iIndex = iListLen; iIndex >= 0; iIndex--)
{
2014-08-20 23:03:12 +08:00
if (oFocused === aList[iIndex])
{
iIndex -= iPageStep;
iIndex = 0 > iIndex ? 0 : iIndex;
oResult = aList[iIndex];
break;
}
}
}
}
}
2014-08-20 23:03:12 +08:00
if (oResult)
{
2014-08-20 23:03:12 +08:00
this.focusedItem(oResult);
if (oFocused)
{
2014-08-20 23:03:12 +08:00
if (bShiftKey)
{
if (Enums.EventKeyCode.Up === iEventKeyCode || Enums.EventKeyCode.Down === iEventKeyCode)
{
oFocused.checked(!oFocused.checked());
}
}
else if (Enums.EventKeyCode.Insert === iEventKeyCode || Enums.EventKeyCode.Space === iEventKeyCode)
{
oFocused.checked(!oFocused.checked());
}
}
2014-08-20 23:03:12 +08:00
if ((this.bAutoSelect || !!bForceSelect) &&
!this.isListChecked() && Enums.EventKeyCode.Space !== iEventKeyCode)
{
this.selectedItem(oResult);
}
this.scrollToFocused();
}
else if (oFocused)
{
if (bShiftKey && (Enums.EventKeyCode.Up === iEventKeyCode || Enums.EventKeyCode.Down === iEventKeyCode))
{
oFocused.checked(!oFocused.checked());
}
else if (Enums.EventKeyCode.Insert === iEventKeyCode || Enums.EventKeyCode.Space === iEventKeyCode)
{
oFocused.checked(!oFocused.checked());
}
2014-08-20 23:03:12 +08:00
this.focusedItem(oFocused);
}
2014-08-20 23:03:12 +08:00
};
2014-08-20 23:03:12 +08:00
/**
* @return {boolean}
*/
Selector.prototype.scrollToFocused = function ()
{
2014-08-20 23:03:12 +08:00
if (!this.oContentVisible || !this.oContentScrollable)
{
2014-08-20 23:03:12 +08:00
return false;
}
2014-08-20 23:03:12 +08:00
var
iOffset = 20,
oFocused = $(this.sItemFocusedSelector, this.oContentScrollable),
oPos = oFocused.position(),
iVisibleHeight = this.oContentVisible.height(),
iFocusedHeight = oFocused.outerHeight()
;
if (oPos && (oPos.top < 0 || oPos.top + iFocusedHeight > iVisibleHeight))
{
2014-08-20 23:03:12 +08:00
if (oPos.top < 0)
{
this.oContentScrollable.scrollTop(this.oContentScrollable.scrollTop() + oPos.top - iOffset);
}
else
{
this.oContentScrollable.scrollTop(this.oContentScrollable.scrollTop() + oPos.top - iVisibleHeight + iFocusedHeight + iOffset);
}
return true;
}
return false;
2014-08-20 23:03:12 +08:00
};
2014-08-20 23:03:12 +08:00
/**
* @param {boolean=} bFast = false
* @return {boolean}
*/
Selector.prototype.scrollToTop = function (bFast)
{
2014-08-20 23:03:12 +08:00
if (!this.oContentVisible || !this.oContentScrollable)
{
return false;
}
2014-11-20 07:25:39 +08:00
if (bFast || 50 > this.oContentScrollable.scrollTop())
{
2014-08-20 23:03:12 +08:00
this.oContentScrollable.scrollTop(0);
}
else
{
2014-08-20 23:03:12 +08:00
this.oContentScrollable.stop().animate({'scrollTop': 0}, 200);
}
return true;
2014-08-20 23:03:12 +08:00
};
2014-08-20 23:03:12 +08:00
Selector.prototype.eventClickFunction = function (oItem, oEvent)
{
2014-08-20 23:03:12 +08:00
var
sUid = this.getItemUid(oItem),
iIndex = 0,
iLength = 0,
oListItem = null,
sLineUid = '',
bChangeRange = false,
bIsInRange = false,
aList = [],
bChecked = false
;
2014-08-20 23:03:12 +08:00
if (oEvent && oEvent.shiftKey)
{
2014-08-20 23:03:12 +08:00
if ('' !== sUid && '' !== this.sLastUid && sUid !== this.sLastUid)
{
2014-08-20 23:03:12 +08:00
aList = this.list();
bChecked = oItem.checked();
2014-08-20 23:03:12 +08:00
for (iIndex = 0, iLength = aList.length; iIndex < iLength; iIndex++)
{
2014-08-20 23:03:12 +08:00
oListItem = aList[iIndex];
sLineUid = this.getItemUid(oListItem);
2014-08-20 23:03:12 +08:00
bChangeRange = false;
if (sLineUid === this.sLastUid || sLineUid === sUid)
{
bChangeRange = true;
}
2014-08-20 23:03:12 +08:00
if (bChangeRange)
{
bIsInRange = !bIsInRange;
}
if (bIsInRange || bChangeRange)
{
oListItem.checked(bChecked);
}
}
}
}
2014-08-20 23:03:12 +08:00
this.sLastUid = '' === sUid ? '' : sUid;
};
2014-08-20 23:03:12 +08:00
/**
* @param {Object} oItem
* @param {Object=} oEvent
*/
Selector.prototype.actionClick = function (oItem, oEvent)
{
2014-08-20 23:03:12 +08:00
if (oItem)
{
2014-08-20 23:03:12 +08:00
var
bClick = true,
sUid = this.getItemUid(oItem)
;
if (oEvent)
{
2014-08-20 23:03:12 +08:00
if (oEvent.shiftKey && !oEvent.ctrlKey && !oEvent.altKey)
{
2014-08-20 23:03:12 +08:00
bClick = false;
if ('' === this.sLastUid)
{
this.sLastUid = sUid;
}
oItem.checked(!oItem.checked());
this.eventClickFunction(oItem, oEvent);
this.focusedItem(oItem);
}
2014-08-20 23:03:12 +08:00
else if (oEvent.ctrlKey && !oEvent.shiftKey && !oEvent.altKey)
{
bClick = false;
this.focusedItem(oItem);
2014-08-20 23:03:12 +08:00
if (this.selectedItem() && oItem !== this.selectedItem())
{
this.selectedItem().checked(true);
}
2014-08-20 23:03:12 +08:00
oItem.checked(!oItem.checked());
}
}
2014-08-20 23:03:12 +08:00
if (bClick)
{
this.focusedItem(oItem);
2014-08-20 23:03:12 +08:00
this.selectedItem(oItem);
2014-04-11 00:03:07 +08:00
2014-08-20 23:03:12 +08:00
this.scrollToFocused();
}
}
2014-08-20 23:03:12 +08:00
};
2014-08-20 23:03:12 +08:00
Selector.prototype.on = function (sEventName, fCallback)
{
this.oCallbacks[sEventName] = fCallback;
};
2014-08-20 23:03:12 +08:00
module.exports = Selector;
2014-09-05 06:49:03 +08:00
}());