I am trying to create a network workflow with Cytoscape and AngularJS 1.6
After creating two nodes, user is able to create an edge between them. For this I am using cy.on('cxttap') cytoscape function to detect right clicks on both the nodes and then insert a new edge.
This works fine. Until I added a new view, where the user can see saved workflows from the database. The problem is, if I open the Viewing tab and then go back to creation tab, the cy.on('cxttap') function is called twice, and two edges are inserted on the cytoscape canvas, but only one entry is made in my scope variable. I used to have the same factory for both the views but now I am using different angular factories for each.
If I open switch between these tabs multiple times, the number of times I open the viewing tab, is the number of times the function is called, hence more the number of lines.
Here (https://jsfiddle.net/y47kwpg7/4/) is the snippet with the code for both my views with their controllers. "Creation View" --> "MlalTextWorkflowGeneratorCtrl" and "Viewing View" --> "MlalTextWorkflowViewerCtrl".
(Please forgive me, I don't know how to make it work in a single file)
The factory I have here is for the cytoscape canvas, and have a similar one for another view, but with a different id selection.
Thank you for your help!
You either need to
(1) remove old listeners if you're re-using an instance, or
(2) create a new instance each time you create a new view.
#maxkfranz 's answer guided me to do a little more debugging and finally I found out the problem.
The problem was I had some listener functions that I called from my controller in order to detect events on the cytoscape nodes. And the listeners to fire were stored in a a variable workflowGraph.listeners. you can see the old code []1. The problem was this variable was not emptied anytime my controller was reloaded, because I was calling the listeners from my controller, they were being added to this variable once again. So, one listener had multiple functions to call.
The method I used to solve this was to create a new "listener" type function I had earlier for cytoscape events, which I would call everytime my controller is loaded, to empty the listeners variable for my cytoscape factory.
In cytoscape factory
workflowGraph.reinitialize = function () {
workflowGraph.listeners = {};
};
In controller
workflowCreation is the name of the factory
workflowCreation.reinitialize();
This would simply empty the listeners variable from the factory.
Remember to call this in your controller after cytoscape initialisation and before calling any other listener functions
Hope this helps someone.
Related
Background
I've been working with Polymer for a while. I've been converting from .5 and building new elements for a production app. We are currently using Polymer 1.0.6, and this particular issue is also using jQuery 2.x.x and typeahead.js.
Issue
We have an element that builds a dynamic list of label and inputs provided by a data source. in the ready function we get a list of input data, and set that to a local list variable that is bound to a foreach template to create the labels and inputs.
I was unable to find a Polymer element I really liked for typeahead, for Polymer 1.0. So I defaulted to using typeahead.js. my problem is that I cannot find a lifecycle event or workaround, to call the typeahead function after the dom has processed setting the bound list in the ready function.
Code
The easiest way to demonstrate this issue, was to create a HEAVILY trimmed down version in a jsbin. I know the element looks bad, it was cut down as much as possible to demo the core issue I'm facing.
http://jsbin.com/zivano/edit?html,output
What Have I Tried?
I've tried using the attached event, and while it does process after the ready function, the dom changes from ready have not taken effect. I found similar issues on SO domReady vs ready - Migrating to Polymer 1.0 I've tried both suggestions, the second is still being used in the jsbin, without success.
I have also bound the click event of my inputs to a function calling the typeahead setup code, to prove that if the calls are made after the dom is rendered it will work correctly.
Summary
If update a data bound, local variable in the ready function, is there a lifecycle event I can call that will guarantee that those dom changes will be rendered, so I can make a dom query against those new items? Or is there a work around that will let me call a js function on a dom element, one time after the element dom fully renders?
my problem is that I cannot find a lifecycle event or workaround, to
call the typeahead function after the dom has processed setting the
bound list in the ready function.
I think I had a problem like this . For my problem I found a solution using the following :
var self = this;
window.addEventListener('WebComponentsReady', function(e) {
// imports are loaded and elements have been registered
/*Example*/
console.log('Components are ready');
var p = self.getElementsByTagName("paper-item");//paper-item created dynamically
console.log(p);//can access and use this paper-item
/*Finish example*/
//here you can call typeahead because the dom has been processed
});
Sorry for my English or if I dont understand your question, my English is bad.
The Issue I had was that the data-bound list was populated through an ajax function, which was completed after the attached function, even if I made an async call inside of the attached function, it would still fail because of race conditions.
It's worth noting the answer by Flavio Ochoa, will work. I personally preffered to not have my custom elements add listeners to the Window. So i went a different route.
Since my issues we're predicated on guaranteeing that the bound list was updated, I wrapped the ajax call in a Promise, and added the typeahead init logic to the then statement. That solution appears to be working.
I do have some concerns whether the promise can guarantee that the bound list will have propagated to the DOM by the time the then statement is processed. But so far it has worked consistently. I'll edit this answer if I can prove otherwise.
I have event objects I load into fullcalendar. Each event has a custom resources array field attached showing who the events are for. Loads fine. User can click on the event and eventClick gives me the event with the resources. I open a dialog to edit. After saving changes to the db, I want to update the calendar using updateEvent. The object I use to update contains the full resources array and all appears to update fine.
Here's the problem: Now when I click on the same event to edit again, the event returned by eventClick has the resources array length set to 0. i.e. all the resources elements are missing.
Thought maybe I wasn't updating with the 'original object' so tried removeEvents followed by renderEvent but get the exact same behavior.
What am I missing?
btw, using fullCalendar 1.6. I can just reload the calendar and it's fine, but I don't want to 'cause it flickers and means unnecessary bytes over the wire.
update
So if I use appointment.resources = angular.copy(appointment.resources) then everything works. I'm not clear on why though. Is it because the attached resources were from an angular $resource and when that refreshed the reference to the previous objects point nowhere?
I think it could be a reference problem.
When you create a fullCalendar event, it wraps the object to a new one.
So, if you do something like:
var myEvent = {id: 1, title: "MyTitle", resources:[1,2,3], start:moment()};
$.('#calendar').fullCalendar('renderEvent', myEvent);
It create another object, with some specific properties like backUps, specific methods, etc... You can access this object with:
var fcEvent = $.('#calendar').fullCalendar('clientEvents', myEvent.id);
But now:
fcEvent.resources != myEvent.resources
Anyway, is difficult to know exactly what's the problem without a piece of code. Is possible to create a Plunker?
You can use my public Skeleton for ui-calendar
I have been working on this task off and on for awhile trying to find an optimal solution (other than telling users to disable popup blocking) and am stumped.
Essentially how it works is this (I omit code because there is a lot of it and propriety info):
I have an angular app implemented in an angular and utilizes fullcalendar.js who's content I want to print. Inside my angular controller I have the jQuery that manages the calendar itself (don't hit my fingers with a ruler please :) )
I want a specific set of styles when I want to print the calendar, so I have a directive that prepares all the content to be ported, and then use a uniform angular factory that is used for all printing activities. This uniform factory opens a new window that contains all the new styles I want and, via a callback, "cleans up" the HTML, which in my case I use for porting the HTML content of the calendar over to the new window.
So the flow basically is this: User clicks print button -> click event in calendar print directive is invoked, the directive calls the factory. -> Factory opens a new window and ports the content via the callback from the directive and then calls JavaScript's print() to print the window.
The problem I am encountering is this:
The print works fine on PC and Mac, but on iOS safari, the window does not pop up. I found that the issue was because iOS Safari requires all new window popups to be inside a click event.
To get around this, I thought I would add some modification to the uniform factory to suit my case: I would open a new window in the directive's click event, then pass a reference of that window into the factory, which can then use the reference to add the html content to its body. This introduced another issue with iOS Safari in that it stalls javascript execution of parent windows if a child window is open, so once the new window is open, the generation of the HTML and the calling of the factory is stalled until the user switches back to the parent tab. This is the point where I got stumped. Any suggested way to get around these issues? Or would I be stuck telling the user to disable popup blocking?
once the new window is open, the generation of the HTML and the
calling of the factory is stalled...
Can you change so the generation of HTML and factory call is made before opening the new window? Otherwise it sounds like you need to avoid the popup.
If you want to display another view in the same window, use ngInclude.
You can choose to switch the path of the ngInclude to swap the HTML or combine with ngShow to show and hide the correct parts when the user clicks.
It sounds like you use a factory as a parent scope and if so it should be converted to a controller that acts as a global scope above the different views.
If this is on the right track I could make a plunkr out of it.
Also, check out fullcalendar-ui for a premade directive if you want to go towards best practice.
Good luck!
After ~3 times coming and going from this over the course of a month, I finally figured it out.
Inside my directive that prepares the content to be printed, I generate a new calendar, then call the rest of the code (including the factory that opens a new window) inside a document.ready. Having the code inside the ready check seems to cause iOS Safari to think it is no longer directly inside a click event, so it would sometimes block the new window popup. Removing the document.ready check seems to have made it work, and has no ill effect on the other browsers.
I decided to create a function inside the directive for the factory call and call inside a document.ready if not iOS, otherwise just call it, to preserve functionality for desktop browsers.
I'm making a charting application that allows you to create graphs using a drag and drop interface.
I have highcharts and I'm using the highcharts-ng directive.
This directive watches the title, options, and series. And when a person makes a change, I process them and make changes to the options object. Then highcharts-ng redraws the chart.
The problem I'm finding is that I change a few properties in a row such as options.xAxis and options.yAxis, and whenever I do this the application is lagging a bit because it's launching a redraw for every change.
So what would be the angular way to approach this, while still being efficient?
A potential solution I thought of was to add a flag to the highcharts-ng directive, and have it trigger whenever it's changed. And then just change it after I'm done processing the data. Another potential solution is to listen for a certain event inside the highchart-ng directive, and then trigger the redraw whenever that event is received. But these solutions seem/feel a bit hacky to me.
Angular does its own dirty checking and (ideally always, not but really) rewrites the Angular-controlled sections of the DOM whenever their corresponding view models change. I think that this behaviour is so fundamental to Angular that if you don't like it, you'd either better work around it, or use a different databinding framework.
The workaround I'd recommend is basically what you described in your first option: a view model inside the view model. Have a private variable inside the directive's scope which tracks the changes you're interested in, the ones which happen more frequently than you want to redraw. Then when you're ready to redraw (you'll need your own logic for what determines "ready"...time? a particular kind of change? a particular threshold of change?), update the real view model by setting your private variable back to its original field on the real view model.
Code sketch:
// (inside the directive)
var _options = $scope.options;
// ...
// rapidfire updates happen; save them to _options rather than $scope.options
// ...
// now you're ready to redraw:
$scope.options = _options; // angular now knows $scope is dirty and triggers the redraw
I've assembled a modestly sized application and I am in the process of factoring code to reduce the overall number of maintained lines, as well as performance tuning. The use case that has me posting this question is that I have a button embedded in a menu that invokes (or needs to invoke) a method on a controller that displays a form. This currently is implemented using a direct reference to the specific button control, creating a panel, and putting that panel inside of my viewport.
The question at: ExtJS 4.1 Call One Controller From Another begins to address the issue of best-practices near the end of responses, but doesn't really settle on a base-case that can be reproduced or extended to cover more complex implementations (which is the aim of my question.)
Given the two controllers:
A "main menu" controller.
// controller/Menu.js
Ext.define("App.controller.Menu", {
extend: "Ext.app.Controller",
init: function () {
this.control({
"viewport > mainmenu > button": function (ctl, evt) {
}
});
}
});
A user account controller
// controller/User.js
Ext.define("App.controller.User", {
extend: "Ext.app.Controller",
stores: ["User"],
views: ["user.Edit", "user.List"],
init: function () {
}
});
The Question
What would be the (best) way to implement a crosswise connection between the two controllers to properly delegate the responsibility of responding to a click event on the menu button for "Create a New Account?"
One Possible Solution
Using componentquery I can easily narrow down the focus of the button in the main menu view using a tag property such that the User controller is responding directly to the event:
// controller/User.js
"viewport > mainmenu > button [tag=user.create]": function () {}
An unknown alternative
Or I could possible winnow my way through the object graph to find the reference to the User controller from the Menu controller and invoke it that way.
// controller/Menu.js
// switch on tag case "user.create"
App.controller.User.createUserForm()
The real question
The question that results from all of this is what the "most acceptable" solution is here. One could imagine the use of a third, mediating controller, to "route" requests from controls to controllers but I think that goes against what the remainder of the framework is attempting to do. Using a variation of either of these methods currently works however neither feels completely clean and reliable; or ultimately maintainable long-term (as code gets spread out rather quickly.) Additionally the thought had occurred to us to drop into raw events but we run into the same kind of maintainability issues there.
Some short lines:
A thing that I don't understand is that Sencha Touch has routing but no eventbus where ExtJS has a event bus but no routing... (and there are more points where the MVC implementation differ) Whatsoever, because I am using ExtJS most of the time I created a custom routing to fill this gap for me. Maybe sencha will add this in version 5.
The easiest and quickest solution: use the getController() of the Ext.app.Application controller to invoke the responsible controller from your menu controller.
The (imo) best solution: write yourself a router where each controller register it's routes to and use both; routing and eventbus. This gets really handy if your app have shared components that are used by more than one dev team.