diff --git a/lam/lib/html.inc b/lam/lib/html.inc index 7bff8a1d..b29a1090 100644 --- a/lam/lib/html.inc +++ b/lam/lib/html.inc @@ -1219,6 +1219,8 @@ class htmlSelect extends htmlElement { protected $tableRowsToHide = array(); /** list of enclosing table rows to show when checked */ protected $tableRowsToShow = array(); + /** dynamic scrolling */ + private $dynamicScrolling = false; /** * Constructor. @@ -1349,6 +1351,10 @@ class htmlSelect extends htmlElement { * @param array $elements list of options */ private function printOptionsHTML($elements) { + if ($this->dynamicScrolling) { + echo "\n"; + return; + } // sorting if ($this->sortElements) { if ($this->hasDescriptiveElements) { @@ -1568,6 +1574,24 @@ class htmlSelect extends htmlElement { return 'tr'; } + /** + * Enable dynamic scrolling. This limits the number of select options to 10000 by dynamically adding/removing options. + * This will not be enabled when optgroups are used or the option size is less than 10000. + */ + public function enableDynamicScrolling() { + // not possible for optgroups and smaller option lists + if ((sizeof($this->elements) < 10000) || $this->containsOptgroups) { + return; + } + $this->dynamicScrolling = true; + $elementData = array(); + foreach ($this->elements as $key => $value) { + $elementData[] = array('label' => $key, 'value' => $value); + } + $this->addDataAttribute('dynamic-options', json_encode($elementData)); + $this->cssClasses[] = 'lam-dynamicOptions'; + } + } /** diff --git a/lam/templates/lib/500_lam.js b/lam/templates/lib/500_lam.js index f302cafb..50ca2932 100644 --- a/lam/templates/lib/500_lam.js +++ b/lam/templates/lib/500_lam.js @@ -1111,6 +1111,139 @@ window.lam.html.preventEnter = function() { }); } +window.lam.dynamicSelect = window.lam.dynamicSelect || {}; + +/** + * Activates dynamic selection for all marked select fields. + */ +window.lam.dynamicSelect.activate = function() { + var dynamicSelects = jQuery('.lam-dynamicOptions'); + dynamicSelects.each(function() { + var selectField = jQuery(this); + selectField.data('option-height', selectField.find("option").height()); + selectField.data('select-height', selectField.height()); + selectField.data('select-last-scroll-top', 0); + selectField.data('select-current-scroll', 0); + selectField.html(''); + var options = selectField.data('dynamic-options'); + var maxOptions = 3000; + var numOfOptionBeforeToLoadNextSet = 10; + var numberOfOptionsToLoad = 200; + for (var i = 0; i < maxOptions; i++) { + selectField.append(window.lam.dynamicSelect.createOption(options[i], i)); + } + if (options.length > maxOptions) { + // activate scrolling logic only if enough options are set + selectField.scroll(function(event) { + window.lam.dynamicSelect.onScroll(selectField, event, maxOptions, numOfOptionBeforeToLoadNextSet, numberOfOptionsToLoad); + }); + } + }); +} + +/** + * Creates an option field inside the select. + * + * @param data option data + * @param index index in list of all options + * @returns option + */ +window.lam.dynamicSelect.createOption = function(data, index) { + var newOption = jQuery(document.createElement("option")); + newOption.attr('value', data.value); + newOption.data('index', index); + newOption.text(data.label); + return newOption; +} + +/** + * Onscroll event. + * + * @param selectField select field + * @param event event + * @param maxOptions maximum options to show + * @param numOfOptionBeforeToLoadNextSet number of options to reach before end of list + * @param numberOfOptionsToLoad number of options to add + */ +window.lam.dynamicSelect.onScroll = function(selectField, event, maxOptions, numOfOptionBeforeToLoadNextSet, numberOfOptionsToLoad) { + var scrollTop = selectField.scrollTop(); + var totalHeight = selectField.find("option").length * selectField.data('option-height'); + var lastScrollTop = selectField.data('select-last-scroll-top'); + var selectBoxHeight = selectField.data('select-height'); + var singleOptionHeight = selectField.data('option-height'); + var currentScroll = scrollTop + selectBoxHeight; + selectField.data('select-current-scroll-top', scrollTop); + if ((scrollTop >= lastScrollTop) + && ((currentScroll + (numOfOptionBeforeToLoadNextSet * singleOptionHeight)) >= totalHeight)) { + window.lam.dynamicSelect.loadNextOptions(selectField, maxOptions, numberOfOptionsToLoad); + } + else if ((scrollTop <= lastScrollTop) + && ((scrollTop - (numOfOptionBeforeToLoadNextSet * singleOptionHeight)) <= 0)) { + window.lam.dynamicSelect.loadPreviousOptions(selectField, maxOptions, numberOfOptionsToLoad); + } + selectField.data('select-last-scroll-top', scrollTop); +} + +/** + * Loads the next bunch of options at the end. + * + * @param selectField selct field + * @param maxOptions maximum options to show + * @param numberOfOptionsToLoad number of options to add + */ +window.lam.dynamicSelect.loadNextOptions = function(selectField, maxOptions, numberOfOptionsToLoad) { + var selectBoxHeight = selectField.data('select-height'); + var singleOptionHeight = selectField.data('option-height'); + var currentScrollPosition = selectField.data('select-current-scroll-top') + selectBoxHeight; + var options = selectField.data('dynamic-options'); + var lastIndex = selectField.children().last().data('index'); + for (var toAdd = 0; toAdd < numberOfOptionsToLoad; toAdd++) { + var addPos = lastIndex + 1 + toAdd; + if (options[addPos] === undefined) { + break; + } + selectField.append(window.lam.dynamicSelect.createOption(options[addPos], addPos)); + } + var numberOfOptions = selectField.children().length; + var toRemove = numberOfOptions - maxOptions; + if (toRemove > 0) { + selectField.children().slice(0, toRemove).remove(); + } + else { + toRemove = 0; + } + selectField.scrollTop(currentScrollPosition - selectBoxHeight - (toRemove * singleOptionHeight)); +} + +/** + * Loads the next bunch of options at the beginning. + * + * @param selectField selct field + * @param maxOptions maximum options to show + * @param numberOfOptionsToLoad number of options to add + */ +window.lam.dynamicSelect.loadPreviousOptions = function(selectField, maxOptions, numberOfOptionsToLoad) { + var singleOptionHeight = selectField.data('option-height'); + var currentScrollPosition = selectField.data('select-current-scroll-top'); + var options = selectField.data('dynamic-options'); + var lastIndex = selectField.children().first().data('index'); + var added = 0; + for (var toAdd = 0; toAdd < numberOfOptionsToLoad; toAdd++) { + var addPos = lastIndex - 1 - toAdd; + if (options[addPos] === undefined) { + break; + } + added++; + selectField.prepend(window.lam.dynamicSelect.createOption(options[addPos], addPos)); + } + var numberOfOptions = selectField.children().length; + var toRemove = numberOfOptions - maxOptions; + if (toRemove > 0) { + selectField.children().slice(maxOptions).remove(); + } + selectField.scrollTop(currentScrollPosition + (added * singleOptionHeight)); +} + window.lam.selfservice = window.lam.selfservice || {}; /** @@ -1173,6 +1306,7 @@ jQuery(document).ready(function() { window.lam.tools.schema.select(); window.lam.html.activateLightboxes(); window.lam.html.preventEnter(); + window.lam.dynamicSelect.activate(); }); /**