Jsplumb detach connection - javascript

I am trying to detach a connection when widget is clicked.
refers to the posts:
Jsplumb remove all connections not working with verion 2.4.3
How to delete jsPlumb connection
I am on newest CDNJS release 2.15.6, here's what I have tried:
this.plumb.bind('click', function(conn){
connect.connections.splice(connect.connections.indexOf(conn.cn), 1);
this.plumb.deleteConnection(conn);
connect.render();
});
// jsPlumb: fire failed for event click : TypeError: undefined is not an object (evaluating 'this.plumb.deleteConnection')
this.plumb.bind('click', function(conn){
connect.connections.splice(connect.connections.indexOf(conn.cn), 1);
jsPlumb.deleteConnection(conn);
connect.render();
});
this successfully delete the connection, however, since that I cannot add new connection any more if the target and sources corresponds to deleted connection. I got an error saying:
TypeError: null is not an object (evaluating 'x.endpoints[0]') jsplumb.min.js
refers to this issue,
I tried to switch to version 5, but I did not find any browser script on https://github.com/jsplumb/jsplumb, by the way there are bunch of errors in the demo provided.
I will be very grateful for any of following:
method to detach a connection
advice on any potential error on my conduction
CDN for newest stable version
minimum snippet to generate the error
setTimeout(()=>{
let source = document.createElement('div');
let target = document.createElement('div');
document.body.appendChild(source);
document.body.appendChild(target);
let instance = jsPlumb.getInstance({
PaintStyle: { strokeWidth: 1 },
Container: document.body,
});
function getConnection(){
return instance.connect({
source: source,
target: target,
cssClass: 'redLine',
endpoint: "Blank",
anchor:"AutoDefault",
overlays: [
["Arrow", {
location: 1,
width: 10,
length: 10
}
],
[ "Label", {
location:0.5,
label:"Label Text"
}
],
],
paintstyle: {
lineWidth: 1,
strokeStyle: 'black',
}
,
connector: ["Flowchart"],
});
}
let conn = getConnection();
jsPlumb.deleteConnection(conn, {
fireEvent: false, //fire a connection detached event?
forceDetach: false //override any beforeDetach listeners
});
conn = getConnection();
}, 0)
now we are trying to solve the issue on Github

The problem here was that the deleteConnection method was being called on the default instance of jsPlumb but the rest of the code was using a specific instance:
let instance = jsPlumb.getInstance({
PaintStyle: { strokeWidth: 1 },
Container: document.body,
});
...
jsPlumb.deleteConnection(...) <-- should have been instance.deleteConnection

Related

ChartJS zoom/pan onPan event not firing in React useEffect hook

I've run into an issue with ChartJS's Pan/Zoom plugin event onPan not firing when the Chart configuration is assigned from inside of a useEffect hook. I am listening to the pan & zoom event and firing an event bus accordingly to synchronize multiple charts' zoom/pan bounds with each other. The problem arises with only onPan as onZoom works perfectly fine.
Here's the general important code (this is all in a functional component):
useEffect(() => {
if(applicableEffectCondition.isMet) {
return; // to prevent infinite loop
}
// do code magic ...
function dispatchInteractEvent(reference: MutableRefObject < any | undefined > ) {
dispatch({
type: "##charts/INTERACT",
payload: {
sender: reference.current.id,
x: {
min: reference.current.scales.x.min,
max: reference.current.scales.x.max,
},
}
});
}
const options: ChartOptions = {
// ...
plugins: {
zoom: {
limits: {
// minimum X value & maximum X value
x: {
min: Math.min(...labelsNumber),
max: Math.max(...labelsNumber)
},
},
zoom: {
wheel: {
enabled: true,
speed: 0.1,
},
pinch: {
enabled: true
},
mode: 'x',
onZoom: () => {
alert("I am zooming!");
dispatchInteractEvent(chartRef);
}
},
pan: {
enabled: true,
mode: 'x',
rangeMin: {
y: allDataMin
},
rangeMax: {
y: allDataMax
},
onPan: () => {
alert("I am panning!");
dispatchInteractEvent(chartRef);
}
}
as any
}
},
};
setChartOptions(options); // use state
const data: ChartData = {
labels: labelsString,
datasets: datasetState.getDatasets(),
};
setChartData(data); // use state
}, [applicableChannels]);
useBus(
'##charts/INTERACT',
(data) => {
const payload = data.payload;
if (payload.sender === chartRef.current.id) {
return;
}
// update chart scale/zoom/pan to be in sync with all others
chartRef.current.options.scales.x.min = payload.x.min;
chartRef.current.options.scales.x.max = payload.x.max;
chartRef.current.update();
},
[],
);
return (
<Chart type={"line"} ref={chartRef} options={chartOptions} data={chartData} />
);
As you can see in the configuration, I put two alerts to notify me when the events are fired. "I am zooming!" is fired perfectly fine, but "I am panning!" is never fired. Nor is any other pan-specific event.
This is a very odd issue. Any help would be appreciated!
Thanks!
The issue has been resolved.
When a chart is initialized, HammerJS is initialized and adds listeners for panning/zooming to the canvas. The issue happens when the chart is initialized with a configuration before the configuration is created (setting the configuration to an empty state), and when the real configuration is updated (with the zoom/pan plugin), the state of enabled in the pan object is not updated correctly, so panning becomes disabled.
The solution is to wait to display the chart React Element until the configuration is available to populate it.
This issue was really happening because the configuration was being set in a useEffect hook, making it available in a later event loop tick than the actual JSX element was being created in.
When you use useEffect(()=>{},[xxx]) hooks with [xxx] as the second parameter, you should know that only when xxx has changed it will run the function. In your case, I didn't see any "applicableChannels" in your code, so it's hard to know where the problem is.

Unexpected error when attempting to update chart data in Chart.js, in a Vue app

I am using Chart.js 3.5 and Vue 3.
I was successfully able to draw a chart, and I am trying to trigger a data change, inside a Vue method. Unfortunately, I encounter the following issue: "Uncaught TypeError: Cannot set property 'fullSize' of undefined".
Edit2: Added a missed }. Code should now be runnable
MyChart.vue:
<template>
<canvas id="chartEl" width="400" height="400" ref="chartEl"></canvas>
<button #click="addData">Update Chart</button>
</template>
<script>
import Chart from 'chart.js/auto';
export default {
name: "Raw",
data() {
return {
chart: null
}
},
methods: {
createChart() {
this.chart= new Chart(this.$refs["chartEl"], {
type: 'doughnut',
data: {
labels: ['VueJs', 'EmberJs', 'ReactJs', 'AngularJs'],
datasets: [
{
backgroundColor: [
'#41B883',
'#E46651',
'#00D8FF',
'#DD1B16'
],
data: [100, 20, 80, 20]
}
]
},
options: {
plugins: {}
}
})
},
addData() {
const data = this.chart.data;
if (data.datasets.length > 0) {
data.labels.push('data #' + (data.labels.length + 1));
for (var index = 0; index < data.datasets.length; ++index) {
data.datasets[index].data.push(123);
}
// Edit2: added missed }
this.chart.update(); } // this line seems to cause the error}
}
},
mounted () {
this.createChart()
},
}
</script>
Edit1: Adding the following to the options makes the chart update successfully, but the error is still present and the animation does not work. The chart flickers and displays the final (updated) state. Other animations, such as hiding/showing arcs do not seem to be afected
options: {
responsive: true,
}
Edit3: Adding "maintainAspectRatio:false" option seems to again stop chart from updating (the above mentioned error is still present)
By walking through the debugger, the following function from 'chart.esm.js' seems to be called successfully a few times, and then error out on last call:
beforeUpdate(chart, _args, options) {
const title = map.get(chart); // this returns null, which will cause the next call to error with the above mentioned exception.
layouts.configure(chart, title, options);
title.options = options;
},
//////////////////////
configure(chart, item, options) {
item.fullSize = options.fullSize;
item.position = options.position;
item.weight = options.weight;
},
This may be a stale post but I just spent several hours wrestling with what seems like the same problem. Perhaps this will help you and/or future people with this issue:
Before assigning the Chart object as an attribute of your Vue component, call Object.seal(...) on it.
Eg:
const chartObj = new Chart(...);
Object.seal(chartObj);
this.chart = chartObj;
This is what worked for me. Vue aggressively mutates attributes of objects under its purview to add reactivity, and as near as I can tell, this prevents the internals of Chart from recognising those objects to retrieve their configurations from its internal mapping when needed. Object.seal prevents this by barring the object from having any new attributes added to it. I'm counting on Chart having added all the attributes it needs at init time - if I notice any weird behaviour from this I'll update this post.
1 year later, Alan's answer helps me too, but my code failed when calling chart.destroy().
So I searched and found what seems to be the "vue way" of handling it: markRaw, here is an example using options API:
import { markRaw } from 'vue'
// ...
export default {
// ...
beforeUnmount () {
if (this.chart) {
this.chart.destroy()
}
},
methods: {
createChart() {
const chart = new Chart(this.$refs["chartEl"], {
// ... your chart data and options
})
this.chart = markRaw(chart)
},
addData() {
// ... your update
this.chart.update()
},
},
}

How to use Workbox setDefaultHandler

I'm switching from sw-toolbox to Workbox and I can't figure out how to use setDefaultHandler().
If I try (as stated in the documentation linked above):
workboxSW.router.setDefaultHandler({
handler: new workbox.runtimeCaching.CacheFirst()
});
I get an error that runtimeCaching is undefined:
Uncaught ReferenceError: router is not defined
So.. how do I use it and configure it in a way similar to how I could configure sw-toolbox:
toolbox.options.cache = {
name: "default",
maxEntries: 128,
maxAgeSeconds: (60*60*24), // 24hrs
};
toolbox.router.default = toolbox.cacheFirst;
I would like to be able to do something like this:
workboxSW.router.setDefaultHandler({
handler: workboxSW.strategies.cacheFirst({
cacheName: 'default',
cacheExpiration: {
maxEntries: 128,
},
cacheableResponse: {statuses: [0, 200]},
})
});
..which doesn't throw compile errors but when I use it I get this:
Uncaught (in promise) TypeError: Request method 'POST' is unsupported
..and my Cache Storage for 'default' remains empty..?
Since my edits for Jeff's first solution were rejected I'll just go ahead and submit an answer myself.
Jeff's sample came close. He suggested:
You could check for the request type in the default handler, and only
apply the cache-first strategy to GET requests:
workboxSW.router.setDefaultHandler({
handler: (args) => {
if (args.event.request.method === 'GET') {
return workboxSW.strategies.cacheFirst(args);
}
return fetch(args.event.request);
},
});
It's the right approach but the example code he provided didn't work. The handler argument needs a handler, not a strategy. Luckily, strategies have exactly one (public) method, called "handle".
So I modified his code a little; First, I create a strategy called defaultStrategy with all the options I need. Then, in the setDefaultHandler call, I return defaultStrategy.handle(args) instead of the CacheFirst constructor. That's it!
// Register 'default'
var defaultStrategy = workboxSW.strategies.cacheFirst({
cacheName: "default",
cacheExpiration: {
maxEntries: 128,
// more options..
},
cacheableResponse: {statuses: [0, 200]},
});
workboxSW.router.setDefaultHandler({
handler: (args) => {
if (args.event.request.method === 'GET') {
return defaultStrategy.handle(args);
}
return fetch(args.event.request);
},
});
UPDATE: Workbox v3
As I pointed out in the comments below, the above code doesn't work with Workbox v3. Use this instead:
// Register 'default'
var defaultStrategy = workbox.strategies.cacheFirst ({
cacheName: "your.cache.name",
plugins: [
new workbox.expiration.Plugin({
maxEntries: 128,
maxAgeSeconds: 7 * 24 * 60 * 60, // 1 week
purgeOnQuotaError: true, // Opt-in to automatic cleanup
}),
new workbox.cacheableResponse.Plugin({
statuses: [0, 200] // for opague requests
}),
],
});
workbox.routing.setDefaultHandler(
(args) => {
if (args.event.request.method === 'GET') {
return defaultStrategy.handle(args); // use default strategy
}
return fetch(args.event.request);
}
);
workboxSW.router.setDefaultHandler({
handler: workboxSW.strategies.cacheFirst({...})
});
is the right syntax in general.
I believe that you're seeing
Uncaught (in promise) TypeError: Request method 'POST' is unsupported
because the default handler is triggered for all HTTP requests that don't match any explicit route, including HTTP POST requests. But a HTTP POST request can't be used with the Cache Storage API, and an exception similar to what you're seeing will be thrown when the cache-first strategy attempts to store the request/response pair in the cache.
In this particular case, when you know that your web app is going to make HTTP POST requests, you could take one of two approaches.
You could check for the request type in the default handler, and only apply the cache-first strategy to GET requests:
workboxSW.router.setDefaultHandler({
handler: (args) => {
if (args.event.request.method === 'GET') {
return workboxSW.strategies.cacheFirst(args);
}
return fetch(args.event.request);
},
});
Alternatively, you could create a wildcard route that matches all requests, and take advantage of the fact that by default, routes will only match HTTP GET:
workboxSW.router.registerRoute(
/./, // This should match all requests.
workboxSW.strategies.cacheFirst({...}),
'GET' // This is the default, and can be left out.
);

Using Pusher with SimpleWebRTC

I've come across the SimpleWebRTC package. Trying to get it to work, but can't seem to get the remote stream coming through. I'm also using Pusher for signalling, rather than the default that comes with SimpleWebRTC.
I've set up my own connection:
var myConnection = {
pusher: new Pusher('mypusherkey', { cluster: 'ap1' } ),
channel: null,
on: function (event, callback) {
this.pusher.bind (event, callback);
},
emit: function () {
if (arguments.length == 1) {
if (arguments[0] === "join") {
this.channel = this.pusher.subscribe(arguments[1]);
}
}
else
this.channel.trigger(arguments);
},
getSessionId: function() {
return this.pusher.connection.socket_id;
},
disconnect: function() {
this.pusher.disconnect();
}
};
Then I have the SimpleWebRTC initialisation:
var webrtc = new SimpleWebRTC({
// the id/element dom element that will hold "our" video
localVideoEl: 'localVideo',
// the id/element dom element that will hold remote videos
remoteVideosEl: 'remotesVideos',
// immediately ask for camera access
autoRequestMedia: true,
debug: true,
connection: myConnection
});
// we have to wait until it's ready
webrtc.on('readyToCall', function () {
console.log('ready to join');
// you can name it anything
webrtc.joinRoom('test-video-chat');
});
Doing a simple test between 2 PCs, it's not setting up the remote stream. In the dev console apart from the intitial event hook ups, I'm not seeing any other activity happening, especially SimpleWebRTC "readyToCall" not firing.
you probably need to emit a 'connect' signal from your socket adapter to trigger this code

Dojo dGrid/dStore realtime update

I've been trying to get my dgrid/dstore grid in realtime.
As var as I understand the function 'Observable' is deprecated, and also it doesnt work for me.
I've tried to update the grid via a Interval timer but then the whole grid is erased and new loaded. How to update the whole grid 'inline' without erasing, and showing 'loading data'?
This is my basic code:
var timer = setInterval(function() {
store.invalidate(); // Invalidate the cache
store.fetch(); // Perform a new request for all items
grid.refresh();
}, 500);
Observable does not exist in dstore, but there is a Trackable mixin which can be used instead. As for updating the grid when new content is fetch from the store, you could use a dstore/Cache and then pass the cached Memory store that mixes in Trackable to the grid to use instead. Each time new data is added to the caching store, it will also be reflected in the grid.
require([
'dgrid/OnDemandGrid',
'dstore/Rest',
'dstore/Memory',
'dstore/Trackable',
'dstore/Cache',
'dojo/domReady!'
], function (OnDemandGrid, Rest, Memory, Trackable, Cache) {
var restStore = new Rest({
target: '/mockRequest/'
});
var store = Cache.create(restStore, {
cachingStore: new (Memory.createSubclass(Trackable))()
});
var grid = new OnDemandGrid({
collection: store.cachingStore,
columns: {
id: 'ID',
name: 'Name',
description: 'Description'
},
loadingMessage: 'Loading...'
}, 'main');
grid.startup();
setInterval(function () {
store.fetch();
}, 2000);
});

Categories