Commit f3d69ed5 authored by Jérome Perrin's avatar Jérome Perrin

GUI: update static version

parent 389c6681
......@@ -2,6 +2,7 @@
(function(window, rJS, RSVP, loopEventListener) {
"use strict";
var gadget_klass = rJS(window);
// TODO: save on parent gadget
function saveGraph(evt) {
var gadget = this;
return new RSVP.Queue().push(function() {
......@@ -11,23 +12,12 @@
}
return gadget.getDeclaredGadget("productionline_graph");
}).push(function(graph_gadget) {
return graph_gadget.getData();
}).push(function(data) {
graph_data = data;
// Always get a fresh version, to prevent deleting spreadsheet & co
return gadget.aq_getAttachment({
_id: gadget.props.jio_key,
_attachment: "body.json"
});
return graph_gadget.getContent();
}).push(function(body) {
var data = JSON.parse(body), json_graph_data = JSON.parse(graph_data);
data.nodes = json_graph_data.nodes;
data.edges = json_graph_data.edges;
data.preference = json_graph_data.preference;
return gadget.aq_putAttachment({
_id: gadget.props.jio_key,
_attachment: "body.json",
_data: JSON.stringify(data, null, 2),
_data: JSON.stringify(JSON.parse(body), null, 2),
_mimetype: "application/json"
});
}).push(function() {
......@@ -56,7 +46,7 @@
}
this.timeout = window.setTimeout(saveGraph.bind(this), 100);
}).declareMethod("render", function(options) {
var jio_key = options.id, gadget = this;
var jio_key = options.id, gadget = this, data;
gadget.props.jio_key = jio_key;
return new RSVP.Queue().push(function() {
/*jslint nomen: true*/
......@@ -65,11 +55,12 @@
_attachment: "body.json"
}), gadget.getDeclaredGadget("productionline_graph") ]);
}).push(function(result_list) {
return result_list[1].render(result_list[0]);
data = result_list[0];
return result_list[1].render(data);
}).push(function() {
return gadget.getDeclaredGadget("productionline_toolbox");
}).push(function(toolbox_gadget) {
toolbox_gadget.render();
toolbox_gadget.render(data);
});
}).declareMethod("startService", function() {
var g = this, graph;
......
......@@ -84,20 +84,22 @@
// Precompile the templates while loading the first gadget instance
var gadget_klass = rJS(window);
initGadgetMixin(gadget_klass);
gadget_klass.declareAcquiredMethod("aq_getAttachment", "jio_getAttachment").declareAcquiredMethod("aq_putAttachment", "jio_putAttachment").declareAcquiredMethod("aq_ajax", "jio_ajax").declareAcquiredMethod("aq_getConfigurationDict", "getConfigurationDict").declareAcquiredMethod("pleaseRedirectMyHash", "pleaseRedirectMyHash").declareAcquiredMethod("whoWantsToDisplayThisDocument", "whoWantsToDisplayThisDocument").declareMethod("render", function(options) {
var gadget = this, property_list, data;
gadget_klass.declareAcquiredMethod("aq_getAttachment", "jio_getAttachment").declareAcquiredMethod("aq_putAttachment", "jio_putAttachment").declareAcquiredMethod("aq_ajax", "jio_ajax").declareAcquiredMethod("pleaseRedirectMyHash", "pleaseRedirectMyHash").declareAcquiredMethod("whoWantsToDisplayThisDocument", "whoWantsToDisplayThisDocument").declareMethod("render", function(options) {
var gadget = this, data;
this.props.jio_key = options.id;
return gadget.aq_getAttachment({
_id: gadget.props.jio_key,
_attachment: "body.json"
}).push(function(json) {
data = JSON.parse(json).general;
return gadget.aq_getConfigurationDict();
}).push(function(configuration_dict) {
property_list = configuration_dict["Dream-Configuration"].property_list;
return gadget.getDeclaredGadget("fieldset");
}).push(function(fieldset_gadget) {
return fieldset_gadget.render(property_list, data);
var application_configuration = {};
data = JSON.parse(json);
application_configuration = data.application_configuration.general || {};
return gadget.getDeclaredGadget("fieldset").push(function(fieldset_gadget) {
return fieldset_gadget.render({
value: data.general,
property_definition: application_configuration
});
});
});
}).declareMethod("startService", function() {
return waitForRunSimulation(this);
......
......@@ -26,6 +26,7 @@
title: "Create Document"
}
},
// TODO: remove this once everything is merged.
Input: {
view: {
gadget: "Input_viewProductionLine",
......@@ -35,50 +36,32 @@
view_wip_part_spreadsheet: {
gadget: "Input_viewWipPartSpreadsheet",
type: "object_view",
title: "WIP Part Spreadsheet",
condition: function(gadget) {
return gadget.props.configuration_dict["Dream-Configuration"].gui.wip_part_spreadsheet;
}
title: "WIP Part Spreadsheet"
},
view_shift_spreadsheet: {
gadget: "Input_viewShiftSpreadsheet",
type: "object_view",
title: "Shift Spreadsheet",
condition: function(gadget) {
return gadget.props.configuration_dict["Dream-Configuration"].gui.shift_spreadsheet;
}
title: "Shift Spreadsheet"
},
view_available_capacity_spreadsheet: {
gadget: "Input_viewAvailableCapacitySpreadsheet",
type: "object_view",
title: "Available Capacity Spreadsheet",
condition: function(gadget) {
return gadget.props.configuration_dict["Dream-Configuration"].gui.capacity_by_project_spreadsheet;
}
title: "Available Capacity Spreadsheet"
},
view_required_capacity_spreadsheet: {
gadget: "Input_viewRequiredCapacitySpreadsheet",
type: "object_view",
title: "Required Capacity Spreadsheet",
condition: function(gadget) {
return gadget.props.configuration_dict["Dream-Configuration"].gui.capacity_by_station_spreadsheet;
}
title: "Required Capacity Spreadsheet"
},
view_dp_capacity_spreadsheet: {
gadget: "Input_viewDemandPlanningCapacitySpreadsheet",
type: "object_view",
title: "Demand Planning Required Capacity Spreadsheet",
condition: function(gadget) {
return gadget.props.configuration_dict["Dream-Configuration"].gui.dp_capacity_spreadsheet;
}
title: "Demand Planning Required Capacity Spreadsheet"
},
view_dp_route_spreadsheet: {
gadget: "Input_viewDemandPlanningRouteSpreadsheet",
type: "object_view",
title: "Demand Planning Route Spreadsheet",
condition: function(gadget) {
return gadget.props.configuration_dict["Dream-Configuration"].gui.dp_route_spreadsheet;
}
title: "Demand Planning Route Spreadsheet"
},
view_simu: {
gadget: "Input_viewSimulation",
......@@ -100,66 +83,42 @@
view: {
gadget: "Output_viewStationUtilisationGraph",
type: "object_view",
title: "Stations Utilization",
condition: function(gadget) {
return gadget.props.configuration_dict["Dream-Configuration"].gui.station_utilisation_graph;
}
title: "Stations Utilization"
},
download_excel_spreadsheet: {
gadget: "Output_viewDownloadExcelSpreadsheet",
type: "object_view",
title: "Download Excel Spreadsheet",
condition: function(gadget) {
return gadget.props.configuration_dict["Dream-Configuration"].gui.download_excel_spreadsheet;
}
title: "Download Excel Spreadsheet"
},
view_capacity_utilization: {
gadget: "Output_viewCapacityUtilisationGraph",
type: "object_view",
title: "Capacity Utilization",
condition: function(gadget) {
return gadget.props.configuration_dict["Dream-Configuration"].gui.capacity_utilisation_graph;
}
title: "Capacity Utilization"
},
view_queue_stat: {
gadget: "Output_viewQueueStatGraph",
type: "object_view",
title: "Queues Statistics",
condition: function(gadget) {
return gadget.props.configuration_dict["Dream-Configuration"].gui.queue_stat;
}
title: "Queues Statistics"
},
view_exit_stat: {
gadget: "Output_viewExitStatistics",
type: "object_view",
title: "Exit Statistics",
condition: function(gadget) {
return gadget.props.configuration_dict["Dream-Configuration"].gui.exit_stat;
}
title: "Exit Statistics"
},
view_gantt: {
gadget: "Output_viewJobGantt",
type: "object_view",
title: "Job Gantt",
condition: function(gadget) {
return gadget.props.configuration_dict["Dream-Configuration"].gui.job_gantt;
}
title: "Job Gantt"
},
view_schedule: {
gadget: "Output_viewJobScheduleSpreadsheet",
type: "object_view",
title: "Job Schedule",
condition: function(gadget) {
return gadget.props.configuration_dict["Dream-Configuration"].gui.job_schedule_spreadsheet;
}
title: "Job Schedule"
},
view_debug: {
gadget: "Output_viewDebugJson",
type: "object_view",
title: "Debug JSON",
condition: function(gadget) {
return gadget.props.configuration_dict["Dream-Configuration"].gui.debug_json;
}
title: "Debug JSON"
}
}
}, panel_template, navigation_template, active_navigation_template, error_template, gadget_klass = rJS(window);
......@@ -328,8 +287,6 @@
id: param_list[0],
result: param_list[1]
});
}).allowPublicAcquisition("getConfigurationDict", function() {
return this.props.configuration_dict;
}).ready(function() {
if (panel_template === undefined) {
// XXX Only works as root gadget
......@@ -354,21 +311,12 @@
$(panel).trigger("create");
});
}).ready(function(g) {
var jio_gadget;
return g.getDeclaredGadget("jio").push(function(gadget) {
jio_gadget = gadget;
return jio_gadget.createJio({
return gadget.createJio({
type: "local",
username: "dream",
applicationname: "dream"
});
}).push(function() {
// XXX Hardcoded relative URL
return jio_gadget.ajax({
url: "../../getConfigurationDict"
});
}).push(function(evt) {
g.props.configuration_dict = JSON.parse(evt.target.responseText);
});
}).declareMethod("render", function(options) {
var gadget = this, back_kw = {
......@@ -389,8 +337,24 @@
back_kw.id = options.id;
}
}
// Get the action information
return gadget.declareGadget(portal_types[portal_type][options.action].gadget + ".html").push(function(g) {
return gadget.getDeclaredGadget("jio").push(function(jio_gadget) {
if (options.id) {
return jio_gadget.getAttachment({
_id: options.id,
_attachment: "body.json"
});
}
}).push(function(result) {
var data;
if (result) {
data = JSON.parse(result);
gadget.props.data = data;
portal_types.Input = data.application_configuration.input;
portal_types.Output = data.application_configuration.output;
}
// Get the action information
return gadget.declareGadget(portal_types[portal_type][options.action].gadget + ".html");
}).push(function(g) {
page_gadget = g;
if (page_gadget.render !== undefined) {
return page_gadget.render(options);
......
......@@ -9,47 +9,39 @@
// Precompile the templates while loading the first gadget instance
var gadget_klass = rJS(window), source = gadget_klass.__template_element.getElementById("label-template").innerHTML, label_template = Handlebars.compile(source);
initGadgetMixin(gadget_klass);
gadget_klass.declareMethod("render", function(property_list, data, key) {
var gadget = this, queue, value, property;
gadget.key = key;
gadget_klass.declareMethod("render", function(options, node_id) {
// XXX node_id is added like a property so that one can change the node
// id
var gadget = this, queue;
gadget.props.key = options.key;
// used for recursive fieldsets
gadget.props.field_gadget_list = [];
function addField(property, value) {
function addField(property_id, property_definition, value) {
var sub_gadget;
queue.push(function() {
// XXX this is incorrect for recursive fieldsets.
// we should use nested fieldset with legend
gadget.props.element.insertAdjacentHTML("beforeend", label_template({
"for": property.id,
name: property.name || property.id
"for": property_id,
name: property_definition.name || property_definition.description || property_id
}));
if (property._class === "Dream.PropertyList") {
if (property_definition.type === "object") {
// Create a recursive fieldset for this key.
return gadget.declareGadget("../fieldset/index.html");
}
if (property.type === "number") {
if (property_definition.type === "number") {
return gadget.declareGadget("../number_field/index.html");
}
if (property.choice) {
if (property_definition.enum) {
return gadget.declareGadget("../list_field/index.html");
}
return gadget.declareGadget("../string_field/index.html");
}).push(function(gg) {
sub_gadget = gg;
var choice = property.choice || [], default_opt = choice[0] ? [ choice[0][1] ] : [ "" ];
value = data[property.id] === undefined ? value : data[property.id];
if (gg.__title === "Fieldset") {
// XXX there must be a better way instead of using __title ?
return gg.render(property.property_list, value, property.id);
}
return sub_gadget.render({
field_json: {
title: property.description || "",
key: property.id,
value: value,
items: choice,
"default": default_opt
}
key: property_id,
value: value,
property_definition: property_definition
});
}).push(function() {
return sub_gadget.getElement();
......@@ -59,10 +51,18 @@
});
}
queue = new RSVP.Queue().push(function() {
Object.keys(property_list).forEach(function(i) {
property = property_list[i];
value = property._default === undefined ? "" : property._default;
addField(property, value);
if (node_id) {
addField("id", {
type: "string"
}, node_id);
}
Object.keys(options.property_definition.properties).forEach(function(property_name) {
var property_definition = options.property_definition.properties[property_name], value = (options.value || {})[property_name] === undefined ? property_definition._default : options.value[property_name];
// XXX some properties are not editable
// XXX should not be defined here
if (property_name !== "coordinate" && property_name !== "_class") {
addField(property_name, property_definition, value);
}
});
});
return queue;
......@@ -75,8 +75,8 @@
return RSVP.all(promise_list);
}).push(function(result_list) {
var name, result = {}, content = result;
if (gadget.key) {
content = result[gadget.key] = {};
if (gadget.props.key) {
content = result[gadget.props.key] = {};
}
for (i = 0; i < result_list.length; i += 1) {
for (name in result_list[i]) {
......
......@@ -3,13 +3,15 @@
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="../lib/jquery-ui.css">
<link rel="stylesheet" href="../lib/jquerymobile.css">
<link rel="stylesheet" href="jsplumb.css">
<script src="../lib/jquery.js"></script>
<script src="../lib/jquery-ui.js"></script>
<script src="../lib/jquerymobile.js"></script>
<script src="../lib/rsvp.min.js"></script>
<script src="../lib/renderjs.min.js"></script>
<script src="../lib/jquery.jsplumb.min.js"></script>
<script src="../lib/jquery.jsplumb.js"></script>
<script src="../lib/handlebars.min.js"></script>
<script id="node-template" type="text/x-handlebars-template">
......@@ -22,9 +24,9 @@
</script>
<template id="popup-edit-template">
<div id="node-edit-popup" data-position-to="origin">
<div id="edit-popup" data-position-to="origin">
<div data-role="header" data-theme="a">
<h1 class="node_class">Node edition</h1>
<h1 class="node_class">Edit properties</h1>
<a href="#" data-rel="back" class="ui-btn ui-corner-all ui-shadow ui-btn-a ui-icon-delete ui-btn-icon-notext ui-btn-right">Close</a>
</div>
<br/>
......
......@@ -16,12 +16,25 @@
* You should have received a copy of the GNU Lesser General Public License
* along with DREAM. If not, see <http://www.gnu.org/licenses/>.
* ==========================================================================*/
/*global RSVP, rJS, $, jsPlumb, Handlebars, initGadgetMixin,
loopEventListener, promiseEventListener, DOMParser, confirm */
/*jslint unparam: true */
(function(RSVP, rJS, $, jsPlumb, Handlebars, initGadgetMixin, loopEventListener, promiseEventListener, DOMParser) {
/*global RSVP, rJS, $, jsPlumb, Handlebars,
loopEventListener, promiseEventListener, DOMParser */
/*jslint unparam: true todo: true */
(function(RSVP, rJS, $, jsPlumb, Handlebars, loopEventListener, promiseEventListener, DOMParser) {
"use strict";
/*jslint nomen: true*/
/* TODO:
* use services
* drop jquery ui dependency
* less dependancies ( promise event listner ? )
* document exposed css / jsplumb config
* no more handlebars
* accept ERP5 format
* auto springy layout
* drop zoom level
* rename draggable()
* allow changing node and edge class
* factorize node & edge popup edition
*/
/*jslint nomen: true */
var gadget_klass = rJS(window), node_template_source = gadget_klass.__template_element.getElementById("node-template").innerHTML, node_template = Handlebars.compile(node_template_source), popup_edit_template = gadget_klass.__template_element.getElementById("popup-edit-template"), domParser = new DOMParser();
function loopJsplumbBind(gadget, type, callback) {
//////////////////////////
......@@ -40,7 +53,7 @@
}
cancelResolver();
}
function itsANonResolvableTrap(resolve, reject) {
function resolver(resolve, reject) {
handle_event_callback = function() {
var args = arguments;
cancelResolver();
......@@ -55,88 +68,84 @@
};
jsplumb_instance.bind(type, handle_event_callback);
}
return new RSVP.Promise(itsANonResolvableTrap, canceller);
return new RSVP.Promise(resolver, canceller);
}
function getNodeId(node_container, element_id) {
function getNodeId(gadget, element_id) {
// returns the ID of the node in the graph from its DOM element id
var node_id;
$.each(node_container, function(k, v) {
if (v.element_id === element_id) {
$.each(gadget.props.node_id_to_dom_element_id, function(k, v) {
if (v === element_id) {
node_id = k;
return false;
}
});
return node_id;
}
function getElementId(node_container, node_id) {
return node_container[node_id].element_id;
}
function generateNodeId(gadget, element_type, option) {
var n = 1;
while (gadget.props.node_container[(option.short_id || element_type) + n] !== undefined) {
function generateNodeId(gadget, element) {
// Generate a node id
var n = 1, class_def = gadget.props.data.class_definition[element._class], id = class_def.short_id || element._class;
while (gadget.props.data.graph.node[id + n] !== undefined) {
n += 1;
}
return (option.short_id || element_type) + n;
return id + n;
}
function generateElementId(gadget_element) {
function generateDomElementId(gadget_element) {
// Generate a probably unique DOM element ID.
var n = 1;
while ($(gadget_element).find("#DreamNode_" + n).length > 0) {
n += 1;
}
return "DreamNode_" + n;
}
function updateConnectionData(gadget, connection, remove, edge_data) {
function getDefaultEdgeClass(gadget) {
// TODO: use first edge class available in class_definition
return "Dream.Edge";
}
function updateConnectionData(gadget, connection, remove) {
if (connection.ignoreEvent) {
// this hack is for edge edition. Maybe there I missed one thing and
// there is a better way.
return;
}
if (remove) {
delete gadget.props.edge_container[connection.id];
delete gadget.props.data.graph.edge[connection.id];
} else {
gadget.props.edge_container[connection.id] = [ getNodeId(gadget.props.node_container, connection.sourceId), getNodeId(gadget.props.node_container, connection.targetId), edge_data || {} ];
var edge_data = gadget.props.data.graph.edge[connection.id] || {
_class: getDefaultEdgeClass(gadget)
};
edge_data.source = getNodeId(gadget, connection.sourceId);
edge_data.destination = getNodeId(gadget, connection.targetId);
gadget.props.data.graph.edge[connection.id] = edge_data;
}
gadget.notifyDataChanged();
}
// bind to connection/connectionDetached events,
// and update the list of connections on screen.
function waitForConnection(gadget) {
loopJsplumbBind(gadget, "connection", function(info, originalEvent) {
updateConnectionData(gadget, info.connection);
});
}
function waitForConnectionDetached(gadget) {
loopJsplumbBind(gadget, "connectionDetached", function(info, originalEvent) {
updateConnectionData(gadget, info.connection, true);
});
}
function waitForConnectionClick(gadget) {
loopJsplumbBind(gadget, "click", function(connection) {
if (confirm("Delete connection ?")) {
gadget.props.jsplumb_instance.detach(connection);
}
});
}
function convertToAbsolutePosition(gadget, x, y) {
var zoom_level = (gadget.props.preference_container.zoom_level || 1) * 1.1111, canvas_size_x = $(gadget.props.element).find("#main").width(), canvas_size_y = $(gadget.props.element).find("#main").height(), size_x = $(gadget.props.element).find(".dummy_window").width() * zoom_level, size_y = $(gadget.props.element).find(".dummy_window").height() * zoom_level, top = Math.floor(y * (canvas_size_y - size_y)) + "px", left = Math.floor(x * (canvas_size_x - size_x)) + "px";
var zoom_level = gadget.props.zoom_level * 1.1111, canvas_size_x = $(gadget.props.main).width(), canvas_size_y = $(gadget.props.main).height(), size_x = $(gadget.props.element).find(".dummy_window").width() * zoom_level, size_y = $(gadget.props.element).find(".dummy_window").height() * zoom_level, top = Math.floor(y * (canvas_size_y - size_y)) + "px", left = Math.floor(x * (canvas_size_x - size_x)) + "px";
return [ left, top ];
}
function convertToRelativePosition(gadget, x, y) {
var zoom_level = (gadget.props.preference_container.zoom_level || 1) * 1.1111, canvas_size_x = $(gadget.props.element).find("#main").width(), canvas_size_y = $(gadget.props.element).find("#main").height(), size_x = $(gadget.props.element).find(".dummy_window").width() * zoom_level, size_y = $(gadget.props.element).find(".dummy_window").height() * zoom_level, top = Math.max(Math.min(y.replace("px", "") / (canvas_size_y - size_y), 1), 0), left = Math.max(Math.min(x.replace("px", "") / (canvas_size_x - size_x), 1), 0);
var zoom_level = gadget.props.zoom_level * 1.1111, canvas_size_x = $(gadget.props.main).width(), canvas_size_y = $(gadget.props.main).height(), size_x = $(gadget.props.element).find(".dummy_window").width() * zoom_level, size_y = $(gadget.props.element).find(".dummy_window").height() * zoom_level, top = Math.max(Math.min(y.replace("px", "") / (canvas_size_y - size_y), 1), 0), left = Math.max(Math.min(x.replace("px", "") / (canvas_size_x - size_x), 1), 0);
return [ left, top ];
}
function updateElementCoordinate(gadget, node_id, coordinate) {
var element_id = gadget.props.node_container[node_id].element_id, coordinates = gadget.props.preference_container.coordinates || {}, element, relative_position;
var element_id = gadget.props.node_id_to_dom_element_id[node_id], element, relative_position;
if (coordinate === undefined) {
coordinate = {};
element = $(gadget.props.element).find("#" + element_id);
relative_position = convertToRelativePosition(gadget, element.css("left"), element.css("top"));
coordinate.top = relative_position[1];
coordinate.left = relative_position[0];
coordinate = {
left: relative_position[0],
top: relative_position[1]
};
}
coordinates[node_id] = coordinate;
gadget.props.preference_container.coordinates = coordinates;
gadget.props.data.graph.node[node_id].coordinate = coordinate;
gadget.notifyDataChanged();
return coordinate;
}
function draggable(gadget) {
var jsplumb_instance = gadget.props.jsplumb_instance, stop = function(element) {
updateElementCoordinate(gadget, getNodeId(gadget.props.node_container, element.target.id));
updateElementCoordinate(gadget, getNodeId(gadget, element.target.id));
};
// XXX This function should only touch the node element that we just added.
jsplumb_instance.draggable(jsplumb_instance.getSelector(".window"), {
containment: "parent",
grid: [ 10, 10 ],
......@@ -162,79 +171,15 @@
anchor: "Continuous"
});
}
function initJsPlumb(gadget) {
var jsplumb_instance = gadget.props.jsplumb_instance;
jsplumb_instance.setRenderMode(jsplumb_instance.SVG);
jsplumb_instance.importDefaults({
HoverPaintStyle: {
strokeStyle: "#1e8151",
lineWidth: 2
},
Endpoint: [ "Dot", {
radius: 2
} ],
ConnectionOverlays: [ [ "Arrow", {
location: 1,
id: "arrow",
length: 14,
foldback: .8
} ] ],
Container: "main"
});
// listen for clicks on connections,
// and offer to change values on click.
// jsplumb_instance
// .bind("connectionDrag", function (connection) {
// return undefined;
// });
// jsplumb_instance
// .bind("connectionDragStop", function (connection) {
// return undefined;
// });
// split in 2 methods ? one for events one for manip
gadget.notifyDataChanged();
draggable(gadget);
}
function updateNodeStyle(gadget, element_id) {
var zoom_level = (gadget.props.preference_container.zoom_level || 1) * 1.1111, element = $(gadget.props.element).find("#" + element_id), new_value;
// Update node size according to the zoom level
// XXX does nothing for now
var zoom_level = gadget.props.zoom_level * 1.1111, element = $(gadget.props.element).find("#" + element_id), new_value;
$.each(gadget.props.style_attr_list, function(i, j) {
new_value = $(gadget.props.element).find(".dummy_window").css(j).replace("px", "") * zoom_level + "px";
element.css(j, new_value);
});
}
function addElementToContainer(node_container, element) {
// Now update the container of elements
/*jslint nomen: true*/
var element_data = {
_class: element._class,
name: element.name,
element_id: element.element_id
};
Object.keys(element).forEach(function(k) {
if (k !== "_class" && k !== "name" && k !== "element_id") {
element_data[k] = element[k];
}
});
node_container[element.id] = element_data;
}
// function redraw(gadget) {
// var coordinates = gadget.props.preference_container.coordinates || {},
// absolute_position,
// element;
// $.each(coordinates, function (node_id, v) {
// absolute_position = convertToAbsolutePosition(
// gadget,
// v.left,
// v.top
// );
// element = $(gadget.props.element).find(
// '#' + getElementId(gadget.props.node_container, node_id)
// );
// element.css('top', absolute_position[1]);
// element.css('left', absolute_position[0]);
// gadget.props.jsplumb_instance.repaint(element);
// });
// }
// function positionGraph(gadget) {
// $.ajax(
// '/positionGraph',
......@@ -259,132 +204,197 @@
// }
// );
// }
// function setZoom(gadget, zoom_level) {
// $.each(gadget.props.style_attr_list, function (i, j) {
// var new_value = $(gadget.props.element).find('.dummy_window')
// .css(j).replace('px', '') * zoom_level + 'px';
// $(gadget.props.element).find('.window').css(j, new_value);
// });
// }
// function zoom_in(gadget) {
// var zoom_level = (gadget.props.preference_container.zoom_level || 1.0) *
// 1.1111;
// setZoom(gadget, zoom_level);
// gadget.props.preference_container.zoom_level = zoom_level;
// gadget.notifyDataChanged();
// redraw(gadget);
// }
// function zoom_out(gadget) {
// var zoom_level = (gadget.props.preference_container.zoom_level || 1.0) *
// 0.9;
// setZoom(gadget, zoom_level);
// gadget.props.preference_container.zoom_level = zoom_level;
// gadget.notifyDataChanged();
// redraw(gadget);
// }
function removeElement(gadget, node_id) {
var element_id = gadget.props.node_container[node_id].element_id;
var element_id = gadget.props.node_id_to_dom_element_id[node_id];
gadget.props.jsplumb_instance.removeAllEndpoints($(gadget.props.element).find("#" + element_id));
$(gadget.props.element).find("#" + element_id).remove();
delete gadget.props.node_container[node_id];
delete gadget.props.preference_container.coordinates[node_id];
$.each(gadget.props.edge_container, function(k, v) {
if (node_id === v[0] || node_id === v[1]) {
delete gadget.props.edge_container[k];
delete gadget.props.data.graph.node[node_id];
delete gadget.props.node_id_to_dom_element_id[node_id];
$.each(gadget.props.data.graph.edge, function(k, v) {
if (node_id === v.source || node_id === v.destination) {
delete gadget.props.data.graph.edge[k];
}
});
gadget.notifyDataChanged();
}
function updateElementData(gadget, node_id, data) {
var element_id = gadget.props.node_container[node_id].element_id, new_id = data.id;
var element_id = gadget.props.node_id_to_dom_element_id[node_id], new_id = data.id;
if (data.data.name) {
$(gadget.props.element).find("#" + element_id).text(data.data.name).append('<div class="ep"></div></div>');
gadget.props.node_container[node_id].name = data.data.name;
$(gadget.props.element).find("#" + element_id).text(data.data.name).attr("title", data.data.name).append('<div class="ep"></div></div>');
gadget.props.data.graph.node[node_id].name = data.data.name;
}
delete data.id;
$.extend(gadget.props.node_container[node_id], data.data);
$.extend(gadget.props.data.graph.node[node_id], data.data);
if (new_id && new_id !== node_id) {
gadget.props.node_container[new_id] = gadget.props.node_container[node_id];
delete gadget.props.node_container[node_id];
delete gadget.props.node_container[new_id].id;
$.each(gadget.props.edge_container, function(k, v) {
if (v[0] === node_id) {
v[0] = new_id;
gadget.props.data.graph.node[new_id] = gadget.props.data.graph.node[node_id];
delete gadget.props.data.graph.node[node_id];
gadget.props.node_id_to_dom_element_id[new_id] = gadget.props.node_id_to_dom_element_id[node_id];
delete gadget.props.node_id_to_dom_element_id[node_id];
delete gadget.props.data.graph.node[new_id].id;
$.each(gadget.props.data.graph.edge, function(k, v) {
if (v.source === node_id) {
v.source = new_id;
}
if (v[1] === node_id) {
v[1] = new_id;
if (v.destination === node_id) {
v.destination = new_id;
}
});
gadget.props.preference_container.coordinates[new_id] = gadget.props.preference_container.coordinates[node_id];
delete gadget.props.preference_container.coordinates[node_id];
}
gadget.notifyDataChanged();
}
// function clearAll(gadget) {
// $.each(gadget.props.node_container, function (node_id) {
// removeElement(gadget, node_id);
// });
// // delete anything if still remains
// $(gadget.props.element).find("#main").children().remove();
// gadget.props.node_container = {};
// gadget.props.edge_container = {};
// gadget.props.preference_container = {};
// gadget.props.general_container = {};
// gadget.props.initGeneralProperties();
// gadget.props.prepareDialogForGeneralProperties();
// }
function addEdge(gadget, edge_id, edge_data) {
var source_id = edge_data[0], target_id = edge_data[1], data = edge_data[2], overlays = [], connection;
if (data && data.title) {
var overlays = [], connection;
if (edge_data.name) {
overlays = [ [ "Label", {
cssClass: "l1 component label",
label: data.title
label: edge_data.name
} ] ];
}
connection = gadget.props.jsplumb_instance.connect({
source: getElementId(gadget.props.node_container, source_id),
target: getElementId(gadget.props.node_container, target_id),
Connector: [ "Bezier", {
curviness: 75
} ],
overlays: overlays
if (gadget.props.data.graph.node[edge_data.source] === undefined) {
throw new Error("Edge Source " + edge_data.source + " does not exist");
}
if (gadget.props.data.graph.node[edge_data.source] === undefined) {
throw new Error("Edge Destination " + edge_data.source + " does not exist");
}
// If an edge has this data:
// { _class: 'Edge',
// source: 'N1',
// destination: 'N2',
// jsplumb_source_endpoint: 'BottomCenter',
// jsplumb_destination_endpoint: 'LeftMiddle',
// jsplumb_connector: 'Flowchart' }
// Then it is rendered using a flowchart connector. The difficulty is that
// jsplumb does not let you configure the connector type on the edge, but
// on the source endpoint. One solution seem to create all types of
// endpoints on nodes.
if (edge_data.jsplumb_connector === "Flowchart") {
connection = gadget.props.jsplumb_instance.connect({
uuids: [ edge_data.source + ".flowChart" + edge_data.jsplumb_source_endpoint, edge_data.destination + ".flowChart" + edge_data.jsplumb_destination_endpoint ],
overlays: overlays
});
} else {
connection = gadget.props.jsplumb_instance.connect({
source: gadget.props.node_id_to_dom_element_id[edge_data.source],
target: gadget.props.node_id_to_dom_element_id[edge_data.destination],
Connector: [ "Bezier", {
curviness: 75
} ],
overlays: overlays
});
}
// set data for 'connection' event that will be called "later"
gadget.props.data.graph.edge[edge_id] = edge_data;
// jsplumb assigned an id, but we are controlling ids ourselves.
connection.id = edge_id;
}
function expandSchema(class_definition, full_schema) {
// minimal expanding of json schema, supports merging allOf and $ref
// references
// TODO: check for a library that would provide full support
var property, referenced, i, expanded_class_definition = {
properties: class_definition.properties || {}
};
if (class_definition.allOf) {
for (i = 0; i < class_definition.allOf.length; i += 1) {
referenced = class_definition.allOf[i];
if (referenced.$ref) {
referenced = expandSchema(full_schema.class_definition[// 2 here is for #/
referenced.$ref.substr(2, referenced.$ref.length)], full_schema);
}
if (referenced.properties) {
for (property in referenced.properties) {
if (referenced.properties.hasOwnProperty(property)) {
if (referenced.properties[property].type) {
expanded_class_definition.properties[property] = referenced.properties[property];
}
}
}
}
}
}
return expanded_class_definition;
}
function openEdgeEditionDialog(gadget, connection) {
var edge_id = connection.id, edge_data = gadget.props.data.graph.edge[edge_id], edit_popup = $(gadget.props.element).find("#popup-edit-template"), schema, fieldset_element, delete_promise;
schema = expandSchema(gadget.props.data.class_definition[edge_data._class], gadget.props.data);
// We do not edit source & destination on edge this way.
delete schema.properties.source;
delete schema.properties.destination;
gadget.props.element.appendChild(document.importNode(popup_edit_template.content, true).children[0]);
edit_popup = $(gadget.props.element).find("#edit-popup");
edit_popup.find(".node_class").text(connection._class);
fieldset_element = edit_popup.find("fieldset")[0];
edit_popup.popup();
edit_popup.show();
function save_promise(fieldset_gadget, edge_id) {
return RSVP.Queue().push(function() {
return promiseEventListener(edit_popup.find("form")[0], "submit", false);
}).push(function(evt) {
var data = {
id: $(evt.target[1]).val(),
data: {}
};
return fieldset_gadget.getContent().then(function(r) {
$.extend(data.data, gadget.props.data.graph.edge[connection.id]);
$.extend(data.data, r);
// to redraw, we remove the edge and add again.
// but we want to disable events on connection, since event
// handling promise are executed asynchronously in undefined order,
// we cannot just remove and /then/ add, because the new edge is
// added before the old is removed.
connection.ignoreEvent = true;
gadget.props.jsplumb_instance.detach(connection);
addEdge(gadget, r.id, data.data);
});
});
}
delete_promise = new RSVP.Queue().push(function() {
return promiseEventListener(edit_popup.find("form [type='button']")[0], "click", false);
}).push(function() {
// connectionDetached event will remove the edge from data
gadget.props.jsplumb_instance.detach(connection);
});
return gadget.declareGadget("../fieldset/index.html", {
element: fieldset_element,
scope: "fieldset"
}).push(function(fieldset_gadget) {
return RSVP.all([ fieldset_gadget, fieldset_gadget.render({
value: edge_data,
property_definition: schema
}, edge_id) ]);
}).push(function(fieldset_gadget) {
edit_popup.enhanceWithin();
edit_popup.popup("open");
return fieldset_gadget[0];
}).push(function(fieldset_gadget) {
// Expose the dialog handling promise so that we can wait for it in
// test.
gadget.props.dialog_promise = RSVP.any([ save_promise(fieldset_gadget, edge_id), delete_promise ]);
return gadget.props.dialog_promise;
}).push(function() {
edit_popup.popup("close");
edit_popup.remove();
delete gadget.props.dialog_promise;
});
// call again updateConnectionData to set the connection data that
// was not passed to the connection hook
updateConnectionData(gadget, connection, 0, data);
}
// function setPreferences(gadget, preferences) {
// gadget.props.preference_container = preferences;
// var zoom_level = gadget.props.preference_container.zoom_level || 1.0;
// setZoom(gadget, zoom_level);
// }
function openNodeDialog(gadget, element, config_dict) {
var node_id = getNodeId(gadget.props.node_container, element.id), node_data = gadget.props.node_container[node_id], element_type = node_data._class.replace(".", "-"), property_list = config_dict[element_type].property_list || [], node_edit_popup = $(gadget.props.element).find("#popup-edit-template"), fieldset_element, delete_promise;
function openNodeEditionDialog(gadget, element) {
var node_id = getNodeId(gadget, element.id), node_data = gadget.props.data.graph.node[node_id], node_edit_popup = $(gadget.props.element).find("#popup-edit-template"), schema, fieldset_element, delete_promise;
// If we have no definition for this, we do not allow edition.
if (gadget.props.data.class_definition[node_data._class] === undefined) {
return;
}
schema = expandSchema(gadget.props.data.class_definition[node_data._class], gadget.props.data);
if (node_edit_popup.length !== 0) {
node_edit_popup.remove();
}
gadget.props.element.appendChild(document.importNode(popup_edit_template.content, true).children[0]);
node_edit_popup = $(gadget.props.element).find("#node-edit-popup");
node_edit_popup = $(gadget.props.element).find("#edit-popup");
// Set the name of the popup to the node class
node_edit_popup.find(".node_class").text(node_data._class);
fieldset_element = node_edit_popup.find("fieldset")[0];
node_edit_popup.popup();
node_data.id = node_id;
if (property_list.length === 0 || property_list[0].id !== "id") {
// XXX name & id should not be handled differently in form.
property_list.unshift({
_class: "Dream.Property",
id: "name",
name: "Name",
type: "string"
});
property_list.unshift({
_class: "Dream.Property",
id: "id",
name: "ID",
type: "string"
});
}
// XXX
function save_promise(fieldset_gadget, node_id) {
return RSVP.Queue().push(function() {
return promiseEventListener(node_edit_popup.find("form")[0], "submit", false);
......@@ -409,60 +419,98 @@
element: fieldset_element,
scope: "fieldset"
}).push(function(fieldset_gadget) {
// XXX those promises can probably be merged
return RSVP.all([ fieldset_gadget, fieldset_gadget.render(property_list, node_data) ]);
return RSVP.all([ fieldset_gadget, fieldset_gadget.render({
value: node_data,
property_definition: schema
}, node_id) ]);
}).push(function(fieldset_gadget) {
node_edit_popup.enhanceWithin();
node_edit_popup.popup("open");
return fieldset_gadget[0];
}).push(function(fieldset_gadget) {
return RSVP.any([ save_promise(fieldset_gadget, node_id), delete_promise ]);
// Expose the dialog handling promise so that we can wait for it in
// test.
gadget.props.dialog_promise = RSVP.any([ save_promise(fieldset_gadget, node_id), delete_promise ]);
return gadget.props.dialog_promise;
}).push(function() {
node_edit_popup.popup("close");
node_edit_popup.remove();
delete gadget.props.dialog_promise;
});
}
function waitForNodeClick(gadget, node, config_dict) {
gadget.props.nodes_click_monitor.monitor(loopEventListener(node, "dblclick", false, openNodeDialog.bind(null, gadget, node, config_dict)));
function waitForNodeClick(gadget, node) {
gadget.props.nodes_click_monitor.monitor(loopEventListener(node, "dblclick", false, openNodeEditionDialog.bind(null, gadget, node)));
}
function newElement(gadget, element, configuration) {
var element_type = element._class.replace(".", "-"), option = configuration[element_type], render_element = $(gadget.props.element).find("#main"), coordinate = element.coordinate, box, absolute_position, domElement;
// we don't save coordinates as properties of nodes
delete element.coordinate;
element.element_id = generateElementId(gadget.props.element);
if (!element.id) {
element.id = generateNodeId(gadget, element_type, option);
}
element.name = element.name || option.name;
addElementToContainer(gadget.props.node_container, element);
if (coordinate !== undefined) {
coordinate = updateElementCoordinate(gadget, element.id, coordinate);
}
if (element.element_id === undefined) {
element.element_id = generateElementId(gadget.props.element);
function waitForConnection(gadget) {
return loopJsplumbBind(gadget, "connection", function(info, originalEvent) {
updateConnectionData(gadget, info.connection, false);
});
}
function waitForConnectionDetached(gadget) {
return loopJsplumbBind(gadget, "connectionDetached", function(info, originalEvent) {
updateConnectionData(gadget, info.connection, true);
});
}
function waitForConnectionClick(gadget) {
return loopJsplumbBind(gadget, "click", function(connection) {
return openEdgeEditionDialog(gadget, connection);
});
}
function addNode(gadget, node_id, node_data) {
var render_element = $(gadget.props.main), class_definition = gadget.props.data.class_definition[node_data._class], coordinate = node_data.coordinate, dom_element_id, box, absolute_position, domElement;
dom_element_id = generateDomElementId(gadget.props.element);
gadget.props.node_id_to_dom_element_id[node_id] = dom_element_id;
node_data.name = node_data.name || class_definition.name;
gadget.props.data.graph.node[node_id] = node_data;
if (coordinate === undefined) {
coordinate = {
top: 0,
left: 0
};
}
node_data.coordinate = updateElementCoordinate(gadget, node_id, coordinate);
// XXX make node template an option, or use CSS from class_definition
/*jslint nomen: true*/
domElement = domParser.parseFromString(node_template({
"class": element._class.replace(".", "-"),
element_id: element.element_id,
title: element.name || element.id,
name: element.name || element.id
"class": node_data._class.replace(".", "-"),
element_id: dom_element_id,
title: node_data.name || node_data.id,
name: node_data.name || node_data.id
}), "text/html").querySelector(".window");
render_element.append(domElement);
waitForNodeClick(gadget, domElement, configuration);
box = $(gadget.props.element).find("#" + element.element_id);
waitForNodeClick(gadget, domElement);
box = $(gadget.props.element).find("#" + dom_element_id);
absolute_position = convertToAbsolutePosition(gadget, coordinate.left, coordinate.top);
box.css("top", absolute_position[1]);
box.css("left", absolute_position[0]);
updateNodeStyle(gadget, element.element_id);
updateNodeStyle(gadget, dom_element_id);
draggable(gadget);
gadget.notifyDataChanged();
}
function waitForDragover(gadget) {
return loopEventListener(gadget.props.main, "dragover", false, function() {
return undefined;
// XXX make only this element draggable.
// Add some flowchart endpoints
// TODO: add them all !
gadget.props.jsplumb_instance.addEndpoint(dom_element_id, {
isSource: true,
maxConnections: -1,
connector: [ "Flowchart", {
stub: [ 40, 60 ],
gap: 10,
cornerRadius: 5,
alwaysRespectStubs: true
} ]
}, {
anchor: "BottomCenter",
uuid: node_id + ".flowchartBottomCenter"
});
gadget.props.jsplumb_instance.addEndpoint(dom_element_id, {
isTarget: true,
maxConnections: -1
}, {
anchor: "LeftMiddle",
uuid: node_id + ".flowChartLeftMiddle"
});
gadget.notifyDataChanged();
}
function waitForDrop(gadget, config) {
function waitForDrop(gadget) {
var callback;
function canceller() {
if (callback !== undefined) {
......@@ -470,71 +518,74 @@
}
}
/*jslint unparam: true*/
function itsANonResolvableTrap(resolve, reject) {
function resolver(resolve, reject) {
callback = function(evt) {
try {
var element = domParser.parseFromString(evt.dataTransfer.getData("text/html"), "text/html").querySelector(".tool"), offset = $(gadget.props.main).offset(), box_top = evt.clientY - offset.top + "px", box_left = evt.clientX - offset.left + "px", element_class = element.id.replace("-", "."), relative_position = convertToRelativePosition(gadget, box_left, box_top);
newElement(gadget, {
var class_name = JSON.parse(evt.dataTransfer.getData("application/json")), offset = $(gadget.props.main).offset(), relative_position = convertToRelativePosition(gadget, evt.clientX - offset.left + "px", evt.clientY - offset.top + "px");
addNode(gadget, generateNodeId(gadget, {
_class: class_name
}), {
coordinate: {
left: relative_position[0],
top: relative_position[1]
},
_class: element_class
}, config);
_class: class_name
});
} catch (e) {
reject(e);
}
};
gadget.props.main.addEventListener("drop", callback, false);
}
return new RSVP.Promise(itsANonResolvableTrap, canceller);
return new RSVP.all([ // loopEventListener adds an event listener that will prevent default for
// dragover
loopEventListener(gadget.props.main, "dragover", false, function() {
return undefined;
}), RSVP.Promise(resolver, canceller) ]);
}
initGadgetMixin(gadget_klass);
gadget_klass.declareAcquiredMethod("getConfigurationDict", "getConfigurationDict").declareAcquiredMethod("notifyDataChanged", "notifyDataChanged").ready(function(g) {
g.props.edge_container = {};
g.props.preference_container = {};
gadget_klass.ready(function(g) {
g.props = {};
g.props.node_id_to_dom_element_id = {};
g.props.zoom_level = 1;
g.props.style_attr_list = [ "width", "height", "padding-top", "line-height" ];
}).declareMethod("render", function(data) {
g.getElement().then(function(element) {
g.props.element = element;
});
}).declareAcquiredMethod("notifyDataChanged", "notifyDataChanged").declareMethod("render", function(data) {
// var gadget = this;
this.props.data = JSON.parse(data);
this.props.node_container = this.props.data.nodes;
this.props.jsplumb_instance = jsPlumb.getInstance();
}).declareMethod("getData", function() {
return JSON.stringify({
nodes: this.props.node_container,
edges: this.props.edge_container,
preference: this.props.preference_container
});
}).declareMethod("getContent", function() {
return JSON.stringify(this.props.data);
}).declareMethod("startService", function() {
var g = this, preference = g.props.data.preference !== undefined ? g.props.data.preference : {}, coordinates = preference.coordinates, config;
g.notifyDataChanged();
return g.getConfigurationDict().push(function(config_dict) {
config = config_dict;
g.props.main = g.props.element.querySelector("#main");
initJsPlumb(g);
g.props.nodes_click_monitor = RSVP.Monitor();
$.each(g.props.data.nodes, function(key, value) {
if (coordinates === undefined || coordinates[key] === undefined) {
value.coordinate = {
top: 0,
left: 0
};
} else {
value.coordinate = coordinates[key];
}
value.id = key;
newElement(g, value, config);
if (value.data) {
// backward compatibility
updateElementData(g, key, {
data: value.data
});
}
});
$.each(g.props.data.edges, function(key, value) {
addEdge(g, key, value);
});
}).push(function() {
return RSVP.all([ waitForDragover(g), waitForDrop(g, config), waitForConnection(g), waitForConnectionDetached(g), waitForConnectionClick(g), g.props.nodes_click_monitor ]);
var gadget = this, jsplumb_instance = gadget.props.jsplumb_instance;
this.props.main = this.props.element.querySelector("#main");
jsplumb_instance.setRenderMode(jsplumb_instance.SVG);
jsplumb_instance.importDefaults({
HoverPaintStyle: {
strokeStyle: "#1e8151",
lineWidth: 2
},
Endpoint: [ "Dot", {
radius: 2
} ],
ConnectionOverlays: [ [ "Arrow", {
location: 1,
id: "arrow",
length: 14,
foldback: .8
} ] ],
Container: this.props.main
});
draggable(gadget);
this.props.nodes_click_monitor = RSVP.Monitor();
// load the data
$.each(this.props.data.graph.node, function(key, value) {
addNode(gadget, key, value);
});
$.each(this.props.data.graph.edge, function(key, value) {
addEdge(gadget, key, value);
});
return RSVP.all([ waitForDrop(gadget), waitForConnection(gadget), waitForConnectionDetached(gadget), waitForConnectionClick(gadget), gadget.props.nodes_click_monitor ]);
});
})(RSVP, rJS, $, jsPlumb, Handlebars, initGadgetMixin, loopEventListener, promiseEventListener, DOMParser);
\ No newline at end of file
})(RSVP, rJS, $, jsPlumb, Handlebars, loopEventListener, promiseEventListener, DOMParser);
\ No newline at end of file
......@@ -657,8 +657,16 @@
};
}(DOMParser));
;// IE does not support have Document.prototype.contains.
if (typeof document.contains !== 'function') {
Document.prototype.contains = function(node) {
if (node === this || node.parentNode === this)
return true;
return this.documentElement.contains(node);
}
}
;/*! RenderJs */
/*global console */
/*global console*/
/*jslint nomen: true*/
function loopEventListener(target, type, useCapture, callback) {
"use strict";
......@@ -709,7 +717,8 @@ function loopEventListener(target, type, useCapture, callback) {
* renderJs - Generic Gadget library renderer.
* http://www.renderjs.org/documentation
*/
(function (document, window, RSVP, DOMParser, Channel, undefined) {
(function (document, window, RSVP, DOMParser, Channel, MutationObserver,
Node) {
"use strict";
var gadget_model_dict = {},
......@@ -717,7 +726,76 @@ function loopEventListener(target, type, useCapture, callback) {
stylesheet_registration_dict = {},
gadget_loading_klass,
loading_klass_promise,
renderJS;
renderJS,
Monitor;
/////////////////////////////////////////////////////////////////
// Helper functions
/////////////////////////////////////////////////////////////////
function listenHashChange(gadget) {
function extractHashAndDispatch(evt) {
var hash = (evt.newURL || window.location.toString()).split('#')[1],
subhashes,
subhash,
keyvalue,
index,
options = {};
if (hash === undefined) {
hash = "";
} else {
hash = hash.split('?')[0];
}
function optionalize(key, value, dict) {
var key_list = key.split("."),
kk,
i;
for (i = 0; i < key_list.length; i += 1) {
kk = key_list[i];
if (i === key_list.length - 1) {
dict[kk] = value;
} else {
if (!dict.hasOwnProperty(kk)) {
dict[kk] = {};
}
dict = dict[kk];
}
}
}
subhashes = hash.split('&');
for (index in subhashes) {
if (subhashes.hasOwnProperty(index)) {
subhash = subhashes[index];
if (subhash !== '') {
keyvalue = subhash.split('=');
if (keyvalue.length === 2) {
optionalize(decodeURIComponent(keyvalue[0]),
decodeURIComponent(keyvalue[1]),
options);
}
}
}
}
if (gadget.render !== undefined) {
return gadget.render(options);
}
}
var result = loopEventListener(window, 'hashchange', false,
extractHashAndDispatch),
event = document.createEvent("Event");
event.initEvent('hashchange', true, true);
event.newURL = window.location.toString();
window.dispatchEvent(event);
return result;
}
function removeHash(url) {
var index = url.indexOf('#');
......@@ -727,6 +805,144 @@ function loopEventListener(target, type, useCapture, callback) {
return url;
}
function letsCrash(e) {
if (e.constructor === XMLHttpRequest) {
e = {
readyState: e.readyState,
status: e.status,
statusText: e.statusText,
response_headers: e.getAllResponseHeaders()
};
}
if (e.constructor === Array ||
e.constructor === String ||
e.constructor === Object) {
try {
e = JSON.stringify(e);
} catch (ignore) {
}
}
document.getElementsByTagName('body')[0].textContent = e;
// XXX Do not crash the application if it fails
// Where to write the error?
/*global console*/
console.error(e.stack);
console.error(e);
}
/////////////////////////////////////////////////////////////////
// Service Monitor promise
/////////////////////////////////////////////////////////////////
function ResolvedMonitorError(message) {
this.name = "resolved";
if ((message !== undefined) && (typeof message !== "string")) {
throw new TypeError('You must pass a string.');
}
this.message = message || "Default Message";
}
ResolvedMonitorError.prototype = new Error();
ResolvedMonitorError.prototype.constructor = ResolvedMonitorError;
Monitor = function () {
var monitor = this,
promise_list = [],
promise,
reject,
notify,
resolved;
if (!(this instanceof Monitor)) {
return new Monitor();
}
function canceller() {
var len = promise_list.length,
i;
for (i = 0; i < len; i += 1) {
promise_list[i].cancel();
}
// Clean it to speed up other canceller run
promise_list = [];
}
promise = new RSVP.Promise(function (done, fail, progress) {
reject = function (rejectedReason) {
if (resolved) {
return;
}
monitor.isRejected = true;
monitor.rejectedReason = rejectedReason;
resolved = true;
canceller();
return fail(rejectedReason);
};
notify = progress;
}, canceller);
monitor.cancel = function () {
if (resolved) {
return;
}
resolved = true;
promise.cancel();
promise.fail(function (rejectedReason) {
monitor.isRejected = true;
monitor.rejectedReason = rejectedReason;
});
};
monitor.then = function () {
return promise.then.apply(promise, arguments);
};
monitor.fail = function () {
return promise.fail.apply(promise, arguments);
};
monitor.monitor = function (promise_to_monitor) {
if (resolved) {
throw new ResolvedMonitorError();
}
var queue = new RSVP.Queue()
.push(function () {
return promise_to_monitor;
})
.push(function (fulfillmentValue) {
// Promise to monitor is fullfilled, remove it from the list
var len = promise_list.length,
sub_promise_to_monitor,
new_promise_list = [],
i;
for (i = 0; i < len; i += 1) {
sub_promise_to_monitor = promise_list[i];
if (!(sub_promise_to_monitor.isFulfilled ||
sub_promise_to_monitor.isRejected)) {
new_promise_list.push(sub_promise_to_monitor);
}
}
promise_list = new_promise_list;
}, function (rejectedReason) {
if (rejectedReason instanceof RSVP.CancellationError) {
if (!(promise_to_monitor.isFulfilled &&
promise_to_monitor.isRejected)) {
// The queue could be cancelled before the first push is run
promise_to_monitor.cancel();
}
}
reject(rejectedReason);
throw rejectedReason;
}, function (notificationValue) {
notify(notificationValue);
return notificationValue;
});
promise_list.push(queue);
return this;
};
};
Monitor.prototype = Object.create(RSVP.Promise.prototype);
Monitor.prototype.constructor = Monitor;
/////////////////////////////////////////////////////////////////
// RenderJSGadget
/////////////////////////////////////////////////////////////////
......@@ -742,10 +958,24 @@ function loopEventListener(target, type, useCapture, callback) {
RenderJSGadget.prototype.__required_css_list = [];
RenderJSGadget.prototype.__required_js_list = [];
function createMonitor(g) {
if (g.__monitor !== undefined) {
g.__monitor.cancel();
}
g.__monitor = new Monitor();
g.__monitor.fail(function (error) {
if (!(error instanceof RSVP.CancellationError)) {
return g.aq_reportServiceError(error);
}
}).fail(function (error) {
// Crash the application if the acquisition generates an error.
return letsCrash(error);
});
}
function clearGadgetInternalParameters(g) {
g.__sub_gadget_dict = {};
g.__monitor = new RSVP.Monitor();
g.__monitor.fail(console.error);
createMonitor(g);
}
function loadSubGadgetDOMDeclaration(g) {
......@@ -781,6 +1011,24 @@ function loopEventListener(target, type, useCapture, callback) {
return this;
};
RenderJSGadget.__service_list = [];
RenderJSGadget.declareService = function (callback) {
this.__service_list.push(callback);
return this;
};
function startService(gadget) {
gadget.__monitor.monitor(new RSVP.Queue()
.push(function () {
var i,
service_list = gadget.constructor.__service_list;
for (i = 0; i < service_list.length; i += 1) {
gadget.__monitor.monitor(service_list[i].apply(gadget));
}
})
);
}
/////////////////////////////////////////////////////////////////
// RenderJSGadget.declareMethod
/////////////////////////////////////////////////////////////////
......@@ -876,24 +1124,11 @@ function loopEventListener(target, type, useCapture, callback) {
// Allow chain
return this;
};
RenderJSGadget.declareAcquiredMethod("aq_reportServiceError",
"reportServiceError");
RenderJSGadget.declareAcquiredMethod("aq_pleasePublishMyState",
"pleasePublishMyState");
/////////////////////////////////////////////////////////////////
// RenderJSGadget.declareListener
/////////////////////////////////////////////////////////////////
RenderJSGadget.declareListener = function (name, callback) {
this.prototype[name] = function () {
var argument_list = Array.prototype.slice.call(arguments, 0),
gadget = this;
console.log("Trying to start listener " + name);
gadget.__monitor.monitor(callback.apply(this, argument_list));
};
// Allow chain
return this;
};
/////////////////////////////////////////////////////////////////
// RenderJSGadget.allowPublicAcquisition
/////////////////////////////////////////////////////////////////
......@@ -940,8 +1175,12 @@ function loopEventListener(target, type, useCapture, callback) {
RenderJSGadget.call(this);
}
RenderJSEmbeddedGadget.__ready_list = RenderJSGadget.__ready_list.slice();
RenderJSEmbeddedGadget.__service_list =
RenderJSGadget.__service_list.slice();
RenderJSEmbeddedGadget.ready =
RenderJSGadget.ready;
RenderJSEmbeddedGadget.declareService =
RenderJSGadget.declareService;
RenderJSEmbeddedGadget.prototype = new RenderJSGadget();
RenderJSEmbeddedGadget.prototype.constructor = RenderJSEmbeddedGadget;
......@@ -1014,6 +1253,9 @@ function loopEventListener(target, type, useCapture, callback) {
RenderJSIframeGadget.__ready_list = RenderJSGadget.__ready_list.slice();
RenderJSIframeGadget.ready =
RenderJSGadget.ready;
RenderJSIframeGadget.__service_list = RenderJSGadget.__service_list.slice();
RenderJSIframeGadget.declareService =
RenderJSGadget.declareService;
RenderJSIframeGadget.prototype = new RenderJSGadget();
RenderJSIframeGadget.prototype.constructor = RenderJSIframeGadget;
......@@ -1023,7 +1265,6 @@ function loopEventListener(target, type, useCapture, callback) {
function privateDeclareIframeGadget(url, options, parent_gadget) {
var gadget_instance,
iframe,
node,
iframe_loading_deferred = RSVP.defer();
if (options.element === undefined) {
throw new Error("DOM element is required to create Iframe Gadget " +
......@@ -1031,14 +1272,7 @@ function loopEventListener(target, type, useCapture, callback) {
}
// Check if the element is attached to the DOM
node = options.element.parentNode;
while (node !== null) {
if (node === document) {
break;
}
node = node.parentNode;
}
if (node === null) {
if (!document.contains(options.element)) {
throw new Error("The parent element is not attached to the DOM for " +
url);
}
......@@ -1197,6 +1431,14 @@ function loopEventListener(target, type, useCapture, callback) {
gadget_instance.__element.setAttribute("data-gadget-url", url);
gadget_instance.__element.setAttribute("data-gadget-sandbox",
options.sandbox);
gadget_instance.__element._gadget = gadget_instance;
if (document.contains(gadget_instance.__element)) {
// Put a timeout
queue.push(startService);
}
// Always return the gadget instance after ready function
queue.push(ready_wrapper);
return gadget_instance;
});
......@@ -1343,16 +1585,17 @@ function loopEventListener(target, type, useCapture, callback) {
RenderJSGadget.call(this);
};
tmp_constructor.__ready_list = RenderJSGadget.__ready_list.slice();
tmp_constructor.__service_list = RenderJSGadget.__service_list.slice();
tmp_constructor.declareMethod =
RenderJSGadget.declareMethod;
tmp_constructor.declareListener =
RenderJSGadget.declareListener;
tmp_constructor.declareAcquiredMethod =
RenderJSGadget.declareAcquiredMethod;
tmp_constructor.allowPublicAcquisition =
RenderJSGadget.allowPublicAcquisition;
tmp_constructor.ready =
RenderJSGadget.ready;
tmp_constructor.declareService =
RenderJSGadget.declareService;
tmp_constructor.prototype = new RenderJSGadget();
tmp_constructor.prototype.constructor = tmp_constructor;
tmp_constructor.prototype.__path = url;
......@@ -1459,25 +1702,28 @@ function loopEventListener(target, type, useCapture, callback) {
if (document_element.nodeType === 9) {
settings.title = document_element.title;
for (i = 0; i < document_element.head.children.length; i += 1) {
element = document_element.head.children[i];
if (element.href !== null) {
// XXX Manage relative URL during extraction of URLs
// element.href returns absolute URL in firefox but "" in chrome;
if (element.rel === "stylesheet") {
settings.required_css_list.push(
renderJS.getAbsoluteURL(element.getAttribute("href"), url)
);
} else if (element.nodeName === "SCRIPT" &&
(element.type === "text/javascript" ||
!element.type)) {
settings.required_js_list.push(
renderJS.getAbsoluteURL(element.getAttribute("src"), url)
);
} else if (element.rel === "http://www.renderjs.org/rel/interface") {
settings.interface_list.push(
renderJS.getAbsoluteURL(element.getAttribute("href"), url)
);
if (document_element.head !== null) {
for (i = 0; i < document_element.head.children.length; i += 1) {
element = document_element.head.children[i];
if (element.href !== null) {
// XXX Manage relative URL during extraction of URLs
// element.href returns absolute URL in firefox but "" in chrome;
if (element.rel === "stylesheet") {
settings.required_css_list.push(
renderJS.getAbsoluteURL(element.getAttribute("href"), url)
);
} else if (element.nodeName === "SCRIPT" &&
(element.type === "text/javascript" ||
!element.type)) {
settings.required_js_list.push(
renderJS.getAbsoluteURL(element.getAttribute("src"), url)
);
} else if (element.rel ===
"http://www.renderjs.org/rel/interface") {
settings.interface_list.push(
renderJS.getAbsoluteURL(element.getAttribute("href"), url)
);
}
}
}
}
......@@ -1542,6 +1788,7 @@ function loopEventListener(target, type, useCapture, callback) {
notifyReady,
notifyDeclareMethod,
gadget_ready = false,
iframe_top_gadget,
last_acquisition_gadget;
// Create the gadget class for the current url
......@@ -1549,55 +1796,67 @@ function loopEventListener(target, type, useCapture, callback) {
throw new Error("bootstrap should not be called twice");
}
loading_klass_promise = new RSVP.Promise(function (resolve, reject) {
if (window.self === window.top) {
last_acquisition_gadget = new RenderJSGadget();
last_acquisition_gadget.__acquired_method_dict = {
getTopURL: function () {
return url;
},
pleaseRedirectMyHash: function (param_list) {
window.location.replace(param_list[0]);
},
pleasePublishMyState: function (param_list) {
var key,
first = true,
hash = "#";
param_list[0] = mergeSubDict(param_list[0]);
for (key in param_list[0]) {
if (param_list[0].hasOwnProperty(key)) {
if (!first) {
hash += "&";
}
hash += encodeURIComponent(key) + "=" +
encodeURIComponent(param_list[0][key]);
first = false;
last_acquisition_gadget = new RenderJSGadget();
last_acquisition_gadget.__acquired_method_dict = {
getTopURL: function () {
return url;
},
reportServiceError: function (param_list) {
letsCrash(param_list[0]);
},
pleaseRedirectMyHash: function (param_list) {
window.location.replace(param_list[0]);
},
pleasePublishMyState: function (param_list) {
var key,
first = true,
hash = "#";
param_list[0] = mergeSubDict(param_list[0]);
for (key in param_list[0]) {
if (param_list[0].hasOwnProperty(key)) {
if (!first) {
hash += "&";
}
hash += encodeURIComponent(key) + "=" +
encodeURIComponent(param_list[0][key]);
first = false;
}
return hash;
}
};
// Stop acquisition on the last acquisition gadget
// Do not put this on the klass, as their could be multiple instances
last_acquisition_gadget.__aq_parent = function (method_name) {
throw new renderJS.AcquisitionError(
"No gadget provides " + method_name
);
};
return hash;
}
};
// Stop acquisition on the last acquisition gadget
// Do not put this on the klass, as their could be multiple instances
last_acquisition_gadget.__aq_parent = function (method_name) {
throw new renderJS.AcquisitionError(
"No gadget provides " + method_name
);
};
//we need to determine tmp_constructor's value before exit bootstrap
//because of function : renderJS
//but since the channel checking is async,
//we can't use code structure like:
// if channel communication is ok
// tmp_constructor = RenderJSGadget
// else
// tmp_constructor = RenderJSEmbeddedGadget
if (window.self === window.top) {
// XXX Copy/Paste from declareGadgetKlass
tmp_constructor = function () {
RenderJSGadget.call(this);
};
tmp_constructor.declareMethod = RenderJSGadget.declareMethod;
tmp_constructor.declareListener =
RenderJSGadget.declareListener;
tmp_constructor.declareAcquiredMethod =
RenderJSGadget.declareAcquiredMethod;
tmp_constructor.allowPublicAcquisition =
RenderJSGadget.allowPublicAcquisition;
tmp_constructor.__ready_list = RenderJSGadget.__ready_list.slice();
tmp_constructor.ready = RenderJSGadget.ready;
tmp_constructor.__service_list = RenderJSGadget.__service_list.slice();
tmp_constructor.declareService =
RenderJSGadget.declareService;
tmp_constructor.prototype = new RenderJSGadget();
tmp_constructor.prototype.constructor = tmp_constructor;
tmp_constructor.prototype.__path = url;
......@@ -1606,6 +1865,10 @@ function loopEventListener(target, type, useCapture, callback) {
// Create the root gadget instance and put it in the loading stack
root_gadget = new gadget_model_dict[url]();
tmp_constructor.declareService(function () {
return listenHashChange(this);
});
setAqParent(root_gadget, last_acquisition_gadget);
} else {
......@@ -1618,18 +1881,10 @@ function loopEventListener(target, type, useCapture, callback) {
// Create the root gadget instance and put it in the loading stack
tmp_constructor = RenderJSEmbeddedGadget;
tmp_constructor.__ready_list = RenderJSGadget.__ready_list.slice();
tmp_constructor.__service_list = RenderJSGadget.__service_list.slice();
tmp_constructor.prototype.__path = url;
root_gadget = new RenderJSEmbeddedGadget();
// Bind calls to renderJS method on the instance
embedded_channel.bind("methodCall", function (trans, v) {
root_gadget[v[0]].apply(root_gadget, v[1]).then(function (g) {
trans.complete(g);
}).fail(function (e) {
trans.error(e.toString());
});
trans.delayReturn(true);
});
// Notify parent about gadget instanciation
notifyReady = function () {
......@@ -1670,16 +1925,16 @@ function loopEventListener(target, type, useCapture, callback) {
return result;
};
tmp_constructor.declareListener =
RenderJSGadget.declareListener;
tmp_constructor.declareService =
RenderJSGadget.declareService;
tmp_constructor.declareAcquiredMethod =
RenderJSGadget.declareAcquiredMethod;
tmp_constructor.allowPublicAcquisition =
RenderJSGadget.allowPublicAcquisition;
// Define __aq_parent to inform parent window
//Default: Define __aq_parent to inform parent window
tmp_constructor.prototype.__aq_parent = function (method_name,
argument_list) {
argument_list, time_out) {
return new RSVP.Promise(function (resolve, reject) {
embedded_channel.call({
method: "acquire",
......@@ -1692,7 +1947,8 @@ function loopEventListener(target, type, useCapture, callback) {
},
error: function (e) {
reject(e);
}
},
timeout: time_out
});
});
};
......@@ -1733,10 +1989,77 @@ function loopEventListener(target, type, useCapture, callback) {
stylesheet_registration_dict[css_list[i]] = null;
}
gadget_loading_klass = undefined;
}).then(function () {
// select the target node
var target = document.querySelector('body'),
// create an observer instance
observer = new MutationObserver(function (mutations) {
var i, k, len, len2, node, added_list;
mutations.forEach(function (mutation) {
if (mutation.type === 'childList') {
len = mutation.removedNodes.length;
for (i = 0; i < len; i += 1) {
node = mutation.removedNodes[i];
if (node.nodeType === Node.ELEMENT_NODE) {
if (node.hasAttribute("data-gadget-url") &&
(node._gadget !== undefined)) {
createMonitor(node._gadget);
}
added_list =
node.querySelectorAll("[data-gadget-url]");
len2 = added_list.length;
for (k = 0; k < len2; k += 1) {
node = added_list[k];
if (node._gadget !== undefined) {
createMonitor(node._gadget);
}
}
}
}
len = mutation.addedNodes.length;
for (i = 0; i < len; i += 1) {
node = mutation.addedNodes[i];
if (node.nodeType === Node.ELEMENT_NODE) {
if (node.hasAttribute("data-gadget-url") &&
(node._gadget !== undefined)) {
if (document.contains(node)) {
startService(node._gadget);
}
}
added_list =
node.querySelectorAll("[data-gadget-url]");
len2 = added_list.length;
for (k = 0; k < len2; k += 1) {
node = added_list[k];
if (document.contains(node)) {
if (node._gadget !== undefined) {
startService(node._gadget);
}
}
}
}
}
}
});
}),
// configuration of the observer:
config = {
childList: true,
subtree: true,
attributes: false,
characterData: false
};
// pass in the target node, as well as the observer options
observer.observe(target, config);
return root_gadget;
}).then(resolve, function (e) {
reject(e);
/*global console */
console.error(e);
throw e;
});
......@@ -1756,17 +2079,51 @@ function loopEventListener(target, type, useCapture, callback) {
}
if (window.top !== window.self) {
tmp_constructor.ready(function () {
var base = document.createElement('base');
return root_gadget.__aq_parent('getTopURL', [])
//checking channel should be done before sub gadget's declaration
//__ready_list:
//0: clearGadgetInternalParameters
//1: loadSubGadgetDOMDeclaration
//.....
tmp_constructor.__ready_list.splice(1, 0, function () {
return root_gadget.__aq_parent('getTopURL', [], 100)
.then(function (topURL) {
var base = document.createElement('base');
base.href = topURL;
base.target = "_top";
document.head.appendChild(base);
//the channel is ok
//so bind calls to renderJS method on the instance
embedded_channel.bind("methodCall", function (trans, v) {
root_gadget[v[0]].apply(root_gadget, v[1])
.then(function (g) {
trans.complete(g);
}).fail(function (e) {
trans.error(e.toString());
});
trans.delayReturn(true);
});
})
.fail(function (error) {
if (error === "timeout_error") {
//the channel fail
//we consider current gadget is parent gadget
//redifine last acquisition gadget
iframe_top_gadget = true;
tmp_constructor.declareService(function () {
return listenHashChange(this);
});
setAqParent(root_gadget, last_acquisition_gadget);
} else {
throw error;
}
});
});
}
tmp_constructor.ready(function (g) {
return startService(g);
});
loading_gadget_promise.push(ready_wrapper);
for (i = 0; i < tmp_constructor.__ready_list.length; i += 1) {
// Put a timeout?
......@@ -1776,110 +2133,31 @@ function loopEventListener(target, type, useCapture, callback) {
.push(ready_wrapper);
}
});
if (window.self !== window.top) {
// Inform parent window that gadget is correctly loaded
if (window.self === window.top) {
loading_gadget_promise
.then(function () {
gadget_ready = true;
notifyReady();
})
.fail(function (e) {
embedded_channel.notify({method: "failed", params: e.toString()});
letsCrash(e);
throw e;
});
} else {
// XXX Bootstrap run
// Inform parent window that gadget is correctly loaded
loading_gadget_promise
.then(function () {
function extractHashAndDispatch(evt) {
var hash = evt.newURL.split('#')[1],
subhashes,
subhash,
keyvalue,
index,
options = {};
if (hash === undefined) {
hash = "";
} else {
hash = hash.split('?')[0];
}
function optionalize(key, value, dict) {
var key_list = key.split("."),
kk,
i;
for (i = 0; i < key_list.length; i += 1) {
kk = key_list[i];
if (i === key_list.length - 1) {
dict[kk] = value;
} else {
if (!dict.hasOwnProperty(kk)) {
dict[kk] = {};
}
dict = dict[kk];
}
}
}
subhashes = hash.split('&');
for (index in subhashes) {
if (subhashes.hasOwnProperty(index)) {
subhash = subhashes[index];
if (subhash !== '') {
keyvalue = subhash.split('=');
if (keyvalue.length === 2) {
optionalize(decodeURIComponent(keyvalue[0]),
decodeURIComponent(keyvalue[1]),
options);
}
}
}
}
if (root_gadget.render !== undefined) {
return root_gadget.render(options);
}
}
// XXX Manually trigger hashchange event!
return RSVP.all([
extractHashAndDispatch({newURL: window.location.toString()}),
loopEventListener(window, 'hashchange', false,
extractHashAndDispatch)
]);
}).fail(function (e) {
// XXX Do not crash the application if it fails
// Where to write the error?
if (e.constructor === XMLHttpRequest) {
console.error(e);
e = {
readyState: e.readyState,
status: e.status,
statusText: e.statusText,
response_headers: e.getAllResponseHeaders()
};
}
if (e.constructor === Array ||
e.constructor === String ||
e.constructor === Object) {
try {
e = JSON.stringify(e);
} catch (exception) {
console.error(exception);
}
gadget_ready = true;
notifyReady();
})
.fail(function (e) {
//top gadget in iframe
if (iframe_top_gadget) {
letsCrash(e);
} else {
embedded_channel.notify({method: "failed", params: e.toString()});
}
console.warn(e);
document.getElementsByTagName('body')[0].textContent = e;
throw e;
});
}
}
bootstrap();
}(document, window, RSVP, DOMParser, Channel));
}(document, window, RSVP, DOMParser, Channel, MutationObserver, Node));
......@@ -12,17 +12,18 @@
g.element = element;
});
}).declareMethod("render", function(options) {
var select = this.element.getElementsByTagName("select")[0], i, template, field_json = options.field_json, tmp = "";
select.setAttribute("name", field_json.key);
for (i = 0; i < field_json.items.length; i += 1) {
if (field_json.items[i][1] === field_json.value) {
var select = this.element.getElementsByTagName("select")[0], i, template, tmp = "";
select.setAttribute("name", options.key);
for (i = 0; i < options.property_definition.enum.length; i += 1) {
if (options.property_definition.enum[i] === options.value) {
template = selected_option_template;
} else {
template = option_template;
}
// XXX value and text are always same in json schema
tmp += template({
value: field_json.items[i][1],
text: field_json.items[i][0]
value: options.property_definition.enum[i],
text: options.property_definition.enum[i]
});
}
select.innerHTML += tmp;
......
......@@ -6,10 +6,10 @@
gadget.element = element;
});
}).declareMethod("render", function(options) {
var input = this.element.querySelector("input"), field_json = options.field_json || {};
input.setAttribute("value", field_json.value);
input.setAttribute("name", field_json.key);
input.setAttribute("title", field_json.title);
var input = this.element.querySelector("input");
input.setAttribute("value", options.value);
input.setAttribute("name", options.key);
input.setAttribute("title", options.title || options.key);
}).declareMethod("getContent", function() {
var input = this.element.querySelector("input"), result = {};
if (input.value !== "") {
......
......@@ -6,10 +6,10 @@
gadget.element = element;
});
}).declareMethod("render", function(options) {
var input = this.element.querySelector("input"), field_json = options.field_json || {};
input.setAttribute("value", field_json.value || "");
input.setAttribute("name", field_json.key);
input.setAttribute("title", field_json.title);
var input = this.element.querySelector("input");
input.setAttribute("value", options.value || "");
input.setAttribute("name", options.key);
input.setAttribute("title", options.title || options.key);
}).declareMethod("getContent", function() {
var input = this.element.querySelector("input"), result = {};
result[input.getAttribute("name")] = input.value;
......
......@@ -5,21 +5,8 @@
<link rel="stylesheet" href="../lib/jquery-ui.css">
<link rel="stylesheet" href="toolbox.css">
<script src="../lib/jquery.js"></script>
<script src="../lib/jquery-ui.js"></script>
<script src="../lib/rsvp.min.js"></script>
<script src="../lib/renderjs.min.js"></script>
<script src="../lib/handlebars.min.js"></script>
<script id="tool-template" type="text/x-handlebars-template">
<div id="{{key}}"
class="tool {{key}}"
draggable="true">
{{name}}
<ul/>
</div>
</script>
<script src="../dream/mixin_gadget.js"></script>
<script src="../dream/mixin_promise.js"></script>
......
/*global window, document, RSVP, rJS, Handlebars, initGadgetMixin*/
(function(window, document, RSVP, rJS, Handlebars, initGadgetMixin) {
/*global window, document, RSVP, rJS, initGadgetMixin*/
(function(window, document, RSVP, rJS, initGadgetMixin) {
"use strict";
/*jslint nomen: true*/
var gadget_klass = rJS(window), tool_template_source = gadget_klass.__template_element.getElementById("tool-template").innerHTML, tool_template = Handlebars.compile(tool_template_source);
var gadget_klass = rJS(window);
function waitForDragstart(tool) {
var callback;
function canceller() {
......@@ -14,7 +14,7 @@
function itsANonResolvableTrap(resolve, reject) {
callback = function(evt) {
try {
evt.dataTransfer.setData("text/html", tool.outerHTML);
evt.dataTransfer.setData("application/json", tool.dataset.class_name);
} catch (e) {
reject(e);
}
......@@ -24,22 +24,28 @@
return new RSVP.Promise(itsANonResolvableTrap, canceller);
}
initGadgetMixin(gadget_klass);
gadget_klass.declareAcquiredMethod("getConfigurationDict", "getConfigurationDict").declareMethod("render", function() {
var g = this;
return g.getConfigurationDict().push(function(config_dict) {
var tools_container = document.createElement("div");
tools_container.className = "tools-container";
Object.keys(config_dict).forEach(function(key) {
var name = config_dict[key].name || key.split("-")[1];
if (key !== "Dream-Configuration") {
tools_container.innerHTML += tool_template({
key: key,
name: name
});
}
});
g.props.element.querySelector(".tools").appendChild(tools_container);
gadget_klass.declareMethod("render", function(json_data) {
var data = JSON.parse(json_data), tools_container = document.createElement("div");
/* display all nodes in the palette.
*/
tools_container.className = "tools-container";
Object.keys(data.class_definition).forEach(function(key) {
var _class = data.class_definition[key], tool;
// XXX "expand" the json schema "allOF" etc
if (_class._class === "node") {
tool = document.createElement("div");
// XXX maybe allow to configure the class name ?
tool.className = "tool " + key;
tool.textContent = _class.name || key;
tool.draggable = true;
tool.dataset.class_name = JSON.stringify(key);
Object.keys(_class.css || {}).forEach(function(k) {
tool.style[k] = _class.css[k];
});
tools_container.appendChild(tool);
}
});
this.props.element.querySelector(".tools").appendChild(tools_container);
}).declareMethod("startService", function() {
var promiseArray = [];
[].forEach.call(this.props.element.querySelectorAll(".tool"), function(tool) {
......@@ -47,4 +53,4 @@
});
return RSVP.all(promiseArray);
});
})(window, document, RSVP, rJS, Handlebars, initGadgetMixin);
\ No newline at end of file
})(window, document, RSVP, rJS, initGadgetMixin);
\ No newline at end of file
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment