High level
I header view and a main view in my angular application. I want the header to have a "back" button that is show/hidden based on the page I'm on.
What I did
app.js (snippet)
app.factory('globalServices', function() {
return {
showBack : false,
back: function() {
window.history.back();
}
};
});
header.html (snippet)
<a class="button-prev" ng-click="back()" ng-show="showBackButton">
Back
</a>
headerCtrl.js (snippet)
$scope.showBackButton = globalServices.showBack;
$scope.back = function() {
globalServices.back();
};
subPageCtrl.js (snippet)
globalServices.showBack = true;
Problem
The button viability isn't refreshed after the value is changed. I only see the value changed after I move one more page.
Is there a way to fix it?
I'm also open for a different approach.
Edit
Trying to call $scope.$apply(); also failed with error $digest already in progress because I'm changing this value as part of the constructor of subPageCtrl.
$scope.showBackButton = globalServices.showBack;
only set's showBackButton to globalServices.showBack once (during controller initialization). Future changes to globalServices.showBack aren't propagated to the $scope.showBackButton, which is the value that your UI is bound to.
You have two options:
1)
$scope.$watch('globalServices.showBack', function(){
$scope.showBackButton = globalServices.showBack;
}
This option will watch globalServices.showBack for changes, and then set $scope.showBackButton to match on any change.
or
2)
$scope.globalServices = globalServices;
<a class="button-prev" ng-click="globalServices.back()" ng-show="globalServices.showBackButton">
Back
</a>
This option exposes globalServices directly to your UI. This is how I would do it.
This is because when booleans are passed, they are assigned by value (they are a by value type) so when you are doing $scope.showBackButton = globalServices.showBack; it is assigning the value of $scope.showBackButton to the value of globalServices.showBack so if you change the value of globalServices.showBack it won't change the value of $scope.showBackButton.
To fix this you should used an object which is assigned by reference:
app.factory('globalServices', function() {
return {
showButtonDetails: {
showBack : false
},
back: function() {
window.history.back();
}
};
});
<a class="button-prev" ng-click="back()" ng-show="showButtonDetails.showBack">
Back
</a>
$scope.showButtonDetails = globalServices.showButtonDetails;
$scope.back = function() {
globalServices.back();
};
globalServices.showButtonDetails.showBack = true;
Related
Im using ui.router and including my navigation like this in my main html file:
<header ng-if-start="logedin()"></header>
<navigation ng-if-end="logedin()"></navigation>
the logedin() boolean will be set through the angular.module().run() in this function:
$rootScope.$on('$stateChangeStart', function(e, to)
If i click logout in one of the navigation, the controller of the navigation will trigger this function:
$scope.logout = function() {
store.remove('jwt');
$state.go('login');
}
The Problems is after the $state.go the navigation is not hiding, but after refreshing the page.
Do i have to rerender the main index template/view (and then how)? Or how could I solve this problem?
So I solved it myself. Sorry for not providing the logedin() method.
The problem was:
$scope.logedin = function() {
return $rootScope.logedin
}
The $rootScope.logedin was set in the angular.module().run()-function.
To solve it i had to create a simple getter/setter service.
angular.module('sample')
.service('sharedLogedIn', function () {
var property = true;
return {
getProperty: function () {
return property;
},
setProperty: function(value) {
property = value;
}
};
});
Good to know the issue was resolved. What might be happening is your values are not propagated... I might do this to troubleshoot:
<header ng-if="loggedinBool"></header>
<navigation ng-if="loggedinBool"></navigation>
1) Assign loggedin() value to a scope model or service property (preferably) loggedinBool and see if the values are propagated after logout which changes loggedinBool value.
2) If that does not work try $broadcast/$emit in loggout and capture that to change value of loggedinBool. This should automatically provide two-way-binding else try $scope.digest() or $scope.apply() method to see if values propagates.
In angularjs scope.$watch() can be used to execute a function each time a variable value changes.
scope.$watch('myvar', function(newValue, oldValue) {
changeCallback();
});
(The angularjs sample is only for showing what i want to do. I want to use only rivets.js, not anglarjs at all.)
I could use an event listener on the element that can change the value, but then i have to have event listeners everywhere where the variable value might get changed from.
<input type='text' rv-on-change='changeCallback' rv-value='myvar'>
Or if the value gets changed from javascript code i would have to execute the change function from there too.
myvar = 'changed value';
changeCallbacl()
QUESTION: Is there a way to execute a function each time a variable value changes in rivers.js without adding any code to the other end where the value gets changed from?
Found the solution here http://jsfiddle.net/nsisodiya/2mkjx44j/ . You need to use sightglass wich is a dependency for rivets and included in rivets.bundled.min.js .
// configure sightlass with same adapters as rivets
sightglass.adapters = rivets.adapters;
sightglass.root = '.';
// listen for changes on list.val
// list is an object and val is property to listen for changes on
sightglass(list, 'val', function() {
log('value changed...');
});
Here is a working sample on codepen
http://codepen.io/mstadius/pen/azroda
And here is code of same sample
html
<div id='app'>{list.val}<br>
<input rv-value='list.val'><br>
<button rv-on-click='list.reset'>Reset</button>
</div>
<div id='log'></div>
js
var log = function(msg){
$('#log').prepend('<div>'+msg+'</div>');
};
var list = {
val: 1
, reset: function(){
log('Clicked reset...');
list.val = 1;
}
};
var a = rivets.bind($('#app'), {list: list});
sightglass.adapters = rivets.adapters;
sightglass.root = '.';
sightglass(list, 'val', function() {
log('value changed...');
});
I want to load my page based on default item selected in the combobox and reload on changing selected item (knockout).
Currently what I am doing is loading the form on click of a button. Here is my code. Please suggest me as I am very new to knockout.
html:
<select data-bind="options : employeeList, optionsText : 'name', value : selectedEmployeeList"></select>
</span>
<button class="toolbar-button" data-bind="click : load, disable :loading">Load employee</button>
js:
var filterVM = function () {
this.employeeList = ko.observableArray();
this.selectedEmployeeList = ko.observable();
};
dataService.getEmployeeLists(this.viewData.EmployeeId).then(loadEmployeeLists).then(enableControls);
filterVM.prototype.load = function () {
var selectedEmployeeList = this.selectedEmployeeList();
var self = this;
if (selectedEmployeeList) {
disableControls();
dataService.getEmployeeByEmployeeList(this.viewData.employeeId, selectedEmployeeList.id)
.done(loadPEmployeeSpread);
}
};
You can call your load function on change of selected value selectedEmployeeList
Add following code in constructor
this.selectedEmployeeList .subscribe(function(newValue) {
//This function is going to get called on every change of the selectedEmployeeList
//Add here your code to load the page
}, this);
subscribe :- is facility provided by knockout that it tracks every change made in the subscribed variable and calls function on every change.
I have a checkbox that I would like to trigger a simple 'select all' functionality. The problem is that I can't figure out how to connect the checkbox's action to an action in my controller so that I can actually update the records.
App.LanguagesController = Ember.ArrayController.extend({
actions: {
toggleAllVisibility: function() {
var newVisibility = !this.get('allAreVisible');
var needingVisibilityChange = this.filterBy('visible', !newVisibility);
needingVisibilityChange.setEach('visible', newVisibility);
}
},
allAreVisible: function(param) {
return this.filterBy('visible', false).get('length') === 0;
}.property('#each.visible'),
});
In my template, I have the following input helper
{{input type='checkbox' checked=allAreVisible}}
This properly updates the checkbox when I change the elements manually (i.e. if all of them are selected, then checkbox updates), but no actions fire when I toggle the checkbox.
It looks like in older versions of Ember.js I could simply add an action parameter to the input helper but that doesn't work anymore. I'm assuming I need to setup something that observes when the computed property attempts to change, but I couldn't find anything in the docs or other help.
I also tried extending checkbox to send the click event:
App.AllLanguagesCheckbox = Ember.Checkbox.extend(Ember.ViewTargetActionSupport, {
click: function() {
this.triggerAction({
action: 'toggleAllVisibility'
});
}
});
And then loaded that in my template with
{{view App.AllLanguagesCheckbox checkedBinding=allAreVisible}}
That allows the checkbox to trigger the action, but does not update based on the computed property in the controller.
I feel like I'm missing something obvious here.
EDIT
Based on kingpin2k's answer below, here's the working controller code:
App.LanguagesController = Ember.ArrayController.extend({
toggleAllVisibility: function() {
var newVisibility = !this.get('controller').get('allAreVisible');
var needingVisibilityChange = this.get('controller').filterBy('visible', !newVisibility);
needingVisibilityChange.setEach('visible', newVisibility);
},
allAreVisible: function(param) {
return this.filterBy('visible', false).get('length') === 0;
}.property('#each.visible'),
});
It's not called with the normal scope so you have to explicitly go through the controller to get the model array, but it works as expected now.
Here's the accompanying input helper:
{{input type='checkbox' checked=allAreVisible change=toggleAllVisibility}}
The problem is your checkbox is connected to a computed property, the computation should derive the value (aka you shouldn't be setting it), which is what would be happening when someone tries to check.
_allAreVisible:false,
allAreVisible: function(param) {
if(this.filterBy('visible', false).get('length') === 0){
// set to true;
// else set to false
}.observes('#each.visible'),
http://emberjs.jsbin.com/abODIKoj/1/edit
I have a case were I need to choose the template of the view based on the initial property value of the controller. Thus I need to access the controller while I am inside the init hook of the view but when i access the controller it returns "null".
MyApp.ApplicationController = Em.Controller.extend({
templateVersion: 'small'
});
MyApp.ApplicationView = Em.View.extend({
init: function(){
this._super();
console.log('Controller is: ',this.get('controller'));
if(this.get('controller').get('templateVersion') == 'small')
{
this.set('templateName', 'application-small');
} else {
this.set('templateName', 'application-bigger');
}
}
});
This is not the real case but an example for the real scenario.
For an example I have setup a jsbin here
I guess a more appropriate way of doing this would be by determine dynamically the templateName, something like the following:
MyApp.ApplicationView = Ember.View.extend({
templateName: function() {
if (this.get("controller.templateVersion") == "small") {
return "application-small";
} else {
return "application-bigger";
}
}.property('controller.templateVersion')
});
Doing it this way you dont need to hook into the init function and thus not having your controller properties available.
Here your updated jsbin.
Update
After your last comment I realized that the delay is the important part to make your use case work, here is an improved version which indeed changes even if the templateVersion is initially not defined and get's setted with some delay, this time we observe the templateName property of the view and invoke a rerender.
MyApp.ApplicationView = Ember.View.extend({
templateName: function() {
if (this.get("controller.templateVersion") == "small") {
return "application-small";
} else {
return "application-bigger";
}
}.property('controller.templateVersion'),
templateChanged: function() {
this.rerender();
}.observes('templateName')
});
And here another jsbin with the new version with a simulated delay of 2 seconds, but it could be whatever value.
Hope it helps.