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

Merge branch 'mr321' into feat/coding_style_check

merge rebased commits of nexedi/erp5/merge_requests/321

with:

`git cherry-pick nexedi/mr/321...47eba793~1`
parents 98597913 dda1824e
......@@ -9,13 +9,17 @@
<script src="renderjs.js" type="text/javascript"></script>
<script src="rsvp.js" type="text/javascript"></script>
-->
<!--
FIXME: Including jQuery twice cause the jsplumb to be loaded twice.
For now we assume that it has already been loaded at this point.
<script src="../lib/jquery.js" type="text/javascript"></script>
-->
<script src="../lib/jquery-ui.js" type="text/javascript"></script>
<script src="../lib/jquery.jsplumb.js" type="text/javascript"></script>
<script src="../lib/handlebars.min.js" type="text/javascript"></script>
<script src="../lib/springy.js" type="text/javascript"></script>
<script src="../dream/mixin_promise.js" type="text/javascript"></script>
<script src="springy.js" type="text/javascript"></script>
<script src="jsplumb.js" type="text/javascript"></script>
<script id="node-template" type="text/x-handlebars-template">
......
/* ===========================================================================
/* ===========================================================================
* Copyright 2013-2015 Nexedi SA and Contributors
*
* This file is part of DREAM.
......@@ -16,63 +16,77 @@
* You should have received a copy of the GNU Lesser General Public License
* along with DREAM. If not, see <http://www.gnu.org/licenses/>.
* ==========================================================================*/
/*global console, window, RSVP, rJS, $, jsPlumb, Handlebars,
/*global console, window, Node, RSVP, rJS, $, jsPlumb, Handlebars,
loopEventListener, promiseEventListener, DOMParser, Springy */
/*jslint unparam: true todo: true */
(function(RSVP, rJS, $, jsPlumb, Handlebars, loopEventListener, promiseEventListener, DOMParser, Springy) {
/*jslint vars: true unparam: true nomen: true todo: true */
(function (RSVP, rJS, $, jsPlumb, Handlebars, loopEventListener, promiseEventListener, DOMParser, Springy) {
"use strict";
/* TODO:
* less dependancies ( promise event listner ? )
* less dependancies ( promise event listener ? )
* no more handlebars
* id should not always be modifiable
* drop zoom level
* rename draggable()
* factorize node & edge popup edition
*/
/*jslint nomen: true */
var gadget_klass = rJS(window),
domParser = new DOMParser(),
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").innerHTML;
var gadget_klass = rJS(window);
var domParser = new DOMParser();
var node_template_source = gadget_klass.__template_element.getElementById("node-template").innerHTML;
var node_template = Handlebars.compile(node_template_source);
var popup_edit_template = gadget_klass.__template_element.getElementById("popup-edit-template").innerHTML;
function layoutGraph(graph_data) {
// Promise returning the graph once springy calculated the layout.
// If the graph already contain layout, return it as is.
function resolver(resolve, reject) {
try {
var springy_graph = new Springy.Graph(),
max_iterations = 100, // we stop layout after 100 iterations.
loop = 0,
springy_nodes = {},
drawn_nodes = {},
min_x=100, max_x=0, min_y=100, max_y=0;
var springy_graph = new Springy.Graph();
var max_iterations = 100; // we stop layout after 100 iterations.
var loop = 0;
var springy_nodes = {};
var drawn_nodes = {};
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 (Object.keys(graph_data.edge).length === 0) {
resolve(graph_data);
return;
}
// make a Springy graph with our graph
$.each(graph_data.node, function(key, value) {
if (value.coordinate) {
$.each(graph_data.node, function (key, value) {
if (value.coordinate && value.coordinate.top && value.coordinate.left) {
// graph already has a layout, no need to layout again
return resolve(graph_data);
resolve(graph_data);
return;
}
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_nodes[value.source], springy_nodes[value.destination]);
});
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,
function clear() {},
function drawEdge(edge, p1, p2) {},
function clear() {
return;
},
function drawEdge() {
return;
},
function drawNode(node, p) {
drawn_nodes[node.data.node_id] = p;
if ( ++loop > max_iterations) {
loop += 1;
if (loop > max_iterations) {
renderer.stop();
}
},
function onRenderStop() {
// 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) {
max_x = drawn_nodes[key].x;
}
......@@ -88,7 +102,7 @@
});
// "resample" the positions from 0 to 1, the scale used by this gadget.
// We keep a 5% margin
$.each(graph_data.node, function(key, value) {
$.each(graph_data.node, function (key) {
graph_data.node[key].coordinate = {
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)
......@@ -110,7 +124,9 @@
// Infinite event listener (promise is never resolved)
// 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() {
if (callback_promise !== undefined && typeof callback_promise.cancel === "function") {
......@@ -125,13 +141,13 @@
cancelResolver();
}
function resolver(resolve, reject) {
handle_event_callback = function() {
function resolver(ignore, reject) {
handle_event_callback = function () {
var args = arguments;
cancelResolver();
callback_promise = new RSVP.Queue().push(function() {
callback_promise = new RSVP.Queue().push(function () {
return callback.apply(jsplumb_instance, args);
}).push(undefined, function(error) {
}).push(undefined, function (error) {
if (!(error instanceof RSVP.CancellationError)) {
canceller();
reject(error);
......@@ -146,7 +162,7 @@
function getNodeId(gadget, element_id) {
// returns the ID of the node in the graph from its DOM element 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) {
node_id = k;
return false;
......@@ -157,9 +173,9 @@
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;
var n = 1;
var class_def = gadget.props.data.class_definition[element._class];
var id = class_def.short_id || element._class;
while (gadget.props.data.graph.node[id + n] !== undefined) {
n += 1;
}
......@@ -177,8 +193,9 @@
function getDefaultEdgeClass(gadget) {
var class_definition = gadget.props.data.class_definition;
for (var key in class_definition) {
if (class_definition.hasOwnProperty(key) && class_definition[key]._class === 'edge') {
var key;
for (key in class_definition) {
if (class_definition.hasOwnProperty(key) && class_definition[key]._class === "edge") {
return key;
}
}
......@@ -205,31 +222,31 @@
}
function convertToAbsolutePosition(gadget, x, y) {
var zoom_level = gadget.props.zoom_level,
canvas_size_x = $(gadget.props.main).width(),
canvas_size_y = $(gadget.props.main).height(),
size_x = $(gadget.props.element).find(".dummy_window").width() * zoom_level,
size_y = $(gadget.props.element).find(".dummy_window").height() * zoom_level,
top = Math.floor(y * (canvas_size_y - size_y)) + "px",
left = Math.floor(x * (canvas_size_x - size_x)) + "px";
var zoom_level = gadget.props.zoom_level;
var canvas_size_x = $(gadget.props.main).width();
var canvas_size_y = $(gadget.props.main).height();
var size_x = $(gadget.props.element).find(".dummy_window").width() * zoom_level;
var size_y = $(gadget.props.element).find(".dummy_window").height() * zoom_level;
var top = Math.floor(y * (canvas_size_y - size_y)) + "px";
var left = Math.floor(x * (canvas_size_x - size_x)) + "px";
return [left, top];
}
function convertToRelativePosition(gadget, x, y) {
var zoom_level = gadget.props.zoom_level,
canvas_size_x = $(gadget.props.main).width(),
canvas_size_y = $(gadget.props.main).height(),
size_x = $(gadget.props.element).find(".dummy_window").width() * zoom_level,
size_y = $(gadget.props.element).find(".dummy_window").height() * zoom_level,
top = Math.max(Math.min(y.replace("px", "") / (canvas_size_y - size_y), 1), 0),
left = Math.max(Math.min(x.replace("px", "") / (canvas_size_x - size_x), 1), 0);
var zoom_level = gadget.props.zoom_level;
var canvas_size_x = $(gadget.props.main).width();
var canvas_size_y = $(gadget.props.main).height();
var size_x = $(gadget.props.element).find(".dummy_window").width() * zoom_level;
var size_y = $(gadget.props.element).find(".dummy_window").height() * zoom_level;
var top = Math.max(Math.min(y.replace("px", "") / (canvas_size_y - size_y), 1), 0);
var 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_id_to_dom_element_id[node_id],
element,
relative_position;
var element_id = gadget.props.node_id_to_dom_element_id[node_id];
var element;
var relative_position;
if (coordinate === undefined) {
element = $(gadget.props.element).find("#" + element_id);
relative_position = convertToRelativePosition(gadget, element.css("left"), element.css("top"));
......@@ -244,8 +261,8 @@
}
function draggable(gadget) {
var jsplumb_instance = gadget.props.jsplumb_instance,
stop = function(element) {
var jsplumb_instance = gadget.props.jsplumb_instance;
var stop = function (element) {
updateElementCoordinate(gadget, getNodeId(gadget, element.target.id));
};
......@@ -279,10 +296,10 @@
function updateNodeStyle(gadget, element_id) {
// Update node size according to the zoom level
// XXX does nothing for now
var zoom_level = gadget.props.zoom_level,
element = $(gadget.props.element).find("#" + element_id),
new_value;
$.each(gadget.props.style_attr_list, function(i, j) {
var zoom_level = gadget.props.zoom_level;
var element = $(gadget.props.element).find("#" + element_id);
var new_value;
$.each(gadget.props.style_attr_list, function (ignore, j) {
new_value = element.css(j).replace("px", "") * zoom_level + "px";
element.css(j, new_value);
});
......@@ -294,7 +311,7 @@
$(gadget.props.element).find("#" + element_id).remove();
delete gadget.props.data.graph.node[node_id];
delete gadget.props.node_id_to_dom_element_id[node_id];
$.each(gadget.props.data.graph.edge, function(k, v) {
$.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];
}
......@@ -303,11 +320,11 @@
}
function updateElementData(gadget, node_id, data) {
var element_id = gadget.props.node_id_to_dom_element_id[node_id],
new_id = data.id || data.data.id;
var element_id = gadget.props.node_id_to_dom_element_id[node_id];
var new_id = data.id || data.data.id;
$(gadget.props.element).find("#" + element_id).text(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;
......@@ -320,7 +337,7 @@
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) {
$.each(gadget.props.data.graph.edge, function (ignore, v) {
if (v.source === node_id) {
v.source = new_id;
}
......@@ -334,8 +351,8 @@
function addEdge(gadget, edge_id, edge_data) {
var overlays = [],
connection;
var overlays = [];
var connection;
if (edge_data.name) {
overlays = [
["Label", {
......@@ -363,7 +380,8 @@
// endpoints on nodes.
if (edge_data.jsplumb_connector === "Flowchart") {
connection = gadget.props.jsplumb_instance.connect({
uuids: [edge_data.source + ".flowChart" + edge_data.jsplumb_source_endpoint,
uuids: [
edge_data.source + ".flowChart" + edge_data.jsplumb_source_endpoint,
edge_data.destination + ".flowChart" + edge_data.jsplumb_destination_endpoint
],
overlays: overlays
......@@ -389,11 +407,10 @@
// references
// XXX this should probably be moved to fieldset ( and not handle
// class_definition here)
function resolveReference(ref, schema) {
// 2 here is for #/
var i, ref_path = ref.substr(2, ref.length),
parts = ref_path.split("/");
var i;
var ref_path = ref.substr(2, ref.length); // 2 here is for #/
var parts = ref_path.split("/");
for (i = 0; i < parts.length; i += 1) {
schema = schema[parts[i]];
}
......@@ -404,10 +421,10 @@
return JSON.parse(JSON.stringify(obj));
}
var referenced,
i,
property,
expanded_class_definition = clone(class_definition) || {};
var referenced;
var i;
var property;
var expanded_class_definition = clone(class_definition) || {};
if (!expanded_class_definition.properties) {
......@@ -461,12 +478,12 @@
}
function openEdgeEditionDialog(gadget, connection) {
var edge_id = connection.id,
edge_data = gadget.props.data.graph.edge[edge_id],
edit_popup = $(gadget.props.element).find("#popup-edit-template"),
schema,
fieldset_element,
delete_promise;
var edge_id = connection.id;
var edge_data = gadget.props.data.graph.edge[edge_id];
var edit_popup = $(gadget.props.element).find("#popup-edit-template");
var schema;
var fieldset_element;
var delete_promise;
schema = expandSchema(gadget.props.data.class_definition[edge_data._class], gadget.props.data);
// We do not edit source & destination on edge this way.
delete schema.properties.source;
......@@ -478,15 +495,15 @@
edit_popup.dialog();
edit_popup.show();
function save_promise(fieldset_gadget, edge_id) {
return RSVP.Queue().push(function() {
function save_promise(fieldset_gadget) {
return new RSVP.Queue().push(function () {
return promiseEventListener(edit_popup.find(".graph_editor_validate_button")[0], "click", false);
}).push(function(evt) {
}).push(function (evt) {
var data = {
id: $(evt.target[1]).val(),
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, r);
// to redraw, we remove the edge and add again.
......@@ -500,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);
}).push(function() {
}).push(function () {
// connectionDetached event will remove the edge from data
gadget.props.jsplumb_instance.detach(connection);
});
return gadget.declareGadget("../fieldset/index.html", {
element: fieldset_element,
scope: "fieldset"
}).push(function(fieldset_gadget) {
}).push(function (fieldset_gadget) {
return RSVP.all([fieldset_gadget, fieldset_gadget.render({
value: edge_data,
property_definition: schema
}, edge_id)]);
}).push(function(fieldset_gadget) {
}).push(function (fieldset_gadget) {
edit_popup.dialog("open");
return fieldset_gadget[0];
}).push(function(fieldset_gadget) {
}).push(function (fieldset_gadget) {
fieldset_gadget.startService(); // XXX
return fieldset_gadget;
}).push(function(fieldset_gadget) {
}).push(function (fieldset_gadget) {
// Expose the dialog handling promise so that we can wait for it in
// test.
gadget.props.dialog_promise = RSVP.any([save_promise(fieldset_gadget, edge_id), delete_promise]);
return gadget.props.dialog_promise;
}).push(function() {
}).push(function () {
edit_popup.dialog("close");
edit_popup.remove();
delete gadget.props.dialog_promise;
......@@ -533,17 +550,17 @@
}
function openNodeEditionDialog(gadget, element) {
var node_id = getNodeId(gadget, element.id),
node_data = gadget.props.data.graph.node[node_id],
node_edit_popup = $(gadget.props.element).find("#popup-edit-template"),
schema,
fieldset_element,
delete_promise;
var node_id = getNodeId(gadget, element.id);
var node_data = gadget.props.data.graph.node[node_id];
var node_edit_popup = $(gadget.props.element).find("#popup-edit-template");
var schema;
var fieldset_element;
var delete_promise;
// If we have no definition for this, we do not allow edition.
// XXX incorrect, we need to display this dialog to be able
// to delete a node
if (gadget.props.data.class_definition[node_data._class] === undefined) {
return;
return false;
}
schema = expandSchema(gadget.props.data.class_definition[node_data._class], gadget.props.data);
if (node_edit_popup.length !== 0) {
......@@ -558,81 +575,91 @@
node_data.id = node_id;
function save_promise(fieldset_gadget, node_id) {
return RSVP.Queue().push(function() {
return promiseEventListener(node_edit_popup.find(".graph_editor_validate_button")[0], "click", false);
}).push(function(evt) {
return new RSVP.Queue().push(function () {
return promiseEventListener(
node_edit_popup.find(".graph_editor_validate_button")[0],
"click",
false
);
}).push(function (evt) {
var data = {
// XXX id should not be handled differently ...
id: $(evt.target[1]).val(),
data: {}
};
return fieldset_gadget.getContent().then(function(r) {
return fieldset_gadget.getContent().then(function (r) {
$.extend(data.data, r);
updateElementData(gadget, node_id, data);
});
});
}
delete_promise = new RSVP.Queue().push(function() {
return promiseEventListener(node_edit_popup.find(".graph_editor_delete_button")[0], "click", false);
}).push(function() {
delete_promise = new RSVP.Queue().push(function () {
return promiseEventListener(
node_edit_popup.find(".graph_editor_delete_button")[0],
"click",
false
);
}).push(function () {
return removeElement(gadget, node_id);
});
return gadget.declareGadget("../fieldset/index.html", {
element: fieldset_element,
scope: "fieldset"
}).push(function(fieldset_gadget) {
return RSVP.all([fieldset_gadget, fieldset_gadget.render({
}).push(function (fieldset_gadget) {
return RSVP.all([
fieldset_gadget,
fieldset_gadget.render(
{
value: node_data,
property_definition: schema
}, node_id)]);
}).push(function(fieldset_gadget) {
},
node_id
)
]);
}).push(function (fieldset_gadget) {
node_edit_popup.dialog("open");
return fieldset_gadget[0];
}).push(function(fieldset_gadget) {
}).push(function (fieldset_gadget) {
fieldset_gadget.startService(); // XXX this should not be needed anymore.
return fieldset_gadget;
}).push(function(fieldset_gadget) {
}).push(function (fieldset_gadget) {
// Expose the dialog handling promise so that we can wait for it in
// test.
gadget.props.dialog_promise = RSVP.any([save_promise(fieldset_gadget, node_id), delete_promise]);
return gadget.props.dialog_promise;
}).push(function() {
}).push(function () {
node_edit_popup.dialog("close");
node_edit_popup.remove();
delete gadget.props.dialog_promise;
});
}
function waitForNodeClick(gadget, node) {
gadget.props.nodes_click_monitor.monitor(loopEventListener(node, "dblclick", false, openNodeEditionDialog.bind(null, gadget, node)));
}
function waitForConnection(gadget) {
return loopJsplumbBind(gadget, "connection", function(info, originalEvent) {
return loopJsplumbBind(gadget, "connection", function (info) {
updateConnectionData(gadget, info.connection, false);
});
}
function waitForConnectionDetached(gadget) {
return loopJsplumbBind(gadget, "connectionDetached", function(info, originalEvent) {
return loopJsplumbBind(gadget, "connectionDetached", function (info) {
updateConnectionData(gadget, info.connection, true);
});
}
function waitForConnectionClick(gadget) {
return loopJsplumbBind(gadget, "click", function(connection) {
return loopJsplumbBind(gadget, "click", function (connection) {
return openEdgeEditionDialog(gadget, connection);
});
}
function addNode(gadget, node_id, node_data) {
var render_element = $(gadget.props.main),
class_definition = gadget.props.data.class_definition[node_data._class],
coordinate = node_data.coordinate,
dom_element_id,
box,
absolute_position,
domElement;
var render_element = $(gadget.props.main);
var class_definition = gadget.props.data.class_definition[node_data._class];
var coordinate = node_data.coordinate;
var dom_element_id;
var box;
var absolute_position;
var domElement;
dom_element_id = generateDomElementId(gadget.props.element);
gadget.props.node_id_to_dom_element_id[node_id] = dom_element_id;
......@@ -645,7 +672,6 @@
};
}
node_data.coordinate = updateElementCoordinate(gadget, node_id, coordinate);
/*jslint nomen: true*/
domElement = domParser.parseFromString(node_template({
"class": node_data._class.replace(".", "-"),
element_id: dom_element_id,
......@@ -653,7 +679,6 @@
name: node_data.name || node_data.id
}), "text/html").querySelector(".window");
render_element.append(domElement);
waitForNodeClick(gadget, domElement);
box = $(gadget.props.element).find("#" + dom_element_id);
absolute_position = convertToAbsolutePosition(gadget, coordinate.left, coordinate.top);
if (class_definition && class_definition.css) {
......@@ -697,16 +722,16 @@
gadget.props.main.removeEventListener("drop", callback, false);
}
}
/*jslint unparam: true*/
function resolver(resolve, reject) {
callback = function(evt) {
function resolver(ignore, reject) {
callback = function (evt) {
try {
var class_name, offset = $(gadget.props.main).offset(),
relative_position = convertToRelativePosition(gadget, evt.clientX - offset.left + "px", evt.clientY - offset.top + "px");
var class_name;
var offset = $(gadget.props.main).offset();
var relative_position = convertToRelativePosition(gadget, evt.clientX - offset.left + "px", evt.clientY - offset.top + "px");
try {
// html5 compliant browser
class_name = JSON.parse(evt.dataTransfer.getData("application/json"));
} catch (e) {
} catch (error_from_drop) {
// internet explorer
class_name = JSON.parse(evt.dataTransfer.getData("text"));
}
......@@ -725,33 +750,30 @@
};
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
loopEventListener(gadget.props.main, "dragover", false, function() {
loopEventListener(gadget.props.main, "dragover", false, function () {
return undefined;
}), RSVP.Promise(resolver, canceller)
}), new RSVP.Promise(resolver, canceller)
]);
}
gadget_klass.ready(function (g) {
g.props = {};
})
.ready(function (g) {
}).ready(function (g) {
return g.getElement().push(function (element) {
g.props.element = element;
});
})
.ready(function(g) {
}).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"];
g.getElement().then(function(element) {
g.getElement().then(function (element) {
g.props.element = element;
});
})
.declareAcquiredMethod("notifyDataChanged", "notifyDataChanged")
.declareMethod("render", function(data) {
var gadget = this, jsplumb_instance;
}).declareAcquiredMethod("notifyDataChanged", "notifyDataChanged")
.declareMethod("render", function (data) {
var gadget = this;
this.props.data = {};
if (data.key) {
......@@ -761,30 +783,30 @@
}
this.props.main = this.props.element.querySelector(".graph_container");
/*
/*
$(this.props.main).resizable({
resize : function(event, ui) {
resize : function (event, ui) {
jsplumb_instance.repaint(ui.helper);
}
});
*/
*/
if (data) {
this.props.data = JSON.parse(data);
// XXX how to make queue ??
return layoutGraph(this.props.data.graph).then(function(graph_data) {
// XXX how to make queue ??
return layoutGraph(this.props.data.graph).then(function (graph_data) {
gadget.props.data.graph = graph_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);
});
$.each(gadget.props.data.graph.edge, function(key, value) {
$.each(gadget.props.data.graph.edge, function (key, value) {
addEdge(gadget, key, value);
});
});
}
})
.declareMethod("getContent", function() {
.declareMethod("getContent", function () {
var ret = {};
if (this.props.erp5_key) {
// ERP5
......@@ -793,16 +815,26 @@
}
return JSON.stringify(this.props.data);
})
.declareService(function() {
var gadget = this, jsplumb_instance;
.onEvent("dblclick", function (evt) {
var node = evt.target;
if (
(node.nodeType === Node.ELEMENT_NODE) &&
(node.tagName === "DIV") && node.classList.contains(["window"])
) {
return openNodeEditionDialog(this, node);
}
})
.declareService(function () {
var gadget = this;
var jsplumb_instance;
this.props.main = this.props.element.querySelector(".graph_container");
this.props.jsplumb_instance = jsplumb_instance = jsPlumb.getInstance();
if (this.props.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);
});
$.each(this.props.data.graph.edge, function(key, value) {
$.each(this.props.data.graph.edge, function (key, value) {
addEdge(gadget, key, value);
});
}
......@@ -827,13 +859,12 @@
});
draggable(gadget);
this.props.nodes_click_monitor = RSVP.Monitor();
return RSVP.all([waitForDrop(gadget),
return RSVP.all([
waitForDrop(gadget),
waitForConnection(gadget),
waitForConnectionDetached(gadget),
waitForConnectionClick(gadget),
gadget.props.nodes_click_monitor
waitForConnectionClick(gadget)
]);
});
})(RSVP, rJS, $, jsPlumb, Handlebars, loopEventListener, promiseEventListener, DOMParser, Springy);
\ No newline at end of file
}(RSVP, rJS, $, jsPlumb, Handlebars, loopEventListener, promiseEventListener, DOMParser, Springy));
\ No newline at end of file
......@@ -15,6 +15,8 @@
<body>
<div id="qunit"></div>
<div id="qunit-fixture"></div>
<div id="qunit-fixture">
<div id="test-element"/>
</div>
</body>
</html>
/*global window, document, rJS, JSON, QUnit, jQuery, RSVP, console, setTimeout
*/
(function(rJS, JSON, QUnit, RSVP, $) {
/*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, $) {
"use strict";
var start = QUnit.start,
stop = QUnit.stop,
test = QUnit.test,
equal = QUnit.equal,
ok = QUnit.ok,
error_handler = function(e) {
var start = QUnit.start;
var stop = QUnit.stop;
var test = QUnit.test;
var equal = QUnit.equal;
var ok = QUnit.ok;
var error_handler = function (e) {
window.console.error(e);
ok(false, e);
},
sample_class_definition = {
};
var sample_class_definition = {
edge: {
description: "Base definition for edge",
properties: {
_class: {
"_class": {
type: "string"
},
destination: {
......@@ -25,7 +24,7 @@
name: {
type: "string"
},
required: [ "name", "_class", "source", "destination" ],
required: ["name", "_class", "source", "destination"],
source: {
type: "string"
}
......@@ -33,35 +32,35 @@
type: "object"
},
"Example.Edge": {
_class: "edge",
allOf: [ {
$ref: "#/edge"
"_class": "edge",
allOf: [{
"$ref": "#/edge"
}, {
properties: {
color: {
"enum": [ "red", "green", "blue" ]
"enum": ["red", "green", "blue"]
}
}
} ],
}],
description: "An example edge with a color property"
},
"Example.Node": {
_class: "node",
allOf: [ {
$ref: "#/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: {
"_class": {
type: "string"
},
coordinate: {
......@@ -74,14 +73,15 @@
name: {
type: "string"
},
required: [ "name", "_class" ]
required: ["name", "_class"]
},
type: "object"
}
}, sample_graph = {
};
var sample_graph = {
edge: {
edge1: {
_class: "Example.Edge",
"_class": "Example.Edge",
source: "N1",
destination: "N2",
color: "blue"
......@@ -89,7 +89,7 @@
},
node: {
N1: {
_class: "Example.Node",
"_class": "Example.Node",
name: "Node 1",
coordinate: {
top: 0,
......@@ -98,7 +98,7 @@
shape: "square"
},
N2: {
_class: "Example.Node",
"_class": "Example.Node",
name: "Node 2",
shape: "circle",
coordinate: {
......@@ -107,127 +107,161 @@
}
}
}
}, sample_graph_not_connected = {
};
var sample_graph_no_node_coodinate = {
edge: {
edge1: {
"_class": "Example.Edge",
source: "N1",
destination: "N2",
color: "blue"
}
},
node: {
N1: {
"_class": "Example.Node",
name: "Node 1",
shape: "square"
},
N2: {
"_class": "Example.Node",
name: "Node 2",
shape: "circle"
}
}
};
var sample_graph_not_connected = {
edge: {},
node: {
N1: {
_class: "Example.Node",
"_class": "Example.Node",
name: "Node 1",
shape: "square"
},
N2: {
_class: "Example.Node",
"_class": "Example.Node",
name: "Node 2",
shape: "circle"
}
}
}, sample_data_graph = JSON.stringify({
};
var sample_data_graph = JSON.stringify({
class_definition: sample_class_definition,
graph: sample_graph
}), sample_data_graph_not_connected = JSON.stringify({
});
var sample_data_graph_no_node_coordinate = JSON.stringify({
class_definition: sample_class_definition,
graph: sample_graph_no_node_coodinate
});
var sample_data_graph_not_connected = JSON.stringify({
class_definition: sample_class_definition,
graph: sample_graph_not_connected
}), sample_data_empty_graph = JSON.stringify({
});
var sample_data_empty_graph = JSON.stringify({
class_definition: sample_class_definition,
graph: {
node: {},
edge: {}
}
});
QUnit.config.testTimeout = 60000;
rJS(window).ready(function(g) {
test("Sample graph can be loaded and output is equal to input", function() {
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) {
element: document.querySelector("#test-element")
}).then(function (new_gadget) {
jsplumb_gadget = new_gadget;
return jsplumb_gadget.render(sample_data_graph);
}).then(function() {
}).then(function () {
return jsplumb_gadget.getContent();
}).then(function(content) {
}).then(function (content) {
equal(content, sample_data_graph);
}).fail(error_handler).always(start);
});
test("New node can be drag & dropped", function() {
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() {
return jsplumb_gadget.getContent().then(function () {
// fake a drop event
var e = new window.Event("drop");
e.dataTransfer = {
getData: function(type) {
getData: function (type) {
// make sure we are called properly
equal("application/json", type, "The drag&dropped element must have data type application/json");
return JSON.stringify("Example.Node");
}
};
jsplumb_gadget.props.main.dispatchEvent(e);
}).then(function() {
}).then(function () {
return jsplumb_gadget.getContent();
}).then(function(content) {
var node, graph = JSON.parse(content).graph;
}).then(function (content) {
var node;
var graph = JSON.parse(content).graph;
equal(1, Object.keys(graph.node).length, "There is one new node class");
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", {
element: document.querySelector("#qunit-fixture")
}).then(function(new_gadget) {
element: document.querySelector("#test-element")
}).then(function (new_gadget) {
jsplumb_gadget = new_gadget;
jsplumb_gadget.render(sample_data_empty_graph);
}).then(runTest).fail(error_handler).always(start);
});
test("Node can be dragged", function() {
test("Node can be dragged", function () {
var jsplumb_gadget;
stop();
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
// might change )
$("div[title='Node 1']").simulate("drag", {
dx: 100,
dy: 60
});
}).then(function() {
}).then(function () {
return jsplumb_gadget.getContent();
}).then(function(content) {
var graph = JSON.parse(content).graph, node_coordinate = graph.node.N1.coordinate;
}).then(function (content) {
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
// as we moved 10%
ok(node_coordinate.top - .1 < .1, "Top is ok");
ok(node_coordinate.left - .1 < .1, "Left is ok");
ok(node_coordinate.top - 0.1 < 0.1, "Top is ok");
ok(node_coordinate.left - 0.1 < 0.1, "Left is ok");
});
}
g.declareGadget("./index.html", {
element: document.querySelector("#qunit-fixture")
}).then(function(new_gadget) {
element: document.querySelector("#test-element")
}).then(function (new_gadget) {
jsplumb_gadget = new_gadget;
jsplumb_gadget.render(sample_data_graph);
}).then(runTest).fail(error_handler).always(start);
});
test("Node properties can be edited", function() {
test("Node properties can be edited", function () {
var jsplumb_gadget;
stop();
function runTest() {
return jsplumb_gadget.getContent().then(function() {
return jsplumb_gadget.getContent().then(function () {
// click on a node to see display the popup
$("div[title='Node 1']").simulate("dblclick");
// Promises that handle the dialog actions are not available
// immediately after clicking.
var promise = RSVP.Promise(function(resolve) {
var fillDialog = function() {
var promise = new RSVP.Promise(function (resolve) {
function fillDialog() {
if (!jsplumb_gadget.props.dialog_promise) {
// Dialog not ready. Let's retry later.
// XXX this condition is actually incorrect. We need to wait
// for the event listener to have been registered for the
// dialog buttons. This setTimeout is good enough for now.
return setTimeout(fillDialog, 1e3);
setTimeout(fillDialog, 1e3);
return;
}
// check displayed values
equal($("input[name='id']").val(), "N1");
......@@ -241,12 +275,13 @@
// resolve our test promise once the dialog handling promise is
// finished.
jsplumb_gadget.props.dialog_promise.then(resolve);
};
}
fillDialog();
});
return promise.then(function() {
return jsplumb_gadget.getContent().then(function(content) {
var graph = JSON.parse(content).graph, node = graph.node.N1;
return promise.then(function () {
return jsplumb_gadget.getContent().then(function (content) {
var graph = JSON.parse(content).graph;
var node = graph.node.N1;
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(1, $("div[title='Modified Name']").length, "DOM title attribute is modified");
......@@ -255,27 +290,29 @@
});
}
g.declareGadget("./index.html", {
element: document.querySelector("#qunit-fixture")
}).then(function(new_gadget) {
element: document.querySelector("#test-element")
}).then(function (new_gadget) {
jsplumb_gadget = new_gadget;
jsplumb_gadget.render(sample_data_graph);
}).then(runTest).fail(error_handler).always(start);
});
test("Node can be connected", function() {
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']");
return jsplumb_gadget.getContent().then(function (content) {
var node1 = jsplumb_gadget.props.main.querySelector("div[title='Node 1']");
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");
jsplumb_gadget.props.jsplumb_instance.connect({
source: node1.id,
target: node2.id
});
}).then(function() {
}).then(function () {
return jsplumb_gadget.getContent();
}).then(function(content) {
var edge, graph = JSON.parse(content).graph;
}).then(function (content) {
var edge;
var graph = JSON.parse(content).graph;
equal(2, Object.keys(graph.node).length, "We still have 2 nodes");
equal(1, Object.keys(graph.edge).length, "We have 1 edge");
edge = graph.edge[Object.keys(graph.edge)[0]];
......@@ -286,42 +323,43 @@
});
}
g.declareGadget("./index.html", {
element: document.querySelector("#qunit-fixture")
}).then(function(new_gadget) {
element: document.querySelector("#test-element")
}).then(function (new_gadget) {
jsplumb_gadget = new_gadget;
jsplumb_gadget.render(sample_data_graph_not_connected);
}).then(runTest).fail(error_handler).always(start);
});
test("Node can be deleted", function() {
test("Node can be deleted", function () {
var jsplumb_gadget;
stop();
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, $("._jsPlumb_connector").length, "there is 1 connection");
// click on node 1 to see display the popup
$("div[title='Node 1']").simulate("dblclick");
// Promises that handle the dialog actions are not available
// immediately after clicking.
var promise = RSVP.Promise(function(resolve) {
var waitForDialogAndDelete = function() {
var promise = new RSVP.Promise(function (resolve) {
function waitForDialogAndDelete() {
if (!jsplumb_gadget.props.dialog_promise) {
// Dialog not ready. Let's retry later.
// XXX this condition is actually incorrect. We need to wait
// for the event listener to have been registered for the
// dialog buttons. This setTimeout is good enough for now.
return setTimeout(waitForDialogAndDelete, 1e3);
setTimeout(waitForDialogAndDelete, 1e3);
return;
}
equal(1, $("input[value='Delete']").length, "There should be one delete button");
$("input[value='Delete']").click();
// resolve our test promise once the dialog handling promise is
// finished.
jsplumb_gadget.props.dialog_promise.then(resolve);
};
}
waitForDialogAndDelete();
});
return promise.then(function() {
return jsplumb_gadget.getContent().then(function(content) {
return promise.then(function () {
return jsplumb_gadget.getContent().then(function (content) {
var graph = JSON.parse(content).graph;
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");
......@@ -332,29 +370,30 @@
});
}
g.declareGadget("./index.html", {
element: document.querySelector("#qunit-fixture")
}).then(function(new_gadget) {
element: document.querySelector("#test-element")
}).then(function (new_gadget) {
jsplumb_gadget = new_gadget;
jsplumb_gadget.render(sample_data_graph);
}).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;
stop();
function runTest() {
return jsplumb_gadget.getContent().then(function() {
return jsplumb_gadget.getContent().then(function () {
// click on a node to see display the popup
$("div[title='Node 1']").simulate("dblclick");
// Promises that handle the dialog actions are not available
// immediately after clicking.
var promise = RSVP.Promise(function(resolve) {
var fillDialog = function() {
var promise = new RSVP.Promise(function (resolve) {
function fillDialog() {
if (!jsplumb_gadget.props.dialog_promise) {
// Dialog not ready. Let's retry later.
// XXX this condition is actually incorrect. We need to wait
// for the event listener to have been registered for the
// dialog buttons. This setTimeout is good enough for now.
return setTimeout(fillDialog, 1e3);
setTimeout(fillDialog, 1e3);
return;
}
equal($("input[name='id']").val(), "N1");
// change the id
......@@ -364,11 +403,11 @@
// resolve our test promise once the dialog handling promise is
// finished.
jsplumb_gadget.props.dialog_promise.then(resolve);
};
}
fillDialog();
});
return promise.then(function() {
return jsplumb_gadget.getContent().then(function(content) {
return promise.then(function () {
return jsplumb_gadget.getContent().then(function (content) {
var graph = JSON.parse(content).graph;
equal(2, Object.keys(graph.node).length, "We still have two nodes");
ok(graph.node.N1b !== undefined, "Node Id changed");
......@@ -379,52 +418,55 @@
});
}
g.declareGadget("./index.html", {
element: document.querySelector("#qunit-fixture")
}).then(function(new_gadget) {
element: document.querySelector("#test-element")
}).then(function (new_gadget) {
jsplumb_gadget = new_gadget;
jsplumb_gadget.render(sample_data_graph);
}).then(runTest).fail(error_handler).always(start);
});
test("New node can be edited", function() {
var jsplumb_gadget, node_id;
test("New node can be edited", function () {
var jsplumb_gadget;
var node_id;
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() {
return jsplumb_gadget.getContent().then(function () {
// fake a drop event
var e = new window.Event("drop");
e.dataTransfer = {
getData: function(type) {
getData: function (type) {
// make sure we are called properly
equal("application/json", type, "The drag&dropped element must have data type application/json");
return JSON.stringify("Example.Node");
}
};
jsplumb_gadget.props.main.dispatchEvent(e);
}).then(function() {
}).then(function () {
return jsplumb_gadget.getContent();
}).then(function(content) {
var node, graph = JSON.parse(content).graph;
}).then(function (content) {
var node;
var graph = JSON.parse(content).graph;
equal(1, Object.keys(graph.node).length);
node_id = Object.keys(graph.node)[0];
node = graph.node[node_id];
equal("Example.Node", node._class);
}).then(function() {
}).then(function () {
// click the new node to see display the popup
// XXX at the moment nodes have class window
equal(1, $("div.window").length, "We have a new node");
$("div.window").simulate("dblclick");
// Promises that handle the dialog actions are not available
// immediately after clicking.
var promise = RSVP.Promise(function(resolve) {
var fillDialog = function() {
var promise = new RSVP.Promise(function (resolve) {
function fillDialog() {
if (!jsplumb_gadget.props.dialog_promise) {
// Dialog not ready. Let's retry later.
// XXX this condition is actually incorrect. We need to wait
// for the event listener to have been registered for the
// dialog buttons. This setTimeout is good enough for now.
return setTimeout(fillDialog, 1e3);
setTimeout(fillDialog, 1e3);
return;
}
// check displayed values
equal($("input[name='id']").val(), node_id);
......@@ -438,12 +480,13 @@
// resolve our test promise once the dialog handling promise is
// finished.
jsplumb_gadget.props.dialog_promise.then(resolve);
};
}
fillDialog();
});
return promise.then(function() {
return jsplumb_gadget.getContent().then(function(content) {
var graph = JSON.parse(content).graph, node = graph.node[node_id];
return promise.then(function () {
return jsplumb_gadget.getContent().then(function (content) {
var graph = JSON.parse(content).graph;
var node = graph.node[node_id];
equal("Modified Name", node.name, "Data is modified");
equal("Modified Name", $("div.window").text(), "DOM is modified");
});
......@@ -451,63 +494,66 @@
});
}
g.declareGadget("./index.html", {
element: document.querySelector("#qunit-fixture")
}).then(function(new_gadget) {
element: document.querySelector("#test-element")
}).then(function (new_gadget) {
jsplumb_gadget = new_gadget;
jsplumb_gadget.render(sample_data_empty_graph);
}).then(runTest).fail(error_handler).always(start);
});
test("New node can be deleted", function() {
var jsplumb_gadget, node_id;
test("New node can be deleted", function () {
var jsplumb_gadget;
var node_id;
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() {
return jsplumb_gadget.getContent().then(function () {
// fake a drop event
var e = new window.Event("drop");
e.dataTransfer = {
getData: function(type) {
getData: function (type) {
// make sure we are called properly
equal("application/json", type, "The drag&dropped element must have data type application/json");
return JSON.stringify("Example.Node");
}
};
jsplumb_gadget.props.main.dispatchEvent(e);
}).then(function() {
}).then(function () {
return jsplumb_gadget.getContent();
}).then(function(content) {
var node, graph = JSON.parse(content).graph;
}).then(function (content) {
var node;
var graph = JSON.parse(content).graph;
equal(1, Object.keys(graph.node).length);
node_id = Object.keys(graph.node)[0];
node = graph.node[node_id];
equal("Example.Node", node._class);
}).then(function() {
}).then(function () {
// click the new node to see display the popup
// XXX at the moment nodes have class window
equal(1, $("div.window").length, "We have a new node");
$("div.window").simulate("dblclick");
// Promises that handle the dialog actions are not available
// immediately after clicking.
var promise = RSVP.Promise(function(resolve) {
var waitForDialogAndDelete = function() {
var promise = new RSVP.Promise(function (resolve) {
function waitForDialogAndDelete() {
if (!jsplumb_gadget.props.dialog_promise) {
// Dialog not ready. Let's retry later.
// XXX this condition is actually incorrect. We need to wait
// for the event listener to have been registered for the
// dialog buttons. This setTimeout is good enough for now.
return setTimeout(waitForDialogAndDelete, 1e3);
setTimeout(waitForDialogAndDelete, 1e3);
return;
}
equal(1, $("input[value='Delete']").length, "There should be one delete button");
$("input[value='Delete']").click();
// resolve our test promise once the dialog handling promise is
// finished.
jsplumb_gadget.props.dialog_promise.then(resolve);
};
}
waitForDialogAndDelete();
});
return promise.then(function() {
return jsplumb_gadget.getContent().then(function(content) {
return promise.then(function () {
return jsplumb_gadget.getContent().then(function (content) {
var graph = JSON.parse(content).graph;
equal(0, Object.keys(graph.node).length, "node is removed from data");
equal(0, $("div.window").length, "DOM is modified");
......@@ -516,11 +562,31 @@
});
}
g.declareGadget("./index.html", {
element: document.querySelector("#qunit-fixture")
}).then(function(new_gadget) {
element: document.querySelector("#test-element")
}).then(function (new_gadget) {
jsplumb_gadget = new_gadget;
jsplumb_gadget.render(sample_data_empty_graph);
}).then(runTest).fail(error_handler).always(start);
});
test("Graph is automatically layout", function () {
var jsplumb_gadget;
stop();
g.declareGadget("./index.html", {
element: document.querySelector("#test-element")
}).then(function (new_gadget) {
jsplumb_gadget = new_gadget;
return jsplumb_gadget.render(sample_data_graph_no_node_coordinate);
}).then(function () {
return jsplumb_gadget.getContent();
}).then(function (content) {
/*jslint unparam: true */
$.each(JSON.parse(content).graph.node, function (ignore, node) {
ok(node.coordinate.top !== undefined, "Node have top coordinate");
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((0 <= node.coordinate.left) && (node.coordinate.left <= 1), "Node left coordinate is between [0..1]");
});
}).fail(error_handler).always(start);
});
});
})(rJS, JSON, QUnit, RSVP, jQuery);
\ No newline at end of file
}(rJS, JSON, QUnit, RSVP, jQuery));
\ No newline at end of file
......@@ -14,16 +14,16 @@
}).declareMethod("render", function(options) {
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) {
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: options.property_definition.enum[i],
text: options.property_definition.enum[i]
value: options.property_definition['enum'][i],
text: options.property_definition['enum'][i]
});
}
select.innerHTML += tmp;
......
erp5_trade
\ No newline at end of file
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Zuite" module="Products.Zelenium.zuite"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_objects</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>graph_editor_zuite</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <string></string> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="ZopePageTemplate" module="Products.PageTemplates.ZopePageTemplate"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_bind_names</string> </key>
<value>
<object>
<klass>
<global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/>
</klass>
<tuple/>
<state>
<dictionary>
<item>
<key> <string>_asgns</string> </key>
<value>
<dictionary>
<item>
<key> <string>name_subpath</string> </key>
<value> <string>traverse_subpath</string> </value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</state>
</object>
</value>
</item>
<item>
<key> <string>content_type</string> </key>
<value> <string>text/html</string> </value>
</item>
<item>
<key> <string>expand</string> </key>
<value> <int>0</int> </value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>testQunit</string> </value>
</item>
<item>
<key> <string>output_encoding</string> </key>
<value> <string>utf-8</string> </value>
</item>
<item>
<key> <string>title</string> </key>
<value> <unicode>Graph Editor Qunit Test</unicode> </value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
<html>
<head><title>Graph Editor Qunit Test</title></head>
<body>
<table cellpadding="1" cellspacing="1" border="1">
<thead>
<tr><td rowspan="1" colspan="4">
Run existing qunit test in zelenium framework, to easily integrate it in current test suite.
</td></tr>
</thead><tbody>
<tr>
<td>open</td>
<td tal:content="string:${context/portal_url}/dream_graph_editor/jsplumb/test.html"></td>
<td></td>
</tr>
<tr>
<td>waitForTextPresent</td>
<td>Tests completed in </td>
<td>30000</td>
</tr>
<tr>
<td>assertText</td>
<td>css=#qunit-testresult span.failed</td>
<td>0</td>
</tr>
</tbody></table>
</body>
</html>
\ No newline at end of file
##############################################################################
#
# Copyright (c) 2017 Nexedi SA and Contributors. All Rights Reserved.
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
import unittest
from Products.ERP5Type.tests.ERP5TypeFunctionalTestCase import ERP5TypeFunctionalTestCase
class TestGraphEditor(ERP5TypeFunctionalTestCase):
run_only = "graph_editor_zuite"
def getBusinessTemplateList(self):
return (
'erp5_graph_editor',
'erp5_graph_editor_ui_test',
'erp5_ui_test_core',
)
def test_suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(TestGraphEditor))
return suite
\ No newline at end of file
<?xml version="1.0"?>
<ZopeData>
<record id="1" aka="AAAAAAAAAAE=">
<pickle>
<global name="Test Component" module="erp5.portal_type"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>_recorded_property_dict</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
</value>
</item>
<item>
<key> <string>default_reference</string> </key>
<value> <string>testFunctionalGraphEditor</string> </value>
</item>
<item>
<key> <string>description</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>id</string> </key>
<value> <string>test.erp5.testFunctionalGraphEditor</string> </value>
</item>
<item>
<key> <string>portal_type</string> </key>
<value> <string>Test Component</string> </value>
</item>
<item>
<key> <string>sid</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>text_content_error_message</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>text_content_warning_message</string> </key>
<value>
<tuple/>
</value>
</item>
<item>
<key> <string>version</string> </key>
<value> <string>erp5</string> </value>
</item>
<item>
<key> <string>workflow_history</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="2" aka="AAAAAAAAAAI=">
<pickle>
<global name="PersistentMapping" module="Persistence.mapping"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>data</string> </key>
<value>
<dictionary/>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="3" aka="AAAAAAAAAAM=">
<pickle>
<global name="PersistentMapping" module="Persistence.mapping"/>
</pickle>
<pickle>
<dictionary>
<item>
<key> <string>data</string> </key>
<value>
<dictionary>
<item>
<key> <string>component_validation_workflow</string> </key>
<value>
<persistent> <string encoding="base64">AAAAAAAAAAQ=</string> </persistent>
</value>
</item>
</dictionary>
</value>
</item>
</dictionary>
</pickle>
</record>
<record id="4" aka="AAAAAAAAAAQ=">
<pickle>
<global name="WorkflowHistoryList" module="Products.ERP5Type.patches.WorkflowTool"/>
</pickle>
<pickle>
<tuple>
<none/>
<list>
<dictionary>
<item>
<key> <string>action</string> </key>
<value> <string>validate</string> </value>
</item>
<item>
<key> <string>validation_state</string> </key>
<value> <string>validated</string> </value>
</item>
</dictionary>
</list>
</tuple>
</pickle>
</record>
</ZopeData>
erp5_graph_editor
erp5_ui_test_core
\ No newline at end of file
portal_tests/graph_editor_zuite
portal_tests/graph_editor_zuite/**
\ No newline at end of file
test.erp5.testFunctionalGraphEditor
\ No newline at end of file
erp5_full_text_mroonga_catalog
\ No newline at end of file
erp5_graph_editor_ui_test
\ 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