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

update static version

parent 84ce9458
......@@ -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;
......
......@@ -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_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,
key: property_id,
value: value,
items: choice,
"default": default_opt
}
property_definition: property_definition
});
}).push(function() {
return sub_gadget.getElement();
......@@ -59,10 +51,17 @@
});
}
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
if (property_name !== "coordinate" && property_name !== "_class") {
addField(property_name, property_definition, value);
}
});
});
return queue;
......@@ -75,8 +74,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]) {
......
......@@ -7,9 +7,10 @@
<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">
......
......@@ -18,10 +18,26 @@
* ==========================================================================*/
/*global RSVP, rJS, $, jsPlumb, Handlebars, initGadgetMixin,
loopEventListener, promiseEventListener, DOMParser, confirm */
/*jslint unparam: true */
/*jslint unparam: true todo: true */
(function(RSVP, rJS, $, jsPlumb, Handlebars, initGadgetMixin, loopEventListener, promiseEventListener, DOMParser) {
"use strict";
/*jslint nomen: true*/
/*jslint nomen: true */
/* TODO:
* - make node edition popup a gadget ?
* - add function to turn event handlers in promise ?
*
* tests:
* - loading & saving DONE
* - dropping a new node from palette DONE
* - dragging a node
* - editing node properties with popup (make sure we can display after
* edit)
* - connecting two nodes
* - removing a connection
* - removing a node ( make sure connections are removed )
* - changing a node id ( make sure connections are updated ) ( make sure
* we can display after edit )
*/
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 +56,7 @@
}
cancelResolver();
}
function itsANonResolvableTrap(resolve, reject) {
function resolver(resolve, reject) {
handle_event_callback = function() {
var args = arguments;
cancelResolver();
......@@ -55,29 +71,29 @@
};
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;
......@@ -86,9 +102,14 @@
}
function updateConnectionData(gadget, connection, remove, edge_data) {
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 || {} ];
edge_data = edge_data || {
_class: "Dream.Edge"
};
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();
}
......@@ -105,6 +126,7 @@
});
}
function waitForConnectionClick(gadget) {
// TODO: dialog to edit connection properties
loopJsplumbBind(gadget, "click", function(connection) {
if (confirm("Delete connection ?")) {
gadget.props.jsplumb_instance.detach(connection);
......@@ -112,30 +134,30 @@
});
}
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.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";
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.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);
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));
};
jsplumb_instance.draggable(jsplumb_instance.getSelector(".window"), {
containment: "parent",
......@@ -196,27 +218,14 @@
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,
......@@ -228,7 +237,7 @@
// v.top
// );
// element = $(gadget.props.element).find(
// '#' + getElementId(gadget.props.node_container, node_id)
// '#' + gadget.props.node_id_to_dom_element_id[node_id];
// );
// element.css('top', absolute_position[1]);
// element.css('left', absolute_position[0]);
......@@ -259,107 +268,91 @@
// }
// );
// }
// 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];
$.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.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;
}
if (v[1] === node_id) {
v[1] = 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.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),
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
});
// 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;
// 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 with 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[referenced.$ref.substr(1, 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;
}
// TODO: remove class_definition from this function and callees signature
function openNodeDialog(gadget, element, class_definition) {
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 = expandSchema(class_definition, gadget.props.data), fieldset_element, delete_promise;
if (node_edit_popup.length !== 0) {
node_edit_popup.remove();
}
......@@ -370,21 +363,7 @@
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);
......@@ -405,12 +384,15 @@
}).push(function() {
return removeElement(gadget, node_id);
});
// XXX the gadget to use on node click should be an option
return gadget.declareGadget("../fieldset/index.html", {
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");
......@@ -424,45 +406,38 @@
function waitForNodeClick(gadget, node, config_dict) {
gadget.props.nodes_click_monitor.monitor(loopEventListener(node, "dblclick", false, openNodeDialog.bind(null, gadget, node, config_dict)));
}
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 addNode(gadget, node_id, node_data) {
var render_element = $(gadget.props.element).find("#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, class_definition);
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;
});
}
function waitForDrop(gadget, config) {
function waitForDrop(gadget) {
var callback;
function canceller() {
if (callback !== undefined) {
......@@ -470,71 +445,52 @@
}
}
/*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.declareAcquiredMethod("notifyDataChanged", "notifyDataChanged").ready(function(g) {
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) {
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);
var gadget = this;
this.props.main = this.props.element.querySelector("#main");
initJsPlumb(this);
this.props.nodes_click_monitor = RSVP.Monitor();
$.each(this.props.data.graph.node, function(key, value) {
addNode(gadget, key, value);
});
}).push(function() {
return RSVP.all([ waitForDragover(g), waitForDrop(g, config), waitForConnection(g), waitForConnectionDetached(g), waitForConnectionClick(g), g.props.nodes_click_monitor ]);
$.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
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="../lib/qunit.css">
<script src="../lib/rsvp.min.js"></script>
<script src="../lib/renderjs.min.js"></script>
<script src="../lib/qunit.js"></script>
<script src="../lib/jquery.js"></script>
<script src="../lib/jquery.simulate.js"></script>
<script src="test.js"></script>
</head>
<body>
<div id="qunit"></div>
<div id="qunit-fixture"></div>
</body>
</html>
/*global rJS, JSON, QUnit, jQuery, RSVP, console*/
(function(rJS, JSON, QUnit, RSVP, $) {
"use strict";
var start = QUnit.start, stop = QUnit.stop, test = QUnit.test, equal = QUnit.equal, ok = QUnit.ok, sample_class_definition = {
edge: {
description: "Base definition for edge",
properties: {
_class: {
type: "string"
},
destination: {
type: "string"
},
name: {
type: "string"
},
required: [ "name", "_class", "source", "destination" ],
source: {
type: "string"
}
},
type: "object"
},
"Example.Edge": {
_class: "edge",
allOf: [ {
$ref: "#edge"
}, {
properties: {
color: {
"enum": [ "red", "green", "blue" ]
}
}
} ],
description: "An example edge with a color property"
},
"Example.Node": {
_class: "node",
allOf: [ {
$ref: "#node"
}, {
properties: {
shape: {
type: "string"
}
}
} ],
description: "An example node with a shape property"
},
node: {
description: "Base definition for node",
properties: {
_class: {
type: "string"
},
coordinate: {
properties: {
left: "number",
top: "number"
},
type: "object"
},
name: {
type: "string"
},
required: [ "name", "_class" ]
},
type: "object"
}
}, sample_graph = {
edge: {
edge1: {
_class: "Example.Edge",
source: "N1",
destination: "N2",
color: "blue"
}
},
node: {
N1: {
_class: "Example.Node",
name: "Node 1",
coordinate: {
top: 0,
left: 0
},
shape: "square"
},
N2: {
_class: "Example.Node",
name: "Node 2",
shape: "circle"
}
}
}, sample_graph_not_connected = {
edge: {},
node: {
N1: {
_class: "Example.Node",
name: "Node 1",
shape: "square"
},
N2: {
_class: "Example.Node",
name: "Node 2",
shape: "circle"
}
}
}, sample_data_graph = JSON.stringify({
class_definition: sample_class_definition,
graph: sample_graph
}), sample_data_graph_not_connected = JSON.stringify({
class_definition: sample_class_definition,
graph: sample_graph_not_connected
}), sample_data_empty_graph = JSON.stringify({
class_definition: sample_class_definition,
graph: {
node: {},
edge: {}
}
});
QUnit.config.testTimeout = 5e3;
rJS(window).ready(function(g) {
test("Sample graph can be loaded and output is equal to input", function() {
var jsplumb_gadget;
stop();
g.declareGadget("./index.html", {
element: document.querySelector("#qunit-fixture")
}).then(function(new_gadget) {
jsplumb_gadget = new_gadget;
return jsplumb_gadget.render(sample_data_graph);
}).then(function() {
return jsplumb_gadget.getContent();
}).then(function(content) {
equal(content, sample_data_graph);
}).fail(console.error.bind(this)).always(start);
});
test("New node can be drag & dropped", function() {
var jsplumb_gadget;
stop();
function runTest() {
// XXX here I used getContent to have a promise, but there must be a
// more elegant way.
return jsplumb_gadget.getContent().then(function() {
// fake a drop event
var e = new Event("drop");
e.dataTransfer = {
getData: function(type) {
// make sure we are called properly
equal(type, "application/json");
return JSON.stringify("Example.Node");
}
};
jsplumb_gadget.props.main.dispatchEvent(e);
}).then(function() {
return jsplumb_gadget.getContent();
}).then(function(content) {
var node, graph = JSON.parse(content).graph;
equal(1, Object.keys(graph.node).length);
node = graph.node[Object.keys(graph.node)[0]];
equal("Example.Node", node._class);
});
}
g.declareGadget("./index.html", {
element: document.querySelector("#qunit-fixture")
}).then(function(new_gadget) {
jsplumb_gadget = new_gadget;
jsplumb_gadget.render(sample_data_empty_graph);
}).then(function() {
return RSVP.any([ jsplumb_gadget.startService(), runTest() ]);
}).fail(console.error.bind(this)).always(start);
});
test("Node can be dragged", function() {
var jsplumb_gadget;
stop();
function runTest() {
return jsplumb_gadget.getContent().then(function() {
// 100 and 60 are about 10% of the #main div ( set by css, so this
// might change )
$("div[title='Node 1']").simulate("drag", {
dx: 100,
dy: 60
});
}).then(function() {
return jsplumb_gadget.getContent();
}).then(function(content) {
var graph = JSON.parse(content).graph, node_coordinate = graph.node.N1.coordinate;
// Since original coordinates where 0,0 we are now about 0.1,0.1
// as we moved 10%
ok(node_coordinate.top - .1 < .1, "Top is ok");
ok(node_coordinate.left - .1 < .1, "Left is ok");
});
}
g.declareGadget("./index.html", {
element: document.querySelector("#qunit-fixture")
}).then(function(new_gadget) {
jsplumb_gadget = new_gadget;
jsplumb_gadget.render(sample_data_graph);
}).then(function() {
return RSVP.any([ jsplumb_gadget.startService(), runTest() ]);
}).fail(console.error.bind(this)).always(start);
});
test("Node properties can be edited", function() {
var jsplumb_gadget;
stop();
function runTest() {
return jsplumb_gadget.getContent().then(function() {
$("div[title='Node 1']").simulate("dblclick");
// XXX popup not displayed
$("input[name='name']").val("Modified Name");
$("input[value='Validate']").click();
}).then(function() {
return jsplumb_gadget.getContent();
}).then(function(content) {
var graph = JSON.parse(content).graph, node = graph.node.N1;
equal(node.name, "Modified Name");
});
}
g.declareGadget("./index.html", {
element: document.querySelector("#qunit-fixture")
}).then(function(new_gadget) {
jsplumb_gadget = new_gadget;
jsplumb_gadget.render(sample_data_graph);
}).then(function() {
return RSVP.any([ jsplumb_gadget.startService(), runTest() ]);
}).fail(console.error.bind(this)).always(start);
});
test("Node can be connected", function() {
var jsplumb_gadget;
stop();
function runTest() {
return jsplumb_gadget.getContent().then(function(content) {
var node1 = jsplumb_gadget.props.main.querySelector("div[title='Node 1']"), node2 = jsplumb_gadget.props.main.querySelector("div[title='Node 2']");
// At this point we have no edge
equal(Object.keys(JSON.parse(content).graph.edge).length, 0);
jsplumb_gadget.props.jsplumb_instance.connect({
source: node1.id,
target: node2.id
});
}).then(function() {
return jsplumb_gadget.getContent();
}).then(function(content) {
var edge, graph = JSON.parse(content).graph;
equal(Object.keys(graph.node).length, 2);
equal(Object.keys(graph.edge).length, 1);
edge = graph.edge[Object.keys(graph.edge)[0]];
// XXX how edge class would be set ? the first one from schema ?
//equal(edge._class, "Example.Edge");
equal(edge.source, "N1");
equal(edge.destination, "N2");
});
}
g.declareGadget("./index.html", {
element: document.querySelector("#qunit-fixture")
}).then(function(new_gadget) {
jsplumb_gadget = new_gadget;
jsplumb_gadget.render(sample_data_graph_not_connected);
}).then(function() {
return RSVP.any([ jsplumb_gadget.startService(), runTest() ]);
}).fail(console.error.bind(this)).always(start);
});
test("Node can be deleted", function() {
var jsplumb_gadget;
stop();
function runTest() {
return jsplumb_gadget.getContent().then(function() {
$("div[title='Node 1']").simulate("dblclick");
// XXX popup not displayed
$("input[value='Delete']").click();
}).then(function() {
return jsplumb_gadget.getContent();
}).then(function(content) {
var graph = JSON.parse(content).graph;
equal(1, Object.keys(graph.node).length);
});
}
g.declareGadget("./index.html", {
element: document.querySelector("#qunit-fixture")
}).then(function(new_gadget) {
jsplumb_gadget = new_gadget;
jsplumb_gadget.render(sample_data_graph);
}).then(function() {
return RSVP.any([ jsplumb_gadget.startService(), runTest() ]);
}).fail(console.error.bind(this)).always(start);
});
});
})(rJS, JSON, QUnit, RSVP, jQuery);
\ No newline at end of file
......@@ -949,17 +949,17 @@
newFunction = newFunction || function() { };
return function() {
var r = null;
try {
//try {
r = newFunction.apply(this, arguments);
} catch (e) {
jsPlumbUtil.log("jsPlumb function failed : " + e);
}
//} catch (e) {
// jsPlumbUtil.log("jsPlumb function failed : " + e);
//}
if (returnOnThisValue == null || (r !== returnOnThisValue)) {
try {
//try {
r = wrappedFunction.apply(this, arguments);
} catch (e) {
jsPlumbUtil.log("wrapped function failed : " + e);
}
//} catch (e) {
// jsPlumbUtil.log("wrapped function failed : " + e);
//}
}
return r;
};
......
......@@ -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");
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(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
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);
}
});
g.props.element.querySelector(".tools").appendChild(tools_container);
});
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