From 68efc832737e76c01cf2a74573842e362f1d3670 Mon Sep 17 00:00:00 2001 From: Stefan Kalscheuer Date: Tue, 10 Nov 2020 15:30:12 +0100 Subject: [PATCH] refactor frontend to use WP REST API instead of WP AJAX Query new ticks using GET request to the REST API. Also we now trigger one request per ticker, if more than one is available on the same page. HTML markup (list element, content container) is generated client side, s.t. we can work on the generic response data model --- .eslintrc.json | 2 +- includes/class-scliveticker.php | 37 +++--- scripts/liveticker.js | 218 +++++++++++++++----------------- 3 files changed, 122 insertions(+), 135 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 1f13912..32330f8 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -4,7 +4,7 @@ "browser": true }, "globals": { - "sclivetickerAjax": "readonly", + "scliveticker": "readonly", "wp": "readonly" }, "extends": [ diff --git a/includes/class-scliveticker.php b/includes/class-scliveticker.php index 109e4e7..7ef9e11 100644 --- a/includes/class-scliveticker.php +++ b/includes/class-scliveticker.php @@ -205,16 +205,6 @@ class SCLiveticker { } else { $show_feed = 1 === self::$options['show_feed']; } - - $output = '
'; + $output = '
'; // Show RSS feed link, if configured. if ( $show_feed ) { @@ -262,7 +264,7 @@ class SCLiveticker { if ( self::$shortcode_present || self::$widget_present || self::block_present() ) { wp_enqueue_script( 'scliveticker-js', - SCLIVETICKER_BASE . 'scripts/liveticker.min.js', + SCLIVETICKER_BASE . 'scripts/liveticker.js', array(), self::VERSION, true @@ -271,10 +273,11 @@ class SCLiveticker { // Add endpoint to script. wp_localize_script( 'scliveticker-js', - 'sclivetickerAjax', + 'scliveticker', array( 'ajax_url' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( 'scliveticker_update-ticks' ), + 'api' => rest_url(), 'poll_interval' => self::$options['poll_interval'] * 1000, ) ); @@ -435,9 +438,9 @@ class SCLiveticker { */ private static function tick_html( $time, $title, $content, $is_widget = false ) { return '
  • ' - . '

    ' . esc_html( $time ) . '' - . '' . esc_html( $title ) . '

    ' - . '

    ' . $content . '

  • '; + . '' . esc_html( $time ) . '' + . '' . esc_html( $title ) . '' + . '
    ' . $content . '
    '; } /** diff --git a/scripts/liveticker.js b/scripts/liveticker.js index 9f2eebe..629873b 100644 --- a/scripts/liveticker.js +++ b/scripts/liveticker.js @@ -4,11 +4,9 @@ * @class */ ( function() { - var ajaxURL = sclivetickerAjax.ajax_url; - var nonce = sclivetickerAjax.nonce; - var pollInterval = sclivetickerAjax.poll_interval; + var apiURL; + var pollInterval; var ticker; - var widgets; /** * Initialize iveticker JS component. @@ -19,67 +17,42 @@ var updateNow = false; // Opt out if AJAX pobject not present. - if ( 'undefined' === typeof sclivetickerAjax ) { + if ( 'undefined' === typeof scliveticker ) { return; } - // Extract AJAX settings. - ajaxURL = sclivetickerAjax.ajax_url; - nonce = sclivetickerAjax.nonce; - pollInterval = sclivetickerAjax.poll_interval; + // Extract settings. + apiURL = scliveticker.api + 'wp/v2/scliveticker_tick'; + pollInterval = scliveticker.poll_interval; // Get ticker elements. ticker = [].map.call( document.querySelectorAll( 'div.wp-block-scliveticker-ticker.sclt-ajax' ), function( elem ) { - var list = elem.querySelector( 'ul' ); - var last = Number( elem.getAttribute( 'data-sclt-last' ) ); - - if ( ! list ) { - list = document.createElement( 'ul' ); - elem.appendChild( list ); - } - - if ( 0 === last ) { + elem = parseElement( elem, false ); + if ( '0' === elem.lastPoll ) { updateNow = true; } - - return { - s: elem.getAttribute( 'data-sclt-ticker' ), - l: elem.getAttribute( 'data-sclt-limit' ), - t: last, - e: list, - }; + return elem; } ); // Get widget elements. - widgets = [].map.call( - document.querySelectorAll( 'div.wp-widget-scliveticker-ticker.sclt-ajax' ), - function( elem ) { - var list = elem.querySelector( 'ul' ); - var last = Number( elem.getAttribute( 'data-sclt-last' ) ); - - if ( ! list ) { - list = document.createElement( 'ul' ); - elem.appendChild( list ); + ticker.concat( + [].map.call( + document.querySelectorAll( 'div.wp-widget-scliveticker-ticker.sclt-ajax' ), + function( elem ) { + elem = parseElement( elem, true ); + if ( 0 === elem.lastPoll ) { + updateNow = true; + } + return elem; } - - if ( 0 === last ) { - updateNow = true; - } - - return { - w: elem.getAttribute( 'data-sclt-ticker' ), - l: elem.getAttribute( 'data-sclt-limit' ), - t: last, - e: list, - }; - } + ) ); // Trigger update, if necessary. - if ( ( 0 < ticker.length || widgets.length ) && 0 < pollInterval ) { + if ( ( 0 < ticker.length ) && 0 < pollInterval ) { if ( updateNow ) { update(); } else { @@ -89,97 +62,108 @@ }; /** - * Update liveticker on current page via AJAX call. + * Parse an HTML element containing a liveticker. + * + * @param {HTMLElement} elem The element. + * @param {boolean} widget Is the element a widget? + * @return {{ticker: string, lastPoll: number, ticks: any, limit: string, isWidget: *}} Ticker descriptor object. + */ + var parseElement = function( elem, widget ) { + var list = elem.querySelector( 'ul' ); + var last = elem.getAttribute( 'data-sclt-last' ); + + if ( ! list ) { + list = document.createElement( 'ul' ); + elem.appendChild( list ); + } + + return { + ticker: elem.getAttribute( 'data-sclt-ticker' ), + limit: elem.getAttribute( 'data-sclt-limit' ), + lastPoll: last, + ticks: list, + isWidget: widget, + }; + }; + + /** + * Update liveticker on current page via REST API call. * * @return {void} */ var update = function() { - // Extract ticker-slug, limit and timestamp of last poll. - var updateReq = 'action=sclt_update-ticks&_ajax_nonce=' + nonce; - var i, j; - var xhr = new XMLHttpRequest(); - - for ( i = 0; i < ticker.length; i++ ) { - updateReq = updateReq + - '&update[' + i + '][s]=' + ticker[ i ].s + - '&update[' + i + '][l]=' + ticker[ i ].l + - '&update[' + i + '][t]=' + ticker[ i ].t; - } - for ( j = 0; j < widgets.length; j++ ) { - updateReq = updateReq + - '&update[' + ( i + j ) + '][w]=' + widgets[ j ].w + - '&update[' + ( i + j ) + '][l]=' + widgets[ j ].l + - '&update[' + ( i + j ) + '][t]=' + widgets[ j ].t; - } - - // Issue AJAX request. - xhr.open( 'POST', ajaxURL, true ); - xhr.setRequestHeader( 'Content-Type', 'application/x-www-form-urlencoded;' ); - xhr.onreadystatechange = function() { - var updateResp; - if ( XMLHttpRequest.DONE === this.readyState && 200 === this.status ) { - try { - updateResp = JSON.parse( this.responseText ); - if ( updateResp ) { - updateResp.forEach( - function( u ) { - ticker.forEach( - function( t ) { - if ( t.s === u.s ) { - t.t = u.t; // Update last poll timestamp. - updateHTML( t, u ); // Update HTML markup. - } - } - ); - widgets.forEach( - function( t ) { - if ( t.w === u.w ) { - t.t = u.t; - updateHTML( t, u ); - } + // Iterate over available tickers. + ticker.forEach( + function( t ) { + var xhr = new XMLHttpRequest(); + var query = '?ticker=' + encodeURI( t.ticker ) + + '&limit=' + encodeURI( t.limit ) + + '&last=' + encodeURI( t.lastPoll ); + xhr.open( 'GET', apiURL + query, true ); + xhr.addEventListener( + 'load', + function() { + var updateResp; + try { + updateResp = JSON.parse( this.responseText ); + if ( updateResp ) { + updateResp.reverse(); + updateResp.forEach( + function( u ) { + addTick( t, u ); } ); } - ); + setTimeout( update, pollInterval ); // Re-trigger update. + } catch ( e ) { + // eslint-disable-next-line no-console + console.warn( 'Liveticker AJAX update failed, stopping automatic updates.' ); + } } - setTimeout( update, pollInterval ); // Re-trigger update. - } catch ( e ) { - // eslint-disable-next-line no-console - console.warn( 'Liveticker AJAX update failed, stopping automatic updates.' ); - } + ); + xhr.send(); } - }; - xhr.send( updateReq ); + ); }; /** * Do actual update of HTML code. * - * @param {Object} t Ticker or Widget reference. - * @param {number} t.l Limit of entries to display. - * @param {HTMLElement} t.e HTML element of the ticker/widget. - * @param {Object} u Update entity. - * @param {string} u.h HTML code to append. - * @param {number} u.t Timetsamp of last update. + * @param {Object} t Ticker or Widget reference. + * @param {Object} u Update entity. * @return {void} */ - var updateHTML = function( t, u ) { + var addTick = function( t, u ) { // Parse new DOM-part. - var n = document.createElement( 'ul' ); - n.innerHTML = u.h; + var li = document.createElement( 'li' ); + var time = document.createElement( 'span' ); + var title = document.createElement( 'span' ); + var content = document.createElement( 'div' ); + var cls = t.isWidget ? 'sclt-widget' : 'sclt-tick'; - // Prepend new ticks to container. - while ( n.hasChildNodes() ) { - t.e.prepend( n.lastChild ); - } + li.classList.add( cls ); + time.classList.add( cls + '-time' ); + time.innerText = u.modified_rendered; + title.classList.add( cls + '-title' ); + title.innerText = u.title.rendered; + content.classList.add( cls + '-content' ); + content.innerHTML = u.content.rendered; + li.appendChild( time ); + li.appendChild( title ); + li.appendChild( content ); - t.e.parentNode.setAttribute( 'data-sclt-last', u.t ); + // Prepend new tick to container. + t.ticks.prepend( li ); + + // Update last poll time. + t.lastPoll = u.date_gmt; + t.ticks.parentNode.setAttribute( 'data-sclt-last', u.date_gmt ); // Remove tail, if limit is set. - if ( 0 < t.l ) { - [].slice.call( t.e.getElementsByTagName( 'li' ), t.l ).forEach( - function( li ) { - li.remove(); + if ( 0 < t.limit ) { + [].slice.call( t.ticks.getElementsByTagName( 'li' ), t.limit ).forEach( + function( l ) { + l.remove(); } ); }