Table of Contents
The app’s HTML code
The following app does the following:
- reads the app’s configuration and applies the required CSS for background color, font color, font family, font style, font weight
- resizes text to fit the available window
- gets and displays time every second
We also include a “js” folder with the jQuery library for easier HTML DOM manipulation.
Count Down/Up index.html
<!DOCTYPE html>
<html>
<head>
<title>Countdown Clock</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<style type="text/css">
html, body {
background-color: transparent;
font-family: tahoma;
font-size: 90%;
height: 100%;
width: 100%;
}
* {
margin: 0;
padding: 0;
}
#countbox1 {
height: 100%;
text-align: center;
white-space: nowrap;
}
</style>
<script src="js/jquery.min.js"></script>
<script src="js/moment.min.js"></script>
<script src="js/moment-timezone-with-data.min.js"></script>
<script language="javascript" type="text/javascript">
/*function that starts the clock*/
function countdown() {
var now = moment().tz(window.widget_timezone);
var timeToCompare = null;
if(now < window.event_end){
timeToCompare = window.event_end
}
if(timeToCompare){
timeDiff = timeToCompare - now
var d="",h="",m="",s="";
var amount = Math.floor(timeDiff/1000);//kill the "milliseconds" so just secs
d=Math.floor(amount/86400);//days
amount=amount%86400;
h=Math.floor(amount/3600);//hours
amount=amount%3600;
m=Math.floor(amount/60);//minutes
amount=amount%60;
s=Math.floor(amount);//seconds
s = checkTime(s);
m = checkTime(m);
h = checkTime(h);
if(d != 0){
$('#countbox1>span').text(d + "d " + h + ":" + m + ":" + s);
if (!window.days){
$('#countbox1').textfill({
maxFontPixels : $('#countbox1').height()
})
window.days = true
}
}
else if(h != 0){
$('#countbox1>span').text(h + ":" + m + ":" + s);
if (!window.hours){
$('#countbox1').textfill({
maxFontPixels : $('#countbox1').height()
})
window.hours = true
}
}
else{
$('#countbox1>span').text(m + ":" + s);
if (!window.minutes){
$('#countbox1').textfill({
maxFontPixels : $('#countbox1').height()
})
window.minutes = true
}
}
}else{
$('#countbox1>span').text(window.end_text);
$('#countbox1').textfill({
maxFontPixels : $('#countbox1').height()
})
return;
}
if (window.t) {
clearTimeout(window.t)
}
/*repeat after one second*/
window.t = setTimeout(countdown, 1000);
}
function countup() {
var now = moment().tz(window.widget_timezone);
var timeToCompare = null
if(now > window.event_start){
timeToCompare = window.event_start
}
if(timeToCompare){
timeDiff = now - timeToCompare
var d="",h="",m="",s="";
var amount = Math.floor(timeDiff/1000);//kill the "milliseconds" so just secs
d=Math.floor(amount/86400);//days
amount=amount%86400;
h=Math.floor(amount/3600);//hours
amount=amount%3600;
m=Math.floor(amount/60);//minutes
amount=amount%60;
s=Math.floor(amount);//seconds
m = checkTime(m);
s = checkTime(s);
h = checkTime(h);
if(d != 0){
$('#countbox1>span').text(d + "d " + h + ":" + m + ":" + s);
if (!window.days){
$('#countbox1').textfill({
maxFontPixels : $('#countbox1').height()
})
window.days = true
}
}
else if(h != 0){
$('#countbox1>span').text(h + ":" + m + ":" + s);
if (!window.hours){
$('#countbox1').textfill({
maxFontPixels : $('#countbox1').height()
})
window.hours = true
}
}
else{
$('#countbox1>span').text(m + ":" + s);
if (!window.minutes){
$('#countbox1').textfill({
maxFontPixels : $('#countbox1').height()
})
window.minutes = true
}
}
}else{
$('#countbox1>span').text(window.start_text);
}
if (window.t) {
clearTimeout(window.t)
}
/*repeat after one second*/
window.t = setTimeout(countup, 1000);
}
/*function that appends a zero to single digit numbers*/
function checkTime(i) {
if (i < 10) {
i = "0" + i
}
; // add zero in front of numbers < 10
return i;
}
//function that starts the widget
function init_widget(config) {
if (!config) {
console.log("no json configuration found");
return;
}
/*apply css based on configuration*/
if ("bgcolor" in config) {
window.bgcolor = hexToRGBA(config.bgcolor)
if (window.bgcolor) {
$('body').css('background-color', window.bgcolor)
}
}
if ("color" in config) {
window.color = hexToRGBA(config.color)
if (window.color) {
$('#countbox1').css('color', window.color)
}
}
window.widget_timezone = moment.tz.guess();
if ('fontfamily' in config) {
$('#countbox1').css('font-family', config['fontfamily'])
}
if ('fontstyle' in config) {
$('#countbox1').css('font-style', config['fontstyle'])
}
if ('fontweight' in config) {
$('#countbox1').css('font-weight', config['fontweight'])
}
if ("event_start" in config) {
window.event_start = moment(config.event_start,"YYYY-MM-DD[T]HH:mm:ss")
}
else{
window.event_start = moment()
}
if ("event_end" in config) {
window.event_end = moment(config.event_end,"YYYY-MM-DD[T]HH:mm:ss")
}
else{
window.event_end = moment()
}
if("count_type" in config){
if(config['count_type'] == 'Count-up'){
window.count_up = true
}
else{
window.count_up = false
}
}
if("end_text" in config){
window.end_text = config['end_text']
}else{
window.end_text = ""
}
if("start_text" in config){
window.start_text = config['start_text']
}else{
window.start_text = ""
}
window.minutes = false
window.hours = false
window.days = false
}
function start_widget() {
var now = moment().tz(window.widget_timezone);
var timeToCompare = null
if(window.count_up){
if(now > window.event_start){
timeToCompare = window.event_start
}
if(!timeToCompare){
$('#countbox1>span').text(window.start_text);
}
countup()
}else{
countdown()
}
$('#countbox1').show()
/*resize text*/
$('#countbox1').textfill({
maxFontPixels : $('#countbox1').height()
})
}
function stop_widget() {
if (window.t) {
clearTimeout(window.t)
}
}
/*test function to test while developing*/
function test_widget() {
init_widget({
bgcolor : "black",
color : "white",
fontfamily : "arial",
fontstyle : "normal",
fontweight : "bold",
event_end:"2018-06-29T11:40:50.000Z",
event_start:"2018-06-29T09:37:00.000Z",
count_type: "Count-down",
start_text: "Starting",
end_text: "Ending"
});
start_widget()
}
/*function that turns an rgba hex to rgba css*/
function hexToRGBA(hex) {
var r = parseInt(hex.substr(0, 2), 16);
var g = parseInt(hex.substr(2, 2), 16);
var b = parseInt(hex.substr(4, 2), 16);
var a = Math.round((parseInt(hex.substr(6, 2), 16) / 255) * 100);
a = a / 100;
var rgba = 'rgba(' + r + ',' + g + ',' + b + ',' + a + ')';
return rgba;
}
/*function to resize text so that it fits to its container*/
(function($) {
$.fn.textfill = function(options) {
var fontSize = options.maxFontPixels;
var ourText = $('span:first', this);
var maxHeight = $(this).height();
var maxWidth = $(this).width();
var textHeight;
var textWidth;
while(true) {
ourText.css('font-size', fontSize);
textHeight = ourText.height();
textWidth = ourText.width();
//console.log(textHeight +' -- '+ maxHeight)
//console.log(textWidth +' -- '+ maxWidth)
if(fontSize < 5){
break;
}
if(textHeight > maxHeight){
fontSize = fontSize - Math.ceil(fontSize*(1-(maxHeight/textHeight)))
continue;
}
if(textWidth > maxWidth){
fontSize = fontSize - Math.ceil(fontSize*(1-(maxWidth/textWidth)))
continue;
}
break;
}
window.fontsize = fontSize;
return this;
}
})(jQuery);
</script>
</head>
<body>
<div id="countbox1" hidden>
<span>N/A</span>
</div>
</body>
</html>
For the app’s JSON Schema
We need to declare a schema for the required configuration fields:
- bgcolor: a color picker field that defines the widget’s background color.
- color: a color picker field that defines the widget’s font color.
- fontfamily: a select field with the available fonts
- fontstyle: a select field with the available font styles (normal, italic …)
- fontweight: a select field with the available font weight (normal, bold …)
- count_type: an option filed with the available Count types (Down/Up)
- start_text: a text field writing a before-start message
- event_start: a time selector when the event will start
- end_text: a text field displaying an after-end message
- event_end: a time selector when the event will end
Count Down/Up schema.json
{
"data": {
"bgcolor": "#1e134a",
"color": "white",
"count_type": "Count-up",
"fontfamily": "Arial",
"fontstyle": "<%=general_normal%>",
"fontweight": "<%=general_normal%>",
"format": 0
},
"supportLiveUpdate": true,
"styleSettings": [
"color",
"bgcolor",
"fontfamily",
"fontstyle",
"fontweight"
],
"fields": [
"count_type",
"format",
"start_text",
"event_start",
"end_text",
"event_end",
"style_seperator",
"bgcolor",
"color",
"fontfamily",
"fontstyle",
"fontweight",
"advanced_seperator"
],
"meta": {
"description": "Timer counting up from event or down to deadline.",
"details": "A simple timer that either counts up from an event or down to a deadline. The size of the timer gets automatically adjusted to the layout zone. Options include color and font customization.",
"name": "Counter up/down",
"group": "Time",
"html_player_support": true,
"preview_category": "live"
},
"schema": {
"bgcolor": {
"title": "<%=general_background_color%>",
"type": "SpectrumColorPicker",
"styleClass": "col-sm-6 right",
"editorAttrs": {
"def": "1e134a"
}
},
"color": {
"title": "<%=general_font_color%>",
"type": "SpectrumColorPicker",
"styleClass": "col-sm-6 left",
"editorAttrs": {
"def": "ffffff"
}
},
"count_type": {
"options": [
{
"hides": [
{
"fields": [
"event_start",
"start_text"
]
},
{
"fields": [
"event_end",
"end_text"
]
}
],
"options": [
"Count-down",
"Count-up"
],
"shows": [
{
"fields": [
"event_end",
"end_text"
]
},
{
"fields": [
"event_start",
"start_text"
]
}
]
}
],
"title": "<%=countupdown_counter_type%>",
"type": "hideFieldsSelect",
"validators": [
"required"
]
},
"end_text": {
"title": "<%=countupdown_text_after_end%>",
"type": "Text"
},
"event_end": {
"minsInterval": 1,
"title": "<%=countupdown_event_end%>",
"type": "DateTime",
"yearEnd": 2030,
"yearStart": 2000
},
"event_start": {
"minsInterval": 1,
"title": "<%=countupdown_event_start%>",
"type": "DateTime",
"yearEnd": 2030,
"yearStart": 2000
},
"fontfamily": {
"title": "<%=general_font_family%>",
"type": "FontFamily",
"editorClass": "select2container full-size",
"validators": [
"required"
]
},
"fontstyle": {
"options": [
"<%=general_normal%>",
"<%=general_italic%>",
"<%=general_oblique%>"
],
"title": "<%=general_font_style%>",
"type": "Select",
"validators": [
"required"
],
"styleClass": "col-sm-6 left"
},
"fontweight": {
"options": [
"<%=general_normal%>",
"<%=general_bold%>",
"<%=general_bolder%>",
"<%=general_lighter%>"
],
"title": "<%=general_font_weight%>",
"type": "Select",
"validators": [
"required"
],
"styleClass": "col-sm-6 right"
},
"start_text": {
"title": "<%=countupdown_text_before_start%>",
"type": "Text"
},
"format": {
"options": [
{
"options": [
{
"label": "Days, Hours, Minutes and Seconds",
"val": 0
},
{
"label": "Days, Hours, and Minutes",
"val": 1
},
{
"label": "Days and Hours",
"val": 2
},
{
"label": "Days Only",
"val": 3
}
],
"tooltips": [
"<b>Down:</b> Shows days until the last day; then hr:min:sec.<br><b>Up:</b> Starts with hr:min:sec; adds days after 1 day.",
"<b>Down:</b> Shows the format until the last minute; then min:sec.<br><b>Up:</b> Starts with min:sec; switches to the chosen format after 1 minute.",
"<b>Down:</b> Shows the format until the last hour; then hr:min:sec.<br><b>Up:</b> Starts with min:sec; switches to the chosen format after 1 hour.",
"<b>Down:</b>Shows days until the last day; then hr:min:sec.<br><b>Up:</b> Starts with hr:min:sec; switches to the chosen format after 1 day."
]
}
],
"title": "Counter Format",
"type": "tooltipSelect",
"help": "Select Counter Format",
"editorClass": "full-size",
"def": "0"
},
"style_seperator": {
"type": "Seperator",
"title": "Style"
},
"advanced_seperator": {
"type": "Seperator",
"title": "Advanced Settings"
}
}
}
Complete ZIP Package
On the upload app page, we create a new app, upload the attached zip file and enter the schema given above. After that, we can create apps with the desired styling/configuration.
You can download the complete ZIP file for the App from here:
https://drive.google.com/file/d/1122yVRHzlLYcD5UmPB27ANi5XSAD6p-n/view?usp=sharing