diff options
author | techknowlogick <techknowlogick@gitea.io> | 2019-12-17 16:34:11 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-12-17 16:34:11 -0500 |
commit | 590d56dbb355a499b2f59d9a6ffa4887ea33c96b (patch) | |
tree | 37933f95c29661c80e0ba40751b7e7cf448a32c1 /web_src/js | |
parent | 601b0cf4c19ba6e75a8df6a73beb5f58f462ad46 (diff) | |
download | gitea-590d56dbb355a499b2f59d9a6ffa4887ea33c96b.tar.gz gitea-590d56dbb355a499b2f59d9a6ffa4887ea33c96b.zip |
switch to fomantic-ui (#9374)
Diffstat (limited to 'web_src/js')
-rw-r--r-- | web_src/js/index.js | 7 | ||||
-rw-r--r-- | web_src/js/semanticDropdown.js | 635 |
2 files changed, 462 insertions, 180 deletions
diff --git a/web_src/js/index.js b/web_src/js/index.js index b35f806598..8600acc95c 100644 --- a/web_src/js/index.js +++ b/web_src/js/index.js @@ -3252,7 +3252,10 @@ function initTopicbar() { const last = viewDiv.children('a').last(); for (let i = 0; i < topicArray.length; i++) { - $(`<a class="ui repo-topic small label topic" href="${suburl}/explore/repos?q=${topicArray[i]}&topic=1">${topicArray[i]}</a>`).insertBefore(last); + const link = $('<a class="ui repo-topic small label topic"></a>'); + link.attr('href', `${suburl}/explore/repos?q=${encodeURIComponent(topicArray[i])}&topic=1`); + link.text(topicArray[i]); + link.insertBefore(last); } } editDiv.css('display', 'none'); @@ -3298,7 +3301,7 @@ function initTopicbar() { label: 'ui small label' }, apiSettings: { - url: `${suburl}/api/v1/topics/search?q={query}`, + url: `${suburl}/api/v1/topics/search?q={encodeURIComponent(query)}`, throttle: 500, cache: false, onResponse(res) { diff --git a/web_src/js/semanticDropdown.js b/web_src/js/semanticDropdown.js index 353603ff44..14dcf63a81 100644 --- a/web_src/js/semanticDropdown.js +++ b/web_src/js/semanticDropdown.js @@ -2,8 +2,8 @@ https://github.com/go-gitea/gitea/pull/8638#issuecomment-549175290 */ /*! - * # Semantic UI 2.3.1 - Dropdown - * http://github.com/semantic-org/semantic-ui/ + * # Fomantic-UI - Dropdown + * http://github.com/fomantic/Fomantic-UI/ * * * Released under the MIT license @@ -22,6 +22,10 @@ 'use strict'; +$.isFunction = $.isFunction || function(obj) { + return typeof obj === "function" && typeof obj.nodeType !== "number"; +}; + window = (typeof window != 'undefined' && window.Math == Math) ? window : (typeof self != 'undefined' && self.Math == Math) @@ -75,6 +79,7 @@ $.fn.dropdown = function(parameters) { $sizer = $module.find(selector.sizer), $input = $module.find(selector.input), $icon = $module.find(selector.icon), + $clear = $module.find(selector.clearIcon), $combo = ($module.prev().find(selector.text).length > 0) ? $module.prev().find(selector.text) @@ -82,13 +87,16 @@ $.fn.dropdown = function(parameters) { $menu = $module.children(selector.menu), $item = $menu.find(selector.item), + $divider = settings.hideDividers ? $item.parent().children(selector.divider) : $(), activated = false, itemActivated = false, internalChange = false, + iconClicked = false, element = this, instance = $module.data(moduleNamespace), + selectActionActive, initialLoad, pageLostFocus, willRefocus, @@ -108,6 +116,10 @@ $.fn.dropdown = function(parameters) { module.setup.reference(); } else { + if (settings.ignoreDiacritics && !String.prototype.normalize) { + settings.ignoreDiacritics = false; + module.error(error.noNormalize, element); + } module.setup.layout(); @@ -142,6 +154,9 @@ $.fn.dropdown = function(parameters) { destroy: function() { module.verbose('Destroying previous dropdown', $module); module.remove.tabbable(); + module.remove.active(); + $menu.transition('stop all'); + $menu.removeClass(className.visible).addClass(className.hidden); $module .off(eventNamespace) .removeData(moduleNamespace) @@ -180,7 +195,7 @@ $.fn.dropdown = function(parameters) { }, observe: { select: function() { - if(module.has.input()) { + if(module.has.input() && selectObserver) { selectObserver.observe($module[0], { childList : true, subtree : true @@ -188,7 +203,7 @@ $.fn.dropdown = function(parameters) { } }, menu: function() { - if(module.has.menu()) { + if(module.has.menu() && menuObserver) { menuObserver.observe($menu[0], { childList : true, subtree : true @@ -214,7 +229,7 @@ $.fn.dropdown = function(parameters) { if(!values) { return false; } - values = $.isArray(values) + values = Array.isArray(values) ? values : [values] ; @@ -276,7 +291,7 @@ $.fn.dropdown = function(parameters) { module.filter(query); } else { - module.hide(); + module.hide(null,true); } }, @@ -414,6 +429,13 @@ $.fn.dropdown = function(parameters) { if( !module.has.menu() ) { module.create.menu(); } + if ( module.is.selection() && module.is.clearable() && !module.has.clearItem() ) { + module.verbose('Adding clear icon'); + $clear = $('<i />') + .addClass('remove icon') + .insertBefore($text) + ; + } if( module.is.search() && !module.has.search() ) { module.verbose('Adding search input'); $search = $('<input />') @@ -454,7 +476,7 @@ $.fn.dropdown = function(parameters) { .attr('class', $input.attr('class') ) .addClass(className.selection) .addClass(className.dropdown) - .html( templates.dropdown(selectValues) ) + .html( templates.dropdown(selectValues, fields, settings.preserveHTML, settings.className) ) .insertBefore($input) ; if($input.hasClass(className.multiple) && $input.prop('multiple') === false) { @@ -469,6 +491,7 @@ $.fn.dropdown = function(parameters) { $module.addClass(className.disabled); } $input + .removeAttr('required') .removeAttr('class') .detach() .prependTo($module) @@ -477,8 +500,9 @@ $.fn.dropdown = function(parameters) { module.refresh(); }, menu: function(values) { - $menu.html( templates.menu(values, fields)); - $item = $menu.find(selector.item); + $menu.html( templates.menu(values, fields,settings.preserveHTML,settings.className)); + $item = $menu.find(selector.item); + $divider = settings.hideDividers ? $item.parent().children(selector.divider) : $(); }, reference: function() { module.debug('Dropdown behavior was called on select, replacing with closest dropdown'); @@ -505,7 +529,8 @@ $.fn.dropdown = function(parameters) { }, refreshItems: function() { - $item = $menu.find(selector.item); + $item = $menu.find(selector.item); + $divider = settings.hideDividers ? $item.parent().children(selector.divider) : $(); }, refreshSelectors: function() { @@ -520,6 +545,7 @@ $.fn.dropdown = function(parameters) { ; $menu = $module.children(selector.menu); $item = $menu.find(selector.item); + $divider = settings.hideDividers ? $item.parent().children(selector.divider) : $(); }, refreshData: function() { @@ -553,7 +579,7 @@ $.fn.dropdown = function(parameters) { } }, - show: function(callback) { + show: function(callback, preventFocus) { callback = $.isFunction(callback) ? callback : function(){} @@ -577,7 +603,7 @@ $.fn.dropdown = function(parameters) { if( module.can.click() ) { module.bind.intent(); } - if(module.has.menuSearch()) { + if(module.has.search() && !preventFocus) { module.focusSearch(); } module.set.visible(); @@ -587,7 +613,7 @@ $.fn.dropdown = function(parameters) { } }, - hide: function(callback) { + hide: function(callback, preventBlur) { callback = $.isFunction(callback) ? callback : function(){} @@ -599,9 +625,15 @@ $.fn.dropdown = function(parameters) { module.aria.removeDescendant(); module.animate.hide(function() { module.remove.visible(); + // hidding search focus + if ( module.is.focusedOnSearch() && preventBlur !== true ) { + $search.blur(); + } callback.call(element); }); } + } else if( module.can.click() ) { + module.unbind.intent(); } }, @@ -689,6 +721,7 @@ $.fn.dropdown = function(parameters) { .on('mousedown' + eventNamespace, selector.menu, module.event.menu.mousedown) .on('mouseup' + eventNamespace, selector.menu, module.event.menu.mouseup) .on('click' + eventNamespace, selector.icon, module.event.icon.click) + .on('click' + eventNamespace, selector.clearIcon, module.event.clearIcon.click) .on('focus' + eventNamespace, selector.search, module.event.search.focus) .on('click' + eventNamespace, selector.search, module.event.search.focus) .on('blur' + eventNamespace, selector.search, module.event.search.blur) @@ -722,6 +755,7 @@ $.fn.dropdown = function(parameters) { .on('mousedown' + eventNamespace, module.event.mousedown) .on('mouseup' + eventNamespace, module.event.mouseup) .on('focus' + eventNamespace, module.event.focus) + .on('click' + eventNamespace, selector.clearIcon, module.event.clearIcon.click) ; if(module.has.menuSearch() ) { $module @@ -805,7 +839,7 @@ $.fn.dropdown = function(parameters) { module.remove.message(); } if(settings.allowAdditions) { - module.add.userSuggestion(query); + module.add.userSuggestion(module.escape.htmlEntities(query)); } if(module.is.searchSelection() && module.can.show() && module.is.focusedOnSearch() ) { module.show(); @@ -821,6 +855,15 @@ $.fn.dropdown = function(parameters) { if(settings.filterRemoteData) { module.filterItems(searchTerm); } + var preSelected = $input.val(); + if(!Array.isArray(preSelected)) { + preSelected = preSelected && preSelected!=="" ? preSelected.split(settings.delimiter) : []; + } + $.each(preSelected,function(index,value){ + $item.filter('[data-value="'+value+'"]') + .addClass(className.filtered) + ; + }); afterFiltered(); }); } @@ -852,10 +895,20 @@ $.fn.dropdown = function(parameters) { callback(); }, onSuccess : function(response) { + var + values = response[fields.remoteValues] + ; + if (!Array.isArray(values)){ + values = []; + } module.remove.message(); module.setup.menu({ - values: response[fields.remoteValues] + values: values }); + + if(values.length===0 && !settings.allowAdditions) { + module.add.message(message.noResults); + } callback(); } } @@ -872,12 +925,14 @@ $.fn.dropdown = function(parameters) { filterItems: function(query) { var - searchTerm = (query !== undefined) + searchTerm = module.remove.diacritics(query !== undefined ? query - : module.get.query(), + : module.get.query() + ), results = null, escapedTerm = module.escape.string(searchTerm), - beginsWithRegExp = new RegExp('^' + escapedTerm, 'igm') + regExpFlags = (settings.ignoreSearchCase ? 'i' : '') + 'gm', + beginsWithRegExp = new RegExp('^' + escapedTerm, regExpFlags) ; // avoid loop if we're matching nothing if( module.has.query() ) { @@ -891,8 +946,8 @@ $.fn.dropdown = function(parameters) { text, value ; - if(settings.match == 'both' || settings.match == 'text') { - text = String(module.get.choiceText($choice, false)); + if(settings.match === 'both' || settings.match === 'text') { + text = module.remove.diacritics(String(module.get.choiceText($choice, false))); if(text.search(beginsWithRegExp) !== -1) { results.push(this); return true; @@ -906,8 +961,8 @@ $.fn.dropdown = function(parameters) { return true; } } - if(settings.match == 'both' || settings.match == 'value') { - value = String(module.get.choiceValue($choice, text)); + if(settings.match === 'both' || settings.match === 'value') { + value = module.remove.diacritics(String(module.get.choiceValue($choice, text))); if(value.search(beginsWithRegExp) !== -1) { results.push(this); return true; @@ -932,6 +987,30 @@ $.fn.dropdown = function(parameters) { .addClass(className.filtered) ; } + + if(!module.has.query()) { + $divider + .removeClass(className.hidden); + } else if(settings.hideDividers === true) { + $divider + .addClass(className.hidden); + } else if(settings.hideDividers === 'empty') { + $divider + .removeClass(className.hidden) + .filter(function() { + // First find the last divider in this divider group + // Dividers which are direct siblings are considered a group + var lastDivider = $(this).nextUntil(selector.item); + + return (lastDivider.length ? lastDivider : $(this)) + // Count all non-filtered items until the next divider (or end of the dropdown) + .nextUntil(selector.divider) + .filter(selector.item + ":not(." + className.filtered + ")") + // Hide divider if no items are found + .length === 0; + }) + .addClass(className.hidden); + } }, fuzzySearch: function(query, term) { @@ -939,8 +1018,8 @@ $.fn.dropdown = function(parameters) { termLength = term.length, queryLength = query.length ; - query = query.toLowerCase(); - term = term.toLowerCase(); + query = (settings.ignoreSearchCase ? query.toLowerCase() : query); + term = (settings.ignoreSearchCase ? term.toLowerCase() : term); if(queryLength > termLength) { return false; } @@ -961,12 +1040,10 @@ $.fn.dropdown = function(parameters) { return true; }, exactSearch: function (query, term) { - query = query.toLowerCase(); - term = term.toLowerCase(); - if(term.indexOf(query) > -1) { - return true; - } - return false; + query = (settings.ignoreSearchCase ? query.toLowerCase() : query); + term = (settings.ignoreSearchCase ? term.toLowerCase() : term); + return term.indexOf(query) > -1; + }, filterActive: function() { if(settings.useLabels) { @@ -989,6 +1066,12 @@ $.fn.dropdown = function(parameters) { } }, + blurSearch: function() { + if( module.has.search() ) { + $search.blur(); + } + }, + forceSelection: function() { var $currentlySelected = $item.not(className.filtered).filter('.' + className.selected).eq(0), @@ -998,19 +1081,12 @@ $.fn.dropdown = function(parameters) { : $activeItem, hasSelected = ($selectedItem.length > 0) ; - if(hasSelected && !module.is.multiple()) { + if(settings.allowAdditions || (hasSelected && !module.is.multiple())) { module.debug('Forcing partial selection to selected item', $selectedItem); - module.event.item.click.call($selectedItem, {}, true); - return; + $selectedItem[0].click(); } else { - if(settings.allowAdditions) { - module.set.selected(module.get.query()); - module.remove.searchTerm(); - } - else { - module.remove.searchTerm(); - } + module.remove.searchTerm(); } }, @@ -1023,11 +1099,30 @@ $.fn.dropdown = function(parameters) { module.setup.menu({values: values}); $.each(values, function(index, item) { if(item.selected == true) { - module.debug('Setting initial selection to', item.value); - module.set.selected(item.value); - return true; + module.debug('Setting initial selection to', item[fields.value]); + module.set.selected(item[fields.value]); + if(!module.is.multiple()) { + return false; + } } }); + + if(module.has.selectInput()) { + module.disconnect.selectObserver(); + $input.html(''); + $input.append('<option disabled selected value></option>'); + $.each(values, function(index, item) { + var + value = settings.templates.deQuote(item[fields.value]), + name = settings.templates.escape( + item[fields.name] || item[fields.value], + settings.preserveHTML + ) + ; + $input.append('<option value="' + value + '">' + name + '</option>'); + }); + module.observe.select(); + } } }, @@ -1084,12 +1179,12 @@ $.fn.dropdown = function(parameters) { } }, search: { - focus: function() { + focus: function(event) { activated = true; if(module.is.multiple()) { module.remove.activeLabel(); } - if(settings.showOnFocus) { + if(settings.showOnFocus || (event.type !== 'focus' && event.type !== 'focusin')) { module.search(); } }, @@ -1099,6 +1194,8 @@ $.fn.dropdown = function(parameters) { if(!itemActivated && !pageLostFocus) { if(settings.forceSelection) { module.forceSelection(); + } else if(!settings.allowAdditions){ + module.remove.searchTerm(); } module.hide(); } @@ -1106,9 +1203,32 @@ $.fn.dropdown = function(parameters) { willRefocus = false; } }, + clearIcon: { + click: function(event) { + module.clear(); + if(module.is.searchSelection()) { + module.remove.searchTerm(); + } + module.hide(); + event.stopPropagation(); + } + }, icon: { click: function(event) { - module.toggle(); + iconClicked=true; + if(module.has.search()) { + if(!module.is.active()) { + if(settings.showOnFocus){ + module.focusSearch(); + } else { + module.toggle(); + } + } else { + module.blurSearch(); + } + } else { + module.toggle(); + } } }, text: { @@ -1193,22 +1313,17 @@ $.fn.dropdown = function(parameters) { event.stopPropagation(); }, hide: function(event) { - module.determine.eventInModule(event, module.hide); + if(module.determine.eventInModule(event, module.hide)){ + if(element.id && $(event.target).attr('for') === element.id){ + event.preventDefault(); + } + } } }, select: { mutation: function(mutations) { module.debug('<select> modified, recreating menu'); - var - isSelectMutation = false - ; - $.each(mutations, function(index, mutation) { - if($(mutation.target).is('select') || $(mutation.addedNodes).is('select')) { - isSelectMutation = true; - return true; - } - }); - if(isSelectMutation) { + if(module.is.selectMutation(mutations)) { module.disconnect.selectObserver(); module.refresh(); module.setup.select(); @@ -1294,7 +1409,7 @@ $.fn.dropdown = function(parameters) { isBubbledEvent = ($subMenu.find($target).length > 0) ; // prevents IE11 bug where menu receives focus even though `tabindex=-1` - if(module.has.menuSearch()) { + if (document.activeElement.tagName.toLowerCase() !== 'input') { $(document.activeElement).blur(); } if(!isBubbledEvent && (!hasSubMenu || settings.allowCategorySelection)) { @@ -1337,7 +1452,8 @@ $.fn.dropdown = function(parameters) { isSearch = module.is.searchSelection(), isFocusedOnSearch = module.is.focusedOnSearch(), isFocused = module.is.focused(), - caretAtStart = (isFocusedOnSearch && module.get.caretPosition() === 0), + caretAtStart = (isFocusedOnSearch && module.get.caretPosition(false) === 0), + isSelectedSearch = (caretAtStart && module.get.caretPosition(true) !== 0), $nextLabel ; if(isSearch && !hasActiveLabel && !isFocusedOnSearch) { @@ -1418,7 +1534,7 @@ $.fn.dropdown = function(parameters) { module.remove.activeLabels($activeLabel); event.preventDefault(); } - else if(caretAtStart && !hasActiveLabel && pressedKey == keys.backspace) { + else if(caretAtStart && !isSelectedSearch && !hasActiveLabel && pressedKey == keys.backspace) { module.verbose('Removing last label on input backspace'); $activeLabel = $label.last().addClass(className.active); module.remove.activeLabels($activeLabel); @@ -1465,6 +1581,9 @@ $.fn.dropdown = function(parameters) { if(module.is.searchSelection()) { module.remove.searchTerm(); } + if(module.is.multiple()){ + event.preventDefault(); + } } // visible menu keyboard shortcuts @@ -1481,6 +1600,9 @@ $.fn.dropdown = function(parameters) { $selectedItem[0].click(); if(module.is.searchSelection()) { module.remove.searchTerm(); + if(module.is.multiple()) { + $search.focus(); + } } } event.preventDefault(); @@ -1636,6 +1758,7 @@ $.fn.dropdown = function(parameters) { determine: { selectAction: function(text, value) { + selectActionActive = true; module.verbose('Determining action', settings.action); if( $.isFunction( module.action[settings.action] ) ) { module.verbose('Triggering preset action', settings.action, text, value); @@ -1648,6 +1771,7 @@ $.fn.dropdown = function(parameters) { else { module.error(error.action, settings.action); } + selectActionActive = false; }, eventInModule: function(event, callback) { var @@ -1674,7 +1798,7 @@ $.fn.dropdown = function(parameters) { $target = $(event.target), $label = $target.closest(selector.siblingLabel), inVisibleDOM = document.body.contains(event.target), - notOnLabel = ($module.find($label).length === 0), + notOnLabel = ($module.find($label).length === 0 || !(module.is.multiple() && settings.useLabels)), notInMenu = ($target.closest($menu).length === 0) ; callback = $.isFunction(callback) @@ -1704,10 +1828,7 @@ $.fn.dropdown = function(parameters) { ; if( module.can.activate( $(element) ) ) { module.set.selected(value, $(element)); - if(module.is.multiple() && !module.is.allFiltered()) { - return; - } - else { + if(!module.is.multiple()) { module.hideAndClear(); } } @@ -1720,10 +1841,7 @@ $.fn.dropdown = function(parameters) { ; if( module.can.activate( $(element) ) ) { module.set.value(value, text, $(element)); - if(module.is.multiple() && !module.is.allFiltered()) { - return; - } - else { + if(!module.is.multiple()) { module.hideAndClear(); } } @@ -1739,7 +1857,7 @@ $.fn.dropdown = function(parameters) { }, hide: function(text, value, element) { - module.set.value(value, text); + module.set.value(value, text, $(element)); module.hideAndClear(); } @@ -1782,7 +1900,7 @@ $.fn.dropdown = function(parameters) { count ; count = ( module.is.multiple() ) - ? $.isArray(values) + ? Array.isArray(values) ? values.length : 0 : (module.get.value() !== '') @@ -1806,7 +1924,7 @@ $.fn.dropdown = function(parameters) { if(!values) { return false; } - values = $.isArray(values) + values = Array.isArray(values) ? values : [values] ; @@ -1819,19 +1937,25 @@ $.fn.dropdown = function(parameters) { return $.inArray(value, array) === index; }); }, - caretPosition: function() { + caretPosition: function(returnEndPos) { var input = $search.get(0), range, rangeLength ; - if('selectionStart' in input) { + if(returnEndPos && 'selectionEnd' in input){ + return input.selectionEnd; + } + else if(!returnEndPos && 'selectionStart' in input) { return input.selectionStart; } - else if (document.selection) { + if (document.selection) { input.focus(); range = document.selection.createRange(); rangeLength = range.text.length; + if(returnEndPos) { + return rangeLength; + } range.moveStart('character', -input.value.length); return range.text.length - rangeLength; } @@ -1841,7 +1965,7 @@ $.fn.dropdown = function(parameters) { value = ($input.length > 0) ? $input.val() : $module.data(metadata.value), - isEmptyMultiselect = ($.isArray(value) && value.length === 1 && value[0] === '') + isEmptyMultiselect = (Array.isArray(value) && value.length === 1 && value[0] === '') ; // prevents placeholder element from being selected when multiple return (value === undefined || isEmptyMultiselect) @@ -1858,7 +1982,7 @@ $.fn.dropdown = function(parameters) { } return ( !module.has.selectInput() && module.is.multiple() ) ? (typeof value == 'string') // delimited string - ? value.split(settings.delimiter) + ? module.escape.htmlEntities(value).split(settings.delimiter) : '' : value ; @@ -1915,7 +2039,11 @@ $.fn.dropdown = function(parameters) { return ($choice.data(metadata.value) !== undefined) ? String( $choice.data(metadata.value) ) : (typeof choiceText === 'string') - ? $.trim(choiceText.toLowerCase()) + ? $.trim( + settings.ignoreSearchCase + ? choiceText.toLowerCase() + : choiceText + ) : String(choiceText) ; }, @@ -1935,7 +2063,8 @@ $.fn.dropdown = function(parameters) { }, selectValues: function() { var - select = {} + select = {}, + oldGroup = [] ; select.values = []; $module @@ -1947,12 +2076,21 @@ $.fn.dropdown = function(parameters) { disabled = $option.attr('disabled'), value = ( $option.attr('value') !== undefined ) ? $option.attr('value') - : name + : name, + group = $option.parent('optgroup') ; if(settings.placeholder === 'auto' && value === '') { select.placeholder = name; } else { + if(group.length !== oldGroup.length || group[0] !== oldGroup[0]) { + select.values.push({ + type: 'header', + divider: settings.headerDivider, + name: group.attr('label') || '' + }); + oldGroup = group; + } select.values.push({ name : name, value : value, @@ -1966,12 +2104,17 @@ $.fn.dropdown = function(parameters) { select.placeholder = settings.placeholder; } if(settings.sortSelect) { - select.values.sort(function(a, b) { - return (a.name > b.name) - ? 1 - : -1 - ; - }); + if(settings.sortSelect === true) { + select.values.sort(function(a, b) { + return a.name.localeCompare(b.name); + }); + } else if(settings.sortSelect === 'natural') { + select.values.sort(function(a, b) { + return (a.name.toLowerCase().localeCompare(b.name.toLowerCase())); + }); + } else if($.isFunction(settings.sortSelect)) { + select.values.sort(settings.sortSelect); + } module.debug('Retrieved and sorted values from select', select); } else { @@ -2017,12 +2160,12 @@ $.fn.dropdown = function(parameters) { ? module.get.values() : module.get.text() ; + isMultiple = (module.is.multiple() && Array.isArray(value)); shouldSearch = (isMultiple) ? (value.length > 0) : (value !== undefined && value !== null) ; - isMultiple = (module.is.multiple() && $.isArray(value)); - strict = (value === '' || value === 0) + strict = (value === '' || value === false || value === true) ? true : strict || false ; @@ -2039,7 +2182,7 @@ $.fn.dropdown = function(parameters) { return; } if(isMultiple) { - if($.inArray( String(optionValue), value) !== -1 || $.inArray(optionText, value) !== -1) { + if($.inArray( String(optionValue), value) !== -1) { $selectedItem = ($selectedItem) ? $selectedItem.add($choice) : $choice @@ -2048,13 +2191,17 @@ $.fn.dropdown = function(parameters) { } else if(strict) { module.verbose('Ambiguous dropdown value using strict type check', $choice, value); - if( optionValue === value || optionText === value) { + if( optionValue === value) { $selectedItem = $choice; return true; } } else { - if( String(optionValue) == String(value) || optionText == value) { + if(settings.ignoreCase) { + optionValue = optionValue.toLowerCase(); + value = value.toLowerCase(); + } + if( String(optionValue) == String(value)) { module.verbose('Found select item by value', optionValue, value); $selectedItem = $choice; return true; @@ -2097,8 +2244,8 @@ $.fn.dropdown = function(parameters) { }, restore: { - defaults: function() { - module.clear(); + defaults: function(preventChangeTrigger) { + module.clear(preventChangeTrigger); module.restore.defaultText(); module.restore.defaultValue(); }, @@ -2165,6 +2312,12 @@ $.fn.dropdown = function(parameters) { else { module.set.selected(); } + var value = module.get.value(); + if(value && value !== '' && !(Array.isArray(value) && value.length === 0)) { + $input.removeClass(className.noselection); + } else { + $input.addClass(className.noselection); + } module.remove.initialLoad(); }, remoteValues: function() { @@ -2244,20 +2397,21 @@ $.fn.dropdown = function(parameters) { } }, - clear: function() { + clear: function(preventChangeTrigger) { if(module.is.multiple() && settings.useLabels) { module.remove.labels(); } else { module.remove.activeItem(); module.remove.selectedItem(); + module.remove.filteredItem(); } module.set.placeholderText(); - module.clearValue(); + module.clearValue(preventChangeTrigger); }, - clearValue: function() { - module.set.value(''); + clearValue: function(preventChangeTrigger) { + module.set.value('', null, null, preventChangeTrigger); }, scrollPage: function(direction, $selectedItem) { @@ -2409,6 +2563,9 @@ $.fn.dropdown = function(parameters) { ? forceScroll : false ; + if(module.get.activeItem().length === 0){ + forceScroll = false; + } if($item && $menu.length > 0 && hasActive) { itemOffset = $item.position().top; @@ -2430,30 +2587,28 @@ $.fn.dropdown = function(parameters) { } }, text: function(text) { - if(settings.action !== 'select') { - if(settings.action == 'combo') { - module.debug('Changing combo button text', text, $combo); - if(settings.preserveHTML) { - $combo.html(text); - } - else { - $combo.text(text); - } + if(settings.action === 'combo') { + module.debug('Changing combo button text', text, $combo); + if(settings.preserveHTML) { + $combo.html(text); } else { - if(text !== module.get.placeholderText()) { - $text.removeClass(className.placeholder); - } - module.debug('Changing text', text, $text); - $text - .removeClass(className.filtered) - ; - if(settings.preserveHTML) { - $text.html(text); - } - else { - $text.text(text); - } + $combo.text(text); + } + } + else if(settings.action === 'activate') { + if(text !== module.get.placeholderText()) { + $text.removeClass(className.placeholder); + } + module.debug('Changing text', text, $text); + $text + .removeClass(className.filtered) + ; + if(settings.preserveHTML) { + $text.html(text); + } + else { + $text.text(text); } } }, @@ -2509,8 +2664,13 @@ $.fn.dropdown = function(parameters) { }, direction: function($menu) { if(settings.direction == 'auto') { - // reset position - module.remove.upward(); + // reset position, remove upward if it's base menu + if (!$menu) { + module.remove.upward(); + } else if (module.is.upward($menu)) { + //we need make sure when make assertion openDownward for $menu, $menu does not have upward class + module.remove.upward($menu); + } if(module.can.openDownward($menu)) { module.remove.upward($menu); @@ -2534,7 +2694,12 @@ $.fn.dropdown = function(parameters) { var $element = $currentMenu || $menu; $element.addClass(className.leftward); }, - value: function(value, text, $selected) { + value: function(value, text, $selected, preventChangeTrigger) { + if(value !== undefined && value !== '' && !(Array.isArray(value) && value.length === 0)) { + $input.removeClass(className.noselection); + } else { + $input.addClass(className.noselection); + } var escapedValue = module.escape.value(value), hasInput = ($input.length > 0), @@ -2564,7 +2729,7 @@ $.fn.dropdown = function(parameters) { if(settings.fireOnInit === false && module.is.initialLoad()) { module.debug('Input native change event ignored on initial load'); } - else { + else if(preventChangeTrigger !== true) { module.trigger.change(); } internalChange = false; @@ -2578,7 +2743,7 @@ $.fn.dropdown = function(parameters) { if(settings.fireOnInit === false && module.is.initialLoad()) { module.verbose('No callback on initial load', settings.onChange); } - else { + else if(preventChangeTrigger !== true) { settings.onChange.call(element, value, text, $selected); } }, @@ -2600,8 +2765,7 @@ $.fn.dropdown = function(parameters) { }, selected: function(value, $selectedItem) { var - isMultiple = module.is.multiple(), - $userSelectedItem + isMultiple = module.is.multiple() ; $selectedItem = (settings.allowAdditions) ? $selectedItem || module.get.itemWithAdditions(value) @@ -2652,7 +2816,7 @@ $.fn.dropdown = function(parameters) { module.set.activeItem($selected); } } - else if(!isFiltered) { + else if(!isFiltered && (settings.useLabels || selectActionActive)) { module.debug('Selected active value, removing label'); module.remove.selected(selectedValue); } @@ -2670,6 +2834,7 @@ $.fn.dropdown = function(parameters) { } }) ; + module.remove.searchTerm(); } }, @@ -2688,7 +2853,7 @@ $.fn.dropdown = function(parameters) { $label = $('<a />') .addClass(className.label) .attr('data-' + metadata.value, escapedValue) - .html(templates.label(escapedValue, text)) + .html(templates.label(escapedValue, text, settings.preserveHTML, settings.className)) ; $label = settings.onLabelCreate.call($label, escapedValue, text); @@ -2704,7 +2869,12 @@ $.fn.dropdown = function(parameters) { $label .addClass(className.hidden) .insertBefore($next) - .transition(settings.label.transition, settings.label.duration) + .transition({ + animation : settings.label.transition, + debug : settings.debug, + verbose : settings.verbose, + duration : settings.label.duration + }) ; } else { @@ -2808,7 +2978,6 @@ $.fn.dropdown = function(parameters) { hasCount = (message.search('{count}') !== -1), hasMaxCount = (message.search('{maxCount}') !== -1), hasTerm = (message.search('{term}') !== -1), - values, count, query ; @@ -2841,7 +3010,7 @@ $.fn.dropdown = function(parameters) { return; } // extend current array - if($.isArray(currentValue)) { + if(Array.isArray(currentValue)) { newValue = currentValue.concat([addedValue]); newValue = module.get.uniqueArray(newValue); } @@ -2866,9 +3035,9 @@ $.fn.dropdown = function(parameters) { else { settings.onAdd.call(element, addedValue, addedText, $selectedItem); } - module.set.value(newValue, addedValue, addedText, $selectedItem); + module.set.value(newValue, addedText, $selectedItem); module.check.maxSelections(); - } + }, }, remove: { @@ -2911,6 +3080,9 @@ $.fn.dropdown = function(parameters) { else { $item.removeClass(className.filtered); } + if(settings.hideDividers) { + $divider.removeClass(className.hidden); + } module.remove.empty(); }, optionValue: function(value) { @@ -3023,7 +3195,7 @@ $.fn.dropdown = function(parameters) { module.check.maxSelections(); }, arrayValue: function(removedValue, values) { - if( !$.isArray(values) ) { + if( !Array.isArray(values) ) { values = [values]; } values = $.grep(values, function(value){ @@ -3035,7 +3207,7 @@ $.fn.dropdown = function(parameters) { label: function(value, shouldAnimate) { var $labels = $module.find(selector.label), - $removedLabel = $labels.filter('[data-' + metadata.value + '="' + module.escape.string(value) +'"]') + $removedLabel = $labels.filter('[data-' + metadata.value + '="' + module.escape.string(settings.ignoreCase ? value.toLowerCase() : value) +'"]') ; module.verbose('Removing label', $removedLabel); $removedLabel.remove(); @@ -3093,6 +3265,9 @@ $.fn.dropdown = function(parameters) { .removeAttr('tabindex') ; } + }, + diacritics: function(text) { + return settings.ignoreDiacritics ? text.normalize('NFD').replace(/[\u0300-\u036f]/g, '') : text; } }, @@ -3100,6 +3275,9 @@ $.fn.dropdown = function(parameters) { menuSearch: function() { return (module.has.search() && $search.closest($menu).length > 0); }, + clearItem: function() { + return ($clear.length > 0); + }, search: function() { return ($search.length > 0); }, @@ -3110,13 +3288,14 @@ $.fn.dropdown = function(parameters) { return ( $input.is('select') ); }, minCharacters: function(searchTerm) { - if(settings.minCharacters) { + if(settings.minCharacters && !iconClicked) { searchTerm = (searchTerm !== undefined) ? String(searchTerm) : String(module.get.query()) ; return (searchTerm.length >= settings.minCharacters); } + iconClicked=false; return true; }, firstLetter: function($item, letter) { @@ -3178,7 +3357,7 @@ $.fn.dropdown = function(parameters) { valueMatchingCase: function(value) { var values = module.get.values(), - hasValue = $.isArray(values) + hasValue = Array.isArray(values) ? values && ($.inArray(value, values) !== -1) : (values == value) ; @@ -3192,7 +3371,7 @@ $.fn.dropdown = function(parameters) { values = module.get.values(), hasValue = false ; - if(!$.isArray(values)) { + if(!Array.isArray(values)) { values = [values]; } $.each(values, function(index, existingValue) { @@ -3234,6 +3413,9 @@ $.fn.dropdown = function(parameters) { var $selectedMenu = $subMenu || $menu; return $selectedMenu.hasClass(className.leftward); }, + clearable: function() { + return ($module.hasClass(className.clearable) || settings.clearable); + }, disabled: function() { return $module.hasClass(className.disabled); }, @@ -3278,9 +3460,9 @@ $.fn.dropdown = function(parameters) { selectChanged = false ; $.each(mutations, function(index, mutation) { - if(mutation.target && $(mutation.target).is('select')) { + if($(mutation.target).is('select') || $(mutation.addedNodes).is('select')) { selectChanged = true; - return true; + return false; } }); return selectChanged; @@ -3483,9 +3665,6 @@ $.fn.dropdown = function(parameters) { hide: function(callback, $subMenu) { var $currentMenu = $subMenu || $menu, - duration = ($subMenu) - ? (settings.duration * 0.9) - : settings.duration, start = ($subMenu) ? function() {} : function() { @@ -3561,7 +3740,7 @@ $.fn.dropdown = function(parameters) { escape: { value: function(value) { var - multipleValues = $.isArray(value), + multipleValues = Array.isArray(value), stringValue = (typeof value === 'string'), isUnparsable = (!stringValue && !multipleValues), hasQuotes = (stringValue && value.search(regExp.quote) !== -1), @@ -3582,6 +3761,27 @@ $.fn.dropdown = function(parameters) { string: function(text) { text = String(text); return text.replace(regExp.escape, '\\$&'); + }, + htmlEntities: function(string) { + var + badChars = /[&<>"'`]/g, + shouldEscape = /[&<>"'`]/, + escape = { + "&": "&", + "<": "<", + ">": ">", + '"': """, + "'": "'", + "`": "`" + }, + escapedChar = function(chr) { + return escape[chr]; + } + ; + if(shouldEscape.test(string)) { + return string.replace(badChars, escapedChar); + } + return string; } }, @@ -3735,7 +3935,7 @@ $.fn.dropdown = function(parameters) { else if(found !== undefined) { response = found; } - if($.isArray(returnedValue)) { + if(Array.isArray(returnedValue)) { returnedValue.push(response); } else if(returnedValue !== undefined) { @@ -3780,6 +3980,8 @@ $.fn.dropdown.settings = { values : false, // specify values to use for dropdown + clearable : false, // whether the value of the dropdown can be cleared + apiSettings : false, selectOnKeydown : true, // Whether selection should occur automatically when keyboard shortcuts used minCharacters : 0, // Minimum characters required to trigger API call @@ -3795,6 +3997,8 @@ $.fn.dropdown.settings = { match : 'both', // what to match against with search selection (both, text, or label) fullTextSearch : false, // search anywhere in value (set to 'exact' to require exact matches) + ignoreDiacritics : false, // match results also if they contain diacritics of the same base character (for example searching for "a" will also match "á" or "â" or "à", etc...) + hideDividers : false, // Whether to hide any divider elements (specified in selector.divider) that are sibling to any items when searched (set to true will hide all dividers, set to 'empty' will hide them when they are not followed by a visible item) placeholder : 'auto', // whether to convert blank <select> values to placeholder text preserveHTML : true, // preserve html when selecting value @@ -3803,7 +4007,8 @@ $.fn.dropdown.settings = { forceSelection : true, // force a choice on blur with search selection allowAdditions : false, // whether multiple select should allow user added values - ignoreCase : false, // whether to consider values not matching in case to be the same + ignoreCase : false, // whether to consider case sensitivity when creating labels + ignoreSearchCase : true, // whether to consider case sensitivity when filtering items hideAdditions : true, // whether or not to hide special message prompting a user they can enter a value maxSelections : false, // When set to a number limits the number of selections to this count @@ -3822,6 +4027,8 @@ $.fn.dropdown.settings = { glyphWidth : 1.037, // widest glyph width in em (W is 1.037 em) used to calculate multiselect input width + headerDivider : true, // whether option headers should have an additional divider line underneath when converted from <select> <optgroup> + // label settings on multi-select label: { transition : 'scale', @@ -3869,11 +4076,12 @@ $.fn.dropdown.settings = { method : 'The method you called is not defined.', noAPI : 'The API module is required to load resources remotely', noStorage : 'Saving remote data requires session storage', - noTransition : 'This module requires ui transitions <https://github.com/Semantic-Org/UI-Transition>' + noTransition : 'This module requires ui transitions <https://github.com/Semantic-Org/UI-Transition>', + noNormalize : '"ignoreDiacritics" setting will be ignored. Browser does not support String().normalize(). You may consider including <https://cdn.jsdelivr.net/npm/unorm@1.4.1/lib/unorm.min.js> as a polyfill.' }, regExp : { - escape : /[-[\]{}()*+?.,\\^$|#\s]/g, + escape : /[-[\]{}()*+?.,\\^$|#\s:=@]/g, quote : /"/g }, @@ -3892,7 +4100,14 @@ $.fn.dropdown.settings = { disabled : 'disabled', // whether value should be disabled name : 'name', // displayed dropdown text value : 'value', // actual dropdown value - text : 'text' // displayed text when selected + text : 'text', // displayed text when selected + type : 'type', // type of dropdown element + image : 'image', // optional image path + imageClass : 'imageClass', // optional individual class for image + icon : 'icon', // optional icon name + iconClass : 'iconClass', // optional individual class for icon (for example to use flag instead) + class : 'class', // optional individual class for item/header + divider : 'divider' // optional divider append for group headers }, keys : { @@ -3911,6 +4126,7 @@ $.fn.dropdown.settings = { selector : { addition : '.addition', + divider : '.divider, .header', dropdown : '.ui.dropdown', hidden : '.hidden', icon : '> .dropdown.icon', @@ -3925,7 +4141,8 @@ $.fn.dropdown.settings = { search : 'input.search, .menu > .search > input, .menu input.search', sizer : '> input.sizer', text : '> .text:not(.icon)', - unselectable : '.disabled, .filtered' + unselectable : '.disabled, .filtered', + clearIcon : '> .remove.icon' }, className : { @@ -3937,6 +4154,8 @@ $.fn.dropdown.settings = { dropdown : 'ui dropdown', filtered : 'filtered', hidden : 'hidden transition', + icon : 'icon', + image : 'image', item : 'item', label : 'ui label', loading : 'loading', @@ -3950,64 +4169,124 @@ $.fn.dropdown.settings = { selection : 'selection', upward : 'upward', leftward : 'left', - visible : 'visible' + visible : 'visible', + clearable : 'clearable', + noselection : 'noselection', + delete : 'delete', + header : 'header', + divider : 'divider', + groupIcon : '' } }; /* Templates */ $.fn.dropdown.settings.templates = { - + deQuote: function(string) { + return String(string).replace(/"/g,""); + }, + escape: function(string, preserveHTML) { + if (preserveHTML){ + return string; + } + var + badChars = /[&<>"'`]/g, + shouldEscape = /[&<>"'`]/, + escape = { + "&": "&", + "<": "<", + ">": ">", + '"': """, + "'": "'", + "`": "`" + }, + escapedChar = function(chr) { + return escape[chr]; + } + ; + if(shouldEscape.test(string)) { + return string.replace(badChars, escapedChar); + } + return string; + }, // generates dropdown from select values - dropdown: function(select) { + dropdown: function(select, fields, preserveHTML, className) { var placeholder = select.placeholder || false, - values = select.values || {}, - html = '' + html = '', + escape = $.fn.dropdown.settings.templates.escape ; html += '<i class="dropdown icon"></i>'; - if(select.placeholder) { - html += '<div class="default text">' + placeholder + '</div>'; + if(placeholder) { + html += '<div class="default text">' + escape(placeholder,preserveHTML) + '</div>'; } else { html += '<div class="text"></div>'; } - html += '<div class="menu">'; - $.each(select.values, function(index, option) { - html += (option.disabled) - ? '<div class="disabled item" data-value="' + option.value + '">' + option.name + '</div>' - : '<div class="item" data-value="' + option.value + '">' + option.name + '</div>' - ; - }); + html += '<div class="'+className.menu+'">'; + html += $.fn.dropdown.settings.templates.menu(select, fields, preserveHTML,className); html += '</div>'; return html; }, // generates just menu from select - menu: function(response, fields) { + menu: function(response, fields, preserveHTML, className) { var - values = response[fields.values] || {}, - html = '' + values = response[fields.values] || [], + html = '', + escape = $.fn.dropdown.settings.templates.escape, + deQuote = $.fn.dropdown.settings.templates.deQuote ; $.each(values, function(index, option) { var - maybeText = (option[fields.text]) - ? 'data-text="' + option[fields.text] + '"' - : '', - maybeDisabled = (option[fields.disabled]) - ? 'disabled ' - : '' + itemType = (option[fields.type]) + ? option[fields.type] + : 'item' ; - html += '<div class="'+ maybeDisabled +'item" data-value="' + option[fields.value] + '"' + maybeText + '>'; - html += option[fields.name]; - html += '</div>'; + + if( itemType === 'item' ) { + var + maybeText = (option[fields.text]) + ? ' data-text="' + deQuote(option[fields.text]) + '"' + : '', + maybeDisabled = (option[fields.disabled]) + ? className.disabled+' ' + : '' + ; + html += '<div class="'+ maybeDisabled + (option[fields.class] ? deQuote(option[fields.class]) : className.item)+'" data-value="' + deQuote(option[fields.value]) + '"' + maybeText + '>'; + if(option[fields.image]) { + html += '<img class="'+(option[fields.imageClass] ? deQuote(option[fields.imageClass]) : className.image)+'" src="' + deQuote(option[fields.image]) + '">'; + } + if(option[fields.icon]) { + html += '<i class="'+deQuote(option[fields.icon])+' '+(option[fields.iconClass] ? deQuote(option[fields.iconClass]) : className.icon)+'"></i>'; + } + html += escape(option[fields.name] || option[fields.value],preserveHTML); + html += '</div>'; + } else if (itemType === 'header') { + var groupName = escape(option[fields.name],preserveHTML), + groupIcon = option[fields.icon] ? deQuote(option[fields.icon]) : className.groupIcon + ; + if(groupName !== '' || groupIcon !== '') { + html += '<div class="' + (option[fields.class] ? deQuote(option[fields.class]) : className.header) + '">'; + if (groupIcon !== '') { + html += '<i class="' + groupIcon + ' ' + (option[fields.iconClass] ? deQuote(option[fields.iconClass]) : className.icon) + '"></i>'; + } + html += groupName; + html += '</div>'; + } + if(option[fields.divider]){ + html += '<div class="'+className.divider+'"></div>'; + } + } }); return html; }, // generates label for multiselect - label: function(value, text) { - return text + '<i class="delete icon"></i>'; + label: function(value, text, preserveHTML, className) { + var + escape = $.fn.dropdown.settings.templates.escape; + return escape(text,preserveHTML) + '<i class="'+className.delete+' icon"></i>'; }, |