Showing, Hiding, then re-showing Layouts breaks Events - javascript

Having trouble showing, hiding, and then re-showing Marionette Layouts. I believe this problem also applies to regular Backbone Views and Marionette ItemViews as well though.
In summary, I have a parent view. When it is initialized, it creates two child layouts that are meant to be used as tab content. The problem is that when tab content from one tab is shown, then content from another tab is shown instead, when the original tab content is shown again, the events do not work anymore.
The child layouts are created in the initialize function of the parent layout and re-used because their states need to be preserved when navigation moves back to them.
Here is a sample application that demonstrates what I am talking about:
Here is a video showing the broken events: Video Link
Thanks so much!

The problem is that you don't create a new istance of your sub-layouts, and just re-use the one you initiate in your main layout. So when you change the content of your region the events get unbinded as part of the close() function of Marionette's View.
You should change your initialize function like that:
initialize: function(){
_.bindAll(this);
// CREATE SUB LAYOUTS
this.tab1Layout = B.tab1Layout;
this.tab2Layout = B.tab2Layout;
},
And call the layouts in this way:
// EVENT HANDLERS
on_show_tab_1_click: function(event){
this.content.show(new this.tab1Layout());
},
on_show_tab_2_click: function(event){
this.content.show(new this.tab2Layout());
}

If you don't want to re-initialize the tab views on every tab change you could call view.delegateEvents() manually:
// views[] is array of initialized tab views
// Swap from displaying views[0] to views[1]
currentTabIndex = 1
this.myRegion.show(views[currentTabIndex])
views[currentTabIndex].delegateEvents()
For preserving state, another option is to render both tabs and simply hide the inactive tab region:
// Assume both regions have initialised views, tab2Region is hidden,
// tab1Region is shown.
// Swap between tabs:
this.tab1Region.$el.hide()
this.tab2Region.$el.show()

Related

In ASP.NET, how can a child window keep track of parent?

I have an application that produces many pages of content. In order to expedite review of the process, I have a linkbutton that opens a child window starting at the page I'm on so reviewers can enter comments. I open the child window in javascript
objCmtWindow = window.open(MyURL, "comments", "width=800,height=600,menubar=no,toolbar=no,status=no,location=no,resizable=yes,scrollbars=yes,directories=no");
objCmtWindow.focus();
The child window has some javascript (an excerpt from a larger script) to listen to an API in the parent window so that it can know what to load
function updtSlide(){
if(window.opener.MyAPI)
{
window.opener.MyAPI.addEventListener("MyEnterBtn", function(e)
{
updtContent();
});
}
}
It works on load to grab the data with the function updtContent as part of the page load. When I click for the next page (MyEnterBtn) in the parent window, the child window is not updating for the next page data it needs to display. Is there a piece I'm missing to poll the parent window? Since the parent can operate independantly of the child, I really can't put the command in the next page button. I know the API is working correctly and emitting next page events.
Thank You
In the js function, the first 'opener' is spelled wrong.

Angular UI Router: Setting autoscroll on header view does not scroll to top

TL;DR: With Angular UI Router, is it possible to autoscroll to a view of the same state that the template displaying it belongs to?
The Setup
I have an Angular 1.4.1 app, using UI Router 0.2.15 and Angular Material 0.10.0.
Example Plunk
There is a parent state called website and three child states: home, foo and bar.
The parent state has a wrapper view, which is displayed via the <ui-view> tag in index.html. It also has two other views, header#website and footer#website, which it displays via the wrapper view template.
Each of the three child states has a main view, which is displayed via the parent state's wrapper view template.
What I Expect
In the parent state's wrapper view template, when I set autoscroll="true" on the ui-view tag for the header#website view, I expect that the page will scroll to the top whenever the state changes. See the accepted answer of this SO question.
What Is Happening
When any of the ui-sref links in the footer is clicked, the page does not scroll to the top.
What I've Tried
If I put autoscroll="true" on the ui-view tag for the main view, it works as expected. This is not what I want, however, because the header is hidden from view even when you navigate to a state from the address bar.
What I Suspect
I think the problem has to do with the fact that header#website is a view that belongs to the website state, and that autoscroll only works for views that normally are displayed on the current template. In other words, normally the header view would be displayed via index.html, not wrapper.html.
The Question
Is my suspicion above correct? Do I need to refactor, or is there a way to make this work as is?
Thanks in advance!
Though I don't advocate DOM manipulation outside of a directive, a quick and dirty solution would be to add a scrollTo method in a .run block of your top level module definition, for ex:
.run(function ($window, $rootScope) {
$rootScope.$on('$viewContentLoaded', function () {
var interval = setInterval(function () {
if (document.readyState == "complete") {
window.scrollTo(0, 0);
clearInterval(interval);
}
}, 1);
});
})
http://plnkr.co/edit/qPqy69o7F9aTjNbUTMGY?p=preview
(borrowed from here https://stackoverflow.com/a/22166553/1516309)
I tried using the $anchorScroll() method of ui-router, but since your links are at the bottom of the page, thats where the page scrolls to, so you dont actually notice it doing anything.

Twitter widget leaking memory on backbone view

I'm building a website using backbone and coffeScript, when i change from one section (innerpage) to another i replace the view rendered on the layout for the new one and call a close method on the old one. Everything seems to work fine, excep that to render the widget again on the home view, i need to call twttr.widgets.load() , and its creating a new widget everytime, but it seems the old ones are being retained, it doesn't matter if i remove the view or the iframe node, or make the container element empty. I've tried detaching the widget before removing the view, store it on a variable and use it to place the widget when the home view is called again, without the need to call the load method, but the iframe element is the only thing that is being kept in the variable (even though before removing the view it has everything on it), i've tried cloning the node, using the iframe contents, everything has failed so far. I can't seem to find a method to unload the twitter widget in the twttr obj, so i'm ending up with a nasty memory leak, it adds up to 3mb everytime it loads the home view, so this is not an option, specially for mobile, i can end up with 100mb being allocated to the ghost widgets.
root = window ? global
root.Backbone.View::close = ()->
#remove()
class Otalvaro.Views.BaseContent extends Backbone.View
initialize: ->
#model.bind('sync', #render, this)
Here is my home view( In this version i'm trying to save the iframe contents, the normal version, does not check for inMemory closes the view with #remove() and removes the iframe directly)
EDIT Note : Removing the view wasn't working here, since on every new instanciation, the widget will create a new iframe (the widget id is even increased), so i'm trying to keep only the homeview on memory, since is better having 1 iframe in memory than 30 ghost iframes.
class Otalvaro.Views.Home extends Otalvaro.Views.BaseContent
template: root.template('homeTemplate')
initialize: ->
super
#inMemory = no
closeTwitterWidget: ->
$('iframe').unbind().remove()
close: =>
#$el = #$el.clone()
#el = #$el[0]
#iframe = #$el.find('iframe').contents()
#inMemory = yes
render: (callback)->
if(#inMemory)
console.log '#iframe', #iframe
else
console.log 'not in memory, doing normal rendering'
if _.isEmpty(#model.toJSON())
console.log 'model is empty, fetching it now'
#model.fetch()
else
console.log 'model fetched, now rendering'
#$el.html( #template(#model.toJSON()) )
# With out the delay, the widget won't load everytime
_.delay( ->
console.log 'firing twttr.widgets.load()'
root.twttr.widgets.load() # <- Guilty of all charges
, 630)
if callback? then callback(#el)
this
This is part of the code of the layoutView, wich renders the views on route change:
class Otalvaro.Views.MainLayOut extends Backbone.View
el: '#mainContent'
closeOldViews: ->
if #oldViews?
for view in #oldViews
console.log 'view to be closed', view
view.close()
###
# Takes an array containing one or more view instance as argument, adds a fadeOut fx to hide the current content
# then it renders each view instance from the array and extracts its node element (el) and pushes it into an array
# that is later (after the fadeIn completes) added as the html content of the layoutView
###
show: (views)->
#fadeOut()
delay = 500
viewNodes = []
for view in views
node = view.render().el
viewNodes.push(node)
_.delay(
_.bind ->
#closeOldViews()
#$el.html(viewNodes)
#fadeIn()
#oldViews = views
, this
delay)
Router Handler for home, using an already instanciated view, (also tried instanciating the home view on route change, but didn't help)
home:->
#mainLayout.show([#homeView])
null
Edit: Added ->Home Template
<script id="homeTemplate" type="text/x-handlebars-template">
<blockquote id="homeQuote">
<p>Some Quote</p>
<footer>Catalina Otalvaro</footer>
</blockquote>
<div class="twitterFeed fadeIn">
Tweets by #kataotalvaro
</div>
</script>
I've read this couple of topics on the tiwtter developers forum, but they got no replies and i'm not very experienced with this, so if anybody outthere has any advice on how should i solve this , i really appreciate it, thanks for taking your time in reading my question :)
https://dev.twitter.com/discussions/30128
https://dev.twitter.com/discussions/5957
Your close method in Otalvaro.Views.Home is overriding the base close method, which means that every time you close that home view, you're not actually removing the view and, further, you're cloning the element:
#$el = #$el.clone();
It could be that the closeTwitterWidget would help with some of that by removing the iframe, but I don't see where that's being called.
Try changing the close method in your home view to just #remove() and see how it goes.
Also, is the Twitter widget definitely loading inside the element for your home view?
I managed to solve this issue, by storing the widget (<div> inside the widget iframe) on a property of my view, to get the widget is not as straightforward as it is to store any node from your document, and the storing must be made before the iframe container (main container) is empty or the iframe will be empty too. Now the main issue on this path, was to reappend the widget to the view, but i found out is not possible (or it seems that way) to add html content to an iframe that is not currently on the DOM, since that was my case because the iframe is actually just in the template of the view, i decided to attach the widget to a div, then all i needed was to take the iframe styling and add it to my main style (with some modifications), now when i rerender my home view is taking the elment from memory and there is no memory leak, i get around 6mb tops when navigating and never goes up, using the widget.load() it went as high as 50mbs. I don't think this is the ideal solution, its actually a hack, twitter should provide a way to unload the widget not just the load method. I hope this helps if somebody is on the same spot. Thanks #glortho for taking your time to answer =)
Here is the modified home view
class Otalvaro.Views.Home extends Otalvaro.Views.BaseContent
template: root.template('homeTemplate')
initialize: ->
super
#inMemory = no
#memoryRender = no
close: =>
unless #inMemory
#$iframe = $( $('.twitter-timeline').contents().find('body').html() )
#inMemory = yes
render: (callback)->
unless #memoryRender
if(#inMemory)
#$el.find('.twitterFeed').html(#$iframe)
#memoryRender = yes
else
console.log 'not in memory, doing normal rendering'
if _.isEmpty(#model.toJSON())
console.log 'model is empty, fetching it now'
#model.fetch()
else
console.log 'model fetched, now rendering'
#$el.html( #template(#model.toJSON()) )
_.delay( ->
console.log 'firing twttr.widgets.load()'
root.twttr.widgets.load()
, 630)
if callback? then callback(#el)
this

Backbone.js using el doesn't work but using $('selector') does. Why?

So, I admit that it might be tricky for somebody to follow my design and build patterns. But I will try best to describe the issue, although I feel this might be easier than getting you to understand my project :) But hell I've spent 3 days trying to figure this one out.
Summery of technical layout
I have a global view (lets call this GV, for global view), everything goes through this template.
I have navigation, this can call one of the 5 separate views on click.
Each of these views first uses the same view (DV for defaultView) which then extends this view and loads details from its own JST (OV for OwnView)
The problem is that, when clicking to load an OV the element that it is bound to load into (target) has not yet been rendered to the page. It is however available in the DOM, and in fact updates the DV with its OV but does not render to the page.
Brief overview - To give you a idea of how my views are set up and what extends what
GV - Loads: login, Home, Global Navigation
DV - Loads: the navigation for this section.
DV extends GV: SD.defaultView.extend
OV - Loads: h2, icon for the option and buttons
each OV extends DV so that I can keep all the click events in one view and not have to paste code all over: SD.defaultSexView.extend
The problem
OV loads into an element that is inside DV. GV and DV all load perfectly. I can click from, login -> home -> navigation -> through which would then load in the DV. This is where the interactions break down. All that is loaded is whatever was inside the JST for DV
Build
Just to give some background information:
I am using JST's to precompile all of my templates.
main.js loads all of my dependencies via require. It also handles my routes
sdFunctions.js has globals and other important things. But the most important is 2 global default views.
1: The GV that everything goes through initially
2: The DV that all options go through (The main navigation menu)
Outline
I know that for OV to load it must have the element that its loading into available. This is el: 'sexdetails'. This element gets loaded from the DV. So I realise that DV needs to be in the DOM for the OV to load. Otherwise it has no element to load into.
This is a console load. page is from the GV sexdetails gets loaded in from the DV and this is where the OV gets loaded into. all these consoles are out put in loading order. So from the top to the bottom.
The bottom seems to be seen in the last console.log which is greyed out as the element has been built with all the correct information. But for whatever reason it not output onto the page.
JS
Now the exciting parts :p
DV - This is the second defaultView, the one that handles the menu and where the click events are bound.
SD.defaultSexView = function(){
//set up homeview
var sexView = SD.defaultView.extend({
el: 'page',
jstemplate: JST['app/www/js/templates/sex/sexTemplate.ejs'],
events:{
"click sexoptions > *" : 'changeSex'
},
changeSex: function(elem){
var me = elem.currentTarget.localName;
$('.selected').removeClass('selected');// #update selected from bottom navigation
$(me).addClass('selected');
SD.pageLoad(elem); // #Call function that will re-render the page
},
render: function () {
var compiled = this.jstemplate();
this.$el.html(compiled);
}
});
SD.DSV = new sexView();
SD.DSV.render();
return sexView;
}();
Own View - each of the 5 files has one of these
//set up homeview
var wank = SD.defaultSexView.extend({
el: 'sexdetails',
jstemplate: JST['app/www/js/templates/sex.ejs'],
data: {
url: SD.HTTP+'stats/add',
sextype: 'wank',
header: 'SOME LOUD STRING!!!',
image: '/img/path.jpg'
},
render: function () {
c($('sexdetails'));
c(this.$el);
var compiled = this.jstemplate(this.data);
this.$el.html(compiled);
}
});
return wank;
All the code in full working order can be found here: http://m.sexdiaries.co.uk/ - don't be put off by the URL, I assure you its SFW. Everything is done with cartoons and in no crude.
Interestingly if I update the code to use: $('sexdetails').html(compiled); it will indeed display correctly, but none of the events will be bound correctly.
Sorry there is so much to take in, I'll be very very impressed if anybody takes the time out to read or understand this :)
I couldn't look into full code. But from my past experience I can say that you are rendering view on a element which might have been removed from page DOM. When you update this.$el.html() of a view, it will take away element from DOM but still maintain elements if referred in a closure. I can see that you are passing
SD.pageLoad(elem)
to some function which does rendering. Since elem is an object, it's passed by reference. even after you update view's template later with
var compiled = this.jstemplate();
this.$el.html(compiled);
element that is send to rendering function remains in memory, but not in DOM, so any element rendered on this element will not be displayed on page. Also you are using $ inside a view, but you should be using this.$. this will make sure that, code from a view will never change elements outside that view.
After taking a look at your code, I noticed that in your "Wank" view, that you are setting el to sexdetails, which doesn't fly. I wasn't able to dig into the details of why this is, but I assume it has something to do with lack of DOM specificity.
Setting el to "body content page" however, allowed to view to render without any issues, as this allowed to view to hook directly into the body of the page and lets the render function do the rest of the work for you.
Hope this helps!

How to store a backbone.js view and then reuse it again

I am creating a web application using backbone.js. I have two tabs. Tab 1 is a backbone.js view and tab2 is another backbone view (of the same kind).
Both the views are loaded to the same div element in the page. Currently what I do is, when user change the tab, I detect it using the backbone router. Then router does a check to see if a view is already there, or should it be created.
I created a object manager object, who store the view object based on a key (key is the tab name)
App.ObjectManager.getContentView = function (tabId) {
return App.ObjectManager.ContentHash[tabId];
};
App.ObjectManager.setContentView = function(tabId, view) {
App.ObjectManager.ContentHash[tabId] = view;
};
App.ObjectManager.hasContentView = function(tabId) {
if(App.ObjectManager.ContentHash[tabId]==undefined) {
return 0;
}
else{
return 1;
}
};
Here is what I do to create and store the view to list object. This code is called every time a tab is clicked, and router detects the change.
$("#myDiv").html("");
if(App.ObjectManager.hasContentView(tabId)==0) { //View is not in the lookup table
console.log("View is not created yet. So we need to create");
content = new App.MyBackboneView(); //create a new view
App.ObjectManager.setContentView(tabId, content); //Store the view
}
else {
console.log("View is already created..So we read it and show it");
content = App.ObjectManager.getContentView(tabId);
}
//Now, we add the view to the main div
$("#myDiv").append(content.el);
The code above works by showing the view to the myDiv. and I can see the values of text boxes and radio buttons, etc being shown.
The only thing - event handling don't work. So, if I have a button in the view that is added an event handler, the event don't get called when the view is rendered from the saved object. First time when the view gets shown, click event is handling.
For eg, I have this button in the view "Say My Name!"
events: {
"click #say": "sayName"
},
sayName: function() {
console.log("I click say Name");
}
First time the view gets loaded, when I press the button, console show me the log. When I come back to the tab again, and this time view is shown from the list object, the click event don't work.
Is my code having any problem ?
Hope you can give me some way how to store the view and I can reuse the view again.
Thanks.
When creating a view you empty myDiv content
$("#myDiv").html("");
I think this action automatically removes all bindings that you set in your View earlier. It's actually quite a common problem with Backbone - this framework sets event listeners to dom elements in the view.
Probably easiest way to deal with this issue is to create a parent View for #myDiv as a whole and set events in there.
View structure may look like that:
TabsView (set events here)
|
- Tab View 1 (render content)
- Tab View 2 (render content)
I'm having the same problem, I have several "tab views" that show aggregated data, something like "my messages", "all messages", "all users" etc. I store views' objects in a hash, say 'views'.
Yes, indeed as Kane Cohen has mentioned:
$('#myDiv').html('') or $('#myDiv').$el.empty()
destroys all bindings... What worked for me is rather using "detach" as it doesn't destroy bindings (http://api.jquery.com/detach/), suppose previous displayed content was 'All messages', and user clicks on 'My messages':
views['All messages'].$el.detach()
$('#myDiv').append views['My messages'].el
I use coffescript...
P.s. Hope this helps, allthough I'm not satisfied with this approach...

Categories

Resources