webshims.register('form-datalist-lazy', function($, webshims, window, document, undefined, options){ var optionID = 0; var formsCFG = $.webshims.cfg.forms; var globStoredOptions = {}; var getStoredOptions = function(name){ if(!name){return [];} if(globStoredOptions[name]){ return globStoredOptions[name]; } var data; try { data = JSON.parse(localStorage.getItem('storedDatalistOptions'+name)); } catch(e){} globStoredOptions[name] = data || []; return data || []; }; var storeOptions = function(name, val){ if(!name){return;} val = val || []; try { localStorage.setItem( 'storedDatalistOptions'+name, JSON.stringify(val) ); } catch(e){} }; var lReg = //g; var splitReg = /\s*,\s*/g; webshims.getDataListVal = function(element){ var datalist = $.data(element, 'datalistWidget'); return (datalist) ? datalist.getPartialValue() : $.prop(element, 'value'); }; $.extend(options.shadowListProto, { _lazyCreate: function(opts){ var that = this; this.hideList = $.proxy(that, 'hideList'); this.index = -1; this.input = opts.input; this.arrayOptions = []; this._updateOptions(); this.popover = webshims.objectCreate(webshims.wsPopover, {}, this.options.popover); this.shadowList = this.popover.element.addClass('datalist-polyfill'); this.shadowList .on('mouseenter.datalistWidget mousedown.datalistWidget click.datalistWidget', 'li', function(e){ if(that._stopMouseOver && e && e.type == 'mouseenter'){return;} var items = $('li:not(.hidden-item)', that.shadowList); var select = (e.type == 'mousedown' || e.type == 'click'); if(select && $(opts.input).getNativeElement().triggerHandler('beforeselect', [$(e.currentTarget).find('.option-value').text()]) === false){ return (e.type != 'mousedown'); } that.markItem(items.index(e.currentTarget), select, items); if(e.type == 'click'){ that.hideList(); if(formsCFG.customDatalist){ $(opts.input).getNativeElement().trigger('datalistselect'); } } return (e.type != 'mousedown'); }) ; opts.input.setAttribute('autocomplete', 'off'); this.lastCompletedValue = ""; $(opts.input) .attr({ //role: 'combobox', 'aria-haspopup': 'true', 'aria-autocomplete': 'both' //support only list? }) .on({ 'input.datalistWidget': function(){ if(!that.triggeredByDatalist){ that.changedValue = false; that.showHideOptions(); } }, 'keydown.datalistWidget': function(e){ var keyCode = e.keyCode; var activeItem; var items; var doValue = !!that.options.inlineValue || 'onlyScroll'; if(keyCode == 40 && !that.showList()){ that.markItem(that.index + 1, doValue); return false; } if(!that.popover.isVisible){return;} if(keyCode == 38){ that.markItem(that.index - 1, doValue); return false; } if(!e.shiftKey && (keyCode == 33 || keyCode == 36)){ that.markItem(0, doValue); return false; } if(!e.shiftKey && (keyCode == 34 || keyCode == 35)){ items = $('li:not(.hidden-item)', that.shadowList); that.markItem(items.length - 1, true, items); return false; } if(keyCode == 13 || keyCode == 27){ if (keyCode == 13){ activeItem = $('li.active-item:not(.hidden-item)', that.shadowList); if(that.isCompleted){ $.prop(opts.input, 'selectionStart', $.prop(opts.input, 'value').length); if(that.lastCompletedValue && !activeItem[0]){ that.lastCompletedValue = ""; that.isCompleted = false; } } if($(opts.input).getNativeElement().triggerHandler('beforeselect', [activeItem.find('.option-value').text()]) === false){ return; } that.changeValue( activeItem ); } that.hideList(); if(formsCFG.customDatalist && activeItem && activeItem[0]){ $(opts.input).getNativeElement().trigger('datalistselect'); } if(e.keyCode != 13 || (activeItem && activeItem[0])){ return false; } } }, 'focus.datalistWidget': function(){ that.lastCompletedValue = ""; if(that.options.focus){ that.showList(); } }, 'mousedown.datalistWidget': function(){ if($(this).is(':focus')){ that.showList(); } } }) ; $(this.datalist) .off('updateDatalist.datalistWidget') .on('updateDatalist.datalistWidget', $.proxy(this, '_resetListCached')) .on('remove', function(e){ if(!e.originalEvent){ that.destroy(); } }) ; this._resetListCached(); if(opts.input.form && (opts.input.name || opts.input.id)){ $(opts.input.form).on('submit.datalistWidget'+opts.input.id, function(){ if(!$(opts.input).hasClass('no-datalist-cache') && that._autocomplete != 'off'){ var val = $.prop(opts.input, 'value'); var name = (opts.input.name || opts.input.id) + $.prop(opts.input, 'type'); if(!that.storedOptions){ that.storedOptions = getStoredOptions( name ); } if(val && that.storedOptions.indexOf(val) == -1){ that.storedOptions.push(val); storeOptions(name, that.storedOptions ); } } }); } $(window).on('unload.datalist'+this.id +' beforeunload.datalist'+this.id, function(e){ that.destroy(e); }); }, _resetListCached: function(e){ var that = this; var forceShow; this.needsUpdate = true; this.lastUpdatedValue = false; this.lastUnfoundValue = ''; if(!this.updateTimer){ if(window.QUnit || (forceShow = ($(that.input).is(':focus') && (that.options.focus || $.prop(that.input, 'value'))) )){ that.updateListOptions(forceShow); } else { that.updateTimer = setTimeout(function(){ that.updateListOptions(); that = null; }, 200); } } }, _updateOptions: function(){ this.options = webshims.getOptions(this.input, 'list', options.list); if($(this.input).prop('multiple')){ if($(this.input).prop('type') != 'email'){ webshims.warn('multiple only used on email and file type. Use data-list-multiple instead.'); } else { this.options.multiple = true; } } if(!this.options.inlineValue && !this.options.valueCompletion){ $.attr(this.input, 'aria-autocomplete', 'list'); $.attr(this.input, 'aria-expanded', 'false'); } if( this.options.getOptionContent && !$.isFunction(this.options.getOptionContent) ){ this.options.getOptionContent = false; } }, updateListOptions: function(_forceShow){ this.needsUpdate = false; clearTimeout(this.updateTimer); this.updateTimer = false; var list = []; var values = []; var allOptions = []; var rElem, rItem, rOptions, rI, rLen, item, value; for(rOptions = $.prop(this.datalist, 'options'), rI = 0, rLen = rOptions.length; rI < rLen; rI++){ rElem = rOptions[rI]; if(!rElem.disabled && (value = $(rElem).val())){ rItem = { value: this.options.noHtmlEscape ? value : value.replace(lReg, '<').replace(gReg, '>'), label: $.trim($.attr(rElem, 'label')) || '', className: rElem.className || '', elem: rElem }; if(rItem.label){ rItem.className += ' has-option-label'; } values.push(rItem.value); allOptions.push(rItem); } } if(!this.storedOptions){ this.storedOptions = ($(this.input).hasClass('no-datalist-cache') || this._autocomplete == 'off') ? [] : getStoredOptions((this.input.name || this.input.id) + $.prop(this.input, 'type')); } this.storedOptions.forEach(function(val, i){ if(values.indexOf(val) == -1){ allOptions.push({value: val, label: '', className: 'stored-suggest', style: ''}); } }); for(rI = 0, rLen = allOptions.length; rI < rLen; rI++){ item = allOptions[rI]; list[rI] = '
  • '+ this.getOptionContent(item) +'
  • '; } this.arrayOptions = allOptions; this.popover.contentElement.html('
    '); $(this.input).removeAttr('aria-activedescendant').triggerHandler('datalistcreated', [{instance: this}]); if(_forceShow || this.popover.isVisible){ if(this.options.valueCompletion && this.lastCompletedValue && (value = $.prop(this.input, 'value')) && !value.indexOf(this.lastCompletedValue)){ $.prop(this.input, 'value', this.lastCompletedValue); $(this.input).triggerHandler('updateInput'); } if(value != this.lastCompletedValue){ this.lastCompletedValue = ""; } this.showHideOptions(); } else { this.lastCompletedValue = ""; this.isCompleted = false; } }, getOptionContent: function(item){ var content; var args = [{instance: this, item: item}]; if( ( content = $(this.input).triggerHandler('getoptioncontent', args) ) && content.indexOf && content.indexOf('option-value') == -1 ){ content += ''; } if(content == null){ content = ''+ item.value +''; if(item.label){ content += ' '+ item.label +''; } } return content || ''; }, setCompletedValue: function(value, foundItem, length){ this.isCompleted = false; if(!this.options.valueCompletion || !foundItem || this.lastCompletedValue.length >= value.length ){ this.lastCompletedValue = value; return; } var newValue; var input = this.input; var end = $.prop(input, 'selectionEnd'); this.lastCompletedValue = value; if(value.length == end){ newValue = value + foundItem.value.substr(length.length); $(input).triggerHandler('triggerinput'); $.prop(input, 'value', newValue); $(input).triggerHandler('updateInput'); $(input).callProp('setSelectionRange', [value.length, newValue.length]); //safari workaround || needs more investigation setTimeout(function(){ if(newValue == $.prop(input, 'value') && $.prop(input, 'selectionEnd') != newValue.length){ $.prop(input, 'selectionEnd', newValue.length); } }, 0); this.isCompleted = true; } }, getPartialValue: function(){ var value = $.prop(this.input, 'value'); if(this.options.valueCompletion && this.lastCompletedValue && !$.prop(this.input, 'value').indexOf(this.lastCompletedValue)){ value = this.lastCompletedValue; } if(this.options.multiple){ value = value.split(splitReg); value = value[value.length - 1] || ''; } return value; }, showHideOptions: function(_fromShowList){ var lis, firstFoundValue; var inputValue = $.prop(this.input, 'value'); var value = inputValue.toLowerCase(); var found = false; var startSearch = this.options.filter == '^'; var that = this; //first check prevent infinite loop, second creates simple lazy optimization if(value === this.lastUpdatedValue){ return; } if(this.options.multiple){ value = value.split(splitReg); value = value[value.length - 1] || ''; } if(this.lastUnfoundValue && value.indexOf(this.lastUnfoundValue) === 0){ this.hideList(); return; } this.lastUpdatedValue = value; lis = $('li', this.shadowList); if(value && this.options.filter != '!'){ this.arrayOptions.forEach(function(item, i){ var search, searchIndex, foundName; if(!('lowerValue' in item)){ item.lowerValue = item.value.toLowerCase(); if(item.label && item.label != item.value ){ item.lowerLabel = item.label.toLowerCase(); } } if(value != item.lowerValue && item.lowerLabel != value){ searchIndex = item.lowerValue.indexOf(value); search = startSearch ? !searchIndex : searchIndex !== -1; if(search){ foundName = 'value'; if(!firstFoundValue && !searchIndex){ firstFoundValue = item; } } else if(item.lowerLabel){ searchIndex = item.lowerLabel.indexOf(value); search = startSearch ? !searchIndex : searchIndex !== -1; foundName = 'label'; } } if(search){ that.addMark($(lis[i]).removeClass('hidden-item'), item, foundName, searchIndex, value.length); found = true; } else { $(lis[i]).addClass('hidden-item'); } }); } else if(lis.length) { this.removeMark(lis.removeClass('hidden-item')); found = true; } this.hasViewableData = found; if(!_fromShowList && found){ if(this.popover.isVisible && this.popover.element.attr('data-vertical') == 'bottom'){ that._stopMouseOver = true; this.popover.element.triggerHandler('pospopover'); setTimeout(function(){ that._stopMouseOver = false; }, 9); } this.showList(); } if(!found){ this.lastUnfoundValue = value; this.hideList(); } else { $(this.input).removeAttr('aria-activedescendant'); this.setCompletedValue(inputValue, firstFoundValue, value); this.lastUnfoundValue = false; } }, otherType: { value: 'label', label: 'value' }, addMark: function(elem, item, prop, start, length){ if(this.options.highlight){ var text = item[prop].substr(start, length); text = item[prop].replace(text ,''+ text +''); $('.option-'+ this.otherType[prop] +' > mark', elem).each(this._replaceMark); $('.option-'+prop, elem).html(text); } }, _replaceMark: function(){ var content = $(this).html(); $(this).replaceWith(content); }, removeMark: function(lis){ if(this.options.highlight){ $('mark', lis).each(this._replaceMark); } }, showList: function(){ if(this.popover.isVisible){return false;} if(this.needsUpdate){ this.updateListOptions(); } this.showHideOptions(true); if(!this.hasViewableData){return false;} var that = this; that.shadowList.find('li.active-item').removeClass('active-item'); that.popover.show(this.input); $(this.input) .attr({'aria-expanded': 'true'}) ; return true; }, hideList: function(){ if(!this.popover.isVisible){return false;} var that = this; $(this.input) .attr({'aria-expanded': 'false'}) .removeAttr('aria-activedescendant') ; this.popover.hide(); that.shadowList.removeClass('datalist-visible list-item-active'); that.index = -1; if(that.changedValue){ that.triggeredByDatalist = true; $(that.input).trigger('input').trigger('change'); that.changedValue = false; that.triggeredByDatalist = false; } return true; }, scrollIntoView: function(elem){ var ul = $('ul', this.shadowList); var div = $('div.datalist-box', this.shadowList); var elemPos = elem.position(); var containerHeight; elemPos.top -= (parseInt(ul.css('paddingTop'), 10) || 0) + (parseInt(ul.css('marginTop'), 10) || 0) + (parseInt(ul.css('borderTopWidth'), 10) || 0); if(elemPos.top < 0){ div.scrollTop( div.scrollTop() + elemPos.top - 2); return; } elemPos.top += elem.outerHeight(); containerHeight = div.height(); if(elemPos.top > containerHeight){ div.scrollTop( div.scrollTop() + (elemPos.top - containerHeight) + 2); } }, changeValue: function(activeItem){ if(!activeItem[0]){return;} var spinner, tmpValue; var newValue = $('span.option-value', activeItem).text(); var oldValue = $.prop(this.input, 'value'); if(this.options.multiple){ tmpValue = oldValue.split(splitReg); tmpValue[tmpValue.length - 1] = newValue; newValue = tmpValue.join(', '); } if(this.options.valueCompletion && this.lastCompletedValue && !oldValue.indexOf(this.lastCompletedValue)){ oldValue = this.lastCompletedValue; this.lastCompletedValue = ""; } if(newValue != oldValue){ $(this.input) .prop('value', newValue) .triggerHandler('updateInput') ; this.changedValue = true; if((spinner = $.data(this.input, 'wsspinner')) && spinner.setInput){ spinner.setInput(newValue); } } }, markItem: function(index, doValue, items){ var activeItem; var goesUp; items = items || $('li:not(.hidden-item)', this.shadowList); if(!items.length){return;} if(index < 0){ index = items.length - 1; } else if(index >= items.length){ index = 0; } items.removeClass('active-item'); this.shadowList.addClass('list-item-active'); activeItem = items.eq(index).addClass('active-item'); if(doValue){ if(doValue != 'onlyScroll'){ this.changeValue(activeItem); } else { $(this.input).attr('aria-activedescendant', activeItem.prop('id')); } this.scrollIntoView(activeItem); } this.index = index; } }); });