Files
Brainstorm/Foundation Web Application/framework.js
2014-05-30 10:31:51 -07:00

489 lines
18 KiB
JavaScript

//
// Requires jquery at a minimum.
//
var framework = new BrainstormFramework();
var brainstormFramework = framework;
function BrainstormFramework() {
//The set of open views. The data for each view is an object containing the:
// divId of the view's outer HTML tag; and the
// displayId used to reference the view on the server.
var openViews = [];
//The display ID last used from the client side sequence. The server has its own sequence (negative numbers) that it can issue from.//
var nextDisplayId = 1;
var cleanupStarted = false;
var clientId = 0;
//Set by the application - a function that is called (no parameters) when a call to retrieve a view is not allowed due to the user not being logged in or not having sufficient permissions.
this.disallowedHandler = null;
//
// The function called every N seconds to cleanup after orphaned views.
//
this.cleanup = function() {
if(openViews) {
for(var index = 0; index < openViews.length; index++) {
var next = openViews[index];
if(!$('#frameworkViewContent' + next.id)) {
//Close the view.//
closeViewInternal(next);
//Remove the metadata from the open views array.//
openViews.splice(index--, 1);
}
}
}
//Re-call cleanup every minute.//
setTimeout("brainstormFramework.cleanup()", 60000);
};
//
// Loads the html at the given URL into the container. The container will be emptied of all content prior to loading. Any scripts inside <runonce>..</runonce> tags will be removed and executed as soon as the html is loaded.
// @param containerRef The jquery reference to the container for the html. This can be for example 'body' to reference the body node, or '#body' to reference the node with the ID of 'body'.
// @param url The URL of the html to be loaded.
//
this.append = function(containerRef, url) {
var _this = this;
var container = containerRef ? $(containerRef) : null;
$.ajax({url: url, dataType: 'html', async: false, success: function(data) {
data = _this.extractViewData(data);
if(data.view) {
if(container) {
container.append(data.view);
}
else {
htmlHandler(data.view);
}
}
if(data.script && data.script.length > 0) {
try {
eval(data.script);
} catch(err) {
alert(err);
}
}
}});
}
//
// Loads the html at the given URL into the container. The container will be emptied of all content prior to loading. Any scripts inside <runonce>..</runonce> tags will be removed and executed as soon as the html is loaded.
// @param containerRef The jquery reference to the container for the html. This can be for example 'body' to reference the body node, or '#body' to reference the node with the ID of 'body'.
// @param url The URL of the html to be loaded.
// @param htmlHandler The optional handler to be called to place the html. This may be specified in place of the container ID. The handler will be passed the HTML for the view as a string.
//
this.load = function(containerRef, url, htmlHandler) {
var _this = this;
var container = containerRef ? $(containerRef) : null;
if(container) {
container.empty();
}
$.ajax({url: url, dataType: 'html', async: false, success: function(data) {
data = _this.extractViewData(data);
if(data.view) {
if(container) {
container.html(data.view);
}
else {
htmlHandler(data.view);
}
}
if(data.script && data.script.length > 0) {
try {
eval(data.script);
} catch(err) {
alert(err);
}
}
}});
}
//
// Opens a view given the container for the view and the view controller's class name.
// @param displayContainerId The ID of the container that the view will be appended to.
// @param viewController The java class name for the view controller.
//
this.openView = function(displayContainerId, viewController) {
var displayId = nextDisplayId++;
var divId = "frameworkViewContent" + displayId;
var success = false;
var _this = this;
var displayContainer = $('#' + displayContainerId);
$.ajax({url: "/FrameworkController.java?Request=OpenView&ClientId=" + clientId + "&Controller=" + viewController + "&DisplayId=" + displayId, dataType: 'json', async: false, success: function(data) {
if(data) {
try {
if(data.result == 'client-switch') {
//TODO: Send the client stored view controller metadata to the server, then re-run this call.
//For now we will simply refresh the display, causing us to rebuild everything and get a new client id.//
location.reload();
}
else if(data.result == "success") {
var viewData = _this.extractViewData(data.content);
//Setup the container for the view.//
displayContainer.append("<div id='" + divId + "' displayId='" + displayId + "'>" + "</div>");
//Place the contents of the view.//
$('#' + divId).append(viewData.view);
//TODO: Store any cleanup script?
openViews[openViews.length] = {'divId': divId, 'displayId': displayId};
if(!brainstormFramework.cleanupStarted) {
brainstormFramework.cleanupStarted = true;
brainstormFramework.cleanup();
}
success = true;
//Run the script if one came with the view.//
if(viewData.script.length > 0) {
try {
eval(viewData.script);
} catch(err) {
alert(err);
}
}
}
else if(data.result == "disallowed") {
if(_this.disallowedHandler) _this.disallowedHandler();
}
else {
//TODO: Properly handle this.
alert(data.result);
}
} catch(err) {
alert(err);
}
}
else {
//Error: View creation failed.
}
}});
return success ? displayId : 0;
};
//
// Refreshes the given view by reloading the view's HTML.
// @param frameworkContainer The DIV jquery object that encloses the view's contents. This can be obtained by calling parent() on the jquery object for any root element in the view being refreshed. Note that this is the FRAMEWORK's container which is in turn placed in the container supplied when opening the view.
//
this.refreshView = function(frameworkContainer) {
var frameworkContainerId = frameworkContainer.attr("id");
var _this = this;
frameworkContainer.empty();
for(var index = 0; index < openViews.length; index++) {
var next = openViews[index];
if(next.divId == frameworkContainerId) {
$.ajax({url: "/FrameworkController.java?Request=RefreshView&ClientId=" + clientId + "&DisplayId=" + next.displayId, dataType: 'json', async: false, success: function(data) {
if(data) {
if(data.result == 'client-switch') {
//TODO: Send the client stored view controller metadata to the server, then re-run this call.
//For now we will simply refresh the display, causing us to rebuild everything and get a new client id.//
location.reload();
}
else if(data.result == "success") {
var viewData = _this.extractViewData(data.content);
frameworkContainer.append(viewData.view);
//Run the script if one came with the view.//
if(viewData.script.length > 0) {
try {
eval(viewData.script);
} catch(err) {
alert(err);
}
}
}
}
}});
break;
}
}
};
//
// Gets the URL to call a view for use within a form. Sometimes a form submittal requires a URL string and can't call the BrainstormFramework.callView() method.
// @param displayId The ID number of the view controller (not the view's outer div's id attribute).
// @param query The function to be called on the view controller.
// @param parameters The parameters to add to the call. The parameters should be in URL format with &amp; characters separating parameters. Example: "param1=abc&param2=xyz".
//
this.getCallViewUrl = function(displayId, query, parameters) {
return "/FrameworkController.java?Request=CallView&ClientId=" + clientId + "&DisplayId=" + displayId + "&Query=" + query + (parameters ? "&" + parameters: "");
}
//
// Calls the view controller. If the view controller opens another view, it will use a view id from the server (different range) and return {view: ..., viewId: xx}.
// @param displayId The ID number of the view controller (not the view's outer div's id attribute).
// @param query The function to be called on the view controller.
// @param parameters The parameters to add to the call. The parameters should be in URL format with &amp; characters separating parameters. Example: "param1=abc&param2=xyz".
// @param isAsync Whether the call should be asynchronous.
// @param resultViewContainer The jquery view container where the view resulting from this call will be placed. If this is not specified and a view is returned then a dialog will be created.
// @param resultHandler The function called passing the result if specified. Otherwise the result is returned.
// @return The result if one is generated by the call, otherwise the dialog object if a view is the result, otherwise null.
//
this.callView = function(displayId, query, parameters, isAsync, resultViewContainer, resultHandler) {
var _this = this;
var result;
$.ajax({url: "/FrameworkController.java?Request=CallView&ClientId=" + clientId + "&DisplayId=" + displayId + "&Query=" + query + (parameters ? "&" + parameters: ""), dataType: 'json', cache: false, async: false, success: function(data, statusText, jqXHR) {
if(data) {
if(data.error) {
alert(data.error);
}
if(data.result == 'client-switch') {
//TODO: Send the client stored view controller metadata to the server, then re-run this call.
//For now we will simply refresh the display, causing us to rebuild everything and get a new client id.//
location.reload();
}
else {
try {
if(data.view) {
var viewData = _this.extractViewData(data.view);
if(resultViewContainer) {
var newDisplayId = data.displayId;
var divId = "frameworkViewContent_" + (newDisplayId < 0 ? '_' + Math.abs(newDisplayId) : newDisplayId);
//Save the view data.//
openViews[openViews.length] = {'divId': divId, 'displayId': newDisplayId};
if(!brainstormFramework.cleanupStarted) {
brainstormFramework.cleanupStarted = true;
brainstormFramework.cleanup();
}
//Create the container for the view.//
resultViewContainer.append("<div id='" + divId + "' displayId='" + newDisplayId + "'></div>");
//Append the view HTML to the container.//
$('#' + divId).append(viewData.view);
//TODO: Setup cleanup code.
//Store the result as the div id.//
result = divId;
//Run the script if one came with the view.//
if(viewData.script.length > 0) {
try {
eval(viewData.script);
} catch(err) {
alert(err);
}
}
}
else {
var newDisplayId = data.displayId;
var divId = "frameworkViewContent_" + Math.abs(newDisplayId);
var metadata = viewData.metadata ? $(viewData.metadata).find('metadata') : undefined;
//Save the view data.//
openViews[openViews.length] = {'divId': divId, 'displayId': newDisplayId};
if(!brainstormFramework.cleanupStarted) {
brainstormFramework.cleanupStarted = true;
brainstormFramework.cleanup();
}
//Create the container for the dialog.//
$("body").append("<div id='" + divId + "' displayId='" + newDisplayId + (metadata && metadata.attr('height') && metadata.attr('width') ? "' style='height: " + metadata.attr('height') + "; width: " + metadata.attr('width') + "": "") + "'></div>");
//Populate the dialog contents.//
$('#' + divId).append(viewData.view);
//Open the dialog and cleanup when it closes.//
dialog.open(divId, function() {
brainstormFramework.closeView(divId);
$('#' + divId).remove();
});
//result = divId;
result = dialog;
//Run the script if one came with the view.//
if(viewData.script && viewData.script.length > 0) {
try {
eval(viewData.script);
} catch(err) {
alert(err);
}
}
}
}
if(data.result) {
result = data.result;
}
if(resultHandler) {
resultHandler(result);
}
} catch(err) {
alert(err);
}
}
}
else {
//Error: Call failed.
}
}, error: function(jqXHR, textStatus, errorThrown) {
alert("Failed");
}});
return result;
};
this.extractViewData = function(viewData) {
var data = {script: "", metadata: undefined, view: ""};
var start;
//Remove the escaping that allowed it to be sent as part of a JSON response.//
viewData = this.unescape(viewData);
//Strip out any run-once scripts to be run after loading the html.//
while(viewData.indexOf("<runonce>") != -1) {
//extract the script.//
data.script += viewData.substring(viewData.indexOf("<runonce>") + 9, viewData.indexOf("</runonce>")).replace("<!--", "").replace("//-->", "");
//Remove the script from the view data.//
viewData = viewData.substring(0, viewData.indexOf("<runonce>")) + viewData.substring(viewData.indexOf("</runonce>") + 10);
}
//Detect and remove any metadata.//
if((start = viewData.indexOf('<metadata>')) != -1) {
var end = viewData.indexOf('</metadata>', start + 10);
var metadata = viewData.substring(start, end + 11);
//Remove the metadata from the document.//
viewData = viewData.substring(0, start) + viewData.substring(end + 11);
//Parse the metadata XML.//
data.metadata = $.parseXML(metadata);
}
else if((start = viewData.indexOf('<metadata ')) != -1) {
var end = viewData.indexOf('/>', start + 10);
var metadata = viewData.substring(start, end + 2);
//Remove the metadata from the document.//
viewData = viewData.substring(0, start) + viewData.substring(end + 2);
//Parse the metadata XML.//
data.metadata = $.parseXML(metadata);
}
else if((start = viewData.indexOf('<metadata/>')) != -1) {
viewData = viewData.substring(0, start) + viewData.substring(start + 11);
}
//Strip out any comments.//
while(viewData.indexOf("<!--") != -1) {
//Remove the comment from the view data.//
viewData = viewData.substring(0, viewData.indexOf("<!--")) + viewData.substring(viewData.indexOf("-->") + 3);
}
data.view = viewData;
return data;
}
//
// Closes the view given the view's div ID (the div is created to wrapper the view content and is the only child of the container passed when creating the view, the div's ID is returned by the call to openView(..)).
// @param id The view's div's ID, or display ID.
//
this.closeView = function(id) {
for(var index = 0; index < openViews.length; index++) {
var next = openViews[index];
//Allow the passed id to be either the div ID or the display ID.//
if(next.divId == id || next.displayId == id) {
//Remove the metadata from the open views array.//
openViews.splice(index, 1);
//Close the actual view.//
this.closeViewInternal(next);
break;
}
}
};
//
// Closes the view given the view metadata. The caller is expected to remove the metadata from the view set.
// @param metadata The metadata for the view being removed.
//
this.closeViewInternal = function(metadata) {
//Remove the view HTML from the DOM.//
$('#' + metadata.divId).remove();
//Notify the server that the view has closed.//
$.ajax({url: "/FrameworkController.java?Request=CloseView&ClientId=" + clientId + "&DisplayId=" + metadata.displayId, dataType: 'json', async: true, success: function(data) {
if(data) {
if(data.success == "true") {
//Do nothing?
}
else if(data.result == 'client-switch') {
//Do nothing for now. Ideally this call failing can be ignored since this close view code should remove the server view controller metadata stored on the client which will be used at some future time to restore the client's session on the server.//
}
else {
//TODO:
}
}
else {
//Error: View removal failed.
}
}});
};
//
// Removes escape characters from text.
// @param text The text whose escape characters are to be removed.
//
this.unescape = function(text) {
var result = text.replace(/\x7C1/g, "\\").replace(/\x7C2/g, "'").replace(/\x7C3/g, "\"").replace(/\x7C4/g, "\x0D").replace(/\x7C5/g, "\x09").replace(/\x7C7/g, "&").replace(/\x7C8/g, "<").replace(/\x7C9/g, ">").replace(/\x7C6/g, "\x7C");
return result;
};
//
// Adds escape characters to text.
// @param text The text whose escape characters are to be added. If this is undefined then the result will be an empty string.
//
this.escape = function(text) {
var result;
if(text) {
result = text.replace(/\x0A\x0D/g, "\n").replace(/\x7C/g, "\x7C6").replace(/\\/g, "\x7C1").replace(/\'/g, "\x7C2").replace(/\"/g, "\x7C3").replace(/\n/g, "\x7C4").replace(/\x09/g, "\x7C5").replace(/%/g, "\x7C6").replace(/&/g, "\x7C7").replace(/\x3C/g, "\x7C8").replace(/\x3E/g, "\x7C9");
}
else {
result = "";
}
return result;
};
//Get this view's client id. Each window/tab or refresh requires a new client ID from the server so it can track which set of displays belongs to which view.//
$.ajax({url: "/FrameworkController.java?Request=CreateId", dataType: 'json', async: false, success: function(data) {
if(data) {
clientId = data.result;
}
}});
$(window).onunload = function() {
while(openViews.length > 0) {
var next = openViews[openViews.length - 1];
closeViewInternal(next);
openViews.splice(openViews.length - 1, 1);
}
};
this.cleanup();
}