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
This commit is contained in:
Stefan Kalscheuer 2020-11-10 15:30:12 +01:00
parent 547fc521d0
commit 68efc83273
3 changed files with 122 additions and 135 deletions

View File

@ -4,7 +4,7 @@
"browser": true "browser": true
}, },
"globals": { "globals": {
"sclivetickerAjax": "readonly", "scliveticker": "readonly",
"wp": "readonly" "wp": "readonly"
}, },
"extends": [ "extends": [

View File

@ -205,16 +205,6 @@ class SCLiveticker {
} else { } else {
$show_feed = 1 === self::$options['show_feed']; $show_feed = 1 === self::$options['show_feed'];
} }
$output = '<div class="wp-block-scliveticker-ticker';
if ( 1 === self::$options['enable_ajax'] ) {
$output .= ' sclt-ajax" '
. 'data-sclt-ticker="' . $ticker . '" '
. 'data-sclt-limit="' . $limit . '" '
. 'data-sclt-last="' . time();
}
$output .= '"><ul>';
$args = array( $args = array(
'post_type' => 'scliveticker_tick', 'post_type' => 'scliveticker_tick',
'posts_per_page' => $limit, 'posts_per_page' => $limit,
@ -229,12 +219,24 @@ class SCLiveticker {
$wp_query = new WP_Query( $args ); $wp_query = new WP_Query( $args );
$ticks = '';
$last = null;
while ( $wp_query->have_posts() ) { while ( $wp_query->have_posts() ) {
$wp_query->the_post(); $wp_query->the_post();
$output .= self::tick_html( get_the_time( 'd.m.Y H:i' ), get_the_title(), get_the_content() ); $ticks .= self::tick_html( get_the_time( 'd.m.Y H:i' ), get_the_title(), get_the_content() );
if ( is_null( $last ) ) {
$last = get_post_modified_time( 'Y-m-d\TH:i:s', true );
}
} }
$output .= '</ul></div>'; $output = '<div class="wp-block-scliveticker-ticker';
if ( 1 === self::$options['enable_ajax'] ) {
$output .= ' sclt-ajax" '
. 'data-sclt-ticker="' . $ticker . '" '
. 'data-sclt-limit="' . $limit . '" '
. 'data-sclt-last="' . $last;
}
$output .= '"><ul>' . $ticks . '</ul></div>';
// Show RSS feed link, if configured. // Show RSS feed link, if configured.
if ( $show_feed ) { if ( $show_feed ) {
@ -262,7 +264,7 @@ class SCLiveticker {
if ( self::$shortcode_present || self::$widget_present || self::block_present() ) { if ( self::$shortcode_present || self::$widget_present || self::block_present() ) {
wp_enqueue_script( wp_enqueue_script(
'scliveticker-js', 'scliveticker-js',
SCLIVETICKER_BASE . 'scripts/liveticker.min.js', SCLIVETICKER_BASE . 'scripts/liveticker.js',
array(), array(),
self::VERSION, self::VERSION,
true true
@ -271,10 +273,11 @@ class SCLiveticker {
// Add endpoint to script. // Add endpoint to script.
wp_localize_script( wp_localize_script(
'scliveticker-js', 'scliveticker-js',
'sclivetickerAjax', 'scliveticker',
array( array(
'ajax_url' => admin_url( 'admin-ajax.php' ), 'ajax_url' => admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( 'scliveticker_update-ticks' ), 'nonce' => wp_create_nonce( 'scliveticker_update-ticks' ),
'api' => rest_url(),
'poll_interval' => self::$options['poll_interval'] * 1000, 'poll_interval' => self::$options['poll_interval'] * 1000,
) )
); );
@ -435,9 +438,9 @@ class SCLiveticker {
*/ */
private static function tick_html( $time, $title, $content, $is_widget = false ) { private static function tick_html( $time, $title, $content, $is_widget = false ) {
return '<li class="sclt-tick">' return '<li class="sclt-tick">'
. '<p><span class="sclt-tick_time">' . esc_html( $time ) . '</span>' . '<span class="sclt-tick-time">' . esc_html( $time ) . '</span>'
. '<span class="sclt-tick-title">' . esc_html( $title ) . '</span></p>' . '<span class="sclt-tick-title">' . esc_html( $title ) . '</span>'
. '<p class="sclt-tick-content">' . $content . '</p></li>'; . '<div class="sclt-tick-content">' . $content . '</div></li>';
} }
/** /**

View File

@ -4,11 +4,9 @@
* @class * @class
*/ */
( function() { ( function() {
var ajaxURL = sclivetickerAjax.ajax_url; var apiURL;
var nonce = sclivetickerAjax.nonce; var pollInterval;
var pollInterval = sclivetickerAjax.poll_interval;
var ticker; var ticker;
var widgets;
/** /**
* Initialize iveticker JS component. * Initialize iveticker JS component.
@ -19,67 +17,42 @@
var updateNow = false; var updateNow = false;
// Opt out if AJAX pobject not present. // Opt out if AJAX pobject not present.
if ( 'undefined' === typeof sclivetickerAjax ) { if ( 'undefined' === typeof scliveticker ) {
return; return;
} }
// Extract AJAX settings. // Extract settings.
ajaxURL = sclivetickerAjax.ajax_url; apiURL = scliveticker.api + 'wp/v2/scliveticker_tick';
nonce = sclivetickerAjax.nonce; pollInterval = scliveticker.poll_interval;
pollInterval = sclivetickerAjax.poll_interval;
// Get ticker elements. // Get ticker elements.
ticker = [].map.call( ticker = [].map.call(
document.querySelectorAll( 'div.wp-block-scliveticker-ticker.sclt-ajax' ), document.querySelectorAll( 'div.wp-block-scliveticker-ticker.sclt-ajax' ),
function( elem ) { function( elem ) {
var list = elem.querySelector( 'ul' ); elem = parseElement( elem, false );
var last = Number( elem.getAttribute( 'data-sclt-last' ) ); if ( '0' === elem.lastPoll ) {
if ( ! list ) {
list = document.createElement( 'ul' );
elem.appendChild( list );
}
if ( 0 === last ) {
updateNow = true; updateNow = true;
} }
return elem;
return {
s: elem.getAttribute( 'data-sclt-ticker' ),
l: elem.getAttribute( 'data-sclt-limit' ),
t: last,
e: list,
};
} }
); );
// Get widget elements. // Get widget elements.
widgets = [].map.call( ticker.concat(
document.querySelectorAll( 'div.wp-widget-scliveticker-ticker.sclt-ajax' ), [].map.call(
function( elem ) { document.querySelectorAll( 'div.wp-widget-scliveticker-ticker.sclt-ajax' ),
var list = elem.querySelector( 'ul' ); function( elem ) {
var last = Number( elem.getAttribute( 'data-sclt-last' ) ); elem = parseElement( elem, true );
if ( 0 === elem.lastPoll ) {
if ( ! list ) { updateNow = true;
list = document.createElement( 'ul' ); }
elem.appendChild( list ); 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. // Trigger update, if necessary.
if ( ( 0 < ticker.length || widgets.length ) && 0 < pollInterval ) { if ( ( 0 < ticker.length ) && 0 < pollInterval ) {
if ( updateNow ) { if ( updateNow ) {
update(); update();
} else { } 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} * @return {void}
*/ */
var update = function() { var update = function() {
// Extract ticker-slug, limit and timestamp of last poll. // Iterate over available tickers.
var updateReq = 'action=sclt_update-ticks&_ajax_nonce=' + nonce; ticker.forEach(
var i, j; function( t ) {
var xhr = new XMLHttpRequest(); var xhr = new XMLHttpRequest();
var query = '?ticker=' + encodeURI( t.ticker ) +
for ( i = 0; i < ticker.length; i++ ) { '&limit=' + encodeURI( t.limit ) +
updateReq = updateReq + '&last=' + encodeURI( t.lastPoll );
'&update[' + i + '][s]=' + ticker[ i ].s + xhr.open( 'GET', apiURL + query, true );
'&update[' + i + '][l]=' + ticker[ i ].l + xhr.addEventListener(
'&update[' + i + '][t]=' + ticker[ i ].t; 'load',
} function() {
for ( j = 0; j < widgets.length; j++ ) { var updateResp;
updateReq = updateReq + try {
'&update[' + ( i + j ) + '][w]=' + widgets[ j ].w + updateResp = JSON.parse( this.responseText );
'&update[' + ( i + j ) + '][l]=' + widgets[ j ].l + if ( updateResp ) {
'&update[' + ( i + j ) + '][t]=' + widgets[ j ].t; updateResp.reverse();
} updateResp.forEach(
function( u ) {
// Issue AJAX request. addTick( t, u );
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 );
}
} }
); );
} }
); 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 ) { xhr.send();
// eslint-disable-next-line no-console
console.warn( 'Liveticker AJAX update failed, stopping automatic updates.' );
}
} }
}; );
xhr.send( updateReq );
}; };
/** /**
* Do actual update of HTML code. * Do actual update of HTML code.
* *
* @param {Object} t Ticker or Widget reference. * @param {Object} t Ticker or Widget reference.
* @param {number} t.l Limit of entries to display. * @param {Object} u Update entity.
* @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.
* @return {void} * @return {void}
*/ */
var updateHTML = function( t, u ) { var addTick = function( t, u ) {
// Parse new DOM-part. // Parse new DOM-part.
var n = document.createElement( 'ul' ); var li = document.createElement( 'li' );
n.innerHTML = u.h; 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. li.classList.add( cls );
while ( n.hasChildNodes() ) { time.classList.add( cls + '-time' );
t.e.prepend( n.lastChild ); 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. // Remove tail, if limit is set.
if ( 0 < t.l ) { if ( 0 < t.limit ) {
[].slice.call( t.e.getElementsByTagName( 'li' ), t.l ).forEach( [].slice.call( t.ticks.getElementsByTagName( 'li' ), t.limit ).forEach(
function( li ) { function( l ) {
li.remove(); l.remove();
} }
); );
} }