How to update a tab view from child page in Angular.JS - javascript

So I'm trying to update a list view in a parent tab window using Angular.js with the Ionic Framework, but I can't quite seem to figure out how to do it.
Here is the code for the child window:
$scope.currentUsername = Parse.User.current().get("username");
$scope.saveChanges = function saveChanges(user){
var currentUser = Parse.User.current();
var newUserName = user.newUsername;
currentUser.set("username", newUserName);
currentUser.save(null, {
success: function(currentUser){
alert("Changes successfully made!");
}
});
$state.go('tab.more');
}
$scope.cancelChanges = function cancelChanges(){
$state.go('tab.more');
}
And I am trying to take the data once it's sent to Parse back to the parent view but I don't know how to refresh the page after the script is run. In my function cancelChanges() I simply used $state.go to go back to the parent view because why update the view when there is not data to be updated.
Here is the code for my parent window:
$scope.currentUser = Parse.User.current().get("username");
$scope.editProfile = function editProfile(){
$state.go('tab.more-editusername');
}
$scope.logOut = function logOut(){
Parse.User.logOut();
alert("Logout successful!");
$state.go('login');
}

ionic views are cached by default... so I do not believe the update is happening unless you force it to
From Documentation
View LifeCycle and Events Views can be cached, which means controllers normally only load once, which may affect your controller
logic. To know when a view has entered or left, events have been added
that are emitted from the view's scope. These events also contain data
about the view, such as the title and whether the back button should
show. Also contained is transition data, such as the transition type
and direction that will be or was used.
you can try and listen for the $ionicView.enter event and then update the view with the new data

Related

Ionic and Angularjs - Update/Refresh View from Controller

The usual start to these, I am new to both Ionic and Angularjs. I am developing an Ionic app which at it's heart is very simple. We show a list of classes(sessions), the person clicks on an icon to book the class then the icon changes to allow them to cancel the class. We also update the card to show the number of places remaining in each session on the day.
I have the code working to add and remove a person to and from a class but I am not sure how to update the template view from within the controller.
The controller code is pretty simple
// Check Person in to session.
$scope.addCheckIn = function(schedule){
var promise = sessionDataService.checkinSession(schedule.sessionID);
promise.then(function(data){
// Update (refresh) Schedule Details
// NOT SURE WHAT TO PUT HERE??
});
};
I have tried a number of different approaches including
Refreshing the $state and calling doRefresh and even calling the original controller methods to populate the cards again but the view won't update unless I physically click between states on the screen
//$state.go('app.schedules', {}, {reload: true});
//$scope.doRefresh();
//getScheduleData(formatDate(selectedDate), formatDate(selectedDate), 'true');
I have also looked at $scope.apply and $scope.timeout but I am not sure if this is taking me further from the real solution
What is the correct way to update the view after an update? Should it be after the promise.then in the controller or should I call a service and update everything.
Any tips on what is the best way to do this and a point in the right would be really appreciated.
Thanks everyone.
In your promise, you should add the data to the scope.
$scope.scheduledetails = data;
Then in your template, you will be able to access the object scheduledetails from the controller with AngularJS brackets to bind the data to the HTML.
<h1>{{scheduledetails.title}}</h1>
<p>Details : {{scheduledetails.details}}</p>
AngularJS should take care of refreshing what is needed without having to call any method or anything.
Full example
Controller
$scope.addCheckIn = function(schedule){
var promise = sessionDataService.checkinSession(schedule.sessionID);
promise.then(function(data){
$scope.scheduledetails = data;
});
};
Template
<h1>{{scheduledetails.title}}</h1>
<p>Details : {{scheduledetails.details}}</p>

Controller lifecycle and emit/broadcasts

On my page, I have a grid listing all articles returned by a web service. The user can go to a seperate page to add a new article and when finished, the article should be added in the grid on the other page. So when the user returns to that page, the article will be listed.
I have a service that handles the communication between the 2 controllers (one for the grid, one for the adding of articles). This service has a function called by the AddArticleController that broadcasts a message to my GridController:
function addNewArticle(articleNumber) {
$rootScope.$emit('newArticleAdded', {
articleNumber: articleNumber
});
}
The gridController picks this up:
$rootScope.$on('newArticleAdded', function(event, data) {
// get all article details generated by the back-end and add it to he grid
});
The problem is that, when the page with the grid has been displayed multiple times already, the article is added multiple times to the grid. I assume the reason for this is because every time a new controller is created, but the old controller is not being destroyed, so the broadcast is picked up multiple times.
How do I solve this issue? Obviously, the broadcast should only be handled 1 time.
Service
myApp.service('myService',function($controller){
this.broadcastEvent = function(){
scope = $scope.$new();
$controller('mainCtrl',{
$scope:scope
});
scope.broadCastMethod();
}
});
Controller
myApp.controller('mainCtrl',function($scope){
$scope.broadCastMethod = function(){
$scope.$broadcast("callBraodcast");
}
})
Acknowledging that I say this from a position of ignorance; would it not be easier to have the service maintain the list of articles and let the controller observe that? It would avoid the need to send messages back and forth through the scopes and have a single source of truth.

Ember Modal on a route accessible from anywhere

I'm building an app that has a user settings panel that pops up in a modal dialog. The panel should be accessible from any page in the app. I'm trying to figure out the best way to build this in Ember.
I would like to build it in such a way that when the app redirects to the "/settings" route, the modal dialog appears with the current route in the background as you would expect. Then when the modal is closed the app redirects back to that route.
If the user goes directly to "/settings" from her browser then the modal will appear with a default page in the background.
Here is what I have so far:
export default Ember.Route.extend({
defaultParentRoute: "project.index",
beforeModel: function(transition){
// get the name of the current route or if
// user came directly to this url, use default route name
var parent = this.get("defaultParentRoute");
var application = this.controllerFor('application');
if(application && application.get('currentRouteName')) {
parent = application.get('currentRouteName');
}
this.set("parentRoute", parent);
},
renderTemplate: function(){
// make sure the current route is still rendered in the main outlet
this.render(this.get("parentRoute"));
// render this route into the 'modal' outlet
this.render({
into: 'application',
outlet: 'modal'
});
},
actions: {
removeModal: function(page){
// go back to the previous route
this.transitionTo(this.get("parentRoute"));
}
}
});
This works pretty well when navigating to the the route from a link in the app. However if a user goes straight to "myapp/settings" in her browser then the default page template gets rendered but without any data or it tries to use the 'model' data from my settings route.
How do I make sure the template underneath the modal gets rendered with the appropriate data?
Here's a JS Bin to demonstrate. Try clicking on 'settings' from any page in the app, then refresh the browser while the settings modal is open.
This organization seems a bit unnatural given Ember conventions. Generally the URL is supposed to represent a serialized version of state sufficient to reconstruct where the user was (see this old discussion).
It seems you want to put the modal state and the current route into the URL. It might be more natural for the settings modal panel to be accessible from other routes but to not change the URL, and then have another separate route which is dedicated to settings and shows only the settings.
Modal panels seem more like a drop-down menu, the opening and closing of which do not change the URL, even though they represent a minor state change.
If the reason you want to have the settings modal reflected in the URL is so that people can bookmark it or share the link, one option would be to have a permalink available on the settings page that gets them to the other dedicated route that is shareable.
The tricky bit with not having the settings as a route is that there is not an obvious place to load the model which behaves as nicely or works as easily as a route's model hook (i.e., which waits until the promise is resolved to complete the transition).
One way around this is to put functionality to load the settings model in a settings service, which can be injected anywhere:
SomeController = Ember.Controller.extend({
settings: Ember.inject.service()
});
And have the settings service only show itself once the model has loaded.
SettingsService = Ember.Service.extend({
settingsLoaded: false,
ensureLoaded: function() {
var _this = this;
return new Ember.RSVP.Promise (resolve) {
if (_this.get('settingsLoaded')) {
resolve()
} else {
_this.store.find('settings', someId).then(function(model) {
_this.set('model', model);
_this.set('settingsLoaded', true);
resolve();
});
}
};
}
});
Finally you can have a function on some controller that wants to show the settings modal only show it once the settings are loaded:
showSettings: function() {
this.get('settings').ensureLoaded().then(function() {
... # something to show the modal pane.
});
}

Destroy Backbone views on route change

My view should be destroyed after the current route position is left.
So in this schematic example the login view should be destroyed after the user entered his credentials:
I tried to solve this by using Backbone.Router events:
var Router = Backbone.Router.extend({
initialize: function () {
Backbone.history.start();
},
routes: {
"sample" : "sample"
},
sample: function(){
// Build view
var demoView = $("<div/>")
.appendTo(document.body)
.text("I am lost!");
// Destroy view
this.once('route', function(){
demoView.remove();
});
},
});
Unfortunately this does not work as the route events are raised after the routes are executed:
http://jsfiddle.net/hcuX9/
Is there a solution to destroy views after leaving the route position?
Do I have to hack a new event into Backbone.js?
What I use to do is to have an App.current variable pointing to the current view being rendered.
At the top of each route (or the relevant ones in your case), I remove the current view from App.current and then assign it the new view:
someRoute: function() {
if(App.current && App.current.remove) App.current.remove();
// Asign a new current page
App.current = new SomeView();
...
}
That way I only let one view live per route, getting rid of problems like yours.
If you don't like to be checking for App.current and invoking the remove method at the top of every route, you can listen for Backbone.history route event and injecting that logic there:
Backbone.history.on('route', function() {
if(App.current && App.current.remove) App.current.remove();
});
I think you are stuck with your hack, unless you can adapt .listenTo to your needs - then you will need to fire a custom event with .trigger anywhere you have a route change, which might not be possible. Note that this functionality has been requested (and denied) before in backbone:
https://github.com/documentcloud/backbone/pull/494
See that pull request for other patches that try to do the same thing you are doing.
Here, we're using on and off to listen for route events coming in instead of once because we can't rely on a single event not being the current route. When we receive a route even that is not our current route, we can destroy the view and remove the listener:
// Destroy view
var self = this;
var onRoute = function(route, params){
if(route !== 'sample'){
demoView.remove();
self.off('route', onRoute);
}
};
this.on('route', onRoute);
I've modified your test fiddle here: http://jsfiddle.net/rgthree/hcuX9/3/
Another option, as your fiddle (not in your question) navigates directly to another view. This causes the other route's event to fire after the sample2 route. Because of this the above will remove the view. Now, it's much more complete. A hackier way you could handle it is to simply defer the once in a setTimeout so it doesn't listen until after the current route has been fired:
// Destroy view
var self = this;
setTimeout(function(){
self.once('route', function(){
demoView.remove();
});
}, 0);
You can see your fiddle with this method here: http://jsfiddle.net/rgthree/hcuX9/4/

Backbone router creates multiple views which causes multiple events to bind to the same view

I'm new to backbone.js and trying to understand how routes, views etc works and now I have a problem with events building up for the same view. here is a clip that will show you exactly what I mean. http://screencast.com/t/QIGNpeT2OUWu
This is how my backbone router looks like
var Router = Backbone.Router.extend({
routes: {
"pages": "pages",
}
pages: function () {
var page_view = new PageView();
}
});
So when I click the Pages link I create a new PageView and this is the code I'm using
PageView = Backbone.View.extend({
el: $("#content"),
initialize: function () {
$.ajax({
url: '/pages',
success: function (data) {
$("#content").html(data);
}
});
},
events: {
"click td input[type=checkbox]": "updatePublishedStatus"
},
updatePublishedStatus: function (event) {
console.log('update publish status');
}
});
pretty basic I guess but as you can see in the clip each time I navigate to /pages I get another event registered to the checkbox.
There are a few things going wrong here.
Your video indicates pages being a collection well, Pages. Pages being a Backbone.Model with attributes such as Page name, slug, published etc... You lack that and it's going to hurt. You shouldn't just load some html and push it to your DOM, this defies the whole purpose of using Backbone in the first place.
If you do create a Model for a Page it will have a View. Then your /pages route will show the view of the Collection Pages etc.
You will fetch your data not inside a view's initialize but rather by doing pages.fetch(); where pages is an instance of the Pages collection. This can happen before you even initialize your router.
When changing attributes through your view, the individual models will be updated.
As a sidepoint: Fetching data on initialize is not great. You can call render() before you actually get the data and that's no fun.
Also instead of doing $('#content') you can use the view's $el. As in this.$el.html(...);
Move var page_view = new PageView() to be outside of Router.pages().
Have the PageView.initialize() success callback save data to a variable. Either in PageView or in a model.
Add a render function to PageView that sets $("#content").html(data);.
Call page_view.render() within Router.pages().

Categories