jst_timer.module in Javascript Timer 8
Same filename and directory in other branches
Default widget implementation for an up and down timer. This widget does not use hook_ctwidget as it is always included.
File
widgets/jst_timer.moduleView source
<?php
/**
* @file
* Default widget implementation for an up and down timer.
* This widget does not use hook_ctwidget as it is always included.
*/
use Drupal\Core\Form\FormStateInterface;
/**
* Implementation of hook_jstwidget().
*
* @returns stdClass
* Keys include: ->name, ->theme_function, ->js_name, and ->js_code.
*/
function jst_timer_jstwidget() {
$ret = new stdClass();
$ret->name = 'jst_timer';
$ret->label = t('Countdown timer');
$ret->settings = array(
'dir' => 'down',
'format_txt' => '',
);
$ret->theme_function = 'jst_timer_show';
$ret->js_name = 'Drupal.jstimer.jst_timer';
$timer_formats = jst_timer_get_js_formats();
// clean php variables for javascript injection
$timer_complete_message = "'" . jstimer_clean_for_javascript(\Drupal::config('jstimer.settings')
->get('jst_timer_complete_message')) . "'";
$timer_complete_alert_message = "'" . jstimer_clean_for_javascript(\Drupal::config('jstimer.settings')
->get('jst_timer_complete_alert_message')) . "'";
$highlight = "'" . jstimer_clean_for_javascript(\Drupal::config('jstimer.settings')
->get('jstimer_highlight')) . "'";
$highlight_threshold = "'" . jstimer_clean_for_javascript(\Drupal::config('jstimer.settings')
->get('jstimer_highlight_threshold')) . "'";
$highlight_down = "'" . jstimer_clean_for_javascript(\Drupal::config('jstimer.settings')
->get('jstimer_highlight_down')) . "'";
$highlight_up = "'" . jstimer_clean_for_javascript(\Drupal::config('jstimer.settings')
->get('jstimer_highlight_up')) . "'";
$redirect_path = "'" . \Drupal::config('jstimer.settings')
->get('jst_timer_redirect_path') . "'";
$redirect_delay = "'" . \Drupal::config('jstimer.settings')
->get('jst_timer_redirect_delay') . "'";
$ret->js_code = <<<JAVASCRIPT_CODE
/*
* Timer widget
*/
Drupal.jstimer.formats = {<span class="php-variable">$timer_formats</span>};
Drupal.jstimer.jst_timer = function() {
this.selector = ".jst_timer";
this.attach = function() {
jQuery(this.selector).each(
function(i) { // i is the position in the each fieldset
var t = new Drupal.jstimer.jst_timer_item(jQuery(this));
if ( t.parse_microformat_success == 1 ) {
Drupal.jstimer.timer_stack[Drupal.jstimer.timer_stack.length] = t;
}
}
);
}
}
Drupal.jstimer.jst_timer_item = function(ele) {
// class methods first so you can use them in the constructor.
this.loadProps = function() {
for (var prop in this.props) {
var prop_selector = "span[class="+prop+"]";
if ( this.element.children(prop_selector).length > 0 ) {
this.props[prop] = this.element.children(prop_selector).html();
}
}
if ( String(this.props['format_txt']).match("'") ) {
this.props['format_txt'] = "<span style=\\"color:red;\\">Format may not contain single quotes(').</span>";
}
// format_txt overrides format_num.
if ( this.props['format_txt'] != "" ) {
this.outformat = this.props['format_txt'];
} else {
if (Drupal.jstimer.formats[this.props['format_num']] != undefined) {
this.outformat = Drupal.jstimer.formats[this.props['format_num']];
}
else {
this.outformat = Drupal.jstimer.formats[0];
}
}
}
this.parse_microformat = function() {
// ajax calls re-run autoattach, make sure the selector is gone.
if ( this.element.hasClass("jst_timer") ) {
this.element.removeClass("jst_timer")
}
// If there is an interval, always use it.
if ( this.props['interval'] != "" ) {
var interval_val = parseInt(this.props['interval']);
var date = new Date();
this.to_date = date;
this.to_date.setTime(date.getTime() + (interval_val*1000));
} else {
if ( this.props['datetime'] == "" ) {
this.parse_microformat_success = 0;
throw new Object({name:"NoDate",message:"Javascript Timer: Span with class=datetime not found within the timer span."});
}
var date = new Date();
try {
date.jstimer_set_iso8601_date(this.props['datetime']);
}
catch(e) {
throw(e);
}
this.to_date = date;
if ( this.props['current_server_time'] != "" ) {
// this is a feedback time from the server to correct for small server-client time differences.
// not used for normal block and node timers.
var date_server = new Date();
date_server.jstimer_set_iso8601_date(this.props['current_server_time']);
var date_client = new Date();
var adj = date_client.getTime() - date_server.getTime();
// adjust target date to clients domain
this.to_date.setTime(this.to_date.getTime() + adj);
}
}
this.parse_microformat_success = 1;
}
this.update = function() {
var now_date = new Date();
var duration = this.get_duration(now_date, this.to_date);
// If counting down and timer is completed, set timer complete statement, check for redirect, and end.
if ( this.props['dir'] == "down" && duration.sign > 0 ) {
// Set the timer complete statement.
if (this.props['timer_complete_message'] != '') {
this.element.html(this.props['timer_complete_message'] + '');
// Clear message as we don't need to reset it again.
this.props['timer_complete_message'] = '';
}
// If there is a complete message, alert it.
if (this.props['tc_msg'] != '') {
alert(this.props['tc_msg']);
// Clear alert msg as we don't need to alert it again.
this.props['tc_msg'] = '';
}
// If there is a redirect url and delay has been met, redirect.
if (this.props['tc_redir'] != '' && (duration.diff >= this.props['tc_redir_delay'])) {
if (this.props['tc_redir'].match('<reload>')) {
window.location.reload(true);
}
else {
window.location = this.props['tc_redir'];
}
return false;
}
// Timer is completed
return true;
}
var outhtml = new String(this.outformat);
// try to handle counts with units first, use a try block because Drupal.formatPlural breaks javascript sometimes
try {
outhtml = outhtml.replace(/%years% years/, Drupal.formatPlural(duration.years, "1 year", "@count years"));
outhtml = outhtml.replace(/%ydays% days/, Drupal.formatPlural(duration.days, "1 day", "@count days"));
outhtml = outhtml.replace(/%days% days/, Drupal.formatPlural(duration.tot_days, "1 day", "@count days"));
outhtml = outhtml.replace(/%hours% hours/, Drupal.formatPlural(duration.hours, "1 hour", "@count hours"));
outhtml = outhtml.replace(/%mins% minutes/, Drupal.formatPlural(duration.minutes, "1 minute", "@count minutes"));
outhtml = outhtml.replace(/%secs% seconds/, Drupal.formatPlural(duration.seconds, "1 second", "@count seconds"));
outhtml = outhtml.replace(/%months% months/, Drupal.formatPlural(duration.months, "1 month", "@count months"));
outhtml = outhtml.replace(/%tot_months% months/, Drupal.formatPlural(duration.tot_months, "1 month", "@count months"));
outhtml = outhtml.replace(/%tot_hours% hours/, Drupal.formatPlural(duration.tot_months, "1 hour", "@count hours"));
outhtml = outhtml.replace(/%tot_mins% minutes/, Drupal.formatPlural(duration.tot_mins, "1 minute", "@count minutes"));
outhtml = outhtml.replace(/%tot_hours% seconds/, Drupal.formatPlural(duration.tot_secs, "1 second", "@count seconds"));
}
catch(e){
// suppress errors
}
//handle counts without units
outhtml = outhtml.replace(/%years%/, duration.years);
outhtml = outhtml.replace(/%ydays%/, duration.days);
outhtml = outhtml.replace(/%days%/, duration.tot_days);
outhtml = outhtml.replace(/%hours%/, LZ(duration.hours));
outhtml = outhtml.replace(/%mins%/, LZ(duration.minutes));
outhtml = outhtml.replace(/%secs%/, LZ(duration.seconds));
outhtml = outhtml.replace(/%hours_nopad%/, duration.hours);
outhtml = outhtml.replace(/%mins_nopad%/, duration.minutes);
outhtml = outhtml.replace(/%secs_nopad%/, duration.seconds);
outhtml = outhtml.replace(/%sign%/, duration.sign < 0 ? '-' : '+');
outhtml = outhtml.replace(/%months%/, duration.months);
outhtml = outhtml.replace(/%tot_months%/, duration.tot_months);
outhtml = outhtml.replace(/%tot_hours%/, duration.tot_hours);
outhtml = outhtml.replace(/%tot_mins%/, duration.tot_mins);
outhtml = outhtml.replace(/%tot_secs%/, duration.tot_secs);
// Proximity styling
if (this.props['dir'] == "down"
&& {<span class="php-variable">$highlight_down</span>} =="1"
&& (duration.diff <= (this.props['threshold'] * 60))
&& this.props['highlight'][0]) {
this.element.html('<span ' + this.props['highlight'][0] + '=' + this.props['highlight'][1] + '>' + outhtml + '</span>');
}
else if (this.props['dir'] == "up"
&& {<span class="php-variable">$highlight_up</span>} =="1"
&& (duration.sign > 0 || (duration.diff <= (this.props['threshold'] * 60)))
&& this.props['highlight'][0]) {
this.element.html('<span ' + this.props['highlight'][0] + '=' + this.props['highlight'][1] + '>' + outhtml + '</span>');
}
// No proximity styling
else {
this.element.html(outhtml);
}
return true;
}
this.get_duration = function(now, target) {
var dur = {diff:0, sign:0, years:0, months:0, days:0, hours:0, minutes:0, seconds:0, tot_months:0, tot_days:0, tot_hours:0, tot_mins:0, tot_secs:0};
dur.diff = Math.floor((now.getTime() - target.getTime()) / 1000);
if ( dur.diff < 0 ) {
dur.sign = -1;
dur.diff = Math.abs(dur.diff);
} else {
dur.sign = 1;
}
dur.years = Math.floor(dur.diff / 60 / 60 / 24 / 365.25);
// Total hours
dur.tot_hours = Math.floor(dur.diff / 60.0 / 60.0);
// Total minutes
dur.tot_mins = Math.floor(dur.diff / 60.0);
// Total seconds
dur.tot_secs = Math.floor(dur.diff);
// Use calendar months, using months based on seconds is problematic.
if(now.getFullYear() == target.getFullYear()) {
dur.tot_months = Math.abs(target.getMonth() - now.getMonth());
dur.months = dur.tot_months;
} else {
dur.tot_months = 11 - now.getMonth();
dur.tot_months += target.getMonth() + 1;
dur.tot_months += (target.getFullYear() - now.getFullYear() - 1) * 12;
dur.months = dur.tot_months - (dur.years * 12);
}
dur.tot_days = Math.floor(dur.diff / 60 / 60 / 24);
dur.days = Math.ceil(dur.tot_days - (dur.years * 365.25));
dur.hours = Math.floor(dur.diff / 60 / 60) - (dur.tot_days * 24);
dur.minutes = Math.floor(dur.diff / 60) - (dur.hours * 60) - (dur.tot_days * 24 * 60);
dur.seconds = dur.diff - (dur.minutes * 60) - (dur.hours * 60 * 60) - (dur.tot_days * 24 * 60 * 60);
return dur;
}
// begin constructor
this.element = ele;
// Defaults for each timer.
this.props = {
datetime: '',
dir: 'down',
format_num: 0,
format_txt: '',
timer_complete_message: new String({<span class="php-variable">$timer_complete_message</span>}),
highlight: new String({<span class="php-variable">$highlight</span>}).split(/=/),
threshold: new Number({<span class="php-variable">$highlight_threshold</span>}),
tc_redir: new String({<span class="php-variable">$redirect_path</span>}),
tc_redir_delay: new Number({<span class="php-variable">$redirect_delay</span>}),
tc_msg: new String({<span class="php-variable">$timer_complete_alert_message</span>}),
interval: '',
current_server_time: ''
};
this.loadProps();
/* bootstrap, parse microformat, load object */
try {
this.parse_microformat();
}
catch(e) {
alert(e.message);
alert(jQuery(ele).html());
this.parse_microformat_success = 0;
return;
}
// replace the static stuff in the format string
// this only needs to be done once, so here is a good spot.
this.outformat = this.outformat.replace(/%day%/, this.to_date.getDate());
this.outformat = this.outformat.replace(/%month%/, this.to_date.getMonth() + 1);
this.outformat = this.outformat.replace(/%year%/, this.to_date.getFullYear());
this.outformat = this.outformat.replace(/%moy%/, this.to_date.jstimer_get_moy());
this.outformat = this.outformat.replace(/%dow%/, this.to_date.jstimer_get_dow());
}
// End of timer widget.
JAVASCRIPT_CODE;
return $ret;
}
/**
* Implementation of hook_form_alter().
* Add the timer widget specific settings to admin screen.
*/
function jst_timer_form_jstimer_admin_settings_alter(&$form, FormStateInterface $form_state, $form_id) {
$config = \Drupal::config('jstimer.settings');
// Get a list of formats.
$timer_formats = jst_timer_get_formats();
$form['jst_timer'] = array(
'#type' => 'fieldset',
'#title' => t('Timer widget'),
'#weight' => 1,
);
$form['jst_timer']['jst_timer_formats'] = array(
'#type' => 'fieldset',
'#title' => t('Timer formats'),
'#tree' => TRUE,
'#description' => t("Formats control how each timer will display and also allow you to add styles/classes.") . '<br/>' . t("Available replacement values are: %day%, %month%, %year%, %dow%, %moy%, %years%, %ydays%, %days%, %hours%, %mins%, and %secs%.") . '<br/>' . t("Refresh the page after saving to reload the examples."),
);
$i = 0;
foreach ($timer_formats as $i => $format) {
// 0 is the global default format.
if ($i == 0) {
$form['jst_timer']['jst_timer_formats'][] = array(
'#type' => 'textarea',
'#rows' => 1,
'#title' => t('Global format'),
'#default_value' => $format,
);
}
else {
$form['jst_timer']['jst_timer_formats'][] = array(
'#type' => 'textarea',
'#rows' => 1,
'#title' => t('Format preset %preset_num', array(
'%preset_num' => $i,
)),
'#default_value' => $format,
);
}
}
// Add a blank one.
$form['jst_timer']['jst_timer_formats'][] = array(
'#type' => 'textarea',
'#rows' => 1,
'#title' => t('Format preset %preset_num', array(
'%preset_num' => $i + 1,
)),
'#default_value' => '',
'#description' => t('Add a new preset. After you save, another blank one will be added.'),
);
$form['jst_timer']['actions'] = array(
'#type' => 'fieldset',
'#title' => t('Timer actions'),
'#weight' => 1,
'#description' => t('Javascript actions that execute when a countdown timer completes or nears completion.'),
);
// Proximity styling.
$form['jst_timer']['actions']['proximity'] = array(
'#type' => 'details',
'#title' => t('Proximity styling'),
'#description' => t('Dynamic styling of timer when countdown approaches completion.'),
'#tree' => FALSE,
);
$form['jst_timer']['actions']['proximity']['jstimer_highlight'] = array(
'#type' => 'textfield',
'#size' => 100,
'#title' => t('CSS statement'),
'#default_value' => $config
->get('jstimer_highlight'),
'#description' => t("Use a complete css attribute statement like style=\"\" or class=\"\"."),
);
$form['jst_timer']['actions']['proximity']['jstimer_highlight_threshold'] = array(
'#type' => 'textfield',
'#size' => 5,
'#title' => t('Threshold (minutes)'),
'#default_value' => $config
->get('jstimer_highlight_threshold'),
'#description' => t("Number of minutes before css statement is applied."),
);
$form['jst_timer']['actions']['proximity']['jstimer_highlight_down'] = array(
'#type' => 'checkbox',
'#title' => t('Apply to countdown timers'),
'#default_value' => $config
->get('jstimer_highlight_down'),
);
$form['jst_timer']['actions']['proximity']['jstimer_highlight_up'] = array(
'#type' => 'checkbox',
'#title' => t('Apply to down/up (NASA) timers'),
'#default_value' => $config
->get('jstimer_highlight_up'),
'#description' => t("Styling latches (ie stays on forever after the threshold has been reached)."),
);
$form['jst_timer']['actions']['proximity']['jstimer_highlight_threshold'] = array(
'#type' => 'textfield',
'#size' => 5,
'#title' => t('Threshold (minutes)'),
'#default_value' => $config
->get('jstimer_highlight_threshold'),
'#description' => t("Number of minutes before css statement is applied."),
);
// Redirect URL.
$form['jst_timer']['actions']['redirect'] = array(
'#type' => 'details',
'#title' => t('Timer complete redirect'),
'#description' => t('Javascript redirect/refresh that runs when a countdown completes.'),
'#tree' => FALSE,
);
$form['jst_timer']['actions']['redirect']['jst_timer_redirect_path'] = array(
'#type' => 'textfield',
'#size' => 100,
'#title' => t('Redirect path'),
'#default_value' => $config
->get('jst_timer_redirect_path'),
'#description' => t('An absolute URL or %reload for same page.', array(
'%reload' => '<reload>',
)),
);
$form['jst_timer']['actions']['redirect']['jst_timer_redirect_delay'] = array(
'#type' => 'textfield',
'#size' => 5,
'#title' => t('Delay (seconds)'),
'#default_value' => $config
->get('jst_timer_redirect_delay'),
'#description' => t("Number of seconds to wait after countdown completion before running redirect/refresh."),
);
// Timer complete message.
$form['jst_timer']['actions']['jst_timer_complete_message'] = array(
'#type' => 'details',
'#title' => t('Timer complete message'),
'#description' => t('Message that replaces timer when a countdown completes.'),
'#weight' => 1,
);
$form['jst_timer']['actions']['jst_timer_complete_message']['jst_timer_complete_message'] = array(
'#type' => 'textfield',
'#title' => t('Message'),
'#size' => 80,
'#default_value' => $config
->get('jst_timer_complete_message'),
);
// Timer complete alert.
$form['jst_timer']['actions']['jst_timer_complete_alert'] = array(
'#type' => 'details',
'#title' => t('Timer complete alert message'),
'#description' => t('Message that pops up in an alert box when when a countdown completes. Do NOT use HTML.'),
'#weight' => 1,
);
$form['jst_timer']['actions']['jst_timer_complete_alert']['jst_timer_complete_alert_message'] = array(
'#type' => 'textfield',
'#title' => t('Message'),
'#size' => 80,
'#default_value' => $config
->get('jst_timer_complete_alert_message'),
);
$form['buttons']['#weight'] = 10;
// Put this module's submit handler first so it can save before javascript file is built.
array_unshift($form['#submit'], 'jst_timer_admin_settings_submit');
}
function jst_timer_admin_settings_submit($form, FormStateInterface $form_state) {
$config = \Drupal::configFactory()
->getEditable('jstimer.settings');
// Remove any blank formats.
$formats = $form_state
->getValue('jst_timer_formats');
foreach ($formats as $i => $format) {
if ($format == '') {
unset($formats[$i]);
}
}
$config
->set('jst_timer_formats', $formats);
$config
->set('jstimer_highlight', $form_state
->getValue('jstimer_highlight'));
$config
->set('jstimer_highlight_threshold', $form_state
->getValue('jstimer_highlight_threshold'));
$config
->set('jstimer_highlight_down', $form_state
->getValue('jstimer_highlight_down'));
$config
->set('jstimer_highlight_up', $form_state
->getValue('jstimer_highlight_up'));
$config
->set('jst_timer_redirect_path', $form_state
->getValue('jst_timer_redirect_path'));
$config
->set('jst_timer_redirect_delay', $form_state
->getValue('jst_timer_redirect_delay'));
$config
->set('jst_timer_complete_message', $form_state
->getValue('jst_timer_complete_message'));
$config
->set('jst_timer_complete_alert_message', $form_state
->getValue('jst_timer_complete_alert_message'));
$config
->save();
\Drupal::messenger()
->addStatus(t('Remember to "reload" the page. Most browsers will cache the javascript file.'));
}
function jst_timer_show($widget_args) {
$output = '<span class="jst_timer">';
if (isset($widget_args['no_js_txt'])) {
$output .= $widget_args['no_js_txt'];
}
$valid_atts = array(
'datetime',
'dir',
'format_txt',
'format_num',
'threshold',
'complete',
'tc_redir',
'tc_redir_delay',
'tc_msg',
'interval',
'current_server_time',
);
foreach ($valid_atts as $att) {
if (isset($widget_args[$att])) {
$output .= '<span style="display:none" class="' . $att . '">' . $widget_args[$att] . '</span>';
}
}
$output .= '</span>';
return $output;
}
/**
* Implementation of hook_install().
*/
function jst_timer_install() {
jstimer_build_js_cache();
drupal_clear_js_cache();
}
/**
* This allows for translation of replacement strings.
* Since this module uses dynamic javascript, the js file is not available
* for localize.drupal.org to translate.
* @return
* Nothing
*/
function translate_replacements() {
$translate = array(
t('1 year'),
t('@count years'),
t('1 day'),
t('@count days'),
t('1 hour'),
t('@count hours'),
t('1 minute'),
t('@count minutes'),
t('1 second'),
t('@count seconds'),
t('1 month'),
t('@count months'),
);
}
/**
* Get a javascript array encoded list of timer formats.
*/
function jst_timer_get_js_formats() {
$js_formats = array();
$formats = jst_timer_get_formats();
foreach ($formats as $f) {
$js_formats[] = "'" . jstimer_clean_for_javascript($f) . "'";
}
return '[' . implode(',', $js_formats) . ']';
}
/**
* Get a list of formats and handle default values.
*/
function jst_timer_get_formats() {
$formats = \Drupal::configFactory()
->get('jstimer.settings')
->get('jst_timer_formats');
if (count($formats) == 0) {
// The first one is the global format.
$formats[] = '<em>(%dow% %moy%%day%)</em><br/>%days% days + %hours%:%mins%:%secs%';
$formats[] = 'Only %days% days, %hours% hours, %mins% minutes and %secs% seconds left';
$formats[] = '%days% shopping days left';
$formats[] = '<em>(%dow% %moy%%day%)</em><br/>%days% days + %hours%:%mins%:%secs%';
}
return $formats;
}
Functions
Name | Description |
---|---|
jst_timer_admin_settings_submit | |
jst_timer_form_jstimer_admin_settings_alter | Implementation of hook_form_alter(). Add the timer widget specific settings to admin screen. |
jst_timer_get_formats | Get a list of formats and handle default values. |
jst_timer_get_js_formats | Get a javascript array encoded list of timer formats. |
jst_timer_install | Implementation of hook_install(). |
jst_timer_jstwidget | Implementation of hook_jstwidget(). |
jst_timer_show | |
translate_replacements | This allows for translation of replacement strings. Since this module uses dynamic javascript, the js file is not available for localize.drupal.org to translate. |