I'm trying to figure out how I can clear the emails in the list. I've managed to create a function where I can remove emails one by one. But I can't seem to find a way on how to clear them all after sending clicking the send invite button.
I'm working on project using angular.js and below is a sample of the code that is a work in progress
<div class="content referral-sender-page">
<div class="wrapper">
<div class="main" ng-controller="ReferralDispatchController">
<h1 class="h large">Send Invites</h1>
<!-- TODO: Explanatory text -->
<div>
<section class="grid__item bp-md-one-third">
<h5 class="h medium gray">To</h5>
<span ng-repeat="e in emails" class="h small email-list">
<span remove-on-click ng-click="removeEmail(e)" class="email-item">{{ e }} <small class="close">X</small></span>
</span>
<div class="col-2">
<input class="input" type="email"
ng-model="email"
placeholder="Email">
<a ng-click="addEmail()" class="button pads primary" >+</a>
</div>
</section>
<section class="grid__item bp-md-two-thirds">
<h5 class="h medium gray">Message</h5>
<p>Write a message to send to your homies with your invite</p>
<textarea class="text-input" ng-model="message" rows="5">
This is awesome and you should try it!
</textarea>
</section>
</div>
<div class="space--bottom one-whole">
<a ng-click="sendReferral()" class="button pads primary">Send Email Invite</a>
</div>
</div>
</div>
</div>
var ctrls = angular.module('elstudio.controllers.site');
//Removes Element only
// ctrls.directive('removeOnClick', function() {
// return {
// link: function(scope, elt, attrs) {
// scope.removeEmail = function() {
// elt.remove();
// };
// }
// }
// });
ctrls.controller('ReferralDispatchController', function ($scope, UserService,
ReferralService) {
$scope.emails = [];
$scope.message = '';
$scope.removeEmail = function(e) {
var index = $scope.emails.indexOf(e);
$scope.emails.splice(index, 1);
};
$scope.addEmail = function() {
if (!$scope.email) {
$scope.$emit('notify', { message: 'Please provide a valid email address' });
return;
}
// If email already in list, ignore
// FIXME: Provide feedback
if ($scope.emails.indexOf($scope.email) != -1) {
$scope.email = '';
return;
}
$scope.emails.push($scope.email);
$scope.email = '';
};
$scope.sendReferral = function() {
if (!$scope.loginUser) {
$scope.$emit('notify', { message: 'Please sign up or log in to your Electric account.',
duration: 3000 });
angular.element('html, body').animate({ scrollTop: 0 }, 'slow');
angular.element('.login-toggle').click();
return;
}
if ($scope.email != '') {
$scope.emails.push($scope.email);
}
if (!$scope.emails) {
$scope.$emit('notify', { message: 'Please provide at least one email address' });
return;
}
var refer = {
emails: $scope.emails,
message: $scope.message
};
var sendSuccess = function() {
$scope.$emit('notify', { message: 'An invitation has been sent!',
duration: 4000 });
};
var sendFailed = function(error) {
// Retry?
$scope.$emit('notify', { message: "Couldn't send invitation",
duration: 4000 });
};
ReferralService.email(refer).$promise.then(sendSuccess, sendFailed);
};
});
$scope.email = []; used to clear the array.
var sendSuccess = function() {
$scope.$emit('notify', { message: 'An invitation has been sent!',
duration: 4000 });
$scope.email = [];
};
Please Use
var refer = {
emails: angular.copy($scope.emails),
message: angular.copy($scope.message)
};
Please view the detail
It will do deep copy. So when we change the value in $scope.emails and $scope.message copied value will not change
also clear the $Scope.email value in the sendSuccess function
Related
I have a form in my page with AngularJS and Stripe JS.
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.6/angular.min.js"> <script type="text/javascript">
var app= angular.module("app",[]);
app.config(function($interpolateProvider){
$interpolateProvider.startSymbol("[[[");
$interpolateProvider.endSymbol("]]]");
});
app.controller("Ctrl",function(stripeService, $scope, $rootScope){
$scope.name= "World";
$scope.stripeCompleted= false;
stripeService.start();
$rootScope.on("stripedone", function(e,stripeEvent){
$scope.stripeCompleted= stripeEvent.complete;
$scope.$apply();
});
});
app.service("stripeService", function($window,$rootScope){
function start(){
var btn= document.querySelectorAll("#test")[0];
var displayError= document.getElementById('card-errors');
var stripe= Stripe("{{ stripe_key }}");
var elements= stripe.elements();
var style= {
base: {
fontSize: "1.1875rem",
fontSmoothing: "always",
fontWeight: "600"
}
};
var card= elements.create("card", {style:style});
card.mount("#card-element");
card.addEventListener('change', function(event) {
if (event.error) {
displayError.textContent = event.error.message;
} else {
displayError.textContent = '';
}
if (event.complete) {
$rootScope.$broadcast("stripedone",event);
} else {
$rootScope.$broadcast("stripedone",event);
}
});
var formID= "register-form";
var form= document.getElementById(formID);
form.addEventListener("submit",function(event){
event.preventDefault();
stripe.createToken(card).then(function(result){
if(result.error) {
displayError.textContent= result.error.message;
} else {
stripeTokenHandler(result.token, formID);
}
});
});
return {"start":start};
}
});
// tut https://stripe.com/docs/stripe-js/elements/quickstart#create-form
function stripeTokenHandler(token, formID) {
// Insert the token ID into the form so it gets submitted to the server
var form = document.getElementById(formID);
var hiddenInput = document.createElement('input');
hiddenInput.setAttribute('type', 'hidden');
hiddenInput.setAttribute('name', 'stripeToken');
hiddenInput.setAttribute('value', token.id);
form.appendChild(hiddenInput);
// Submit the form
form.submit();
}
</script> <form id="register-form" name="regForm" method="post>
<input ng-model="reg.email" type="email" name="username">
<div id="stripe-wrapper">
<div id="card-element"></div>
</div>
<small id="card-errors" class="text-danger" role="alert">{{ ccErrMsg }}</small>
<br>
<button type="submit" ng-model="reg.btn" ng-disabled="!regForm.username.$valid>Register</button>
</form>
I want for my button to be un-clickable unless the user fills out the Stipe credit card section correctly. How do I make it so my button is disabled unless the the Stripe credit card fields are filled out correctly?
Update: Following karthick's answer gives me a new error:
angular.js:13642 TypeError: stripeService.start is not a function
at Object.<anonymous> ((index):135)
at Object.invoke (angular.js:4708)
at P.instance (angular.js:10177)
at n (angular.js:9096)
at g (angular.js:8459)
at angular.js:8339
at angular.js:1782
at m.$eval (angular.js:17378)
at m.$apply (angular.js:17478)
at angular.js:1780
You should use a AngularJS directive in this case. This is nothing to put in a controller, service, factory or component. Once you use a directive your code will be much smarter and become fully supported by AngularJS diggest cycles and DOM bindings. This is how the documentation introduces directives:
What are Directives? At a high level, directives are markers on a DOM element (such as an attribute, element name, comment or CSS class) that tell AngularJS's HTML compiler ($compile) to attach a specified behavior to that DOM element (e.g. via event listeners), or even to transform the DOM element and its children.
> Demo fiddle
Your solution could be smart like this one by using a nice directive:
View
<body ng-app="angularjs-starter">
<script src="https://js.stripe.com/v3/"></script>
<div ng-controller="MainCtrl">
<form name="regForm" id="register-form">
<label>Mail</label>
<input ng-model="reg.email" type="email" name="username">
<div stripe-validator
stripe-complete="stripeCompleted"
stripe-form-id="register-form"></div>
<br>
<button ng-model="reg.btn" ng-disabled="stripeCompleted === false || !regForm.username.$valid">Register</button>
</form>
</div>
</body>
AngularJS Application / Stripe.js card validation directive
var app = angular.module('angularjs-starter', []);
app.controller('MainCtrl', function($scope, $rootScope) {
//Init stripe state via controller
$scope.stripeCompleted = false;
});
app.directive('stripeValidator', function() {
return {
restrict: 'A',
template: `
<div id="stripe-wrapper">
<div id="card-element"></div>
</div>
<small id="card-errors" class="text-danger" role="alert">{{ ccErrMsg }}</small>
<input type="hidden" name="stripeToken" ng-value="stripeToken" />`,
scope: {
"stripeComplete": '=',
"stripeFormId": '#',
"stripeError": '=',
"stripeToken": '=',
},
link: function(scope, element, attrs) {
//Init
var stripe = Stripe("pk_test_6pRNASCoBOKtIshFeQd4XMUh");
var elements = stripe.elements();
var card = elements.create("card");
var form = document.getElementById(scope.stripeFormId);
//mount card element https://stripe.com/docs/stripe-js/reference#element-mount
card.mount("#card-element");
//add event listener
card.addEventListener('change', function(event) {
//check for errors
if (event.error) {
scope.ccErrMsg = event.error.message;
} else {
scope.ccErrMsg = '';
}
//check for complete
scope.stripeComplete = event.complete ? true : false;
//apply scope
scope.$apply();
});
//inject form submit event
form.addEventListener("submit", function(event) {
//prevent form submit
event.preventDefault();
//handle token, handle error and form submit forward
stripe.createToken(card).then(function(result) {
if (result.error) {
scope.ccErrMsg = event.error.message;
scope.stripeToken = '';
} else {
scope.ccErrMsg = '';
scope.stripeToken = result.token;
}
//apply scope
scope.$apply();
//forward submit
form.submit();
})
});
}
}
});
I'm starting with MEAN.js framework and I´m trying to create my first app. I'm creating the App from a CRUD module generated with Yeoman (yo meanjs:crud-module).
The app is a simple form with 2 fields. "name" and "image". Name is the title of the image, and image the URL of image.
My problem now is that I can´t insert in the Mongo collection "photos" both fields from the form. Only the field "name" generated by default with the CRUD module generator is inserted correctly in the collection.
What am I doing wrong?
photo.server.model.js
'use strict';
/**
* Module dependencies.
*/
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
/**
* Photo Schema
*/
var PhotoSchema = new Schema({
name: {
type: String,
default: '',
required: 'Introduce el nombre de la imagen',
trim: true
},
image: {
type: String,
default: '',
required: 'Introduce la url de la imagen',
trim: true
},
created: {
type: Date,
default: Date.now
},
user: {
type: Schema.ObjectId,
ref: 'User'
}
});
mongoose.model('Photo', PhotoSchema);
photo.server.controller.js
'use strict';
/**
* Module dependencies.
*/
var path = require('path'),
mongoose = require('mongoose'),
Photo = mongoose.model('Photo'),
errorHandler = require(path.resolve('./modules/core/server/controllers/errors.server.controller')),
_ = require('lodash');
/**
* Create a Photo
*/
exports.create = function(req, res) {
var photo = new Photo(req.body);
photo.user = req.user;
photo.save(function(err) {
if (err) {
return res.status(400).send({
message: errorHandler.getErrorMessage(err)
});
} else {
res.jsonp(photo);
}
});
};
/**
* Show the current Photo
*/
exports.read = function(req, res) {
// convert mongoose document to JSON
var photo = req.photo ? req.photo.toJSON() : {};
// Add a custom field to the Article, for determining if the current User is the "owner".
// NOTE: This field is NOT persisted to the database, since it doesn't exist in the Article model.
photo.isCurrentUserOwner = req.user && photo.user && photo.user._id.toString() === req.user._id.toString();
res.jsonp(photo);
};
/**
* Update a Photo
*/
exports.update = function(req, res) {
var photo = req.photo;
photo = _.extend(photo, req.body);
photo.save(function(err) {
if (err) {
return res.status(400).send({
message: errorHandler.getErrorMessage(err)
});
} else {
res.jsonp(photo);
}
});
};
/**
* Delete an Photo
*/
exports.delete = function(req, res) {
var photo = req.photo;
photo.remove(function(err) {
if (err) {
return res.status(400).send({
message: errorHandler.getErrorMessage(err)
});
} else {
res.jsonp(photo);
}
});
};
/**
* List of Photos
*/
exports.list = function(req, res) {
Photo.find().sort('-created').populate('user', 'displayName').exec(function(err, photos) {
if (err) {
return res.status(400).send({
message: errorHandler.getErrorMessage(err)
});
} else {
res.jsonp(photos);
}
});
};
/**
* Photo middleware
*/
exports.photoByID = function(req, res, next, id) {
if (!mongoose.Types.ObjectId.isValid(id)) {
return res.status(400).send({
message: 'Photo is invalid'
});
}
Photo.findById(id).populate('user', 'displayName').exec(function (err, photo) {
if (err) {
return next(err);
} else if (!photo) {
return res.status(404).send({
message: 'No Photo with that identifier has been found'
});
}
req.photo = photo;
next();
});
};
photo.client.controller.js
(function () {
'use strict';
// Photos controller
angular
.module('photos')
.controller('PhotosController', PhotosController);
PhotosController.$inject = ['$scope', '$state', '$window', 'Authentication', 'photoResolve'];
function PhotosController ($scope, $state, $window, Authentication, photo) {
var vm = this;
vm.authentication = Authentication;
vm.photo = photo;
vm.error = null;
vm.form = {};
vm.remove = remove;
vm.save = save;
// Remove existing Photo
function remove() {
if ($window.confirm('Are you sure you want to delete?')) {
vm.photo.$remove($state.go('photos.list'));
}
}
// Save Photo
function save(isValid) {
if (!isValid) {
$scope.$broadcast('show-errors-check-validity', 'vm.form.photoForm');
return false;
}
// TODO: move create/update logic to service
if (vm.photo._id) {
vm.photo.$update(successCallback, errorCallback);
} else {
vm.photo.$save(successCallback, errorCallback);
//console.log(vm.photo.image);
}
function successCallback(res) {
$state.go('photos.view', {
photoId: res._id
});
}
function errorCallback(res) {
vm.error = res.data.message;
}
}
}
}());
form-photo.client.view.html
<section>
<div class="page-header">
<h1>{{vm.photo._id ? 'Edit Photo' : 'Nueva imagen'}}</h1>
</div>
<div class="col-md-12">
<form name="vm.form.photoForm" class="form-horizontal" ng-submit="vm.save(vm.form.photoForm.$valid)" novalidate>
<fieldset>
<div class="form-group" show-errors>
<label class="control-label" for="name">Nombre</label>
<input name="name" type="text" ng-model="vm.photo.name" id="name" class="form-control" placeholder="Name" required>
<div ng-messages="vm.form.photoForm.name.$error" role="alert">
<p class="help-block error-text" ng-message="required">El nombre de la foto es requerido</p>
</div>
</div>
<div class="form-group" show-errors>
<label class="control-label" for="image">URL</label>
<input name="image" type="text" ng-model="vm.photo.image" id="image" class="form-control" placeholder="Url" required>
<div ng-messages="vm.form.photoForm.image.$error" role="alert">
<p class="help-block error-text" ng-message="required">La URL de la foto es requerido</p>
</div>
</div>
<div class="form-group">
<button type="submit" class="btn btn-default">{{vm.photo._id ? 'Update' : 'Crear'}}</button>
</div>
<div ng-show="vm.error" class="text-danger">
<strong ng-bind="vm.error"></strong>
</div>
</fieldset>
</form>
</div>
</section>
Use this link and see example, since
mean 'js'
uses
multer
to upload in 0.4.2 is already integrated so if you want u can also generate and copy from there and edit little bit since they are not compatible
Getting this error: Error: [$rootScope:infdig] 10 $digest() iterations reached. Aborting!
I have created a reddit style nested comment system using AngularJS. But after 10 levels deep i get a very ugly error on my console that looks like this:
This happens after exactly 10 levels deep:
The nested comments are directives that look like this:
commentCont.tpl.html:
<div class="comment-cont">
<div
class="upvote-arrow"
ng-show="!collapsed"
ng-click="vote()"
ng-class="{'active' : node.votedByMe}"
>
<div class="upvote-arrow-top"></div>
<div class="upvote-arrow-bottom"></div>
</div>
<div class="wrapper">
<div class="info">
<span class="collapse" ng-click="collapsed = !collapsed">[ {{collapsed ? '+' : '–'}} ]</span>
<span ng-class="{'collapsed-style' : collapsed}">
<a ui-sref="main.profile({id: node.userId})">{{node.username}}</a>
<span>{{node.upvotes}} point{{node.upvotes != 1 ? 's' : ''}}</span>
<span mg-moment-auto-update="node.createdTime"></span>
<span ng-show="collapsed">({{node.children.length}} child{{node.children.length != 1 ? 'ren' : ''}})</span>
</span>
</div>
<div class="text" ng-bind-html="node.comment | autolink | nl2br" ng-show="!collapsed"></div>
<div class="reply" ng-show="!collapsed">
<span ng-click="formHidden = !formHidden">reply</span>
</div>
</div>
<div class="input-area" ng-show="!collapsed">
<form ng-show="!formHidden" name="form" autocomplete="off" novalidate ng-submit="submitted = true; submit(formData, form)">
<div class="form-group comment-input-feedback-branch has-error" ng-show="form.comment.$invalid && form.comment.$dirty">
<div ng-messages="form.comment.$error" ng-if="form.comment.$dirty">
<div class="input-feedback" ng-message="required">Comment text is required.</div>
<div class="input-feedback" ng-message="maxlength">Comment text cannot exceed 2500 characters.</div>
</div>
</div>
<div
class="form-group"
ng-class="{ 'has-error' : form.comment.$invalid && form.comment.$dirty, 'has-success' : form.comment.$valid }"
>
<textarea
name="comment"
class="textarea comment-cont-textarea"
ng-model="formData.comment"
required
ng-maxlength="2500"
textarea-autoresize
></textarea>
</div>
<div class="form-group">
<mg-button-loading
mgbl-condition="awaitingResponse"
mgbl-text="Save"
mgbl-loading-text="Saving..."
mgbl-class="btn-blue btn-small"
mgbl-disabled="!form.$valid"
></mg-button-loading>
<mg-button-loading
mgbl-text="Cancel"
mgbl-class="btn-white btn-small"
ng-click="formHidden=true;"
></mg-button-loading>
</div>
</form>
</div>
<div ng-show="!collapsed">
<div ng-repeat="node in node.children" ng-include="'commentTree'"></div>
</div>
</div>
commentCont.directive.js:
(function () {
'use strict';
angular
.module('app')
.directive('commentCont', commentCont);
/* #ngInject */
function commentCont ($http, user, $timeout) {
return {
restrict: 'E',
replace: true,
scope: {
node: '='
},
templateUrl: 'app/post/commentCont.tpl.html',
link: function (scope, element, attrs) {
var textarea = element.querySelector('.comment-cont-textarea');
var voteOK = true;
var action = '';
var userInfo = user.get();
scope.formHidden = true; // Do not ng-init="" inside an ng-repeat.
scope.collapsed = false; // Do not ng-init="" inside an ng-repeat.
// Autofocus textarea when reply link is clicked.
scope.$watch('formHidden', function(newValue, oldValue) {
if (newValue !== true) {
$timeout(function() {
textarea[0].focus();
});
}
});
scope.submit = function (formData, form) {
if (form.$valid) {
scope.awaitingResponse = true;
formData.parentId = scope.node.id;
formData.postId = scope.node.postId;
formData.type = 4;
formData.fromUsername = userInfo.username;
formData.toId = scope.node.userId;
formData.fromImage = userInfo.thumbnail36x36.split('/img/')[1];
// console.log(formData);
$http.post('/api/comment', formData)
.then(function (response) {
scope.awaitingResponse = false;
if (response.data.success) {
if (response.data.rateLimit) {
alert(rateLimitMessage);
return false;
}
// id and createdTime is sent with response.data.comment.
var c = response.data.comment;
var newCommentNode = {
id: c.id,
userId: userInfo.id,
username: userInfo.username,
parentId: formData.parentId,
comment: formData.comment,
upvotes: 0,
createdTime: c.createdTime,
votedByMe: false,
children: [],
postId: scope.node.postId
};
// console.log('user', user.get());
// console.log('scope.node', scope.node);
// console.log('response', response);
// console.log('newCommentNode', newCommentNode);
formData.comment = '';
form.comment.$setPristine();
scope.formHidden = true;
scope.node.children.unshift(newCommentNode);
}
});
}
};
scope.vote = function() {
if (voteOK) {
voteOK = false;
if (!scope.node.votedByMe) {
scope.node.votedByMe = true;
action = 'add';
} else {
scope.node.votedByMe = false;
action = 'remove';
}
var data = {
commentId: scope.node.id,
action: action
};
$http.post('/api/comment/vote', data)
.then(function (response) {
// console.log(response.data);
voteOK = true;
if (action === 'add') {
scope.node.upvotes++;
} else {
scope.node.upvotes--;
}
});
}
};
}
};
}
}());
The tree is being called like this:
<script type="text/ng-template" id="commentTree">
<comment-cont
node="node"
></comment-cont>
</script>
<div ng-repeat="node in tree[0].children" ng-include="'commentTree'"></div>
How can I have more than 10 levels of nested ng-repeat without getting an error like this?
The default implementation of $digest() has limit of 10 iterations . If the scope is still dirty after 10 iterations error is thrown from $digest().
The below stated is one way of configuring the limit of digest iterations to 20.
var app = angular.module('plunker', [], function($rootScopeProvider) {
$rootScopeProvider.digestTtl(20); // 20 being the limit of iterations.
});
But you should look in to stabilizing your model rather than configuring the limit of iterations.
I was dealing with a similar issue. end up with the following directive:
(function () {
'use strict';
angular
.module('app')
.directive('deferDom', ['$compile', ($compile) => {
return {
restrict: 'A',
compile: (tElement) => {
// Find, remove, and compile the li.node element.
let $el = tElement.find( "li.node" );
let transclude = $compile($el);
$el.remove();
return function($scope){
// Append li.node to the list.
$scope.$applyAsync(()=>{
tElement.append(transclude($scope));
});
}
},
};
}]);
})();
I couldn't think of a good title for this as I'm not 100% sure what to ask for plus this might be tricky without posting my whole application.
I'm new to angular, and want to create a site for holding recipes.
I'm using MVC and Web API and I have the basics together.
I want to make the recipe editing process a good user experience so want the user to be able to add an ingredient and edit and save it all without leaving the page.
At the moment I have an angular module and controller for the recipe editing, I want to have a sub-application to allow them to add and edit ingredients. I don't want them to have to navigate away.
What I need to know is a) the terminology. I'm thinking of the ingredient editor as a sub app, but is it just a sub controller?
and b) what is the pattern called? Master Detail View?
The following is my current "EditRecipeViewModel"
At the moment my newAddIngredient pushes an ingredient object into an array, this can then be edited and saved with saveNewIngredient, however this is nasty and doesn't work for editing the ingredient. I think I want the functionality for editing an ingredient encapsulated in a separate something, but is that a module a controller or what? maybe I'm trying to run before I can walk??
//edit recipe
//add ingredients
//add picture(s)
//confirm
//save
var editRecipeModule = angular.module('editRecipe', ['common'])
.config(function ($routeProvider, $locationProvider) {
$routeProvider.when(CookBook.rootPath + 'recipe/edit', { templateUrl: CookBook.rootPath + 'Templates/EditRecipe/EditRecipe.html', controller: 'EditRecipeViewModel' });
$routeProvider.when(CookBook.rootPath + 'recipe/edit/confirm', { templateUrl: CookBook.rootPath + 'Templates/EditRecipe/ConfirmRecipe.html', controller: 'ConfirmRecipeViewModel' });
$routeProvider.otherwise({ redirectTo: CookBook.rootPath + 'recipe/edit/' });
$locationProvider.html5Mode({ enabled: true });
});
editRecipeModule.controller("EditRecipeViewModel", function ($scope, $window, viewModelHelper, validator) {
$scope.viewModelHelper = viewModelHelper;
$scope.editRecipeModel = new CookBook.EditRecipeModel();
$scope.newIngredient = new CookBook.IngredientModel();
$scope.ingredients = [];
var editRecipeRules = [];
var setupRules = function () {
editRecipeRules.push(new validator.PropertyRule("Name", {
required: { message: "Recipe name is required." }
}));
editRecipeRules.push(new validator.PropertyRule("Description", {
required: { message: "Please add a description." }
}));
editRecipeRules.push(new validator.PropertyRule("Method", {
required: { message: "All good recipes need a method." }
}));
}
var editIngredientRules = [];
var setupIngredientRules = function () {
editIngredientRules.push(new validator.PropertyRule("Name", {
required: { message: "Name is required" }
}));
editIngredientRules.push(new validator.PropertyRule("Amount", {
required: { message: "Amount is required" }
}));
editIngredientRules.push(new validator.PropertyRule("Unit", {
required: { message: "Unit is required" }
}));
}
$scope.confirm = function () {
validator.ValidateModel($scope.editRecipeModel, editRecipeRules);
viewModelHelper.modelIsValid = $scope.editRecipeModel.isValid;
viewModelHelper.modelErrors = $scope.editRecipeModel.errors;
if (viewModelHelper.modelIsValid) {
//save via web api
}
}
$scope.newAddIngredient = function () {
$scope.newIngredient = new CookBook.IngredientModel();
$scope.newIngredient.EditMode = true;
$scope.ingredients.push($scope.newIngredient);
}
$scope.saveNewIngredient = function() {
$scope.newIngredient.EditMode = false;
}
$scope.addIngredient = function () {
validator.ValidateModel($scope.newIngredient, editIngredientRules);
viewModelHelper.modelIsValid = $scope.newIngredient.isValid;
viewModelHelper.modelErrors = $scope.newIngredient.errors;
if (viewModelHelper.modelIsValid) {
$scope.ingredients.push($scope.newIngredient);
$scope.newIngredient = new CookBook.IngredientModel();
}
}
setupRules();
setupIngredientRules();
});
editRecipeModule.controller("ConfirmRecipeViewModel", function ($scope, $window, viewModelHelper) {
$scope.viewModelHelper = viewModelHelper;
//$scope.editRecipeModel = new CookBook.editRecipeModel();
});
Having done some more reading
I now know the answer is to use an additional controller to deal with that small part of the UI.
The key part i was missing was to put the controller to use in the HTML.
ng-controller="IngredientViewModel as ivm"
My HTML.
<div class="row" ng-controller="IngredientViewModel as ivm" ng-repeat="ingredient in ingredients">
<span class="col-lg-3 col-md-3 col-sm-3 col-xs-3">
<input ng-show="ivm.ingredient.EditMode" ng-model="ivm.ingredient.Name" ng-change="newIngredientChanged()"></input>
<span ng-hide="ivm.ingredient.EditMode">{{ivm.ingredient.Name}}</span>
</span>
<span class="col-lg-3 col-md-3 col-sm-3 col-xs-3">
<input ng-show="ivm.ingredient.EditMode" ng-model="ivm.ingredient.Amount"></input>
<span ng-hide="ivm.ingredient.EditMode">{{ivm.ingredient.Amount}}</span>
</span>
<span class="col-lg-3 col-md-3 col-sm-3 col-xs-3">
<input ng-show="ivm.ingredient.EditMode" ng-model="ivm.ingredient.Unit"></input>
<span ng-hide="ivm.ingredient.EditMode">{{ivm.ingredient.Unit}}</span>
</span>
<span class="col-lg-3 col-md-3 col-sm-3 col-xs-3">
<button ng-show="ivm.ingredient.EditMode" ng-click="ivm.save()">OK</button>
<button ng-hide="ivm.ingredient.EditMode" ng-click="ivm.edit()">Edit</button>
</span>
</div>
The controller..
editRecipeModule.controller("IngredientViewModel", function ($scope, $window, viewModelHelper, validator) {
var local = this;
local.ingredient = new CookBook.IngredientModel();
local.ingredient.Name = "bob";
local.ingredient.Amount = "100";
local.ingredient.Unit = "g";
local.ingredient.EditMode = true;
var editRules = [];
var setupRules = function () {
editRules.push(new validator.PropertyRule("Name", {
required: { message: "Name is required" }
}));
editRules.push(new validator.PropertyRule("Amount", {
required: { message: "Amount is required" }
}));
editRules.push(new validator.PropertyRule("Unit", {
required: { message: "Unit is required" }
}));
}
local.save = function () {
validator.ValidateModel(local.ingredient, editRules);
viewModelHelper.modelIsValid = local.ingredient.isValid;
viewModelHelper.modelErrors = local.ingredient.errors;
if (viewModelHelper.modelIsValid) {
local.ingredient.EditMode = false;
}
}
local.edit = function () {
local.ingredient.EditMode = true;
}
setupRules();
});
I need the following functionality: When a user goes to the login form, the browser should auto fill the username and password.
My implementation works (on FF and Chrome) but, there is this bug (not consistent) where the model data does not get updated correctly when switching between users. This means that I log in with user ONE, then log out, and enter the credentials for user TWO, but after I click the login button, I'm still logged in with user ONE.
The login form looks like this:
<form class="form-horizontal" ng-submit="login(credentials)">
<fieldset>
<div class="form-group" ng-class="{'has-error' : validationErrors.email}">
<div class="btn-icon-lined btn-icon-round btn-icon-sm btn-default-light">
<span class="glyphicon glyphicon-envelope"></span>
</div>
<input type="email" name="email" autocomplete="on" ng-model="credentials.email" class="form-control input-lg input-round text-center" placeholder="Email" >
<span class="help-block" ng-if="validationErrors.email">{{ validationErrors.email.0 }}</span>
</div>
<div class="form-group" ng-class="{'has-error' : validationErrors.password}">
<div class="btn-icon-lined btn-icon-round btn-icon-sm btn-default-light">
<span class="glyphicon glyphicon-lock"></span>
</div>
<input type="password" name="password" autocomplete="on" ng-model="credentials.password" class="form-control input-lg input-round text-center" placeholder="Password" >
<span class="help-block" ng-if="validationErrors.password">{{ validationErrors.password.0 }}</span>
</div>
<div class="form-group">
<input type="submit" value="Sign in" class="btn btn-primary btn-lg btn-round btn-block text-center">
</div>
</fieldset>
</form>
The login controller contains something like:
// Login Controller
app.controller( 'LoginCtrl', ['$rootScope','$scope', '$state', 'AppUser', 'Auth',
function($rootScope, $scope, $state, AppUser, Auth){
console.log("LoginCtrl");
$scope.credentials = {
"email" : "",
"password": ""
};
$scope.redirectAfterLogin = function() {
// set user data
AppUser.setUserData($scope.user);
...
}
// Login attempt handler
$scope.login = function(data) {
data = {'user':data};
Auth.login(data,
function(response) {
$scope.user = response.data;
...
},function(response){
$scope.validationErrors = {
"email" : [],
"password": []
};
...
}
);
};
}]);
Logout:
// logout
$scope.logout = function() {
// remove attempted URL, if any
AppUser.removeAttemptUrl();
data = {'user':
{
'email': $scope.user.email
}
};
Auth.logout(data,
function(){
AppUser.unSetUserData($scope.user); // se method below
$state.go(ApplicationState.LOGIN);
},
function(){
console.log("Logout failed");
});
}
angular.module('app.service').factory('AppUser', [
'$window', '$rootScope', 'LocalStorage', 'appConfig', '$injector', '$location',
function($window, $rootScope, localStorage, appConfig, $injector, $location){
// Redirect to the original requested page after login
var redirectToUrlAfterLogin = { url: '' };
var userKey = "AppUser";
var userData = {};
angular.element($window).on('storage', function(event) {
if (event.key === userKey) {
$rootScope.$apply();
}
});
return {
/**
* Redirect to the original requested page after login
* - we need to be able to save the intended URL, request it, remove it and redirect to it
*/
saveAttemptUrl: function() {
if ($location.path().toLowerCase() != ApplicationState.LOGIN) {
redirectToUrlAfterLogin.url = $location.path();
}
else {
redirectToUrlAfterLogin.url = '/';
}
},
getAttemptedUrl: function() {
return redirectToUrlAfterLogin.url;
},
removeAttemptUrl: function() {
// re-initialize URL
redirectToUrlAfterLogin = { url: '' };
},
redirectToAttemptedUrl: function() {
$location.path(redirectToUrlAfterLogin.url);
},
/**
* Returns the current user's state
* #returns {boolean}
*/
isAuthenticated: function() {
userData = JSON.parse(localStorage.get(userKey) || '{}').userData;
if (!this._isSessionExpired()) {
if (userData !== undefined){
return !!(userData.id !== null && userData.email);
}
else{
return false;
}
}
else{
if (userData !== undefined){
var data = {
'user':{
'email': userData.email
}
}
// we use $injector to avoid Circular Dependency which is thrown by injecting the $api service
$injector.invoke(['$api', function($api){
$api.auth.logout(data).success(function(result){
userData = {};
localStorage.remove(userKey);
});
}]);
return false;
}
}
},
getUserData: function() {
return userData;
},
setUserData: function(data) {
userData = data;
localStorage.set(userKey, JSON.stringify({
userData: data,
stamp: Date.now()
}));
},
unSetUserData: function() {
userData = {};
localStorage.remove(userKey);
},
_isSessionExpired: function() {
var session = JSON.parse(localStorage.get(userKey) || '{}');
return (Date.now() - (session.stamp || 0)) > appConfig.sessionTimeout;
},
userData : userData
};
}] );
Any ideas on why this is happening?
After you logout check the localStorage with the browser inspector.
Probably you will find some variable that you didn't clear.
So just clear the storage and it should be fine.
To clear the storage use:
localStorage.clear();
One additional problem it could be you didn't clean the $rootScope if you didn't refresh all the data are still in there.
Is this the problem? userKey doesn't seem to be defined in the code you've showed.
// add userKey param
unSetUserData: function(userKey) {
userData = {};
localStorage.remove(userKey);
},