Count Down/Up

The app’s HTML code

The following app does the following:

  1. reads the app’s configuration and applies the required CSS for background color, font color, font family, font style, font weight
  2. resizes text to fit the available window
  3. 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:

  1. bgcolor: a color picker field that defines the widget’s background color.
  2. color: a color picker field that defines the widget’s font color.
  3. fontfamily: a select field with the available fonts
  4. fontstyle: a select field with the available font styles (normal, italic …)
  5. fontweight: a select field with the available font weight (normal, bold …)
  6. count_type: an option filed with the available Count types (Down/Up)
  7. start_text: a text field writing a before-start message
  8. event_start: a time selector when the event will start
  9. end_text: a text field displaying an after-end message
  10. 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