Can anyone help me with integrating a state machine to control routing?
What's the best method to do this? Create a service?
I need to basically intercept every $location request, run the state machine and let it figure out what the next $location.path should be.
Think of the problem like a bank of questions that get added and removed over time. The user visits once in a while, passes in the user's answers object to the statemachine, and the statemachine figures out which question to load. This is my pseudocode, but i need to figure out where to put this or what event I can hook into to make sure all route requests are passed through the machine. Do I need a specific stateMachine controller? Do I create a service? Where do I use the service? Do I need to override $locationProvider?
$scope.user.answers = [{
id: 32,
answer: "whatever"
},
{
id:33,
answer: "another answer"
}]
$scope.questions = [{
id:32,
question:"what is your name?",
path:"/question/1"
},{
id:34,
question:"how old are you?",
path:"/question/2"
}]
var questions = $scope.questions;
angular.forEach(questions, function(question) {
if(question.id !exist in $scope.user.answers.id) {
$location.path = question.path
break;
});
Thanks
Have you looked into this project yet?
https://github.com/angular-ui/ui-router
I am only starting to try it out, but it looks like it should meet your needs.
Instead of intercepting $location changes, how about using ng-click and ng-include? Use ng-click to call your state machine logic, and have it update a model/scope property that specifies which template to load via ng-include:
<a ng-click="runStateMachine()">process answers</a>
<div ng-include src="partialToInclude"></div>
Controller:
$scope.runStateMachine() {
... process $scope.answers and set $scope.partialToInclude ...
}
Related
I am trying to call an API end point once a user clicks a button holding a myNavigator.pushPage() request. However,I can not get the $scope data generated from the $http.get request to be passed to the new page.
If I test using console.log('test'); inside the .success of the $http.get request I successfully get the log info in the console but any data held in $scope.var = 'something'; does not gets passed to the page! Really confused!
$scope.historyDetails = function(id){
var options = {
animation: 'slide',
onTransitionEnd: function() {
$http.get('http://xxx-env.us-east-1.elasticbeanstalk.com/apiget/testresult/testId/'+id).success(function(data) {
$scope.testscore = 'something'; // this is not getting passed to page!
console.log('bahh'); // But I see this in console
});
}
};
myNavigator.pushPage("activity.html", options);
}
Page:
<ons-page ng-controller="HistoryController">
...
<span style="font-size:1.2em">{{testscore}} </span><span style="font-size:0.5em;color:#555"></span>
...
</ons-page>
Yes, that's so because both pages has different controllers, resulting in different scopes. One can not access variables from one scope to another.
Hence one solution in this case can be using rootScope service.
Root Scope is parent scope for all scopes in your angular application.
Hence you can access variable of root scopes from any other scope, provided that you are injecting $rootScope service in that controller.
to know more about rootScope check this link.
Good luck.
Update 1:
check these articles
http://www.dotnet-tricks.com/Tutorial/angularjs/UVDE100914-Understanding-AngularJS-$rootScope-and-$scope.html
https://toddmotto.com/all-about-angulars-emit-broadcast-on-publish-subscribing/
As Yogesh said the reason you're not getting your values is because if you look at $scope.testscore and try to find where is the $scope defined you will see that it's an argument for the controller function (thus it's only for that controller).
However we can see that the controller is attached to the page and you are pushing another page.
So in that case you have several options:
Use the $rootScope service as Yogesh suggested (in that case accept his answer).
Create your own service/factory/etc doing something similar to $rootScope.
(function(){
var historyData = {};
myApp.factory('historyData', function() {
return historyData;
});
})();
Technically you could probably make it more meaningful, but maybe these things are better described in some angular guides.
If you have multiple components sharing the same data then maybe you could just define your controller on a level higher - for example the ons-navigator - that way it will include all the pages. That would be ok only if your app is really small though - it's not recommended for large apps.
If this data is required only in activity.html you could just get it in that page's controller. For example:
myApp.controller('activityController', function($scope, $http) {
$http.get(...).success(function(data) {
$scope.data = data;
});
}
But I guess you would still need to get some id. Anyway it's probably better if you do the request here, now you just need the id, not the data.
You could actually cheat it with the var directive. If you give the activity page <ons-page var="myActivityPage"> then you will be able to access it through the myActivityPage variable.
And the thing you've been searching for - when you do
myNavigator.pushPage("activity.html", options);
actually the options is saved inside the ons-page of activity.html.
So you can do
myNavigator.pushPage("activity.html", {data: {id: 33}, animation: 'slide'});
And in the other controller your id will be myActivityPage.options.data.id.
If you still insist on passing all the data instead of an id - here's a simple example. In the newer versions of the 2.0 beta (I think since beta 6 or 7) all methods pushPage, popPage etc return a promise - which resolve to the ons-page, making things easier.
$scope.historyDetails = function(id){
myNavigator.pushPage("activity.html", {animation: 'slide'}).then(function(page) {
$http.get('...' + id).success(function(data) {
page.options.data = data;
});
});
});
Side note: You may want to close the question which you posted 5 days ago, as it's a duplicate of this one (I must've missed it at that time).
I'm building a web application and I have a screen that consists in five sections, each section represents a level, the areas are the higher level of my tree, when I click in any card of the area, the system should return the skills of that area and so on.
I need to change the url and state according what the user is accessing, for example, if the user access some skill, the url must be
example.com/#/curriculum/skill/<skillId>
and if I access this link it should automatically load the capabilities from this skill and his parent which is area in this case.
I have one controller for area, skill, capability, knowledge and criteria, in each controller I have a action to load the next level of the tree, which looks like that
$scope.loadSkills = function (id) {
Area.loadSkills(...)
$state.go('curriculo.skill', {id: this.current.id}, {nofity: false, reload: false});
}
And these are my states
$stateProvider
.state('curriculum', {
url: '/curriculum',
templateUrl: '/templates/curriculo.html',
})
.state('curriculum.are', {
url: '/area/:id',
template: '',
})
.state('curriculum.skill', {
url: '/skill/:id',
template: '',
})
.state('curriculum.capability', {
url: '/capability/:id',
})
.state('curriculum.knowledge', {
url: '/knowledge/:id',
})
.state('curriculum.criteria', {
url: '/criteria/:id',
});
I'm new in Angular and I not sure about what to do, should I created multiple nested views in this case, and if so, how do I load stuff that I need according the url?
I would suggest to use the capability of multiple named views offered by the ui-router. You can read more about it here. Basically the documentation says the following:
You can name your views so that you can have more than one ui-view per
template.
If you check the example in the documentation, you'll notive that there are similarities between your scenario and the example, because you want to dynamically populate a different views (here named views).
Example
I tried to recreate your scenario in this JSFiddle.
First I created an abstract state which provides the different views like areas, skills etc. This is the template for the abstract state:
<div class="curriculum" ui-view="areas"></div>
<div class="curriculum" ui-view="skills"></div>
Next I created a nested state curriculo.main, which declares the different views (areas, skills etc.) you need. Each view has its own template and controller. Notice that the nested state has a resolve which initially loads the areas from a service called curriculo. If you use resolves remember that the resolve keyword MUST be relative to the state not the views (when using multiple views).
Basically the service is responsible for the business logic, means getting the areas, skills etc. In the JSFiddle I have hard-coded the HTTP results. Replace that with HTTP calls and make use of promises. Since each named view has its own controller we need a mechanism to notify about changes, for example to notify the SkillsController that skills have been loaded. Thus, I created a simple event system (subcribe-notify):
.factory('notifier', function($rootScope) {
return {
subscribe: function(scope, callback, eventName) {
var handler = $rootScope.$on(eventName, callback);
scope.$on('$destroy', handler);
},
notify: function(eventName, data) {
$rootScope.$emit(eventName, data);
}
};
});
The SkillsController can then subscribe to a specific event like so:
notifier.subscribe($scope, function(event, data) {
$scope.skills = data;
}, 'onSkillsLoaded');
The curriculo service calls (at the end of the getSkills()) notifyand provides an event. In this case the same event as you subscribed to in the SkillsController.
notifier.notify('onSkillsLoaded', result);
All in all, that's the magic behind my little example. It's worth mentioning that you need to apply best practices to the code, since this is just to recreate your scenario. For best practices I suggest the Angular Style Guide by John Papa.
Update 1
I updated my example to show you deep linking. I simulate the deep link via
$state.go('.', {area: 2, skill: 5});
This way I can activate a certain state. Now each view has its activate function. Inside this function I do all the work that is neseccary for the initialization, e.g. selecting an area if the query param is set. As you know, you can access the params with the $state service. I had to use a $timeout to delay the init of the areas controller because the subscribe wasn't made yet. Please try to find a better solution to this problem. Maybe you can use promises or register each controller in a service which resolves if all controller have been initialized.
If anything has been selected I also use the go with an additional option to set the notify to false.
$state.go('.', {area: area.id, skill: skillId ? skillId : undefined}, {notify: false});
If notify is set to false it will prevent the controllers from being reinitialized. Thus you can only update the URL and no state change will happen.
I realize that "the correct way" is subjective but I think this is a specific enough question that there is a best practices approach to it.
I'm new to Angular and trying to understand what the prescribed mechanism is for the following.
I have a series of dependant <SELECT>s which don't have any data associated with them at the time of launch.
The first one goes and fetches some items (that need to be populated as <option>s) via $http and the resulting JSON response is used to populate the next <SELECT>.
Depending on the response there may or may not be subsequent <SELECT>s, meaning if the user chooses option 1 there is a follow up choice but if they choose option 2 there isn't and I don't wish to hard code all the possible <SELECT>s into my model, I need it to be elastic.
So... from what I'm reading, the controller is not the right place to deal with this, and I should use a directive, though I'm having a hard time finding documentation on how exactly to handle the specifics of modifying the DOM as necessary to introduce new <SELECT>s as required. Additionally I'm not clear on where I should do my AJAX calls and how to connect them to whatever it is that will respond by modifying the UI.
I'm hoping someone can point me to some effective tutorial on how to deal with this (or similar) scenarios.
You are absolutely right that you need to use a directive to do the DOM manipulation, but in this case I don't think you'll have to write any of your own, you can use the built in ones that angular provides.
You should also stick to the best practice of providing your data (in this case, option values etc.) through a service. I'm going to assume you can handle the service side of things yourself. Because I am lazy I will just manually enter the data into the scope in my controller (you will still need a minimal controller to get the data from the service to the scope).
The first one goes and fetches some items (that need to be populated as <option>s) via $http and the resulting JSON response is used to populate the next <SELECT>.
It's not clear if you've worked out how to do this already or not, but you'll want to use the ng-options directive:
Provided you have data like this:
[
{ key: "Ford fiesta", value: "fordFiesta" },
{ key: "Audi TT", value: "audiTT" }
]
You can use the following
markup:
<select ng-model="selection"
ng-options="options.label as (options.key, options.value) in options">
Again, I'm being lazy so I used a simpler markup later where the key is the same as the value.
Depending on the response there may or may not be subsequent <SELECT>s, meaning if the user chooses option 1 there is a follow up choice but if they choose option 2 there isn't and I don't wish to hard code all the possible <SELECT>s into my model, I need it to be elastic.
For this you will need a more complex data structure than simply the array of options. For my example I devised something like the following:
[
{
modelName: "apples"
title: "Do you like apples?"
options: [ "yes", "no" ]
followUps: [
{
modelName: "appleType"
condition: "yes"
title: "Do you prefer Granny smiths or snow white?"
options: ["Granny Smith", "Snow White"]
}
]
},
{
modelName: "pears"
title: "Do you like pears?"
options: [ "yes", "no" ]
}
]
modelName will be how we save the results, followUps are dependent selects that are shown if the answer is condition.
You can then make use of ng-repeat to loop through this array.
Note the below code is Jade:
div.question(ng-repeat="select in selects")
span.title {{select.title}}
select(ng-model="results[select.modelName]",
ng-options="option for option in select.options")
div.subquestion(ng-repeat="subselect in select.followUps",
ng-show="!subselect.condition ||
subselect.condition == results[select.modelName]")
span.title {{subselect.title}}
select(ng-model="results[subselect.modelName]",
ng-options="option for option in subselect.options")
Essentially what you are doing is repeating your title followed by the select populated with the options (using ng-options), as well as all the followUps selects, but we control the visibility of the followUp selects based on whether the answer matches the condition or not using the ng-show directive.
This could be neatened up significantly (make your own directive with a template), and also made tolerant to an infinite number of layers of followUps, but hopefully this puts you on the right track?
See it working in this plunker.
Here is a good video from the AngularJS conference in Salt Lake City... he covers some of what you are interested in within 20 min.
http://youtu.be/tnXO-i7944M?t=15m20s
AJAX request belongs in a factory, and that factory is injected in the controller as a dependency.
EDIT: So totally missed the guts of your question, sorry about that. You would setup the select using the ng-repeat directive like so:
<select ng-repeat="select in selects">
<option ng-repeat="option in select.options" handle-fetch-select>{{ option }}</option>
</select>
app.factory('selectFactory', function (['$http']){
var factory = {};
factory.getSelects = function(){
return $http.get('/selects.json');
}
factory.getSomeOtherSelect = function(){
return $http.get('/otherSelects.json');
}
return factory;
});
app.controller('SelectController', function( ['$scope', 'selectFactory'] ){
$scope.selects = [];
init();
function init(){
selectFactory.getSelects().success(function(data){
//would be $scope.selects = data; just mocking a response
$scope.selects = [ { label : 'Foo', options : ['opt1', 'opt2', 'opt3']} ]};
});
}
});
app.directive('handleFetchSelect', function(['$scope', 'selectFactory']){
return function(scope, element, attrs){
element.bind('click', function(){
//
//Add logic to determine if a fetch is required or not
//
//if (noFetchRequired)
// return;
//determine which selects to request from server
switch (expression) {
case (expression1) :
selectFactory.getSomeOtherSelect.success(function(returnedArrayOfSelects){
scope.apply(function(returnedArrayOfSelects){
scope.selects.concat(returnedArrayOfSelects);
});
}).error(function(){});
break;
}
}
})
});
Didn't debug this stub so... <-- disclaimer :) Hopefully you get the idea.
I am new to mobile application development with Ionic. On login and logout I need to reload the page, in order to refresh the data, however, $state.go('mainPage') takes the user back to the view without reloading - the controller behind it is never invoked.
Is there a way to clear history and reload the state in Ionic?
Welcome to the framework! Actually the routing in Ionic is powered by ui-router. You should probably check out this previous SO question to find a couple of different ways to accomplish this.
If you just want to reload the state you can use:
$state.go($state.current, {}, {reload: true});
If you actually want to reload the page (as in, you want to re-bootstrap everything) then you can use:
$window.location.reload(true)
Good luck!
I found that JimTheDev's answer only worked when the state definition had cache:false set. With the view cached, you can do $ionicHistory.clearCache() and then $state.go('app.fooDestinationView') if you're navigating from one state to the one that is cached but needs refreshing.
See my answer here as it requires a simple change to Ionic and I created a pull request: https://stackoverflow.com/a/30224972/756177
The correct answer:
$window.location.reload(true);
I have found a solution which helped me to get it done. Setting cache-view="false" on ion-view tag resolved my problem.
<ion-view cache-view="false" view-title="My Title!">
....
</ion-view>
Reload the page isn't the best approach.
you can handle state change events for reload data without reload the view itself.
read about ionicView life-cycle here:
http://blog.ionic.io/navigating-the-changes/
and handle the event beforeEnter for data reload.
$scope.$on('$ionicView.beforeEnter', function(){
// Any thing you can think of
});
In my case I need to clear just the view and restart the controller. I could get my intention with this snippet:
$ionicHistory.clearCache([$state.current.name]).then(function() {
$state.reload();
});
The cache still working and seems that just the view is cleared.
ionic --version says 1.7.5.
None of the solutions mentioned above worked for a hostname that is different from localhost!
I had to add notify: false to the list of options that I pass to $state.go, to avoid calling Angular change listeners, before $window.location.reload call gets called. Final code looks like:
$state.go('home', {}, {reload: true, notify: false});
>>> EDIT - $timeout might be necessary depending on your browser >>>
$timeout(function () {
$window.location.reload(true);
}, 100);
<<< END OF EDIT <<<
More about this on ui-router reference.
I was trying to do refresh page using angularjs when i saw websites i got confused but no code was working for the code then i got solution for reloading page using
$state.go('path',null,{reload:true});
use this in a function this will work.
I needed to reload the state to make scrollbars work. They did not work when coming through another state - 'registration'. If the app was force closed after registration and opened again, i.e. it went directly to 'home' state, the scrollbars worked. None of the above solutions worked.
When after registration, I replaced:
$state.go("home");
with
window.location = "index.html";
The app reloaded, and the scrollbars worked.
The controller is called only once, and you SHOULD preserve this logic model, what I usually do is:
I create a method $scope.reload(params)
At the beginning of this method I call $ionicLoading.show({template:..}) to show my custom spinner
When may reload process is finished, I can call $ionicLoading.hide() as a callback
Finally, Inside the button REFRESH, I add ng-click = "reload(params)"
The only downside of this solution is that you lose the ionic navigation history system
Hope this helps!
If you want to reload after view change you need to
$state.reload('state',{reload:true});
If you want to make that view the new "root", you can tell ionic that the next view it's gonna be the root
$ionicHistory.nextViewOptions({ historyRoot: true });
$state.go('app.xxx');
return;
If you want to make your controllers reload after each view change
app.config(function ($stateProvider, $urlRouterProvider, $ionicConfigProvider) {
$ionicConfigProvider.views.maxCache(0);
.state(url: '/url', controller: Ctl, templateUrl: 'template.html', cache: false)
cache: false ==> solved my problem !
As pointed out by #ezain reload controllers only when its necessary. Another cleaner way of updating data when changing states rather than reloading the controller is using broadcast events and listening to such events in controllers that need to update data on views.
Example: in your login/logout functions you can do something like so:
$scope.login = function(){
//After login logic then send a broadcast
$rootScope.$broadcast("user-logged-in");
$state.go("mainPage");
};
$scope.logout = function(){
//After logout logic then send a broadcast
$rootScope.$broadcast("user-logged-out");
$state.go("mainPage");
};
Now in your mainPage controller trigger the changes in the view by using the $on function to listen to broadcast within the mainPage Controller like so:
$scope.$on("user-logged-in", function(){
//update mainPage view data here eg. $scope.username = 'John';
});
$scope.$on("user-logged-out", function(){
//update mainPage view data here eg. $scope.username = '';
});
I tried many methods, but found this method is absolutely correct:
$window.location.reload();
Hope this help others stuck for days like me with version: angular 1.5.5, ionic 1.2.4, angular-ui-router 1.0.0
I want to call method in controller from view when click a cell in grid.
{ header: "<img src='/Content/images/icons/page_white_acrobat.png'/>", width: 30, dataIndex: 'documents', sortable: true, renderer: this.hasDocument,
listeners: {
click: function () {
//how to call method in controller?
}
}
},
Anybody know, please advice me.
Thanks!
You will have a lot of tutorials for extjs 4 on the official forum of the project by Sencha.
When I provide some usefull link to good starting tutorials... with a specific one's about grid management... I think people could look at it really before voting down. Look at it and see yourself some very better ways to do what the question asker wants to do.
Providing direct answers are not always the best way to learn.
Anyway... the following will do the trick:
var controller = this.getController(Ext.String.capitalize(config.controller));
/* where config was an argument of your callback method) */
I suggest you to decouple as much as possible View from Controllers and View from Model. If you look at the projects I have linked, you will find in the Viewport.js a good way to do that. It is calling the controller with .callParent(arguments) method call at the end of these short script.
I am sure the original person have come across the answer or did something to do the trick.
But for the people that may have the same question here is a quick example of what to do:
Don't put a listener in to (MVC)-VIEW of your application. Give the element an ID (in this case the grid)
In the (MVC) - CONTROLLER add this function:
init : function(app) {
this.control({
'myWindow': {
afterrender : this.doAfterRender
/*SAMPLE*/
},
'myWindow #someGrid_ID' : {
select: this.doSelect
/* THIS FUNCTION IS LOCATED in the Controller*/
}
});
},
doSelect : function() {
/*....*/
}
now the controller will listen for the event and react on it.
I hope this helps a few people who might struggle with this.
look at :
http://docs.sencha.com/extjs/4.1.0/#!/guide/application_architecture
http://docs.sencha.com/extjs/4.1.0/#!/api/Ext.dom.Query
Dom Query - Explained:
"myWindow #someGrid_ID" - The dom Query note the # it refers to the ID of the element.
"myWindow" - refers to my window's Alias.
"someGrid_ID" - refers to my grid's ID.
(The grid is a child element of "myWindow")
hope this helps