In a Meteor app, a large collection containing 1000 records is published to the client. However users loading the {{loginButtons} will experience a 3-5 second lag as it fully renders only after all the large collection loads.
It appears that the div #login-buttons rendered by {{ loginButtons }} is rendered instantly on page load, but the div #login-dropdown-list is what's taking some time to start rendering. #login-dropodown-list template
The site is using Meteor 0.7.0.1 with Iron Router.
Update
Here's the template code for the accounts-ui-bootstrap-3 dropdown menu that take a few seconds to load after the rest of the page renders. It's just the basic template from the Meteor package, nothing special.
<ul class="nav navbar-nav navbar-right">
{{ loginButtons }}
</ul>
I used to think the problem is due to that this dropdown menu using the Meteor.users collection, so here's my Fast Render route.
FastRender.onAllRoutes(function(urlPath) {
this.subscribe(Meteor.users);
this.subscribe(users);
})
This does not seem to help with the problem. I also found out that Meteor.userId() is already defined when the dropdown menu is still not rendered. The dropdown menu only appears/renders at the point in time pointed to by the red arrow, which is the point where all the collections have loaded.
Furthermore, the div #login-buttons rendered by {{ loginButtons }} is rendered instantly on page load, but the div #login-dropdown-list is what's taking some time to start rendering.
Maybe it's how accounts-ui-bootstrap-3 handles the rendering?
You can use iron-router's waiton on specific routes and loadingTemplate properties to show the user a progress indicator while the subscription gets ready. As seen on https://github.com/EventedMind/iron-router#waiting-on-subscriptions-waiton
Router.configure({
layoutTemplate: 'layout',
notFoundTemplate: 'notFound',
loadingTemplate: 'loading'
});
Router.map(function () {
this.route('postShow', {
path: '/posts/:_id',
waitOn: function () {
return Meteor.subscribe('posts');
}
});
});
Also, https://atmosphere.meteor.com/package/fast-render is a third party package which integates into iron-router and sends down the initial data along with the template therefore making the page appear loaded with data instantly.
There is a good tutorial about this at http://meteorhacks.com/integrating-iron-router-based-apps-with-fast-render.html
I see two other packages that could help you :
Paginated subscription : https://atmosphere.meteor.com/package/paginated-subscription
The idea here would be to set a small limit for the initial load, and when the rest is loaded, change the limit to load the rest
Lazy subscription : https://atmosphere.meteor.com/package/lazy-subscription
The idea here is not to subscribe at all to your large collection in the first time ; only init it like that :
Post = new Meteor.Lazy('post', function() {
Meteor.subscribe('posts');
});
(it does not subscribe to anything)
And then when ready :
Template.some_template.get_posts = function() {
return Post().find({}); //Note that Post is now a function.
};
=> it doe subscribe
The second solution may seem more straight forward, but you can manage it way better with the first one.
Related
I have developed a SAPUI5 master detail app. When I'm clicking on a master item, the corresponding detail page is shown (so far so good). The problem now is, that if I hit refresh (F5) in the browser, the last selected item is loaded (because of the URL parameter).
What I want to achieve is, to display the master list, but no item is selected. Instead a "select an item" page should be shown instead of the item detail page.
I've tried many things such as manipulate the routing, but none of this works. Any ideas on how to achieve this?
By doing that you give up the whole idea of deep-linking which is imho one of the major benefits of the routing. So think twice before doing so.
Anyways you could just reset the hash before initializing the router in your Component like this:
sap.ui.define([
"sap/ui/core/UIComponent",
"sap/ui/core/routing/HashChanger"
], function (UIComponent, HashChanger) {
"use strict";
return UIComponent.extend("sap.ui.demo.nav.Component", {
metadata: {
manifest: "json"
},
init: function () {
// reset the routing hash
HashChanger.getInstance().replaceHash("");
// call the init function of the parent
UIComponent.prototype.init.apply(this, arguments);
// create the views based on the url/hash
this.getRouter().initialize();
}
});
});
BR
Chris
Ember 2. I have a template where I display my model data. And I have a JS script that makes some changes to HTML (inits some JQuery plugins etc), and I need to run it every time I render the view.
I trigger it in the didRender hook of my view. It works fine on the first load. But when I visit the page second time, I can see that plugins are initializing, but in the next moment all changes disappear and the page is as it was initially in the template.
I guess that there is something that looks for changes in the model and re-render the page after it was rendered in the second time, but I'm not sure about it. I tried to listen for other hooks, like didUpdate, but they are not triggering.
What could be a reason of such strange behaviour?
A simple example:
Js:
App.ResumeView = Ember.Component.extend({
didRender: function () {
$('.event h6').text('Hi!');
},
didUpdate: function () {
$('.event h6').text('Hi!');
}
});
Hbs:
{{#each model.work as |work|}}
<div class="event">
<h4>{{work.position}}</h4>
<h5>{{work.name}}</h5>
<h6></h6>
<span class="location">{{work.location}}</span>
<p>{{work.description}}</p>
</div>
{{/each}}
Result: on the first load all H6s say 'hi', then if I go to another page and return to this, it shows 'hi' for a second and then it disappears.
I'm going to bet the problem is because the plugins have initialized already, they aren't initializing again. Try tearing down your jQuery plugins in the willDestroyElement event.
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.
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
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!