freeboard-leaflet-realtime
Version:
Freeboard plugin for real time Leaflet web maps
1,049 lines (897 loc) • 34.1 kB
JavaScript
// ┌────────────────────────────────────────────────────────────────────┐ \\
// │ F R E E B O A R D │ \\
// ├────────────────────────────────────────────────────────────────────┤ \\
// │ Copyright © 2013 Jim Heising (https://github.com/jheising) │ \\
// │ Copyright © 2013 Bug Labs, Inc. (http://buglabs.net) │ \\
// ├────────────────────────────────────────────────────────────────────┤ \\
// │ Licensed under the MIT license. │ \\
// └────────────────────────────────────────────────────────────────────┘ \\
(function () {
var SPARKLINE_HISTORY_LENGTH = 100;
var SPARKLINE_COLORS = ["#FF9900", "#FFFFFF", "#B3B4B4", "#6B6B6B", "#28DE28", "#13F7F9", "#E6EE18", "#C41204", "#CA3CB8", "#0B1CFB"];
function easeTransitionText(newValue, textElement, duration) {
var currentValue = $(textElement).text();
if (currentValue == newValue)
return;
if ($.isNumeric(newValue) && $.isNumeric(currentValue)) {
var numParts = newValue.toString().split('.');
var endingPrecision = 0;
if (numParts.length > 1) {
endingPrecision = numParts[1].length;
}
numParts = currentValue.toString().split('.');
var startingPrecision = 0;
if (numParts.length > 1) {
startingPrecision = numParts[1].length;
}
jQuery({transitionValue: Number(currentValue), precisionValue: startingPrecision}).animate({transitionValue: Number(newValue), precisionValue: endingPrecision}, {
duration: duration,
step: function () {
$(textElement).text(this.transitionValue.toFixed(this.precisionValue));
},
done: function () {
$(textElement).text(newValue);
}
});
}
else {
$(textElement).text(newValue);
}
}
function addSparklineLegend(element, legend) {
var legendElt = $("<div class='sparkline-legend'></div>");
for(var i=0; i<legend.length; i++) {
var color = SPARKLINE_COLORS[i % SPARKLINE_COLORS.length];
var label = legend[i];
legendElt.append("<div class='sparkline-legend-value'><span style='color:" +
color + "'>●</span>" + label + "</div>");
}
element.empty().append(legendElt);
freeboard.addStyle('.sparkline-legend', "margin:5px;");
freeboard.addStyle('.sparkline-legend-value',
'color:white; font:10px arial,san serif; float:left; overflow:hidden; width:50%;');
freeboard.addStyle('.sparkline-legend-value span',
'font-weight:bold; padding-right:5px;');
}
function addValueToSparkline(element, value, legend) {
var values = $(element).data().values;
var valueMin = $(element).data().valueMin;
var valueMax = $(element).data().valueMax;
if (!values) {
values = [];
valueMin = undefined;
valueMax = undefined;
}
var collateValues = function(val, plotIndex) {
if(!values[plotIndex]) {
values[plotIndex] = [];
}
if (values[plotIndex].length >= SPARKLINE_HISTORY_LENGTH) {
values[plotIndex].shift();
}
values[plotIndex].push(Number(val));
if(valueMin === undefined || val < valueMin) {
valueMin = val;
}
if(valueMax === undefined || val > valueMax) {
valueMax = val;
}
}
if(_.isArray(value)) {
_.each(value, collateValues);
} else {
collateValues(value, 0);
}
$(element).data().values = values;
$(element).data().valueMin = valueMin;
$(element).data().valueMax = valueMax;
var tooltipHTML = '<span style="color: {{color}}">●</span> {{y}}';
var composite = false;
_.each(values, function(valueArray, valueIndex) {
$(element).sparkline(valueArray, {
type: "line",
composite: composite,
height: "100%",
width: "100%",
fillColor: false,
lineColor: SPARKLINE_COLORS[valueIndex % SPARKLINE_COLORS.length],
lineWidth: 2,
spotRadius: 3,
spotColor: false,
minSpotColor: "#78AB49",
maxSpotColor: "#78AB49",
highlightSpotColor: "#9D3926",
highlightLineColor: "#9D3926",
chartRangeMin: valueMin,
chartRangeMax: valueMax,
tooltipFormat: (legend && legend[valueIndex])?tooltipHTML + ' (' + legend[valueIndex] + ')':tooltipHTML
});
composite = true;
});
}
var valueStyle = freeboard.getStyleString("values");
freeboard.addStyle('.widget-big-text', valueStyle + "font-size:75px;");
freeboard.addStyle('.tw-display', 'width: 100%; height:100%; display:table; table-layout:fixed;');
freeboard.addStyle('.tw-tr',
'display:table-row;');
freeboard.addStyle('.tw-tg',
'display:table-row-group;');
freeboard.addStyle('.tw-tc',
'display:table-caption;');
freeboard.addStyle('.tw-td',
'display:table-cell;');
freeboard.addStyle('.tw-value',
valueStyle +
'overflow: hidden;' +
'display: inline-block;' +
'text-overflow: ellipsis;');
freeboard.addStyle('.tw-unit',
'display: inline-block;' +
'padding-left: 10px;' +
'padding-bottom: 1.1em;' +
'vertical-align: bottom;');
freeboard.addStyle('.tw-value-wrapper',
'position: relative;' +
'vertical-align: middle;' +
'height:100%;');
freeboard.addStyle('.tw-sparkline',
'height:20px;');
var textWidget = function (settings) {
var self = this;
var currentSettings = settings;
var displayElement = $('<div class="tw-display"></div>');
var titleElement = $('<h2 class="section-title tw-title tw-td"></h2>');
var valueElement = $('<div class="tw-value"></div>');
var unitsElement = $('<div class="tw-unit"></div>');
var sparklineElement = $('<div class="tw-sparkline tw-td"></div>');
function updateValueSizing()
{
if(!_.isUndefined(currentSettings.units) && currentSettings.units != "") // If we're displaying our units
{
valueElement.css("max-width", (displayElement.innerWidth() - unitsElement.outerWidth(true)) + "px");
}
else
{
valueElement.css("max-width", "100%");
}
}
this.render = function (element) {
$(element).empty();
$(displayElement)
.append($('<div class="tw-tr"></div>').append(titleElement))
.append($('<div class="tw-tr"></div>').append($('<div class="tw-value-wrapper tw-td"></div>').append(valueElement).append(unitsElement)))
.append($('<div class="tw-tr"></div>').append(sparklineElement));
$(element).append(displayElement);
updateValueSizing();
}
this.onSettingsChanged = function (newSettings) {
currentSettings = newSettings;
var shouldDisplayTitle = (!_.isUndefined(newSettings.title) && newSettings.title != "");
var shouldDisplayUnits = (!_.isUndefined(newSettings.units) && newSettings.units != "");
if(newSettings.sparkline)
{
sparklineElement.attr("style", null);
}
else
{
delete sparklineElement.data().values;
sparklineElement.empty();
sparklineElement.hide();
}
if(shouldDisplayTitle)
{
titleElement.html((_.isUndefined(newSettings.title) ? "" : newSettings.title));
titleElement.attr("style", null);
}
else
{
titleElement.empty();
titleElement.hide();
}
if(shouldDisplayUnits)
{
unitsElement.html((_.isUndefined(newSettings.units) ? "" : newSettings.units));
unitsElement.attr("style", null);
}
else
{
unitsElement.empty();
unitsElement.hide();
}
var valueFontSize = 30;
if(newSettings.size == "big")
{
valueFontSize = 75;
if(newSettings.sparkline)
{
valueFontSize = 60;
}
}
valueElement.css({"font-size" : valueFontSize + "px"});
updateValueSizing();
}
this.onSizeChanged = function()
{
updateValueSizing();
}
this.onCalculatedValueChanged = function (settingName, newValue) {
if (settingName == "value") {
if (currentSettings.animate) {
easeTransitionText(newValue, valueElement, 500);
}
else {
valueElement.text(newValue);
}
if (currentSettings.sparkline) {
addValueToSparkline(sparklineElement, newValue);
}
}
}
this.onDispose = function () {
}
this.getHeight = function () {
if (currentSettings.size == "big" || currentSettings.sparkline) {
return 2;
}
else {
return 1;
}
}
this.onSettingsChanged(settings);
};
freeboard.loadWidgetPlugin({
type_name: "text_widget",
display_name: "Text",
"external_scripts" : [
"plugins/thirdparty/jquery.sparkline.min.js"
],
settings: [
{
name: "title",
display_name: "Title",
type: "text"
},
{
name: "size",
display_name: "Size",
type: "option",
options: [
{
name: "Regular",
value: "regular"
},
{
name: "Big",
value: "big"
}
]
},
{
name: "value",
display_name: "Value",
type: "calculated"
},
{
name: "sparkline",
display_name: "Include Sparkline",
type: "boolean"
},
{
name: "animate",
display_name: "Animate Value Changes",
type: "boolean",
default_value: true
},
{
name: "units",
display_name: "Units",
type: "text"
}
],
newInstance: function (settings, newInstanceCallback) {
newInstanceCallback(new textWidget(settings));
}
});
var gaugeID = 0;
freeboard.addStyle('.gauge-widget-wrapper', "width: 100%;text-align: center;");
freeboard.addStyle('.gauge-widget', "width:200px;height:160px;display:inline-block;");
var gaugeWidget = function (settings) {
var self = this;
var thisGaugeID = "gauge-" + gaugeID++;
var titleElement = $('<h2 class="section-title"></h2>');
var gaugeElement = $('<div class="gauge-widget" id="' + thisGaugeID + '"></div>');
var gaugeObject;
var rendered = false;
var currentSettings = settings;
function createGauge() {
if (!rendered) {
return;
}
gaugeElement.empty();
gaugeObject = new JustGage({
id: thisGaugeID,
value: (_.isUndefined(currentSettings.min_value) ? 0 : currentSettings.min_value),
min: (_.isUndefined(currentSettings.min_value) ? 0 : currentSettings.min_value),
max: (_.isUndefined(currentSettings.max_value) ? 0 : currentSettings.max_value),
label: currentSettings.units,
showInnerShadow: false,
valueFontColor: "#d3d4d4"
});
}
this.render = function (element) {
rendered = true;
$(element).append(titleElement).append($('<div class="gauge-widget-wrapper"></div>').append(gaugeElement));
createGauge();
}
this.onSettingsChanged = function (newSettings) {
if (newSettings.min_value != currentSettings.min_value || newSettings.max_value != currentSettings.max_value || newSettings.units != currentSettings.units) {
currentSettings = newSettings;
createGauge();
}
else {
currentSettings = newSettings;
}
titleElement.html(newSettings.title);
}
this.onCalculatedValueChanged = function (settingName, newValue) {
if (!_.isUndefined(gaugeObject)) {
gaugeObject.refresh(Number(newValue));
}
}
this.onDispose = function () {
}
this.getHeight = function () {
return 3;
}
this.onSettingsChanged(settings);
};
freeboard.loadWidgetPlugin({
type_name: "gauge",
display_name: "Gauge",
"external_scripts" : [
"plugins/thirdparty/raphael.2.1.0.min.js",
"plugins/thirdparty/justgage.1.0.1.js"
],
settings: [
{
name: "title",
display_name: "Title",
type: "text"
},
{
name: "value",
display_name: "Value",
type: "calculated"
},
{
name: "units",
display_name: "Units",
type: "text"
},
{
name: "min_value",
display_name: "Minimum",
type: "text",
default_value: 0
},
{
name: "max_value",
display_name: "Maximum",
type: "text",
default_value: 100
}
],
newInstance: function (settings, newInstanceCallback) {
newInstanceCallback(new gaugeWidget(settings));
}
});
freeboard.addStyle('.sparkline', "width:100%;height: 75px;");
var sparklineWidget = function (settings) {
var self = this;
var titleElement = $('<h2 class="section-title"></h2>');
var sparklineElement = $('<div class="sparkline"></div>');
var sparklineLegend = $('<div></div>');
var currentSettings = settings;
this.render = function (element) {
$(element).append(titleElement).append(sparklineElement).append(sparklineLegend);
}
this.onSettingsChanged = function (newSettings) {
currentSettings = newSettings;
titleElement.html((_.isUndefined(newSettings.title) ? "" : newSettings.title));
if(newSettings.include_legend) {
addSparklineLegend(sparklineLegend, newSettings.legend.split(","));
}
}
this.onCalculatedValueChanged = function (settingName, newValue) {
if (currentSettings.legend) {
addValueToSparkline(sparklineElement, newValue, currentSettings.legend.split(","));
} else {
addValueToSparkline(sparklineElement, newValue);
}
}
this.onDispose = function () {
}
this.getHeight = function () {
var legendHeight = 0;
if (currentSettings.include_legend && currentSettings.legend) {
var legendLength = currentSettings.legend.split(",").length;
if (legendLength > 4) {
legendHeight = Math.floor((legendLength-1) / 4) * 0.5;
} else if (legendLength) {
legendHeight = 0.5;
}
}
return 2 + legendHeight;
}
this.onSettingsChanged(settings);
};
freeboard.loadWidgetPlugin({
type_name: "sparkline",
display_name: "Sparkline",
"external_scripts" : [
"plugins/thirdparty/jquery.sparkline.min.js"
],
settings: [
{
name: "title",
display_name: "Title",
type: "text"
},
{
name: "value",
display_name: "Value",
type: "calculated",
multi_input: "true"
},
{
name: "include_legend",
display_name: "Include Legend",
type: "boolean"
},
{
name: "legend",
display_name: "Legend",
type: "text",
description: "Comma-separated for multiple sparklines"
}
],
newInstance: function (settings, newInstanceCallback) {
newInstanceCallback(new sparklineWidget(settings));
}
});
freeboard.addStyle('div.pointer-value', "position:absolute;height:95px;margin: auto;top: 0px;bottom: 0px;width: 100%;text-align:center;");
var pointerWidget = function (settings) {
var self = this;
var paper;
var strokeWidth = 3;
var triangle;
var width, height;
var currentValue = 0;
var valueDiv = $('<div class="widget-big-text"></div>');
var unitsDiv = $('<div></div>');
function polygonPath(points) {
if (!points || points.length < 2)
return [];
var path = []; //will use path object type
path.push(['m', points[0], points[1]]);
for (var i = 2; i < points.length; i += 2) {
path.push(['l', points[i], points[i + 1]]);
}
path.push(['z']);
return path;
}
this.render = function (element) {
width = $(element).width();
height = $(element).height();
var radius = Math.min(width, height) / 2 - strokeWidth * 2;
paper = Raphael($(element).get()[0], width, height);
var circle = paper.circle(width / 2, height / 2, radius);
circle.attr("stroke", "#FF9900");
circle.attr("stroke-width", strokeWidth);
triangle = paper.path(polygonPath([width / 2, (height / 2) - radius + strokeWidth, 15, 20, -30, 0]));
triangle.attr("stroke-width", 0);
triangle.attr("fill", "#fff");
$(element).append($('<div class="pointer-value"></div>').append(valueDiv).append(unitsDiv));
}
this.onSettingsChanged = function (newSettings) {
unitsDiv.html(newSettings.units);
}
this.onCalculatedValueChanged = function (settingName, newValue) {
if (settingName == "direction") {
if (!_.isUndefined(triangle)) {
var direction = "r";
var oppositeCurrent = currentValue + 180;
if (oppositeCurrent < newValue) {
//direction = "l";
}
triangle.animate({transform: "r" + newValue + "," + (width / 2) + "," + (height / 2)}, 250, "bounce");
}
currentValue = newValue;
}
else if (settingName == "value_text") {
valueDiv.html(newValue);
}
}
this.onDispose = function () {
}
this.getHeight = function () {
return 4;
}
this.onSettingsChanged(settings);
};
freeboard.loadWidgetPlugin({
type_name: "pointer",
display_name: "Pointer",
"external_scripts" : [
"plugins/thirdparty/raphael.2.1.0.min.js"
],
settings: [
{
name: "direction",
display_name: "Direction",
type: "calculated",
description: "In degrees"
},
{
name: "value_text",
display_name: "Value Text",
type: "calculated"
},
{
name: "units",
display_name: "Units",
type: "text"
}
],
newInstance: function (settings, newInstanceCallback) {
newInstanceCallback(new pointerWidget(settings));
}
});
var pictureWidget = function(settings)
{
var self = this;
var widgetElement;
var timer;
var imageURL;
function stopTimer()
{
if(timer)
{
clearInterval(timer);
timer = null;
}
}
function updateImage()
{
if(widgetElement && imageURL)
{
var cacheBreakerURL = imageURL + (imageURL.indexOf("?") == -1 ? "?" : "&") + Date.now();
$(widgetElement).css({
"background-image" : "url(" + cacheBreakerURL + ")"
});
}
}
this.render = function(element)
{
$(element).css({
width : "100%",
height: "100%",
"background-size" : "cover",
"background-position" : "center"
});
widgetElement = element;
}
this.onSettingsChanged = function(newSettings)
{
stopTimer();
if(newSettings.refresh && newSettings.refresh > 0)
{
timer = setInterval(updateImage, Number(newSettings.refresh) * 1000);
}
}
this.onCalculatedValueChanged = function(settingName, newValue)
{
if(settingName == "src")
{
imageURL = newValue;
}
updateImage();
}
this.onDispose = function()
{
stopTimer();
}
this.getHeight = function()
{
return 4;
}
this.onSettingsChanged(settings);
};
freeboard.loadWidgetPlugin({
type_name: "picture",
display_name: "Picture",
fill_size: true,
settings: [
{
name: "src",
display_name: "Image URL",
type: "calculated"
},
{
"type": "number",
"display_name": "Refresh every",
"name": "refresh",
"suffix": "seconds",
"description":"Leave blank if the image doesn't need to be refreshed"
}
],
newInstance: function (settings, newInstanceCallback) {
newInstanceCallback(new pictureWidget(settings));
}
});
freeboard.addStyle('.indicator-light', "border-radius:50%;width:22px;height:22px;border:2px solid #3d3d3d;margin-top:5px;float:left;background-color:#222;margin-right:10px;");
freeboard.addStyle('.indicator-light.on', "background-color:#FFC773;box-shadow: 0px 0px 15px #FF9900;border-color:#FDF1DF;");
freeboard.addStyle('.indicator-text', "margin-top:10px;");
var indicatorWidget = function (settings) {
var self = this;
var titleElement = $('<h2 class="section-title"></h2>');
var stateElement = $('<div class="indicator-text"></div>');
var indicatorElement = $('<div class="indicator-light"></div>');
var currentSettings = settings;
var isOn = false;
var onText;
var offText;
function updateState() {
indicatorElement.toggleClass("on", isOn);
if (isOn) {
stateElement.text((_.isUndefined(onText) ? (_.isUndefined(currentSettings.on_text) ? "" : currentSettings.on_text) : onText));
}
else {
stateElement.text((_.isUndefined(offText) ? (_.isUndefined(currentSettings.off_text) ? "" : currentSettings.off_text) : offText));
}
}
this.render = function (element) {
$(element).append(titleElement).append(indicatorElement).append(stateElement);
}
this.onSettingsChanged = function (newSettings) {
currentSettings = newSettings;
titleElement.html((_.isUndefined(newSettings.title) ? "" : newSettings.title));
updateState();
}
this.onCalculatedValueChanged = function (settingName, newValue) {
if (settingName == "value") {
isOn = Boolean(newValue);
}
if (settingName == "on_text") {
onText = newValue;
}
if (settingName == "off_text") {
offText = newValue;
}
updateState();
}
this.onDispose = function () {
}
this.getHeight = function () {
return 1;
}
this.onSettingsChanged(settings);
};
freeboard.loadWidgetPlugin({
type_name: "indicator",
display_name: "Indicator Light",
settings: [
{
name: "title",
display_name: "Title",
type: "text"
},
{
name: "value",
display_name: "Value",
type: "calculated"
},
{
name: "on_text",
display_name: "On Text",
type: "calculated"
},
{
name: "off_text",
display_name: "Off Text",
type: "calculated"
}
],
newInstance: function (settings, newInstanceCallback) {
newInstanceCallback(new indicatorWidget(settings));
}
});
freeboard.addStyle('.gm-style-cc a', "text-shadow:none;");
var googleMapWidget = function (settings) {
var self = this;
var currentSettings = settings;
var map;
var marker;
var currentPosition = {};
function updatePosition() {
if (map && marker && currentPosition.lat && currentPosition.lon) {
var newLatLon = new google.maps.LatLng(currentPosition.lat, currentPosition.lon);
marker.setPosition(newLatLon);
map.panTo(newLatLon);
}
}
this.render = function (element) {
function initializeMap() {
var mapOptions = {
zoom: 13,
center: new google.maps.LatLng(37.235, -115.811111),
disableDefaultUI: true,
draggable: false,
styles: [
{"featureType": "water", "elementType": "geometry", "stylers": [
{"color": "#2a2a2a"}
]},
{"featureType": "landscape", "elementType": "geometry", "stylers": [
{"color": "#000000"},
{"lightness": 20}
]},
{"featureType": "road.highway", "elementType": "geometry.fill", "stylers": [
{"color": "#000000"},
{"lightness": 17}
]},
{"featureType": "road.highway", "elementType": "geometry.stroke", "stylers": [
{"color": "#000000"},
{"lightness": 29},
{"weight": 0.2}
]},
{"featureType": "road.arterial", "elementType": "geometry", "stylers": [
{"color": "#000000"},
{"lightness": 18}
]},
{"featureType": "road.local", "elementType": "geometry", "stylers": [
{"color": "#000000"},
{"lightness": 16}
]},
{"featureType": "poi", "elementType": "geometry", "stylers": [
{"color": "#000000"},
{"lightness": 21}
]},
{"elementType": "labels.text.stroke", "stylers": [
{"visibility": "on"},
{"color": "#000000"},
{"lightness": 16}
]},
{"elementType": "labels.text.fill", "stylers": [
{"saturation": 36},
{"color": "#000000"},
{"lightness": 40}
]},
{"elementType": "labels.icon", "stylers": [
{"visibility": "off"}
]},
{"featureType": "transit", "elementType": "geometry", "stylers": [
{"color": "#000000"},
{"lightness": 19}
]},
{"featureType": "administrative", "elementType": "geometry.fill", "stylers": [
{"color": "#000000"},
{"lightness": 20}
]},
{"featureType": "administrative", "elementType": "geometry.stroke", "stylers": [
{"color": "#000000"},
{"lightness": 17},
{"weight": 1.2}
]}
]
};
map = new google.maps.Map(element, mapOptions);
google.maps.event.addDomListener(element, 'mouseenter', function (e) {
e.cancelBubble = true;
if (!map.hover) {
map.hover = true;
map.setOptions({zoomControl: true});
}
});
google.maps.event.addDomListener(element, 'mouseleave', function (e) {
if (map.hover) {
map.setOptions({zoomControl: false});
map.hover = false;
}
});
marker = new google.maps.Marker({map: map});
updatePosition();
}
if (window.google && window.google.maps) {
initializeMap();
}
else {
window.gmap_initialize = initializeMap;
head.js("https://maps.googleapis.com/maps/api/js?v=3.exp&sensor=false&callback=gmap_initialize");
}
}
this.onSettingsChanged = function (newSettings) {
currentSettings = newSettings;
}
this.onCalculatedValueChanged = function (settingName, newValue) {
if (settingName == "lat") {
currentPosition.lat = newValue;
}
else if (settingName == "lon") {
currentPosition.lon = newValue;
}
updatePosition();
}
this.onDispose = function () {
}
this.getHeight = function () {
return 4;
}
this.onSettingsChanged(settings);
};
freeboard.loadWidgetPlugin({
type_name: "google_map",
display_name: "Google Map",
fill_size: true,
settings: [
{
name: "lat",
display_name: "Latitude",
type: "calculated"
},
{
name: "lon",
display_name: "Longitude",
type: "calculated"
}
],
newInstance: function (settings, newInstanceCallback) {
newInstanceCallback(new googleMapWidget(settings));
}
});
freeboard.addStyle('.html-widget', "white-space:normal;width:100%;height:100%");
var htmlWidget = function (settings) {
var self = this;
var htmlElement = $('<div class="html-widget"></div>');
var currentSettings = settings;
this.render = function (element) {
$(element).append(htmlElement);
}
this.onSettingsChanged = function (newSettings) {
currentSettings = newSettings;
}
this.onCalculatedValueChanged = function (settingName, newValue) {
if (settingName == "html") {
htmlElement.html(newValue);
}
}
this.onDispose = function () {
}
this.getHeight = function () {
return Number(currentSettings.height);
}
this.onSettingsChanged(settings);
};
freeboard.loadWidgetPlugin({
"type_name": "html",
"display_name": "HTML",
"fill_size": true,
"settings": [
{
"name": "html",
"display_name": "HTML",
"type": "calculated",
"description": "Can be literal HTML, or javascript that outputs HTML."
},
{
"name": "height",
"display_name": "Height Blocks",
"type": "number",
"default_value": 4,
"description": "A height block is around 60 pixels"
}
],
newInstance: function (settings, newInstanceCallback) {
newInstanceCallback(new htmlWidget(settings));
}
});
}());