$scope.$apply() creating multiple instances of a new scope - javascript

I'm working with a provider and factory set up in angular, and in the factory is where I do all the heavy lifting of generating the templates, creating the instances, and doing all of the animations. The provider creates a very nifty slider menu from the left.
Problem
What's happening though, is that after the first instance of the slider menu, menu options start to double themselves. So I'll have the original 5, then 10, then 20, then 40... I have found a solution where we start with a null instance, and check if that instance is null, if it is null render the menu. So that forces it to only continuously render the initial 5, but then if we dynamically change the menu we won't ever see those changes and that is not what we want.
Fiddle
https://jsfiddle.net/Mr_Pikachu/chdbxt1h/351/
Broken Code
This is the chunk of code that I am most focused on, as it is the bit that is causing us the issue.
backdropScope.close = function(){
$animate.leave(menu).then(function(){
backdrop.remove();
//menuOpts.scope.$destroy();
// menu_rendered = null;
menu.remove();
});
}
// menustack object
$menuStack = {
'open': function(menuOpts){
menuOpts.scope.main = menuOpts.menu.main;
if(!menu_rendered) {
menu_rendered = menu_template(menuOpts.scope);
}
if(!backdropRendered) {
backdropRendered = backdropTemplate(backdropScope);
}
menuOpts.scope.$apply(function(){
$animate.enter(backdropRendered, body).then(function(){
$animate.enter(menu_rendered, body);
});
});
}
};
List of Attempted Fixes
setting menu_rendered = null in the $animate.leave() will work on the first instance, and re-render the menu properly, but then the backdrop instance won't recognize a click event
Using menuOpts.scope.$destory(), but it did absolutely nothing
Using the current solution of menu_rendered check. It is not optimal and looking for a solution that allows the use of dynamic content.

Updated Fiddle: https://jsfiddle.net/chdbxt1h/355/
I moved the angular.element calls into the body of the $menuStack.open method. The menu content does not get duplicated in repeated exposures. Presumably, this is because the DOM Node is created anew on each open, and garbage collected cleanly on leave and/or remove.
Both the background (menu-overlay) and menu are re-created on each open, so this should honor changes in the source menu data, though possibly not while the menu is open.

Related

IntersectionOberserver with newly appearing Elements

In a react project we have an element "stickyButton" that is fixed on the bottom of the viewport on mobile. It is supposed to be displayed as long as none of some other buttons are visible. So, we try to use IntersectionObserver to check if these buttons are visible
useEffect(() => {
let stickyButton = document.getElementById("stickyButton");
function handler(entries) {
entries.forEach(function(entry) {
if (entry.isIntersecting) {
intersecting.push(entry);
} else {
intersecting.pop();
}
});
if (intersecting.length > 0) {
stickyButton.classList.add("hide");
} else {
stickyButton.classList.remove("hide");
}
}
let options = {
threshold: 0.05
};
let observer = new IntersectionObserver(handler, options);
document.querySelectorAll('div.buttons').forEach(button => observer.observe(button));
return () => observer.disconnect();
},[visibleHosts]);
Each time a button becomes visible we add it to an array "intersecting" and each time one becomes invisible again, we remove one from this array. As long as one button is visible, we hide the sticky button, when none are visible we show it.
Works fine so far, but alas, at the end of our list we have another button that loads eight more entities. This changes the variable "visibleHosts", which gets our useEffect to reinitiate. So far that's what we wanted.
But here is the problem: the new entity could very well be added by react in the viewport so it would count as visible. But since we never enter the handler function for it, it is never added to our "intersecting" array. So we suddenly have a difference between the number of elements in the array and the number of elements actually visible. And of course from there on out the sticky button no longer behaves as expected.
Is there a way to check all observed elements for visibility beyond doing it by hand (which would render the usage of IntersectionObserver pretty much moot?)
After a bit of trial and error we found the solution by not just pushing and popping the entries, but instead pushing entries.target if it is not already in the list and instead of popping filtering only entries.target from the list. That worked way better then our previous version.

J-Query Context Menu updating via Knockout call

We are implementing a rather complex single page application and decided to use the "Jquery-context-menu" toolbox. However, we have a fundamental question, and after two days of searching, and reading stuff on the web we are somewhat out of ideas.
The basic question is: If and if yes, how, it will be possible to access the update function (i.e the disable function) from outsite the menu, while the menu is still upon?
The scenario:
We are implementing a game. We use the "jquery context menu" as a context menu for an icon that can be activated by the user (think of a rpg type of icon). After activating it via a click it will be deactvitated automatically (after a few seconds) and then be ready for reactivation again after a while. What we what is that the menu is capturing this state of the icon, by enabling or disabling the menu, while the menu is held open.
There is one example of how to change the visibility of a menu item, via a button press in the same menu, as can be found here:
http://medialize.github.io/jQuery-contextMenu/demo/disabled-changing.html
However, we aim to update the menu from outsite (via a knockout subscribe call). So we would like to do something like:
myknockoutobservable.subscribe(function(newValue){
correctRefernceToThis.data('disableItem1', newvalue)});
while the disabled function looks somehitng like:
disabled: function(key, opt) {
return !this.data('cutDisabled')};
OR, if this does not work we maybe could directly call the update fucntion
myKnockoutobservable.subscribe(function(newValue){
$.contextMenu.op.update(correctRefernceToOpt,correctRefernceToroot)});
and then querying the knockout observable in the callback
if (!myKnockoutobservable) {
return true;}
else {
return false;
The main problem seems to be that we don't reference the context correctly, so we don't have a handle on the correct this, opt, to root, variables, from outside of the Jquery context menu (At least that is our current opinion). We would be very happy if someone could help us finding a solution, or even some good ideas what to try (what we haven't yet).
Thanks you for your help! I understand your approach and it is indeed exactly what we have done:-) I might have not been so clear on this issue but this is the current code od the disabled callback.
disabled: function(key, opt) {
if (!self.item._blocks.Feature._processedStack().canBeActivated()) {
return true;
}
else {
return false;
}
}
This works such that when the menu in closed and reopned again the state is updated. However the update is NOT working while the menu is still open because nothing is triggering the menu to update to the new value.
At the moment we are trying to solve the issue with a different library, will keep you updated.
best, Caspar
You won't subscribe, you'll just have an observable in your viewmodel that you set to true or false, and the menu item will toggle in response. The disabled member of your menu item will look like this:
disabled: function() {
return myobservable();
}
As James Thorpe commented, you'll want to create a custom binding handler to set up your context menu.
It sounds like you're working with several unfamiliar tools. Here is a Fiddle with a minimal example: http://jsfiddle.net/sv3m7ok8/
Update
It occurred to me that since the context menu doesn't bind to a single element, but uses a selector, it makes more sense to do the binding on the body tag. So an updated example: http://jsfiddle.net/sv3m7ok8/1/
Updated again
I now understand that you are specifically trying to get the menu item to enable/disable while the menu is open (and that it doesn't do that normally). I had to go under the covers to get at the menu item node, and hook up a subscription to set the disabled class on it.
init: function (element, data, allbindings, data) {
var dynamicDisable = false;
$.contextMenu({
selector: '.context-menu-one',
callback: function (key, options) {
var m = "clicked: " + key;
window.console && console.log(m) || alert(m);
},
items: {
"edit": {
name: "Clickable",
icon: "edit",
disabled: function (opt, key) {
if (!dynamicDisable) {
var node = key.items.edit.$node[0];
data.disableMenu.subscribe(function (dis) {
$(node)[dis ? 'addClass' : 'removeClass']('disabled')
});
dynamicDisable = true;
}
return data.disableMenu();
}
}
}
});
}
My fiddle sets an interval to toggle disableMenu. You can watch the menu item become active and gray.
http://jsfiddle.net/sv3m7ok8/3/

Can't seem to cleanup detached DOM elements

I'm using jquery-ui Tabs and I'm having a problem that occurs when a tab has been removed. The tab appears to be removed, along with its content div but when you take a look at the heap in Chrome DevTools Profiles (after a tab has been removed) you'll see that the tab li and div elements are still present, but detached. Over time, the repeated addition/removal of tabs causes these elements to accumulate. For example, if you add a tab 10 times, there will be 10 detached div elements and 10 detached li elements showing up in the heap snapshot:
I have the following views:
TabLabel = Marionette.ItemView.extend({
template: "#tab-label",
tagName: "li",
events: {
"click .ui-icon-close": "closeTab"
},
closeTab: function(e) {
this.$el.contents().remove();
this.model.collection.remove(this.model);
$("#main-container").tabs("refresh");
this.close();
}
});
TabContainer = Marionette.ItemView.extend({
template: "#tab-container",
tagName: "div",
onBeforeRender: function() {
this.$el.attr("id", "div-" + this.id);
},
onClose: function() {
// This removes the region that contains the container
App.layout.removeRegion(this.containerRegion);
}
});
TabLabels = Marionette.CollectionView.extend({
tagName: "ul"
});
TabContainers = Marionette.CollectionView.extend({
tagName: "div"
});
The views are instantiated like so:
tabs = new TabsCollection(); // Create a new collection instance
var tabLabelView = new TabLabels({
itemView: TabLabel,
collection: tabs
});
var tabContainerView = new TabContainers({
itemView: TabContainer,
collection: tabs
});
As you can see, the views both refer to the same collection; each model in the collection can be said to represent a single tab (it just so happens that the model contains the necessary information to satisfy jquery-ui tabs). The views are shown in a region via Marionette.Layout... pretty standard. Adding a tab is accomplished by clicking a link in the tab container; all this does is adds another model to the collection and then calls tabs("refresh") on the main container, which makes the new tab appear. Removing a tab is accomplished by clicking an "X" icon in the upper right-hand corner of the tab.
I've spent a lot of time trying to track down this leak and I can't figure out if it's a problem in my code (the way I'm closing views/models/etc. perhaps?) or if it's a problem in the jquery-ui tabs plugin.
Thoughts? Thanks in advance!
Update #1 As requested, here is a jsfiddle demonstrating the problem -- just close the tabs and you'll see that detached elements are left behind.
Also, a screenshot:
Update #2 This appears to be a leak in the jquery-ui tabs widget. The same behavior occurs in the widget demonstration on the jquery-ui website. I added a few tabs and then closed them out and sure enough, they persisted:
I've tested this with the latest (at the time of this writing) version of jQuery UI (1.10.3) and the previous version (1.10.2).
Is there a reason why you use this.$el.contents().remove() instead of this.$el.empty()?
Using this.$el.empty() in that jsFiddle of yours seemed to remedy the detached NodeList.
A few notes memory profiling:
watch http://www.youtube.com/watch?v=L3ugr9BJqIs
Use the 3 snapshots method, 2 is not enough. What does that mean?
Start fresh, incognito mode and refreshed
Take snapshot (forced GC will happen every time you take snapshot)
Do something, take snapshot (gc)
Do something similar, take snapshot (gc)
Compare snapshot 2 with snapshot 1, find deltas with +
Choose Summary and Objects allocated between Snapshots 1 and 2 for snapshot 3
Look for stuff that you found comparing 2 and 1 that shouldn´t be there. These are the leeks
I have found cases where jQuery seems to leek because they save the current jQuery object in .prevObject when doing some operations like calling .add(). Maybe that call to .contents() do some funky magic.
So I eventually fixed this problem (quite some time ago) by doing two things:
Ditching jQueryUI in favor of Bootstrap
Changing the closeTab function (see the original jsFiddle) to the following:
closeTab: function (e) {
e.stopPropagation();
e.preventDefault();
this.model.collection.remove(this.model);
this.close();
}
In that snippet, stopPropagation and preventDefault are really what stopped this element from remaining detached (and accumulating on subsequent adds/removes) in the DOM. To summarize: this problem was created by not calling stopPropagation/preventDefault on the event object after it was triggered. I've updated the jsFiddle so that it correctly removes the tab elements and for posterity, here's a screenshot of the result:
As you can see, none of the tab elements are remaining detached after clicking the "X" to close them. The only detached elements are the ones that come from jsFiddle's interface.

Set the scroll position in WinJS.UI.ListView

I want to scroll my ListView on a specific item automatically. The ListView must auto-scroll to an item from his index.
listView.ensureVisible(itemIndex);
But it's not working. Another alternative:
yourListView.currentItem = { index: 8, hasFocus: true, showFocus: true }
And it's failed also.
How can this be solved?
Generally you have to wrap your call to ensureVisible(index) in a call to msSetImmediate. Not sure exactly why this is the case, probably a bug, but works for me. Example:
msSetImmediate(function (){ listView.ensureVisible(4);} );
If you look at the documentation for setImmediate (msSetImmediate being a Microsoft specific implentation), the function is described as:
Requests that a function be called when current or pending tasks are complete, such as events or screen updates.
This does make a bit of sense as it sounds like it ensures that all list view animating etc is completed before making your call to ensure an item is visible.
See this thread for a related post: http://social.msdn.microsoft.com/Forums/en-US/winappswithhtml5/thread/2f11e46f-9421-4e31-93d3-fca06563ec41/
I might have an answer for you. You can't set the scroll position of a ListView immediately because the layout for the ListView has not been done yet and so attempting to scroll it to a position is futile. So you have to wait until the ListView gets to that state where its layout has been calculated. Here's how...
myListView.onloadingstatechanged = function () {
if (app.sessionState.homeScrollPosition && myListView.loadingState == "viewPortLoaded") {
myListView.scrollPosition = app.sessionState.homeScrollPosition;
app.sessionState.homeScrollPosition = null;
}
};
You can see this in context by looking at the /pages/home/home.js file in my open source codeSHOW project.
Hope that helps.

How can these generic classes be distinct between different divs?

I'm creating a notification system for a game to work similar to how notifications might work in a phone.
The notifications are all created initially, hidden, and later the game is supposed to "activate" certain ones from in-game triggers.
I'm running into problems when trying to keep the notifications separate in terms of their classes. Each notification starts off as a small rectangular box with only the title visible. Upon clicking, the notification expands and the description becomes visible.
Right now, clicking a notification does expand that notification and display its notification, but any other notifications also show their descriptions as well.
Example code:
var NotificationItems = new Array();
scope.registerNotification = function(title, description)
{
//add it to the array
NotificationItems.push(new scope.Application(title, description));
var $NotificationContainer = $("#NotificationContainer");
$NotificationContainer.append('<div class="Notification" title="'+title+'"></div>');
var $thisNotification = $NotificationContainer.children('.Notification[title='+title+']');
$thisNotification.append('<div class="NotificationTitle">'+title+'</div>');
$thisNotification.append('<div class="NotificationDescription">'+description+'</div>');
$(".NotificationDescription").hide();
$thisNotification.click(function()
{
$(this).toggleClass('expanded');
$('.NotificationDescription').slideToggle('slow');
});
}
How can I get the .NotificationDescription to be uniquely recognized for each notification?
You could try the .children() method: jQuery docs for children method
$thisNotification.click(function()
{
$(this).toggleClass('expanded').children('.NotificationDescription').slideToggle('slow');
});
Just find the right one for the clicked element:
$thisNotification.click(function()
{
$(this).toggleClass('expanded');
$(this).find('.NotificationDescription').slideToggle('slow');
});
You can chain the calls if you like:
$thisNotification.click(function()
{
$(this).toggleClass('expanded').find('.NotificationDescription').slideToggle('slow');
});
You might want to try out event delegations.
$('#NotificationContainer > div.Notification').live('click',function()
{
$(this).toggleClass('expanded').find('div.NotificationDescription').slideToggle('slow');
});
This way you only need to attach the event once (on init), and a single event handles all the notifications.
You also should add all your html at one time:
var $NotificationContainer = $("#NotificationContainer");
var $Notification = $('<div class="Notification" title="'+title+'"></div>');
$Notification.append('<div class="NotificationTitle">'+title+'</div>');
$Notification.append('<div class="NotificationDescription">'+description+'</div>');
$NotificationContainer.append($Notification);
notice the subtle difference, we're building the elements in jquery rather than the dom, and append them all at once.

Categories