Create an Angularjs web form that duplicates but doesn't clone - javascript

I have "attempted" to build an Angularjs web form that duplicates the input fields upon clicking the add button and removes newly added fields upon clicking the "close button" which seems to work okay. Great, right!?
http://plnkr.co/edit/jUulJQ52m9QEZLkG086B
However, the duplicate forms created after clicking the add button are exact clones of each other or the original. So, when there are two forms on the screen and data entered into form 1, it is duplicated on form 2 and visa versa.
My question:
How do I continue down the path of having the ability
to duplicate the elements, really, within the "fieldset" element of the form, but without it being cloned?
Following are snippets of my HTML and AngularJs file. I have wracked my head on this for a while now and keep ending up in the same spot.....A fresh set of eyes and/or any suggestions would be great!
Thanks!
Index.html
<body>
<div class="container-fluid">
<div class="row">
<div class="col-md-4"></div>
<div class="col-md-4" ng-controller="formCtrl">
<h1>Expense Form</h1>
<form class="form-horizontal" id="itvExpenseForm" name="ExpenseForm" ng-repeat="field in table.fields track by $index" novalidate>
<fieldset id="innerForm" ng-model="table.fields[$index]">
<legend>
<span>{{$index + 1}}.</span>
</legend>
<div class="form-group" ng-controller="quarterListCtrl">
<label for="ddlQuarters" class="col-sm-4 control-label">Year Quarter:</label>
<div class="col-sm-8">
<select class="form-control" name="Quarters" id="ddlQuarters" ng-model="formData.quarter">
<option value="">---Please select---</option>
<option ng-repeat="q in quarters">{{q.name}}</option>
</select>
</div>
</div>
<div class="form-group">
<label for="ddlDate" class="col-sm-4 control-label">Purchase Date:</label>
<div class="col-sm-8">
<input class="form-control" id="ddlDate" type="date" ng-model="formData.date" required>
</div>
</div>
<div class="form-group">
<div class="col-lg-10 col-lg-offset-2">
<button id="removeMore" aria-hidden="false" ng-click="removeForm($index)" ng-show="table.fields.length > 1">Close</button>
<button id="addMore" aria-hidden="true" ng-click="addNewForm()">Add</button>
</div>
</div>
</fieldset>
</form>
<div class="form-group">
<div class="col-lg-10 col-lg-offset-2">
<button type="submit" class="btn btn-success" ng-click="submitForm()">Submit</button>
</div>
</div>
</div>
<div class="col-md-4"></div>
</div>
</div>
</body>
App.JS
var app = angular.module('app', []);
app.controller('quarterListCtrl', function($scope, $http) {
$scope.quarters = quarters;
});
app.controller('accountsListCtrl', function($scope, $http) {
$scope.accounts = accounts;
});
app.controller('locationsListCtrl', function($scope, $http) {
$scope.locations = locations;
});
app.controller('formCtrl', function($scope, $http) {
$scope.formData = {};
var formCount = 1;
$scope.table = {
fields: []
};
// create first form on page load
$scope.table.fields.push(formCount);
// adds new field to table array which adds to page
$scope.addNewForm = function() {
formCount++;
$scope.table.fields.push(formCount);
};
// removes field from table array which removes form
$scope.removeForm = function(myIndex) {
if ($scope.table.fields.length > 1) {
$scope.table.fields.splice(myIndex, 1);
formCount;
}
};
});
var accounts = [{
'name': 'Account One',
'acct no': 123456789,
'id': 1
}, {
'name': 'Account Two',
'acct no': 987654321,
'id': 2
}];
var quarters = [{
'name': 'Fall',
'id': 1
}, {
'name': 'Winter',
'id': 2
}, {
'name': 'Spring',
'id': 3
}, {
'name': 'Summer',
'id': 4
}, ];
var locations = [{
'name': 'Here',
'id': 1
}, {
'name': 'There',
'id': 2
}, {
'name': 'Everywhere',
'id': 3
}];

As mentioned already, your formData is reference by all the elements in your array so a change in one element affects all of them.
To make things simpler, you don't need a formCount. Just have an array of formData and make use of angular.copy. So your controller becomes:
app.controller('formCtrl', function ($scope, $http) {
$scope.forms = [];
// create first form on page load
$scope.forms.push({});
// adds new field to table array which adds to page
$scope.addNewForm = function (formToCopy){
$scope.forms.push(angular.copy(formToCopy));
};
// removes field from table array which removes form
$scope.removeForm = function (formToRemove) {
var formIndex = $scope.forms.indexOf(formToRemove);
if (formIndex > -1) {
$scope.forms.splice(formIndex, 1);
}
};
});
See forked plunker for full markup changes but the main points are to change references from table.fields to just forms and pass the formData object to addNewForm and removeForm. So your ng-repeat becomes:
<form class="form-horizontal" name="ExpenseForm" ng-repeat="formData in forms track by $index" novalidate>
You can't use ng-model on a fieldset, so this doesn't do anything:
<fieldset id="innerForm" ng-model="table.fields[$index]">
Also, I would avoid using the id attribute on repeated elements otherwise there will be multiple elements with the same id on the page. You could opt for a dynamic id like id="innerForm-{{$index}}" but it's really not necessary in this case.

All your "ng-model" declarations link to a variable in the object "formData", e.g.
formData.quarter
formData.date
But you do only have one instance of formData on your scope. So there is one object $scope.formData and all duplicates of your form use that very same variable (formData.quarter) hence when you change quarter in your first form the quarter in the second form will change as well since its the very same variable.
What you need to do is to have a formData for every single created form.
For example you can have a variable
$scope.allFormDatas = [];
on your scope and in this array you will push a new instance of your old formData object, e.g.
$scope.addNewForm = function() {
formCount++;
$scope.table.fields.push(formCount);
var newFormData = {};
$scope.allFormDatas.push (newFormData);
};
now you will have to use that very instance of newFormData in your given forms, e.g.
<select class="form-control" name="Quarters" id="ddlQuarters" ng-model="allFormDatas[$index].quarter">
Furthermore you have to remove those formData objects from the array as well when you remove the form itself, of course.

Related

Unable to validate mutli-select input with AngularJS

I'm new to Angular, and I struggle with validating a multi select input with ngOptions attribute.
I want the field to be required, so the user must chose at least one option. However the validation methods Angular have simply doesn't work in my case, Here's what I've tried:
<form name="products" novalidate>
<div class="form-group">
<label for="select-product">Chose product/s</label>
<select id="select-product" name="selectedProduct" class="form-control"
required
multiple
ng-model="selectedProduct"
ng-options="product.name for product in products">
</select>
</div>
<button class="btn btn-primary pull-left" ng-click="products.selectedProduct.$valid && goToChildrenId()">Next</button>
</form>
Even if I chose a product and I can see that the form class switch from .ng-invalid to .ng-valid, the function goToChildrenId() doesn't run.
Also, if I add {{products.selectedProduct.$valid}} at the bottom, when I refresh the page I can see "false" for a second but it disappear, why?
If it's relevent, this is the controller:
adminCheckout.controller('productsCtrl', function($scope, $http, wpMiniapps, $location, $rootScope){
$scope.products = [];
var url = wpMiniapps.routeUrl("getProducts");
$http.post(url, {}).then(function(res){
$scope.products = res.data.data;
}, function(err){
console.log(err);
});
$scope.$watch('selectedProduct', function (newVal) {
$rootScope.globalData.products = newVal;
});
$scope.goToChildrenId = function(){
$location.path('/select-children');
};
});
I searched the web but nothing seems to work in my case.
I will really appreciate any help.
You have a variable name collision which is causing the problem: the form is named products and this ends up as a $scope variable. But it collides with $scope.products = [] from the controller. Simply renaming the form to, e.g., productsForm solves the problem:
<form name="productsForm" novalidate>
...
<button class="btn btn-primary pull-left" ng-click="productsForm.selectedProduct.$valid && goToChildrenId()">Next</button>
</form>

Event in Angular Controller Only Fires Once

I'm trying to implement an Angular version of an autocomplete textbox. I found some working examples, but none seem to exhibit the behavior I'm getting.
The autocomplete functionality itself works fine. When a suggested item is selected, the control correctly handles the selection. Subsequent uses of the control (typing in the autocomplete box, making a selection) fail to engage the 'selected' event/condition, although the autocomplete bit continues to work.
Here's my module & controller:
var app = angular.module('myapp', ['angucomplete-alt']); //add angucomplete-alt dependency in app
app.controller('AutoCompleteController', ['$scope', '$http', function ($scope, $http) {
//reset users
$scope.Users = [];
$scope.SelectedUser = null;
//get data from the database
$http({
method: 'GET',
url: '/UserRoleAdministration/Autocomplete'
}).then(function (data) {
$scope.Users = data.data;
}, function () {
alert('Error');
})
//to fire when selection made
$scope.SelectedUser = function (selected) {
if (selected) {
$scope.SelectedUser = selected.originalObject;
}
}
}]);
I'm guessing the problem is in there, but I don't know what it is. I include the bit from my view below, although there doesn't seem to be much there to fuss with:
<div class="form-group">
<div ng-app="myapp" ng-controller="AutoCompleteController">
<div angucomplete-alt id="txtAutocomplete" pause="0" selected-object="SelectedUser" local-data="Users" search-fields="RegularName" placeholder="People Search" title-field="RegularName" minlength="2" input-class="form-control" match-class="highlight"></div>
<!--display selected user-->
<br /><br />
<div class="panel panel-default" id="panelResults">
<div class="panel-heading"><h3 class="panel-title">Manage Roles for {{SelectedUser.RegularName}}</h3></div>
<div class="panel-body">
<div class="row">
<div class="col-md-2">
<img src="~/Images/avatar_blank.png" width="100%" />
</div>
<div class="col-md-4">
<div class="row">
<div class="col-md-4">Selected User:</div> <div class="col-md-6">{{SelectedUser.RegularName}}</div>
</div>
</div>
</div>
</div>
</div>
</div>
Any help would be appreciated!
UPDATE
After fixing the mistake Yaser pointed out, I wasn't getting any information regarding the selected object. So I set the page to output the entire object, rather than the specified fields, and I noticed I was getting information about the selected object, and on subsequent attempts as well.
So this worked: {{SelectedUser}}
This did not: {{SelectedUser.Department}}
Then I looked at the object and noticed its format. It had "title" and "description", and description had inside it the key/value pairs.
So now this works: {{SelectedUser.description.Department}}
And that's it.
Because the first time you are setting $scope.SelectedUser as a function but inside that you are rewriting the same one with an object. so next time it is not a function any more, try to rename the function:
$scope.setUser = function (selected) {
if (selected) {
$scope.SelectedUser = selected.originalObject;
}
}

Strange binding permanence between controllers

I've got a project in which you write a note in a formulary. Then, you submit that note into an information container (now it's just an array for testing purposes, but it's intended to be a DB later).
The formulary has the following controller:
app.controller('controlFormulario', ['$scope', 'SubmitService', function($scope, submitService) {
$scope.formData = {
"titulo":"",
"texto":"",
"fecha": new Date()
};
$scope.submit = function() {
var temp = $scope.formData;
submitService.prepForBroadcast(temp);
}
// more things we don't need now
... which is bound to this part of the DOM, which is added into it, via a directive:
<form ng-controller="controlFormulario as formCtrl">
<div class="element">
<div class="form-group" ng-class="{'has-error': formData.titulo.length > 50 }">
<label for="inputTitulo">Título</label>
<input type="titulo" class="form-control" id="inputTitulo" ng-model="formData.titulo">
<span ng-show="formData.titulo.length > 50" id="helpBlock" class="help-block">El título no puede exceder los 50 caracteres.</span>
</div>
<div class="form-group">
<label for="inputTexto">Texto</label>
<textarea class="form-control" id="inputTexto" ng-model="formData.texto"></textarea>
</div>
<div class="form-group">
<label for="fecha">Fecha</label>
<input type="fecha" class="form-control" id="fecha" ng-model="formData.fecha" disabled>
</div>
<div class="form-group" >
<button class="btn btn-primary" style="height:35px;width:100px;float:right;" id="submit"
ng-disabled="isDisabled()" ng-click="submit()">
Enviar
</button>
</div>
</div>
<div class="note" ng-show="formData.titulo.length > 0">
<div class="title" ng-model="formData.titulo" class="title">{{formData.titulo | limitTo:50}}</div>
<div class="text" ng-model="formData.texto" class="text">{{formData.texto}}</div>
<div class="date" ng-model="formData.fecha" class="date">{{formData.fecha | date}}</div>
</div>
</form>
This is my directive (I don't think it's really needed, but just in case):
app.directive('formulario', [function() {
return {
restrict: 'E', // C: class, E: element, M: comments, A: attributes
templateUrl: 'modules/formulario.html',
};
}]);
I use a service for passing the data between the previous controller, and the note controller (which controls the note objects of the array). This is the service:
app.factory('SubmitService', function($rootScope) {
var data = {};
data.prepForBroadcast = function(recvData) {
data.data = recvData;
this.broadcastItem();
};
data.broadcastItem = function() {
$rootScope.$broadcast('handleBroadcast');
};
return data;
});
... and I receive it in this part of my note controller:
app.controller('noteController', ['$scope', 'SubmitService', function($scope, submitService) {
var nc = this;
$scope.$on('handleBroadcast', function() {
nc.pruebaNota.push(submitService.data);
$scope.formData.titulo = "";
$scope.formData.texto= "";
$scope.formData.fecha = new Date();
});
// more things, the array, etc...
Ok. This should work, and it does, but something strange happens: as you can see, the preview note is binded with ng-model to the form. That works great, ok. But when I submit the form, the new note object keeps bound to the form (so if I delete the form text, the note appears in blank, and if I write something, it gets automatically updated both in the preview note, and the new note), when there isn't any relation between them. The new note, which appears dynamically on the screen, shouldn't be bound to anything.
Am I doing something wrong? Some help would be really nice!
You are forgetting something really important. Memory address. So, the rought idea is something like: imagine that $scope.formData is in the address 123123. You first create a temp var pointing to 123123 then you send it to the service and the service holds the same address 123123 into data.data.
Then in your second controller you say: hey, I want to work with that data.data (AKA your data in 123123) you have SubmitService.
Now when you modify $scope.formData again, you are updating what you have in that 123123 and everything that is "looking" into that address will be updated.
That is the rough idea. To point it simple, you're modifying the same piece of information everywhere.
See it here: http://plnkr.co/edit/zcEDQLHFWxYg4D7FqlmP?p=preview
As a AWolf suggested, to fix this issue, you can use angular.copy like this:
nc.pruebaNota.push(angular.copy(submitService.data));

Add dynamic model to newly created elements via directives in angularjs

Following is my PLNKR CODE which is working fine.
Problem - I need to add dynamic scope to these element so that I can grab the contact number + type.
I google the problem before asking but as I am new to directives in angular I am confused with the results, let me know what else I need to add to grab the result.
Following kind of result I am expecting -
contact: [
{number: 56432452, type: "Cell"},
{number: 67895644, type: "Work"},
{number: 78943245, type: "Cell"},
{number: 66793456, type: "Home"},
{number: 90546675, type: "Fax"},
];
Also, I need to use the same form in EDIT mode, let me know what are the extra things that I need to keep in mind while developing this functionality for the edit case.
Following is my directive code -
<div class="form-group">
<label class="col-sm-2 control-label">Contact Number<span class="asterisk">*</span></label>
<div class="col-sm-5">
<input type="text" class="form-control">
</div>
<div class="col-sm-2">
<select class="btn">
<option>Cell</option>
<option>Work</option>
<option>Home</option>
<option>Fax</option>
</select>
</div>
<div class="col-sm-1">
<img src="http://img.informer.com/icons/png/16/3225/3225535.png" class="remCls">
</div>
</div>
As you can see currently the select and input do not contain and ngModel. Let me know how do I introduce this to obtain the above mentioned result.
I'm not sure this is what you need but I think you could define your controller as:
myApp.controller("myCtrl", function($scope){
//Create and array of contacts in your model
$scope.contacts = [];
//Add a new contact to the model
$scope.addContact = function() {
var contacts = $scope.contacts;
contacts[contacts.length] = {};
}
//Remove a contact from the model based on its index
$scope.removeContact = function(index) {
$scope.contacts.splice(index, 1);
}
});
Then on your HTML, you leverage the Angular directives ng-repeat and ng-click:
<body ng-controller="myCtrl">
<button ng-click="addContact()"> Add Contact </button>
<div class="form-group" ng-repeat="contact in contacts">
<label>Contact Number</label>
<input type="text" ng-model="contact.contact">
<select ng-model="contact.type">
<option>Cell</option>
<option>Work</option>
<option>Home</option>
<option>Fax</option>
</select>
<button ng-click="removeContact($index)"> Remove Contact </button>
</div> <!-- Close Repeater -->
</body>
Here's your PLNKR link with the changes proposed:
http://plnkr.co/edit/VWCdXSnOsY18XoCKxO0t?p=preview
First of all I would like to thank ExpertSystem for suggesting me to think in Angular way. Then I would like to thank Foxandxss and medice from angular IRC for making the things right not by code but improving my concept and approach for angular.
This is the WORKING code, I came up with for the above problem.
Actually I don't need directive and managed things easily without it.
medice: directives are fine, but when you set up click events that
modify dom, it's gonna break
medice: angularjs can't bind directives properly
Following is my controller code -
var myApp = angular.module("myApp", []);
myApp.controller("myCtrl", function($scope){
$scope.cnctnum = [];
$scope.cncttype = [];
$scope.types = [
{name: "Cell", value: 1},
{name: "Work", value: 2},
{name: "Home", value: 3},
{name: "Fax", value: 4}
];
$scope.items = [];
var i =0;
$scope.addCnt = function(){
$scope.items.push(i);
i++;
};
$scope.remCl = function(index){
$scope.cnctnum.splice(index, 1);
$scope.cncttype.splice(index, 1);
$scope.items.splice(index, 1);
};
$scope.getval = function(){
console.log($scope.cnctnum);
console.log($scope.cncttype);
};
});

AngularJS - radio button not updating model

First steps in AngularJS. I'm facing a strange problem related to this, but the solution doesn't work to me, maybe I'm missing something (as I said, I'm a really n00b with angular).
I'm my HTML, I'm building some radio buttons like that:
<div ng-Controller="PageTwo" >
<h3>General Information > Knowledge About </h3>
<div>
<b>User</b>
<div>
<div ng-repeat="option in userOptions">
<input type="radio" name="userGroups" ng-model="$parent.userSelected" value="{{option.id}}" id="{{option.id}}">{{option.text}}
</div>
</div>
Selected thing: {{userSelected}}
</div>
</div>
This is my related Controller:
uxctModule.controller ('PageTwo', function ($scope, ModelData){
$scope.data = ModelData;
$scope.userOptions = [{text:'Option 1', id:0}, {text:'Option 2', id:1}, {text:'Option 3',id:2}, {text:'Option 4', id:3}];;
$scope.userSelected = ModelData.knowledgeAboutUser;
});
The model is the following object
uxctModule.factory ("ModelData", function () {
var data = {
// more code here
knowledgeAboutUser : 3,
}
return data
});
Now, the problem is that I'm logging in the console the ModelData object, and I've noticed that it's not updating when clicking the radio buttons.
I think the binding it's ok: I've tried to change the value in the Model, and the app selects the corresponding radio button.
Any help it's really appreciated, I'm stuck on this for hours
You can remove the intermediate variable $scope.userSelected:
<div ng-repeat="option in userOptions">
<input type="radio" name="userGroups" ng-model="data.knowledgeAboutUser" value="{{option.id}}" id="{{option.id}}">{{option.text}}
</div>
Selected thing: {{data.knowledgeAboutUser}}
It working fine
just change
$scope.userSelected
to
$scope.userSelected.selected
Working Code
script
var app = angular.module('app', []);
app.factory ("ModelData", function () {
var data = {
// more code here
knowledgeAboutUser : 2,
}
return data
});
app.controller("PageTwo", function ($scope, ModelData) {
$scope.userSelected = {};
$scope.userOptions = [{text:'Option 1', id:0}, {text:'Option 2', id:1}, {text:'Option 3',id:2}, {text:'Option 4', id:3}];;
$scope.userSelected.selected = ModelData.knowledgeAboutUser;
});
html
<div ng-app="app" ng-Controller="PageTwo">
<h3>General Information > Knowledge About </h3>
<div> <b>User</b>
<div>
<div ng-repeat="option in userOptions">
<input type="radio" name="userGroups" ng-model="userSelected.selected" value="{{option.id}}" id="{{option.id}}">{{option.text}}
</div>
</div>
{{userSelected.selected}}
</div>
</div>

Categories

Resources