1730 lines
40 KiB
JavaScript
1730 lines
40 KiB
JavaScript
// 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);
|
|
|
|
var deccoboard = (function()
|
|
{
|
|
var datasourcePlugins = {};
|
|
var widgetPlugins = {};
|
|
var grid;
|
|
var deccoboardModel = new DeccoboardModel();
|
|
var currentStyle = {
|
|
values: {
|
|
"font-family": '"HelveticaNeue-UltraLight", "Helvetica Neue Ultra Light", "Helvetica Neue", sans-serif',
|
|
"color" : "#d3d4d4",
|
|
"font-weight": 100
|
|
}
|
|
};
|
|
|
|
var veDatasourceRegex = new RegExp(".*datasources[.]([^.]*)([.][^\\s]*)?$");
|
|
|
|
function createValueEditor(element)
|
|
{
|
|
var dropdown = null;
|
|
var selectedOptionIndex = 0;
|
|
|
|
$(element).bind("keyup mouseup decco-eval",function(event)
|
|
{
|
|
// Ignore arrow keys and enter keys
|
|
if(dropdown && event.type == "keyup" && (event.keyCode == 38 || event.keyCode == 40 || event.keyCode == 13))
|
|
{
|
|
event.preventDefault();
|
|
return;
|
|
}
|
|
|
|
var inputString = $(element).val().substring(0, $(element).getCaretPosition());
|
|
var match = veDatasourceRegex.exec(inputString);
|
|
|
|
var options = [];
|
|
var replacementString = undefined;
|
|
|
|
if(match)
|
|
{
|
|
if(match[1] == "") // List all datasources
|
|
{
|
|
_.each(deccoboardModel.datasources(), function(datasource)
|
|
{
|
|
options.push({value: datasource.name(), follow_char: "."});
|
|
});
|
|
}
|
|
else if(match[1] != "" && _.isUndefined(match[2])) // List partial datasources
|
|
{
|
|
replacementString = match[1];
|
|
|
|
_.each(deccoboardModel.datasources(), function(datasource)
|
|
{
|
|
|
|
var name = datasource.name();
|
|
|
|
if(name != match[1] && name.indexOf(match[1]) == 0)
|
|
{
|
|
options.push({value: name, follow_char: "."});
|
|
}
|
|
});
|
|
}
|
|
else
|
|
{
|
|
var datasource = _.find(deccoboardModel.datasources(), function(datasource)
|
|
{
|
|
return (datasource.name() === match[1]);
|
|
});
|
|
|
|
if(!_.isUndefined(datasource))
|
|
{
|
|
var dataPath = "";
|
|
|
|
if(!_.isUndefined(match[2]))
|
|
{
|
|
dataPath = match[2];
|
|
}
|
|
|
|
var dataPathItems = dataPath.split(".");
|
|
dataPath = "data";
|
|
|
|
for(var index = 1; index < dataPathItems.length - 1; index++)
|
|
{
|
|
if(dataPathItems[index] != "")
|
|
{
|
|
dataPath = dataPath + "." + dataPathItems[index];
|
|
}
|
|
}
|
|
|
|
var lastPathObject = _.last(dataPathItems);
|
|
|
|
// If the last character is a [, then ignore it
|
|
if(lastPathObject.charAt(lastPathObject.length - 1) == "[")
|
|
{
|
|
lastPathObject = lastPathObject.replace(/\[+$/, "");
|
|
dataPath = dataPath + "." + lastPathObject;
|
|
}
|
|
|
|
var dataValue = datasource.getDataRepresentation(dataPath);
|
|
|
|
if(_.isArray(dataValue))
|
|
{
|
|
for(var index = 0; index < dataValue.length; index++)
|
|
{
|
|
var followChar = "]";
|
|
|
|
if(_.isObject(dataValue[index]))
|
|
{
|
|
followChar = followChar + ".";
|
|
}
|
|
else if(_.isArray(dataValue[index]))
|
|
{
|
|
followChar = followChar + "[";
|
|
}
|
|
|
|
options.push({value: index, follow_char: followChar});
|
|
}
|
|
}
|
|
else if(_.isObject(dataValue))
|
|
{
|
|
replacementString = lastPathObject;
|
|
|
|
if(_.keys(dataValue).indexOf(replacementString) == -1)
|
|
{
|
|
_.each(dataValue, function(value, name)
|
|
{
|
|
if(name != lastPathObject && name.indexOf(lastPathObject) == 0)
|
|
{
|
|
var followChar = undefined;
|
|
|
|
if(_.isArray(value))
|
|
{
|
|
followChar = "[";
|
|
}
|
|
else if(_.isObject(value))
|
|
{
|
|
followChar = ".";
|
|
}
|
|
|
|
options.push({value: name, follow_char: followChar});
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if(options.length > 0)
|
|
{
|
|
if(!dropdown)
|
|
{
|
|
dropdown = $('<ul id="value-selector" class="value-dropdown"></ul>').insertAfter(element).width($(element).outerWidth() - 2).css("left", $(element).position().left).css("top", $(element).position().top + $(element).outerHeight() - 1);
|
|
}
|
|
|
|
dropdown.empty();
|
|
dropdown.scrollTop(0);
|
|
|
|
var selected = true;
|
|
selectedOptionIndex = 0;
|
|
|
|
var currentIndex = 0;
|
|
|
|
_.each(options, function(option)
|
|
{
|
|
var li = $('<li>' + option.value + '</li>').appendTo(dropdown).mouseenter(function()
|
|
{
|
|
$(this).trigger("decco-select");
|
|
}).mousedown(function(event)
|
|
{
|
|
$(this).trigger("decco-insertValue");
|
|
event.preventDefault();
|
|
}).data("decco-optionIndex", currentIndex).data("decco-optionValue", option.value).bind("decco-insertValue",function()
|
|
{
|
|
var optionValue = option.value;
|
|
|
|
if(!_.isUndefined(option.follow_char))
|
|
{
|
|
optionValue = optionValue + option.follow_char;
|
|
}
|
|
|
|
if(!_.isUndefined(replacementString))
|
|
{
|
|
var replacementIndex = inputString.lastIndexOf(replacementString);
|
|
|
|
if(replacementIndex != -1)
|
|
{
|
|
$(element).replaceTextAt(replacementIndex, replacementIndex + replacementString.length, optionValue);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$(element).insertAtCaret(optionValue);
|
|
}
|
|
|
|
$(element).triggerHandler("mouseup");
|
|
}).bind("decco-select", function()
|
|
{
|
|
$(this).parent().find("li.selected").removeClass("selected");
|
|
$(this).addClass("selected");
|
|
selectedOptionIndex = $(this).data("decco-optionIndex");
|
|
});
|
|
|
|
if(selected)
|
|
{
|
|
$(li).addClass("selected");
|
|
selected = false;
|
|
}
|
|
|
|
currentIndex++;
|
|
});
|
|
}
|
|
else
|
|
{
|
|
$(element).next("ul#value-selector").remove();
|
|
dropdown = null;
|
|
selectedOptionIndex = -1;
|
|
}
|
|
}).focusout(function()
|
|
{
|
|
$(element).next("ul#value-selector").remove();
|
|
dropdown = null;
|
|
selectedOptionIndex = -1;
|
|
}).bind("keydown", function(event)
|
|
{
|
|
|
|
if(dropdown)
|
|
{
|
|
if(event.keyCode == 38 || event.keyCode == 40) // Handle Arrow keys
|
|
{
|
|
event.preventDefault();
|
|
|
|
var optionItems = $(dropdown).find("li");
|
|
|
|
if(event.keyCode == 38) // Up Arrow
|
|
{
|
|
selectedOptionIndex--;
|
|
}
|
|
else if(event.keyCode == 40) // Down Arrow
|
|
{
|
|
selectedOptionIndex++;
|
|
}
|
|
|
|
if(selectedOptionIndex < 0)
|
|
{
|
|
selectedOptionIndex = optionItems.size() - 1;
|
|
}
|
|
else if(selectedOptionIndex >= optionItems.size())
|
|
{
|
|
selectedOptionIndex = 0;
|
|
}
|
|
|
|
var optionElement = $(optionItems).eq(selectedOptionIndex);
|
|
|
|
optionElement.trigger("decco-select");
|
|
$(dropdown).scrollTop($(optionElement).position().top);
|
|
}
|
|
else if(event.keyCode == 13) // Handle enter key
|
|
{
|
|
event.preventDefault();
|
|
|
|
if(selectedOptionIndex != -1)
|
|
{
|
|
$(dropdown).find("li").eq(selectedOptionIndex).trigger("decco-insertValue");
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
function createDialogBox(contentElement, title, okTitle, cancelTitle, okCallback)
|
|
{
|
|
var modal_width = 900;
|
|
|
|
// Initialize our modal overlay
|
|
var overlay = $('<div id="modal_overlay"></div>').css({ 'display': 'block', opacity: 0 });
|
|
|
|
var modalDialog = $('<div class="modal"></div>').css({
|
|
|
|
'display' : 'block',
|
|
'position' : 'fixed',
|
|
'opacity' : 0,
|
|
'width' : modal_width,
|
|
'z-index' : 11000,
|
|
'left' : 50 + '%',
|
|
'margin-left': -(modal_width / 2) + "px",
|
|
'top' : 120 + "px"
|
|
|
|
});
|
|
|
|
function closeModal()
|
|
{
|
|
overlay.fadeTo(200, 0.0, function()
|
|
{
|
|
$(this).remove();
|
|
});
|
|
modalDialog.fadeTo(200, 0.0, function()
|
|
{
|
|
$(this).remove();
|
|
});
|
|
}
|
|
|
|
// Create our header
|
|
modalDialog.append("<header><h1>" + title + "</h1></header>");
|
|
|
|
$('<section></section>').appendTo(modalDialog).append(contentElement);
|
|
|
|
// Create our footer
|
|
var footer = $('<footer></footer>').appendTo(modalDialog);
|
|
$('<span id="dialog-ok" class="text-button">' + okTitle + '</span>').appendTo(footer).click(function()
|
|
{
|
|
if(_.isFunction(okCallback))
|
|
{
|
|
okCallback();
|
|
}
|
|
|
|
closeModal();
|
|
});
|
|
|
|
$('<span id="dialog-cancel" class="text-button">' + cancelTitle + '</span>').appendTo(footer).click(function()
|
|
{
|
|
closeModal();
|
|
});
|
|
|
|
$("body").append([overlay, modalDialog]);
|
|
|
|
overlay.fadeTo(200, 0.8);
|
|
modalDialog.fadeTo(200, 1);
|
|
}
|
|
|
|
function createPluginEditor(title, pluginTypes, currentInstanceName, currentTypeName, currentSettingsValues, settingsSavedCallback)
|
|
{
|
|
var newSettings = {
|
|
name : currentInstanceName,
|
|
type : currentTypeName,
|
|
settings: {}
|
|
};
|
|
|
|
function createSettingRow(displayName)
|
|
{
|
|
var tr = $("<tr></tr>").appendTo(form);
|
|
|
|
tr.append('<td class="form-table-label"><label class="control-label">' + displayName + '</label></td>');
|
|
return $('<td class="form-table-value"></td>').appendTo(tr);
|
|
}
|
|
|
|
|
|
var form = $('<table class="form-table"></table>');
|
|
|
|
// Create our body
|
|
if(!_.isUndefined(currentInstanceName))
|
|
{
|
|
createSettingRow("Name").append($('<input type="text">').val(currentInstanceName).change(function()
|
|
{
|
|
newSettings.name = $(this).val();
|
|
}));
|
|
}
|
|
|
|
function createSettingsFromDefinition(settingsDefs)
|
|
{
|
|
_.each(settingsDefs, function(settingDef)
|
|
{
|
|
// Set a default value if one doesn't exist
|
|
if(!_.isUndefined(settingDef.default_value) && _.isUndefined(currentSettingsValues[settingDef.name]))
|
|
{
|
|
currentSettingsValues[settingDef.name] = settingDef.default_value;
|
|
}
|
|
|
|
var displayName = settingDef.name;
|
|
|
|
if(!_.isUndefined(settingDef.display_name))
|
|
{
|
|
displayName = settingDef.display_name;
|
|
}
|
|
|
|
var valueCell = createSettingRow(displayName);
|
|
|
|
switch (settingDef.type)
|
|
{
|
|
case "array":
|
|
{
|
|
var subTableDiv = $('<div class="form-table-value-subtable"></div>').appendTo(valueCell);
|
|
|
|
var subTable = $('<table class="table table-condensed sub-table"></table>').appendTo(subTableDiv);
|
|
var subTableHead = $("<thead></thead>").hide().appendTo(subTable);
|
|
var subTableHeadRow = $("<tr></tr>").appendTo(subTableHead);
|
|
var subTableBody = $('<tbody></tbody>').appendTo(subTable);
|
|
|
|
var currentSubSettingValues = [];
|
|
|
|
// Create our headers
|
|
_.each(settingDef.settings, function(subSettingDef)
|
|
{
|
|
var subsettingDisplayName = subSettingDef.name;
|
|
|
|
if(!_.isUndefined(subSettingDef.display_name))
|
|
{
|
|
subsettingDisplayName = subSettingDef.display_name;
|
|
}
|
|
|
|
$('<th>' + subsettingDisplayName + '</th>').appendTo(subTableHeadRow);
|
|
});
|
|
|
|
if(settingDef.name in currentSettingsValues)
|
|
{
|
|
currentSubSettingValues = currentSettingsValues[settingDef.name];
|
|
}
|
|
|
|
function processHeaderVisibility()
|
|
{
|
|
if(newSettings.settings[settingDef.name].length > 0)
|
|
{
|
|
subTableHead.show();
|
|
}
|
|
else
|
|
{
|
|
subTableHead.hide();
|
|
}
|
|
}
|
|
|
|
function createSubsettingRow(subsettingValue)
|
|
{
|
|
var subsettingRow = $('<tr></tr>').appendTo(subTableBody);
|
|
|
|
var newSetting = {};
|
|
|
|
if(!_.isArray(newSettings.settings[settingDef.name]))
|
|
{
|
|
newSettings.settings[settingDef.name] = [];
|
|
}
|
|
|
|
newSettings.settings[settingDef.name].push(newSetting);
|
|
|
|
_.each(settingDef.settings, function(subSettingDef)
|
|
{
|
|
var subsettingCol = $('<td></td>').appendTo(subsettingRow);
|
|
var subsettingValueString = "";
|
|
|
|
if(!_.isUndefined(subsettingValue[subSettingDef.name]))
|
|
{
|
|
subsettingValueString = subsettingValue[subSettingDef.name];
|
|
}
|
|
|
|
newSetting[subSettingDef.name] = subsettingValueString;
|
|
|
|
$('<input class="table-row-value" type="text">').appendTo(subsettingCol).val(subsettingValueString).change(function()
|
|
{
|
|
newSetting[subSettingDef.name] = $(this).val();
|
|
});
|
|
});
|
|
|
|
subsettingRow.append($('<td class="table-row-operation"></td>').append($('<i class="icon-trash icon-white action-icon"></i>').click(function()
|
|
{
|
|
var subSettingIndex = newSettings.settings[settingDef.name].indexOf(newSetting);
|
|
|
|
if(subSettingIndex != -1)
|
|
{
|
|
newSettings.settings[settingDef.name].splice(subSettingIndex, 1);
|
|
subsettingRow.remove();
|
|
processHeaderVisibility();
|
|
}
|
|
})));
|
|
|
|
subTableDiv.scrollTop(subTableDiv[0].scrollHeight);
|
|
|
|
processHeaderVisibility();
|
|
}
|
|
|
|
$('<span class="table-operation text-button">ADD</span>').appendTo(valueCell).click(function(){
|
|
var newSubsettingValue = {};
|
|
|
|
_.each(settingDef.settings, function(subSettingDef){
|
|
newSubsettingValue[subSettingDef.name] = "";
|
|
});
|
|
|
|
createSubsettingRow(newSubsettingValue);
|
|
});
|
|
|
|
// Create our rows
|
|
_.each(currentSubSettingValues, function(currentSubSettingValue, subSettingIndex)
|
|
{
|
|
createSubsettingRow(currentSubSettingValue);
|
|
});
|
|
|
|
break;
|
|
}
|
|
case "boolean":
|
|
{
|
|
newSettings.settings[settingDef.name] = currentSettingsValues[settingDef.name];
|
|
|
|
var input = $('<input type="checkbox">').appendTo(valueCell).change(function()
|
|
{
|
|
newSettings.settings[settingDef.name] = this.checked;
|
|
});
|
|
|
|
if(settingDef.name in currentSettingsValues)
|
|
{
|
|
input.prop("checked", currentSettingsValues[settingDef.name]);
|
|
}
|
|
|
|
break;
|
|
}
|
|
case "option":
|
|
{
|
|
var defaultValue = currentSettingsValues[settingDef.name];
|
|
|
|
var input = $('<select></select>').appendTo(valueCell).change(function()
|
|
{
|
|
newSettings.settings[settingDef.name] = $(this).val();
|
|
});
|
|
|
|
_.each(settingDef.options, function(option)
|
|
{
|
|
|
|
var optionName;
|
|
var optionValue;
|
|
|
|
if(_.isObject(option))
|
|
{
|
|
optionName = option.name;
|
|
optionValue = option.value;
|
|
}
|
|
else
|
|
{
|
|
optionName = option;
|
|
}
|
|
|
|
if(_.isUndefined(optionValue))
|
|
{
|
|
optionValue = optionName;
|
|
}
|
|
|
|
if(_.isUndefined(defaultValue))
|
|
{
|
|
defaultValue = optionValue;
|
|
}
|
|
|
|
$("<option></option>").text(optionName).attr("value", optionValue).appendTo(input);
|
|
});
|
|
|
|
newSettings.settings[settingDef.name] = defaultValue;
|
|
|
|
if(settingDef.name in currentSettingsValues)
|
|
{
|
|
input.val(currentSettingsValues[settingDef.name]);
|
|
}
|
|
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
newSettings.settings[settingDef.name] = currentSettingsValues[settingDef.name];
|
|
|
|
var input = $('<input type="text">').appendTo(valueCell).change(function()
|
|
{
|
|
newSettings.settings[settingDef.name] = $(this).val();
|
|
});
|
|
|
|
if(!_.isUndefined(settingDef.suffix))
|
|
{
|
|
input.addClass("small align-right");
|
|
$('<div class="input-suffix">' + settingDef.suffix + '</div>').appendTo(valueCell);
|
|
}
|
|
|
|
if(settingDef.name in currentSettingsValues)
|
|
{
|
|
input.val(currentSettingsValues[settingDef.name]);
|
|
}
|
|
|
|
if(settingDef.type == "calculated")
|
|
{
|
|
createValueEditor(input);
|
|
|
|
$(valueCell).append($('<div class="input-suffix text-button">+ Datasource</div>').mousedown(function(e)
|
|
{
|
|
e.preventDefault();
|
|
$(input).focus();
|
|
$(input).insertAtCaret("datasources.");
|
|
$(input).trigger("decco-eval");
|
|
}));
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
createDialogBox(form, title, "Save", "Cancel", function()
|
|
{
|
|
if(_.isFunction(settingsSavedCallback))
|
|
{
|
|
settingsSavedCallback(newSettings);
|
|
}
|
|
});
|
|
|
|
if(_.keys(pluginTypes).length > 1)
|
|
{
|
|
var typeRow = createSettingRow("Type");
|
|
var typeSelect = $('<select></select>').appendTo(typeRow);
|
|
|
|
typeSelect.append($("<option>Select a type...</option>").attr("value", "undefined"));
|
|
|
|
_.each(pluginTypes, function(pluginType)
|
|
{
|
|
typeSelect.append($("<option></option>").text(pluginType.display_name).attr("value", pluginType.type_name));
|
|
});
|
|
|
|
typeSelect.change(function()
|
|
{
|
|
newSettings.type = $(this).val();
|
|
newSettings.settings = {};
|
|
|
|
// Remove all the previous settings
|
|
typeRow.parent().nextAll().remove();
|
|
|
|
var currentType = pluginTypes[typeSelect.val()];
|
|
|
|
if(_.isUndefined(currentType))
|
|
{
|
|
$("#dialog-ok").hide();
|
|
}
|
|
else
|
|
{
|
|
$("#dialog-ok").show();
|
|
createSettingsFromDefinition(currentType.settings);
|
|
}
|
|
});
|
|
|
|
if(_.isUndefined(currentTypeName))
|
|
{
|
|
$("#dialog-ok").hide();
|
|
}
|
|
else
|
|
{
|
|
$("#dialog-ok").show();
|
|
typeSelect.val(currentTypeName).trigger("change");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
createSettingsFromDefinition(pluginTypes.settings);
|
|
}
|
|
|
|
|
|
}
|
|
|
|
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>');
|
|
createDialogBox(phraseElement, "Confirm Delete", "Yes", "No", function()
|
|
{
|
|
|
|
if(options.type == 'datasource')
|
|
{
|
|
deccoboardModel.deleteDatasource(viewModel);
|
|
}
|
|
else if(options.type == 'widget')
|
|
{
|
|
deccoboardModel.deleteWidget(viewModel);
|
|
}
|
|
else if(options.type == 'pane')
|
|
{
|
|
deccoboardModel.deletePane(viewModel);
|
|
}
|
|
|
|
});
|
|
}
|
|
else
|
|
{
|
|
var instanceName = undefined;
|
|
var instanceType = undefined;
|
|
|
|
if(options.type == 'datasource')
|
|
{
|
|
if(options.operation == 'add')
|
|
{
|
|
settings = {};
|
|
instanceName = "";
|
|
}
|
|
else
|
|
{
|
|
instanceName = viewModel.name();
|
|
instanceType = viewModel.type();
|
|
settings = viewModel.settings();
|
|
}
|
|
}
|
|
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();
|
|
}
|
|
|
|
types = {
|
|
settings: [
|
|
{
|
|
name : "title",
|
|
display_name: "Title",
|
|
type : "text"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
|
|
createPluginEditor(title, types, instanceName, instanceType, settings, function(newSettings)
|
|
{
|
|
if(options.operation == 'add')
|
|
{
|
|
if(options.type == 'datasource')
|
|
{
|
|
var newViewModel = new DatasourceModel();
|
|
deccoboardModel.addDatasource(newViewModel);
|
|
|
|
newViewModel.settings(newSettings.settings);
|
|
newViewModel.name(newSettings.name);
|
|
newViewModel.type(newSettings.type);
|
|
}
|
|
else if(options.type == 'widget')
|
|
{
|
|
var newViewModel = new WidgetModel();
|
|
newViewModel.settings(newSettings.settings);
|
|
newViewModel.type(newSettings.type);
|
|
|
|
viewModel.widgets.push(newViewModel);
|
|
|
|
attachWidgetEditIcons(element);
|
|
}
|
|
}
|
|
else if(options.operation == 'edit')
|
|
{
|
|
if(options.type == 'pane')
|
|
{
|
|
viewModel.title(newSettings.settings.title);
|
|
}
|
|
else
|
|
{
|
|
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.grid = {
|
|
init: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext)
|
|
{
|
|
// Initialize our grid
|
|
grid = $(element).gridster({
|
|
widget_margins : [10, 10],
|
|
widget_base_dimensions: [300, 40]
|
|
}).data("gridster");
|
|
|
|
grid.disable();
|
|
}
|
|
}
|
|
|
|
ko.bindingHandlers.pane = {
|
|
init : function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext)
|
|
{
|
|
if(deccoboardModel.isEditing())
|
|
{
|
|
$(element).css({cursor: "pointer"});
|
|
}
|
|
|
|
grid.add_widget(element, viewModel.width(), viewModel.getCalculatedHeight(), viewModel.col(), viewModel.row());
|
|
|
|
if(bindingContext.$root.isEditing())
|
|
{
|
|
showPaneEditIcons(true);
|
|
}
|
|
|
|
$(element).data("deccoPaneModel", viewModel);
|
|
|
|
$(element).attrchange({
|
|
trackValues: true,
|
|
callback : function(event)
|
|
{
|
|
if(event.attributeName == "data-row")
|
|
{
|
|
viewModel.row(event.newValue);
|
|
}
|
|
else if(event.attributeName == "data-col")
|
|
{
|
|
viewModel.col(event.newValue);
|
|
}
|
|
}
|
|
});
|
|
},
|
|
update: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext)
|
|
{
|
|
// If pane has been removed
|
|
if(deccoboardModel.panes.indexOf(viewModel) == -1)
|
|
{
|
|
grid.remove_widget(element);
|
|
}
|
|
// If widget has been added or removed
|
|
else
|
|
{
|
|
grid.resize_widget($(element), undefined, viewModel.getCalculatedHeight());
|
|
}
|
|
}
|
|
}
|
|
|
|
ko.bindingHandlers.widget = {
|
|
init : function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext)
|
|
{
|
|
if(deccoboardModel.isEditing())
|
|
{
|
|
attachWidgetEditIcons($(element).parent());
|
|
}
|
|
},
|
|
update: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext)
|
|
{
|
|
if(viewModel.shouldRender())
|
|
{
|
|
$(element).empty();
|
|
viewModel.render(element);
|
|
}
|
|
}
|
|
}
|
|
|
|
function DeccoboardModel()
|
|
{
|
|
var self = this;
|
|
|
|
this.isEditing = ko.observable(false);
|
|
this.allow_edit = ko.observable(true);
|
|
|
|
this.datasources = ko.observableArray();
|
|
this.panes = ko.observableArray();
|
|
this.datasourceData = {};
|
|
this.processDatasourceUpdate = function(datasourceModel, newData)
|
|
{
|
|
var datasourceName = datasourceModel.name();
|
|
|
|
self.datasourceData[datasourceName] = newData;
|
|
|
|
_.each(deccoboardModel.panes(), function(pane)
|
|
{
|
|
_.each(pane.widgets(), function(widget)
|
|
{
|
|
widget.processDatasourceUpdate(datasourceName);
|
|
});
|
|
});
|
|
}
|
|
|
|
this._datasourceTypes = ko.observable();
|
|
this.datasourceTypes = ko.computed({
|
|
read: function()
|
|
{
|
|
self._datasourceTypes();
|
|
|
|
var returnTypes = [];
|
|
|
|
_.each(datasourcePlugins, function(datasourcePluginType)
|
|
{
|
|
var typeName = datasourcePluginType.type_name;
|
|
var displayName = typeName;
|
|
|
|
if(!_.isUndefined(datasourcePluginType.display_name))
|
|
{
|
|
displayName = datasourcePluginType.display_name;
|
|
}
|
|
|
|
returnTypes.push({
|
|
name : typeName,
|
|
display_name: displayName
|
|
});
|
|
});
|
|
|
|
return returnTypes;
|
|
}
|
|
});
|
|
|
|
this._widgetTypes = ko.observable();
|
|
this.widgetTypes = ko.computed({
|
|
read: function()
|
|
{
|
|
self._widgetTypes();
|
|
|
|
var returnTypes = [];
|
|
|
|
_.each(widgetPlugins, function(widgetPluginType)
|
|
{
|
|
var typeName = widgetPluginType.type_name;
|
|
var displayName = typeName;
|
|
|
|
if(!_.isUndefined(widgetPluginType.display_name))
|
|
{
|
|
displayName = widgetPluginType.display_name;
|
|
}
|
|
|
|
returnTypes.push({
|
|
name : typeName,
|
|
display_name: displayName
|
|
});
|
|
});
|
|
|
|
return returnTypes;
|
|
}
|
|
});
|
|
|
|
this.serialize = function()
|
|
{
|
|
var panes = [];
|
|
|
|
_.each(self.panes(), function(pane)
|
|
{
|
|
panes.push(pane.serialize());
|
|
});
|
|
|
|
var datasources = [];
|
|
|
|
_.each(self.datasources(), function(datasource)
|
|
{
|
|
datasources.push(datasource.serialize());
|
|
});
|
|
|
|
return {
|
|
allow_edit : self.allow_edit(),
|
|
panes : panes,
|
|
datasources: datasources
|
|
};
|
|
}
|
|
|
|
this.deserialize = function(object)
|
|
{
|
|
this.datasources.removeAll();
|
|
this.panes.removeAll();
|
|
|
|
if(!_.isUndefined(object.allow_edit))
|
|
{
|
|
self.allow_edit(object.allow_edit);
|
|
}
|
|
|
|
_.each(object.datasources, function(datasourceConfig)
|
|
{
|
|
var datasource = new DatasourceModel();
|
|
datasource.deserialize(datasourceConfig);
|
|
self.addDatasource(datasource);
|
|
});
|
|
|
|
_.each(object.panes, function(paneConfig)
|
|
{
|
|
var pane = new PaneModel();
|
|
pane.deserialize(paneConfig);
|
|
self.panes.push(pane);
|
|
});
|
|
}
|
|
|
|
this.loadDashboard = function()
|
|
{
|
|
// Check for the various File API support.
|
|
if(window.File && window.FileReader && window.FileList && window.Blob)
|
|
{
|
|
var input = document.createElement('input');
|
|
input.type = "file";
|
|
$(input).on("change", function(event)
|
|
{
|
|
var files = event.target.files;
|
|
|
|
if(files && files.length > 0)
|
|
{
|
|
var file = files[0];
|
|
var reader = new FileReader();
|
|
|
|
reader.addEventListener("load", function(fileReaderEvent)
|
|
{
|
|
|
|
var textFile = fileReaderEvent.target;
|
|
var jsonObject = JSON.parse(textFile.result);
|
|
|
|
self.deserialize(jsonObject);
|
|
});
|
|
|
|
reader.readAsText(file);
|
|
}
|
|
|
|
});
|
|
$(input).trigger("click");
|
|
}
|
|
else
|
|
{
|
|
alert('Unable to load a file in this browser.');
|
|
}
|
|
}
|
|
|
|
this.saveDashboard = function()
|
|
{
|
|
var contentType = 'application/octet-stream';
|
|
var a = document.createElement('a');
|
|
var blob = new Blob([JSON.stringify(self.serialize())], {'type': contentType});
|
|
a.href = window.URL.createObjectURL(blob);
|
|
a.download = "dashboard.json";
|
|
a.click();
|
|
}
|
|
|
|
this.addDatasource = function(datasource)
|
|
{
|
|
self.datasources.push(datasource);
|
|
}
|
|
|
|
this.deleteDatasource = function(datasource)
|
|
{
|
|
delete self.datasourceData[datasource.name()];
|
|
datasource.dispose();
|
|
self.datasources.remove(datasource);
|
|
}
|
|
|
|
this.createPane = function()
|
|
{
|
|
var newPane = new PaneModel();
|
|
self.addPane(newPane);
|
|
}
|
|
|
|
this.addPane = function(pane)
|
|
{
|
|
self.panes.push(pane);
|
|
}
|
|
|
|
this.deletePane = function(pane)
|
|
{
|
|
pane.dispose();
|
|
self.panes.remove(pane);
|
|
}
|
|
|
|
this.deleteWidget = function(widget)
|
|
{
|
|
ko.utils.arrayForEach(self.panes(), function(pane)
|
|
{
|
|
pane.widgets.remove(widget);
|
|
});
|
|
|
|
widget.dispose();
|
|
}
|
|
|
|
this.toggleEditing = function()
|
|
{
|
|
var editing = !self.isEditing();
|
|
self.isEditing(editing);
|
|
|
|
if(!editing)
|
|
{
|
|
$(".gridster .gs_w").css({cursor: "default"});
|
|
$("#main-header").animate({top: "-280px"}, 250);
|
|
$(".gridster").animate({"margin-top": "20px"}, 250);
|
|
$("#main-header").data().shown = false;
|
|
|
|
$(".sub-section").unbind();
|
|
|
|
grid.disable();
|
|
}
|
|
else
|
|
{
|
|
$(".gridster .gs_w").css({cursor: "pointer"});
|
|
$("#main-header").animate({top: "0px"}, 250);
|
|
$(".gridster").animate({"margin-top": "300px"}, 250);
|
|
$("#main-header").data().shown = true;
|
|
|
|
attachWidgetEditIcons($(".sub-section"));
|
|
|
|
grid.enable();
|
|
}
|
|
|
|
showPaneEditIcons(editing);
|
|
}
|
|
}
|
|
|
|
function PaneModel()
|
|
{
|
|
var self = this;
|
|
|
|
this.title = ko.observable();
|
|
this.width = ko.observable(1);
|
|
this.row = ko.observable(1);
|
|
this.col = ko.observable(1);
|
|
this.widgets = ko.observableArray();
|
|
|
|
this.addWidget = function(widget)
|
|
{
|
|
this.widgets.push(widget);
|
|
}
|
|
|
|
this.getCalculatedHeight = function()
|
|
{
|
|
var sumHeights = _.reduce(self.widgets(), function(memo, widget)
|
|
{
|
|
return memo + widget.height();
|
|
}, 0);
|
|
|
|
return Math.max(2, sumHeights + 1);
|
|
}
|
|
|
|
this.serialize = function()
|
|
{
|
|
var widgets = [];
|
|
|
|
_.each(self.widgets(), function(widget)
|
|
{
|
|
widgets.push(widget.serialize());
|
|
});
|
|
|
|
return {
|
|
title : self.title(),
|
|
width : self.width(),
|
|
row : self.row(),
|
|
col : self.col(),
|
|
widgets: widgets
|
|
};
|
|
}
|
|
|
|
this.deserialize = function(object)
|
|
{
|
|
self.title(object.title);
|
|
self.width(object.width);
|
|
self.row(object.row);
|
|
self.col(object.col);
|
|
|
|
_.each(object.widgets, function(widgetConfig)
|
|
{
|
|
var widget = new WidgetModel();
|
|
widget.deserialize(widgetConfig);
|
|
self.widgets.push(widget);
|
|
});
|
|
}
|
|
|
|
this.dispose = function()
|
|
{
|
|
ko.utils.arrayForEach(self.widgets(), function(widget)
|
|
{
|
|
widget.dispose();
|
|
});
|
|
}
|
|
}
|
|
|
|
function WidgetModel()
|
|
{
|
|
function disposeWidgetInstance()
|
|
{
|
|
if(!_.isUndefined(self.widgetInstance))
|
|
{
|
|
if(_.isFunction(self.widgetInstance.onDispose))
|
|
{
|
|
self.widgetInstance.onDispose();
|
|
}
|
|
|
|
self.widgetInstance = undefined;
|
|
}
|
|
}
|
|
|
|
var self = this;
|
|
|
|
this.datasourceRefreshNotifications = {};
|
|
this.calculatedSettingScripts = {};
|
|
|
|
this.title = ko.observable();
|
|
|
|
this.type = ko.observable();
|
|
this.type.subscribe(function(newValue)
|
|
{
|
|
disposeWidgetInstance();
|
|
|
|
if((newValue in widgetPlugins) && _.isFunction(widgetPlugins[newValue].newInstance))
|
|
{
|
|
var widgetInstance = widgetPlugins[newValue].newInstance(self.settings(), self.updateCallback);
|
|
self.widgetInstance = widgetInstance;
|
|
self.shouldRender(true);
|
|
}
|
|
|
|
//self.updateCalculatedSettings();
|
|
self._heightUpdate.valueHasMutated();
|
|
});
|
|
|
|
this.settings = ko.observable({});
|
|
this.settings.subscribe(function(newValue)
|
|
{
|
|
if(!_.isUndefined(self.widgetInstance) && _.isFunction(self.widgetInstance.onSettingsChanged))
|
|
{
|
|
self.widgetInstance.onSettingsChanged(newValue);
|
|
}
|
|
|
|
self.updateCalculatedSettings();
|
|
self._heightUpdate.valueHasMutated();
|
|
});
|
|
|
|
this.processDatasourceUpdate = function(datasourceName)
|
|
{
|
|
var refreshSettingNames = self.datasourceRefreshNotifications[datasourceName];
|
|
|
|
if(_.isArray(refreshSettingNames))
|
|
{
|
|
_.each(refreshSettingNames, function(settingName)
|
|
{
|
|
self.processCalculatedSetting(settingName);
|
|
});
|
|
}
|
|
}
|
|
|
|
this.callValueFunction = function(theFunction)
|
|
{
|
|
return theFunction.call(undefined, deccoboardModel.datasourceData);
|
|
}
|
|
|
|
this.processCalculatedSetting = function(settingName)
|
|
{
|
|
if(_.isFunction(self.calculatedSettingScripts[settingName]))
|
|
{
|
|
var returnValue = undefined;
|
|
|
|
try
|
|
{
|
|
returnValue = self.callValueFunction(self.calculatedSettingScripts[settingName]);
|
|
}
|
|
catch(e)
|
|
{
|
|
var rawValue = self.settings()[settingName];
|
|
|
|
// If there is a reference error and the value just contains letters and numbers, then
|
|
if(e instanceof ReferenceError && (/^\w+$/).test(rawValue))
|
|
{
|
|
returnValue = rawValue;
|
|
}
|
|
}
|
|
|
|
if(!_.isUndefined(self.widgetInstance) && _.isFunction(self.widgetInstance.onCalculatedValueChanged) && !_.isUndefined(returnValue))
|
|
{
|
|
self.widgetInstance.onCalculatedValueChanged(settingName, returnValue);
|
|
}
|
|
}
|
|
}
|
|
|
|
this.updateCalculatedSettings = function()
|
|
{
|
|
self.datasourceRefreshNotifications = {};
|
|
self.calculatedSettingScripts = {};
|
|
|
|
if(_.isUndefined(self.type()))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Check for any calculated settings
|
|
var settingsDefs = widgetPlugins[self.type()].settings;
|
|
var datasourceRegex = new RegExp("datasources.(\\w+)", "g");
|
|
var currentSettings = self.settings();
|
|
|
|
_.each(settingsDefs, function(settingDef)
|
|
{
|
|
if(settingDef.type == "calculated")
|
|
{
|
|
var script = currentSettings[settingDef.name];
|
|
|
|
if(!_.isUndefined(script))
|
|
{
|
|
// If there is no return, add one
|
|
if((script.match(/;/g) || []).length <= 1 && script.indexOf("return") == -1)
|
|
{
|
|
script = "return " + script;
|
|
}
|
|
|
|
var valueFunction;
|
|
|
|
try
|
|
{
|
|
valueFunction = new Function("datasources", script);
|
|
}
|
|
catch(e)
|
|
{
|
|
var literalText = currentSettings[settingDef.name].replace(/"/g, '\\"');
|
|
|
|
// If the value function cannot be created, then go ahead and treat it as literal text
|
|
valueFunction = new Function("datasources", "return \"" + literalText + "\";");
|
|
}
|
|
|
|
self.calculatedSettingScripts[settingDef.name] = valueFunction;
|
|
self.processCalculatedSetting(settingDef.name);
|
|
|
|
// Are there any datasources we need to be subscribed to?
|
|
var matches;
|
|
|
|
while(matches = datasourceRegex.exec(script))
|
|
{
|
|
var refreshSettingNames = self.datasourceRefreshNotifications[matches[1]];
|
|
|
|
if(_.isUndefined(refreshSettingNames))
|
|
{
|
|
refreshSettingNames = [];
|
|
self.datasourceRefreshNotifications[matches[1]] = refreshSettingNames;
|
|
}
|
|
|
|
refreshSettingNames.push(settingDef.name);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
this._heightUpdate = ko.observable();
|
|
this.height = ko.computed({
|
|
read: function()
|
|
{
|
|
self._heightUpdate();
|
|
|
|
if(!_.isUndefined(self.widgetInstance) && _.isFunction(self.widgetInstance.getHeight))
|
|
{
|
|
return self.widgetInstance.getHeight();
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
});
|
|
|
|
this.shouldRender = ko.observable(false);
|
|
this.render = function(element)
|
|
{
|
|
self.shouldRender(false);
|
|
if(!_.isUndefined(self.widgetInstance) && _.isFunction(self.widgetInstance.render))
|
|
{
|
|
self.widgetInstance.render(element);
|
|
self.updateCalculatedSettings();
|
|
}
|
|
}
|
|
|
|
this.dispose = function()
|
|
{
|
|
|
|
}
|
|
|
|
this.serialize = function()
|
|
{
|
|
return {
|
|
title : self.title(),
|
|
type : self.type(),
|
|
settings: self.settings()
|
|
};
|
|
}
|
|
|
|
this.deserialize = function(object)
|
|
{
|
|
self.title(object.title);
|
|
self.type(object.type);
|
|
self.settings(object.settings);
|
|
}
|
|
}
|
|
|
|
function DatasourceModel()
|
|
{
|
|
var self = this;
|
|
|
|
function disposeDatasourceInstance()
|
|
{
|
|
if(!_.isUndefined(self.datasourceInstance))
|
|
{
|
|
if(_.isFunction(self.datasourceInstance.onDispose))
|
|
{
|
|
self.datasourceInstance.onDispose();
|
|
}
|
|
|
|
self.datasourceInstance = undefined;
|
|
}
|
|
}
|
|
|
|
this.name = ko.observable();
|
|
this.latestData = ko.observable();
|
|
this.settings = ko.observable({});
|
|
this.settings.subscribe(function(newValue)
|
|
{
|
|
if(!_.isUndefined(self.datasourceInstance) && _.isFunction(self.datasourceInstance.onSettingsChanged))
|
|
{
|
|
self.datasourceInstance.onSettingsChanged(newValue);
|
|
}
|
|
});
|
|
|
|
this.updateCallback = function(newData)
|
|
{
|
|
deccoboardModel.processDatasourceUpdate(self, newData);
|
|
|
|
self.latestData(newData);
|
|
|
|
var now = new Date();
|
|
self.last_updated(now.toLocaleTimeString());
|
|
}
|
|
|
|
this.type = ko.observable();
|
|
this.type.subscribe(function(newValue)
|
|
{
|
|
disposeDatasourceInstance();
|
|
|
|
if((newValue in datasourcePlugins) && _.isFunction(datasourcePlugins[newValue].newInstance))
|
|
{
|
|
var datasourceInstance = datasourcePlugins[newValue].newInstance(self.settings(), self.updateCallback);
|
|
self.datasourceInstance = datasourceInstance;
|
|
datasourceInstance.updateNow();
|
|
}
|
|
});
|
|
|
|
|
|
this.last_updated = ko.observable("never");
|
|
this.last_error = ko.observable();
|
|
|
|
this.serialize = function()
|
|
{
|
|
return {
|
|
name : self.name(),
|
|
type : self.type(),
|
|
settings: self.settings()
|
|
};
|
|
}
|
|
|
|
this.deserialize = function(object)
|
|
{
|
|
self.settings(object.settings);
|
|
self.name(object.name);
|
|
self.type(object.type);
|
|
}
|
|
|
|
this.getDataRepresentation = function(dataPath)
|
|
{
|
|
var valueFunction = new Function("data", "return " + dataPath + ";");
|
|
return valueFunction.call(undefined, self.latestData());
|
|
}
|
|
|
|
this.updateNow = function()
|
|
{
|
|
if(!_.isUndefined(self.datasourceInstance) && _.isFunction(self.datasourceInstance.updateNow))
|
|
{
|
|
self.datasourceInstance.updateNow();
|
|
}
|
|
}
|
|
|
|
this.dispose = function()
|
|
{
|
|
disposeDatasourceInstance();
|
|
}
|
|
}
|
|
|
|
function showPaneEditIcons(show)
|
|
{
|
|
if(show)
|
|
{
|
|
$(".widget-tools").css("display", "block").animate({opacity: 1.0}, 250);
|
|
}
|
|
else
|
|
{
|
|
$(".widget-tools").animate({opacity: 0.0}, 250, function()
|
|
{
|
|
$().css("display", "none");
|
|
});
|
|
}
|
|
}
|
|
|
|
function attachWidgetEditIcons(element)
|
|
{
|
|
$(element).hover(function()
|
|
{
|
|
showWidgetEditIcons(this, true);
|
|
}, function()
|
|
{
|
|
showWidgetEditIcons(this, false);
|
|
});
|
|
}
|
|
|
|
function showWidgetEditIcons(element, show)
|
|
{
|
|
if(show)
|
|
{
|
|
$(element).find(".sub-section-tools").fadeIn(250);
|
|
}
|
|
else
|
|
{
|
|
$(element).find(".sub-section-tools").fadeOut(250);
|
|
}
|
|
}
|
|
|
|
$(function()
|
|
{ //DOM Ready
|
|
|
|
ko.applyBindings(deccoboardModel);
|
|
|
|
if(deccoboardModel.allow_edit() && deccoboardModel.panes().length == 0)
|
|
{
|
|
deccoboardModel.toggleEditing();
|
|
}
|
|
|
|
// Fade everything in
|
|
$(".gridster").css("opacity", 1);
|
|
});
|
|
|
|
// PUBLIC FUNCTIONS
|
|
return {
|
|
loadConfiguration : function(configuration)
|
|
{
|
|
deccoboardModel.deserialize(configuration);
|
|
},
|
|
loadDatasourcePlugin: function(plugin)
|
|
{
|
|
if(_.isUndefined(plugin.display_name))
|
|
{
|
|
plugin.display_name = plugin.type_name;
|
|
}
|
|
|
|
datasourcePlugins[plugin.type_name] = plugin;
|
|
deccoboardModel._datasourceTypes.valueHasMutated();
|
|
},
|
|
loadWidgetPlugin : function(plugin)
|
|
{
|
|
if(_.isUndefined(plugin.display_name))
|
|
{
|
|
plugin.display_name = plugin.type_name;
|
|
}
|
|
|
|
widgetPlugins[plugin.type_name] = plugin;
|
|
deccoboardModel._widgetTypes.valueHasMutated();
|
|
},
|
|
getStyleString : function(name)
|
|
{
|
|
var returnString = "";
|
|
|
|
_.each(currentStyle[name], function(value, name)
|
|
{
|
|
returnString = returnString + name + ":" + value + ";";
|
|
});
|
|
|
|
return returnString;
|
|
},
|
|
getStyleObject : function(name)
|
|
{
|
|
return currentStyle[name];
|
|
}
|
|
};
|
|
}()); |