UNPKG

@progress/kendo-ui

Version:

This package is part of the [Kendo UI for jQuery](http://www.telerik.com/kendo-ui) suite.

1,352 lines (1,351 loc) 56.4 kB
//#region ../src/kendo.tabstrip.js const __meta__ = { id: "tabstrip", name: "TabStrip", category: "web", description: "The TabStrip widget displays a collection of tabs with associated tab content.", depends: [ "data", "icons", "html.button", "sortable" ], features: [{ id: "tabstrip-fx", name: "Animation", description: "Support for animation", depends: ["fx"] }] }; (function($, undefined) { var kendo = window.kendo, ui = kendo.ui, keys = kendo.keys, map = $.map, each = $.each, trim = kendo.trim, extend = $.extend, isFunction = kendo.isFunction, template = kendo.template, outerWidth = kendo._outerWidth, outerHeight = kendo._outerHeight, Widget = ui.Widget, excludedNodesRegExp = /^(a|div)$/i, excludedNodesRegExp_DOM_DS = /^(a|div|button)$/i, NS = ".kendoTabStrip", IMG = "img", HREF = "href", PREV = "prev", NEXT = "next", SHOW = "show", LINK = "k-link", LINK_TEXT = "k-link-text", LAST = "k-last", CLICK = "click", ERROR = "error", EMPTY = ":empty", IMAGE = "k-image", FIRST = "k-first", SELECT = "select", ACTIVATE = "activate", CONTENT = "k-tabstrip-content", CONTENTURL = "contentUrl", MOUSEENTER = "mouseenter", MOUSELEAVE = "mouseleave", CONTENTLOAD = "contentLoad", DISABLEDSTATE = "k-disabled", ACTIVESTATE = "k-active", FOCUSEDSTATE = "k-focus", HOVERSTATE = "k-hover", DOM_DATASOURCE_EMPTY = "EMPTY_URL", NAVIGATABLEITEMS = ".k-tabstrip-item:not(." + DISABLEDSTATE + ")", KEYBOARDNAVIGATABLEITEMS = ".k-tabstrip-item", HOVERABLEITEMS = ".k-tabstrip-items > " + NAVIGATABLEITEMS + ":not(." + ACTIVESTATE + ")", DEFAULTDISTANCE = 200, ARIA_HIDDEN = "aria-hidden", ARIA_CONTROLS = "aria-controls", ARIA_DISABLED = "aria-disabled", ARIA_SELECTED = "aria-selected", ARIA_ORIENTATION = "aria-orientation", ARIA_LABELLEDBY = "aria-labelledby", templates = { content: (data) => `<div class='k-tabstrip-content' ${data.contentAttributes(data)} tabindex='0'>${data.content(data.item)}</div>`, textWrapper: ({ tag, item, contentUrl, textAttributes, image, sprite, text }) => `<${tag(item)} class='${LINK}' ${contentUrl(item)} ${textAttributes(item)}>` + `${image(item)}${sprite(item)}` + ((item.icon || item.iconClass) && item.iconPosition === "before" ? kendo.ui.icon({ icon: item.icon || "none", iconClass: item.iconClass }) : "") + `<span class='${LINK_TEXT}'>${text(item)}</span>` + ((item.icon || item.iconClass) && item.iconPosition === "after" ? kendo.ui.icon({ icon: item.icon || "none", iconClass: item.iconClass }) : "") + `</${tag(item)}>`, item: (data) => templates.itemWrapper(data, `${data.textWrapper(data)}`), itemWrapper: (data, item) => `<li class='${data.wrapperCssClass(data.group, data.item)}' ${data.itemAttributes(data.item)} role='tab' ${data.item.active ? "aria-selected='true'" : ""}>` + item + "</li>", image: ({ imageUrl }) => `<img class='k-image' alt='' src='${imageUrl}' />`, sprite: ({ spriteCssClass }) => `<span class='k-sprite ${spriteCssClass}'></span>`, empty: () => "", itemActionsWrapperTemplate: () => `<span class="k-item-actions"></span>`, itemActionTemplate: ({ element, icon, iconClass, attributes }) => { let resolvedAttributes = attributes ? attributes.toJSON ? attributes.toJSON() : attributes : {}; return kendo.html.renderButton(element || $("<button unselectable='on'></button>").attr(resolvedAttributes), { icon, iconClass, fillMode: "flat" }); } }, rendering = { wrapperCssClass: function(group, item) { var result = ["k-tabstrip-item"], index = item.index; const classAttributes = item.attributes && item.attributes.class || ""; if (item.enabled === false) { result.push("k-disabled"); } if (index === 0) { result.push("k-first"); } if (index == group.length - 1) { result.push("k-last"); } classAttributes.split(" ").forEach(function(className) { if (className && !result.includes(className)) { result.push(className); } }); return result.join(" "); }, itemAttributes: function(item) { const attributes = item.attributes || {}; return Object.entries(attributes).map(([key, value]) => { if (key === "class" || key === "role" || key === "aria-selected") { return ""; } return `${key}='${kendo.htmlEncode(value)}'`; }).join(" "); }, textAttributes: function(item) { return item.url ? " href='" + kendo.sanitizeLink(item.url) + "'" : ""; }, text: function(item) { return item.encoded === false ? item.text : kendo.htmlEncode(item.text); }, tag: function(item) { return item.url ? "a" : "span"; }, contentAttributes: function(content) { return content.active !== true ? ` ${kendo.attr("style-display")}="none" aria-hidden='true'` : ""; }, content: function(item) { return item.content ? item.content : item.contentUrl ? "" : "&nbsp;"; }, contentUrl: function(item) { return item.contentUrl ? kendo.attr("content-url") + "=\"" + item.contentUrl + "\"" : ""; } }; function updateTabClasses(tabs, options) { tabs.children(IMG).addClass(IMAGE); tabs.children("a").addClass(LINK).children(IMG).addClass(IMAGE); tabs.filter("li[disabled]").addClass(DISABLEDSTATE).attr(ARIA_DISABLED, "true").prop("disabled", false); tabs.filter(":not([class*=k-state])").children("a").filter(":focus").parent().addClass(ACTIVESTATE); tabs.attr("role", "tab"); tabs.each(function() { var item = $(this); item.attr(ARIA_SELECTED, item.is("." + ACTIVESTATE)); const regex = options._enableDOMDataSource ? excludedNodesRegExp_DOM_DS : excludedNodesRegExp; if (!item.children("." + LINK).length) { item.contents().filter(function() { return !this.nodeName.match(regex) && !(this.nodeType == 3 && !trim(this.nodeValue)); }).wrapAll("<span UNSELECTABLE='on' class='" + LINK + "'/>").wrapAll("<span UNSELECTABLE='on' class='" + LINK_TEXT + "'/>"); } }); } function updateFirstLast(tabGroup) { const tabs = tabGroup.children(".k-tabstrip-item"); tabs.filter(".k-first:not(:first-child)").removeClass(FIRST); tabs.filter(".k-last:not(:last-child)").removeClass(LAST); tabs.filter(":first-child").addClass(FIRST); if (tabs.length > 1) { tabs.filter(":last-child").addClass(LAST); } } function scrollButtonHtml(buttonClass, iconClass) { return `<span aria-hidden='true' class='k-button k-button-flat k-icon-button k-rounded-none k-tabstrip-${buttonClass}' unselectable='on'>${kendo.ui.icon({ icon: iconClass, iconClass: "k-button-icon" })}</span>`; } function ajaxXhr() { return $.ajaxSettings.xhr(); } var TabStrip = Widget.extend({ init: function(element, options) { var that = this, value; Widget.fn.init.call(that, element, options); that._animations(that.options); options = that.options; if (kendo.isPresent(TabStrip._enableDOMDataSource)) { options._enableDOMDataSource = TabStrip._enableDOMDataSource; } that._contentUrls = options.contentUrls || []; that._wrapper(); that._isRtl = kendo.support.isRtl(that.wrapper); that._updateClasses(); that._dataSource(); that._tabindex(that.tabGroup); that.tabGroup.attr("role", "tablist"); if (options.dataSource || options._enableDOMDataSource) { that.dataSource.fetch(); } that._removeAdditionalWrapperClasses(); that._tabSizes(); that._tabPosition(); that._scrollable(); that._tabAlignment(); that._sortable(); that._processContentUrls(); that._attachEvents(); if (that.options.value) { value = that.options.value; } that._initialActivate(); that.value(value); kendo.notify(that); if (options._enableDOMDataSource && options.contentUrls) { options.contentUrls = options.contentUrls.map(function(url) { return url || DOM_DATASOURCE_EMPTY; }); that._contentUrls = options.contentUrls; that._updateContentElements(options._enableDOMDataSource); } if (that._showWatermarkOverlay) { that._showWatermarkOverlay(that.element[0]); } }, events: [ SELECT, ACTIVATE, SHOW, ERROR, CONTENTLOAD, "change", "dataBinding", "dataBound" ], options: { name: "TabStrip", dataEncodedField: "", dataTextField: "", dataContentField: "", dataImageUrlField: "", dataUrlField: "", dataSpriteCssClass: "", dataContentUrlField: "", dataIconField: "icon", dataIconPositionField: "iconPosition", tabPosition: "top", tabAlignment: "start", size: undefined, tabTemplate: null, animation: { open: { effects: "expand:vertical fadeIn", duration: 200 }, close: { duration: 200 } }, closable: false, collapsible: false, navigatable: true, contentUrls: false, applyMinHeight: true, scrollable: { distance: DEFAULTDISTANCE, scrollButtonsPosition: "split", scrollButtons: "auto" }, sortable: false, _enableDOMDataSource: false }, setDataSource: function(dataSource) { var that = this; that.options.dataSource = dataSource; that._dataSource(); that.dataSource.fetch(); }, setOptions: function(options) { var that = this, animation = that.options.animation; that._animations(options); if (options.contentUrls) { that._contentUrls = options.contentUrls; } options.animation = extend(true, animation, options.animation); if (options.navigatable) { that.tabGroup.on("keydown" + NS, that._keyDownProxy); } else { that.tabGroup.off("keydown" + NS, that._keyDownProxy); } Widget.fn.setOptions.call(that, options); }, activateTab: function(item) { if (this.tabGroup.children("[data-animating]").length) { return; } item = this.tabGroup.find(item); var that = this, animationSettings = that.options.animation, animation = animationSettings.open, close = extend({}, animationSettings.close), hasCloseAnimation = close && "effects" in close, neighbours = item.parent().children(), oldTab = neighbours.filter("." + ACTIVESTATE), itemIndex = neighbours.index(item), isAnimationEnabled = animation && "duration" in animation && "effects" in animation; close = extend(hasCloseAnimation ? close : extend({ reverse: true }, animation), { hide: true }); if (kendo.size(animation.effects)) { oldTab.kendoRemoveClass(ACTIVESTATE, { duration: close.duration }); item.kendoRemoveClass(HOVERSTATE, { duration: close.duration }); } else { oldTab.removeClass(ACTIVESTATE); item.removeClass(HOVERSTATE); } var contentAnimators = that.contentAnimators; if (that.inRequest) { that.xhr.abort(); that.inRequest = false; } if (contentAnimators.length === 0) { that.tabGroup.find("." + ACTIVESTATE); item.addClass(ACTIVESTATE); that._current(item, true); that.trigger("change"); if (that._scrollableModeActive) { that._scrollTabsToItem(item); } return false; } var visibleContents = contentAnimators.filter("." + ACTIVESTATE), contentHolder = that.contentHolder(itemIndex), contentElement = contentHolder.closest(".k-tabstrip-content"); that.tabsHeight = outerHeight(that.tabGroup) + parseInt(that.wrapper.css("border-top-width"), 10) + parseInt(that.wrapper.css("border-bottom-width"), 10); if (contentHolder.length === 0) { visibleContents.removeClass(ACTIVESTATE).attr(ARIA_HIDDEN, true).kendoStop(true, true).kendoAnimate(close); return false; } item.attr("data-animating", true); var isAjaxContent = (item.children("." + LINK).data(CONTENTURL) || that._contentUrls[itemIndex] || false) && contentHolder.is(EMPTY), showContentElement = function() { oldTab.attr(ARIA_SELECTED, false); item.attr(ARIA_SELECTED, true); that.tabGroup.attr("aria-activedescendant", item.attr("id")); that._current(item, true); contentElement.addClass(ACTIVESTATE).removeAttr(ARIA_HIDDEN).kendoStop(true, true).kendoAnimate(extend({ init: function() { that.trigger(SHOW, { item: item[0], contentElement: contentHolder[0] }); kendo.resize(contentHolder); } }, animation, { complete: function() { that.element.css("min-height", oldMinHeight); item.removeAttr("data-animating"); that.trigger(ACTIVATE, { item: item[0], contentElement: contentHolder[0] }); kendo.resize(contentHolder); } })); }, showContent = function() { if (!isAjaxContent) { showContentElement(); that.trigger("change"); } else { item.removeAttr("data-animating"); that.ajaxRequest(item, contentHolder, function() { item.attr("data-animating", true); showContentElement(); that.trigger("change"); }); } if (that._scrollableModeActive) { that._scrollTabsToItem(item); } }; var oldMinHeight = that.element.css("min-height"); if (that.options.applyMinHeight) { that.element.css("min-height", that.element.outerHeight()); } visibleContents.removeClass(ACTIVESTATE); that.tabGroup.find("." + ACTIVESTATE); if (kendo.size(animation.effects)) { item.kendoAddClass(ACTIVESTATE, { duration: animation.duration }); } else { item.addClass(ACTIVESTATE); } visibleContents.attr(ARIA_HIDDEN, true); if (visibleContents.length) { visibleContents.kendoStop(true, true).kendoAnimate(extend({ complete: showContent }, close)); } else { showContent(); } return true; }, ajaxRequest: function(element, content, complete, url) { element = this.tabGroup.find(element); var that = this, link = element.find("." + LINK), data = {}; url = url || link.data(CONTENTURL) || that._contentUrls[element.index()] || link.attr(HREF); that.inRequest = true; var ajaxOptions = { type: "GET", cache: false, url, dataType: "html", data, xhr: ajaxXhr, error: function(xhr, status) { if (that.trigger("error", { xhr, status })) { this.complete(); } }, complete: function(xhr) { that.inRequest = false; }, success: function(data) { try { kendo.destroy(content); content.html(data); } catch (e) { this.error(this.xhr, "error"); } if (complete) { complete.call(that, content); } that.trigger(CONTENTLOAD, { item: element[0], contentElement: content[0] }); } }; if (typeof url === "object") { ajaxOptions = $.extend(true, {}, ajaxOptions, url); if (isFunction(ajaxOptions.url)) { ajaxOptions.url = ajaxOptions.url(); } if (isFunction(ajaxOptions.data)) { ajaxOptions.data = ajaxOptions.data(); } } that.xhr = $.ajax(ajaxOptions); }, append: function(tab) { var that = this, inserted = that._create(tab); each(inserted.tabs, function(idx) { var contents = inserted.contents[idx]; that.tabGroup.append(this); if (that.options.tabPosition == "bottom") { that.tabWrapper.before(contents); } else { that.wrapper.append(contents); } }); updateFirstLast(that.tabGroup); that._updateContentElements(); that.resize(true); return that; }, contentElement: function(itemIndex) { if (isNaN(itemIndex - 0)) { return undefined; } var contentElements = this.contentElements && this.contentElements[0] && !kendo.kineticScrollNeeded ? this.contentElements : this.contentAnimators; var id = $(this.tabGroup.children()[itemIndex]).attr(ARIA_CONTROLS); if (contentElements) { for (var i = 0, len = contentElements.length; i < len; i++) { if (contentElements.eq(i).closest(".k-tabstrip-content")[0].id == id) { return contentElements[i]; } } } return undefined; }, contentHolder: function(itemIndex) { var contentElement = $(this.contentElement(itemIndex)), scrollContainer = contentElement.children(".km-scroll-container"); return kendo.support.touch && scrollContainer[0] ? scrollContainer : contentElement; }, deactivateTab: function(item) { var that = this, animationSettings = that.options.animation, animation = animationSettings.open, close = extend({}, animationSettings.close), hasCloseAnimation = close && "effects" in close; item = that.tabGroup.find(item); close = extend(hasCloseAnimation ? close : extend({ reverse: true }, animation), { hide: true }); if (kendo.size(animation.effects)) { item.kendoRemoveClass(ACTIVESTATE, { duration: animation.duration }); } else { item.removeClass(ACTIVESTATE); } item.attr(ARIA_SELECTED, false); that.tabGroup.removeAttr("aria-activedescendant"); that.contentAnimators.filter("." + ACTIVESTATE).kendoStop(true, true).kendoAnimate(close).removeClass(ACTIVESTATE).attr(ARIA_HIDDEN, true); }, _removeAdditionalWrapperClasses: function() { const that = this; that.wrapper.removeClass("k-tabstrip-sm k-tabstrip-md k-tabstrip-lg"); that.tabGroup.removeClass("k-tabstrip-items-start k-tabstrip-items-center k-tabstrip-items-end k-tabstrip-items-justify k-tabstrip-items-stretched"); }, destroy: function() { var that = this; const isHidden = that.options.scrollable && that.options.scrollable.scrollButtons === "hidden"; Widget.fn.destroy.call(that); if (that._refreshHandler) { that.dataSource.unbind("change", that._refreshHandler); } if (that.options.scrollable && isHidden) { that.tabGroup.unbind("scroll" + NS); } that._removeAdditionalWrapperClasses(); that.wrapper.off(NS); that.tabGroup.off(NS); if (that._scrollableModeActive && !isHidden) { that._scrollPrevButton.off().remove(); that._scrollNextButton.off().remove(); } kendo.destroy(that.wrapper); }, disable: function(element) { this._toggleDisabled(element, false); return this; }, enable: function(element, state) { this._toggleDisabled(element, state !== false); return this; }, insertAfter: function(tab, referenceTab) { if ($(tab).is($(referenceTab))) { referenceTab = this.tabGroup.find(referenceTab).prev(); } else { referenceTab = this.tabGroup.find(referenceTab); } var that = this, inserted = that._create(tab), referenceContent = that.element.find("[id='" + referenceTab.attr(ARIA_CONTROLS) + "']"); each(inserted.tabs, function(idx) { var contents = inserted.contents[idx]; var fromIndex = inserted.newTabsCreated ? that._contentUrls.length - (inserted.tabs.length - idx) : $(contents).index() - 1; referenceTab.after(this); referenceContent.after(contents); that._moveUrlItem(fromIndex, $(this).index()); }); updateFirstLast(that.tabGroup); that._updateContentElements(inserted.newTabsCreated); that.resize(true); return that; }, insertBefore: function(tab, referenceTab) { if ($(tab).is($(referenceTab))) { referenceTab = this.tabGroup.find(referenceTab).next(); } else { referenceTab = this.tabGroup.find(referenceTab); } var that = this, inserted = that._create(tab), referenceContent = that.element.find("[id='" + referenceTab.attr(ARIA_CONTROLS) + "']"); each(inserted.tabs, function(idx) { var contents = inserted.contents[idx]; var fromIndex = inserted.newTabsCreated ? that._contentUrls.length - (inserted.tabs.length - idx) : $(contents).index() - 1; referenceTab.before(this); referenceContent.before(contents); that._moveUrlItem(fromIndex, $(this).index()); }); updateFirstLast(that.tabGroup); that._updateContentElements(inserted.newTabsCreated); that.resize(true); return that; }, items: function() { return this.tabGroup[0].children; }, refresh: function(e) { var that = this, options = that.options, encoded = kendo.getter(options.dataEncodedField), text = kendo.getter(options.dataTextField), content = kendo.getter(options.dataContentField), contentUrl = kendo.getter(options.dataContentUrlField), image = kendo.getter(options.dataImageUrlField), url = kendo.getter(options.dataUrlField), sprite = kendo.getter(options.dataSpriteCssClass), icon = kendo.getter(options.dataIconField), iconPosition = kendo.getter(options.dataIconPositionField), idx, tabs = [], tab, action, view = that.dataSource.view(), length; const enableDOMDataSource = that._enableDataSourceFromDOM; const pristineData = that.dataSource._pristineData; e = e || {}; action = e.action; if (action) { view = e.items; } for (idx = 0, length = view.length; idx < length; idx++) { tab = { text: view[idx] && view[idx].text || text(view[idx]) }; if (options?.tabTemplate) { tab.model = view[idx]; tab.template = options.tabTemplate; } if (options?.dataEncodedField) { tab.encoded = encoded(view[idx]); } if (options?.dataContentField) { tab.content = content(view[idx]); } if (options?.dataContentUrlField) { tab.contentUrl = contentUrl(view[idx]); } if (options?.dataUrlField) { tab.url = url(view[idx]); } if (options?.dataImageUrlField) { tab.imageUrl = image(view[idx]); } if (options?.dataSpriteCssClass) { tab.spriteCssClass = sprite(view[idx]); } if (view[idx]?.actions) { const actions = typeof view[idx].actions === "object" ? Array.from(view[idx].actions) : view[idx].actions; tab.actions = actions; } if (icon(view[idx]) || view[idx]?.iconClass) { tab.icon = icon(view[idx]); tab.iconClass = view[idx].iconClass; tab.iconPosition = iconPosition(view[idx]) ?? "before"; } tab.enabled = view[idx]?.enabled; tab.closable = view[idx]?.closable ?? options.closable; if (pristineData) { tab.attributes = pristineData[idx]?.attributes; } if (view[idx]?.content) { tab.content = view[idx].content; } tabs[idx] = tab; } if (e.action == "add") { if (e.index < that.tabGroup.children().length) { that.insertBefore(tabs, that.tabGroup.children().eq(e.index)); } else { that.append(tabs); } } else if (e.action == "remove") { for (idx = 0; idx < view.length; idx++) { that.remove(e.index); } } else if (e.action == "itemchange") { idx = that.dataSource.view().indexOf(view[0]); if (e.field === options.dataTextField) { that.tabGroup.children().eq(idx).find(".k-link").text(view[0].get(e.field)); } if (e.field === options.dataUrlField) { that._contentUrls[idx] = view[0].get(e.field); } } else if (enableDOMDataSource && kendo.isPresent(that.dataSource)) { that._wrapExistingDOMItems(); that._updateContentElements(); that._enableDataSourceFromDOM = false; } else { that.trigger("dataBinding"); that.remove("li"); that._contentUrls = []; that.append(tabs); that.trigger("dataBound"); } }, _wrapExistingDOMItems: function() { const that = this; const items = that.tabGroup.children("li"); const dataSource = that.dataSource; const dataItems = dataSource.view(); dataItems.forEach((dataItem, index) => { const item = items.eq(index); const linkContainer = item.find("." + LINK); const tabText = linkContainer.find("." + LINK_TEXT); const checkActions = kendo.isPresent(dataItem.actions) && dataItem.actions.length || dataItem.closable || !dataItem.closable && that.options.closable; if (dataItem.contentUrl) { linkContainer.attr("data-content-url", dataItem.contentUrl); } if (!that.options.contentUrls) { that._contentUrls.push(dataItem.contentUrl || DOM_DATASOURCE_EMPTY); } if (dataItem.icon || dataItem.iconClass) { if (dataItem.iconPosition === "before") { linkContainer.prepend(kendo.ui.icon({ icon: dataItem.icon || "none", iconClass: dataItem.iconClass })); } if (dataItem.iconPosition === "after") { linkContainer.append(kendo.ui.icon({ icon: dataItem.icon || "none", iconClass: dataItem.iconClass })); } } let template = ""; if (dataItem.imageUrl) { template += templates.image({ imageUrl: dataItem.imageUrl }); } if (dataItem.spriteCssClass) { template += templates.sprite({ spriteCssClass: dataItem.spriteCssClass }); } linkContainer.prepend(template); if (checkActions) { that._initTabActions(item, dataItem); } if (dataItem.text) { if (dataItem.encoded) { tabText.text(kendo.htmlEncode(dataItem.text)); } else { const tempDiv = $("<div>").html(dataItem.text); if (tempDiv.children().length > 0) { tabText.html(dataItem.text); } else { tabText.text(dataItem.text); } } } if (item.attr("data-content") && !dataItem.contentUrl) { let tabContent = that.contentElements.eq(index); tabContent.text(dataItem.content); } if (dataItem.hasOwnProperty("enabled")) { dataItem.enabled ? item.removeClass(DISABLEDSTATE).attr(ARIA_DISABLED, true) : item.addClass(DISABLEDSTATE).removeAttr(ARIA_DISABLED, false); } else if (item.hasClass(DISABLEDSTATE)) { item.addClass(DISABLEDSTATE).removeAttr(ARIA_DISABLED, false); } }); }, reload: function(element) { element = this.tabGroup.find(element); var that = this; var contentUrls = that._contentUrls; element.each(function() { var item = $(this), contentUrl = item.find("." + LINK).data(CONTENTURL) || contentUrls[item.index()], content = that.contentHolder(item.index()); if (contentUrl) { that.ajaxRequest(item, content, null, contentUrl); } }); return that; }, remove: function(elements) { var that = this; var type = typeof elements; var contents; if (type === "string") { elements = that.tabGroup.find(elements); } else if (type === "number") { elements = that.tabGroup.children().eq(elements); } contents = elements.map(function() { var idx = $(this).index(); var content = that.contentElement(idx); kendo.destroy(content); that._removeUrlItem(idx); return content; }); elements.remove(); contents.empty(); contents.remove(); that._updateContentElements(); that.resize(true); return that; }, select: function(element) { var that = this; if (arguments.length === 0) { return that.tabGroup.children("li." + ACTIVESTATE); } if (!isNaN(element)) { element = that.tabGroup.children().get(element); } element = that.tabGroup.find(element); $(element).each(function(index, item) { item = $(item); if (!item.hasClass(ACTIVESTATE) && !that.trigger(SELECT, { item: item[0], contentElement: that.contentHolder(item.index())[0] })) { that.activateTab(item); that.tabGroup.attr("aria-activedescendant", item.attr("id")); } }); return that; }, value: function(value) { var that = this; if (value !== undefined) { if (value != that.value()) { that.tabGroup.children().each(function() { if (kendo.trim($(this).text()) == value) { that.select(this); } }); } } else { return that.select().text(); } }, _initTabActions: function(tab, tabOptions) { const that = this; const tabElement = $(tab); let actions = []; let isClosable = tabOptions.closable; let closeButtonAttributes = { icon: "x", attributes: { "ref-close-button": true }, action: that._handleClose }; if (tabOptions.actions) { actions = Array.from(tabOptions.actions); } if (isClosable) { actions.push(closeButtonAttributes); } if (actions?.length) { const actionsWrapperTemplate = $(templates.itemActionsWrapperTemplate()); const existingActionButtons = tabElement.find("button"); actions.forEach((action, index) => { const isClosableAction = isClosable && index > existingActionButtons.length - 1; if (existingActionButtons.length && !isClosableAction) { action.element = existingActionButtons.eq(index); } const actionTemplate = $(templates.itemActionTemplate(action)); if (!existingActionButtons.length) { actionsWrapperTemplate.append(actionTemplate); } if (isClosableAction && existingActionButtons.length) { existingActionButtons.parent().append(actionTemplate); } if (isFunction(action?.action)) { if (!existingActionButtons.length || isClosableAction) { actionTemplate.bind(CLICK, action.action.bind(that)); } else { existingActionButtons.eq(index).bind(CLICK, action.action.bind(that)); } } }); if (existingActionButtons.length) { existingActionButtons.wrapAll(actionsWrapperTemplate); } else { tabElement.append(actionsWrapperTemplate); } } return tabElement[0]; }, _handleClose: function(e) { const that = this; const target = $(e.currentTarget); const tab = target.closest(".k-tabstrip-item"); if (tab.hasClass(ACTIVESTATE)) { if (tab.prev().length > 0) { that.activateTab(tab.prev()); } else { that.activateTab(tab.next()); } } that.remove(tab); }, _tabAlignment: function() { const that = this; let tabAlignment = that.options.tabAlignment; if (that._scrollableModeActive) { tabAlignment = "start"; } that.tabGroup.addClass("k-tabstrip-items-" + tabAlignment); }, _active: function() { var that = this; setTimeout(function() { var item = that.tabGroup.children().filter("." + ACTIVESTATE); item = item[0] ? item : that._endItem("first"); if (item[0]) { that._current(item); } }, 100); }, _animations: function(options) { if (options && "animation" in options && !options.animation) { options.animation = { open: { effects: {} }, close: { effects: {} } }; } }, _appendUrlItem: function(url) { this._contentUrls.push(url); }, _attachEvents: function() { var that = this, options = that.options; that.tabGroup.on(CLICK + NS, ".k-disabled .k-link", false).on(CLICK + NS, " > " + NAVIGATABLEITEMS, that._itemClick.bind(that)); that.wrapper.on("focus" + NS, function() { that.tabGroup.trigger("focus"); }); if (options.scrollable && options.scrollable.scrollButtons === "hidden") { that.tabGroup.bind("scroll", function(e) { that._toggleScrollButtons(); }); } that.tabGroup.on(MOUSEENTER + NS + " " + MOUSELEAVE + NS, HOVERABLEITEMS, that._toggleHover).on("focus" + NS, that._active.bind(that)).on("blur" + NS, function() { that._current(null); }); that._keyDownProxy = that._keydown.bind(that); if (options.navigatable) { that.tabGroup.on("keydown" + NS, that._keyDownProxy); } $(window).on("resize" + NS, that._resize.bind(that)); }, _click: function(item) { var that = this, link = item.find("." + LINK), href = link.attr(HREF), collapse = that.options.collapsible, index = item.index(), contentHolder = that.contentHolder(index), prevent, isAnchor, neighbours = item.parent().children(), oldFocusedTab = neighbours.filter("." + FOCUSEDSTATE); if (item.closest(".k-tabstrip")[0] != that.wrapper[0]) { return; } if (item.is("." + DISABLEDSTATE + (!collapse ? ",." + ACTIVESTATE : ""))) { oldFocusedTab.removeClass(FOCUSEDSTATE); that._focused = item; item.addClass(FOCUSEDSTATE); that._current(item); if (that._scrollableModeActive) { that._scrollTabsToItem(item); } return true; } isAnchor = link.data(CONTENTURL) || that._contentUrls[index] || href && (href.charAt(href.length - 1) == "#" || href.indexOf("#" + that.element[0].id + "-") != -1); prevent = !href || isAnchor; if (that.tabGroup.children("[data-animating]").length) { return prevent; } if (that.trigger(SELECT, { item: item[0], contentElement: contentHolder[0] })) { return true; } if (prevent === false) { return; } if (collapse && item.is("." + ACTIVESTATE)) { that.deactivateTab(item); return true; } if (that.activateTab(item)) { that._current(item); prevent = true; } return prevent; }, _create: function(tab) { var that = this, tabs, contents, content, newTabsCreated = false; tab = tab instanceof kendo.data.ObservableArray ? tab.toJSON() : tab; if ($.isPlainObject(tab) || Array.isArray(tab)) { tab = Array.isArray(tab) ? tab : [tab]; newTabsCreated = true; tabs = map(tab, function(value, idx) { that._appendUrlItem(tab[idx].contentUrl || null); const renderedTabItem = TabStrip.renderItem({ group: that.tabGroup, item: extend(value, { index: idx }) }); value.closable = value.closable ?? that.options.closable; return $(that._initTabActions(renderedTabItem, value)); }); contents = map(tab, function(value, idx) { if (typeof value.content == "string" || value.contentUrl) { let tabstripContent = $(TabStrip.renderContent({ item: extend(value, { index: idx }) })); kendo.applyStylesFromKendoAttributes(tabstripContent, ["display"]); return tabstripContent; } return $("<div class='" + CONTENT + "'></div>"); }); } else { if (typeof tab == "string" && tab[0] != "<") { tabs = that.element.find(tab); } else { tabs = $(tab); } contents = $(); tabs.each(function() { if (/k-tabstrip-items/.test(this.parentNode.className)) { var element = that.element.find("[id='" + this.getAttribute(ARIA_CONTROLS) + "']"); content = element; } else { content = $("<div class='" + CONTENT + "'/>"); } contents = contents.add(content); }); updateTabClasses(tabs, that.options); } return { tabs, contents, newTabsCreated }; }, _current: function(candidate, preventFocus) { var that = this, focused = that._focused; if (candidate === undefined) { return focused; } if (focused && candidate && focused[0] === candidate[0]) { focused = false; } if (focused) { focused.removeClass(FOCUSEDSTATE); } if (candidate && !preventFocus) { candidate.addClass(FOCUSEDSTATE); } that._focused = candidate; }, _dataSource: function() { var that = this; if (that.dataSource && that._refreshHandler) { that.dataSource.unbind("change", that._refreshHandler); } else { that._refreshHandler = that.refresh.bind(that); } const dataSource = that.options.dataSource || that._createDataSourceFromDOM(); that.dataSource = kendo.data.DataSource.create(dataSource).bind("change", that._refreshHandler); }, _createDataSourceFromDOM: function() { const that = this; const dataSourceOptions = []; const enableDOMDataSource = that.options._enableDOMDataSource; const itemOptions = [ "text", "content", "icon", "iconPosition", "iconClass", "spriteCssClass", "imageUrl", "contentUrl", "encoded", "closable", "actions", "enabled" ]; const actionOptions = [ "icon", "iconClass", "text", "action" ]; if (!enableDOMDataSource) { return that.options.dataSource; } const dataItems = that.tabGroup.children("li"); const contentElements = that.contentElements; that._enableDataSourceFromDOM = dataItems.length > 0; dataItems.each(function(idx, item) { const $item = $(item); const actions = $item.find("button"); const itemData = {}; itemOptions.forEach(function(option) { let data = $item.data(option); if (option === "text" && !data) { const linkText = $item.find(".k-link-text"); if (linkText.children().length > 0) { data = linkText.html(); } else { data = linkText.text().trim(); } } if (option === "content" && !data) { const content = $(contentElements[idx]); if (content.length) { data = content.html(); } } if (option === "closable" && data === undefined) { data = that.options.closable; } if (data !== undefined) { itemData[option] = data; } }); if (itemData.iconPosition === undefined) { itemData.iconPosition = "before"; } if (actions.length) { itemData.actions = []; that._decorateActions(actions, actionOptions, itemData); } dataSourceOptions.push(itemData); }); return dataSourceOptions; }, _decorateActions: function(actions, actionOptions, itemData) { actions.each(function(idx, action) { const $action = $(action); const actionData = {}; actionOptions.forEach(function(option) { let data = $action.data(option); if (option === "text" && !data) { data = kendo.htmlEncode($action.text().trim()); } if (option === "action" && typeof data === "string") { let fn = window[data]; if (typeof fn === "function") { data = fn; } } if (data !== undefined) { actionData[option] = data; } }, this); itemData.actions.push(actionData); }); }, _elementId: function(element, idx, tab) { var elementId = element.attr("id"), wrapperId = this.element.attr("id"), guid = kendo.guid(); if (!elementId || elementId.indexOf(wrapperId + "-") > -1) { var tabStripID = (wrapperId || guid) + "-"; if (tab) { tabStripID += "tab-"; } return tabStripID + (idx + 1); } return elementId; }, _endItem: function(action) { return this.tabGroup.children(NAVIGATABLEITEMS)[action](); }, _getItem: function(action) { return this.tabGroup.children(KEYBOARDNAVIGATABLEITEMS)[action](); }, _initialActivate: function() { var that = this, selectedItems = that.tabGroup.children("li." + ACTIVESTATE), content = that.contentHolder(selectedItems.index()); if (selectedItems[0] && content.length > 0 && content[0].childNodes.length === 0) { that.activateTab(selectedItems.eq(0)); } }, _item: function(item, action) { var endItem; if (action === PREV) { endItem = "last"; } else { endItem = "first"; } if (!item) { return this._endItem(endItem); } item = item[action](); if (!item[0]) { item = this.tabGroup.children(KEYBOARDNAVIGATABLEITEMS)[endItem](); } if (item.hasClass(DISABLEDSTATE)) { item.addClass(FOCUSEDSTATE); } if (item.hasClass(DISABLEDSTATE) || item.hasClass(ACTIVESTATE)) { this._focused = item; } return item; }, _itemClick: function(e) { var that = this, tabGroup = that.tabGroup[0]; if (e.target.closest(".k-item-actions")) { return; } if (tabGroup !== document.activeElement) { var msie = kendo.support.browser.msie; if (msie) { try { tabGroup.setActive(); } catch (j) { tabGroup.focus(); } } else { tabGroup.focus(); } } if (that._click($(e.currentTarget))) { e.preventDefault(); } }, _keydown: function(e) { var that = this, key = e.keyCode, current = that._current(), rtl = that._isRtl, isHorizontal = /top|bottom/.test(that.options.tabPosition), action; if (e.target != e.currentTarget || !current) { return; } if (key === keys.DOWN && !isHorizontal) { action = NEXT; } else if (key === keys.UP && !isHorizontal) { action = PREV; } else if (key === keys.RIGHT && isHorizontal) { action = rtl ? PREV : NEXT; } else if (key === keys.LEFT && isHorizontal) { action = rtl ? NEXT : PREV; } else if (key == keys.ENTER || key == keys.SPACEBAR) { that._click(current); e.preventDefault(); } else if (key == keys.HOME) { that._click(that._getItem("first")); e.preventDefault(); return; } else if (key == keys.END) { that._click(that._getItem("last")); e.preventDefault(); return; } if (action) { that._click(that._item(current, action)); e.preventDefault(); } }, _moveUrlItem: function(from, to) { this._contentUrls.splice(to, 0, this._contentUrls.splice(from, 1)[0]); }, _processContentUrls: function() { var that = this; if (that._contentUrls.length) { that.tabGroup.children(".k-tabstrip-item").each(function(index, item) { var url = that._contentUrls[index]; if (typeof url === "string") { $(item).find(">." + LINK).data(CONTENTURL, url); } }); } else { that._contentUrls.length = that.tabGroup.find("li.k-tabstrip-item").length; } }, _removeUrlItem: function(index) { this._contentUrls.splice(index, 1); }, _resize: function() { this._scrollable(); }, _getChildrenWidth: function(element) { let width = 0; element.children().each(function() { width += outerWidth($(this)); }); return Math.floor(width); }, _getChildrenHeight: function(element) { let height = 0; element.children().each(function() { height += outerHeight($(this)); }); return Math.floor(height); }, _scrollable: function() { const that = this, options = that.options, scrollButtonsPosition = options.scrollable.scrollButtonsPosition, scrollButtonsVisibility = options.scrollable.scrollButtons, isHorizontal = options.tabPosition == "top" || options.tabPosition == "bottom", isHidden = scrollButtonsVisibility === "hidden", isVisible = scrollButtonsVisibility === "visible"; let wrapperOffset, tabGroupScroll, scrollPrevButton, scrollNextButton; if (that._scrollableAllowed()) { that.wrapper.addClass("k-tabstrip-scrollable"); wrapperOffset = isHorizontal ? that.wrapper[0].offsetWidth : that.wrapper[0].offsetHeight; tabGroupScroll = isHorizontal ? that.tabGroup[0].scrollWidth : that.tabGroup[0].scrollHeight; const condition = isHorizontal ? that._getChildrenWidth(that.tabGroup) > that.tabGroup.outerWidth() : that._getChildrenHeight(that.tabGroup) > that.tabGroup.outerHeight(); const enableScroll = tabGroupScroll > wrapperOffset || condition; if (enableScroll && !that._scrollableModeActive && isHidden) { that.tabGroup.addClass("k-tabstrip-items-scroll"); that.wrapper.addClass("k-tabstrip-scrollable-overlay"); that._scrollableModeActive = true; that._toggleScrollButtons(); } else if ((enableScroll || isVisible) && !that._scrollableModeActive) { that._nowScrollingTabs = false; that._isRtl = kendo.support.isRtl(that.element); const mouseDown = kendo.support.touch ? "touchstart" : "mousedown"; const mouseUp = kendo.support.touch ? "touchend" : "mouseup"; const browser = kendo.support.browser; const isRtlScrollDirection = that._isRtl && !browser.msie && !browser.edge; const prevIcon = isHorizontal ? "caret-alt-left" : "caret-alt-up"; const nextIcon = isHorizontal ? "caret-alt-right" : "caret-alt-down"; const scrollLeft = scrollButtonHtml("prev", prevIcon); const scrollRight = scrollButtonHtml("next", nextIcon); switch (scrollButtonsPosition) { case "split": that.tabWrapper.prepend(scrollLeft); that.tabWrapper.append(scrollRight); break; case "start": that.tabWrapper.prepend(scrollRight); that.tabWrapper.prepend(scrollLeft); break; case "end": that.tabWrapper.append(scrollLeft); that.tabWrapper.append(scrollRight); break; } scrollPrevButton = that._scrollPrevButton = that.tabWrapper.children(".k-tabstrip-prev"); scrollNextButton = that._scrollNextButton = that.tabWrapper.children(".k-tabstrip-next"); scrollPrevButton.on(mouseDown + NS, function() { that._nowScrollingTabs = true; that._scrollTabsByDelta(options.scrollable.distance * (isRtlScrollDirection ? 1 : -1)); }); scrollNextButton.on(mouseDown + NS, function() { that._nowScrollingTabs = true; that._scrollTabsByDelta(options.scrollable.distance * (isRtlScrollDirection ? -1 : 1)); }); scrollPrevButton.add(scrollNextButton).on(mouseUp + NS, function() { that._nowScrollingTabs = false; }); that._scrollableModeActive = true; that._toggleScrollButtons(); } else if (that._scrollableModeActive && !enableScroll && !isVisible) { that._scrollableModeActive = false; that._removeScrollableClasses(); that._scrollPrevButton && that._scrollPrevButton.off().remove(); that._scrollNextButton && that._scrollNextButton.off().remove(); } else if (!that._scrollableModeActive && !isVisible) { that._removeScrollableClasses(); } else { that._toggleScrollButtons(); } } }, _removeScrollableClasses: function() { const that = this; const isHidden = that.options.scrollable.scrollButtons === "hidden"; that.wrapper.removeClass("k-tabstrip-scrollable"); if (isHidden) { that.wrapper.removeClass("k-tabstrip-scrollable-overlay"); that.wrapper.removeClass("k-tabstrip-scrollable-start"); that.wrapper.removeClass("k-tabstrip-scrollable-end"); that.tabGroup.removeClass("k-tabstrip-items-scroll"); } }, _scrollableAllowed: function() { var options = this.options; if (options.scrollable && !options.scrollable.distance) { options.scrollable = { distance: DEFAULTDISTANCE }; } return options.scrollable && !isNaN(options.scrollable.distance); }, _scrollTabsToItem: function(item) { var that = this, tabGroup = that.tabGroup, isHorizontal = that.options.tabPosition == "top" || that.options.tabPosition == "bottom", currentScrollOffset = isHorizontal ? kendo.scrollLeft(tabGroup) : tabGroup.scrollTop(), itemSize = isHorizontal ? outerWidth(item) : outerHeight(item), itemOffset = isHorizontal ? that._isRtl ? item.position().left : item.position().left - tabGroup.children().first().position().left : item.position().top, tabGroupSize = isHorizontal ? tabGroup[0].offsetWidth : tabGroup[0].offsetHeight, browser = kendo.support.browser, itemPosition; if (that._isRtl && isHorizontal && (browser.mozilla || browser.webkit && browser.version >= 85)) { currentScrollOffset = currentScrollOffset * -1; } if (that._isRtl && isHorizontal) { if (itemOffset < 0) { itemPosition = currentScrollOffset + itemOffset - (tabGroupSize - currentScrollOffset); } else if (itemOffset + itemSize > tabGroupSize) { itemPosition = currentScrollOffset + itemOffset - itemSize; } } else { if (currentScrollOffset + tabGroupSize < itemOffset + itemSize) { itemPosition = itemOffset + itemSize - tabGroupSize; } else if (currentScrollOffset > itemOffset) { itemPosition = itemOffset; } } var animationProps = isHorizontal ? { "scrollLeft": itemPosition } : { "scrollTop": itemPosition }; tabGroup.finish().animate(animationProps, "fast", "linear", function() { that._toggleScrollButtons(); }); }, _scrollTabsByDelta: function(delta) { const that = this; const tabGroup = that.tabGroup; const isHorizontal = that.options.tabPosition == "top" || that.options.tabPosition == "bottom"; let scrOffset = isHorizontal ? kendo.scrollLeft(tabGroup) : tabGroup.scrollTop(); const browser = kendo.support.browser; if (that._isRtl && isHorizontal && (browser.mozilla || browser.webkit && browser.version >= 85)) { scrOffset = scrOffset * -1; } var animationProps = isHorizontal ? { "scrollLeft": scrOffset + delta } : { "scrollTop": scrOffset + delta }; tabGroup.finish().animate(animationProps, "fast", "linear", function() { if (that._nowScrollingTabs && !jQuery.fx.off) { that._scrollTabsByDelta(delta); } else { that._toggleScrollButtons(); } }); }, _sortable: function() { var that = this, options = that.options, position = options.tabPosition, isHidden = options.scrollable && options.scrollable.scrollButtons === "hidden", axis = position === "left" || position === "right" ? "y" : "x"; if (!that.options.sortable) { return; } that.sortable = new kendo.ui.Sortable(that.tabGroup, { filter: "li.k-tabstrip-item", axis, holdToDrag: isHidden, allowTouchActions: isHidden, container: that.tabWrapper, hint: (el) => `<div id='hint' class='k-tabstrip k-tabstrip-${position}'> <div class= 'k-tabstrip-items-wrapper k-hstack'> <ul class='k-tabstrip-items k-reset'> <li class='k-tabstrip-item k-first k-active'>${el.html()}</li> </ul> </div> </div>`, change: that._sortChange.bind(that), start: (e) => that.activateTab(e.item) }); }, _sortChange: function(e) { var that = this, reference = that.tabGroup.children().eq(e.newIndex); if (e.oldIndex < e.newIndex) { that.insertAfter(e.item, reference); } else { that.insertBefore(e.item, reference); } }, _tabSizes: function() { const that = this; const tabSize = that.options.size; let className; switch (tabSize) { case "small": className = "k-tabstrip-sm"; break; case "medium": className = "k-tabstrip-md"; break; case "large": className = "k-tabstrip-lg"; break; } that.wrapper.addClass(className); }, _tabPosition: function() { var that = this, tabPosition = that.options.tabPosition; that.wrapper.addClass("k-tabstrip-" + tabPosition); if (tabPosition == "bottom") { that.tabWrapper.appendTo(that.wrapper); } if (tabPosition === "left" || tabPosition === "right") { that.tabGroup.attr(ARIA_ORIENTATION, "vertical"); } that.resize(true); }, _toggleHover: function(e) { $(e.currentTarget).toggleClass(HOVERSTATE, e.type == MOUSEENTER); }, _toggleDisabled: function(element, enable) { element = this.tabGroup.find(element); element.each(function() { $(this).toggleClass(DISABLEDSTATE, !enable).attr(ARIA_DISABLED, !enable); }); }, _toggleScrollButtons: function() { var that = this, ul = that.tabGroup, scrollLeft = Math.floor(kendo.scrollLeft(ul)), scrollTop = Math.floor(ul.scrollTop()), scrollButtonsVisibility = that.options.scrollable.scrollButtons, isHorizontal = that.options.tabPosition == "top" || that.options.tabPosition == "bottom"; const disableNextButton = (isHorizontal ? Math.abs(scrollLeft - (ul[0].scrollWidth - ul[0].offsetWidth)) : Math.abs(scrollTop - (ul[0].scrollHeight - ul[0].offsetHeight))) <