JS-Plumb: (Re-)Draw a line between two elements after clicking - javascript

Edit: After trying out different hand-made solutions, I am using JSPlumb and trying to let it visually connect a clicked item from one list with a clicked item from another list (see screenshot).
I built upon this Stackoverflow thread and made it work basically, however the code provided there allows multiple connections, i.e. JSPlumb draws multiple endpoints and lines, and it doesn't react if a 'Target' is clicked first.
However, in my case there should be strictly only one connection, and JSPlumb should re-connect once I click on another list item on either side.
(E.g. I click on 'Source 1' and 'Target 3', JSPlumb draws the connection. I click on 'Target 4', JSPlumb should keep 'Source 1' as source and re-set 'Target 4' as the target, e.g. now draw the connection from 'Source 1' to 'Target 4'. The same with clicking a different 'Source', i.e. the target should stay the same.)
In what way would I need to alter the code in order to achieve the desired re-draw?
CodePen
jQuery(document).ready(function () {
var targetOption = {
anchor: "LeftMiddle",
isSource: false,
isTarget: true,
reattach: true,
endpoint: "Dot",
connector: ["Bezier", {
curviness: 50}],
setDragAllowedWhenFull: true
};
var sourceOption = {
tolerance: "touch",
anchor: "RightMiddle",
maxConnections: 1,
isSource: true,
isTarget: false,
reattach: true,
endpoint: "Dot",
connector: ["Bezier", {
curviness: 50}],
setDragAllowedWhenFull: true
};
jsPlumb.importDefaults({
ConnectionsDetachable: true,
ReattachConnections: true,
Container: 'page_connections'
});
//current question clicked on
var questionSelected = null;
var questionEndpoint = null;
//remember the question you clicked on
jQuery("#select_list_lebensbereiche ul > li").click( function () {
//remove endpoint if there is one
if( questionSelected !== null )
{
jsPlumb.removeAllEndpoints(questionSelected);
}
//add new endpoint
questionSelected = jQuery(this)[0];
questionEndpoint = jsPlumb.addEndpoint(questionSelected, sourceOption);
});
//now click on an answer to link it with previously selected question
jQuery("#select_list_wirkdimensionen ul > li").click( function () {
//we must have previously selected question
//for this to work
if( questionSelected !== null )
{
//create endpoint
var answer = jsPlumb.addEndpoint(jQuery(this)[0], targetOption);
//link it
jsPlumb.connect({ source: questionEndpoint, target: answer });
//cleanup
questionSelected = null;
questionEndpoint = null;
}
});
});

You were already keeping track of the "source" end of the linked items in a global variable; one way of getting to your desired behavior mostly just requires keeping track of the "target" end the same way. (There's room for improving this -- globals are maybe not an ideal strategy, and there's some code duplication between the 'source' and 'target' click handlers, but this should do for demonstration at least.)
// ...other init variables skipped
var questionEndpoints = []; // 'source' and 'target' endpoints
// "source" click handler
jQuery("#select_list_lebensbereiche ul > li").click(function() {
//remove existing start endpoint, if any:
jsPlumb.deleteEndpoint(questionEndpoints[0]);
// add a new one on the clicked element:
questionEndpoints[0] = jsPlumb.addEndpoint(jQuery(this), sourceOption);
connectEndpoints();
});
// "target" endpoint
jQuery("#select_list_wirkdimensionen ul > li").click(function() {
if (!questionEndpoints[0]) return; // don't respond if a source hasn't been selected
// remove existing endpoint if any
jsPlumb.deleteEndpoint(questionEndpoints[1]);
//create a new one:
questionEndpoints[1] = jsPlumb.addEndpoint(jQuery(this), targetOption);
connectEndpoints();
});
var connectEndpoints = function() {
jsPlumb.connect({
source: questionEndpoints[0],
target: questionEndpoints[1]
});
};
});
Working CodePen example

Related

Dynamically created elements not draggable

I have a simple block its element is dynamically added to DOM, I want the user to be able to create a block and it should be draggable using jsplumb library.
Unfortunately, now I can create element but their not draggable but if I add them manually to the dom, it's draggable.
Here is what I have so far
function addMovieButton() {
var newMovieBlockButton = $("<div class='movie-button w'>Button New<div class='ep' action='begin'></div><div>");
}
Here is plumb.js
jsPlumb.ready(function () {
// setup some defaults for jsPlumb.
var instance = jsPlumb.getInstance({
Endpoint: ["Dot", {radius: 5}],
Connector:"StateMachine",
HoverPaintStyle: {stroke: "#1e8151", strokeWidth: 2 },
ConnectionOverlays: [
[ "Arrow", {
location: 1,
id: "arrow",
length: 14,
foldback: 0.8
} ],
[ "Label", { label: "FOO", id: "label", cssClass: "aLabel" }]
],
Container: "canvas"
});
instance.registerConnectionType("basic", { anchor:"Continuous", connector:"StateMachine" });
window.jsp = instance;
var canvas = document.getElementById("canvas");
var windows = jsPlumb.getSelector(".statemachine-demo .w");
var windows_movie = jsPlumb.getSelector(".statemachine-demo .movie-block ");
// bind a click listener to each connection; the connection is deleted. you could of course
// just do this: jsPlumb.bind("click", jsPlumb.detach), but I wanted to make it clear what was
// happening.
instance.bind("click", function (c) {
instance.deleteConnection(c);
});
// bind a connection listener. note that the parameter passed to this function contains more than
// just the new connection - see the documentation for a full list of what is included in 'info'.
// this listener sets the connection's internal
// id as the label overlay's text.
instance.bind("connection", function (info) {
info.connection.getOverlay("label").setLabel(info.connection.id);
});
// bind a double click listener to "canvas"; add new node when this occurs.
jsPlumb.on(canvas, "dblclick", function(e) {
// newNode(e.offsetX, e.offsetY);
});
//
// initialise element as connection targets and source.
//
var initNode = function(el) {
// initialise draggable elements.
instance.draggable(el);
instance.makeSource(el, {
filter: ".ep",
anchor: "Continuous",
connectorStyle: { stroke: "#5c96bc", strokeWidth: 2, outlineStroke: "transparent", outlineWidth: 4 },
connectionType:"basic",
extract:{
"action":"the-action"
},
maxConnections: 6,
onMaxConnections: function (info, e) {
alert("Maximum connections (" + info.maxConnections + ") reached");
}
});
instance.makeTarget(el, {
dropOptions: { hoverClass: "dragHover" },
anchor: "Continuous",
allowLoopback: true
});
// this is not part of the core demo functionality; it is a means for the Toolkit edition's wrapped
// version of this demo to find out about new nodes being added.
//
instance.fire("jsPlumbDemoNodeAdded", el);
};
// suspend drawing and initialise.
instance.batch(function () {
for (var i = 0; i < windows.length; i++) {
initNode(windows[i], true);
console.log(windows[i]);
}
for (var j = 0; j < windows_movie.length; j++) {
initNode(windows_movie[j], true);
console.log(windows_movie[j]);
}
});
jsPlumb.fire("jsPlumbDemoLoaded", instance);
});
Here is live demo live demo
Here is plunker full source code
On the demo above just right click to add movie block for testing
Why does draggable not working for dynamically created elements?
here is a sample page I made a while ago when I first discovered 'jsplumb', it does exactly what you want so you might wanna use it or build on top of it.
Remember, indeed you should call the draggable method after the elements are added to the DOM, my example is so simple:
it doesn't need the jsplumb.fire
it doesn't need the .ready binding
it doesn't need the 'batch' processing offered by jsplumb
so you get to avoid problems like the scope of ready and other I'm still trying to master.

can't use jsplumb with second function

I'm getting my instance like this:
jsp = jsPlumb.getInstance();
jsp.setContainer(_domnodeId);
jsp.ready(function(){
//doing some stuff - connecting boxes with arrows...
var conn2 = jsp.connect({
source: boxSST_IPMRS_COBRAIP.boxId,
target: boxCOBRA_IM.boxId
});
}
result:
in another function I'm doing the same:
jsp = jsPlumb.getInstance();
jsp.setContainer(_domnodeId);
jsp.ready(function(){
//var dynamicAnchor = [ [ 0.2,1,0.5 ], [ 0.2, 1, 0.5 ], "Top", "Bottom" ];
var common = {
anchor:[ "Continuous", { faces:["bottom","right"] }],
endpoint: "Blank",
connector:[ "Bezier", { curviness:50 }, common ],
overlays: [
["Arrow", {location:1, width:10, length:10}],
]
};
jsp.connect({
source: boxes.b1.boxId,
target: boxes.b2.boxId
}, common);
}
The arrows are all moving to the left,top corner...
var jsp is global and I cleared _domnodeId at the beginning of my second function. Any suggestions?
clearing my domnodeID:
function clean(container){
//remove everything
$("#" + container)
.children()
.not('nav')
.remove();
// box id counter
window.EvmClasses.chartBox.boxId = 0;
}
I cleared _domnodeId at the beginning of my second function
How did you done that? It seems to me that you didn't clear it properly.
Did you read "Removing" section of the manual?
If you have configured a DOM element with jsPlumb in any way you
should use jsPlumb to remove the element from the DOM (as opposed to
using something like jQuery's remove function, for example).
Please read it thoroughly. You may need either jsPlumb.empty, deleteEveryEndpoint, or reset.

Noty.js removing oldest notification on new notification

I am using this script http://ned.im/noty/
for showing notifications
var n = noty({
text: message,
type: type,
dismissQueue: true,
force: true,
layout : "bottomLeft",
theme: 'newTheme',
maxVisible : 5
});
So this is the current config, it has queued 5 items. The problem is that I can't figure out how to remove the first notification on showing new one, when a button is clicked.
Any ideas are welcome.
Okay I figured it out using the noty.js API: http://ned.im/noty/#api
first I defined the top notification
var notyclose_id = $("#noty_bottomLeft_layout_container>li:first-child>.noty_bar").attr('id');
after that I get the amount of notifications
var noty_list_count = $("#noty_bottomLeft_layout_container li").size();
than I check if this amount is bigger or equal to my notifications setting
if(noty_list_count >= 5)
$.noty.close(notyclose_id);
and if yes I use the api to close it. :)
Was looking for this and ended up here; current version for the time being (3.1.0) allows you to use the killer option:
noty({
text: 'I have just killed it',
type: 'success',
killer : true
})
Looks like it "kills" all previous notifications in the queue, making "this one" the only one displayed.
You can also consider an auto removal timeout. That's what I do with information and success messages: they disappear automatically after 3 seconds (timeout: 3000). I leave the error and warning messages permanent (user has to click it to be removed, timeout: false, that's the default).
This way it's less likely that things pile up and requires less user interaction. Less friction. I don't know if it fits your use-case though.
noty({
text: 'Sucessfully persisted xy value',
layout: 'bottomRight',
type: 'success',
timeout: 3000,
animation: {
open: 'animated zoomInUp', // Animate.css class names
close: 'animated zoomOutUp' // Animate.css class names
}
});
noty({
text: 'Error while writing value xy',
layout: 'bottomRight',
type: 'error',
animation: {
open: 'animated zoomInUp', // Animate.css class names
close: 'animated zoomOutUp' // Animate.css class names
}
});
if your are using layout as 'topRight' then given below code is helpful.
var notyclose_id = $("#noty_topRight_layout_container>li:first- child>.noty_bar").attr('id');
var noty_list_count = $("#noty_topRight_layout_container li").size();
if(noty_list_count >= 6) {
$.noty.close(notyclose_id);
}
you also need to call $.notyRenderer.show() to show a new message, it's already there but you need to call it everytime
if(instance.options.maxVisible > 0) {
//if($(instance.options.layout.container.selector + ' > li').length < instance.options.maxVisible) {
$.notyRenderer.show($.noty.queue.shift());
//}
//else {
//}
}

How to get Event or DOM element of selected feature in OpenLayers

I'm implementing an OpenLayers SelectFeature control, and trying to position an JQuery UI dialog widget right on top of the selected feature. To use the JQuery UI Position utility, it requires either a DOM element or an Event.
The onSelect callback of the SelectFeature control gives me an OpenLayers.Feature.Vector object representing the selected feature. From this, how do I get either the DOM element of the selected feature, or the Event object of the click event?
var selectControl = new OpenLayers.Control.SelectFeature(clientsLayer, {
hover : false,
clickout: false,
multiple: false,
onSelect: function(feature) {
// how do I get the DOM element of the feature
// or alternately, the click event of the selection?
}
});
You are doing it right.
If you do a console.log(feature) You'll see that it returns an object with CLASS_NAME =
"OpenLayers.Feature.Vector"
onSelect: function(feature) {
console.log(feature);
}
Update:
I see.
You could add event listeners
var selectControl = new OpenLayers.Control.SelectFeature(clientsLayer, {
hover: false,
clickout: false,
multiple: false,
eventListeners: {
featurehighlighted: function (event) {
console.log(event);
console.log(event.feature);
}
}
});
Is it something like this you look for ?
onSelect: function onFeatureSelect(event) {
var feature = event.feature;
if ( feature.layer.name == 'theone') {
...
}
}
Note I have also posted this answer at How do I get the DOM element from openlayers vector feature
If you want to find the position of the mouse or feature on hover so you can display a custom overlay, create a custom hover control and define the featurehighlighted function as follows:
var featureHoverControl = new OpenLayers.Control.SelectFeature([myLayer], {
id: 'featureHoverControl',
hover: true,
autoActivate: true,
highlightOnly: true,
renderIntent: "temporary",
eventListeners: {
featurehighlighted: function(e) {
// either use the mouse's position when the event was triggered
var mouseXPosition = this.handlers.feature.evt.x;
var mouseYPosition = this.handlers.feature.evt.y;
// or retrieve the feature's center point
var featureCenterLonLat = e.feature.geometry.bounds.getCenterLonLat();
var featureCenterPoint = map.getPixelFromLonLat(featureCenterLonLat);
// display your custom popup here
// e.g. showTooltip(e.feature.attributes.name, featureCenterPoint.x, featureCenterPoint.y)
}
,
featureunhighlighted: function(e) {
// hide your custom popup here
// e.g. hideTooltip()
}
}
});
map.addControl(featureHoverControl);
If you require access to the SVG element representing your layer/feature (in the event you are using a third-party library and you don't feel like modifying the source code), use either of the following lines (depending if you require the layer or feature):
var layerElement = map.getLayersByName("My Layer")[0].features.root;
var layerElementId = map.getLayersByName("My Layer")[0].features.root.id;
var featureElementId = map.getLayersByName("My Layer")[0].getFeaturesByAttribute("name","My Feature Name")[0].geometry.components[0].id;
Note that since this only grabs the element's id, you'll still need to use an appropriate method to grab a reference to the element itself. Something like either of the following:
var elementReference1 = document.getElementById(featureElementId);
var elementReference2 = jQuery('#'+featureElementId)[0];

YUI Tooltip not displaying on top of Panel

Currently having a problem trying to get YUI Tooltips to display on top of a YUI Panel after it is shown that were previously created. The problem is is that the Panel cannot be registered to the overlay manager because it would require a TON of code to be changed and tested extending a hard deadline. The only way to get this to work is to setup the Tooltips after the Panel is shown. Problem there is the amount of code changes that would have to be done to attach another function call. My problem is that I was hoping that I could use the event handling to use "showEvent" but I cannot seem to get it to work (I apologize for word count):
var panel_obj = new YAHOO.widget.Panel('someID', {
width: "700px",
height: "500px",
close: true,
draggable: false,
modal: true,
constraintoviewport: true,
visible: false,
fixedcenter: true
});
panel_obj.render();
var tooltip_name = 'newTooltip1';
var element_id = 'htmlElementIDToBecomeTooltip';
function createTooltip() {
window[tooltip_name] = new YAHOO.widget.Tooltip(tooltip_name, {
context: element_id,
xyoffset: [15, -15],
zIndex: 999
});
}
function successfulScenario() {
panel_obj.show();
createTooltip();
}
function failedScenario1() {
YAHOO.util.Event.addListener(
'someID',
"showEvent",
createTooltip
);
}
function failedScenario2() {
createTooltip();
panel_obj.show();
}
The only way I have seem to get it working is by running something like successfulScenario(). I'm coming from a jQuery background so I'm still learning YUI. I would love to be able to just extend (subclass) YAHOO.widget.Panel's show() function to call createTooltip but I'm not that much of a guru or I would probably need to change a very large codebase to do it.
try using the "container" property for the tooltip config (so the container would be the panel's element):
function createTooltip() {
window[tooltip_name] = new YAHOO.widget.Tooltip(tooltip_name, {
container: panel_obj.element,
context: element_id,
xyoffset: [15, -15]
});
}
This is the quick solution, using the show event and/or extending the class would be nice but gotta run, if you still need help, I'll check back (also check the example that i made with your code http://jsfiddle.net/3GWaM/2/ ).
function createTooltip() {
var tooltipEl = document.createElement('DIV');
panel_obj.get('element').appendChild(tooltipEl);
window[tooltip_name] = new YAHOO.widget.Tooltip(tooltipEl, {
context: element_id,
xyoffset: [15, -15],
zIndex: 999
});
}
This will ensure the that the tool tip div is created inside the dialog box, instead of in the document body, ensuring it does not appear below the dialog box.
Also, if you want to extend the panel class just do the following
function MyPanel(el, config) {
MyPanel.superclass.constructor.apply(this, arguments);
this.createToolTip();
}
YAHOO.lang.extend(MyPanel, YAHOO.widget.Panel , {
createToolTip: function () {
// create tool tip here
this.on('show', this.showTooltip, this, true);
},
showToolTip: function () {this.toolTip.show();}
});
function getPanelIDFromElementID (element_id) {
var parent_panel = YAHOO.util.Dom.getAncestorByClassName(element_id, 'yui-panel');
var parent_id = null;
if (parent_panel) {
parent_id = parent_panel.id;
}
return parent_id;
}
function createTooltips() {
var tooltip_elements = YAHOO.util.Dom.getElementsByClassName('tooltip');
for (var i = 0; i < tooltip_elements.length; i++) {
var ele_id = tooltip_elements[i].getAttribute('id');
var name = ele_id.charAt(0).toLowerCase() + ele_id.slice(1);
var nameArray = name.split("_");
for (var x=1; x < nameArray.length; x++) {
nameArray[x] = nameArray[x].charAt(0).toUpperCase() + nameArray[x].slice(1);
}
var elementName = nameArray.join('');
window[elementName] = new YAHOO.widget.Tooltip(elementName, {
context: escape(ele_id),
xyoffset: [15, -15],
zIndex: 999,
container: getPanelIDFromElementID(ele_id)
});
}
}

Categories