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

graph_editor: comply with jslint 2014-04-21

Changes:
 * indentation and whitespaces between function and (. ( tips: use ignore whitespace for review)
 * usage of `new`. Some RSVP.Promise where not using `new`, some RSVP.all had an extra `new`
 * fix a "weird condition", javascript does not evaluate (a <= b <= c) same as python
 * change `var` declarations one per line, to be compatible with
   upcoming jslint version. Use a vars: true  directive so that jslint 2014-04-21 is also happy  (
   see https://stackoverflow.com/questions/34862541/expected-and-instead-saw-jslint-multivar-setting )
 * in function definitions: rename unused arguments as `ignore` or remove them when possible ( when they were last )
 * use named functions ( for exemple fillDialog ), otherwise jslint complain they are not in scope.
parent 61629778
/* =========================================================================== /* ===========================================================================
* Copyright 2013-2015 Nexedi SA and Contributors * Copyright 2013-2015 Nexedi SA and Contributors
* *
* This file is part of DREAM. * This file is part of DREAM.
...@@ -16,44 +16,46 @@ ...@@ -16,44 +16,46 @@
* You should have received a copy of the GNU Lesser General Public License * You should have received a copy of the GNU Lesser General Public License
* along with DREAM. If not, see <http://www.gnu.org/licenses/>. * along with DREAM. If not, see <http://www.gnu.org/licenses/>.
* ==========================================================================*/ * ==========================================================================*/
/*global console, window, Node, RSVP, rJS, $, jsPlumb, Handlebars, /*global console, window, Node, RSVP, rJS, $, jsPlumb, Handlebars,
loopEventListener, promiseEventListener, DOMParser, Springy */ loopEventListener, promiseEventListener, DOMParser, Springy */
/*jslint unparam: true todo: true */ /*jslint vars: true unparam: true nomen: true todo: true */
(function(RSVP, rJS, $, jsPlumb, Handlebars, loopEventListener, promiseEventListener, DOMParser, Springy) { (function (RSVP, rJS, $, jsPlumb, Handlebars, loopEventListener, promiseEventListener, DOMParser, Springy) {
"use strict"; "use strict";
/* TODO: /* TODO:
* less dependancies ( promise event listner ? ) * less dependancies ( promise event listener ? )
* no more handlebars * no more handlebars
* id should not always be modifiable * id should not always be modifiable
* drop zoom level * drop zoom level
* rename draggable() * rename draggable()
* factorize node & edge popup edition * factorize node & edge popup edition
*/ */
/*jslint nomen: true */ var gadget_klass = rJS(window);
var gadget_klass = rJS(window), var domParser = new DOMParser();
domParser = new DOMParser(), var node_template_source = gadget_klass.__template_element.getElementById("node-template").innerHTML;
node_template_source = gadget_klass.__template_element.getElementById("node-template").innerHTML, var node_template = Handlebars.compile(node_template_source);
node_template = Handlebars.compile(node_template_source), var popup_edit_template = gadget_klass.__template_element.getElementById("popup-edit-template").innerHTML;
popup_edit_template = gadget_klass.__template_element.getElementById("popup-edit-template").innerHTML;
function layoutGraph(graph_data) { function layoutGraph(graph_data) {
// Promise returning the graph once springy calculated the layout. // Promise returning the graph once springy calculated the layout.
// If the graph already contain layout, return it as is. // If the graph already contain layout, return it as is.
function resolver(resolve, reject) { function resolver(resolve, reject) {
try { try {
var springy_graph = new Springy.Graph(), var springy_graph = new Springy.Graph();
max_iterations = 100, // we stop layout after 100 iterations. var max_iterations = 100; // we stop layout after 100 iterations.
loop = 0, var loop = 0;
springy_nodes = {}, var springy_nodes = {};
drawn_nodes = {}, var drawn_nodes = {};
min_x=100, max_x=0, min_y=100, max_y=0; var min_x = 100;
var max_x = 0;
var min_y = 100;
var max_y = 0;
// if graph is empty, no need to layout // if graph is empty, no need to layout
if (Object.keys(graph_data.edge).length === 0) { if (Object.keys(graph_data.edge).length === 0) {
resolve(graph_data); resolve(graph_data);
return; return;
} }
// make a Springy graph with our graph // make a Springy graph with our graph
$.each(graph_data.node, function(key, value) { $.each(graph_data.node, function (key, value) {
if (value.coordinate && value.coordinate.top && value.coordinate.left) { if (value.coordinate && value.coordinate.top && value.coordinate.left) {
// graph already has a layout, no need to layout again // graph already has a layout, no need to layout again
resolve(graph_data); resolve(graph_data);
...@@ -61,26 +63,30 @@ ...@@ -61,26 +63,30 @@
} }
springy_nodes[key] = springy_graph.newNode({node_id: key}); springy_nodes[key] = springy_graph.newNode({node_id: key});
}); });
$.each(graph_data.edge, function(key, value) { $.each(graph_data.edge, function (ignore, value) {
springy_graph.newEdge( springy_graph.newEdge(springy_nodes[value.source], springy_nodes[value.destination]);
springy_nodes[value.source],
springy_nodes[value.destination]);
}); });
var layout = new Springy.Layout.ForceDirected(springy_graph, 400.0, 400.0, 0.5); var layout = new Springy.Layout.ForceDirected(springy_graph, 400.0, 400.0, 0.5);
var renderer = new Springy.Renderer( var renderer;
renderer = new Springy.Renderer(
layout, layout,
function clear() {}, function clear() {
function drawEdge(edge, p1, p2) {}, return;
},
function drawEdge() {
return;
},
function drawNode(node, p) { function drawNode(node, p) {
drawn_nodes[node.data.node_id] = p; drawn_nodes[node.data.node_id] = p;
if ( ++loop > max_iterations) { loop += 1;
if (loop > max_iterations) {
renderer.stop(); renderer.stop();
} }
}, },
function onRenderStop() { function onRenderStop() {
// calculate the min and max of x and y // calculate the min and max of x and y
$.each(graph_data.node, function(key, value) { $.each(graph_data.node, function (key) {
if (drawn_nodes[key].x > max_x) { if (drawn_nodes[key].x > max_x) {
max_x = drawn_nodes[key].x; max_x = drawn_nodes[key].x;
} }
...@@ -96,7 +102,7 @@ ...@@ -96,7 +102,7 @@
}); });
// "resample" the positions from 0 to 1, the scale used by this gadget. // "resample" the positions from 0 to 1, the scale used by this gadget.
// We keep a 5% margin // We keep a 5% margin
$.each(graph_data.node, function(key, value) { $.each(graph_data.node, function (key) {
graph_data.node[key].coordinate = { graph_data.node[key].coordinate = {
left: 0.05 + 0.9 * (drawn_nodes[key].x - min_x) / (max_x - min_x), left: 0.05 + 0.9 * (drawn_nodes[key].x - min_x) / (max_x - min_x),
top: 0.05 + 0.9 * (drawn_nodes[key].y - min_y) / (max_y - min_y) top: 0.05 + 0.9 * (drawn_nodes[key].y - min_y) / (max_y - min_y)
...@@ -118,7 +124,9 @@ ...@@ -118,7 +124,9 @@
// Infinite event listener (promise is never resolved) // Infinite event listener (promise is never resolved)
// eventListener is removed when promise is cancelled/rejected // eventListener is removed when promise is cancelled/rejected
////////////////////////// //////////////////////////
var handle_event_callback, callback_promise, jsplumb_instance = gadget.props.jsplumb_instance; var handle_event_callback;
var callback_promise;
var jsplumb_instance = gadget.props.jsplumb_instance;
function cancelResolver() { function cancelResolver() {
if (callback_promise !== undefined && typeof callback_promise.cancel === "function") { if (callback_promise !== undefined && typeof callback_promise.cancel === "function") {
...@@ -133,13 +141,13 @@ ...@@ -133,13 +141,13 @@
cancelResolver(); cancelResolver();
} }
function resolver(resolve, reject) { function resolver(ignore, reject) {
handle_event_callback = function() { handle_event_callback = function () {
var args = arguments; var args = arguments;
cancelResolver(); cancelResolver();
callback_promise = new RSVP.Queue().push(function() { callback_promise = new RSVP.Queue().push(function () {
return callback.apply(jsplumb_instance, args); return callback.apply(jsplumb_instance, args);
}).push(undefined, function(error) { }).push(undefined, function (error) {
if (!(error instanceof RSVP.CancellationError)) { if (!(error instanceof RSVP.CancellationError)) {
canceller(); canceller();
reject(error); reject(error);
...@@ -154,7 +162,7 @@ ...@@ -154,7 +162,7 @@
function getNodeId(gadget, element_id) { function getNodeId(gadget, element_id) {
// returns the ID of the node in the graph from its DOM element id // returns the ID of the node in the graph from its DOM element id
var node_id; var node_id;
$.each(gadget.props.node_id_to_dom_element_id, function(k, v) { $.each(gadget.props.node_id_to_dom_element_id, function (k, v) {
if (v === element_id) { if (v === element_id) {
node_id = k; node_id = k;
return false; return false;
...@@ -165,9 +173,9 @@ ...@@ -165,9 +173,9 @@
function generateNodeId(gadget, element) { function generateNodeId(gadget, element) {
// Generate a node id // Generate a node id
var n = 1, var n = 1;
class_def = gadget.props.data.class_definition[element._class], var class_def = gadget.props.data.class_definition[element._class];
id = class_def.short_id || element._class; var id = class_def.short_id || element._class;
while (gadget.props.data.graph.node[id + n] !== undefined) { while (gadget.props.data.graph.node[id + n] !== undefined) {
n += 1; n += 1;
} }
...@@ -185,8 +193,9 @@ ...@@ -185,8 +193,9 @@
function getDefaultEdgeClass(gadget) { function getDefaultEdgeClass(gadget) {
var class_definition = gadget.props.data.class_definition; var class_definition = gadget.props.data.class_definition;
for (var key in class_definition) { var key;
if (class_definition.hasOwnProperty(key) && class_definition[key]._class === 'edge') { for (key in class_definition) {
if (class_definition.hasOwnProperty(key) && class_definition[key]._class === "edge") {
return key; return key;
} }
} }
...@@ -213,31 +222,31 @@ ...@@ -213,31 +222,31 @@
} }
function convertToAbsolutePosition(gadget, x, y) { function convertToAbsolutePosition(gadget, x, y) {
var zoom_level = gadget.props.zoom_level, var zoom_level = gadget.props.zoom_level;
canvas_size_x = $(gadget.props.main).width(), var canvas_size_x = $(gadget.props.main).width();
canvas_size_y = $(gadget.props.main).height(), var canvas_size_y = $(gadget.props.main).height();
size_x = $(gadget.props.element).find(".dummy_window").width() * zoom_level, var size_x = $(gadget.props.element).find(".dummy_window").width() * zoom_level;
size_y = $(gadget.props.element).find(".dummy_window").height() * zoom_level, var size_y = $(gadget.props.element).find(".dummy_window").height() * zoom_level;
top = Math.floor(y * (canvas_size_y - size_y)) + "px", var top = Math.floor(y * (canvas_size_y - size_y)) + "px";
left = Math.floor(x * (canvas_size_x - size_x)) + "px"; var left = Math.floor(x * (canvas_size_x - size_x)) + "px";
return [left, top]; return [left, top];
} }
function convertToRelativePosition(gadget, x, y) { function convertToRelativePosition(gadget, x, y) {
var zoom_level = gadget.props.zoom_level, var zoom_level = gadget.props.zoom_level;
canvas_size_x = $(gadget.props.main).width(), var canvas_size_x = $(gadget.props.main).width();
canvas_size_y = $(gadget.props.main).height(), var canvas_size_y = $(gadget.props.main).height();
size_x = $(gadget.props.element).find(".dummy_window").width() * zoom_level, var size_x = $(gadget.props.element).find(".dummy_window").width() * zoom_level;
size_y = $(gadget.props.element).find(".dummy_window").height() * zoom_level, var 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), var 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 left = Math.max(Math.min(x.replace("px", "") / (canvas_size_x - size_x), 1), 0);
return [left, top]; return [left, top];
} }
function updateElementCoordinate(gadget, node_id, coordinate) { function updateElementCoordinate(gadget, node_id, coordinate) {
var element_id = gadget.props.node_id_to_dom_element_id[node_id], var element_id = gadget.props.node_id_to_dom_element_id[node_id];
element, var element;
relative_position; var relative_position;
if (coordinate === undefined) { if (coordinate === undefined) {
element = $(gadget.props.element).find("#" + element_id); element = $(gadget.props.element).find("#" + element_id);
relative_position = convertToRelativePosition(gadget, element.css("left"), element.css("top")); relative_position = convertToRelativePosition(gadget, element.css("left"), element.css("top"));
...@@ -252,8 +261,8 @@ ...@@ -252,8 +261,8 @@
} }
function draggable(gadget) { function draggable(gadget) {
var jsplumb_instance = gadget.props.jsplumb_instance, var jsplumb_instance = gadget.props.jsplumb_instance;
stop = function(element) { var stop = function (element) {
updateElementCoordinate(gadget, getNodeId(gadget, element.target.id)); updateElementCoordinate(gadget, getNodeId(gadget, element.target.id));
}; };
...@@ -287,10 +296,10 @@ ...@@ -287,10 +296,10 @@
function updateNodeStyle(gadget, element_id) { function updateNodeStyle(gadget, element_id) {
// Update node size according to the zoom level // Update node size according to the zoom level
// XXX does nothing for now // XXX does nothing for now
var zoom_level = gadget.props.zoom_level, var zoom_level = gadget.props.zoom_level;
element = $(gadget.props.element).find("#" + element_id), var element = $(gadget.props.element).find("#" + element_id);
new_value; var new_value;
$.each(gadget.props.style_attr_list, function(i, j) { $.each(gadget.props.style_attr_list, function (ignore, j) {
new_value = element.css(j).replace("px", "") * zoom_level + "px"; new_value = element.css(j).replace("px", "") * zoom_level + "px";
element.css(j, new_value); element.css(j, new_value);
}); });
...@@ -302,7 +311,7 @@ ...@@ -302,7 +311,7 @@
$(gadget.props.element).find("#" + element_id).remove(); $(gadget.props.element).find("#" + element_id).remove();
delete gadget.props.data.graph.node[node_id]; delete gadget.props.data.graph.node[node_id];
delete gadget.props.node_id_to_dom_element_id[node_id]; delete gadget.props.node_id_to_dom_element_id[node_id];
$.each(gadget.props.data.graph.edge, function(k, v) { $.each(gadget.props.data.graph.edge, function (k, v) {
if (node_id === v.source || node_id === v.destination) { if (node_id === v.source || node_id === v.destination) {
delete gadget.props.data.graph.edge[k]; delete gadget.props.data.graph.edge[k];
} }
...@@ -311,11 +320,11 @@ ...@@ -311,11 +320,11 @@
} }
function updateElementData(gadget, node_id, data) { function updateElementData(gadget, node_id, data) {
var element_id = gadget.props.node_id_to_dom_element_id[node_id], var element_id = gadget.props.node_id_to_dom_element_id[node_id];
new_id = data.id || data.data.id; var new_id = data.id || data.data.id;
$(gadget.props.element).find("#" + element_id).text(data.data.name || new_id) $(gadget.props.element).find("#" + element_id).text(data.data.name || new_id)
.attr("title", data.data.name || new_id) .attr("title", data.data.name || new_id)
.append('<div class="ep"></div></div>'); .append("<div class='ep'></div></div>");
delete data.id; delete data.id;
...@@ -328,7 +337,7 @@ ...@@ -328,7 +337,7 @@
delete 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; delete gadget.props.data.graph.node[new_id].id;
$.each(gadget.props.data.graph.edge, function (k, v) { $.each(gadget.props.data.graph.edge, function (ignore, v) {
if (v.source === node_id) { if (v.source === node_id) {
v.source = new_id; v.source = new_id;
} }
...@@ -342,8 +351,8 @@ ...@@ -342,8 +351,8 @@
function addEdge(gadget, edge_id, edge_data) { function addEdge(gadget, edge_id, edge_data) {
var overlays = [], var overlays = [];
connection; var connection;
if (edge_data.name) { if (edge_data.name) {
overlays = [ overlays = [
["Label", { ["Label", {
...@@ -371,7 +380,8 @@ ...@@ -371,7 +380,8 @@
// endpoints on nodes. // endpoints on nodes.
if (edge_data.jsplumb_connector === "Flowchart") { if (edge_data.jsplumb_connector === "Flowchart") {
connection = gadget.props.jsplumb_instance.connect({ connection = gadget.props.jsplumb_instance.connect({
uuids: [edge_data.source + ".flowChart" + edge_data.jsplumb_source_endpoint, uuids: [
edge_data.source + ".flowChart" + edge_data.jsplumb_source_endpoint,
edge_data.destination + ".flowChart" + edge_data.jsplumb_destination_endpoint edge_data.destination + ".flowChart" + edge_data.jsplumb_destination_endpoint
], ],
overlays: overlays overlays: overlays
...@@ -397,11 +407,10 @@ ...@@ -397,11 +407,10 @@
// references // references
// XXX this should probably be moved to fieldset ( and not handle // XXX this should probably be moved to fieldset ( and not handle
// class_definition here) // class_definition here)
function resolveReference(ref, schema) { function resolveReference(ref, schema) {
// 2 here is for #/ var i;
var i, ref_path = ref.substr(2, ref.length), var ref_path = ref.substr(2, ref.length); // 2 here is for #/
parts = ref_path.split("/"); var parts = ref_path.split("/");
for (i = 0; i < parts.length; i += 1) { for (i = 0; i < parts.length; i += 1) {
schema = schema[parts[i]]; schema = schema[parts[i]];
} }
...@@ -412,10 +421,10 @@ ...@@ -412,10 +421,10 @@
return JSON.parse(JSON.stringify(obj)); return JSON.parse(JSON.stringify(obj));
} }
var referenced, var referenced;
i, var i;
property, var property;
expanded_class_definition = clone(class_definition) || {}; var expanded_class_definition = clone(class_definition) || {};
if (!expanded_class_definition.properties) { if (!expanded_class_definition.properties) {
...@@ -469,12 +478,12 @@ ...@@ -469,12 +478,12 @@
} }
function openEdgeEditionDialog(gadget, connection) { function openEdgeEditionDialog(gadget, connection) {
var edge_id = connection.id, var edge_id = connection.id;
edge_data = gadget.props.data.graph.edge[edge_id], var edge_data = gadget.props.data.graph.edge[edge_id];
edit_popup = $(gadget.props.element).find("#popup-edit-template"), var edit_popup = $(gadget.props.element).find("#popup-edit-template");
schema, var schema;
fieldset_element, var fieldset_element;
delete_promise; var delete_promise;
schema = expandSchema(gadget.props.data.class_definition[edge_data._class], gadget.props.data); schema = expandSchema(gadget.props.data.class_definition[edge_data._class], gadget.props.data);
// We do not edit source & destination on edge this way. // We do not edit source & destination on edge this way.
delete schema.properties.source; delete schema.properties.source;
...@@ -486,15 +495,15 @@ ...@@ -486,15 +495,15 @@
edit_popup.dialog(); edit_popup.dialog();
edit_popup.show(); edit_popup.show();
function save_promise(fieldset_gadget, edge_id) { function save_promise(fieldset_gadget) {
return RSVP.Queue().push(function() { return new RSVP.Queue().push(function () {
return promiseEventListener(edit_popup.find(".graph_editor_validate_button")[0], "click", false); return promiseEventListener(edit_popup.find(".graph_editor_validate_button")[0], "click", false);
}).push(function(evt) { }).push(function (evt) {
var data = { var data = {
id: $(evt.target[1]).val(), id: $(evt.target[1]).val(),
data: {} data: {}
}; };
return fieldset_gadget.getContent().then(function(r) { return fieldset_gadget.getContent().then(function (r) {
$.extend(data.data, gadget.props.data.graph.edge[connection.id]); $.extend(data.data, gadget.props.data.graph.edge[connection.id]);
$.extend(data.data, r); $.extend(data.data, r);
// to redraw, we remove the edge and add again. // to redraw, we remove the edge and add again.
...@@ -508,32 +517,32 @@ ...@@ -508,32 +517,32 @@
}); });
}); });
} }
delete_promise = new RSVP.Queue().push(function() { delete_promise = new RSVP.Queue().push(function () {
return promiseEventListener(edit_popup.find(".graph_editor_delete_button")[0], "click", false); return promiseEventListener(edit_popup.find(".graph_editor_delete_button")[0], "click", false);
}).push(function() { }).push(function () {
// connectionDetached event will remove the edge from data // connectionDetached event will remove the edge from data
gadget.props.jsplumb_instance.detach(connection); gadget.props.jsplumb_instance.detach(connection);
}); });
return gadget.declareGadget("../fieldset/index.html", { return gadget.declareGadget("../fieldset/index.html", {
element: fieldset_element, element: fieldset_element,
scope: "fieldset" scope: "fieldset"
}).push(function(fieldset_gadget) { }).push(function (fieldset_gadget) {
return RSVP.all([fieldset_gadget, fieldset_gadget.render({ return RSVP.all([fieldset_gadget, fieldset_gadget.render({
value: edge_data, value: edge_data,
property_definition: schema property_definition: schema
}, edge_id)]); }, edge_id)]);
}).push(function(fieldset_gadget) { }).push(function (fieldset_gadget) {
edit_popup.dialog("open"); edit_popup.dialog("open");
return fieldset_gadget[0]; return fieldset_gadget[0];
}).push(function(fieldset_gadget) { }).push(function (fieldset_gadget) {
fieldset_gadget.startService(); // XXX fieldset_gadget.startService(); // XXX
return fieldset_gadget; return fieldset_gadget;
}).push(function(fieldset_gadget) { }).push(function (fieldset_gadget) {
// Expose the dialog handling promise so that we can wait for it in // Expose the dialog handling promise so that we can wait for it in
// test. // test.
gadget.props.dialog_promise = RSVP.any([save_promise(fieldset_gadget, edge_id), delete_promise]); gadget.props.dialog_promise = RSVP.any([save_promise(fieldset_gadget, edge_id), delete_promise]);
return gadget.props.dialog_promise; return gadget.props.dialog_promise;
}).push(function() { }).push(function () {
edit_popup.dialog("close"); edit_popup.dialog("close");
edit_popup.remove(); edit_popup.remove();
delete gadget.props.dialog_promise; delete gadget.props.dialog_promise;
...@@ -541,12 +550,12 @@ ...@@ -541,12 +550,12 @@
} }
function openNodeEditionDialog(gadget, element) { function openNodeEditionDialog(gadget, element) {
var node_id = getNodeId(gadget, element.id), var node_id = getNodeId(gadget, element.id);
node_data = gadget.props.data.graph.node[node_id], var node_data = gadget.props.data.graph.node[node_id];
node_edit_popup = $(gadget.props.element).find("#popup-edit-template"), var node_edit_popup = $(gadget.props.element).find("#popup-edit-template");
schema, var schema;
fieldset_element, var fieldset_element;
delete_promise; var delete_promise;
// If we have no definition for this, we do not allow edition. // If we have no definition for this, we do not allow edition.
// XXX incorrect, we need to display this dialog to be able // XXX incorrect, we need to display this dialog to be able
// to delete a node // to delete a node
...@@ -566,45 +575,59 @@ ...@@ -566,45 +575,59 @@
node_data.id = node_id; node_data.id = node_id;
function save_promise(fieldset_gadget, node_id) { function save_promise(fieldset_gadget, node_id) {
return RSVP.Queue().push(function() { return new RSVP.Queue().push(function () {
return promiseEventListener(node_edit_popup.find(".graph_editor_validate_button")[0], "click", false); return promiseEventListener(
}).push(function(evt) { node_edit_popup.find(".graph_editor_validate_button")[0],
"click",
false
);
}).push(function (evt) {
var data = { var data = {
// XXX id should not be handled differently ... // XXX id should not be handled differently ...
id: $(evt.target[1]).val(), id: $(evt.target[1]).val(),
data: {} data: {}
}; };
return fieldset_gadget.getContent().then(function(r) { return fieldset_gadget.getContent().then(function (r) {
$.extend(data.data, r); $.extend(data.data, r);
updateElementData(gadget, node_id, data); updateElementData(gadget, node_id, data);
}); });
}); });
} }
delete_promise = new RSVP.Queue().push(function() { delete_promise = new RSVP.Queue().push(function () {
return promiseEventListener(node_edit_popup.find(".graph_editor_delete_button")[0], "click", false); return promiseEventListener(
}).push(function() { node_edit_popup.find(".graph_editor_delete_button")[0],
"click",
false
);
}).push(function () {
return removeElement(gadget, node_id); return removeElement(gadget, node_id);
}); });
return gadget.declareGadget("../fieldset/index.html", { return gadget.declareGadget("../fieldset/index.html", {
element: fieldset_element, element: fieldset_element,
scope: "fieldset" scope: "fieldset"
}).push(function(fieldset_gadget) { }).push(function (fieldset_gadget) {
return RSVP.all([fieldset_gadget, fieldset_gadget.render({ return RSVP.all([
fieldset_gadget,
fieldset_gadget.render(
{
value: node_data, value: node_data,
property_definition: schema property_definition: schema
}, node_id)]); },
}).push(function(fieldset_gadget) { node_id
)
]);
}).push(function (fieldset_gadget) {
node_edit_popup.dialog("open"); node_edit_popup.dialog("open");
return fieldset_gadget[0]; return fieldset_gadget[0];
}).push(function(fieldset_gadget) { }).push(function (fieldset_gadget) {
fieldset_gadget.startService(); // XXX this should not be needed anymore. fieldset_gadget.startService(); // XXX this should not be needed anymore.
return fieldset_gadget; return fieldset_gadget;
}).push(function(fieldset_gadget) { }).push(function (fieldset_gadget) {
// Expose the dialog handling promise so that we can wait for it in // Expose the dialog handling promise so that we can wait for it in
// test. // test.
gadget.props.dialog_promise = RSVP.any([save_promise(fieldset_gadget, node_id), delete_promise]); gadget.props.dialog_promise = RSVP.any([save_promise(fieldset_gadget, node_id), delete_promise]);
return gadget.props.dialog_promise; return gadget.props.dialog_promise;
}).push(function() { }).push(function () {
node_edit_popup.dialog("close"); node_edit_popup.dialog("close");
node_edit_popup.remove(); node_edit_popup.remove();
delete gadget.props.dialog_promise; delete gadget.props.dialog_promise;
...@@ -612,31 +635,31 @@ ...@@ -612,31 +635,31 @@
} }
function waitForConnection(gadget) { function waitForConnection(gadget) {
return loopJsplumbBind(gadget, "connection", function(info, originalEvent) { return loopJsplumbBind(gadget, "connection", function (info) {
updateConnectionData(gadget, info.connection, false); updateConnectionData(gadget, info.connection, false);
}); });
} }
function waitForConnectionDetached(gadget) { function waitForConnectionDetached(gadget) {
return loopJsplumbBind(gadget, "connectionDetached", function(info, originalEvent) { return loopJsplumbBind(gadget, "connectionDetached", function (info) {
updateConnectionData(gadget, info.connection, true); updateConnectionData(gadget, info.connection, true);
}); });
} }
function waitForConnectionClick(gadget) { function waitForConnectionClick(gadget) {
return loopJsplumbBind(gadget, "click", function(connection) { return loopJsplumbBind(gadget, "click", function (connection) {
return openEdgeEditionDialog(gadget, connection); return openEdgeEditionDialog(gadget, connection);
}); });
} }
function addNode(gadget, node_id, node_data) { function addNode(gadget, node_id, node_data) {
var render_element = $(gadget.props.main), var render_element = $(gadget.props.main);
class_definition = gadget.props.data.class_definition[node_data._class], var class_definition = gadget.props.data.class_definition[node_data._class];
coordinate = node_data.coordinate, var coordinate = node_data.coordinate;
dom_element_id, var dom_element_id;
box, var box;
absolute_position, var absolute_position;
domElement; var domElement;
dom_element_id = generateDomElementId(gadget.props.element); dom_element_id = generateDomElementId(gadget.props.element);
gadget.props.node_id_to_dom_element_id[node_id] = dom_element_id; gadget.props.node_id_to_dom_element_id[node_id] = dom_element_id;
...@@ -649,7 +672,6 @@ ...@@ -649,7 +672,6 @@
}; };
} }
node_data.coordinate = updateElementCoordinate(gadget, node_id, coordinate); node_data.coordinate = updateElementCoordinate(gadget, node_id, coordinate);
/*jslint nomen: true*/
domElement = domParser.parseFromString(node_template({ domElement = domParser.parseFromString(node_template({
"class": node_data._class.replace(".", "-"), "class": node_data._class.replace(".", "-"),
element_id: dom_element_id, element_id: dom_element_id,
...@@ -700,16 +722,16 @@ ...@@ -700,16 +722,16 @@
gadget.props.main.removeEventListener("drop", callback, false); gadget.props.main.removeEventListener("drop", callback, false);
} }
} }
/*jslint unparam: true*/ function resolver(ignore, reject) {
function resolver(resolve, reject) { callback = function (evt) {
callback = function(evt) {
try { try {
var class_name, offset = $(gadget.props.main).offset(), var class_name;
relative_position = convertToRelativePosition(gadget, evt.clientX - offset.left + "px", evt.clientY - offset.top + "px"); var offset = $(gadget.props.main).offset();
var relative_position = convertToRelativePosition(gadget, evt.clientX - offset.left + "px", evt.clientY - offset.top + "px");
try { try {
// html5 compliant browser // html5 compliant browser
class_name = JSON.parse(evt.dataTransfer.getData("application/json")); class_name = JSON.parse(evt.dataTransfer.getData("application/json"));
} catch (e) { } catch (error_from_drop) {
// internet explorer // internet explorer
class_name = JSON.parse(evt.dataTransfer.getData("text")); class_name = JSON.parse(evt.dataTransfer.getData("text"));
} }
...@@ -728,33 +750,30 @@ ...@@ -728,33 +750,30 @@
}; };
gadget.props.main.addEventListener("drop", callback, false); gadget.props.main.addEventListener("drop", callback, false);
} }
return new RSVP.all([ // loopEventListener adds an event listener that will prevent default for return RSVP.all([ // loopEventListener adds an event listener that will prevent default for
// dragover // dragover
loopEventListener(gadget.props.main, "dragover", false, function() { loopEventListener(gadget.props.main, "dragover", false, function () {
return undefined; return undefined;
}), RSVP.Promise(resolver, canceller) }), new RSVP.Promise(resolver, canceller)
]); ]);
} }
gadget_klass.ready(function (g) { gadget_klass.ready(function (g) {
g.props = {}; g.props = {};
}) }).ready(function (g) {
.ready(function (g) {
return g.getElement().push(function (element) { return g.getElement().push(function (element) {
g.props.element = element; g.props.element = element;
}); });
}) }).ready(function (g) {
.ready(function(g) {
g.props.node_id_to_dom_element_id = {}; g.props.node_id_to_dom_element_id = {};
g.props.zoom_level = 1; g.props.zoom_level = 1;
g.props.style_attr_list = ["width", "height", "padding-top", "line-height"]; g.props.style_attr_list = ["width", "height", "padding-top", "line-height"];
g.getElement().then(function(element) { g.getElement().then(function (element) {
g.props.element = element; g.props.element = element;
}); });
}) }).declareAcquiredMethod("notifyDataChanged", "notifyDataChanged")
.declareAcquiredMethod("notifyDataChanged", "notifyDataChanged") .declareMethod("render", function (data) {
.declareMethod("render", function(data) { var gadget = this;
var gadget = this, jsplumb_instance;
this.props.data = {}; this.props.data = {};
if (data.key) { if (data.key) {
...@@ -764,30 +783,30 @@ ...@@ -764,30 +783,30 @@
} }
this.props.main = this.props.element.querySelector(".graph_container"); this.props.main = this.props.element.querySelector(".graph_container");
/* /*
$(this.props.main).resizable({ $(this.props.main).resizable({
resize : function(event, ui) { resize : function (event, ui) {
jsplumb_instance.repaint(ui.helper); jsplumb_instance.repaint(ui.helper);
} }
}); });
*/ */
if (data) { if (data) {
this.props.data = JSON.parse(data); this.props.data = JSON.parse(data);
// XXX how to make queue ?? // XXX how to make queue ??
return layoutGraph(this.props.data.graph).then(function(graph_data) { return layoutGraph(this.props.data.graph).then(function (graph_data) {
gadget.props.data.graph = graph_data; gadget.props.data.graph = graph_data;
// load the data // load the data
$.each(gadget.props.data.graph.node, function(key, value) { $.each(gadget.props.data.graph.node, function (key, value) {
addNode(gadget, key, value); addNode(gadget, key, value);
}); });
$.each(gadget.props.data.graph.edge, function(key, value) { $.each(gadget.props.data.graph.edge, function (key, value) {
addEdge(gadget, key, value); addEdge(gadget, key, value);
}); });
}); });
} }
}) })
.declareMethod("getContent", function() { .declareMethod("getContent", function () {
var ret = {}; var ret = {};
if (this.props.erp5_key) { if (this.props.erp5_key) {
// ERP5 // ERP5
...@@ -798,21 +817,24 @@ ...@@ -798,21 +817,24 @@
}) })
.onEvent("dblclick", function (evt) { .onEvent("dblclick", function (evt) {
var node = evt.target; var node = evt.target;
if ((node.nodeType === Node.ELEMENT_NODE) && if (
(node.tagName === "DIV") && node.classList.contains(['window'])) { (node.nodeType === Node.ELEMENT_NODE) &&
(node.tagName === "DIV") && node.classList.contains(["window"])
) {
return openNodeEditionDialog(this, node); return openNodeEditionDialog(this, node);
} }
}) })
.declareService(function() { .declareService(function () {
var gadget = this, jsplumb_instance; var gadget = this;
var jsplumb_instance;
this.props.main = this.props.element.querySelector(".graph_container"); this.props.main = this.props.element.querySelector(".graph_container");
this.props.jsplumb_instance = jsplumb_instance = jsPlumb.getInstance(); this.props.jsplumb_instance = jsplumb_instance = jsPlumb.getInstance();
if (this.props.data) { if (this.props.data) {
// load the data // load the data
$.each(this.props.data.graph.node, function(key, value) { $.each(this.props.data.graph.node, function (key, value) {
addNode(gadget, key, value); addNode(gadget, key, value);
}); });
$.each(this.props.data.graph.edge, function(key, value) { $.each(this.props.data.graph.edge, function (key, value) {
addEdge(gadget, key, value); addEdge(gadget, key, value);
}); });
} }
...@@ -837,11 +859,12 @@ ...@@ -837,11 +859,12 @@
}); });
draggable(gadget); draggable(gadget);
return RSVP.all([waitForDrop(gadget), return RSVP.all([
waitForDrop(gadget),
waitForConnection(gadget), waitForConnection(gadget),
waitForConnectionDetached(gadget), waitForConnectionDetached(gadget),
waitForConnectionClick(gadget) waitForConnectionClick(gadget)
]); ]);
}); });
})(RSVP, rJS, $, jsPlumb, Handlebars, loopEventListener, promiseEventListener, DOMParser, Springy); }(RSVP, rJS, $, jsPlumb, Handlebars, loopEventListener, promiseEventListener, DOMParser, Springy));
\ No newline at end of file \ No newline at end of file
/*global window, document, rJS, JSON, QUnit, jQuery, RSVP, console, setTimeout /*jslint vars:true nomen:true */ /* these two options are for compatibility with jslint 2014-04-21 . We'll remove them once we switch to more recent jslint */
/*global window, document, rJS, JSON, QUnit, jQuery, RSVP, console, setTimeout */
*/ (function (rJS, JSON, QUnit, RSVP, $) {
(function(rJS, JSON, QUnit, RSVP, $) {
"use strict"; "use strict";
var start = QUnit.start, var start = QUnit.start;
stop = QUnit.stop, var stop = QUnit.stop;
test = QUnit.test, var test = QUnit.test;
equal = QUnit.equal, var equal = QUnit.equal;
ok = QUnit.ok, var ok = QUnit.ok;
error_handler = function(e) { var error_handler = function (e) {
window.console.error(e); window.console.error(e);
ok(false, e); ok(false, e);
}, };
sample_class_definition = { var sample_class_definition = {
edge: { edge: {
description: "Base definition for edge", description: "Base definition for edge",
properties: { properties: {
_class: { "_class": {
type: "string" type: "string"
}, },
destination: { destination: {
...@@ -25,7 +24,7 @@ ...@@ -25,7 +24,7 @@
name: { name: {
type: "string" type: "string"
}, },
required: [ "name", "_class", "source", "destination" ], required: ["name", "_class", "source", "destination"],
source: { source: {
type: "string" type: "string"
} }
...@@ -33,35 +32,35 @@ ...@@ -33,35 +32,35 @@
type: "object" type: "object"
}, },
"Example.Edge": { "Example.Edge": {
_class: "edge", "_class": "edge",
allOf: [ { allOf: [{
$ref: "#/edge" "$ref": "#/edge"
}, { }, {
properties: { properties: {
color: { color: {
"enum": [ "red", "green", "blue" ] "enum": ["red", "green", "blue"]
} }
} }
} ], }],
description: "An example edge with a color property" description: "An example edge with a color property"
}, },
"Example.Node": { "Example.Node": {
_class: "node", "_class": "node",
allOf: [ { allOf: [{
$ref: "#/node" "$ref": "#/node"
}, { }, {
properties: { properties: {
shape: { shape: {
type: "string" type: "string"
} }
} }
} ], }],
description: "An example node with a shape property" description: "An example node with a shape property"
}, },
node: { node: {
description: "Base definition for node", description: "Base definition for node",
properties: { properties: {
_class: { "_class": {
type: "string" type: "string"
}, },
coordinate: { coordinate: {
...@@ -74,14 +73,15 @@ ...@@ -74,14 +73,15 @@
name: { name: {
type: "string" type: "string"
}, },
required: [ "name", "_class" ] required: ["name", "_class"]
}, },
type: "object" type: "object"
} }
}, sample_graph = { };
var sample_graph = {
edge: { edge: {
edge1: { edge1: {
_class: "Example.Edge", "_class": "Example.Edge",
source: "N1", source: "N1",
destination: "N2", destination: "N2",
color: "blue" color: "blue"
...@@ -89,7 +89,7 @@ ...@@ -89,7 +89,7 @@
}, },
node: { node: {
N1: { N1: {
_class: "Example.Node", "_class": "Example.Node",
name: "Node 1", name: "Node 1",
coordinate: { coordinate: {
top: 0, top: 0,
...@@ -98,7 +98,7 @@ ...@@ -98,7 +98,7 @@
shape: "square" shape: "square"
}, },
N2: { N2: {
_class: "Example.Node", "_class": "Example.Node",
name: "Node 2", name: "Node 2",
shape: "circle", shape: "circle",
coordinate: { coordinate: {
...@@ -107,10 +107,11 @@ ...@@ -107,10 +107,11 @@
} }
} }
} }
}, sample_graph_no_node_coodinate = { };
var sample_graph_no_node_coodinate = {
edge: { edge: {
edge1: { edge1: {
_class: "Example.Edge", "_class": "Example.Edge",
source: "N1", source: "N1",
destination: "N2", destination: "N2",
color: "blue" color: "blue"
...@@ -118,110 +119,118 @@ ...@@ -118,110 +119,118 @@
}, },
node: { node: {
N1: { N1: {
_class: "Example.Node", "_class": "Example.Node",
name: "Node 1", name: "Node 1",
shape: "square" shape: "square"
}, },
N2: { N2: {
_class: "Example.Node", "_class": "Example.Node",
name: "Node 2", name: "Node 2",
shape: "circle" shape: "circle"
} }
} }
}, sample_graph_not_connected = { };
var sample_graph_not_connected = {
edge: {}, edge: {},
node: { node: {
N1: { N1: {
_class: "Example.Node", "_class": "Example.Node",
name: "Node 1", name: "Node 1",
shape: "square" shape: "square"
}, },
N2: { N2: {
_class: "Example.Node", "_class": "Example.Node",
name: "Node 2", name: "Node 2",
shape: "circle" shape: "circle"
} }
} }
}, sample_data_graph = JSON.stringify({ };
var sample_data_graph = JSON.stringify({
class_definition: sample_class_definition, class_definition: sample_class_definition,
graph: sample_graph graph: sample_graph
}), sample_data_graph_no_node_coordinate = JSON.stringify({ });
var sample_data_graph_no_node_coordinate = JSON.stringify({
class_definition: sample_class_definition, class_definition: sample_class_definition,
graph: sample_graph_no_node_coodinate graph: sample_graph_no_node_coodinate
}), sample_data_graph_not_connected = JSON.stringify({ });
var sample_data_graph_not_connected = JSON.stringify({
class_definition: sample_class_definition, class_definition: sample_class_definition,
graph: sample_graph_not_connected graph: sample_graph_not_connected
}), sample_data_empty_graph = JSON.stringify({ });
var sample_data_empty_graph = JSON.stringify({
class_definition: sample_class_definition, class_definition: sample_class_definition,
graph: { graph: {
node: {}, node: {},
edge: {} edge: {}
} }
}); });
QUnit.config.testTimeout = 60000; QUnit.config.testTimeout = 60000;
rJS(window).ready(function(g) { rJS(window).ready(function (g) {
test("Sample graph can be loaded and output is equal to input", function() { test("Sample graph can be loaded and output is equal to input", function () {
var jsplumb_gadget; var jsplumb_gadget;
stop(); stop();
g.declareGadget("./index.html", { g.declareGadget("./index.html", {
element: document.querySelector("#test-element") element: document.querySelector("#test-element")
}).then(function(new_gadget) { }).then(function (new_gadget) {
jsplumb_gadget = new_gadget; jsplumb_gadget = new_gadget;
return jsplumb_gadget.render(sample_data_graph); return jsplumb_gadget.render(sample_data_graph);
}).then(function() { }).then(function () {
return jsplumb_gadget.getContent(); return jsplumb_gadget.getContent();
}).then(function(content) { }).then(function (content) {
equal(content, sample_data_graph); equal(content, sample_data_graph);
}).fail(error_handler).always(start); }).fail(error_handler).always(start);
}); });
test("New node can be drag & dropped", function() { test("New node can be drag & dropped", function () {
var jsplumb_gadget; var jsplumb_gadget;
stop(); stop();
function runTest() { function runTest() {
// XXX here I used getContent to have a promise, but there must be a // XXX here I used getContent to have a promise, but there must be a
// more elegant way. // more elegant way.
return jsplumb_gadget.getContent().then(function() { return jsplumb_gadget.getContent().then(function () {
// fake a drop event // fake a drop event
var e = new window.Event("drop"); var e = new window.Event("drop");
e.dataTransfer = { e.dataTransfer = {
getData: function(type) { getData: function (type) {
// make sure we are called properly // make sure we are called properly
equal("application/json", type, "The drag&dropped element must have data type application/json"); equal("application/json", type, "The drag&dropped element must have data type application/json");
return JSON.stringify("Example.Node"); return JSON.stringify("Example.Node");
} }
}; };
jsplumb_gadget.props.main.dispatchEvent(e); jsplumb_gadget.props.main.dispatchEvent(e);
}).then(function() { }).then(function () {
return jsplumb_gadget.getContent(); return jsplumb_gadget.getContent();
}).then(function(content) { }).then(function (content) {
var node, graph = JSON.parse(content).graph; var node;
var graph = JSON.parse(content).graph;
equal(1, Object.keys(graph.node).length, "There is one new node class"); equal(1, Object.keys(graph.node).length, "There is one new node class");
node = graph.node[Object.keys(graph.node)[0]]; node = graph.node[Object.keys(graph.node)[0]];
equal("Example.Node", node._class, "Node class is set to Example.?ode"); equal("Example.Node", node._class, "Node class is set to Example.Node");
}); });
} }
g.declareGadget("./index.html", { g.declareGadget("./index.html", {
element: document.querySelector("#test-element") element: document.querySelector("#test-element")
}).then(function(new_gadget) { }).then(function (new_gadget) {
jsplumb_gadget = new_gadget; jsplumb_gadget = new_gadget;
jsplumb_gadget.render(sample_data_empty_graph); jsplumb_gadget.render(sample_data_empty_graph);
}).then(runTest).fail(error_handler).always(start); }).then(runTest).fail(error_handler).always(start);
}); });
test("Node can be dragged", function() { test("Node can be dragged", function () {
var jsplumb_gadget; var jsplumb_gadget;
stop(); stop();
function runTest() { function runTest() {
return jsplumb_gadget.getContent().then(function() { return jsplumb_gadget.getContent().then(function () {
// 100 and 60 are about 10% of the .graph_container div ( set by css, so this // 100 and 60 are about 10% of the .graph_container div ( set by css, so this
// might change ) // might change )
$("div[title='Node 1']").simulate("drag", { $("div[title='Node 1']").simulate("drag", {
dx: 100, dx: 100,
dy: 60 dy: 60
}); });
}).then(function() { }).then(function () {
return jsplumb_gadget.getContent(); return jsplumb_gadget.getContent();
}).then(function(content) { }).then(function (content) {
var graph = JSON.parse(content).graph, node_coordinate = graph.node.N1.coordinate; var graph = JSON.parse(content).graph;
var node_coordinate = graph.node.N1.coordinate;
// Since original coordinates where 0,0 we are now about 0.1,0.1 // Since original coordinates where 0,0 we are now about 0.1,0.1
// as we moved 10% // as we moved 10%
ok(node_coordinate.top - 0.1 < 0.1, "Top is ok"); ok(node_coordinate.top - 0.1 < 0.1, "Top is ok");
...@@ -230,22 +239,22 @@ ...@@ -230,22 +239,22 @@
} }
g.declareGadget("./index.html", { g.declareGadget("./index.html", {
element: document.querySelector("#test-element") element: document.querySelector("#test-element")
}).then(function(new_gadget) { }).then(function (new_gadget) {
jsplumb_gadget = new_gadget; jsplumb_gadget = new_gadget;
jsplumb_gadget.render(sample_data_graph); jsplumb_gadget.render(sample_data_graph);
}).then(runTest).fail(error_handler).always(start); }).then(runTest).fail(error_handler).always(start);
}); });
test("Node properties can be edited", function() { test("Node properties can be edited", function () {
var jsplumb_gadget; var jsplumb_gadget;
stop(); stop();
function runTest() { function runTest() {
return jsplumb_gadget.getContent().then(function() { return jsplumb_gadget.getContent().then(function () {
// click on a node to see display the popup // click on a node to see display the popup
$("div[title='Node 1']").simulate("dblclick"); $("div[title='Node 1']").simulate("dblclick");
// Promises that handle the dialog actions are not available // Promises that handle the dialog actions are not available
// immediately after clicking. // immediately after clicking.
var promise = RSVP.Promise(function(resolve) { var promise = new RSVP.Promise(function (resolve) {
var fillDialog = function() { function fillDialog() {
if (!jsplumb_gadget.props.dialog_promise) { if (!jsplumb_gadget.props.dialog_promise) {
// Dialog not ready. Let's retry later. // Dialog not ready. Let's retry later.
// XXX this condition is actually incorrect. We need to wait // XXX this condition is actually incorrect. We need to wait
...@@ -266,12 +275,13 @@ ...@@ -266,12 +275,13 @@
// resolve our test promise once the dialog handling promise is // resolve our test promise once the dialog handling promise is
// finished. // finished.
jsplumb_gadget.props.dialog_promise.then(resolve); jsplumb_gadget.props.dialog_promise.then(resolve);
}; }
fillDialog(); fillDialog();
}); });
return promise.then(function() { return promise.then(function () {
return jsplumb_gadget.getContent().then(function(content) { return jsplumb_gadget.getContent().then(function (content) {
var graph = JSON.parse(content).graph, node = graph.node.N1; var graph = JSON.parse(content).graph;
var node = graph.node.N1;
equal("Modified Name", node.name, "Data is modified"); equal("Modified Name", node.name, "Data is modified");
equal("Modified Name", $("div#" + jsplumb_gadget.props.node_id_to_dom_element_id.N1).text(), "DOM is modified"); equal("Modified Name", $("div#" + jsplumb_gadget.props.node_id_to_dom_element_id.N1).text(), "DOM is modified");
equal(1, $("div[title='Modified Name']").length, "DOM title attribute is modified"); equal(1, $("div[title='Modified Name']").length, "DOM title attribute is modified");
...@@ -281,27 +291,28 @@ ...@@ -281,27 +291,28 @@
} }
g.declareGadget("./index.html", { g.declareGadget("./index.html", {
element: document.querySelector("#test-element") element: document.querySelector("#test-element")
}).then(function(new_gadget) { }).then(function (new_gadget) {
jsplumb_gadget = new_gadget; jsplumb_gadget = new_gadget;
jsplumb_gadget.render(sample_data_graph); jsplumb_gadget.render(sample_data_graph);
}).then(runTest).fail(error_handler).always(start); }).then(runTest).fail(error_handler).always(start);
}); });
test("Node can be connected", function() { test("Node can be connected", function () {
var jsplumb_gadget; var jsplumb_gadget;
stop(); stop();
function runTest() { function runTest() {
return jsplumb_gadget.getContent().then(function(content) { return jsplumb_gadget.getContent().then(function (content) {
var node1 = jsplumb_gadget.props.main.querySelector("div[title='Node 1']"), var node1 = jsplumb_gadget.props.main.querySelector("div[title='Node 1']");
node2 = jsplumb_gadget.props.main.querySelector("div[title='Node 2']"); var node2 = jsplumb_gadget.props.main.querySelector("div[title='Node 2']");
equal(0, Object.keys(JSON.parse(content).graph.edge).length, "There are no edge at the beginning"); equal(0, Object.keys(JSON.parse(content).graph.edge).length, "There are no edge at the beginning");
jsplumb_gadget.props.jsplumb_instance.connect({ jsplumb_gadget.props.jsplumb_instance.connect({
source: node1.id, source: node1.id,
target: node2.id target: node2.id
}); });
}).then(function() { }).then(function () {
return jsplumb_gadget.getContent(); return jsplumb_gadget.getContent();
}).then(function(content) { }).then(function (content) {
var edge, graph = JSON.parse(content).graph; var edge;
var graph = JSON.parse(content).graph;
equal(2, Object.keys(graph.node).length, "We still have 2 nodes"); equal(2, Object.keys(graph.node).length, "We still have 2 nodes");
equal(1, Object.keys(graph.edge).length, "We have 1 edge"); equal(1, Object.keys(graph.edge).length, "We have 1 edge");
edge = graph.edge[Object.keys(graph.edge)[0]]; edge = graph.edge[Object.keys(graph.edge)[0]];
...@@ -313,24 +324,24 @@ ...@@ -313,24 +324,24 @@
} }
g.declareGadget("./index.html", { g.declareGadget("./index.html", {
element: document.querySelector("#test-element") element: document.querySelector("#test-element")
}).then(function(new_gadget) { }).then(function (new_gadget) {
jsplumb_gadget = new_gadget; jsplumb_gadget = new_gadget;
jsplumb_gadget.render(sample_data_graph_not_connected); jsplumb_gadget.render(sample_data_graph_not_connected);
}).then(runTest).fail(error_handler).always(start); }).then(runTest).fail(error_handler).always(start);
}); });
test("Node can be deleted", function() { test("Node can be deleted", function () {
var jsplumb_gadget; var jsplumb_gadget;
stop(); stop();
function runTest() { function runTest() {
return jsplumb_gadget.getContent().then(function() { return jsplumb_gadget.getContent().then(function () {
equal(1, $("div[title='Node 1']").length, "node 1 is visible"); equal(1, $("div[title='Node 1']").length, "node 1 is visible");
equal(1, $("._jsPlumb_connector").length, "there is 1 connection"); equal(1, $("._jsPlumb_connector").length, "there is 1 connection");
// click on node 1 to see display the popup // click on node 1 to see display the popup
$("div[title='Node 1']").simulate("dblclick"); $("div[title='Node 1']").simulate("dblclick");
// Promises that handle the dialog actions are not available // Promises that handle the dialog actions are not available
// immediately after clicking. // immediately after clicking.
var promise = RSVP.Promise(function(resolve) { var promise = new RSVP.Promise(function (resolve) {
var waitForDialogAndDelete = function() { function waitForDialogAndDelete() {
if (!jsplumb_gadget.props.dialog_promise) { if (!jsplumb_gadget.props.dialog_promise) {
// Dialog not ready. Let's retry later. // Dialog not ready. Let's retry later.
// XXX this condition is actually incorrect. We need to wait // XXX this condition is actually incorrect. We need to wait
...@@ -344,11 +355,11 @@ ...@@ -344,11 +355,11 @@
// resolve our test promise once the dialog handling promise is // resolve our test promise once the dialog handling promise is
// finished. // finished.
jsplumb_gadget.props.dialog_promise.then(resolve); jsplumb_gadget.props.dialog_promise.then(resolve);
}; }
waitForDialogAndDelete(); waitForDialogAndDelete();
}); });
return promise.then(function() { return promise.then(function () {
return jsplumb_gadget.getContent().then(function(content) { return jsplumb_gadget.getContent().then(function (content) {
var graph = JSON.parse(content).graph; var graph = JSON.parse(content).graph;
equal(1, Object.keys(graph.node).length, "node is removed from data"); equal(1, Object.keys(graph.node).length, "node is removed from data");
equal(0, Object.keys(graph.edge).length, "edge referencing this node is also removed"); equal(0, Object.keys(graph.edge).length, "edge referencing this node is also removed");
...@@ -360,22 +371,22 @@ ...@@ -360,22 +371,22 @@
} }
g.declareGadget("./index.html", { g.declareGadget("./index.html", {
element: document.querySelector("#test-element") element: document.querySelector("#test-element")
}).then(function(new_gadget) { }).then(function (new_gadget) {
jsplumb_gadget = new_gadget; jsplumb_gadget = new_gadget;
jsplumb_gadget.render(sample_data_graph); jsplumb_gadget.render(sample_data_graph);
}).then(runTest).fail(error_handler).always(start); }).then(runTest).fail(error_handler).always(start);
}); });
test("Node id can be changed (connections are updated and node can be edited afterwards)", function() { test("Node id can be changed (connections are updated and node can be edited afterwards)", function () {
var jsplumb_gadget; var jsplumb_gadget;
stop(); stop();
function runTest() { function runTest() {
return jsplumb_gadget.getContent().then(function() { return jsplumb_gadget.getContent().then(function () {
// click on a node to see display the popup // click on a node to see display the popup
$("div[title='Node 1']").simulate("dblclick"); $("div[title='Node 1']").simulate("dblclick");
// Promises that handle the dialog actions are not available // Promises that handle the dialog actions are not available
// immediately after clicking. // immediately after clicking.
var promise = RSVP.Promise(function(resolve) { var promise = new RSVP.Promise(function (resolve) {
var fillDialog = function() { function fillDialog() {
if (!jsplumb_gadget.props.dialog_promise) { if (!jsplumb_gadget.props.dialog_promise) {
// Dialog not ready. Let's retry later. // Dialog not ready. Let's retry later.
// XXX this condition is actually incorrect. We need to wait // XXX this condition is actually incorrect. We need to wait
...@@ -392,11 +403,11 @@ ...@@ -392,11 +403,11 @@
// resolve our test promise once the dialog handling promise is // resolve our test promise once the dialog handling promise is
// finished. // finished.
jsplumb_gadget.props.dialog_promise.then(resolve); jsplumb_gadget.props.dialog_promise.then(resolve);
}; }
fillDialog(); fillDialog();
}); });
return promise.then(function() { return promise.then(function () {
return jsplumb_gadget.getContent().then(function(content) { return jsplumb_gadget.getContent().then(function (content) {
var graph = JSON.parse(content).graph; var graph = JSON.parse(content).graph;
equal(2, Object.keys(graph.node).length, "We still have two nodes"); equal(2, Object.keys(graph.node).length, "We still have two nodes");
ok(graph.node.N1b !== undefined, "Node Id changed"); ok(graph.node.N1b !== undefined, "Node Id changed");
...@@ -408,45 +419,47 @@ ...@@ -408,45 +419,47 @@
} }
g.declareGadget("./index.html", { g.declareGadget("./index.html", {
element: document.querySelector("#test-element") element: document.querySelector("#test-element")
}).then(function(new_gadget) { }).then(function (new_gadget) {
jsplumb_gadget = new_gadget; jsplumb_gadget = new_gadget;
jsplumb_gadget.render(sample_data_graph); jsplumb_gadget.render(sample_data_graph);
}).then(runTest).fail(error_handler).always(start); }).then(runTest).fail(error_handler).always(start);
}); });
test("New node can be edited", function() { test("New node can be edited", function () {
var jsplumb_gadget, node_id; var jsplumb_gadget;
var node_id;
stop(); stop();
function runTest() { function runTest() {
// XXX here I used getContent to have a promise, but there must be a // XXX here I used getContent to have a promise, but there must be a
// more elegant way. // more elegant way.
return jsplumb_gadget.getContent().then(function() { return jsplumb_gadget.getContent().then(function () {
// fake a drop event // fake a drop event
var e = new window.Event("drop"); var e = new window.Event("drop");
e.dataTransfer = { e.dataTransfer = {
getData: function(type) { getData: function (type) {
// make sure we are called properly // make sure we are called properly
equal("application/json", type, "The drag&dropped element must have data type application/json"); equal("application/json", type, "The drag&dropped element must have data type application/json");
return JSON.stringify("Example.Node"); return JSON.stringify("Example.Node");
} }
}; };
jsplumb_gadget.props.main.dispatchEvent(e); jsplumb_gadget.props.main.dispatchEvent(e);
}).then(function() { }).then(function () {
return jsplumb_gadget.getContent(); return jsplumb_gadget.getContent();
}).then(function(content) { }).then(function (content) {
var node, graph = JSON.parse(content).graph; var node;
var graph = JSON.parse(content).graph;
equal(1, Object.keys(graph.node).length); equal(1, Object.keys(graph.node).length);
node_id = Object.keys(graph.node)[0]; node_id = Object.keys(graph.node)[0];
node = graph.node[node_id]; node = graph.node[node_id];
equal("Example.Node", node._class); equal("Example.Node", node._class);
}).then(function() { }).then(function () {
// click the new node to see display the popup // click the new node to see display the popup
// XXX at the moment nodes have class window // XXX at the moment nodes have class window
equal(1, $("div.window").length, "We have a new node"); equal(1, $("div.window").length, "We have a new node");
$("div.window").simulate("dblclick"); $("div.window").simulate("dblclick");
// Promises that handle the dialog actions are not available // Promises that handle the dialog actions are not available
// immediately after clicking. // immediately after clicking.
var promise = RSVP.Promise(function(resolve) { var promise = new RSVP.Promise(function (resolve) {
var fillDialog = function() { function fillDialog() {
if (!jsplumb_gadget.props.dialog_promise) { if (!jsplumb_gadget.props.dialog_promise) {
// Dialog not ready. Let's retry later. // Dialog not ready. Let's retry later.
// XXX this condition is actually incorrect. We need to wait // XXX this condition is actually incorrect. We need to wait
...@@ -467,12 +480,13 @@ ...@@ -467,12 +480,13 @@
// resolve our test promise once the dialog handling promise is // resolve our test promise once the dialog handling promise is
// finished. // finished.
jsplumb_gadget.props.dialog_promise.then(resolve); jsplumb_gadget.props.dialog_promise.then(resolve);
}; }
fillDialog(); fillDialog();
}); });
return promise.then(function() { return promise.then(function () {
return jsplumb_gadget.getContent().then(function(content) { return jsplumb_gadget.getContent().then(function (content) {
var graph = JSON.parse(content).graph, node = graph.node[node_id]; var graph = JSON.parse(content).graph;
var node = graph.node[node_id];
equal("Modified Name", node.name, "Data is modified"); equal("Modified Name", node.name, "Data is modified");
equal("Modified Name", $("div.window").text(), "DOM is modified"); equal("Modified Name", $("div.window").text(), "DOM is modified");
}); });
...@@ -481,45 +495,47 @@ ...@@ -481,45 +495,47 @@
} }
g.declareGadget("./index.html", { g.declareGadget("./index.html", {
element: document.querySelector("#test-element") element: document.querySelector("#test-element")
}).then(function(new_gadget) { }).then(function (new_gadget) {
jsplumb_gadget = new_gadget; jsplumb_gadget = new_gadget;
jsplumb_gadget.render(sample_data_empty_graph); jsplumb_gadget.render(sample_data_empty_graph);
}).then(runTest).fail(error_handler).always(start); }).then(runTest).fail(error_handler).always(start);
}); });
test("New node can be deleted", function() { test("New node can be deleted", function () {
var jsplumb_gadget, node_id; var jsplumb_gadget;
var node_id;
stop(); stop();
function runTest() { function runTest() {
// XXX here I used getContent to have a promise, but there must be a // XXX here I used getContent to have a promise, but there must be a
// more elegant way. // more elegant way.
return jsplumb_gadget.getContent().then(function() { return jsplumb_gadget.getContent().then(function () {
// fake a drop event // fake a drop event
var e = new window.Event("drop"); var e = new window.Event("drop");
e.dataTransfer = { e.dataTransfer = {
getData: function(type) { getData: function (type) {
// make sure we are called properly // make sure we are called properly
equal("application/json", type, "The drag&dropped element must have data type application/json"); equal("application/json", type, "The drag&dropped element must have data type application/json");
return JSON.stringify("Example.Node"); return JSON.stringify("Example.Node");
} }
}; };
jsplumb_gadget.props.main.dispatchEvent(e); jsplumb_gadget.props.main.dispatchEvent(e);
}).then(function() { }).then(function () {
return jsplumb_gadget.getContent(); return jsplumb_gadget.getContent();
}).then(function(content) { }).then(function (content) {
var node, graph = JSON.parse(content).graph; var node;
var graph = JSON.parse(content).graph;
equal(1, Object.keys(graph.node).length); equal(1, Object.keys(graph.node).length);
node_id = Object.keys(graph.node)[0]; node_id = Object.keys(graph.node)[0];
node = graph.node[node_id]; node = graph.node[node_id];
equal("Example.Node", node._class); equal("Example.Node", node._class);
}).then(function() { }).then(function () {
// click the new node to see display the popup // click the new node to see display the popup
// XXX at the moment nodes have class window // XXX at the moment nodes have class window
equal(1, $("div.window").length, "We have a new node"); equal(1, $("div.window").length, "We have a new node");
$("div.window").simulate("dblclick"); $("div.window").simulate("dblclick");
// Promises that handle the dialog actions are not available // Promises that handle the dialog actions are not available
// immediately after clicking. // immediately after clicking.
var promise = RSVP.Promise(function(resolve) { var promise = new RSVP.Promise(function (resolve) {
var waitForDialogAndDelete = function() { function waitForDialogAndDelete() {
if (!jsplumb_gadget.props.dialog_promise) { if (!jsplumb_gadget.props.dialog_promise) {
// Dialog not ready. Let's retry later. // Dialog not ready. Let's retry later.
// XXX this condition is actually incorrect. We need to wait // XXX this condition is actually incorrect. We need to wait
...@@ -533,11 +549,11 @@ ...@@ -533,11 +549,11 @@
// resolve our test promise once the dialog handling promise is // resolve our test promise once the dialog handling promise is
// finished. // finished.
jsplumb_gadget.props.dialog_promise.then(resolve); jsplumb_gadget.props.dialog_promise.then(resolve);
}; }
waitForDialogAndDelete(); waitForDialogAndDelete();
}); });
return promise.then(function() { return promise.then(function () {
return jsplumb_gadget.getContent().then(function(content) { return jsplumb_gadget.getContent().then(function (content) {
var graph = JSON.parse(content).graph; var graph = JSON.parse(content).graph;
equal(0, Object.keys(graph.node).length, "node is removed from data"); equal(0, Object.keys(graph.node).length, "node is removed from data");
equal(0, $("div.window").length, "DOM is modified"); equal(0, $("div.window").length, "DOM is modified");
...@@ -547,29 +563,30 @@ ...@@ -547,29 +563,30 @@
} }
g.declareGadget("./index.html", { g.declareGadget("./index.html", {
element: document.querySelector("#test-element") element: document.querySelector("#test-element")
}).then(function(new_gadget) { }).then(function (new_gadget) {
jsplumb_gadget = new_gadget; jsplumb_gadget = new_gadget;
jsplumb_gadget.render(sample_data_empty_graph); jsplumb_gadget.render(sample_data_empty_graph);
}).then(runTest).fail(error_handler).always(start); }).then(runTest).fail(error_handler).always(start);
}); });
test("Graph is automatically layout", function() { test("Graph is automatically layout", function () {
var jsplumb_gadget; var jsplumb_gadget;
stop(); stop();
g.declareGadget("./index.html", { g.declareGadget("./index.html", {
element: document.querySelector("#test-element") element: document.querySelector("#test-element")
}).then(function(new_gadget) { }).then(function (new_gadget) {
jsplumb_gadget = new_gadget; jsplumb_gadget = new_gadget;
return jsplumb_gadget.render(sample_data_graph_no_node_coordinate); return jsplumb_gadget.render(sample_data_graph_no_node_coordinate);
}).then(function() { }).then(function () {
return jsplumb_gadget.getContent(); return jsplumb_gadget.getContent();
}).then(function(content) { }).then(function (content) {
$.each(JSON.parse(content).graph.node, function(i, node){ /*jslint unparam: true */
$.each(JSON.parse(content).graph.node, function (ignore, node) {
ok(node.coordinate.top !== undefined, "Node have top coordinate"); ok(node.coordinate.top !== undefined, "Node have top coordinate");
ok(0 <= node.coordinate.top <= 1, "Node top coordinate is between [0..1]"); ok((0 <= node.coordinate.top) && (node.coordinate.top <= 1), "Node top coordinate is between [0..1]");
ok(node.coordinate.left !== undefined, "Node have left coordinate"); ok(node.coordinate.left !== undefined, "Node have left coordinate");
ok(0 <= node.coordinate.left <= 1, "Node left coordinate is between [0..1]"); ok((0 <= node.coordinate.left) && (node.coordinate.left <= 1), "Node left coordinate is between [0..1]");
}); });
}).fail(error_handler).always(start); }).fail(error_handler).always(start);
}); });
}); });
})(rJS, JSON, QUnit, RSVP, jQuery); }(rJS, JSON, QUnit, RSVP, jQuery));
\ No newline at end of file \ 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