diff --git a/source/wp-content/themes/wporg-developer-2023/inc/autocomplete.php b/source/wp-content/themes/wporg-developer-2023/inc/autocomplete.php index 1b112e4ae..42f48c1bc 100644 --- a/source/wp-content/themes/wporg-developer-2023/inc/autocomplete.php +++ b/source/wp-content/themes/wporg-developer-2023/inc/autocomplete.php @@ -20,15 +20,25 @@ public function __construct() { * @access public */ public function init() { - - add_action( 'wp_ajax_autocomplete', array( $this, 'autocomplete_data_update' ) ); - add_action( 'wp_ajax_nopriv_autocomplete', array( $this, 'autocomplete_data_update' ) ); + add_action( + 'rest_api_init', + function () { + register_rest_route( + 'wporg-developer/v1', + '/autocomplete', + array( + 'methods' => 'POST', + 'callback' => array( $this, 'autocomplete_rest_handler' ), + 'permission_callback' => '__return_true', + ) + ); + } + ); // Enqueue scripts and styles. add_action( 'wp_enqueue_scripts', array( $this, 'scripts_and_styles' ), 11 ); } - /** * Enqueues scripts and styles. * @@ -45,89 +55,87 @@ public function scripts_and_styles() { 'awesomplete-css', get_stylesheet_directory_uri() . '/stylesheets/awesomplete.css', array(), - filemtime( dirname( __DIR__ ) . '/stylesheets/awesomplete.css' ) + filemtime( get_stylesheet_directory() . '/stylesheets/awesomplete.css' ) ); wp_enqueue_style( 'autocomplete-css', get_stylesheet_directory_uri() . '/stylesheets/autocomplete.css', array(), - filemtime( dirname( __DIR__ ) . '/stylesheets/autocomplete.css' ) + filemtime( get_stylesheet_directory() . '/stylesheets/autocomplete.css' ) ); - wp_register_script( - 'awesomplete', - get_stylesheet_directory_uri() . '/js/awesomplete.min.js', + wp_register_script_module( + '@wporg-developer/awesomplete', + get_stylesheet_directory_uri() . '/js/awesomplete.js', array(), - filemtime( dirname( __DIR__ ) . '/js/awesomplete.min.js' ), - true + filemtime( get_stylesheet_directory() . '/js/awesomplete.js' ) ); - wp_enqueue_script( 'awesomplete' ); - - wp_register_script( 'autocomplete', get_stylesheet_directory_uri() . '/js/autocomplete.js', array( 'awesomplete' ), filemtime( dirname( __DIR__ ) . '/js/autocomplete.js' ), true ); - wp_localize_script( - 'autocomplete', - 'autocomplete', - array( - 'ajaxurl' => admin_url( 'admin-ajax.php' ), - 'nonce' => wp_create_nonce( 'autocomplete_nonce' ), - 'post_type' => get_post_type(), - ) + wp_enqueue_script_module( + '@wporg-developer/autocomplete', + get_stylesheet_directory_uri() . '/js/autocomplete.js', + array( array( 'id' => '@wporg-developer/awesomplete', 'import' => 'dynamic' ) ), + filemtime( get_stylesheet_directory() . '/js/autocomplete.js' ) ); - wp_enqueue_script( 'autocomplete' ); + add_filter( + "script_module_data_@wporg-developer/autocomplete", + function ( $data ) { + return array_merge( $data, array( + 'endpoint_url' => rest_url('wporg-developer/v1/autocomplete'), + 'nonce' => wp_create_nonce( 'wporg/autocomplete' ), + ) ); + } + ); } + public function autocomplete_rest_handler( WP_REST_Request $request ) { + $req_json = $request->get_json_params(); - /** - * Handles AJAX updates for the autocomplete list. - * - * @access public - * - * @return string JSON data - */ - public function autocomplete_data_update() { + $nonce_ok = wp_verify_nonce( $req_json['nonce'], 'wporg/autocomplete' ); + if ( ! $nonce_ok ) { + return new WP_REST_Response( array( 'error' => __( 'Invalid nonce' ) ), 403 ); + } - check_ajax_referer( 'autocomplete_nonce', 'nonce' ); + // No search query. + $req_search = $req_json['search'] ?? ''; + if ( '' === $req_search ) { + return new WP_REST_Response( + array( 'error' => __( 'Missing or invalid "s" search.', 'wporg' ) ), + 400 + ); + } $parser_post_types = DevHub\get_parsed_post_types(); - $defaults = array( - 's' => '', - 'posts' => array(), - ); - if ( ! ( isset( $_POST['data'] ) && $_POST['data'] ) ) { - wp_send_json_error( $defaults ); + $req_post_types = $req_json['post_types']; + if ( ! is_array( $req_post_types ) ) { + $req_post_types = array(); + } else { + $req_post_types = array_intersect( $parser_post_types, $req_post_types ); } - // Parse the search form fields. - wp_parse_str( $_POST['data'], $form_data ); - $form_data = array_merge( $defaults, $form_data ); - - // No search query. - if ( empty( $form_data['s'] ) ) { - wp_send_json_error( $defaults ); + if ( array() === $req_post_types ) { + $req_post_types = $parser_post_types; } - $post_types = isset( $_POST['post_type'] ) && 'command' === $_POST['post_type'] ? - array( 'command' ) : - $parser_post_types; - $args = array( 'posts_per_page' => -1, - 'post_type' => $post_types, - 's' => $form_data['s'], + 'post_type' => $req_post_types, + 's' => $req_search, 'orderby' => '', 'search_orderby_title' => 1, 'order' => 'ASC', '_autocomplete_search' => true, ); - $search = get_posts( $args ); + $search_result = get_posts( $args ); - if ( ! empty( $search ) ) { + if ( empty( $search_result ) ) { + return new WP_REST_Response( null, 200 ); + } else { $post_types_function_like = array( 'wp-parser-function', 'wp-parser-method' ); - foreach ( $search as $post ) { + foreach ( $search_result as $post ) { $permalink = get_permalink( $post->ID ); $title = $post->post_title; @@ -139,13 +147,12 @@ public function autocomplete_data_update() { $title = 'class ' . $title . ' {}'; } - $form_data['posts'][ $title ] = $permalink; + $request_data['posts'][ $title ] = $permalink; } } - wp_send_json_success( $form_data ); + return new WP_REST_Response( $request_data, 200 ); } - } $autocomplete = new DevHub_Search_Form_Autocomplete(); diff --git a/source/wp-content/themes/wporg-developer-2023/js/autocomplete.js b/source/wp-content/themes/wporg-developer-2023/js/autocomplete.js index b99bd19a1..a8198cf90 100644 --- a/source/wp-content/themes/wporg-developer-2023/js/autocomplete.js +++ b/source/wp-content/themes/wporg-developer-2023/js/autocomplete.js @@ -5,126 +5,171 @@ * https://leaverou.github.io/awesomplete/ */ -( function( $ ) { +function init() { + /** @type {string} */ + const stringifiedData = document.getElementById( + 'wp-script-module-data-@wporg-developer/autocomplete' + )?.textContent; + + let data; + try { + data = JSON.parse( stringifiedData ); + } catch { + // No data from server, nothing to do. + return; + } - if ( typeof autocomplete === 'undefined' ) { + const form = document.querySelector( '#wporg-search .wp-block-search' ); + if ( ! form ) { return; } - var form = $( '#wporg-search .wp-block-search' ); - if ( ! form.length ) { + /** @type {HTMLInputElement} */ + const searchfield = form.querySelector( 'input[type="search"]' ); + if ( ! searchfield ) { return; } - var searchfield = $( 'input[type="search"]', form ), - processing = false, - search = '', - autocompleteResults = {}; - - var awesome = new Awesomplete( searchfield.get( 0 ), { - maxItems: 9999, - minChars: 3, - filter: function( text, input ) { - // Filter autocomplete matches - - // Full match - if ( Awesomplete.FILTER_CONTAINS( text, input ) ) { - // mark - return true; - } + let processing = false; + let search = ''; + let autocompleteResults = new Map(); - // Replace - _ and whitespace with a single space - var _text = Awesomplete.$.regExpEscape( text.trim().toLowerCase().replace( /[\_\-\s]+/g, ' ' ) ); - var _input = Awesomplete.$.regExpEscape( input.trim().toLowerCase().replace( /[\_\-\s]+/g, ' ' ) ); + let awesome; - // Matches with with single spaces between words - if ( Awesomplete.FILTER_CONTAINS( _text, _input ) ) { - return true; - } + async function createAwesomplete() { + const Awesomplete = ( await import( '@wporg-developer/awesomplete' ) ).Awesomplete; + return new Awesomplete( searchfield, { + maxItems: 9999, + minChars: 3, + filter: ( text, input ) => { + // Filter autocomplete matches - _input = _input.split( " " ); - var words = _input.length; + // Full match + if ( Awesomplete.FILTER_CONTAINS( text, input ) ) { + // mark + return true; + } - if ( 1 >= words ) { - return false; - } + // Replace - _ and whitespace with a single space + const _text = Awesomplete.$.regExpEscape( + text + .trim() + .toLowerCase() + .replace( /[\_\-\s]+/g, ' ' ) + ); + const _input = Awesomplete.$.regExpEscape( + input + .trim() + .toLowerCase() + .replace( /[\_\-\s]+/g, ' ' ) + ); + + // Matches with with single spaces between words + if ( Awesomplete.FILTER_CONTAINS( _text, _input ) ) { + return true; + } + + const words = _input.split( ' ' ).length; - // Partial matches - var partials = 0; - for ( i = 0; i < words; i++ ) { - if ( _text.indexOf( _input[ i ].trim() ) !== -1 ) { - partials++; + if ( 1 >= words ) { + return false; } - } - if ( partials === words ) { - return true; - } + // Partial matches + let partials = 0; + for ( let i = 0; i < words; i++ ) { + if ( _text.indexOf( _input[ i ].trim() ) !== -1 ) { + partials++; + } + } - return false; - }, - replace: function( text ) { - searchfield.val( text ); + if ( partials === words ) { + return true; + } - if ( text in autocompleteResults ) { - window.location = autocompleteResults[ text ]; - } - } - } ); + return false; + }, + replace: ( text ) => { + searchfield.val( text ); - // On input event for the search field. - searchfield.on( 'input.autocomplete', function( e ) { - - // Update the autocomlete list: - // if there are more than 2 characters - // and it's not already processing an Ajax request - if ( !processing && $( this ).val().trim().length > 2 ) { - search = $( this ).val(); - autocomplete_update(); - } - } ); + if ( autocompleteResults.has( text ) ) { + window.location = autocompleteResults.get( text ); + } + }, + } ); + } + // On input event for the search field. + searchfield.addEventListener( + 'input', + async function () { + const el = this; + // Update the autocomplete list: + // if there are more than 2 characters + // and it's not already processing an Ajax request + if ( ! processing && this.value.trim().length > 2 ) { + search = this.value; + await autocomplete_update( el ); + } + }, + { passive: true } + ); /** * Updates the autocomplete list */ - function autocomplete_update() { - - processing = true; + async function autocomplete_update( el ) { + if ( processing ) { + return; + } - var data = { - action: "autocomplete", - data: form.serialize(), - nonce: autocomplete.nonce, - post_type: autocomplete.post_type - }; + if ( ! awesome ) { + // When the autocomplete is created, the node is moved and looses focus. + // Restore it. + awesome = await createAwesomplete(); + el.focus(); + } - $.post( autocomplete.ajaxurl, data ) - .done( function( response ) { + processing = true; - if ( typeof response.success === 'undefined' ) { - return false; - } + const url = new URL( data.endpoint_url ); - if ( typeof response.data === 'undefined' ) { - return false; - } + const requestData = { + search: form.elements.namedItem( 's' )?.value ?? '', + post_types: [], + nonce: data.nonce, + }; + for ( const postTypeEl of form.elements.namedItem( 'post_type[]' ) ) { + if ( postTypeEl.checked ) { + requestData.post_types.push( postTypeEl.value ); + } + } - if ( response.success === true && Object.values( response.data.posts ).length ) { - autocompleteResults = response.data.posts; + /** @type {Response} */ + let response; + try { + response = await fetch( url, { + method: 'POST', + body: JSON.stringify( requestData ), + headers: { 'Content-Type': 'application/json' }, + } ); + if ( ! response.ok ) { + throw new Error( 'Bad response.' ); + } + response = await response.json(); + } finally { + processing = false; + } - // Update the autocomplete list - awesome.list = Object.keys( response.data.posts ); - } - } ) - .always( function() { - processing = false; + /** @type {Record|undefined} */ + const posts = response.posts; + if ( ! posts ) { + return; + } - // Check if the search was updated during processing - if ( search !== searchfield.val() ) { - searchfield.trigger( "input.autocomplete" ); - } - } ); + autocompleteResults = new Map( Object.entries( posts ) ); + awesome.list = Object.keys( posts ); } +} -} )( jQuery ); \ No newline at end of file +init(); diff --git a/source/wp-content/themes/wporg-developer-2023/js/awesomplete.js b/source/wp-content/themes/wporg-developer-2023/js/awesomplete.js index a0eda9caf..65f6f96f8 100644 --- a/source/wp-content/themes/wporg-developer-2023/js/awesomplete.js +++ b/source/wp-content/themes/wporg-developer-2023/js/awesomplete.js @@ -1,394 +1,352 @@ /** - * Simple, lightweight, usable local autocomplete library for modern browsers - * Because there weren’t enough autocomplete scripts in the world? Because I’m completely insane and have NIH syndrome? Probably both. :P - * @author Lea Verou http://leaverou.github.io/awesomplete - * MIT license + * Awesomplete module. + * + * Adapted from (MIT license): https://leaverou.github.io/awesomplete/ */ -(function () { - -var _ = function (input, o) { - var me = this; - - // Setup - - this.input = $(input); - this.input.setAttribute("autocomplete", "off"); - this.input.setAttribute("aria-autocomplete", "list"); - - o = o || {}; - - configure.call(this, { - minChars: 2, - maxItems: 10, - autoFirst: false, - filter: _.FILTER_CONTAINS, - sort: _.SORT_BYLENGTH, - item: function (text, input) { - var html = input === '' ? text : text.replace(RegExp($.regExpEscape(input.trim()), "gi"), "$&"); - return $.create("li", { - innerHTML: html, - "aria-selected": "false" - }); - }, - replace: function (text) { - this.input.value = text; - } - }, o); - - this.index = -1; - - // Create necessary elements - - this.container = $.create("div", { - className: "awesomplete", - around: input - }); - - this.ul = $.create("ul", { - hidden: "hidden", - inside: this.container - }); - - this.status = $.create("span", { - className: "visually-hidden", - role: "status", - "aria-live": "assertive", - "aria-relevant": "additions", - inside: this.container - }); - - // Bind events - - $.bind(this.input, { - "input": this.evaluate.bind(this), - "blur": this.close.bind(this), - "keydown": function(evt) { - var c = evt.keyCode; - - // If the dropdown `ul` is in view, then act on keydown for the following keys: - // Enter / Esc / Up / Down - if(me.opened) { - if (c === 13 && me.selected) { // Enter - evt.preventDefault(); - me.select(); - } - else if (c === 27) { // Esc - me.close(); - } - else if (c === 38 || c === 40) { // Down/Up arrow - evt.preventDefault(); - me[c === 38? "previous" : "next"](); - } - } - } - }); +const slice = Array.prototype.slice; - $.bind(this.input.form, {"submit": this.close.bind(this)}); +export class Awesomplete { + static all = []; - $.bind(this.ul, {"mousedown": function(evt) { - var li = evt.target; - - if (li !== this) { + static FILTER_CONTAINS( text, input ) { + return RegExp( Awesomplete.$.regExpEscape( input.trim() ), 'i' ).test( text ); + } - while (li && !/li/i.test(li.nodeName)) { - li = li.parentNode; - } + static FILTER_STARTSWITH( text, input ) { + return RegExp( '^' + Awesomplete.$.regExpEscape( input.trim() ), 'i' ).test( text ); + } - if (li && evt.button === 0) { // Only select on left click - evt.preventDefault(); - me.select(li, evt); - } + static SORT_BYLENGTH( a, b ) { + if ( a.length !== b.length ) { + return a.length - b.length; } - }}); - if (this.input.hasAttribute("list")) { - this.list = "#" + this.input.getAttribute("list"); - this.input.removeAttribute("list"); + return a < b ? -1 : 1; } - else { - this.list = this.input.getAttribute("data-list") || o.list || []; + + // eslint-disable-next-line id-length + static $( expr, con ) { + return typeof expr === 'string' ? ( con || document ).querySelector( expr ) : expr || null; } - _.all.push(this); -}; + // eslint-disable-next-line id-length + static $$( expr, con ) { + return slice.call( ( con || document ).querySelectorAll( expr ) ); + } -_.prototype = { - set list(list) { - if (Array.isArray(list)) { - this._list = list; + constructor( input, opts = {} ) { + // eslint-disable-next-line id-length + const me = this; + + // Setup + this.input = Awesomplete.$( input ); + this.input.setAttribute( 'autocomplete', 'off' ); + this.input.setAttribute( 'aria-autocomplete', 'list' ); + + this.configure( + { + minChars: 2, + maxItems: 10, + autoFirst: false, + filter: Awesomplete.FILTER_CONTAINS, + sort: Awesomplete.SORT_BYLENGTH, + item: ( text, itemInput ) => { + const html = + itemInput === '' + ? text + : text.replace( + RegExp( Awesomplete.$.regExpEscape( itemInput.trim() ), 'gi' ), + '$&' + ); + return Awesomplete.$.create( 'li', { + innerHTML: html, + 'aria-selected': 'false', + } ); + }, + replace: ( text ) => { + this.input.value = text; + }, + }, + opts + ); + + this.index = -1; + + // Create necessary elements + + this.container = Awesomplete.$.create( 'div', { + className: 'awesomplete', + around: input, + } ); + + // eslint-disable-next-line id-length + this.ul = Awesomplete.$.create( 'ul', { + hidden: 'hidden', + inside: this.container, + } ); + + this.status = Awesomplete.$.create( 'span', { + className: 'visually-hidden', + role: 'status', + 'aria-live': 'assertive', + 'aria-relevant': 'additions', + inside: this.container, + } ); + + // Bind events + + Awesomplete.$.bind( this.input, { + input: this.evaluate.bind( this ), + blur: this.close.bind( this ), + keydown: ( evt ) => { + const keyCode = evt.keyCode; + + // If the dropdown `ul` is in view, then act on keydown for the following keys: + // Enter / Esc / Up / Down + if ( this.opened ) { + if ( keyCode === 13 && this.selected ) { + // Enter (13) + evt.preventDefault(); + this.select(); + } else if ( keyCode === 27 ) { + // Escape (27) + this.close(); + } else if ( keyCode === 38 || keyCode === 40 ) { + // Up (38) / Down (40) + evt.preventDefault(); + this[ keyCode === 38 ? 'previous' : 'next' ](); + } + } + }, + } ); + + Awesomplete.$.bind( this.input.form, { submit: this.close.bind( this ) } ); + + Awesomplete.$.bind( this.ul, { + mousedown( evt ) { + // eslint-disable-next-line id-length + let li = evt.target; + + if ( li !== this ) { + while ( li && ! /li/i.test( li.nodeName ) ) { + li = li.parentNode; + } + + if ( li && evt.button === 0 ) { + // Only select on left click + evt.preventDefault(); + me.select( li, evt ); + } + } + }, + } ); + + if ( this.input.hasAttribute( 'list' ) ) { + this.list = '#' + this.input.getAttribute( 'list' ); + this.input.removeAttribute( 'list' ); + } else { + this.list = this.input.getAttribute( 'data-list' ) || opts.list || []; } - else if (typeof list === "string" && list.indexOf(",") > -1) { - this._list = list.split(/\s*,\s*/); + + Awesomplete.all.push( this ); + } + + configure( properties, opts ) { + for ( const i in properties ) { + const initial = properties[ i ]; + const attrValue = this.input.getAttribute( 'data-' + i.toLowerCase() ); + + if ( typeof initial === 'number' ) { + this[ i ] = parseInt( attrValue ); + } else if ( initial === false ) { + // Boolean options must be false by default anyway + this[ i ] = attrValue !== null; + } else if ( initial instanceof Function ) { + this[ i ] = null; + } else { + this[ i ] = attrValue; + } + + if ( ! this[ i ] && this[ i ] !== 0 ) { + this[ i ] = i in opts ? opts[ i ] : initial; + } } - else { // Element or CSS selector - list = $(list); + } - if (list && list.children) { - this._list = slice.apply(list.children).map(function (el) { - return el.textContent.trim(); - }); + set list( list ) { + if ( Array.isArray( list ) ) { + this._list = list; + } else if ( typeof list === 'string' && list.indexOf( ',' ) > -1 ) { + this._list = list.split( /\s*,\s*/ ); + } else { + // Element or CSS selector + list = Awesomplete.$( list ); + + if ( list && list.children ) { + this._list = slice.apply( list.children ).map( ( element ) => element.textContent.trim() ); } } - if (document.activeElement === this.input) { + if ( document.activeElement === this.input ) { this.evaluate(); } - }, + } get selected() { return this.index > -1; - }, + } get opened() { - return this.ul && this.ul.getAttribute("hidden") == null; - }, + return this.ul && this.ul.getAttribute( 'hidden' ) == null; + } - close: function () { - this.ul.setAttribute("hidden", ""); + close() { + this.ul.setAttribute( 'hidden', '' ); this.index = -1; - $.fire(this.input, "awesomplete-close"); - }, + Awesomplete.$.fire( this.input, 'awesomplete-close' ); + } - open: function () { - this.ul.removeAttribute("hidden"); + open() { + this.ul.removeAttribute( 'hidden' ); - if (this.autoFirst && this.index === -1) { - this.goto(0); + if ( this.autoFirst && this.index === -1 ) { + this.goto( 0 ); } - $.fire(this.input, "awesomplete-open"); - }, + Awesomplete.$.fire( this.input, 'awesomplete-open' ); + } - next: function () { - var count = this.ul.children.length; + next() { + const count = this.ul.children.length; - this.goto(this.index < count - 1? this.index + 1 : -1); - }, + this.goto( this.index < count - 1 ? this.index + 1 : -1 ); + } - previous: function () { - var count = this.ul.children.length; + previous() { + const count = this.ul.children.length; - this.goto(this.selected? this.index - 1 : count - 1); - }, + this.goto( this.selected ? this.index - 1 : count - 1 ); + } // Should not be used, highlights specific item without any checks! - goto: function (i) { - var lis = this.ul.children; + goto( i ) { + const lis = this.ul.children; - if (this.selected) { - lis[this.index].setAttribute("aria-selected", "false"); + if ( this.selected ) { + lis[ this.index ].setAttribute( 'aria-selected', 'false' ); } this.index = i; - if (i > -1 && lis.length > 0) { - lis[i].setAttribute("aria-selected", "true"); - this.status.textContent = lis[i].textContent; + if ( i > -1 && lis.length > 0 ) { + lis[ i ].setAttribute( 'aria-selected', 'true' ); + this.status.textContent = lis[ i ].textContent; } - $.fire(this.input, "awesomplete-highlight"); - }, + Awesomplete.$.fire( this.input, 'awesomplete-highlight' ); + } - select: function (selected, originalEvent) { - selected = selected || this.ul.children[this.index]; + select( selected, originalEvent ) { + selected = selected || this.ul.children[ this.index ]; - if (selected) { - var prevented; + if ( selected ) { + let prevented; - $.fire(this.input, "awesomplete-select", { + Awesomplete.$.fire( this.input, 'awesomplete-select', { text: selected.textContent, preventDefault: function () { prevented = true; }, - originalEvent: originalEvent - }); + originalEvent: originalEvent, + } ); - if (!prevented) { - this.replace(selected.textContent); + if ( ! prevented ) { + this.replace( selected.textContent ); this.close(); - $.fire(this.input, "awesomplete-selectcomplete"); + Awesomplete.$.fire( this.input, 'awesomplete-selectcomplete' ); } } - }, + } - evaluate: function() { - var me = this; - var value = this.input.value; + evaluate() { + const value = this.input.value; - if (value.length >= this.minChars && this._list.length > 0) { + if ( value.length >= this.minChars && this._list.length > 0 ) { this.index = -1; // Populate list with options that match - this.ul.innerHTML = ""; + this.ul.innerHTML = ''; this._list - .filter(function(item) { - return me.filter(item, value); - }) - .sort(this.sort) - .every(function(text, i) { - me.ul.appendChild(me.item(text, value)); + .filter( ( item ) => { + return this.filter( item, value ); + } ) + .sort( this.sort ) + .every( ( text, i ) => { + this.ul.appendChild( this.item( text, value ) ); - return i < me.maxItems - 1; - }); + return i < this.maxItems - 1; + } ); - if (this.ul.children.length === 0) { + if ( this.ul.children.length === 0 ) { this.close(); } else { this.open(); } - } - else { + } else { this.close(); } } -}; - -// Static methods/properties - -_.all = []; - -_.FILTER_CONTAINS = function (text, input) { - return RegExp($.regExpEscape(input.trim()), "i").test(text); -}; - -_.FILTER_STARTSWITH = function (text, input) { - return RegExp("^" + $.regExpEscape(input.trim()), "i").test(text); -}; - -_.SORT_BYLENGTH = function (a, b) { - if (a.length !== b.length) { - return a.length - b.length; - } - - return a < b? -1 : 1; -}; - -// Private functions - -function configure(properties, o) { - for (var i in properties) { - var initial = properties[i], - attrValue = this.input.getAttribute("data-" + i.toLowerCase()); - - if (typeof initial === "number") { - this[i] = parseInt(attrValue); - } - else if (initial === false) { // Boolean options must be false by default anyway - this[i] = attrValue !== null; - } - else if (initial instanceof Function) { - this[i] = null; - } - else { - this[i] = attrValue; - } - - if (!this[i] && this[i] !== 0) { - this[i] = (i in o)? o[i] : initial; - } - } -} - -// Helpers - -var slice = Array.prototype.slice; - -function $(expr, con) { - return typeof expr === "string"? (con || document).querySelector(expr) : expr || null; -} - -function $$(expr, con) { - return slice.call((con || document).querySelectorAll(expr)); } -$.create = function(tag, o) { - var element = document.createElement(tag); - - for (var i in o) { - var val = o[i]; - - if (i === "inside") { - $(val).appendChild(element); - } - else if (i === "around") { - var ref = $(val); - ref.parentNode.insertBefore(element, ref); - element.appendChild(ref); - } - else if (i in element) { - element[i] = val; - } - else { - element.setAttribute(i, val); +Awesomplete.$.create = function ( tag, opts ) { + const element = document.createElement( tag ); + + for ( const i in opts ) { + const val = opts[ i ]; + + if ( i === 'inside' ) { + Awesomplete.$( val ).appendChild( element ); + } else if ( i === 'around' ) { + const ref = Awesomplete.$( val ); + ref.parentNode.insertBefore( element, ref ); + element.appendChild( ref ); + } else if ( i in element ) { + element[ i ] = val; + } else { + element.setAttribute( i, val ); } } return element; }; -$.bind = function(element, o) { - if (element) { - for (var event in o) { - var callback = o[event]; +Awesomplete.$.bind = function ( element, opts ) { + if ( element ) { + for ( const event in opts ) { + const callback = opts[ event ]; - event.split(/\s+/).forEach(function (event) { - element.addEventListener(event, callback); - }); + event.split( /\s+/ ).forEach( function ( eventName ) { + element.addEventListener( eventName, callback ); + } ); } } }; -$.fire = function(target, type, properties) { - var evt = document.createEvent("HTMLEvents"); +Awesomplete.$.fire = function ( target, type, properties ) { + const evt = document.createEvent( 'HTMLEvents' ); - evt.initEvent(type, true, true ); + // @todo Deprecated: replace. @see https://developer.mozilla.org/en-US/docs/Web/API/Event/initEvent + evt.initEvent( type, true, true ); - for (var j in properties) { - evt[j] = properties[j]; + for ( const prop in properties ) { + evt[ prop ] = properties[ prop ]; } - target.dispatchEvent(evt); + target.dispatchEvent( evt ); }; -$.regExpEscape = function (s) { - return s.replace(/[-\\^$*+?.()|[\]{}]/g, "\\$&"); -} - -// Initialization - -function init() { - $$("input.awesomplete").forEach(function (input) { - new _(input); - }); -} - -// Are we in a browser? Check for Document constructor -if (typeof Document !== "undefined") { - // DOM already loaded? - if (document.readyState !== "loading") { - init(); - } - else { - // Wait for it - document.addEventListener("DOMContentLoaded", init); - } -} - -_.$ = $; -_.$$ = $$; - -// Make sure to export Awesomplete on self when in a browser -if (typeof self !== "undefined") { - self.Awesomplete = _; -} - -// Expose Awesomplete as a CJS module -if (typeof module === "object" && module.exports) { - module.exports = _; -} - -return _; - -}()); +Awesomplete.$.regExpEscape = function ( str ) { + return str.replace( /[-\\^$*+?.()|[\]{}]/g, '\\$&' ); +};