jsplumb.connect is not working for second time after adding new source element in angular 4 - jsplumb

I am using Angular 4, and I am trying to connect two elements using jsplumb connect plugin.
But it is working fine only for first time,
means if I am having 3 source elements and want to connect to a single target, then it will work fine, it will get connected.
But now if I add the 4th source element in that sources list programmatically, using the child component and then calling connect method, it's not working.
Means once I used that jsplumb.connect function, and then added new source element to list and again calling to connect, it is not working.
parentCreateLine.component.ts
sourceIds = ['s1', 's2', 's3'];
/* this is step 1 intial call from ui */
showIntialConnection() {
this.connectSourceToTargetUsingJSPlumb(this.sourceIds);
}
/* this function will be called from UI to add new source and then show connection */
addNewSourceToListAndConnect(){
this.sourceIds.push('s4');
this.connectSourceToTargetUsingJSPlumb(this.sourceIds);
}
connectSourceToTargetUsingJSPlumb(sourceIds) {
console.log('create connection');
jsPlumb.reset();
let labelName;
for (let i = 0; i < sourceIds.length; i++) {
labelName = 'connection' + (i + 1);
jsPlumb.connect({
connector: ['Flowchart', {stub: [212, 67], cornerRadius: 1, alwaysRespectStubs: true}],
source: sourceIds[i],
target: 'target0',
anchor: ['Right', 'Left'],
endpoint: 'Blank',
paintStyle: {stroke: '#B8C5D6', strokeWidth: 4},
overlays: [
['Label', {label: labelName, location: 0, cssClass: 'connectingConnectorLabel'}]
],
});
}
}
Please help me.
I have tried with uuid also, but got the same output.
Please suggest me the correct way of doing it in Angular 4.

Finally, I got the solution,
I have created the instance of jsplumb in ngAfterViewInit, and then using it and
resetting in the proper order,
this.jsPlumbInstance.reset();
jsPlumb.reset();
this.jsPlumbInstance = jsPlumb.getInstance();
so that everytime will get the new instance.
parentCreateLine.component.ts
jsPlumbInstance;
ngAfterViewInit() {
jsPlumb.reset();
this.jsPlumbInstance = jsPlumb.getInstance();
}
connectSourceToTargetUsingJSPlumb(sourceIds) {
this.jsPlumbInstance.reset();
jsPlumb.reset();
this.jsPlumbInstance = jsPlumb.getInstance();
let labelName;
for (let i = 0; i < sourceIds.length; i++) {
labelName = 'connection' + (i + 1);
this.jsPlumbInstance.connect({
... above code ...
});
}
}

You can share the common jsPlumb instance, Find code below
these works for me
app.component.ts
import { jsPlumb } from 'jsplumb';
constructor(private customService: CustomService) {
customService.jsPlumbInstance = jsPlumb.getInstance();
}
child.component.ts
import {CustomService} from '...path to the service';
constructor(private customService: CustomService) {}
ngAfterViewInit() {
this.jsPlumbInstance = this.customService.jsPlumbInstance;
this.jsPlumbInstance.deleteEveryConnection();
this.jsPlumbInstance.deleteEveryEndpoint();
this.jsPlumbInstance.importDefaults({...})
}

Related

Ckeditor5 error with undoing multiline paste operation

I have a plugin for my ckeditor build which should convert pasted content with formulas,
separated by '(' ')', '$$' etc. into math-formulas from ckeditor5-math (https://github.com/isaul32/ckeditor5-math). I changed the AutoMath Plugin so that it supports text with the separators.
I have run into a problem where undoing (ctrl-z) the operation works fine for single-line content, but not for multiline content.
To reproduce the issue, I have built a similar plugin which does not require the math plugin. This plugin converts text enclosed by '&' to bold text.
To reproduce this issue with an editor instance it is required to have the cursor inside a word (not after or before the end of the text, I don't know why that doesn't work, if you know why, help is appreciated^^) and paste it from the clipboard. The content will inside the '&' will be marked bold, however if you undo this operation twice, an model-position-path-incorrect-format error will be thrown.
example to paste:
aa &bb& cc
dd
ee &ff& gg
Undoing the operation twice results in this error:
Uncaught CKEditorError: model-position-path-incorrect-format {"path":[]}
Read more: https://ckeditor.com/docs/ckeditor5/latest/support/error-codes.html#error-model-position-path-incorrect-form
Unfortunately, I haven't found a way to fix this issue, and have not found a similar issue.
I know it has to do with the batches that are operated, and that maybe the position parent has to do something with it, that I should cache the position of the parent. However, I do not know how.
Below my code for an example to reproduce:
import Plugin from '#ckeditor/ckeditor5-core/src/plugin';
import Undo from '#ckeditor/ckeditor5-undo/src/undo';
import LiveRange from '#ckeditor/ckeditor5-engine/src/model/liverange';
import LivePosition from '#ckeditor/ckeditor5-engine/src/model/liveposition';
import global from '#ckeditor/ckeditor5-utils/src/dom/global';
export default class Test extends Plugin {
static get requires() {
return [Undo];
}
static get pluginName() {
return 'Test';
}
constructor(editor) {
super(editor);
this._timeoutId = null;
this._positionToInsert = null;
}
init() {
const editor = this.editor;
const modelDocument = editor.model.document;
const view = editor.editing.view;
//change < Clipboard > to < 'ClipboardPipeline' > because in version upgrade from 26 to 27
//the usage of this call changed
this.listenTo(editor.plugins.get('ClipboardPipeline'), 'inputTransformation', (evt, data) => {
const firstRange = modelDocument.selection.getFirstRange();
const leftLivePosition = LivePosition.fromPosition(firstRange.start);
leftLivePosition.stickiness = 'toPrevious';
const rightLivePosition = LivePosition.fromPosition(firstRange.end);
rightLivePosition.stickiness = 'toNext';
modelDocument.once('change:data', () => {
this._boldBetweenPositions(leftLivePosition, rightLivePosition);
leftLivePosition.detach();
rightLivePosition.detach();
}, {priority: 'high'});
});
editor.commands.get('undo').on('execute', () => {
if (this._timeoutId) {
global.window.clearTimeout(this._timeoutId);
this._timeoutId = null;
}
}, {priority: 'high'});
}
_boldBetweenPositions(leftPosition, rightPosition) {
const editor = this.editor;
const equationRange = new LiveRange(leftPosition, rightPosition);
// With timeout user can undo conversation if wants to use plain text
this._timeoutId = global.window.setTimeout(() => {
this._timeoutId = null;
let walker = equationRange.getWalker({ignoreElementEnd: true});
let nodeArray = [];
for (const node of walker) { // remember nodes, because when they are changed model-textproxy-wrong-length error occurs
nodeArray.push(node);
}
editor.model.change(writer => {
for (let node of nodeArray) {
let text = node.item.data;
if (node.item.is('$textProxy') && text !== undefined && text.match(/&/g)) {
let finishedFormulas = this._split(text);
const realRange = writer.createRange(node.previousPosition, node.nextPosition);
writer.remove(realRange);
for (let i = finishedFormulas.length - 1; i >= 0; i--) {
if (i % 2 === 0) {
writer.insertText(finishedFormulas[i], node.previousPosition);
} else {
writer.insertText(finishedFormulas[i], {bold: true}, node.previousPosition);
}
}
}
}
});
}, 100);
}
_split(text) {
let mathFormsAndText = text.split(/(&)/g);
let mathTextArray = [];
for (let i = 0; i < mathFormsAndText.length; i++) {
if (i % 4 === 0) {
mathTextArray.push(mathFormsAndText[i]);
} else if (i % 2 === 0) {
mathTextArray.push(mathFormsAndText[i]);
}
}
return mathTextArray;
}
}
Let me know if I can clarify anything.

Going back to "previous tab" in TabRouter

In react-navigation, I wanted to use TabRouter but on this.props.navigation.goBack() I wanted it to go to the previous tab.
Does anyone have any idea how to go to previous tab, I was hoping it would as simple as setting backBehavior: 'previousTab'.
I have hacked solution here, but its a bad hack as I had to modify the lib files:
I was only able to accomplish this by setting backBehavior to initialRoute, and then on my TabRouter adding a custom getStateForAction like this:
const defaultGetStateForAction = HubNavigator.router.getStateForAction;
HubNavigator.router.getStateForAction = function(action, state) {
switch (action.type) {
case NavigationActions.INIT: {
if (!this.TAB_HISTORY) this.TAB_HISTORY = [];
this.TAB_HISTORY.length = 0;
this.TAB_HISTORY.push({ index:ROUTE_INDEX[INITIAL_ROUTE_NAME], params:undefined }); // i dont think INIT ever has params - C:\Users\Mercurius\Pictures\Screenshot - 1, 2017 10.47 AM.png
break;
}
case NavigationActions.NAVIGATE: {
const { routeName } = action;
this.TAB_HISTORY.push({ index:ROUTE_INDEX[routeName], params:action.params });
break;
}
case NavigationActions.BACK: {
if (this.TAB_HISTORY.length === 1) {
BackHandler.exitApp();
return null;
} else {
const current = this.TAB_HISTORY.pop();
const previous = this.TAB_HISTORY[this.TAB_HISTORY.length - 1];
const default_ = defaultGetStateForAction(action, state, ()=>{
console.log('returning previous index of:', previous.index);
return previous.index
});
default_.index = previous.index;
default_.routes[previous.index].params = previous.params;
return default_;
}
}
}
return defaultGetStateForAction(action, state);
}
What I do is, on NavigationActions.BACK I modify the returned object index to have the previous index, which I hold in the array this.TAB_HISTORY.
However when I start the app, switch from initial tab to tab 2, then from tab 2 back to initial tab... pressing "back" would do nothing this is because activeTabIndex is always set to initialRouteIndex here - https://github.com/react-community/react-navigation/blob/5e075e1c31d5e6192f2532a815b1737fa27ed65b/src/routers/TabRouter.js#L138
So you see in my fix above I pass a third argument to defaultGetStateForAction which returns the index, but I had to modify react-navigation/src/routers/TabRouter.js for this, which is not what I want to do.
Does anyone have any idea how to go to previous tab, I was hoping it would as simple as setting backBehavior: 'previousTab'.
Here is my HubNavigator in case you want to see that:
const ROUTE_INDEX = { Players: 0, Girls: 1, Customers: 2, Assets: 3 };
const INITIAL_ROUTE_NAME = 'Players';
const HubNavigator = TabNavigator(
{
Players: { screen:ScreenPlayers },
Girls: { screen:ScreenGirls },
Customers: { screen:ScreenCustomers },
Assets: { screen:ScreenAssets }
},
{
initialRouteName: INITIAL_ROUTE_NAME,
backBehavior: 'initialRoute',
cardStyle: styles.card,
order: Object.entries(ROUTE_INDEX).sort(([,indexA], [,indexB]) => indexA - indexB).map(([routeName]) => routeName),
}
)

how to project data change into child component with angular2?

i'm trying to build angular2 component which draws chart (using jquery plot)
import {Component, ElementRef, Input, OnChanges} from 'angular2/core';
#Component({
selector: 'flot',
template: `<div>loading</div>`
})
export class FlotCmp implements OnChanges{
private width = '100%';
private height = 220;
static chosenInitialized = false;
#Input() private options: any;
#Input() private dataset:any;
#Input() private width:string;
#Input() private height:string;
constructor(public el: ElementRef) {}
ngOnChanges() {
if(!FlotCmp.chosenInitialized) {
let plotArea = $(this.el.nativeElement).find('div').empty();
plotArea.css({
width: this.width,
height: this.height
});
$.plot( plotArea, this.dataset, this.options);
FlotCmp.chosenInitialized = true;
}
}
}
Component getting chart "data" property as input parameter:
<flot [options]="splineOptions" [dataset]="dataset" height="250px" width="100%"></flot>
So far i managed to make it work as long as "dataset" is static variable.
this.dataset = [{label: "line1",color:"blue",data:[[1, 130], [3, 80], [4, 160], [5, 159], [12, 350]]}];
My problem is to make it work when data came as a promise:
export class App implements OnInit {
private dataset:any;
public entries;
getEntries() {
this._flotService.getFlotEntries().then(
entries => this.dataset[0].data = entries,
error => this.errorMessage = <any>error);
}
ngOnInit() {
this.getEntries()
}
constructor(private _flotService:FlotService) {
this.name = 'Angular2'
this.splineOptions = {
series: {
lines: { show: true },
points: {
radius: 3,
show: true
}
}
};
this.dataset = [{label: "line1",color:"blue",data:null]}];
}
}
For some reason data change cannot project to "flot" component
here is link to plunk
Please help
The problem is
entries => this.dataset[0].data = entries,
because only the inner state of the bound value is changed and Angular2 change detection doesn't observe the content only the value or reference itself.
A workaround would be to create a new array with the same content
this._flotService.getFlotEntries().then(
entries => {
this.dataset[0].data = entries;
this.dataset = this.dataset.slice();
},
In your case an additional event could work that notifies the child component that updates have happended.
Besides Günter's answer, another option is to implement your own change detection inside ngDoCheck() which will be called when your data comes back from the server:
ngDoCheck() {
if(this.dataset[0].data !== null && !this.dataPlotted) {
console.log('plotting data');
let plotArea = $(this.el.nativeElement).find('div').empty();
$.plot( plotArea, this.dataset, this.options);
this.dataPlotted = true;
}
}
I feel this is a cleaner approach, since we don't have to write code a specific way just to satisfy/trigger Angular change detection. But alas, it is less efficient. (I hate it when that happens!)
Also, the code you have in ngOnChanges() can be moved to ngOnInit().
Plunker
As Günter already mentioned, ngOnChanges() isn't called because the dataset array reference doesn't change when you fill in your data. So Angular doesn't think any input properties changed, so ngOnChanges() isn't called. ngDoCheck() is always called every change detection cycle, whether or not there are any input property changes.
Yet another option is to use #ViewChild(FlotCmp) in the parent component, which will get a reference to FlotCmp. The parent could then use that reference to call some method, say drawPlot(), on FlotCmp to draw/update the plot when the data arrives.
drawPlot(dataset) {
console.log('plotting data', dataset);
let plotArea = $(this.el.nativeElement).find('div').empty();
$.plot( plotArea, dataset, this.options);
this.dataset = dataset;
}
Plunker
This is more efficient than ngDoCheck(), and it doesn't have the issue I described above with the ngOnChanges() approach.
However, if I were to use this approach, I would rework the code somewhat, since I don't like how dataset is currently an input property, but then drawPlot() gets the data passed in via a function argument.

google map v3, cannot load marks for firefox 26.0(latest version)

For now we are using google map v3, after we upgraded firefox 26.0, the marks cannot be loaded
I set many alerts and compared them between Chrome and firefox 26.0, I found here is a variable this.ready_ cannot be assigned a value(true) in firefox 26.0 , that is this method cannot be called below
MarkerClusterer.prototype.onAdd = function() {
alert("enter MarkerClusterer.prototype.onAdd!");
this.setReady_(true);
};
MarkerClusterer.prototype.setReady_ = function(ready) {
alert("enter setReady!");
if (!this.ready_) {
this.ready_ = ready;
alert("will enter createClusters_() in setReady_");
this.createClusters_();
}
};
I tried to find where does call onAdd for pinpoint the issue, however, it called by google itself, here is stack call
MarkerClusterer.onAdd MarkerClusterer.js:225
mG VM148:1
anonymous function %7Bmain,places%7D.js:11
Thank you by advance!
Jason
I have fixed this issue, the problem was from MarkerClusterer.js, someone commentted these 2 lines below leads to this issue(just comment them off will be ok), hope it is also helpful for others.
in MarkerClusterer.js
MarkerClusterer.prototype.setupStyles_ = function() {
if (this.styles_.length) {
return;
}
// for (var i = 0, size; size = this.sizes[i]; i++) {
this.styles_.push({
url: this.imagePath_ + '.' + this.imageExtension_,
height: 36,
width: 43
// });
}

Magento Enterprise Tabs - How to select specific tab in link?

I am trying to link to a specific tab in Magento Enterprise. It seems that all of the answers I've found don't apply well to their method. I just need a link to the page to also pull up a specific tab. This is the code they use:
Enterprise.Tabs = Class.create();
Object.extend(Enterprise.Tabs.prototype, {
initialize: function (container) {
this.container = $(container);
this.container.addClassName('tab-list');
this.tabs = this.container.select('dt.tab');
this.activeTab = this.tabs.first();
this.tabs.first().addClassName('first');
this.tabs.last().addClassName('last');
this.onTabClick = this.handleTabClick.bindAsEventListener(this);
for (var i = 0, l = this.tabs.length; i < l; i ++) {
this.tabs[i].observe('click', this.onTabClick);
}
this.select();
},
handleTabClick: function (evt) {
this.activeTab = Event.findElement(evt, 'dt');
this.select();
},
select: function () {
for (var i = 0, l = this.tabs.length; i < l; i ++) {
if (this.tabs[i] == this.activeTab) {
this.tabs[i].addClassName('active');
this.tabs[i].style.zIndex = this.tabs.length + 2;
/*this.tabs[i].next('dd').show();*/
new Effect.Appear (this.tabs[i].next('dd'), { duration:0.5 });
this.tabs[i].parentNode.style.height=this.tabs[i].next('dd').getHeight() + 15 + 'px';
} else {
this.tabs[i].removeClassName('active');
this.tabs[i].style.zIndex = this.tabs.length + 1 - i;
this.tabs[i].next('dd').hide();
}
}
}
});
Anyone have an idea?
I would consider modifying how the class starts up.
initialize: function (container) {
this.container = $(container);
this.container.addClassName('tab-list');
this.tabs = this.container.select('dt.tab');
// change starts here //
var hashTab = $(window.location.hash.slice(1));
this.activeTab = ( this.tabs.include(hashTab) ? hashTab : this.tabs.first());
// change ends here //
this.tabs.first().addClassName('first');
this.tabs.last().addClassName('last');
this.onTabClick = this.handleTabClick.bindAsEventListener(this);
for (var i = 0, l = this.tabs.length; i < l; i ++) {
this.tabs[i].observe('click', this.onTabClick);
}
this.select();
}
Here, I have only changed how the initial tab is chosen. It checks for an URL fragment which is commonly known as a hash, if that identifies one of the tabs it is preselected. As a bonus the browser will also scroll to that element if possible.
Then you only need to append the tab's ID to the URL. For example you might generate the URL by;
$productUrl = Mage::getUrl('catalog/product/view', array(
'id' => $productId,
'_fragment' => 'tab_id',
));
If you've recently migrated from an earlier Magento release, e.g. from Enterprise 1.11 to Enterprise 1.12, make sure the javascript in /template/catalog/product/view.phtml
right after the foreach that generates the tabs gets updated to the 1.12 version:
<script type="text/javascript">
var collateralTabs = new Enterprise.Tabs('collateral-tabs');
Event.observe(window, 'load', function() {
collateralTabs.select();
});
</script>
surfimp's VERY helpful suggestions did not produce the desired opening of the closed tab otherwise. Once this updated javascript was added, clicking on a link to read Review or Add Your Review on the product page, jumped to the Reviews tab, even if the tab had been hidden.
Similar to Zifius' answer, you can modify the initialize function to just take another argument which will be the active tab.
Event.observe(window, 'load', function() {
new Enterprise.Tabs('collateral-tabs', $('tab_review'));
});
and then in the scripts.js (or wherever this class may exist for you)
initialize: function (container, el) {
...
this.activeTab = el;
...
}
Use whatever logic in the template you like to set 'el' to the desired value.
The reason I did it this way is because when I used Zifius' method, the desired tab would be the active tab, but the default tab's content was still displayed.
Had the same task yesterday and as I don't know about prototype much I solved it by adding another method:
selectTab: function (element) {
this.activeTab = element;
this.select();
},
Usage:
var Tabs = new Enterprise.Tabs('collateral-tabs');
Tabs.selectTab($('tabId'));
Would like to know if it's a correct approach

Resources