I have 2 controllers like below,
app.controller('ParentMenuController',
function ($scope,MenuService) {
$scope.contentLoaded = false;
$scope.showButton = false;
$scope.showButton = MenuService.getStatus();
});
Controller2:
app.controller('ChildMenuController',
function ($scope,MenuService) {
MenuService.setStatus(true);
});
Service:
app.factory('MenuService', function ($q,$http) {
var status= false;
var setStatus=function(newObj){
status=newObj;
};
var getStatus=function(){
return status;
};
return {
getStatus:getStatus,
setStatus:setStatus
};
});
I am not able to set the status to true, but the below line of coding is not at all executing, so the status is always false.
$scope.showButton = MenuService.getStatus();
On button click or any action from user i can trigger the event, but my requirement is while page load, the button should not be visible. When childMenu controller executes, then parent controller button should be visible. I dont want to use $broadcast which requires $rootscope.
Note: My controller and html has hundereds of lines. I just pasted here required code for this functionality. ChildMenuController(childMenu.html) has separate html and ParentMenuController(parentMenu.html) has separete html.
So $scope.showButton is not available in ChildMenucontroller. Both html is used as directive. Main html is index.html.
See this sample:
http://plnkr.co/edit/TepAZGOAZZzQgjUdSpVF?p=preview
You need to use a wrapper object so that it's properties are changed instead of the main object.
app.controller('ParentMenuController',
function ($scope,MenuService) {
$scope.contentLoaded = false;
$scope.showButton = MenuService.getStatus();
});
app.controller('ChildMenuController',
function ($scope,MenuService) {
MenuService.setStatus(true);
});
app.factory('MenuService', function ($q,$http) {
var status= {value:false};
var setStatus=function(newObj){
status.value=newObj;
};
var getStatus=function(){
return status;
};
return {
getStatus:getStatus,
setStatus:setStatus
};
});
It's exactly the same as your code, but state is now an object with a value property. The state object is always the same one so that when the value is changed in the service the changes are propagated to everyone that has ever requested that object.
Actually your service is getting called and this piece of line is executing
$scope.showButton = MenuService.getStatus();
but once your child controller got loaded you are only setting the status but in order to show button you should getStatus after setting it
Like this,
app.controller('ChildMenuController', function($scope, MenuService) {
MenuService.setStatus(true);
$scope.$parent.showButton = MenuService.getStatus();
});
this will set the button to true and it will be shown.
I have done a sample with your code please take a look into it
DEMO
Related
I have the following in a controller, which is designed to show the content after it's loaded. Should in theory be pretty simple but is not playing ball, so I'm trying to understand what I'm doing wrong. This is for a view containing an ionic slide box which is what I'm trying to hide until the data is loaded, and with an ion-refresher for pull to refresh, hence the $scope.broadcast('scroll.refreshComplete');
//initial controller vars:
$scope.data = {};
$scope.data.loading = true;
$scope.data.user = 1;
$scope.data.week = {};
$scope.data.getContent = function() {
var now = new Date().getTime();
serverRequestFactory.getWeek(now)
.success(function(response){
$scope.data.week = response;
$scope.data.loading = false;
$ionicSlideBoxDelegate.update();
$scope.$broadcast('scroll.refreshComplete');
})
.error(function(response){
$scope.data.loading = false;
$scope.$broadcast('scroll.refreshComplete');
})
}
if($scope.data.user == 1){
//calls above on view load
$scope.data.getContent();
//$scope.data.getContent();
}
The weird thing is the above works if I uncomment the second call to $scope.data.getContent() but I don't know why. I've tried $scope.apply() both before and after setting the $scope.data.week object and the update of the slide box delegate. Where's my error?
EDIT: So I just added an ng-repeat directive to one of the slide box items:
<ion-slide-box on-slide-changed="slideHasChanged($index)" ng-hide="data.loading">
<ion-slide class="customSlidePadding" ng-repeat="item in data.week.items">
And now the entire slide box respects the initial ng-hide value and shows up without a second function call... There surely has to be an angular reason adding the directive to a nested item in the hidden slide box makes it work?
If you are using $scope.data.week on your view you should initialize it, otherwise angular does not create $watch over it after the first call.
Just do.
$scope.data.week = []; //or whatever data you want...
you should do this before calling the async request.
Unless your template is actually calling your function via an event handler
on-event="data.getContent()"
or via some binding mechanism (which I don't recommend)
<p>{{data.getContent()}}
Then you aren't actually calling this method. Nothing I've seen in the supplied code is actually calling this, you've only defined the method which calls itself in the if block.
Try explicitly calling it:
$scope.data.getContent = function() {
var now = new Date().getTime();
serverRequestFactory.getWeek(now)
.success(function(response){
$scope.data.week = response;
$scope.data.loading = false;
$ionicSlideBoxDelegate.update();
$scope.$broadcast('scroll.refreshComplete');
})
.error(function(response){
$scope.data.loading = false;
$scope.$broadcast('scroll.refreshComplete');
})
}
if($scope.data.user == 1){
//calls above on view load
$scope.data.getContent();
//$scope.data.getContent();
}
}
//explicitly calling
$scope.data.getContent();
I have a single shared jQuery function that checks a RadioButton selection: if 1 is selected, it hides a span, otherwise it shows it.
This shared function is called both on startup and on Change, because on startup, it needs to do the same thing. The startup works, but the onChange reference does NOT work:
JS_OBJ = {
toggleTier : function() {
if ($('input[name="tier"]:checked').val() == 'Y_YES')
{
$('#tierSpan').hide();
}
else
{
$('#tierSpan').show();
}
},
// this is called from document.onReady - it comes here, OK
onReady : function() {
// on startup, toggle Tier - works OK
this.toggleTier();
// Also link the radio button Change to this shared function
$('input[name="tier"]:radio').change(function () {
alert('About to enter toggle...');
// NEVER COMES HERE - Object doesn't support this property or method
this.toggleTier();
});
}
};
the this is changing value as it is passing thru the different zones. when it is first instantiated, it has a good value, but the radiobutton:change has a different this
I was able to change it get it to work:
$('input[name="tier"]:radio').change(function () {
alert('About to enter toggle...');
self; //closure
toggleTier();
});
see this: What underlies this JavaScript idiom: var self = this?
Inside the change event, this does not refer to the current JS_OBJ, it refers to the current event target in stead. You want to explicitly save your reference to this, so you can use it inside the event.
Example:
onReady : function() {
var me = this;
me.toggleTier();
// Also link the radio button Change to this shared function
$('input[name="tier"]:radio').change(function () {
me.toggleTier();
});
}
I have a modal dialog where the user can select files to be uploaded. The actual file select/upload is handled by ng-file-upload. When the user selects one or more file, they are added to a list in the dialog, showing progress, completion and failure statuses for each element. The list of items are handled inside a custom directive, since it's used other places as well.
I need to prevent the user from dismissing the dialog while files are still uploading, and that's a challenge for me, cause the button for closing the dialog is in one controller, while the list of uploads is in another (the directive controller). I have solved that by giving and empty list to the directive like this:
//extract from directive:
var directive = {
...
scope: {
'files': '='
}
}
//extract from usage
<uploadFiles files="files" />
Now the outer controller and the inner controller shares the list of files uploading.
So when the user tries to dismiss the dialog by clicking the Close button, I first check if the list contains files still uploading, and if so, I disable the button and display a spinner and a 'please wait'-text.
//from the outer controller
function onModalOk() {
if (uploadInProgress()) {
waitForCompletionBeforeClosingDialog();
} else {
closeDialog();
}
}
the waitForCompletionBeforeClosingDialog() is implemented by setting up a deep watch on the files array. Each time the watch is triggered, I loop through to see if every file has completed. If so, I delete the watch and dismiss the dialog.
function waitForCompletionBeforeClosingDialog() {
$scope.showWaitText = true;
var unregisterWatchForCompletion = $scope.$watch('files', function(files) {
if (allCompleted(files)) {
unregisterWatchForCompletion();
closeDialog();
}
}, true);
}
Everything is working ok, except for one little thing...
In the console, I get this error:
TypeError: Illegal invocation
at equals (angular.js:931)
at equals (angular.js:916)
at Scope.$digest (angular.js:14302)
at Scope.$apply (angular.js:14571)
at angular.js:16308
at completeOutstandingRequest (angular.js:4924)
at angular.js:5312
and it's fired in a tight loop.
I have tried debugging this error, but with no luck..
Do anyone have any ideas?
Is there better ways of doing this all together?
What about using an $httpInterceptor to keep count of the amount of active requests?
something like:
angular.module('someModule').provider('httpStatus', ['$httpProvider', function ($httpProvider) {
var currentRequestCount = 0;
var interceptor = ['$q', function ($q) {
return {
request: function (config) {
currentRequestCount++;
return config;
},
response: function (response) {
currentRequestCount--;
return response;
},
responseError: function (rejection) {
currentRequestCount--;
return $q.reject(rejection);
}
}
}];
$httpProvider.interceptors.push(interceptor);
this.$get = function () {
return {
isWaiting: function () {
return currentRequestLength > 0;
}
}
};
}]);
You could inject the httpStatus service into your dialog and use it to disable the buttons if there are any active requests. May need to add the requestError handler also.
https://docs.angularjs.org/api/ng/service/$http
I am trying to dynamically get and set a pageTitle for my sample Meteor app, ideally I'd like to use jQuery, but any other solution would be good too.
I am trying to do it the rough way by setting a session when a certain div class exists on the page, but it's not working for some reason.
Basically, I have this:
Template.layout.helpers({
pageTitle: function() { return Session.get('pageTitle'); }
});
And I want to do something like
if ($('section').hasClass('single')) {
Session.set('pageTitle', 'Post Page');
}
Any idea ho to make this work? thanks!
You need to call it in a controller or the templates rendered section like this:
Template.layout.helpers({
pageTitle: function() {
return Session.get('pageTitle');
}
});
// set when a page template renders
Template.posts.rendered = function() {
setPageTitle("Blog Posts");
};
// or with Iron Router on *every* route using some sort of variable
Router.onBeforeAction(function() {
var someDynamicValue = "FOO";
setPageTitle(someDynamicValue);
});
// or in an IronRouter Controller
PostsController = RouteController.extend({
onBeforeAction: function() {
setPageTitle("Blog Posts");
}
});
// helper function to set page title. puts a prefix in front of page, you
// could opt set a boolean to override that
function setPageTitle(titleName) {
var prefix = "My Site - ";
if ($('section').hasClass('single')) {
Session.set('pageTitle', prefix + titleName);
}
}
As #Kuba Wyrobek pointed out, I needed to use the Template.layout.rendered function.
Here's a snippet that works
Template.postsList.rendered = function(){
Session.set('pageTitle', 'Listing Page')
};
I have set up an ember checkbox:
{{view Ember.Checkbox checkedBinding='isChecked'}}
This checkbox is bound to this controller:
App.SettingsController = Ember.Controller.extend({
isChecked: false,
isItChecked: function(){
var me = this;
$.getJSON('ajax/checkIfUseLowRes.php', function(data){
//console.log(data);
//me.isChecked = data;
me.set('isChecked', data);
//console.log(me.isChecked);
})
},
sendSetting: function(){
var isChecked = this.isChecked;
//this.set('isChecked', !isChecked);
console.log(isChecked);
$.post('ajax/setLowRes.php', {useLowRes: isChecked});
App.nameSpace.set('needsRefresh', true);
}.observes('isChecked')
});
Essentially it is a checkbox which posts a setting, my problem is setting the original state of the checkbox. When I load the route, I want to have isItChecked() run to set the checkbox's original state.
Example:
1. I go in to settings, click the checkbox
2. I leave the site and come back to the settings page
Here is where I want the isItChecked() function to run to query the server and see if the setting is set
If it is set, the get request returns true, I want the checkbox to be checked, hence setting me.isChecked to data (true)
as of right now I can't figure out how to do this, can anyone help me?
I've tried setting it in my App.SettingsRoute's model, but that doesn't seem to be able to run the function in my controller.
Thanks.
JSBin:http://jsbin.com/ukuwiq/1/edit
One way to accomplish this is by using the route's setupController method.
App.SettingsRoute = Ember.Route.extend({
setupController: function(controller, model) {
$.getJSON('ajax/checkIfUseLowRes.php', function(data){
console.log('setting isChecked to: ', data);
controller.set('isChecked', data);
})
}
})
This should get the job done with the fewest changes to your existing code but does have some drawbacks and is not exactly the-ember-way. Probably it would make more sense to have a model object that represents your settings, move ajax code to that model object and use the route's model hook to trigger as needed. See ember-without-ember-data for an example of how that can work.
as Mike says, you can use 'setupController' or you could also use this:
App.SettingsRoute = Ember.Route.extend({
activate: function() {
this.controllerFor('settings').isItChecked();
}
})
Could you not just use the init function? e.g.
App.IndexController = Ember.ObjectController.extend({
init: function() {
console.log("function run");
},