@openui5/sap.m
Version:
OpenUI5 UI Library sap.m
1,626 lines (1,367 loc) • 41.8 kB
JavaScript
/*!
* UI development toolkit for HTML5 (OpenUI5)
* (c) Copyright 2009-2022 SAP SE or an SAP affiliate company.
* Licensed under the Apache License, Version 2.0 - see LICENSE.txt.
*/
// Provides control sap.m.Tokenizer.
sap.ui.define([
'./library',
'sap/ui/core/Control',
'sap/ui/core/delegate/ScrollEnablement',
'sap/ui/Device',
'sap/ui/core/InvisibleText',
'sap/ui/core/ResizeHandler',
'./TokenizerRenderer',
"sap/ui/dom/containsOrEquals",
"sap/ui/events/KeyCodes",
"sap/base/Log",
"sap/ui/thirdparty/jquery",
// jQuery Plugin "control"
"sap/ui/dom/jquery/control"
],
function(
library,
Control,
ScrollEnablement,
Device,
InvisibleText,
ResizeHandler,
TokenizerRenderer,
containsOrEquals,
KeyCodes,
Log,
jQuery
) {
"use strict";
/**
* Constructor for a new Tokenizer.
*
* @param {string} [sId] ID for the new control, generated automatically if no ID is given
* @param {object} [mSettings] Initial settings for the new control
*
* @class
* <h3>Overview</h3>
* A tokenizer is a container for {@link sap.m.Token Tokens}. It also handles all actions associated with the tokens like adding, deleting, selecting and editing.
* <h3>Structure</h3>
* The tokens are stored in the <code>tokens</code> aggregation.
* The tokenizer can determine, by setting the <code>editable</code> property, whether the tokens in it are editable.
* Still the Token itself can determine if it is <code>editable</code>. This allows you to have non-editable Tokens in an editable Tokenizer.
*
* <h3>Usage</h3>
* <h4>When to use:</h4>
* The tokenizer can only be used as part of {@link sap.m.MultiComboBox MultiComboBox},{@link sap.m.MultiInput MultiInput} or {@link sap.ui.comp.valuehelpdialog.ValueHelpDialog ValueHelpDialog}
*
* @extends sap.ui.core.Control
* @author SAP SE
* @version 1.60.39
*
* @constructor
* @public
* @alias sap.m.Tokenizer
* @see {@link fiori:https://experience.sap.com/fiori-design-web/token/ Tokenizer}
* @ui5-metamodel This control/element also will be described in the UI5 (legacy) designtime metamodel
*/
var Tokenizer = Control.extend("sap.m.Tokenizer", /** @lends sap.m.Tokenizer.prototype */ { metadata : {
library : "sap.m",
properties : {
/**
* true if tokens shall be editable otherwise false
*/
editable : {type : "boolean", group : "Misc", defaultValue : true},
/**
* Defines the width of the Tokenizer.
*/
width : {type : "sap.ui.core.CSSSize", group : "Dimension", defaultValue : null},
/**
* Defines the maximum width of the Tokenizer.
*/
maxWidth : {type: "sap.ui.core.CSSSize", group: "Dimension", defaultValue : "100%"}
},
defaultAggregation : "tokens",
aggregations : {
/**
* the currently displayed tokens
*/
tokens : {type : "sap.m.Token", multiple : true, singularName : "token"},
/**
* Hidden text used for accesibility
*/
_tokensInfo: {type: "sap.ui.core.InvisibleText", multiple: false, visibility: "hidden"}
},
associations : {
/**
* Association to controls / ids which describe this control (see WAI-ARIA attribute aria-describedby).
*/
ariaDescribedBy: {type: "sap.ui.core.Control", multiple: true, singularName: "ariaDescribedBy"},
/**
* Association to controls / ids which label this control (see WAI-ARIA attribute aria-labelledby).
*/
ariaLabelledBy: {type: "sap.ui.core.Control", multiple: true, singularName: "ariaLabelledBy"}
},
events : {
/**
* fired when the tokens aggregation changed (add / remove token)
*/
tokenChange : {
parameters : {
/**
* type of tokenChange event.
* There are four TokenChange types: "added", "removed", "removedAll", "tokensChanged".
* Use Tokenizer.TokenChangeType.Added for "added", Tokenizer.TokenChangeType.Removed for "removed", Tokenizer.TokenChangeType.RemovedAll for "removedAll" and Tokenizer.TokenChangeType.TokensChanged for "tokensChanged".
*/
type: { type : "string"},
/**
* the added token or removed token.
* This parameter is used when tokenChange type is "added" or "removed".
*/
token: { type: "sap.m.Token"},
/**
* the array of removed tokens.
* This parameter is used when tokenChange type is "removedAll".
*/
tokens: { type: "sap.m.Token[]"},
/**
* the array of tokens that are added.
* This parameter is used when tokenChange type is "tokenChanged".
*/
addedTokens : { type: "sap.m.Token[]"},
/**
* the array of tokens that are removed.
* This parameter is used when tokenChange type is "tokenChanged".
*/
removedTokens : { type: "sap.m.Token[]"}
}
},
/**
* Fired when the tokens aggregation changed due to a user interaction (add / remove token)
*/
tokenUpdate: {
allowPreventDefault : true,
parameters: {
/**
* Type of tokenChange event.
* There are two TokenUpdate types: "added", "removed"
* Use Tokenizer.TokenUpdateType.Added for "added" and Tokenizer.TokenUpdateType.Removed for "removed".
*/
type: {type: "string"},
/**
* The array of tokens that are added.
* This parameter is used when tokenUpdate type is "added".
*/
addedTokens: {type: "sap.m.Token[]"},
/**
* The array of tokens that are removed.
* This parameter is used when tokenUpdate type is "removed".
*/
removedTokens: {type: "sap.m.Token[]"}
}
}
}
}});
var oRb = sap.ui.getCore().getLibraryResourceBundle("sap.m");
///**
// * This file defines behavior for the control,
// */
Tokenizer.prototype.init = function() {
this.bAllowTextSelection = false;
this._oTokensWidthMap = {};
this._oIndicator = null;
this._bAdjustable = false;
this._aTokenValidators = [];
this._oScroller = new ScrollEnablement(this, this.getId() + "-scrollContainer", {
horizontal : true,
vertical : false,
nonTouchScrolling : true
});
if (sap.ui.getCore().getConfiguration().getAccessibility()) {
var sAriaTokenizerContainToken = new InvisibleText({
text: oRb.getText("TOKENIZER_ARIA_CONTAIN_TOKEN")
});
this.setAggregation("_tokensInfo", sAriaTokenizerContainToken);
}
};
/**
* Function determines the callback to be executed on N-more label press
*
* @param {function} fCallback The callback
* @private
*/
Tokenizer.prototype._handleNMoreIndicatorPress = function(fCallback) {
this._fnOnNMorePress = fCallback;
};
/**
* Function determines if the N-more state is active
*
* @private
*/
Tokenizer.prototype._hasMoreIndicator = function () {
var domRef = this.$();
return !!domRef.length && this.$().find(".sapMHiddenToken").length > 0;
};
/**
* Function determines which tokens should be displayed and adds N-more label
*
* @private
*/
Tokenizer.prototype._adjustTokensVisibility = function() {
if (!this.getDomRef()) {
return;
}
var iTokenizerWidth = parseInt(this.getMaxWidth(), 10),
aTokens = this._getVisibleTokens().reverse(),
iTokensCount = aTokens.length,
iLabelWidth, iFreeSpace,
iCounter, iFirstTokenToHide = -1;
// find the index of the first overflowing token
aTokens.some(function (oToken, iIndex) {
iTokenizerWidth = iTokenizerWidth - this._oTokensWidthMap[oToken.getId()];
if (iTokenizerWidth <= 0) {
iFirstTokenToHide = iIndex;
return true;
} else {
iFreeSpace = iTokenizerWidth;
}
}.bind(this));
// adjust the visibility of the tokens
if (iFirstTokenToHide > -1) {
for (iCounter = 0; iCounter < iTokensCount; iCounter++) {
if (iCounter >= iFirstTokenToHide) {
aTokens[iCounter].addStyleClass("sapMHiddenToken");
} else {
aTokens[iCounter].removeStyleClass("sapMHiddenToken");
}
}
this._handleNMoreIndicator(iTokensCount - iFirstTokenToHide);
iLabelWidth = this._oIndicator.width();
// if there is not enough space after getting the actual indicator width, hide the last visible token
// and update the n-more indicator
if (iLabelWidth >= iFreeSpace) {
iFirstTokenToHide = iFirstTokenToHide - 1;
this._handleNMoreIndicator(iTokensCount - iFirstTokenToHide);
aTokens[iFirstTokenToHide].addStyleClass("sapMHiddenToken");
}
} else {
// if no token needs to be hidden, show all
this._showAllTokens();
}
};
/**
* Renders the N-more label
* @private
*
* @param {number} iHiddenTokensCount The number of hidden tokens
* @returns {sap.m.Tokenizer} this instance for method chaining
*/
Tokenizer.prototype._handleNMoreIndicator = function (iHiddenTokensCount) {
if (!this.getDomRef()) {
return this;
}
if (iHiddenTokensCount) {
var sLabelKey = "MULTIINPUT_SHOW_MORE_TOKENS";
if (iHiddenTokensCount === this._getVisibleTokens().length) {
this.$().css("overflow", "visible");
if (iHiddenTokensCount === 1) {
sLabelKey = "TOKENIZER_SHOW_ALL_ITEM";
} else {
sLabelKey = "TOKENIZER_SHOW_ALL_ITEMS";
}
}
this._oIndicator.removeClass("sapUiHidden");
this._oIndicator.html(oRb.getText(sLabelKey, iHiddenTokensCount));
} else {
this.$().css("overflow", "hidden");
this._oIndicator.addClass("sapUiHidden");
}
return this;
};
/**
* Returns the visible tokens
*
* @returns {array} Array of tokens
* @private
*/
Tokenizer.prototype._getVisibleTokens = function () {
return this.getTokens().filter(function (oToken) {
return oToken.getVisible();
});
};
/**
* Function makes all tokens visible, used for collapsed=false
*
* @private
*/
Tokenizer.prototype._showAllTokens = function() {
this._handleNMoreIndicator(0);
this._getVisibleTokens().forEach(function(oToken) {
oToken.removeStyleClass("sapMHiddenToken");
});
};
/**
* Function returns the internally used scroll delegate
*
* @public
* @returns {sap.ui.core.delegate.ScrollEnablement} The scroll delegate
*/
Tokenizer.prototype.getScrollDelegate = function() {
return this._oScroller;
};
/**
* Function scrolls the tokens to the end
*
* @public
*/
Tokenizer.prototype.scrollToEnd = function() {
var domRef = this.getDomRef(),
that;
if (!domRef) {
return;
}
if (!this._sResizeHandlerId) {
that = this;
this._sResizeHandlerId = ResizeHandler.register(domRef, function() {
that.scrollToEnd();
});
}
var scrollDiv = this.$().find(".sapMTokenizerScrollContainer")[0];
domRef.scrollLeft = scrollDiv.scrollWidth;
};
Tokenizer.prototype.setWidth = function(sWidth) {
this.setProperty("width", sWidth, true);
this.$().css("width", this.getWidth());
return this;
};
/**
* Function sets the maximum width of the Tokenizer.
*
* @public
* @param {string} sWidth The new maximal width
*/
Tokenizer.prototype.setMaxWidth = function(sWidth) {
this.setProperty("maxWidth", sWidth, true);
this.$().css("max-width", this.getMaxWidth());
if (this.getDomRef() && this._getAdjustable()) {
this._adjustTokensVisibility();
}
return this;
};
/**
* Function returns whether the n-more indicator is visible
*
* @protected
* @param {boolean} If true the indicator is visible
*/
Tokenizer.prototype._getIndicatorVisibility = function() {
return this._oIndicator && !this._oIndicator.hasClass("sapUiHidden");
};
/**
* Function sets whether the tokens visibility should be adjusted
*
* @protected
* @param {boolean} If true the the tokenizer should adjust the tokens visibility
*/
Tokenizer.prototype._setAdjustable = function(bAdjust) {
this._bAdjustable = bAdjust;
};
/**
* Function gets whether the tokens visibility should be adjusted
*
* @protected
* @returns {boolean} If true the the tokenizer should adjust the tokens visibility
*/
Tokenizer.prototype._getAdjustable = function() {
return this._bAdjustable;
};
/**
* Function sets the tokenizer's width in pixels
*
* @public
* @param {number} nWidth The new width in pixels
*/
Tokenizer.prototype.setPixelWidth = function(nWidth) {
if (typeof nWidth !== "number") {
Log.warning("Tokenizer.setPixelWidth called with invalid parameter. Expected parameter of type number.");
return;
}
this.setWidth(nWidth + "px");
if (this._oScroller) {
this._oScroller.refresh();
}
};
/**
* Function scrolls the tokens to the start
*
* @public
*
*/
Tokenizer.prototype.scrollToStart = function() {
var domRef = this.getDomRef();
if (!domRef) {
return;
}
this._deactivateScrollToEnd();
domRef.scrollLeft = 0;
};
Tokenizer.prototype._deactivateScrollToEnd = function(){
this._deregisterResizeHandler();
};
/**
* Function returns the tokens' width
*
* @public
*
* @returns {number} The complete width of all tokens
*/
Tokenizer.prototype.getScrollWidth = function(){
if (!this.getDomRef()) {
return 0;
}
return this.$().children(".sapMTokenizerScrollContainer")[0].scrollWidth;
};
Tokenizer.prototype.onBeforeRendering = function() {
this._setTokensAria();
this._deregisterResizeHandler();
};
/**
* Called after the control is rendered.
*
* @private
*/
Tokenizer.prototype.onAfterRendering = function() {
if (!this._sResizeHandlerId) {
var that = this;
this._sResizeHandlerId = ResizeHandler.register(this.getDomRef(), function() {
that.scrollToEnd();
});
}
this._oIndicator = this.$().find(".sapMTokenizerIndicator");
};
/**
* Called after a new theme is applied.
*
* @private
*/
Tokenizer.prototype.onThemeChanged = function() {
if (!this._getAdjustable()) {
return;
}
this.getTokens().forEach(function(oToken){
if (oToken.getDomRef() && !oToken.$().hasClass("sapMHiddenToken")) {
this._oTokensWidthMap[oToken.getId()] = oToken.getDomRef().offsetWidth;
}
}.bind(this));
this._adjustTokensVisibility();
};
/**
* Handles the setting of collapsed state
*
* @param {boolean} If true collapses the tokenizer's content
* @private
*/
Tokenizer.prototype._useCollapsedMode = function(bCollapse) {
var oParent = this.getParent(),
aTokens = this._getVisibleTokens();
if (!aTokens.length) {
return;
}
if (bCollapse) {
this._adjustTokensVisibility();
} else {
this._showAllTokens();
}
oParent._syncInputWidth && setTimeout(oParent["_syncInputWidth"].bind(oParent, this), 0);
};
Tokenizer.prototype.invalidate = function(oOrigin) {
var oParent = this.getParent();
if (oParent instanceof sap.m.MultiInput) {
oParent.invalidate(oOrigin);
} else {
Control.prototype.invalidate.call(this, oOrigin);
}
};
/**
* Handle the focus leave event, deselects token
*
* @param {jQuery.Event} oEvent The occuring event
* @private
*/
Tokenizer.prototype.onsapfocusleave = function(oEvent) {
// when focus goes to token, keep the select status, otherwise deselect all tokens
if (document.activeElement == this.getDomRef() || !this._checkFocus()) {
this._changeAllTokensSelection(false);
this._oSelectionOrigin = null;
}
};
/**
* check if all tokens in the tokenizer are selected.
* @returns {boolean} True if all tokens are selected
* @private
*/
Tokenizer.prototype.isAllTokenSelected = function() {
if (this._getVisibleTokens().length === this.getSelectedTokens().length) {
return true;
}
return false;
};
/**
* Handle the key down event for Ctrl+ a , Ctrl+ c and Ctrl+ x
*
* @param {jQuery.Event}oEvent The occuring event
* @private
*/
Tokenizer.prototype.onkeydown = function(oEvent) {
if (oEvent.which === KeyCodes.TAB) {
this._changeAllTokensSelection(false);
}
// ctrl/meta + c OR ctrl/meta + A
if ((oEvent.ctrlKey || oEvent.metaKey) && oEvent.which === KeyCodes.A) {
//to check how many tokens are selected before Ctrl + A in Tokenizer
this._iSelectedToken = this.getSelectedTokens().length;
if (this._getVisibleTokens().length > 0) {
this.focus();
this._changeAllTokensSelection(true);
oEvent.preventDefault();
}
}
// ctrl/meta + c OR ctrl/meta + Insert
if ((oEvent.ctrlKey || oEvent.metaKey) && (oEvent.which === KeyCodes.C || oEvent.which === KeyCodes.INSERT)) {
this._copy();
}
// ctr/meta + x OR Shift + Delete
if (((oEvent.ctrlKey || oEvent.metaKey) && oEvent.which === KeyCodes.X) || (oEvent.shiftKey && oEvent.which === KeyCodes.DELETE)) {
if (this.getEditable()) {
this._cut();
} else {
this._copy();
}
}
};
/**
* Handles the copy event
*
* @private
*/
Tokenizer.prototype._copy = function() {
var selectedTokens = this.getSelectedTokens(),
selectedText = "",
token,
copyToClipboard = function(oEvent) {
if (oEvent.clipboardData) {
oEvent.clipboardData.setData('text/plain', selectedText);
} else {
oEvent.originalEvent.clipboardData.setData('text/plain', selectedText);
}
oEvent.preventDefault();
};
for (var i = 0; i < selectedTokens.length; i++) {
token = selectedTokens[i];
selectedText += (i > 0 ? "\r\n" : "") + token.getText();
}
if (!selectedText) {
return;
}
if (Device.browser.msie && window.clipboardData) {
/* TODO remove after 1.62 version */
window.clipboardData.setData("text", selectedText);
} else {
document.addEventListener('copy', copyToClipboard);
document.execCommand('copy');
document.removeEventListener('copy', copyToClipboard);
}
};
/**
* Handles the cut event
*
* @private
*/
Tokenizer.prototype._cut = function() {
var self = this,
selectedTokens = self.getSelectedTokens(),
selectedText = "",
removedTokens = [],
eventResult,
token,
cutToClipboard = function(oEvent) {
if (oEvent.clipboardData) {
oEvent.clipboardData.setData('text/plain', selectedText);
} else {
oEvent.originalEvent.clipboardData.setData('text/plain', selectedText);
}
oEvent.preventDefault();
};
eventResult = self.fireTokenUpdate({
addedTokens : [],
removedTokens : removedTokens,
type : Tokenizer.TokenUpdateType.Removed
});
for (var i = 0; i < selectedTokens.length; i++) {
token = selectedTokens[i];
selectedText += (i > 0 ? "\r\n" : "") + token.getText();
if (eventResult && token.getEditable()) {
self.removeToken(token);
removedTokens.push(token);
token.destroy();
}
}
if (!selectedText) {
return;
}
if (Device.browser.msie && window.clipboardData) {
/* TODO remove after 1.62 version */
window.clipboardData.setData("text", selectedText);
} else {
document.addEventListener('cut', cutToClipboard);
document.execCommand('cut');
document.removeEventListener('cut', cutToClipboard);
}
};
/**
* Function is called on keyboard backspace, deletes selected tokens
*
* @private
* @param {jQuery.Event} oEvent The event object
*/
Tokenizer.prototype.onsapbackspace = function(oEvent) {
if (this.getSelectedTokens().length === 0) {
this.onsapprevious(oEvent);
} else if (this.getEditable()) {
this._removeSelectedTokens();
}
oEvent.preventDefault();
oEvent.stopPropagation();
};
/**
* Function is called on keyboard delete, deletes token
*
* @private
* @param {jQuery.Event} oEvent The event object
*/
Tokenizer.prototype.onsapdelete = function(oEvent) {
if (this.getEditable()) {
this._removeSelectedTokens();
}
};
/**
* Adjusts the scrollLeft so that the given token is visible from its left side
* @param {sap.m.Token} oToken The token that will be fully visible
* @private
*/
Tokenizer.prototype._ensureTokenVisible = function(oToken) {
if (!oToken || !oToken.getDomRef() || !this.getDomRef()) {
return;
}
var iTokenizerLeftOffset = this.$().offset().left,
iTokenizerWidth = this.$().width(),
iTokenLeftOffset = oToken.$().offset().left,
iTokenWidth = oToken.$().width();
if (this._getVisibleTokens().indexOf(oToken) === 0) {
this.$().scrollLeft(0);
return;
}
if (iTokenLeftOffset < iTokenizerLeftOffset) {
this.$().scrollLeft(this.$().scrollLeft() - iTokenizerLeftOffset + iTokenLeftOffset);
}
if (iTokenLeftOffset - iTokenizerLeftOffset + iTokenWidth > iTokenizerWidth) {
this.$().scrollLeft(this.$().scrollLeft() + (iTokenLeftOffset - iTokenizerLeftOffset + iTokenWidth - iTokenizerWidth));
}
};
/**
* Called when the user presses the left arrow key, selects previous token
* @param {jQuery.Event} oEvent The event triggered by the user
* @private
*/
Tokenizer.prototype.onsapprevious = function(oEvent) {
if (oEvent.which === KeyCodes.ARROW_UP) {
return;
}
var iLength = this.getTokens().length;
if (iLength === 0) {
return;
}
var oFocusedElement = jQuery(document.activeElement).control()[0];
// oFocusedElement could be undefined since the focus element might not correspond to an SAPUI5 Control
var index = oFocusedElement ? this.getTokens().indexOf(oFocusedElement) : -1;
if (index == 0) {
// focus is on first token - we do not handle this event and let it bubble
return;
}
var targetToken;
if (index > 0) {
targetToken = this.getTokens()[index - 1];
this._changeAllTokensSelection(false, targetToken);
targetToken._changeSelection(true);
targetToken.focus();
} else {
targetToken = this.getTokens()[this.getTokens().length - 1];
targetToken._changeSelection(true);
targetToken.focus();
}
this._deactivateScrollToEnd();
this._ensureTokenVisible(targetToken);
// mark the event that it is handled by the control
oEvent.setMarked();
};
/**
* Called when the user presses the right arrow key, selects next token
* @param {jQuery.Event} oEvent The event triggered by the user
* @private
*/
Tokenizer.prototype.onsapnext = function(oEvent) {
if (oEvent.which === KeyCodes.ARROW_DOWN) {
return;
}
var iLength = this.getTokens().length;
if (iLength === 0) {
return;
}
var oFocusedElement = jQuery(document.activeElement).control()[0];
// oFocusedElement could be undefined since the focus element might not correspond to an SAPUI5 Control
var index = oFocusedElement ? this.getTokens().indexOf(oFocusedElement) : -1;
if (index < iLength - 1) {
var oNextToken = this.getTokens()[index + 1];
this._changeAllTokensSelection(false, oNextToken);
oNextToken._changeSelection(true);
oNextToken.focus();
this._ensureTokenVisible(oNextToken);
} else {
// focus is on last token - we do not handle this event and let it bubble
return;
}
this._deactivateScrollToEnd();
// mark the event that it is handled by the control
oEvent.setMarked();
};
/**
* Function adds a validation callback called before any new token gets added to the tokens aggregation
*
* @public
* @param {function} fValidator The validation function
*/
Tokenizer.prototype.addValidator = function(fValidator) {
if (typeof (fValidator) === "function") {
this._aTokenValidators.push(fValidator);
}
};
/**
* Function removes a validation callback
*
* @public
* @param {function} fValidator The validation function
*/
Tokenizer.prototype.removeValidator = function(fValidator) {
var i = this._aTokenValidators.indexOf(fValidator);
if (i !== -1) {
this._aTokenValidators.splice(i, 1);
}
};
/**
* Function removes all validation callbacks
*
* @public
*/
Tokenizer.prototype.removeAllValidators = function() {
this._aTokenValidators = [];
};
/**
* Function validates a given token using the set validators
*
* @private
* @param {object} oParameters Parameter bag containing fields for text, token, suggestionObject and validation callback
* @param {function[]} aValidators [optional] Array of all validators to be used
* @returns {sap.m.Token} A valid token or null
*/
Tokenizer.prototype._validateToken = function(oParameters, aValidators) {
var oToken = oParameters.token;
var sText;
if (oToken && oToken.getText()) {
sText = oToken.getText();
} else {
sText = oParameters.text;
}
var fValidateCallback = oParameters.validationCallback;
var oSuggestionObject = oParameters.suggestionObject;
var i, validator, length;
if (!aValidators) {
aValidators = this._aTokenValidators;
}
length = aValidators.length;
if (length === 0) { // no custom validators, just return given token
if (!oToken && fValidateCallback) {
fValidateCallback(false);
}
return oToken;
}
for (i = 0; i < length; i++) {
validator = aValidators[i];
oToken = validator({
text : sText,
suggestedToken : oToken,
suggestionObject : oSuggestionObject,
asyncCallback : this._getAsyncValidationCallback(aValidators, i, sText, oSuggestionObject, fValidateCallback)
});
if (!oToken) {
if (fValidateCallback) {
fValidateCallback(false);
}
return null;
}
if (oToken === Tokenizer.WaitForAsyncValidation) {
return null;
}
}
return oToken;
};
/**
* Function returns a callback function which is used for executing validators after an asynchronous validator was triggered
* @param {function[]} aValidators The validator array
* @param {int} iValidatorIndex The current validator index
* @param {string} sInitialText The initial text used for validation
* @param {object} oSuggestionObject A pre-validated token or suggestion item
* @param {function} fValidateCallback Callback after validation has finished
* @returns {function} A callback function which is used for executing validators
* @private
*/
Tokenizer.prototype._getAsyncValidationCallback = function(aValidators, iValidatorIndex, sInitialText,
oSuggestionObject, fValidateCallback) {
var that = this,
bAddTokenSuccess;
return function(oToken) {
if (oToken) { // continue validating
aValidators = aValidators.slice(iValidatorIndex + 1);
oToken = that._validateToken({
text : sInitialText,
token : oToken,
suggestionObject : oSuggestionObject,
validationCallback : fValidateCallback
}, aValidators);
bAddTokenSuccess = that._addUniqueToken(oToken, fValidateCallback);
if (bAddTokenSuccess) {
that.fireTokenUpdate({
addedTokens : [oToken],
removedTokens : [],
type : Tokenizer.TokenUpdateType.Added
});
}
} else {
if (fValidateCallback) {
fValidateCallback(false);
}
}
};
};
/**
* Function validates the given text and adds a new token if validation was successful
*
* @public
* @param {object} oParameters - parameter bag containing following fields:
* {sap.m.String} text - the source text {sap.m.Token}
* [optional] token - a suggested token
* {object} [optional] suggestionObject - any object used to find the suggested token
* {function} [optional] validationCallback - callback which gets called after validation has finished
*/
Tokenizer.prototype.addValidateToken = function(oParameters) {
var oToken = this._validateToken(oParameters);
this._addUniqueToken(oToken, oParameters.validationCallback);
};
/**
* Private function used by MultiInput which validates the given text and adds a new token if validation was successful
*
* @private
* @param {object}
* oParameters - parameter bag containing following fields:
* {sap.m.String} text - the source text
* {sap.m.Token} [optional] token - a suggested token
* {object} [optional] suggestionObject - any object used to find the suggested token
* {function} [optional] validationCallback - callback which gets called after validation has finished
*/
Tokenizer.prototype._addValidateToken = function(oParameters) {
var oToken = this._validateToken(oParameters),
bAddTokenSuccessful = this._addUniqueToken(oToken, oParameters.validationCallback);
if (bAddTokenSuccessful) {
this.fireTokenUpdate({
addedTokens : [oToken],
removedTokens : [],
type : Tokenizer.TokenUpdateType.Added
});
}
};
/**
* Function adds token if it does not already exist
*
* @private
* @param {sap.m.Token} oToken The token to be added
* @param {function} fValidateCallback [optional] A validation function callback
* @returns {boolean} True if the token was added
*/
Tokenizer.prototype._addUniqueToken = function(oToken, fValidateCallback) {
if (!oToken) {
return false;
}
var tokenExists = this._tokenExists(oToken);
if (tokenExists) {
var oParent = this.getParent();
if (oParent instanceof sap.m.MultiInput && fValidateCallback) {
fValidateCallback(false);
}
return false;
}
this.addToken(oToken);
if (fValidateCallback) {
fValidateCallback(true);
}
this.fireTokenChange({
addedTokens : [oToken],
removedTokens : [],
type : Tokenizer.TokenChangeType.TokensChanged
});
return true;
};
/**
* Function parses given text, and text is separated by line break
*
* @private
* @param {String} sString The texts that needs to be parsed
* @returns {array} Array of string after parsing
*/
Tokenizer.prototype._parseString = function(sString) {
// for the purpose to copy from column in excel and paste in MultiInput/MultiComboBox
// delimiter is line break
return sString.split(/\r\n|\r|\n/g);
};
/**
* Checks whether the Tokenizer or one of its internal DOM elements has the focus.
* @returns {object} The control that has the focus
* @private
*/
Tokenizer.prototype._checkFocus = function() {
return this.getDomRef() && containsOrEquals(this.getDomRef(), document.activeElement);
};
/**
* Function checks if a given token already exists in the tokens aggregation based on their keys
*
* @private
* @param {sap.m.Token} oToken The token to search for
* @return {boolean} true if it exists, otherwise false
*/
Tokenizer.prototype._tokenExists = function(oToken) {
var tokens = this.getTokens();
if (!(tokens && tokens.length)) {
return false;
}
var key = oToken.getKey();
if (!key) {
return false;
}
var length = tokens.length;
for (var i = 0; i < length; i++) {
var currentToken = tokens[i];
var currentKey = currentToken.getKey();
if (currentKey === key) {
return true;
}
}
return false;
};
Tokenizer.prototype.addToken = function(oToken, bSuppressInvalidate) {
// if tokenizer is in MultiInput
var oParent = this.getParent();
if (oParent instanceof sap.m.MultiInput) {
// if max number is set and the number of existing tokens is equal to or more than the max number, then do not add token.
if (oParent.getMaxTokens() !== undefined && oParent.getTokens().length >= oParent.getMaxTokens()) {
return this;
}
}
this.addAggregation("tokens", oToken, bSuppressInvalidate);
this.fireTokenChange({
token : oToken,
type : Tokenizer.TokenChangeType.Added
});
oToken.addEventDelegate({
onAfterRendering: function () {
if (sap.ui.getCore().isThemeApplied() && oToken.getDomRef() && !oToken.$().hasClass("sapMHiddenToken")) {
this._oTokensWidthMap[oToken.getId()] = oToken.getDomRef().offsetWidth;
}
}.bind(this)
});
return this;
};
Tokenizer.prototype.removeToken = function(oToken) {
oToken = this.removeAggregation("tokens", oToken);
this._bScrollToEndIsActive = true; //Ensure scroll to end is active after rendering
this.fireTokenChange({
token : oToken,
type : Tokenizer.TokenChangeType.Removed
});
return oToken;
};
Tokenizer.prototype.setTokens = function(aTokens) {
var oldTokens = this.getTokens();
this.removeAllTokens(false);
var i;
for (i = 0; i < aTokens.length; i++) {
this.addToken(aTokens[i], true);
}
this.invalidate();
this.fireTokenChange({
addedTokens : aTokens,
removedTokens : oldTokens,
type : Tokenizer.TokenChangeType.TokensChanged
});
};
Tokenizer.prototype.removeAllTokens = function(bFireEvent) {
var tokens = this.getTokens();
var aRemoved = this.removeAllAggregation("tokens");
if (typeof (bFireEvent) === "boolean" && !bFireEvent) {
return aRemoved;
}
this.fireTokenChange({
addedTokens : [],
removedTokens : tokens,
type : Tokenizer.TokenChangeType.TokensChanged
});
this.fireTokenChange({
tokens : tokens,
type : Tokenizer.TokenChangeType.RemovedAll
});
return aRemoved;
};
Tokenizer.prototype.updateTokens = function () {
this.destroyTokens();
this.updateAggregation("tokens");
};
/**
* Function removes all selected tokens
*
* @public
* @returns {sap.m.Tokenizer} this instance for method chaining
*/
Tokenizer.prototype._removeSelectedTokens = function() {
var tokensToBeDeleted = this.getSelectedTokens();
var token,
i,
length,
eventResult;
length = tokensToBeDeleted.length;
if (length === 0) {
return this;
}
eventResult = this.fireTokenUpdate({
addedTokens : [],
removedTokens : tokensToBeDeleted,
type: Tokenizer.TokenUpdateType.Removed
});
if (!eventResult) {
return;
}
for (i = 0; i < length; i++) {
token = tokensToBeDeleted[i];
if (token.getEditable()) {
token.destroy();
}
}
this.scrollToEnd();
this.fireTokenChange({
addedTokens : [],
removedTokens : tokensToBeDeleted,
type : Tokenizer.TokenChangeType.TokensChanged
});
var oParent = this.getParent(),
bIsParentMultiInput = oParent && oParent instanceof sap.m.MultiInput;
if (bIsParentMultiInput) {
// not set focus to MultiInput in phone mode
if (!oParent._bUseDialog) {
oParent.$('inner').focus();
}
} else {
this.focus();
}
this._doSelect();
return this;
};
/**
* Function selects all tokens
*
* @public
* @param {boolean} bSelect [optional] true for selecting, false for deselecting
* @returns {sap.m.Tokenizer} this instance for method chaining
*/
Tokenizer.prototype.selectAllTokens = function(bSelect) {
if (bSelect === undefined) {
bSelect = true;
}
var tokens = this._getVisibleTokens(),
length = tokens.length,
i;
for (i = 0; i < length; i++) {
tokens[i].setSelected(bSelect);
}
this._doSelect();
return this;
};
/**
* Function selects/deselects all tokens and fires the correct "select" or "deselect" events.
* @param {boolean} bSelect Whether the tokens should be selected
* @param {sap.m.Token} skipToken [optional] this token will be skipped when changing the selection
* @private
*/
Tokenizer.prototype._changeAllTokensSelection = function(bSelect, skipToken) {
var tokens = this._getVisibleTokens(),
length = tokens.length,
token,
i;
for (i = 0; i < length; i++) {
token = tokens[i];
if (token !== skipToken) {
token._changeSelection(bSelect);
}
}
this._doSelect();
return this;
};
/**
* Function returns all currently selected tokens
*
* @public
* @returns {sap.m.Token[]} Array of selected tokens or empty array
*/
Tokenizer.prototype.getSelectedTokens = function() {
var aSelectedTokens = [],
tokens = this._getVisibleTokens(),
i,
token,
length = tokens.length;
for (i = 0; i < length; i++) {
token = tokens[i];
if (token.getSelected()) {
aSelectedTokens.push(token);
}
}
return aSelectedTokens;
};
/**
* Function is called when token's delete icon was pressed function destroys token from Tokenizer's aggregation
*
* @private
* @param {sap.m.Token} token The deleted token
*/
Tokenizer.prototype._onTokenDelete = function(token) {
if (token && this.getEditable()) {
var eventResult = this.fireTokenUpdate({
addedTokens : [],
removedTokens : [token],
type : Tokenizer.TokenUpdateType.Removed
});
if (!eventResult) {
return;
}
delete this._oTokensWidthMap[token.getId()];
token.destroy();
this.fireTokenChange({
addedTokens : [],
removedTokens : [token],
type : Tokenizer.TokenChangeType.TokensChanged
});
}
};
Tokenizer.prototype._onTokenSelect = function(oTokenSource, ctrlKey, shiftKey) {
var aTokens = this._getVisibleTokens(),
oToken,
i;
if (shiftKey) {
var oFocusedToken = this._getFocusedToken();
if (!oFocusedToken) {
this._oSelectionOrigin = null;
return;
}
if (this._oSelectionOrigin) {
oFocusedToken = this._oSelectionOrigin;
} else {
this._oSelectionOrigin = oFocusedToken;
}
var iFocusIndex = this.indexOfToken(oFocusedToken),
iIndex = this.indexOfToken(oTokenSource),
iMinIndex = Math.min(iFocusIndex, iIndex),
iMaxIndex = Math.max(iFocusIndex, iIndex);
for (i = 0; i < aTokens.length; i++) {
oToken = aTokens[i];
if (i >= iMinIndex && i <= iMaxIndex) {
oToken._changeSelection(true);
} else if (!ctrlKey) {
oToken._changeSelection(false);
}
}
return;
}
this._oSelectionOrigin = null;
// ctrl key was pressed, do nothing, the token handled it
if (ctrlKey) {
return;
}
// simple select, neither ctrl nor shift key was pressed, deselects other tokens
this._oSelectionOrigin = oTokenSource;
for (i = 0; i < aTokens.length; i++) {
oToken = aTokens[i];
if (oToken !== oTokenSource) {
oToken._changeSelection(false);
}
}
};
Tokenizer.prototype._getFocusedToken = function() {
var oFocusedToken = sap.ui.getCore().byId(document.activeElement.id);
// if the focus is not on a Token in this Tokenizer do nothing
if (!oFocusedToken ||
!(oFocusedToken instanceof sap.m.Token) ||
this.indexOfToken(oFocusedToken) == -1) {
return null;
}
return oFocusedToken;
};
Tokenizer.prototype.setEditable = function(bEditable) {
this.$().toggleClass("sapMTokenizerReadonly", !bEditable);
return this.setProperty("editable", bEditable, true);
};
/**
* Handle the home button, scrolls to the first token
*
* @param {jQuery.Event}oEvent The occuring event
* @private
*/
Tokenizer.prototype.onsaphome = function(oEvent) {
this.scrollToStart();
};
/**
* Handle the end button, scrolls to the last token
*
* @param {jQuery.Event} oEvent The occuring event
* @private
*/
Tokenizer.prototype.onsapend = function(oEvent) {
this.scrollToEnd();
};
/**
* Handle the focus event on the control
*
* @param {jQuery.Event} oEvent The occuring event
* @protected
*/
Tokenizer.prototype.onclick = function(oEvent) {
var bFireIndicatorHandler;
bFireIndicatorHandler = jQuery(oEvent.target).hasClass("sapMTokenizerIndicator") || (oEvent.target === this.getFocusDomRef());
if (bFireIndicatorHandler) {
this._fnOnNMorePress && this._fnOnNMorePress(oEvent);
}
};
/**
* Handles the touch start event on the control.
*
* @param {jQuery.Event} oEvent The event object.
*/
Tokenizer.prototype.ontouchstart = function(oEvent) {
// needed when the control is inside active controls
oEvent.setMarked();
// Workaround for chrome bug
// BCP: 1680011538
if (Device.browser.chrome && window.getSelection()) {
window.getSelection().removeAllRanges();
}
};
/**
* Function cleans up registered eventhandlers
*
* @private
*/
Tokenizer.prototype.exit = function() {
this._deregisterResizeHandler();
};
/**
* Function deregisters eventhandlers
*
* @private
*/
Tokenizer.prototype._deregisterResizeHandler = function(){
if (this._sResizeHandlerId) {
ResizeHandler.deregister(this._sResizeHandlerId);
delete this._sResizeHandlerId;
}
};
/**
* Sets accessibility information about the tokens
*
* @private
*/
Tokenizer.prototype._setTokensAria = function() {
var iTokenCount = this._getVisibleTokens().length,
oInvisibleText,
sTokenizerAria = "";
if (sap.ui.getCore().getConfiguration().getAccessibility()) {
oInvisibleText = this.getAggregation("_tokensInfo");
switch (iTokenCount) {
case 0:
sTokenizerAria = oRb.getText("TOKENIZER_ARIA_CONTAIN_TOKEN");
break;
case 1:
sTokenizerAria = oRb.getText("TOKENIZER_ARIA_CONTAIN_ONE_TOKEN");
break;
default:
sTokenizerAria = oRb.getText("TOKENIZER_ARIA_CONTAIN_SEVERAL_TOKENS", iTokenCount);
break;
}
oInvisibleText.setText(sTokenizerAria);
}
};
/**
* Selects the hidden clip div to enable copy to clipboad.
*
* @private
*/
Tokenizer.prototype._doSelect = function(){
if (this._checkFocus() && this._bCopyToClipboardSupport) {
var oFocusRef = document.activeElement;
var oSelection = window.getSelection();
oSelection.removeAllRanges();
if (this.getSelectedTokens().length) {
var oRange = document.createRange();
oRange.selectNodeContents(this.getDomRef("clip"));
oSelection.addRange(oRange);
}
if (window.clipboardData && oFocusRef.id == this.getId() + "-clip" && this.getDomRef()) {
this.getDomRef().focus();
}
}
};
/**
* Returns if tokens should be rendered in reverse order
* @private
* @returns {boolean} true if tokens should be rendered in reverse order
*/
Tokenizer.prototype.getReverseTokens = function() {
return !!this._reverseTokens;
};
/**
* Sets internal property defining if tokens should be rendered in reverse order
* @param {boolean} bReverseTokens Whether tokens should be rendered in reverse
* @private
*/
Tokenizer.prototype.setReverseTokens = function(bReverseTokens) {
this._reverseTokens = bReverseTokens;
};
/**
* Gets the accessibility text aggregation id
* @returns {string} Returns the InvisibleText control id
* @protected
*/
Tokenizer.prototype.getTokensInfoId = function() {
return this.getAggregation("_tokensInfo").getId();
};
Tokenizer.TokenChangeType = {
Added : "added",
Removed : "removed",
RemovedAll : "removedAll",
TokensChanged : "tokensChanged"
};
Tokenizer.TokenUpdateType = {
Added : "added",
Removed : "removed"
};
Tokenizer.WaitForAsyncValidation = "sap.m.Tokenizer.WaitForAsyncValidation";
return Tokenizer;
});