From 1c377fc61ebe5e9e6e72e4eff9a925f22a7a5e69 Mon Sep 17 00:00:00 2001 From: Shamim Rezaie Date: Sun, 5 Nov 2023 03:28:03 +1100 Subject: [PATCH] MDL-78885 comboboxsearch: Show x button when user search is not empty --- lib/amd/build/comboboxsearch/search_combobox.min.js | 2 +- lib/amd/build/comboboxsearch/search_combobox.min.js.map | 2 +- lib/amd/src/comboboxsearch/search_combobox.js | 5 +++++ 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/amd/build/comboboxsearch/search_combobox.min.js b/lib/amd/build/comboboxsearch/search_combobox.min.js index 773a2e4e145..2074b0e62eb 100644 --- a/lib/amd/build/comboboxsearch/search_combobox.min.js +++ b/lib/amd/build/comboboxsearch/search_combobox.min.js @@ -5,6 +5,6 @@ define("core/comboboxsearch/search_combobox",["exports","jquery","core/utils","c * @module core/comboboxsearch/search_combobox * @copyright 2023 Mathew May * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_jquery=_interopRequireDefault(_jquery),_pending=_interopRequireDefault(_pending);return _exports.default=class{constructor(){var _this$searchInput$val,_this$searchInput;_defineProperty(this,"selectors",{component:this.componentSelector(),trigger:this.triggerSelector(),toggle:'[data-toggle="dropdown"]',input:'[data-action="search"]',clearSearch:'[data-action="clearsearch"]',dropdown:this.dropdownSelector(),resultitems:'[role="option"]',viewall:"#select-all",combobox:'[role="combobox"]'}),_defineProperty(this,"matchedResults",[]),_defineProperty(this,"searchTerm",""),_defineProperty(this,"preppedSearchTerm",null),_defineProperty(this,"resultNodes",[]),_defineProperty(this,"currentNode",null),_defineProperty(this,"currentViewAll",null),_defineProperty(this,"dataset",null),_defineProperty(this,"datasetSize",0),_defineProperty(this,"component",document.querySelector(this.selectors.component)),_defineProperty(this,"toggle",this.component.querySelector(this.selectors.toggle)),_defineProperty(this,"searchInput",this.component.querySelector(this.selectors.input)),_defineProperty(this,"searchDropdown",this.component.querySelector(this.selectors.dropdown)),_defineProperty(this,"clearSearchButton",this.component.querySelector(this.selectors.clearSearch)),_defineProperty(this,"combobox",this.component.querySelector(this.selectors.combobox)),_defineProperty(this,"$component",(0,_jquery.default)(this.component)),this.setSearchTerms(null!==(_this$searchInput$val=null===(_this$searchInput=this.searchInput)||void 0===_this$searchInput?void 0:_this$searchInput.value)&&void 0!==_this$searchInput$val?_this$searchInput$val:""),this.registerClickHandlers(),null!==this.searchInput&&(this.registerInputHandlers(),this.registerChangeHandlers())}fetchDataset(){throw new Error("fetchDataset() must be implemented in ".concat(this.constructor.name))}filterDataset(dataset){throw new Error("filterDataset(".concat(dataset,") must be implemented in ").concat(this.constructor.name))}filterMatchDataset(){throw new Error("filterMatchDataset() must be implemented in ".concat(this.constructor.name))}renderDropdown(){throw new Error("renderDropdown() must be implemented in ".concat(this.constructor.name))}componentSelector(){throw new Error("componentSelector() must be implemented in ".concat(this.constructor.name))}dropdownSelector(){throw new Error("dropdownSelector() must be implemented in ".concat(this.constructor.name))}triggerSelector(){throw new Error("triggerSelector() must be implemented in ".concat(this.constructor.name))}async getDataset(){return this.dataset||(this.dataset=await this.fetchDataset()),this.datasetSize=this.dataset.length,this.dataset}getDatasetSize(){return this.datasetSize}getMatchedResults(){return this.matchedResults}setMatchedResults(result){this.matchedResults=result}getSearchTerm(){return this.searchTerm}getPreppedSearchTerm(){return this.preppedSearchTerm}setSearchTerms(result){this.searchTerm=result,this.preppedSearchTerm=result.toLowerCase()}getHTMLElements(){return this.updateNodes(),{searchDropdown:this.searchDropdown,currentViewAll:this.currentViewAll,searchInput:this.searchInput,clearSearchButton:this.clearSearchButton,trigger:this.component.querySelector(this.selectors.trigger)}}closeSearch(){let clear=arguments.length>0&&void 0!==arguments[0]&&arguments[0];this.toggleDropdown(),clear&&(this.clearSearchButton.classList.add("d-none"),this.setSearchTerms(""),this.searchInput.value="")}searchResultsVisible(){const{searchDropdown:searchDropdown}=this.getHTMLElements();return null!==searchDropdown.offsetParent}toggleDropdown(){arguments.length>0&&void 0!==arguments[0]&&arguments[0]?(0,_jquery.default)(this.toggle).dropdown("show"):(0,_jquery.default)(this.toggle).dropdown("hide")}updateNodes(){this.resultNodes=[...this.component.querySelectorAll(this.selectors.resultitems)],this.currentNode=this.resultNodes.find((r=>r.id===document.activeElement.id)),this.currentViewAll=this.component.querySelector(this.selectors.viewall),this.clearSearchButton=this.component.querySelector(this.selectors.clearSearch),this.searchInput=this.component.querySelector(this.selectors.input),this.searchDropdown=this.component.querySelector(this.selectors.dropdown)}registerClickHandlers(){this.component.addEventListener("click",this.clickHandler.bind(this))}registerChangeHandlers(){this.component.querySelector("#".concat(this.combobox.dataset.inputElement)).addEventListener("change",this.changeHandler.bind(this))}registerInputHandlers(){this.searchInput.addEventListener("input",(0,_utils.debounce)((async()=>{if(this.getSearchTerm()===this.searchInput.value&&this.searchResultsVisible())return void window.console.warn("Search term matches input value - skipping");this.setSearchTerms(this.searchInput.value);const pendingPromise=new _pending.default;""===this.getSearchTerm()?(this.toggleDropdown(),this.clearSearchButton.classList.add("d-none"),await this.filterrenderpipe()):(this.clearSearchButton.classList.remove("d-none"),await this.renderAndShow()),pendingPromise.resolve()}),300,{pending:!0}))}async filterrenderpipe(){this.updateNodes(),this.setMatchedResults(await this.filterDataset(await this.getDataset())),this.filterMatchDataset(),await this.renderDropdown()}async renderAndShow(){this.setMatchedResults(await this.filterDataset(await this.getDataset())),await this.filterMatchDataset(),await this.renderDropdown(),this.toggleDropdown(!0)}async clickHandler(e){this.updateNodes(),e.target.closest(this.selectors.clearSearch)&&(this.closeSearch(!0),this.searchInput.focus(),this.searchInput.removeAttribute("aria-activedescendant")),""!==this.getSearchTerm()&&!this.getHTMLElements().searchDropdown.classList.contains("show")&&e.target.closest(this.selectors.input)&&await this.renderAndShow()}changeHandler(e){}},_exports.default})); + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_jquery=_interopRequireDefault(_jquery),_pending=_interopRequireDefault(_pending);return _exports.default=class{constructor(){var _this$searchInput$val,_this$searchInput;_defineProperty(this,"selectors",{component:this.componentSelector(),trigger:this.triggerSelector(),toggle:'[data-toggle="dropdown"]',input:'[data-action="search"]',clearSearch:'[data-action="clearsearch"]',dropdown:this.dropdownSelector(),resultitems:'[role="option"]',viewall:"#select-all",combobox:'[role="combobox"]'}),_defineProperty(this,"matchedResults",[]),_defineProperty(this,"searchTerm",""),_defineProperty(this,"preppedSearchTerm",null),_defineProperty(this,"resultNodes",[]),_defineProperty(this,"currentNode",null),_defineProperty(this,"currentViewAll",null),_defineProperty(this,"dataset",null),_defineProperty(this,"datasetSize",0),_defineProperty(this,"component",document.querySelector(this.selectors.component)),_defineProperty(this,"toggle",this.component.querySelector(this.selectors.toggle)),_defineProperty(this,"searchInput",this.component.querySelector(this.selectors.input)),_defineProperty(this,"searchDropdown",this.component.querySelector(this.selectors.dropdown)),_defineProperty(this,"clearSearchButton",this.component.querySelector(this.selectors.clearSearch)),_defineProperty(this,"combobox",this.component.querySelector(this.selectors.combobox)),_defineProperty(this,"$component",(0,_jquery.default)(this.component)),this.setSearchTerms(null!==(_this$searchInput$val=null===(_this$searchInput=this.searchInput)||void 0===_this$searchInput?void 0:_this$searchInput.value)&&void 0!==_this$searchInput$val?_this$searchInput$val:""),this.registerClickHandlers(),null!==this.searchInput&&(this.registerInputHandlers(),this.registerChangeHandlers()),""!==this.getSearchTerm()&&this.clearSearchButton.classList.remove("d-none")}fetchDataset(){throw new Error("fetchDataset() must be implemented in ".concat(this.constructor.name))}filterDataset(dataset){throw new Error("filterDataset(".concat(dataset,") must be implemented in ").concat(this.constructor.name))}filterMatchDataset(){throw new Error("filterMatchDataset() must be implemented in ".concat(this.constructor.name))}renderDropdown(){throw new Error("renderDropdown() must be implemented in ".concat(this.constructor.name))}componentSelector(){throw new Error("componentSelector() must be implemented in ".concat(this.constructor.name))}dropdownSelector(){throw new Error("dropdownSelector() must be implemented in ".concat(this.constructor.name))}triggerSelector(){throw new Error("triggerSelector() must be implemented in ".concat(this.constructor.name))}async getDataset(){return this.dataset||(this.dataset=await this.fetchDataset()),this.datasetSize=this.dataset.length,this.dataset}getDatasetSize(){return this.datasetSize}getMatchedResults(){return this.matchedResults}setMatchedResults(result){this.matchedResults=result}getSearchTerm(){return this.searchTerm}getPreppedSearchTerm(){return this.preppedSearchTerm}setSearchTerms(result){this.searchTerm=result,this.preppedSearchTerm=result.toLowerCase()}getHTMLElements(){return this.updateNodes(),{searchDropdown:this.searchDropdown,currentViewAll:this.currentViewAll,searchInput:this.searchInput,clearSearchButton:this.clearSearchButton,trigger:this.component.querySelector(this.selectors.trigger)}}closeSearch(){let clear=arguments.length>0&&void 0!==arguments[0]&&arguments[0];this.toggleDropdown(),clear&&(this.clearSearchButton.classList.add("d-none"),this.setSearchTerms(""),this.searchInput.value="")}searchResultsVisible(){const{searchDropdown:searchDropdown}=this.getHTMLElements();return null!==searchDropdown.offsetParent}toggleDropdown(){arguments.length>0&&void 0!==arguments[0]&&arguments[0]?(0,_jquery.default)(this.toggle).dropdown("show"):(0,_jquery.default)(this.toggle).dropdown("hide")}updateNodes(){this.resultNodes=[...this.component.querySelectorAll(this.selectors.resultitems)],this.currentNode=this.resultNodes.find((r=>r.id===document.activeElement.id)),this.currentViewAll=this.component.querySelector(this.selectors.viewall),this.clearSearchButton=this.component.querySelector(this.selectors.clearSearch),this.searchInput=this.component.querySelector(this.selectors.input),this.searchDropdown=this.component.querySelector(this.selectors.dropdown)}registerClickHandlers(){this.component.addEventListener("click",this.clickHandler.bind(this))}registerChangeHandlers(){this.component.querySelector("#".concat(this.combobox.dataset.inputElement)).addEventListener("change",this.changeHandler.bind(this))}registerInputHandlers(){this.searchInput.addEventListener("input",(0,_utils.debounce)((async()=>{if(this.getSearchTerm()===this.searchInput.value&&this.searchResultsVisible())return void window.console.warn("Search term matches input value - skipping");this.setSearchTerms(this.searchInput.value);const pendingPromise=new _pending.default;""===this.getSearchTerm()?(this.toggleDropdown(),this.clearSearchButton.classList.add("d-none"),await this.filterrenderpipe()):(this.clearSearchButton.classList.remove("d-none"),await this.renderAndShow()),pendingPromise.resolve()}),300,{pending:!0}))}async filterrenderpipe(){this.updateNodes(),this.setMatchedResults(await this.filterDataset(await this.getDataset())),this.filterMatchDataset(),await this.renderDropdown()}async renderAndShow(){this.setMatchedResults(await this.filterDataset(await this.getDataset())),await this.filterMatchDataset(),await this.renderDropdown(),this.toggleDropdown(!0)}async clickHandler(e){this.updateNodes(),e.target.closest(this.selectors.clearSearch)&&(this.closeSearch(!0),this.searchInput.focus(),this.searchInput.removeAttribute("aria-activedescendant")),""!==this.getSearchTerm()&&!this.getHTMLElements().searchDropdown.classList.contains("show")&&e.target.closest(this.selectors.input)&&await this.renderAndShow()}changeHandler(e){}},_exports.default})); //# sourceMappingURL=search_combobox.min.js.map \ No newline at end of file diff --git a/lib/amd/build/comboboxsearch/search_combobox.min.js.map b/lib/amd/build/comboboxsearch/search_combobox.min.js.map index c1b320e2521..929a00be6f7 100644 --- a/lib/amd/build/comboboxsearch/search_combobox.min.js.map +++ b/lib/amd/build/comboboxsearch/search_combobox.min.js.map @@ -1 +1 @@ -{"version":3,"file":"search_combobox.min.js","sources":["../../src/comboboxsearch/search_combobox.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\nimport $ from 'jquery';\nimport {debounce} from 'core/utils';\nimport Pending from 'core/pending';\n\n/**\n * The class that manages the state of the search within a combobox.\n *\n * @module core/comboboxsearch/search_combobox\n * @copyright 2023 Mathew May \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nexport default class {\n // Define our standard lookups.\n selectors = {\n component: this.componentSelector(),\n trigger: this.triggerSelector(),\n toggle: '[data-toggle=\"dropdown\"]',\n input: '[data-action=\"search\"]',\n clearSearch: '[data-action=\"clearsearch\"]',\n dropdown: this.dropdownSelector(),\n resultitems: '[role=\"option\"]',\n viewall: '#select-all',\n combobox: '[role=\"combobox\"]',\n };\n\n // The results from the called filter function.\n matchedResults = [];\n\n // What did the user search for?\n searchTerm = '';\n\n // What the user searched for as a lowercase.\n preppedSearchTerm = null;\n\n // The DOM nodes after the dropdown render.\n resultNodes = [];\n\n // Where does the user currently have focus?\n currentNode = null;\n\n // The current node for the view all link.\n currentViewAll = null;\n\n dataset = null;\n\n datasetSize = 0;\n\n // DOM nodes that persist.\n component = document.querySelector(this.selectors.component);\n toggle = this.component.querySelector(this.selectors.toggle);\n searchInput = this.component.querySelector(this.selectors.input);\n searchDropdown = this.component.querySelector(this.selectors.dropdown);\n clearSearchButton = this.component.querySelector(this.selectors.clearSearch);\n combobox = this.component.querySelector(this.selectors.combobox);\n $component = $(this.component);\n\n constructor() {\n // If we have a search input, try to get the value otherwise fallback.\n this.setSearchTerms(this.searchInput?.value ?? '');\n // Begin handling the base search component.\n this.registerClickHandlers();\n // Conditionally set up the input handler since we don't know exactly how we were called.\n if (this.searchInput !== null) {\n this.registerInputHandlers();\n this.registerChangeHandlers();\n }\n }\n\n /**\n * Stub out a required function.\n */\n fetchDataset() {\n throw new Error(`fetchDataset() must be implemented in ${this.constructor.name}`);\n }\n\n /**\n * Stub out a required function.\n * @param {Array} dataset\n */\n filterDataset(dataset) {\n throw new Error(`filterDataset(${dataset}) must be implemented in ${this.constructor.name}`);\n }\n\n /**\n * Stub out a required function.\n */\n filterMatchDataset() {\n throw new Error(`filterMatchDataset() must be implemented in ${this.constructor.name}`);\n }\n\n /**\n * Stub out a required function.\n */\n renderDropdown() {\n throw new Error(`renderDropdown() must be implemented in ${this.constructor.name}`);\n }\n\n /**\n * Stub out a required function.\n */\n componentSelector() {\n throw new Error(`componentSelector() must be implemented in ${this.constructor.name}`);\n }\n\n /**\n * Stub out a required function.\n */\n dropdownSelector() {\n throw new Error(`dropdownSelector() must be implemented in ${this.constructor.name}`);\n }\n\n /**\n * Stub out a required function.\n */\n triggerSelector() {\n throw new Error(`triggerSelector() must be implemented in ${this.constructor.name}`);\n }\n\n /**\n * Return the dataset that we will be searching upon.\n *\n * @returns {Promise}\n */\n async getDataset() {\n if (!this.dataset) {\n this.dataset = await this.fetchDataset();\n }\n this.datasetSize = this.dataset.length;\n return this.dataset;\n }\n\n /**\n * Return the size of the dataset.\n *\n * @returns {number}\n */\n getDatasetSize() {\n return this.datasetSize;\n }\n\n /**\n * Return the results of the filter upon the dataset.\n *\n * @returns {Array}\n */\n getMatchedResults() {\n return this.matchedResults;\n }\n\n /**\n * Given a filter has been run across the dataset, store the matched results.\n *\n * @param {Array} result\n */\n setMatchedResults(result) {\n this.matchedResults = result;\n }\n\n /**\n * Get the value that the user entered.\n *\n * @returns {string}\n */\n getSearchTerm() {\n return this.searchTerm;\n }\n\n /**\n * Get the transformed search value.\n *\n * @returns {string}\n */\n getPreppedSearchTerm() {\n return this.preppedSearchTerm;\n }\n\n /**\n * When a user searches for something, set our variable to manage it.\n *\n * @param {string} result\n */\n setSearchTerms(result) {\n this.searchTerm = result;\n this.preppedSearchTerm = result.toLowerCase();\n }\n\n /**\n * Return an object containing a handfull of dom nodes that we sometimes need the value of.\n *\n * @returns {object}\n */\n getHTMLElements() {\n this.updateNodes();\n return {\n searchDropdown: this.searchDropdown,\n currentViewAll: this.currentViewAll,\n searchInput: this.searchInput,\n clearSearchButton: this.clearSearchButton,\n trigger: this.component.querySelector(this.selectors.trigger),\n };\n }\n\n /**\n * When called, close the dropdown and reset the input field attributes.\n *\n * @param {Boolean} clear Conditionality clear the input box.\n */\n closeSearch(clear = false) {\n this.toggleDropdown();\n if (clear) {\n // Hide the \"clear\" search button search bar.\n this.clearSearchButton.classList.add('d-none');\n // Clear the entered search query in the search bar and hide the search results container.\n this.setSearchTerms('');\n this.searchInput.value = \"\";\n }\n }\n\n /**\n * Check whether search results are currently visible.\n *\n * @returns {Boolean}\n */\n searchResultsVisible() {\n const {searchDropdown} = this.getHTMLElements();\n // If a Node is not visible, then the offsetParent is null.\n return searchDropdown.offsetParent !== null;\n }\n\n /**\n * When called, update the dropdown fields.\n *\n * @param {Boolean} on Flag to toggle hiding or showing values.\n */\n toggleDropdown(on = false) {\n if (on) {\n $(this.toggle).dropdown('show');\n } else {\n $(this.toggle).dropdown('hide');\n }\n }\n\n /**\n * These class members change when a new result set is rendered. So update for fresh data.\n */\n updateNodes() {\n this.resultNodes = [...this.component.querySelectorAll(this.selectors.resultitems)];\n this.currentNode = this.resultNodes.find(r => r.id === document.activeElement.id);\n this.currentViewAll = this.component.querySelector(this.selectors.viewall);\n this.clearSearchButton = this.component.querySelector(this.selectors.clearSearch);\n this.searchInput = this.component.querySelector(this.selectors.input);\n this.searchDropdown = this.component.querySelector(this.selectors.dropdown);\n }\n\n /**\n * Register clickable event listeners.\n */\n registerClickHandlers() {\n // Register click events within the component.\n this.component.addEventListener('click', this.clickHandler.bind(this));\n }\n\n /**\n * Register change event listeners.\n */\n registerChangeHandlers() {\n const valueElement = this.component.querySelector(`#${this.combobox.dataset.inputElement}`);\n valueElement.addEventListener('change', this.changeHandler.bind(this));\n }\n\n /**\n * Register input event listener for the text input area.\n */\n registerInputHandlers() {\n // Register & handle the text input.\n this.searchInput.addEventListener('input', debounce(async() => {\n if (this.getSearchTerm() === this.searchInput.value && this.searchResultsVisible()) {\n window.console.warn(`Search term matches input value - skipping`);\n // The debounce canhappen multiple times quickly. GRrargh\n return;\n }\n this.setSearchTerms(this.searchInput.value);\n\n const pendingPromise = new Pending();\n if (this.getSearchTerm() === '') {\n this.toggleDropdown();\n this.clearSearchButton.classList.add('d-none');\n await this.filterrenderpipe();\n } else {\n this.clearSearchButton.classList.remove('d-none');\n await this.renderAndShow();\n }\n pendingPromise.resolve();\n }, 300, {pending: true}));\n }\n\n /**\n * Update any changeable nodes, filter and then render the result.\n *\n * @returns {Promise}\n */\n async filterrenderpipe() {\n this.updateNodes();\n this.setMatchedResults(await this.filterDataset(await this.getDataset()));\n this.filterMatchDataset();\n await this.renderDropdown();\n }\n\n /**\n * A combo method to take the matching fields and render out the results.\n *\n * @returns {Promise}\n */\n async renderAndShow() {\n // User has given something for us to filter against.\n this.setMatchedResults(await this.filterDataset(await this.getDataset()));\n await this.filterMatchDataset();\n // Replace the dropdown node contents and show the results.\n await this.renderDropdown();\n // Set the dropdown to open.\n this.toggleDropdown(true);\n }\n\n /**\n * The handler for when a user interacts with the component.\n *\n * @param {MouseEvent} e The triggering event that we are working with.\n */\n async clickHandler(e) {\n this.updateNodes();\n // The \"clear search\" button is triggered.\n if (e.target.closest(this.selectors.clearSearch)) {\n this.closeSearch(true);\n this.searchInput.focus();\n // Remove aria-activedescendant when the available options change.\n this.searchInput.removeAttribute('aria-activedescendant');\n }\n // User may have accidentally clicked off the dropdown and wants to reopen it.\n if (\n this.getSearchTerm() !== ''\n && !this.getHTMLElements().searchDropdown.classList.contains('show')\n && e.target.closest(this.selectors.input)\n ) {\n await this.renderAndShow();\n }\n }\n\n /**\n * The handler for when a user changes the value of the component (selects an option from the dropdown).\n *\n * @param {Event} e The change event.\n */\n // eslint-disable-next-line no-unused-vars\n changeHandler(e) {\n // Components may override this method to do something.\n }\n}\n"],"names":["constructor","component","this","componentSelector","trigger","triggerSelector","toggle","input","clearSearch","dropdown","dropdownSelector","resultitems","viewall","combobox","document","querySelector","selectors","setSearchTerms","searchInput","_this$searchInput","value","registerClickHandlers","registerInputHandlers","registerChangeHandlers","fetchDataset","Error","name","filterDataset","dataset","filterMatchDataset","renderDropdown","datasetSize","length","getDatasetSize","getMatchedResults","matchedResults","setMatchedResults","result","getSearchTerm","searchTerm","getPreppedSearchTerm","preppedSearchTerm","toLowerCase","getHTMLElements","updateNodes","searchDropdown","currentViewAll","clearSearchButton","closeSearch","clear","toggleDropdown","classList","add","searchResultsVisible","offsetParent","resultNodes","querySelectorAll","currentNode","find","r","id","activeElement","addEventListener","clickHandler","bind","inputElement","changeHandler","async","window","console","warn","pendingPromise","Pending","filterrenderpipe","remove","renderAndShow","resolve","pending","getDataset","e","target","closest","focus","removeAttribute","contains"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"search_combobox.min.js","sources":["../../src/comboboxsearch/search_combobox.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\nimport $ from 'jquery';\nimport {debounce} from 'core/utils';\nimport Pending from 'core/pending';\n\n/**\n * The class that manages the state of the search within a combobox.\n *\n * @module core/comboboxsearch/search_combobox\n * @copyright 2023 Mathew May \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nexport default class {\n // Define our standard lookups.\n selectors = {\n component: this.componentSelector(),\n trigger: this.triggerSelector(),\n toggle: '[data-toggle=\"dropdown\"]',\n input: '[data-action=\"search\"]',\n clearSearch: '[data-action=\"clearsearch\"]',\n dropdown: this.dropdownSelector(),\n resultitems: '[role=\"option\"]',\n viewall: '#select-all',\n combobox: '[role=\"combobox\"]',\n };\n\n // The results from the called filter function.\n matchedResults = [];\n\n // What did the user search for?\n searchTerm = '';\n\n // What the user searched for as a lowercase.\n preppedSearchTerm = null;\n\n // The DOM nodes after the dropdown render.\n resultNodes = [];\n\n // Where does the user currently have focus?\n currentNode = null;\n\n // The current node for the view all link.\n currentViewAll = null;\n\n dataset = null;\n\n datasetSize = 0;\n\n // DOM nodes that persist.\n component = document.querySelector(this.selectors.component);\n toggle = this.component.querySelector(this.selectors.toggle);\n searchInput = this.component.querySelector(this.selectors.input);\n searchDropdown = this.component.querySelector(this.selectors.dropdown);\n clearSearchButton = this.component.querySelector(this.selectors.clearSearch);\n combobox = this.component.querySelector(this.selectors.combobox);\n $component = $(this.component);\n\n constructor() {\n // If we have a search input, try to get the value otherwise fallback.\n this.setSearchTerms(this.searchInput?.value ?? '');\n // Begin handling the base search component.\n this.registerClickHandlers();\n // Conditionally set up the input handler since we don't know exactly how we were called.\n if (this.searchInput !== null) {\n this.registerInputHandlers();\n this.registerChangeHandlers();\n }\n\n // If we have a search term, show the clear button.\n if (this.getSearchTerm() !== '') {\n this.clearSearchButton.classList.remove('d-none');\n }\n }\n\n /**\n * Stub out a required function.\n */\n fetchDataset() {\n throw new Error(`fetchDataset() must be implemented in ${this.constructor.name}`);\n }\n\n /**\n * Stub out a required function.\n * @param {Array} dataset\n */\n filterDataset(dataset) {\n throw new Error(`filterDataset(${dataset}) must be implemented in ${this.constructor.name}`);\n }\n\n /**\n * Stub out a required function.\n */\n filterMatchDataset() {\n throw new Error(`filterMatchDataset() must be implemented in ${this.constructor.name}`);\n }\n\n /**\n * Stub out a required function.\n */\n renderDropdown() {\n throw new Error(`renderDropdown() must be implemented in ${this.constructor.name}`);\n }\n\n /**\n * Stub out a required function.\n */\n componentSelector() {\n throw new Error(`componentSelector() must be implemented in ${this.constructor.name}`);\n }\n\n /**\n * Stub out a required function.\n */\n dropdownSelector() {\n throw new Error(`dropdownSelector() must be implemented in ${this.constructor.name}`);\n }\n\n /**\n * Stub out a required function.\n */\n triggerSelector() {\n throw new Error(`triggerSelector() must be implemented in ${this.constructor.name}`);\n }\n\n /**\n * Return the dataset that we will be searching upon.\n *\n * @returns {Promise}\n */\n async getDataset() {\n if (!this.dataset) {\n this.dataset = await this.fetchDataset();\n }\n this.datasetSize = this.dataset.length;\n return this.dataset;\n }\n\n /**\n * Return the size of the dataset.\n *\n * @returns {number}\n */\n getDatasetSize() {\n return this.datasetSize;\n }\n\n /**\n * Return the results of the filter upon the dataset.\n *\n * @returns {Array}\n */\n getMatchedResults() {\n return this.matchedResults;\n }\n\n /**\n * Given a filter has been run across the dataset, store the matched results.\n *\n * @param {Array} result\n */\n setMatchedResults(result) {\n this.matchedResults = result;\n }\n\n /**\n * Get the value that the user entered.\n *\n * @returns {string}\n */\n getSearchTerm() {\n return this.searchTerm;\n }\n\n /**\n * Get the transformed search value.\n *\n * @returns {string}\n */\n getPreppedSearchTerm() {\n return this.preppedSearchTerm;\n }\n\n /**\n * When a user searches for something, set our variable to manage it.\n *\n * @param {string} result\n */\n setSearchTerms(result) {\n this.searchTerm = result;\n this.preppedSearchTerm = result.toLowerCase();\n }\n\n /**\n * Return an object containing a handfull of dom nodes that we sometimes need the value of.\n *\n * @returns {object}\n */\n getHTMLElements() {\n this.updateNodes();\n return {\n searchDropdown: this.searchDropdown,\n currentViewAll: this.currentViewAll,\n searchInput: this.searchInput,\n clearSearchButton: this.clearSearchButton,\n trigger: this.component.querySelector(this.selectors.trigger),\n };\n }\n\n /**\n * When called, close the dropdown and reset the input field attributes.\n *\n * @param {Boolean} clear Conditionality clear the input box.\n */\n closeSearch(clear = false) {\n this.toggleDropdown();\n if (clear) {\n // Hide the \"clear\" search button search bar.\n this.clearSearchButton.classList.add('d-none');\n // Clear the entered search query in the search bar and hide the search results container.\n this.setSearchTerms('');\n this.searchInput.value = \"\";\n }\n }\n\n /**\n * Check whether search results are currently visible.\n *\n * @returns {Boolean}\n */\n searchResultsVisible() {\n const {searchDropdown} = this.getHTMLElements();\n // If a Node is not visible, then the offsetParent is null.\n return searchDropdown.offsetParent !== null;\n }\n\n /**\n * When called, update the dropdown fields.\n *\n * @param {Boolean} on Flag to toggle hiding or showing values.\n */\n toggleDropdown(on = false) {\n if (on) {\n $(this.toggle).dropdown('show');\n } else {\n $(this.toggle).dropdown('hide');\n }\n }\n\n /**\n * These class members change when a new result set is rendered. So update for fresh data.\n */\n updateNodes() {\n this.resultNodes = [...this.component.querySelectorAll(this.selectors.resultitems)];\n this.currentNode = this.resultNodes.find(r => r.id === document.activeElement.id);\n this.currentViewAll = this.component.querySelector(this.selectors.viewall);\n this.clearSearchButton = this.component.querySelector(this.selectors.clearSearch);\n this.searchInput = this.component.querySelector(this.selectors.input);\n this.searchDropdown = this.component.querySelector(this.selectors.dropdown);\n }\n\n /**\n * Register clickable event listeners.\n */\n registerClickHandlers() {\n // Register click events within the component.\n this.component.addEventListener('click', this.clickHandler.bind(this));\n }\n\n /**\n * Register change event listeners.\n */\n registerChangeHandlers() {\n const valueElement = this.component.querySelector(`#${this.combobox.dataset.inputElement}`);\n valueElement.addEventListener('change', this.changeHandler.bind(this));\n }\n\n /**\n * Register input event listener for the text input area.\n */\n registerInputHandlers() {\n // Register & handle the text input.\n this.searchInput.addEventListener('input', debounce(async() => {\n if (this.getSearchTerm() === this.searchInput.value && this.searchResultsVisible()) {\n window.console.warn(`Search term matches input value - skipping`);\n // The debounce canhappen multiple times quickly. GRrargh\n return;\n }\n this.setSearchTerms(this.searchInput.value);\n\n const pendingPromise = new Pending();\n if (this.getSearchTerm() === '') {\n this.toggleDropdown();\n this.clearSearchButton.classList.add('d-none');\n await this.filterrenderpipe();\n } else {\n this.clearSearchButton.classList.remove('d-none');\n await this.renderAndShow();\n }\n pendingPromise.resolve();\n }, 300, {pending: true}));\n }\n\n /**\n * Update any changeable nodes, filter and then render the result.\n *\n * @returns {Promise}\n */\n async filterrenderpipe() {\n this.updateNodes();\n this.setMatchedResults(await this.filterDataset(await this.getDataset()));\n this.filterMatchDataset();\n await this.renderDropdown();\n }\n\n /**\n * A combo method to take the matching fields and render out the results.\n *\n * @returns {Promise}\n */\n async renderAndShow() {\n // User has given something for us to filter against.\n this.setMatchedResults(await this.filterDataset(await this.getDataset()));\n await this.filterMatchDataset();\n // Replace the dropdown node contents and show the results.\n await this.renderDropdown();\n // Set the dropdown to open.\n this.toggleDropdown(true);\n }\n\n /**\n * The handler for when a user interacts with the component.\n *\n * @param {MouseEvent} e The triggering event that we are working with.\n */\n async clickHandler(e) {\n this.updateNodes();\n // The \"clear search\" button is triggered.\n if (e.target.closest(this.selectors.clearSearch)) {\n this.closeSearch(true);\n this.searchInput.focus();\n // Remove aria-activedescendant when the available options change.\n this.searchInput.removeAttribute('aria-activedescendant');\n }\n // User may have accidentally clicked off the dropdown and wants to reopen it.\n if (\n this.getSearchTerm() !== ''\n && !this.getHTMLElements().searchDropdown.classList.contains('show')\n && e.target.closest(this.selectors.input)\n ) {\n await this.renderAndShow();\n }\n }\n\n /**\n * The handler for when a user changes the value of the component (selects an option from the dropdown).\n *\n * @param {Event} e The change event.\n */\n // eslint-disable-next-line no-unused-vars\n changeHandler(e) {\n // Components may override this method to do something.\n }\n}\n"],"names":["constructor","component","this","componentSelector","trigger","triggerSelector","toggle","input","clearSearch","dropdown","dropdownSelector","resultitems","viewall","combobox","document","querySelector","selectors","setSearchTerms","searchInput","_this$searchInput","value","registerClickHandlers","registerInputHandlers","registerChangeHandlers","getSearchTerm","clearSearchButton","classList","remove","fetchDataset","Error","name","filterDataset","dataset","filterMatchDataset","renderDropdown","datasetSize","length","getDatasetSize","getMatchedResults","matchedResults","setMatchedResults","result","searchTerm","getPreppedSearchTerm","preppedSearchTerm","toLowerCase","getHTMLElements","updateNodes","searchDropdown","currentViewAll","closeSearch","clear","toggleDropdown","add","searchResultsVisible","offsetParent","resultNodes","querySelectorAll","currentNode","find","r","id","activeElement","addEventListener","clickHandler","bind","inputElement","changeHandler","async","window","console","warn","pendingPromise","Pending","filterrenderpipe","renderAndShow","resolve","pending","getDataset","e","target","closest","focus","removeAttribute","contains"],"mappings":"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"} \ No newline at end of file diff --git a/lib/amd/src/comboboxsearch/search_combobox.js b/lib/amd/src/comboboxsearch/search_combobox.js index 1f7a62a1c8b..920dfa55b83 100644 --- a/lib/amd/src/comboboxsearch/search_combobox.js +++ b/lib/amd/src/comboboxsearch/search_combobox.js @@ -80,6 +80,11 @@ export default class { this.registerInputHandlers(); this.registerChangeHandlers(); } + + // If we have a search term, show the clear button. + if (this.getSearchTerm() !== '') { + this.clearSearchButton.classList.remove('d-none'); + } } /**