AngularJs: ng-if reacting too late - javascript

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.

Related

meteor: recalculating helper value after typing some input values

My template looks like this:
<input type="text" name="inputValue" id="inputVal">
{{#each group}}
<section>
<h2>{{title}}</h2>
{{#each element}}
<p>{{description}} {{numberValue}} {{unit}}</p>
{{/each}}
</section>
{{/each}}
So far this is working. But now I want to do a little calculating by multiplicate the value of the inputfield with the numberValue (group.element.numberValue) and use this with {{calculatedValue}} in the template:
<p>{{description}} {{calculatedValue}} {{unit}}</p>
Therefore I need a helper:
Template.usedTemplate.helpers({
'calculatedValue': function() {
return document.getElementById("#inputVal").value * this.numberValue;
}
});
I hope this is the correct coding for creating the helper as the values are used in an each-loop.
But my problem is, that the user types the inputValue after the site has been loaded. So what do I have to do, to recalculate the rows after the user typed some values in the input field?
(Also it would be great if there would be a kind of output info text at the beginning as there is no value given by default. After typing some value the result is been shown.)
I think you can simply use Session or ReactiveVar to solve your problem
Session.setDefault("input-value", "")
Template.usedTemplate.events({
'keypress #inputVal': function (event) {
Session.set("input-value", Template.instance().$("#inputVal").val());
}
});
Template.usedTemplate.helpers({
'calculatedValue': function() {
return Session.get("input-value") * this.numberValue;
}
});
or use ReactiveVar
Template.usedTemplate.onCreated(function(){
this.inputValue = new ReactiveVar("")
})
Template.usedTemplate.events({
'keypress #inputVal': function (event) {
Template.instance().set(Template.instance().$("#inputVal").val());
}
});
Template.usedTemplate.helpers({
'calculatedValue': function() {
return Template.instance().get() * this.numberValue;
}
});
I suggest using ReactiveVar, because Session is global val.
And you should add ReactiveVar to your app before using.
meteor add reactive-var
I haven't tested this code, but try this:
var userInput = new Tracker.Dependency;
Template.usedTemplate.events({
'keypress #inputVal': function (event) {
userInput.changed();
}
});
Template.usedTemplate.helpers({
'calculatedValue': function() {
userInput.depend();
return Template.instance().$("#inputVal").val() * this.numberValue;
}
});
Side note: document.getElementById() already expects an ID as an argument, so you don't need the # prefix.
Normally, the calculatedValue helper will not update automatically because it contains no reactive method calls, such as someCollection.find() or Session.get('xyz'). But using Tracker.Dependency, you can make anything reactive.

Angular render view after parameter changed in service

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;

Meteor JS: What is the best way to store states for a specific template instance?

I'm learning about Session and reactive data sources in Meteor JS. They work great for setting global UI states. However, I can't figure out how to scope them to a specific instance of a template.
Here's what I'm trying to do
I have multiple contenteditable elements on a page. Below each is an "Edit" button. When the user clicks on the Edit button, it should focus on the element and also show "Save" and "Cancel" buttons.
If the user clicks "Cancel", then any changes are eliminated, and the template instance should rerender with the original content.
Here's the code I have so far
// Helper
Template.form.helpers({
editState: function() {
return Session.get("editState");
}
});
// Rendered
Template.form.rendered = function(e){
var $this = $(this.firstNode);
var formField = this.find('.form-field');
if (Session.get("editState")) formField.focus();
};
// Event map
Template.form.events({
'click .edit-btn' : function (e, template) {
e.preventDefault();
Session.set("editState", "is-editing");
},
'click .cancel-btn' : function (e, template) {
e.preventDefault();
Session.set("editState", null);
},
});
// Template
<template name="form">
<div class="{{editState}}">
<p class="form-field" contenteditable>
{{descriptionText}}
</p>
</div>
Edit
Save
Cancel
</template>
// CSS
.edit-btn
.cancel-btn,
.save-btn {
display: inline-block;
}
.cancel-btn,
.save-btn {
display: none;
}
.is-editing .cancel-btn,
.is-editing .save-btn {
display: inline-block;
}
The problem
If I have more than one instance of the Form template, then .form-field gets focused for each one, instead of just the one being edited. How do I make so that only the one being edited gets focused?
You can render a template with data, which is basically just an object passed to it when inserted in to a page.
The data could simply be the key to use in the Session for editState.
eg, render the template with Template.form({editStateKey:'editState-topForm'})
you could make a handlebars helper eg,
Handlebars.registerHelper('formWithOptions',
function(editStateKey){
return Template.form({editStateKey:editStateKey})
});
then insert it in your template with
{{{formWithOptions 'editState-topForm'}}} (note the triple {, })
Next, change references from Session.x('editState') to Session.x(this.editStateKey)/ Session.x(this.data.editStateKey)
Template.form.helpers({
editState: function() {
return Session.get(this.editStateKey);
}
});
// Rendered
Template.form.rendered = function(e){
var $this = $(this.firstNode);
var formField = this.find('.form-field');
if (Session.get(this.data.editStateKey)) formField.focus();
};
// Event map
Template.form.events({
'click .edit-btn' : function (e, template) {
e.preventDefault();
Session.set(this.editStateKey, "is-editing");
},
'click .cancel-btn' : function (e, template) {
e.preventDefault();
Session.set(this.editStateKey, null);
},
});
Note: if you are using iron-router it has additional api's for passing data to templates.
Note2: In meteor 1.0 there is supposed to be better support for writing your own widgets. Which should allow better control over this sort of thing.
As a matter of policy I avoid Session in almost all cases. I feel their global scope leads to bad habits and lack of good discipline regarding separation-of-concerns as your application grows. Also because of their global scope, Session can lead to trouble when rendering multiple instances of a template. For those reasons I feel other approaches are more scalable.
Alternative approaches
1 addClass/removeClass
Instead of setting a state then reacting to it elsewhere, can you perform the needed action directly. Here classes display and hide blocks as needed:
'click .js-edit-action': function(event, t) {
var $this = $(event.currentTarget),
container = $this.parents('.phenom-comment');
// open and focus
container.addClass('editing');
container.find('textarea').focus();
},
'click .js-confirm-delete-action': function(event, t) {
CardComments.remove(this._id);
},
2 ReactiveVar scoped to template instance
if (Meteor.isClient) {
Template.hello.created = function () {
// counter starts at 0
this.counter = new ReactiveVar(0);
};
Template.hello.helpers({
counter: function () {
return Template.instance().counter.get();
}
});
Template.hello.events({
'click button': function (event, template) {
// increment the counter when button is clicked
template.counter.set(template.counter.get() + 1);
}
});
}
http://meteorcapture.com/a-look-at-local-template-state/
3 Iron-Router's state variables
Get
Router.route('/posts/:_id', {name: 'post'});
PostController = RouteController.extend({
action: function () {
// set the reactive state variable "postId" with a value
// of the id from our url
this.state.set('postId', this.params._id);
this.render();
}
});
Set
Template.Post.helpers({
postId: function () {
var controller = Iron.controller();
// reactively return the value of postId
return controller.state.get('postId');
}
});
https://github.com/iron-meteor/iron-router/blob/devel/Guide.md#setting-reactive-state-variables
4 Collection data
Another approach is to simply state by updating data in your collection. Sometimes this makes perfect sense.
5 update the data context
Session is often the worse choice in my opinion. Also I don't personally use #3 as I feel like being less tied to iron-router is better incase we ever want to switch to another router package such as "Flow".

Ember.js checkbox for array controller computed property

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

Can we access the respective controller of the view inside the init hook of the view

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.

Categories