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

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);
});

Related

how to save state of the page when refreshing? (HTML, JS)

(DISCLAIMER: I'm new to coding so my code probably isn't optimal. If you know a better way to do it, feel free to leave it in the comments )
Most of the time I had no idea what I was doing, but with patience and with you guys' help I came up with this:
if (window.localStorage) {
// Create the Key/Value
var cNum = localStorage.getItem("currentNumber");
if (localStorage.currentNumber == undefined) {
localStorage.setItem("currentNumber","0");}
// Variables
resetCount.innerHTML = localStorage.currentNumber;
// Functions
function btnR() {
cNum++;
localStorage.currentNumber = cNum;
resetCount.innerHTML = cNum;}}
else { console.log("No"); }
HTML:
<button id="resetButton" onclick="btnR()">Reset</button>
<p id="resetCount">0</p>
I was creating a button that each time you click on it, it reset the checkboxes, but I also wanted a counter to see how many times they got rested. The problem was that every time I click the button the counter also reset. Now that is solved I can try to apply the same thing for the checkboxes, so they don't reset on refresh either.
Note: I had to put the .SetItem in an if statement cause, even tho the value was in storage it kept setting back the value to zero every time I refreshed the page. This was the way I found to stop that.
You either need to set up a back end to send data to and save the information you want to keep stored, or save data in localStorage.
Just know it is not the best practice to save sensitive info in localStorage (as they can be compromised in cross-site scripting attacks).
localStorage.setItem puts a bit of data into localStorage (and that stays there till you clear it) and localStorage.getData extracts it.
This might help get you started on localStorage, but you will have to figure out the function to set the colour to the element you have.
let boxColour = localStorage.getItem("boxColour");
if (boxColour === null) {
setBoxColour("colour");
} else {
setBoxColour(boxColour);
}
function setBoxColour(colour){ localStorage.setItem("colour");}
/* Inside the function you have to get the item and change it's style attribute or add a class to add styles */
Careful with that localStorage data!
You could use LocalStorage.
It saves data in the page to be used after when the page is refreshed or closed and opened later.
Theres a example:
(Unfortunally, it seems to not work in the stackoverflow site, but if you try at your HTML file it will work)
var loadFunc = (elem) => {
console.log("Value saved is: "+ localStorage.getItem("savedValue"));
if(localStorage.getItem("savedValue")){ //checks if value is saved or not
elem.checked = localStorage.getItem("savedValue");
}
}
var clickFunc = (elem) => {
localStorage.setItem("savedValue", elem.checked); //set te value if in localStorage
}
Click the checkbox and the value will be saved.
<input type="checkbox" onload="loadFunc(this)" onclick="clickFunc(this)">

Can I use ActionCable to refresh the page?

I've recently been trying to create a live-scoring system for squash matches. I've managed to use ActionCable with Rails 5 to auto-update the score on the page, but I'd like to know if it's possible to tell Rails to refresh the page if a certain condition is met.
For example, if the game has finished, a different page is shown to say that the players are having a break between games. I need the page to refresh completely for this to happen.
In my database the boolean 'break' is marked as true when a game ends, and then the view uses a conditional if/else statement to decide what to show.
The code I use to update the score is attached below, I was thinking something along the lines of if data.break == true then the page will automatically refresh.
// match_channel.js (app/assets/javascripts/channels/match_channel.js)
$(function() {
$('[data-channel-subscribe="match"]').each(function(index, element) {
var $element = $(element),
match_id = $element.data('match-id')
messageTemplate = $('[data-role="message-template"]');
App.cable.subscriptions.create(
{
channel: "MatchChannel",
match: match_id
},
{
received: function(data) {
var content = messageTemplate.children().clone(true, true);
content.find('[data-role="player_score"]').text(data.player_score);
content.find('[data-role="opponent_score"]').text(data.opponent_score);
content.find('[data-role="server_name"]').text(data.server_name);
content.find('[data-role="side"]').text(data.side);
$element.append(content);
}
}
);
});
});
I don't know if this sort of thing is possible, and I'm not much good at anything Javascript related so I'd appreciate any help on this.
Thanks.
Reloading the current page is relatively straightforward. If you are using Turbolinks, you can use Turbolinks.visit(location.toString()) to trigger a revisit to the current page. If you aren't using Turbolinks, use location.reload(). So, your received function might look like:
received: function(data) {
if (data.break) {
return location.reload();
// or...
// return Turbolinks.visit(location.toString());
}
// your DOM updates
}
Either way is the equivalent to the user hitting the reload button, so it will trigger another GET, which calls your controller and re-renders the view.

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.

Emberjs: Loading data for other route

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.

Categories

Resources