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