ng-grid and back button: remembering grid selections and data - angularjs

I have a view with an ng-grid, like this:
<div class="ng-grid-style" ng-grid="customersGrid"></div>
In the controller of this view I keep a list of customers and a list of selected customers:
$scope.customers = [];
$scope.selectedCustomers = [];
I also do the binding of the grid with these two:
$scope.customersGrid = {
data: 'customers',
selectedCustomers: $scope.selectedCustomers
};
I also fetch customers from a URL at the start of the controller instance:
$scope.fetchCustomers() = {
// with $http I bring customers...
// (...)
$scope.customers = response.customers
};
$scope.fetchCustomers();
In the view I have links to other routes of the same app. These routes associate other views controlled by other controllers.
My problem is that if I navigate from the view containing the ng-grid to another view, and then I use the back button, I lose the state of $scope.customers and $scope.selectedCustomers.
This app is an SPA, so I should be able to keep these two in memory (or elsewhere) along the life of the app instead of having them in the $scope
What is the most recommended way of storing these two? I've read that I could use localStorage, sessionStarge, a service, etc. Also I'm not sure how the binding works when NOT USING the scope instead. Can I pass to the grid's data something that is not in the scope? eg:
$scope.customersGrid = {
data: localStorage.customers, // possible?
selectedCustomers: localStorage.selectedCustomers // possible?
};

Related

Getting data from a service always return the same value (not the updated) on AngularJs

i'm doing a crud like a phone list,
in a view i want to add persons to my list, and in another view i want to show that peoples,
for do that, i'm using two controllers, and a service to store my array with the persons.
to add peoples i'm using set way to pushing it to array when i click on a button and thats works fine(possible to see with console.log at salvar function in service).
My problem is, when i go to show the list with the get method after added some persons with set, the get method still returning my list like she starts (empty). how i can fix this?
angular
.module('moduloLista')
.factory('addMostrarService', addMostrarService);
function addMostrarService() {
var listacontatos = [
];
var salvar = function(obj){
listacontatos.push(obj);
};
var getLista = function(){
return listacontatos;
};
return{
salvar: function(obj){
salvar(obj);
},
getLista: function(){
return getLista();
}
}
}
^ Service Code
angular
.module('moduloLista')
.controller('testeController',testeController);
testeController.$inject = ['addMostrarService'];
function testeController(addMostrarService) {
var vm = this;
vm.dadoslista = addMostrarService.getLista();
console.log('vm.dadoslista');
}
^ controller to get the list from service.

When changing route state via a dropdown, how can I set a tab as active in a sub view?

I've assembled an angularjs app with ui-bootstrap & ui-router which has some tabbed sub-views where 'index' has a 'ui-view', then for some views,
a template with 'uib-tabset' and another 'ui-view' is loaded into the main 'ui-view'.
My tabset isn't rendered with a repeater, but I do have a 'tabs' array setup in the controller with 'active: false' for all 5 of them, for example:
$scope.tabs = [{ active: false }, { active: false }, { active: false }, { active: false }, { active: false }];
The views load OK and the tabs activate OK because they have the 'active' attrib, for example:
<uib-tab heading="Users" ng-click="go('/settings/users')" active="tabs[3].active"></uib-tab>
But I also have a ui-bootstrap dropdown in the header, which transitions to the views as well as fires an 'itemSelected' event, for example:
<li>Users</li>
$scope.itemSelected = function(item) {
$rootScope.$broadcast("itemSelected", { tabIdx: item });
The 'tabs' controller responds to the broadcast & sets the active tab:
$scope.$on('itemSelected', function (event, args) {
$scope.tabs[args.tabIdx].active = true;
This (tab activation via dropdown) works OK when I'm within the sub views, but when I first enter into the sub views, this process fails to set the active, I think because of the order things are loaded and become ready for manipulation.
I don't have a base controller, but have defined controllers for every view since I will need to be adding more behaviors for them.
In one of the sub view controllers (for example, sub view #3 'Users'), I added the tabs controller via the '$controller' service, for example:
$controller('TabsCtrl', { $scope: $scope })
With this I seem to get a handle on the '$scope.tabs', but am still not able to set the active tab, for example:
$scope.tabs[3].active = true;
I realize I might set up a base controller and do these things in there, but then I might be affecting just one tabset in scope where I might want more later.
Or I would need logic to check the route state and only set active tab in certain states.
Or I could set an 'activeTab' variable on the rootScope and do something with it in the tabs template or controller to make it active.
But I would rather leverage automatic binding in some way, or at least listen and respond to the state changes in a more correct angularjs way, without adding hacks or workarounds.
When changing route state via a dropdown, how can I set a tab as active in a sub view?
I suppose I could pass the tab state around in the URL then process it somehow. I noticed I could control the tabs from the same controller and view but since my drop down is in the index header, and my tabset is in a ui-view, they don't play well. I suppose there are ways to pass data into views, but the issue with what I had is that controllers clean up the scope data when transitioning routes, so I would lose my tab state no matter what I tried.
A more angularjs approach was to add a factory to the base app module, for example:
.factory('tabsFactory', function() {
var tabsService = {};
var _tabsArray = [{ active: false }, { active: false }, { active: false }, { active: false }, { active: false }];
tabsService.activateTab = function(idx) {
for(var i = 0; i < _tabsArray.length; ++i) {
if (idx != i) {
_tabsArray[i].active = false;
} else {
_tabsArray[i].active = true;
}
};
};
tabsService.getTabsArray = function() {
return _tabsArray;
}
return tabsService;
Now my dropdown controller 'itemSelected' function sets the activeTab:
$scope.itemSelected = function(idx) {
tabsFactory.activateTab(idx);
In my tabs controller, I don't need to use '$scope.$on' to react to the broadcast, I just get the tabs from the factory, and the component renders the model state automatically:
$scope.tabs = tabsFactory.getTabsArray();
Also, since each view/sub-view has a controller, I can accommodate browser refresh by activating the appropriate tab in each sub-view controller:
tabsFactory.activateTab(1);
With this arrangement, I have working tab behaviors no matter how I navigate around the app. Thx

How to reference a form from controller

Background:
I have an AngularJs wizard like app with 3 steps. The wizard steps contain 4 uiRouter states that share a single controller. The first state is an abstract view containing bootstrap pill navigation with a ui-view as a placeholder for the other 3 nested states. The 3 nested states have a separate views that contain uniquely named forms. I would like to control the pill navigation in the the parent state by checking if the current visible form is valid; if the form is invalid then I would hide/disable the ability for the user to click on the next navigation pill.
Note: I'm using the "controller as" syntax declared in the uiRouter states.
Issue:
I don't seem to be able to get a reference to the current viewable form from within the controller.
Here's what I've tried:
1. passing in the reference to the form visible form in an init call --> gets undefined error when I try to write to console
2. renaming the forms and even passing in the scope reference as described in this link:
http://www.technofattie.com/2014/07/01/using-angular-forms-with-controller-as-syntax.html
Demo:
Here's a plnkr link that shows intent:
http://plnkr.co/edit/YUnwoBD2dt4bzbfujJSW
Controller code:
(function () {
'use strict';
angular
.module('recquisitionManagement')
.controller('RecquisitionEditCtrl', [RecquisitionEditCtrl]);
function RecquisitionEditCtrl() {
var vm = this;
// get ref to request form
vm.setRequestForm = function(form) {
vm.requestForm = form;
console.log(form);
// tried to call from form ng-init --> undefined response
}
// console.log($scope.vm.requestForm);
// console.log(vm.requestForm);
vm.recquisition = { id: 0, name: 'some name', date: 'date here' };
vm.requestFormIsInvalid = true; //this.requestForm.$valid;
vm.approvalFormIsInvalid = true;
if (vm.recquisition && vm.recquisition.id > 0) {
vm.title = 'Edit';
} else {
vm.title = 'Create';
}
}
}());
Any suggestions on how to get the needed form reference?
Multiple options are explained here: Can I access a form in the controller?
Also it can be good to rename functions that share the name of the controller and try to use the common angular practice.
yes like so:
<form name="vm.theFormName">
you can then access it via vm
Bit late now I realise but you were passing the ctrl as an array, needed to change this:
.controller('RecquisitionEditCtrl', [RecquisitionEditCtrl]);
to this:
.controller('RecquisitionEditCtrl', RecquisitionEditCtrl);

Collection renders fine when loaded from remote Url, but not when loaded from localStorage (initialize vs. onRender)

I'm using a layout's 'initialize' method to instantiate a collection, fetch() data from a url on that collection, and then instantiate a new collectionView passing through the collection with it's fetched data. The collectionView later gets 'shown' in a region.
It works perfectly.
I wanted to switch over to using localStorage to retrieve the data, instead of from a remote url. So I saved the data retrieved from remote url into localStorage. I then updated the collection with the 'localStorage: new Backbone.LocalStorage('entries')'. Now it successfully retrieves the data from localStorage. But. None of the regions in the layout get rendered (i've tried calling this.render() in various places with no luck).
If I replace the 'initialize' method to 'onRender' in the layout which is responsible for instantiating everything, then it renders perfectly (regardless of whether it came from remote url or localStorage).
Why would it be that if i use the layout's 'initialize' method to fetch data in a collection from localStorage, then nothing gets rendered; whereas it does get rendered if that data is fetched from remote Url? why does both work when using the 'onRender' method?
Some code for reference:
var EntriesCollection = Backbone.Collection.extend({
model: EntryModel,
url: '/api/entries',
localStorage: new Backbone.LocalStorage('entries')
});
return EntriesLayout = Marionette.Layout.extend({
...
// Below, if I use onRender everything works, if I change to 'initialize' then
// it works only when data is loaded via remote url, not localStorage. (the collection
// is successfully populated with data in both cases.)
onRender: function() {
...
this.entries_collection = new EntriesCollection();
var self = this;
this.entries_collection.fetch().done(function() {
/*
self.entries_collection.localStorage = new Backbone.LocalStorage('entries');
self.entries_collection.each(function(model) {
model.save();
});
*/
self.entries_list = new EntriesList({collection: self.entries_collection});
self.collectionSynced();
});
},
collectionSynced: function() {
this.columnRight.show(this.voting_intro);
this.collectionList.show(this.entries_list);
this.listenTo(this.voting_intro, 'start:clicked', function() {
this.displayEntry(0);
});
},
...
I'm using Marionette's Layout, CollectionView, ItemView's etc, not sure how critical that is to the question.

Backbone routes and view states

Apologies for the possibly poorly formulated title. New to Backbone.
I'm having trouble wrapping my head around how to deal with routes in association with views. Basically I have a view (let's call it ListView) that, depending on its viewMode, renders ItemViews using different templates. It looks something like this:
var ListView = Backbone.View.extend({
// Cache a bunch of templates here
viewMode: 'list', // Default is list
render: function() {
switch(this.viewMode) {
case 'list':
// Render ItemView based on list template
break;
case 'gallery':
// Render ItemView based on gallery template
break;
}
// Render all items in list
this.collection.each(function(model, index) {
new ItemView(); // Maybe pass viewMode as a parameter
});
}
});
My goal is that whenever ListView uses the viewMode "list" or "gallery", this should be reflected in the address bar, and likewise manually entering or clicking a link that leads to e.g. mysite.com/page.html#items/list or #items/gallery should render the same results.
Is there a way of automating this process, or in some other way solve it?
Think your router would be something like:
var yourRouter = Backbone.Router.extend({
routes: {
"items/list": "showList",
"items/gallery": "showGallery"
},
showList: function() {
listView.viewMode = "list"
listView.render();
}
showGallery: function() {
listView.viewMode = "gallery"
listView.render();
}
});
Then in your view events, you can call the navigate method of your router. This will update the address bar.
yourRouter.navigate("items/list")

Resources