From 2009.igem.org
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
/* ************************************************************************
Copyright: SynBioWave (synbiowave.org)
License: GPL
Authors: David Nellessen (mail@davidn.de)
DESCRIPTION:
This Application is used to create a GUI inside a wave
gadget from a JSON passed via the wave API (wave.getState()). This
Application expects the JSON data to construct the menu
from in wave.getState('ui.structure') with a special structure we call
the qooxwave protocol. This protocol is not yet completed.
For understanding qooxwave protocol, have a look at the documentation:
https://2009.igem.org/Team:Freiburg_software/Project/qooxWave-details
TODO:
- complete qooxwave protocol
- gadget resizing should respect menus
- Refreshing UI on every state update is not neccessary
- solve absolute url problem (still pointing to sourceforge.net in config.josn)
- rebuild the model on user input and bind model to store
- MVC improvement: when changing something like clicking on a checkbox, the model is not updated correctly
therefore the state has still the old value (no checkbox clicked),
nothing will happen, eventhough the server might whant to uncheck the checkbox!
So there must be either
- a update of the model
- allways update gui on stateupdate
- some kind of mixing of the above: not on every state update, but if the model
might be out of sync
- modelling is very sensible cerncerning their ids. they cannot contain certain characters, which?
- check target url for upload widget
************************************************************************ */
/* ************************************************************************
#asset(qooxwaveclient/*)
************************************************************************ */
/**
* This is the main application class of "qooxwaveClient"
*/
qx.Class.define("qooxwaveclient.Application",
{
extend : qx.application.Standalone,
properties : { //this app makes use of the dynamic property feature of qooxdoo. guiModel bind to the model in the store using data binding feature of qooxdoo
guiModel : {nullable : true, apply : "_applyGuiModel", event : "changeGuiModel"}
},
/*
*****************************************************************************
MEMBERS
*****************************************************************************
*/
members :
{
/**
* This method contains the initial application code and gets called
* during startup of the application
*/
main : function()
{
// Call super class
this.base(arguments);
// Enable logging in debug variant
if (qx.core.Variant.isSet("qx.debug", "on"))
{
// support native logging capabilities, e.g. Firebug for Firefox
qx.log.appender.Native;
// support additional cross-browser console. Press F7 to toggle visibility
qx.log.appender.Console;
}
/*
-------------------------------------------------------------------------
Below is actual application code...
-------------------------------------------------------------------------
*/
/*
* Define main objects
*/
//general member variables:
this.namespace = new Object();
this.namespace.ui = 'ui';
this.namespace.ui_structure = this.namespace.ui + '.structure';
//set minHeight of the gadget. This is unfortunaltely nedded, because the auto resizing does not respect menus
this.minHeight = 80;
//Get Wave Object (simulated when outside of wave)
var wave = new qooxwaveclient.wave.WaveFactory().getWave();
//create a Store for server-client (wave-thisApp) communication
this.store = new qooxwaveclient.Store(wave, new Array(this.namespace.ui_structure), this.namespace.ui_structure);
wave.setStateCallback(this.store.processStateUpdate, this.store);
this.store.bind("model", this, "guiModel");
//uncomment the following lines to activate the testing ui
//this.buildTestingUI();
//this.minHeight = 400;
//Testing Ui can also be activated with a shortcut (works only outside wave)
var find = new qx.event.Command("Control-Y");
find.addListener("execute", function(){
if(qx.ui.core.Widget.contains(this.getRoot(), this.testUi)){
this.testUi.destroy();
this.minHeight = 100;
}
else{
this.buildTestingUI();
this.minHeight = 400;
}
this.refresh();
}, this);
},
//build the main Widget from guiModel | destruction suggested first
buildMainWidget : function (model){
this.__buildRootGadget();
if(!model) return true;
if(model.type == "ui" && model.subitem){ //distinguish different implementations of qooxwave
model = model.subitem;
}
for(var itemid in model){
var widget = this.__loop(itemid, model[itemid]);
if(widget.qxwidget) this.gadget.add(widget.qxwidget);
else return false;
}
return true;
},
//public method to destruct gadget
destructMainWidget :function () {
this.__destructRootGadget();
},
//reBuild this.gadget from guiModel
refresh : function (){
this.destructMainWidget();
//alert(this.getGuiModel().toString());
this.buildMainWidget(this.getGuiModel());
this.resizeGadget();
return true;
},
//resize wave gadget
resizeGadget : function (){
var heightDest = this.minHeight; //minimal height
if(this.gadget.getBounds()) var heightDest = Math.max(heightDest, this.gadget.getBounds().height);
if(typeof gadgets != "undefined") gadgets.window.adjustHeight(heightDest);
this.debug("SET HEIGHT: " + heightDest);
},
//executed when new value of GuiModel applied
_applyGuiModel : function (value, old){
if(value == old) this.debug("Apply guiModel. But NO Change detected");
else {
this.debug("Apply guiModel: " + value);
this.refresh();
}
},
//loops an object item according to the qooxWave protocol
__loop : function (itemid, item){
item.id = itemid;
if(item.type == "toolbar") item = this.__processToolbar(itemid, item);
else if(item.type == "button") item = this.__processButton(itemid, item);
else if(item.type == "checkbox") item = this.__processCheckbox(itemid, item);
else if(item.type == "menu") item = this.__processMenu(itemid, item);
else if(item.type == "label") item = this.__processLabel(itemid, item);
else if(item.type == "form") item = this.__processForm(itemid, item);
else if(item.type == "upload") item = this.__processUpload(itemid, item);
else if(item.type == "chron") item = this.__processChron(itemid, item);
return item;
},
//proccesses the toolbar typed items
__processToolbar : function (itemid, item){
if(typeof item.qxwidget == "undefined") item.qxwidget = new qx.ui.toolbar.ToolBar(); //could be create earlier
if(typeof item.subitem != "undefined"){ //loop subitems
for(var subitemid in item.subitem){
if(item.subitem[subitemid].type == "button"){ //if type of subitem is button create a special toolbar button (design issue)
item.subitem[subitemid].qxwidget = new qx.ui.toolbar.Button();
item.subitem[subitemid] = this.__loop(subitemid, item.subitem[subitemid]);
item.qxwidget.add(item.subitem[subitemid].qxwidget);
}
else if(item.subitem[subitemid].type == "checkbox"){ //if type of subitem is checkbox, create a special toolbar button (design issue)
item.subitem[subitemid].qxwidget = new qx.ui.toolbar.CheckBox();
item.subitem[subitemid] = this.__loop(subitemid, item.subitem[subitemid]);
item.qxwidget.add(item.subitem[subitemid].qxwidget);
}
else if(item.subitem[subitemid].type == "menu"){ //create a special menu (design issue)
item.subitem[subitemid].qxwidget = new qx.ui.toolbar.MenuButton();
item.subitem[subitemid] = this.__loop(subitemid, item.subitem[subitemid]);
item.qxwidget.add(item.subitem[subitemid].qxwidget);
}
}
}
return item;
},
//processes the form typed items
__processForm : function (itemid, item){
if(typeof item.qxform == "undefined") item.qxform = new qx.ui.form.Form(); //could be create earlier }
item.qxform.addGroupHeader(item.label);
//create widget
item.qxwidget = new qx.ui.groupbox.GroupBox(""+item.label, null);
item.qxwidget.setLayout(new qx.ui.layout.VBox(5));
item.qxwidget.setMaxWidth(300);
//A atom that shows loading state
var loadingAtom = new qx.ui.basic.Atom("State", "qooxwaveclient/ajax-loader-large.gif");
loadingAtom.set({alignX : "center", marginTop : 10});
loadingAtom.hide();
//create model
var model = new Object();
//create submit queue
var queue = new Array();
var queueCount = 0;
if(typeof item.subitem != "undefined"){ //loop subitems | most of them are processed directly here, cause they can only be subitems of form
for(var subitemid in item.subitem){
var skipModelSetup = false;
if(item.subitem[subitemid].type == "textarea"){
item.subitem[subitemid].qxwidget = new qx.ui.form.TextArea();
item.subitem[subitemid].value = item.subitem[subitemid].value+"";
}
else if(item.subitem[subitemid].type == "textfield"){
item.subitem[subitemid].qxwidget = new qx.ui.form.TextField();
item.subitem[subitemid].qxwidget.setMinWidth(150);
item.subitem[subitemid].value = item.subitem[subitemid].value+"";
}
else if(item.subitem[subitemid].type == "checkbox"){
item.subitem[subitemid].qxwidget = new qx.ui.form.CheckBox();
item.subitem[subitemid].value = !!item.subitem[subitemid].value; //parse to boolean
}
else if(item.subitem[subitemid].type == "radio"){
item.subitem[subitemid].qxwidget = new qx.ui.form.RadioButton();
item.subitem[subitemid].value = !!item.subitem[subitemid].value; //parse to boolean
//add to radio group
if(!item.subitem[subitemid].group) continue;
if(typeof item.radiogroups == "undefined") item.radiogroups = new Object();
if(typeof item.radiogroups[item.subitem[subitemid].group] == "undefined") item.radiogroups[item.subitem[subitemid].group] = new qx.ui.form.RadioButtonGroup();
item.radiogroups[item.subitem[subitemid].group].add(item.subitem[subitemid].qxwidget);
}
else if(item.subitem[subitemid].type == "download"){ //section copied from == "upload" so the notaion is wrong. TODO: change this
item.subitem[subitemid].upload = new qooxwaveclient.Download(item.subitem[subitemid].target+"", this.namespace.ui + "." + subitemid, this.store, false, "application/x-www-form-urlencoded");
item.qxwidget.add(item.subitem[subitemid].upload);
item.subitem[subitemid].qxwidget = new qx.ui.form.TextField();
item.subitem[subitemid].qxwidget.setReadOnly(true);
//bind filename upload -> textfield
item.subitem[subitemid].upload.bind("fileName", item.subitem[subitemid].qxwidget, "value");
item.subitem[subitemid].value = item.subitem[subitemid].value+"";
//process submitting
item.subitem[subitemid].upload.queueCount = queueCount;
item.subitem[subitemid].upload.go = function(){
toogelLoadingAtom("downloading ...");
this.modefieUri(item.serialize("uri"));
this.submit();
toogelLoadingAtom("Uploading " + this.getFileName() + "...");
};
item.subitem[subitemid].upload.addListener("onload", function(e){
qx.event.Timer.once(queue[this.queueCount+1].go, queue[this.queueCount+1], 1000);
},item.subitem[subitemid].upload);
queue[queueCount] = item.subitem[subitemid].upload;
queueCount++;
//hide textfield
item.subitem[subitemid].qxwidget.setEnabled(false);
}
else if(item.subitem[subitemid].type == "upload" ){
item.subitem[subitemid].upload = new qooxwaveclient.Upload(item.subitem[subitemid].target+"", this.namespace.ui + "." + subitemid, this.store, false, "multipart/form-data");
item.qxwidget.add(item.subitem[subitemid].upload);
item.subitem[subitemid].qxwidget = new qx.ui.form.TextField();
item.subitem[subitemid].qxwidget.setReadOnly(true);
//bind filename upload -> textfield
item.subitem[subitemid].upload.bind("fileName", item.subitem[subitemid].qxwidget, "value");
item.subitem[subitemid].value = item.subitem[subitemid].value+"";
//process submitting
item.subitem[subitemid].upload.queueCount = queueCount;
item.subitem[subitemid].upload.go = function(){
toogelLoadingAtom("Uploading ...");
this.modefieUri(item.serialize("uri"));
this.submit();
toogelLoadingAtom("Uploading " + this.getFileName() + "...");
};
item.subitem[subitemid].upload.addListener("onload", function(e){
qx.event.Timer.once(queue[this.queueCount+1].go, queue[this.queueCount+1], 1000);
},item.subitem[subitemid].upload);
item.subitem[subitemid].upload.addListener("error", function(e){
alert('There was one upload field not specified. Nothing to send.');
qx.event.Timer.once(queue[this.queueCount+1].go, queue[this.queueCount+1], 1000);
},item.subitem[subitemid].upload);
queue[queueCount] = item.subitem[subitemid].upload;
queueCount++;
//hide textfield
item.subitem[subitemid].qxwidget.setEnabled(false);
}
else continue; //make sure only available subitems are processed
if(!skipModelSetup){ //TODO ... HMMMM
model[subitemid] = item.subitem[subitemid].value; //fill the model
item.subitem[subitemid].qxwidget.setValue(item.subitem[subitemid].value);
}
item.subitem[subitemid].qxwidget.setWidth(180); //TODO
item.qxform.add(item.subitem[subitemid].qxwidget, item.subitem[subitemid].label, null, subitemid);
}
}
//form model and data binding
model = qx.data.marshal.Json.createModel(model);
var formController = new qx.data.controller.Form(model, item.qxform);
item.serialize = function(method){
if(method == "json"){
var modelJson = qx.util.Serializer.toJson(model);
var value = modelJson+"";
value = value.replace(/"/g, "'");
}
else if(method == "uri"){
var modelUri = qx.util.Serializer.toUriParameter(model, function(object){return ""+object;});
var value = modelUri+"";
value = value.replace(/"/g, "'");
}
else value = null;
return value;
};
//toggle loading icon
toogelLoadingAtom = function (label){
//if(qx.ui.core.contains(item.qxwidget, this.))
if(!label) loadingAtom.hide();
else {
loadingAtom.setLabel(""+label);
loadingAtom.show();
}
};
// send button
var sendButton = new qx.ui.form.Button("Send", "icon/16/actions/dialog-ok.png");
queue[queueCount] = new Object();
queue[queueCount].deltaid = this.namespace.ui + "." + itemid;
queue[queueCount].store = this.store;
queue[queueCount].gadget = this.gadget;
queue[queueCount].widget = item.qxwidget;
queue[queueCount].go = function(){
toogelLoadingAtom("validating form");
//TODO: validation
var delta = new Object();
value = item.serialize("json");
delta[this.deltaid] = "{'event' : 'changeValue', 'value' : " + value + "}";
this.store.submitEventDelta(delta);
toogelLoadingAtom(null);
if(qx.ui.core.Widget.contains(this.gadget, this.widget)) this.gadget.remove(this.widget); //remove form
};
//submit form on pressing the send button
sendButton.addListener("execute", function(e){
queue[0].go();
}, this);
item.qxform.addButton(sendButton);
// reset button
var resetButton = new qx.ui.form.Button("Reset", "icon/16/actions/dialog-cancel.png");
resetButton.addListener("execute", function() {
item.qxform.reset();
}, this);
item.qxform.addButton(resetButton);
//add formwidget to item.qxwidget
item.qxwidget.view = item.qxform.createView();
item.qxwidget.add(item.qxwidget.view);
//add loadingAtom
item.qxwidget.add(loadingAtom);
return item;
},
//processes the upload typed items
__processUpload : function (itemid, item){
item.qxwidget = new qooxwaveclient.Upload(item.target+"", this.namespace.ui + "." + item.id, this.store, true, "multipart/form-data");
return item;
},
//processes the button typed items
__processButton : function (itemid, item){
if(typeof item.qxwidget == "undefined") item.qxwidget = new qx.ui.form.Button(); //could be create earlier }
item.qxwidget.setLabel(item.label);
var childrenToToogle = new Array();
if(typeof item.subitem != "undefined"){ //loop subitems
for(var subitemid in item.subitem){
if(item.subitem[subitemid].type == "form"){ //create a form
item.subitem[subitemid] = this.__loop(subitemid, item.subitem[subitemid]);
//for toogling on and off the forms onClick (so some lines below...)
childrenToToogle.push(item.subitem[subitemid].qxwidget);
}
if(item.subitem[subitemid].type == "upload"){ //create a upload form
item.subitem[subitemid] = this.__loop(subitemid, item.subitem[subitemid]);
//for toogling on and off the forms onClick (so some lines below...)
childrenToToogle.push(item.subitem[subitemid].qxwidget);
}
else continue; // make sure only allowed subitems are added
}
}
//attach onClick event
item.qxwidget.addListener("execute", function (event) {
//toogle forms on and off
for(var i = 0; i<childrenToToogle.length; i++){
//alert(i);
if(qx.ui.core.Widget.contains(this.gadget, childrenToToogle[i])){
this.gadget.remove(childrenToToogle[i]);
}
else{
this.gadget.add(childrenToToogle[i]);
}
}
if(typeof item.subitem == "undefined") { //only fire if there are no subitems
var delta = new Object();
var id = this.namespace.ui + "." + item.id;
delta[id] = "{'event' : 'execute'}";
//delta[id]['status'] = "fired";
this.store.submitEventDelta(delta);
}
}, this);
return item;
},
//processes the checkbox typed items
__processCheckbox : function (itemid, item){
if(typeof item.qxwidget == "undefined") item.qxwidget = new qx.ui.form.CheckBox(); //could be create earlier }
item.qxwidget.setLabel(item.label);
item.qxwidget.setValue(!!item.value);
//attach onClick event
item.qxwidget.addListener("changeValue", function (event) {
var delta = new Object();
var id = this.namespace.ui + "." + item.id;
delta[id] = "{\"event\" : \"changeValue\", \"value\" : \"" + event.getValue() + "\"}";
this.store.submitEventDelta(delta);
}, this);
return item;
},
//processes the chron typed items. This item fires automated events.
__processChron : function (itemid, item){
item.qxwidget = new qx.ui.groupbox.GroupBox("", null);
item.qxwidget.setLayout(new qx.ui.layout.VBox(5));
item.qxwidget.setMaxWidth(300);
var loadingAtom = new qx.ui.basic.Atom(""+item.label, "qooxwaveclient/ajax-loader-large.gif");
loadingAtom.set({alignX : "center"});
item.qxwidget.add(loadingAtom);
var count = 1;
item.chron = function(){
var delta = new Object();
var id = this.namespace.ui + "." + item.id;
//loadingAtom.setLabel(""+count);
count++;
delta[id] = "{'event' : 'execute'}";
//delta[id]['status'] = "fired";
this.store.submitEventDelta(delta);
};
//create timer to regularily fire events
var timer = new qx.event.Timer(parseInt(item.value)+10);
timer.addListener("interval", item.chron, this);
timer.start();
return item;
},
//processes the label typed items
__processLabel : function (itemid, item){
if(typeof item.qxwidget == "undefined") item.qxwidget = new qx.ui.basic.Label(item.label); //could be create earlier }
return item;
},
//processes the menu typed items
__processMenu : function (itemid, item){
if(typeof item.qxwidget == "undefined") item.qxwidget = new qx.ui.menu.Button(); //could be create earlier }
item.qxwidget.setLabel(item.label);
var menu = new qx.ui.menu.Menu(); //create a new menucontainer
item.qxwidget.setMenu(menu); //and set it
if(typeof item.subitem != "undefined"){ //loop subitems
for(var subitemid in item.subitem){
if(item.subitem[subitemid].type == "button"){ //if type of subitem is button, create a special menu button (design issue)
item.subitem[subitemid].qxwidget = new qx.ui.menu.Button();
item.subitem[subitemid] = this.__loop(subitemid, item.subitem[subitemid]);
menu.add(item.subitem[subitemid].qxwidget);
}
else if(item.subitem[subitemid].type == "checkbox"){ //if type of subitem is checkbox, create a special menu checkbox (design issue)
item.subitem[subitemid].qxwidget = new qx.ui.menu.CheckBox();
item.subitem[subitemid] = this.__loop(subitemid, item.subitem[subitemid]);
menu.add(item.subitem[subitemid].qxwidget);
}
else if(item.subitem[subitemid].type == "menu"){ //create a special menu (design issue)
item.subitem[subitemid].qxwidget = new qx.ui.menu.Button();
item.subitem[subitemid] = this.__loop(subitemid, item.subitem[subitemid]);
menu.add(item.subitem[subitemid].qxwidget);
}
}
}
return item;
},
//Destructs this.gadget, the root gadget of the application
__destructRootGadget : function (){
if(typeof this.gadget != "undefined" && !this.gadget.isDisposed()){
this.getRoot().remove(this.gadget);
this.gadget.dispose();
}
},
//builds the root geadget (this,gadget) of the application. Any other gadget is added to this. this.gadget != this.getRoot()
__buildRootGadget : function (){
this.gadget = new qx.ui.container.Composite(); //set an individual 'root' object called gadget. could be changed later on to calculate the correct height of the widget. Is also used to use a different layout than canvas
//Set the layut of the root.gadget container
var layout = new qx.ui.layout.VBox();
layout.setSpacing(4); // apply spacing
this.gadget.setLayout(layout);
this.getRoot().add(this.gadget, {left:0, top:0, right:0});
this.gadget.addListener("resize", function(){
this.debug("HEIGHT: " + this.gadget.getBounds().height);
this.resizeGadget();
}, this);
},
//returns a test string in qooxwave protocol form
__getTestString : function (){
var submenu = "'submenu' : {" +
"'type' : 'menu' ," +
"'label' : 'Click Me' ," +
"'subitem' : {'subsubmenu' : {" +
"'type' : 'button' ," +
"'label' : 'or click me'" +
"}}" +
"}";
var upload =
"'datei1' : {" +
"'label' : 'Datei 1' ," +
"'type' : 'upload'," +
"'target' : 'http://www.davidn.de/index.php'" +
"}";
var upload2 =
"'datei2' : {" +
"'label' : 'Datei 2' ," +
"'type' : 'upload'," +
"'target' : 'http://www.davidn.de/index.php'" +
"}";
var formular = "'id0000' : {" +
"'label' : 'Upload File' ," +
"'type' : 'form'," +
"'subitem' : {" +
"'form_bemerkung' : {" +
"'type' : 'textfield'," +
"'label' : 'Bemerkung'," +
"'value' : ''" +
"}," +
"'form_checkbox1' : {" +
"'type' : 'checkbox'," +
"'label' : 'Aut. Erkennung'," +
"'value' : true" +
"}," +
"'form_format1' : {" +
"'type' : 'radio'," +
"'label' : 'Format 1'," +
"'group' : 'formatgruppe'," +
"'value' : 'abc'" +
"}," +
"'form_format2' : {" +
"'type' : 'radio'," +
"'label' : 'Format 3'," +
"'group' : 'formatgruppe'," +
"'value' : false" +
"}," +
upload + "," + upload2 +
"} " +
"}";
return "{" +
// formular + "," +
"'rootid' : {" +
"'type' : 'toolbar' ," +
"'label' : 'root element' ," +
"'subitem' : {" +
/* "'subid1' : {" +
"'type' : 'button' ," +
"'label' : 'Form'," +
"'subitem' : " +
"{" +
formular +
"}" +
"}," +*/
"'subid2' : {" +
"'type' : 'button' ," +
"'label' : 'Upload a file'," +
"'subitem' : {" +
upload +
"}" +
"}," +
" 'subid3' : {" +
"'type' : 'menu' ," +
"'label' : 'Menu' ," +
"'subitem' : {" +
submenu +
"}" +
"}}" +
"} " +
"}";
},
//builds the testing ui for debuggin. It shows state data and allows to create ui from user input strings
buildTestingUI : function () {
var container = new qx.ui.container.Composite();
this.testUi = container;
var layout = new qx.ui.layout.VBox();
layout.setSpacing(10);
container.setLayout(layout);
this.getRoot().add(container, {right: 0, top: 40});
var button = new qx.ui.form.Button("Refresh GUI (state update causes this)");
button.addListener("execute", this.refresh, this);
container.add(new qx.ui.core.Spacer(50));
container.add(new qx.ui.basic.Label("Submit a JSON-String as Delta with key " + this.namespace.ui_structure + ":"));
var input = new qx.ui.form.TextArea();
input.app = this;
container.add(input);
var button = new qx.ui.form.Button("Submit the above input as structure");
container.add(button);
button.addListener("execute", function (e){
var delta = new Object();
delta[this.app.namespace.ui_structure] = this.getValue();
this.app.store.submitEventDelta(delta);
}, input);
var button = new qx.ui.form.Button("TEST ME! (Example JSON)");
container.add(button);
button.addListener("execute", function (e){
var delta = new Object();
delta[this.app.namespace.ui_structure] = this.app.__getTestString();
this.app.store.submitEventDelta(delta);
this.setValue(delta[this.app.namespace.ui_structure]);
}, input);
container.add(new qx.ui.core.Spacer(50));
var info = new qx.ui.form.TextArea();
info.setReadOnly(true);
container.add(new qx.ui.basic.Label("Submit Delta Info:"));
container.add(info);
this.store.bind("modelDebug", info, "value");
}
}
});