Emberjs: Loading data for other route - javascript

Resolved itself when updating to emberjs-rc.2. See answer.
In my application, I have 2 pages with wizard-ish functionality. On the first page, the user will provide some data, and on the next page the user will select an item in a list built based on the data on the first page. What I'm trying to do is to start loading the data for the second page as soon as the required data on the first page is valid, even if the user is still on the first page. This because building the list server will take some time, an I'd like to have the data ready when transitioning to the next page.
The two pages / routes are nested under the same resource.
My routes:
App.YayRoute = Ember.Route.extend({
events: {
dataValid: function() {
this.controllerFor('yaySecond').send('loadStuff');
}
}
});
App.YaySecondRoute = Ember.Route.extend({
events: {
loadStuff: function() {
this.controller.set('stuff', App.Stuff.find());
}
}
});
In YayFirstController, I'm observing relevant data and doing this.send('dataValid') when it is. The top route YayRoute picks this up ok, and triggers loadStuff on the second route. App.Stuff.find() looks like this
find: function() {
var result = Ember.A([]);
$.getJSON(url, function(data) {
// populate result
});
return result;
}
Everything is being run when intended, but my problem is that stuff on YaySecondController is not populated when called from loadStuff. If I add controller.set('stuff', App.Stuff.find()) contents of loadStuff to setupController on YaySecondRoute, the data will load OK. But then I lose the coolness with the data being loaded as soon as possible. Any ideas?

So, this resolved itself when updating to emberjs-rc.2 today. I'm still new to ember, so I have no idea what was going on. Anyway, is this the correct way to do this? It seems slighty cumbersome.

Related

How to get dynamic view to update properly when scope variable changes

I am developing an Angular SPA and on one view there is a $scope variable that determines the majority of the content for that page. If the user clicks on a different link in a menu, the parameters will be passed through the url, and the scope variable gets updated. However, when doing this, the view does not get updated. I generally don't ask questions on here, but I have tried what seems like everything, and can't figure out why the view isn't being updated. Here is what the code looks like:
html
<div ng-init="loadTopicView()">
<h1 ng-bind="selectedTopic.title"></h1>
<p ng-bind="selectedTopic.body"></p>
</div>
js
$scope.loadTopicView = function() {
var selectedTopic = urlParams.index;
$scope.selectedTopic = $scope.topics[selectedTopic];
}
Initially, I thought because of the way it was being called the $scope.selectedTopic wasn't getting the correct value, like the url didn't have the value yet. $scope.topics is an array of objects and which ever link the user clicks on will pass an index through the url and then assign the correct object to the $scope.selectedTopic.
After trying many things, I even tried running it over and over to see if it would make a difference using window.setInterval but it didn't change the fact that for some reason the html isn't updating.
I've seen issues like this before, with boolean values and ng-show but I can't figure this one out. Any help would be deeply appreciated.
More Info as requested:
Here is the html for the side nav that selects what content will be showed on the page
<ul class="sidebar-nav">
<li ng-repeat="topic in topics">
</li>
</ul>
Here is the javascript for $scope.loadPage
$scope.loadPage = function(path, passedParams) {
// at this point, `path` is going to the /topics view, and the `passedParams` has the id for the topic to load, lets say it is the integer: 5.
if(path === $location.path() && path == '/topics') { // If you're switching topics but staying on the topic page
if (!passedParams) {
$state.reload(); // Meaning just refresh the page, no topic was selected.
} else {
$location.path(path).search({params: passedParams});
$rootScope.$emit("CallingTopicUpdate", {});
}
} else { // This works fine in loading the correct topic, it is only when you are already on the topic page (so the above if), that it won't load the scope changes.
if (passedParams) {
$location.path(path).search({params: passedParams});
} else {
$location.path(path);
}
}
};
oh and this is in the same controller as the $scope.loadTopicView
$rootScope.$on("CallingTopicUpdate", function(){
$scope.loadTopicView();
});
Using $emit and $on so that when a different topic is selected while on the topic page, it will run this function again. But this is where, it updates the $scope.selectedTopic but the change isn't made manifest in the data that is loaded on the screen.
As a result, you can see the params (an integer representing a topic), change in the url, but the page view that is dynamically binding data depending on the topic doesn't switch to the item you selected until you select another topic. In essence, if you keep selecting topics, the url is right, and the view is one topic behind the url.
$scope.loadTopicView = function(param) {
var selectedTopic = param ? param : 1;
$scope.selectedTopic = $scope.topics.filter(function(value){return value.id==selectedTopic; });
};
$scope.loadPage = function(path, passedParams) {
// at this point, `path` is going to the /topics view, and the `passedParams` has the id for the topic to load, lets say it is the integer: 5.
if(path === $location.path() && path == '/topics') { // If you're switching topics but staying on the topic page
if (!passedParams) {
$state.reload(); // Meaning just refresh the page, no topic was selected.
} else {
$location.path(path).search({params: passedParams});
$rootScope.$emit("CallingTopicUpdate", passedParams);
}
} else { // This works fine in loading the correct topic, it is only when you are already on the topic page (so the above if), that it won't load the scope changes.
if (passedParams) {
$location.path(path).search({params: passedParams});
} else {
$location.path(path);
}
}
};
$rootScope.$on("CallingTopicUpdate", function(event, param){
$scope.loadTopicView(param);
});

How to redirect user after successful login?

Update:
After implementing the below suggestion by Rob Sedgwick, it has become apparent that the redirect only works when the user manually "F5" refreshers the browser (Chrome). What I need to achieve is that this happens automatically in the code so the redirect happens without the user having to hot refresh. Thanks for help with this last part.
At the moment ManageTodosView from what I understand is the first action after the user has been logged in. It prints a list of to do items set up by the user. A working example can be found here http://parseplatform.github.io/Todo/ and the code is https://github.com/ParsePlatform/Todo
I'm using to code to really get user logins to work, I'm not to worries about what the output of the rest of the code is because the long term plan will be to remove it, for the time being its helpful to keep in place to show that the app functioning correctly.
I'm using this code as a base to build a web app. At the moment, once the user is logged in, they are displayed data on the same page.
I want to be able to change this so that after they login, the user is redirected to a different page and the information is then displayed to them there.
The reason for this is that the index page is just a landing/login page and I want to redirect them to a more structured HTML page with menus, etc.
Within the JS code, do I just put in a redirect, something like:
self.location="top.htm";
to this area of the code?
// The main view for the app
var AppView = Parse.View.extend({
// Instead of generating a new element, bind to the existing skeleton of
// the App already present in the HTML.
el: $("#todoapp"),
initialize: function() {
this.render();
},
render: function() {
if (Parse.User.current()) {
new ManageTodosView();
} else {
new LogInView();
}
}
});
I have added the JS code to this JSFiddle
Update:
To address the issue of the page needing a manual fresh before the redirect works, insert
window.location.href="/someurl";
into the following code section within the todoe.js file and comment out the new ManageTodosView(); code.
Parse.User.logIn(username, password, {
success: function(user) {
window.location.href="user_home.html";
//new ManageTodosView();
self.undelegateEvents();
delete self;
},
Try this one also
window.open('url','_parent');
I would suggest a more robust template for integrating Parse Todo samples with real apps. 'Marionette' offers lots of value in real world.
If you take the time to look over the app's structure and then look at the 'loginSuccess' function (scroll to very bottom of link), its pretty straightforward to redirect. You can either use the router as shown OR you can use the Marionette aggregated events which would look like:
vent.trigger('header:loggedIn', _user);
somewhere else in any module within the app....
vent.on('header:loggedIn', function (user) {
that.setViewNew(user);
});
...
setViewNew : function (user) {
User = user;
var viewOptionsUser = {
collection : this.roleList,
events : {
'keypress #new-todo': 'createTaskOnEnter'},
parentUser : User};
this.headerRegion.show(new HeaderView(viewOptionsUser));
this.mainRegion.show(new RoleRoleListCompositeView(viewOptionsUser));
}
Try something like this to redirect your user
window.location = 'www.google.com'

Ember.js when using ModelFor('') in conjunction with the back button fails to load data

I am using Ember data and The RESTAdapter with an extension for Django.
Here is a JSBin
Here is how our routes are set up:
App.Router.map(function() {
this.resource('locations');
this.resource('location', {path:'locations/:location_id'}, function() {
this.resource('items', function() {
this.route('create');
});
this.resource('item', { path:'item/:item_id' }, function() {
this.route('edit');
});
});
});
App.LocationsRoute = Ember.Route.extend({
model: function () {
return this.get('store').find('location');
}
});
App.ItemsRoute = Ember.Route.extend({
model: function(){
//Get the model for the selected location and grab its item list.
//This will do a call to /locations/1/items
return this.modelFor('location').get('items');
}
});
Now this all works fine when we navigate to locations/1/items. The user is presented with a list of items relevant to the location with id 1.
When the user clicks one of these items it brings the url to #/locations/1/item/1 and displays the details of the item with id 1.
Now what doesnt work is this:
When I hit the back button the #/locations/1/items route loads but it does not have its data any more and no REST call to api/locations/1/items occurs. Even though the data displayed just fine when we first navigated to #/locations/1/items.
It is like Ember said "Well we already loaded that data, so we dont need to call the api again" but the data is somehow not being displayed in our template.
If I change the ItemsRoute model to this:
return this.get('store').find('item');
The scenario above works perfectly fine but the data is not based on our location.
Is there something I am missing with using this.modelFor? Or is my route set up incorrect?
Let me know if theres any extra info you need.
Update 1:
Having changed the model function to this I have discovered some new insights:
model: function(){
//Get the model for the selected location and grab its item list.
//This will do a call to /locations/1/items
var location = this.modelFor('location');
var items = location.get('items');
return items;
}
Upon first load of #/locations/1/items the location variable holds the location model and the items variable holds something which has a 'content' property, 'isFulfilled: true' and some other things.
This correctly works and displays the list of items. When i click on a particular item and got to #/locations/1/items/1 then hit the back button. The breakpoint triggers and location is still correctly populating with the location model.
However the items variable seems to just be a PromiseArray, do I need to somehow wait for the promise to be fulfilled before this model hook returns? I thought Ember already did this automatically? I suppose this means that the route is loading before the promise is fulfilled and thats why there is not data displayed?
Therefore my list is not populated correctly.
I'm on a phone, so I'll update this a bit later with more, but the problem is your location resource isn't a child of locations. Becaude of that, ember says why waste time fetching that route if it isn't a part of that resource path. It only hits the location route, which I'm betting you don't have a model hook for fetching the single location (at least based on the code above).
Ok, here is the deal. I have fixed the issue but have no idea why it fixed the issue.
Without further ado:
Here is a jsbin example of the exact setup I have. The only difference between the real one and the jsbin is the fact that I am using the RestAdapter instead of the FixtureAdapter. This is not technically true because I am using the ember-django-rest-adapter which extends the REST one.
The issue described in the question does not present itself in the jsbin but the exact setup with the ember-django-rest-adapter does present the issue.
The fix was to break the cyclic relationship between User -> Site -> Items -> User
For example if I comment out the 'locations' relationship in the User model, the back button works.
Or if I comment out the 'owner' relationship to User in the Item model the back button works.
I might ask a separate question to see what the reasoning behind the problem is, although if someone can shed any light in to why this is happening I'll happily accept the answer here.

History.js getState() at pageload

I'm a little confused about how History.js works at page-load. I've done a few experiments but the results seem indeterministic.
My website is a search engine and the query is stored in the URL parameters: ?Query=cats. The site is written purely in javascript. History.js works great when I do a new search, the new query is updated, and the state is pushed.
My problem is how to create an initial state if the user manually enters in a URL including a Query parameter. Every way I try to do this ends up resulting in running the search query twice in some case. The two use-cases that seem to conflict are:
User manually enters URL (mydomain.com?Query=cats) into address bar and hits enter.
User navigates to an external page, and then clicks the back button
In both cases, the javascript loads, and therefore looks to the URL parameters to generate an initial state.
However, in the second case, History.js will trigger the statechange event as well.
Necessary code:
History.Adapter.bind(window,'statechange',function() { // Note: We are using statechange instead of popstate
var s = History.getState();
if(s.data["Query"]){
executeQuery(s.data);
}
});
and in $(document).ready I have
// Get history from URL
s = getQueryObjectFromUrl(location.href);
if(s["Query"]){
History.pushState(s,'',$.param(s))
}
Is there a better way to handle creating an initial state from URL parameters?
As I had a similar problem to to yours, what i did was to define the function bound to a statechange as a named function, and then all I had it running when the page load as well.
It worked better than trying to parse the URI or anything else, hope it helps.
This is the way I chose to do it (based on Fabiano's response) to store the initial state parameters
var renderHistory = function () {
var State = History.getState(), data = State.data;
if (data.rendered) {
//Your render page methods using data.renderData
} else {
History.replaceState({ rendered: true, renderData: yourInitData}, "Title You Want", null);
}
};
History.Adapter.bind(window, 'statechange', renderHistory);
History.Adapter.onDomLoad(renderHistory);
Of course if you are using a different on DOM load like jquery's you can just place renderHistory(); inside of it, but this way doesn't require any additional libraries. It causes a state change only once and it replaces the empty initial state with one containing data. In this way if you use ajax to get the initData inside the else, and it will not need to get it the next time the person returns to the page, and you can always set rendered to false to go back to initial page state / refresh content.

Postponing search until template load

I want to make a request to a server to get a bunch of news articles based off of what the user clicks on (recent, trending, etc). I'd like to be able to load the page first and show a loading bar while I wait for the response from the API. What I have, and although it works and returns the JSON data I need, will wait until the response comes back from the server before loading anything. This is all it is so far:
What I want to achieve is the following: Load up an empty array of objects, and then make API calls to articles incrementally (let's say grab all the articles from this hour, then last hour, then the hour before, and so on) and whenever I retrive articles populate the view (I'm assuming I can just inject them into a controller somehow) however I'm getting lost on the Emberisms on how to add to that array of objects. I think I need to make an ArrayController and then create a model in said array, then call a function to add to it, but as I said I'm lost as to how to add items into that controller on the fly
App = Ember.Application.create();
App.Router.map(function() {
this.resource('today');
});
App.TodayRoute = Ember.Route.extend({
model: function() {
return $.getJSON('/today');
}
});
To elaborate a bit on the reasoning for my question - I'm confused on how to approach this with ember. I'm familiar on how to do something like this using jquery, however I'm trying to learn the framework and am having a little bit of trouble originally knowing what the division of labor is between the two. I know the actual AJAX requests should be jquery, but I want to do as much as possible in Ember. If this however is something that should be done by jquery though, then that is fine as well!
The getJSON method looks to have a callback function on success you could use, something like this maybe?
App.TodayRoute = Ember.Route.extend({
model: function() {
$.getJSON('/today', function(data) {
// hide loading here
/ data is the JSON returned
});
}
});
Check out this link http://api.jquery.com/jQuery.getJSON/
I found the solution - and like everything with ember, it was very simple. I used some blank fixture data, then defined my controller as below.
App.ArticleRoute = Ember.Route.extend({
model: function() {
return App.Article.find();
}
})
Then upon pageload I call a method focused around
App.Article.createRecord({
// Attributes, pulling from the returned JSON
})
Simple! :)

Categories

Resources