-
Emanuele Laface authoredEmanuele Laface authored
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
freeboard.js 16.57 KiB
// ┌────────────────────────────────────────────────────────────────────┐ \\
// │ 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. │ \\
// └────────────────────────────────────────────────────────────────────┘ \\
// Jquery plugin to watch for attribute changes
(function($)
{
function isDOMAttrModifiedSupported()
{
var p = document.createElement('p');
var flag = false;
if(p.addEventListener)
{
p.addEventListener('DOMAttrModified', function()
{
flag = true
}, false);
}
else if(p.attachEvent)
{
p.attachEvent('onDOMAttrModified', function()
{
flag = true
});
}
else
{
return false;
}
p.setAttribute('id', 'target');
return flag;
}
function checkAttributes(chkAttr, e)
{
if(chkAttr)
{
var attributes = this.data('attr-old-value');
if(e.attributeName.indexOf('style') >= 0)
{
if(!attributes['style'])
{
attributes['style'] = {};
} //initialize
var keys = e.attributeName.split('.');
e.attributeName = keys[0];
e.oldValue = attributes['style'][keys[1]]; //old value
e.newValue = keys[1] + ':' + this.prop("style")[$.camelCase(keys[1])]; //new value
attributes['style'][keys[1]] = e.newValue;
}
else
{
e.oldValue = attributes[e.attributeName];
e.newValue = this.attr(e.attributeName);
attributes[e.attributeName] = e.newValue;
}
this.data('attr-old-value', attributes); //update the old value object
}
}
//initialize Mutation Observer
var MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
$.fn.attrchange = function(o)
{
var cfg = {
trackValues: false,
callback : $.noop
};
//for backward compatibility
if(typeof o === "function")
{
cfg.callback = o;
}
else
{
$.extend(cfg, o);
}
if(cfg.trackValues)
{ //get attributes old value
$(this).each(function(i, el)
{
var attributes = {};
for(var attr, i = 0, attrs = el.attributes, l = attrs.length; i < l; i++)
{
attr = attrs.item(i);
attributes[attr.nodeName] = attr.value;
}
$(this).data('attr-old-value', attributes);
});
}
if(MutationObserver)
{ //Modern Browsers supporting MutationObserver
/*
Mutation Observer is still new and not supported by all browsers.
http://lists.w3.org/Archives/Public/public-webapps/2011JulSep/1622.html
*/
var mOptions = {
subtree : false,
attributes : true,
attributeOldValue: cfg.trackValues
};
var observer = new MutationObserver(function(mutations)
{
mutations.forEach(function(e)
{
var _this = e.target;
//get new value if trackValues is true
if(cfg.trackValues)
{
/**
* @KNOWN_ISSUE: The new value is buggy for STYLE attribute as we don't have
* any additional information on which style is getting updated.
* */
e.newValue = $(_this).attr(e.attributeName);
}
cfg.callback.call(_this, e);
});
});
return this.each(function()
{
observer.observe(this, mOptions);
});
}
else if(isDOMAttrModifiedSupported())
{ //Opera
//Good old Mutation Events but the performance is no good
//http://hacks.mozilla.org/2012/05/dom-mutationobserver-reacting-to-dom-changes-without-killing-browser-performance/
return this.on('DOMAttrModified', function(event)
{
if(event.originalEvent)
{
event = event.originalEvent;
} //jQuery normalization is not required for us
event.attributeName = event.attrName; //property names to be consistent with MutationObserver
event.oldValue = event.prevValue; //property names to be consistent with MutationObserver
cfg.callback.call(this, event);
});
}
else if('onpropertychange' in document.body)
{ //works only in IE
return this.on('propertychange', function(e)
{
e.attributeName = window.event.propertyName;
//to set the attr old value
checkAttributes.call($(this), cfg.trackValues, e);
cfg.callback.call(this, e);
});
}
return this;
}
})(jQuery);
(function(jQuery) {
jQuery.eventEmitter = {
_JQInit: function() {
this._JQ = jQuery(this);
},
emit: function(evt, data) {
!this._JQ && this._JQInit();
this._JQ.trigger(evt, data);
},
once: function(evt, handler) {
!this._JQ && this._JQInit();
this._JQ.one(evt, handler);
},
on: function(evt, handler) {
!this._JQ && this._JQInit();
this._JQ.bind(evt, handler);
},
off: function(evt, handler) {
!this._JQ && this._JQInit();
this._JQ.unbind(evt, handler);
}
};
}(jQuery));
var freeboard = (function()
{
var datasourcePlugins = {};
var widgetPlugins = {};
var freeboardUI = new FreeboardUI();
var theFreeboardModel = new FreeboardModel(datasourcePlugins, widgetPlugins, freeboardUI);
var jsEditor = new JSEditor();
var valueEditor = new ValueEditor(theFreeboardModel);
var pluginEditor = new PluginEditor(jsEditor, valueEditor);
var developerConsole = new DeveloperConsole(theFreeboardModel);
var currentStyle = {
values: {
"font-family": '"HelveticaNeue-UltraLight", "Helvetica Neue Ultra Light", "Helvetica Neue", sans-serif',
"color" : "#d3d4d4",
"font-weight": 100
}
};
ko.bindingHandlers.pluginEditor = {
init: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext)
{
var options = ko.unwrap(valueAccessor());
var types = {};
var settings = undefined;
var title = "";
if(options.type == 'datasource')
{
types = datasourcePlugins;
title = "Datasource";
}
else if(options.type == 'widget')
{
types = widgetPlugins;
title = "Widget";
}
else if(options.type == 'pane')
{
title = "Pane";
}
$(element).click(function(event)
{
if(options.operation == 'delete')
{
var phraseElement = $('<p>Are you sure you want to delete this ' + title + '?</p>');
new DialogBox(phraseElement, "Confirm Delete", "Yes", "No", function()
{
if(options.type == 'datasource')
{
theFreeboardModel.deleteDatasource(viewModel);
}
else if(options.type == 'widget')
{
theFreeboardModel.deleteWidget(viewModel);
}
else if(options.type == 'pane')
{
theFreeboardModel.deletePane(viewModel);
}
});
}
else
{
var instanceType = undefined;
if(options.type == 'datasource')
{
if(options.operation == 'add')
{
settings = {};
}
else
{
instanceType = viewModel.type();
settings = viewModel.settings();
settings.name = viewModel.name();
}
}
else if(options.type == 'widget')
{
if(options.operation == 'add')
{
settings = {};
}
else
{
instanceType = viewModel.type();
settings = viewModel.settings();
}
}
else if(options.type == 'pane')
{
settings = {};
if(options.operation == 'edit')
{
settings.title = viewModel.title();
settings.col_width = viewModel.col_width();
}
types = {
settings: {
settings: [
{
name : "title",
display_name: "Title",
type : "text"
},
{
name : "col_width",
display_name : "Columns",
type : "integer",
default_value : 1,
required : true
}
]
}
}
}
pluginEditor.createPluginEditor(title, types, instanceType, settings, function(newSettings)
{
if(options.operation == 'add')
{
if(options.type == 'datasource')
{
var newViewModel = new DatasourceModel(theFreeboardModel, datasourcePlugins);
theFreeboardModel.addDatasource(newViewModel);
newViewModel.name(newSettings.settings.name);
delete newSettings.settings.name;
newViewModel.settings(newSettings.settings);
newViewModel.type(newSettings.type);
}
else if(options.type == 'widget')
{
var newViewModel = new WidgetModel(theFreeboardModel, widgetPlugins);
newViewModel.settings(newSettings.settings);
newViewModel.type(newSettings.type);
viewModel.widgets.push(newViewModel);
freeboardUI.attachWidgetEditIcons(element);
}
}
else if(options.operation == 'edit')
{
if(options.type == 'pane')
{
viewModel.title(newSettings.settings.title);
viewModel.col_width(newSettings.settings.col_width);
freeboardUI.processResize(false);
}
else
{
if(options.type == 'datasource')
{
viewModel.name(newSettings.settings.name);
delete newSettings.settings.name;
}
viewModel.type(newSettings.type);
viewModel.settings(newSettings.settings);
}
}
});
}
});
}
}
ko.virtualElements.allowedBindings.datasourceTypeSettings = true;
ko.bindingHandlers.datasourceTypeSettings = {
update: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext)
{
processPluginSettings(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext);
}
}
ko.bindingHandlers.pane = {
init : function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext)
{
if(theFreeboardModel.isEditing())
{
$(element).css({cursor: "pointer"});
}
freeboardUI.addPane(element, viewModel, bindingContext.$root.isEditing());
},
update: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext)
{
// If pane has been removed
if(theFreeboardModel.panes.indexOf(viewModel) == -1)
{
freeboardUI.removePane(element);
}
freeboardUI.updatePane(element, viewModel);
}
}
ko.bindingHandlers.widget = {
init : function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext)
{
if(theFreeboardModel.isEditing())
{
freeboardUI.attachWidgetEditIcons($(element).parent());
}
},
update: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext)
{
if(viewModel.shouldRender())
{
$(element).empty();
viewModel.render(element);
}
}
}
function getParameterByName(name)
{
name = name.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]");
var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"), results = regex.exec(location.search);
return results == null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
}
$(function()
{ //DOM Ready
// Show the loading indicator when we first load
freeboardUI.showLoadingIndicator(true);
var resizeTimer;
function resizeEnd()
{
freeboardUI.processResize(true);
}
$(window).resize(function() {
clearTimeout(resizeTimer);
resizeTimer = setTimeout(resizeEnd, 500);
});
});
// PUBLIC FUNCTIONS
return {
initialize : function(allowEdit, finishedCallback)
{
ko.applyBindings(theFreeboardModel);
// Check to see if we have a query param called load. If so, we should load that dashboard initially
// var freeboardLocation = getParameterByName("load");
var freeboardLocation = "dashboard.json";
if(freeboardLocation != "")
{
$.ajax({
url : freeboardLocation,
success: function(data)
{
theFreeboardModel.loadDashboard(data);
if(_.isFunction(finishedCallback))
{
finishedCallback();
}
}
});
}
else
{
theFreeboardModel.allow_edit(allowEdit);
theFreeboardModel.setEditing(allowEdit);
freeboardUI.showLoadingIndicator(false);
if(_.isFunction(finishedCallback))
{
finishedCallback();
}
freeboard.emit("initialized");
}
},
newDashboard : function()
{
theFreeboardModel.loadDashboard({allow_edit: true});
},
loadDashboard : function(configuration, callback)
{
theFreeboardModel.loadDashboard(configuration, callback);
},
serialize : function()
{
return theFreeboardModel.serialize();
},
setEditing : function(editing, animate)
{
theFreeboardModel.setEditing(editing, animate);
},
isEditing : function()
{
return theFreeboardModel.isEditing();
},
loadDatasourcePlugin: function(plugin)
{
if(_.isUndefined(plugin.display_name))
{
plugin.display_name = plugin.type_name;
}
// Add a required setting called name to the beginning
plugin.settings.unshift({
name : "name",
display_name : "Name",
type : "text",
required : true
});
theFreeboardModel.addPluginSource(plugin.source);
datasourcePlugins[plugin.type_name] = plugin;
theFreeboardModel._datasourceTypes.valueHasMutated();
},
resize : function()
{
freeboardUI.processResize(true);
},
loadWidgetPlugin : function(plugin)
{
if(_.isUndefined(plugin.display_name))
{
plugin.display_name = plugin.type_name;
}
theFreeboardModel.addPluginSource(plugin.source);
widgetPlugins[plugin.type_name] = plugin;
theFreeboardModel._widgetTypes.valueHasMutated();
},
// To be used if freeboard is going to load dynamic assets from a different root URL
setAssetRoot : function(assetRoot)
{
jsEditor.setAssetRoot(assetRoot);
},
addStyle : function(selector, rules)
{
var styleString = selector + "{" + rules + "}";
var styleElement = $("style#fb-styles");
if(styleElement.length == 0)
{
styleElement = $('<style id="fb-styles" type="text/css"></style>');
$("head").append(styleElement);
}
if(styleElement[0].styleSheet)
{
styleElement[0].styleSheet.cssText += styleString;
}
else
{
styleElement.text(styleElement.text() + styleString);
}
},
showLoadingIndicator: function(show)
{
freeboardUI.showLoadingIndicator(show);
},
showDialog : function(contentElement, title, okTitle, cancelTitle, okCallback)
{
new DialogBox(contentElement, title, okTitle, cancelTitle, okCallback);
},
getDatasourceSettings : function(datasourceName)
{
var datasources = theFreeboardModel.datasources();
// Find the datasource with the name specified
var datasource = _.find(datasources, function(datasourceModel){
return (datasourceModel.name() === datasourceName);
});
if(datasource)
{
return datasource.settings();
}
else
{
return null;
}
},
setDatasourceSettings : function(datasourceName, settings)
{
var datasources = theFreeboardModel.datasources();
// Find the datasource with the name specified
var datasource = _.find(datasources, function(datasourceModel){
return (datasourceModel.name() === datasourceName);
});
if(!datasource)
{
console.log("Datasource not found");
return;
}
var combinedSettings = _.defaults(settings, datasource.settings());
datasource.settings(combinedSettings);
},
getStyleString : function(name)
{
var returnString = "";
_.each(currentStyle[name], function(value, name)
{
returnString = returnString + name + ":" + value + ";";
});
return returnString;
},
getStyleObject : function(name)
{
return currentStyle[name];
},
showDeveloperConsole : function()
{
developerConsole.showDeveloperConsole();
}
};
}());
$.extend(freeboard, jQuery.eventEmitter);