Removing and modifying items from ko.observableArray - javascript

I have put up a snippet of my MVC4 code here. I would like the "minus" button to remove the row it belongs to, then goes through the array and adjust the input names to be sequential. I think I will need it to be sequential to work with MVC4 model binding.
My problem is, how do I identify which button has just been clicked, and which object in the array it belongs to? Any ideas please? I'm completely new to knockout so I'm not even sure if this is the best way to do it.
This is my viewmodel:
function ViewModel() {
this.breeders = ko.observableArray([{
keyName: ko.observable("Breeders[0].Key"),
valueName: ko.observable("Breeders[0].Value"),
canAdd: ko.observable(true),
canRemove: ko.observable(true)
}]);
this.addRow = function () {
var next = this.breeders().length;
this.breeders.push({
keyName: ko.observable("Breeders[" + next.toString() + "].Key"),
valueName: ko.observable("Breeders[" + next.toString() + "].Value"),
canAdd: ko.observable(true),
canRemove: ko.observable(true)
});
};
this.removeRow = function () {
};
}
And this is my markup:
<div class="form-group">
<div id="breedersFormsContainer" data-bind="template: {name: 'breederForm', foreach: breeders}"></div>
</div>
<script type="text/html" id="breederForm">
<div class="col-lg-offset-3">
<span class="col-lg-1 control-label">Reg: </span><span class="col-lg-2"><input data-bind="attr: {name: keyName}" type="text" class="form-control" /></span>
<span class="col-lg-1 control-label">Name: </span><span class="col-lg-6"><input data-bind="attr: {name: valueName}" type="text" class="form-control" /></span>
<button type="button" class="btn btn-default" data-bind="enable: canRemove"><span class="glyphicon glyphicon-minus">-</span></button>
<button type="button" class="btn btn-default" data-bind="enable: canAdd, click: $parent.addRow.bind($parent)"><span class="glyphicon glyphicon-plus">+</span></button>
</div>
</script>

If you have bound the click handler to the button, you can do the following
this.removeRow = function (data) {
yourObservableArray.remove(data);
};
data will be a reference to the object bound to the current row

Related

Uncaught ReferenceError: Unable to process binding with KnockoutJS

We have a multiple page form like below , each page on the form is associated with different model classes. I am trying use the value the users selected in Page 1 and based upon that value selected in Pg1 I need to show/hide the field in Page2.
Page2 has a button which allows the users add courses, when they click on the button few fields shows up in the page in foreach loop and one of the field should show/hide based on the selection made on the previous page. But the above logic throws error like Uncaught ReferenceError: Unable to process binding "visible:" below is the viewmodel
How can I have the binding work properly here and get rid of the error
It's case sensitive. Also, the foreach loop changes the binding context, so you need to do this:
<div class="form-group required" data-bind="visible: $parent.Solution() == 'Other'">
Edit- that is, if you're indeed trying to reference the Solution property from the parent viewmodel. It's not clear from your example wheter a CoursesList item also has such a property.
Just expanding on #Brother Woodrow's answer with an very basic runnable example might help with things.
function ViewModel() {
var self = this;
self.pages = [1, 2]
self.currentPage = ko.observable(1)
self.solutions = ko.observableArray(['Solution 1', 'Solutions 2', 'Other']);
self.solution = ko.observable().extend({
required: {
params: true,
message: "Required"
}
});
self.next = function() {
self.currentPage(self.currentPage() + 1);
};
self.back = function() {
self.currentPage(self.currentPage() - 1);
};
self.CourseDetails = ko.observableArray();
self.addCourse = function() {
self.CourseDetails.push(new coursesList());
}
self.pageVisible = function(page) {
return self.currentPage() == page;
}
}
function coursesList() {
var self = this;
self.otherSolution = ko.observable().extend({
required: {
params: true,
message: "Required"
}
});
}
ko.applyBindings(new ViewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<div id="Page_1" data-bind="visible: pageVisible(1)">
<h2>Page 1</h2>
<div class="form-group required">
<label for="Solution" class="control-label">Solution</label>
<select id="Solution" name="Solution" class="form-control" data-bind="options: solutions, value: solution, optionsCaption: 'Select'"></select>
</div>
<button type="submit" id="NtPg" class="SubmitButton" data-bind="click: next">Next</button>
</div>
<div id="Page_2" data-bind="visible: pageVisible(2)">
<h2>Page 2</h2>
<button type="submit" id="AddCourseInfo" class="SubmitButton" data-bind="click: addCourse">Add Course Info</button>
<div data-bind="foreach:CourseDetails">
<div class="form-group required" data-bind="visible: $parent.solution() == 'Other'">
<label for="OtherSolution" class="control-label">Explain Other Solution : </label>
<input type="text" maxlength="1000" id="OtherSolution" name="OtherSolution" class="form-control" data-bind="value : otherSolution" required/>
</div>
</div>
<button type="submit" id="NtPg" class="SubmitButton" data-bind="click: back">Back</button>
</div>
<pre data-bind="text: ko.toJSON($root)"></pre>

Highlight error on specific textbox: KnockoutJS

Using asp.net mvc, c#, knockoutjs
I have a textarea and a ADD button next to it. User can enter in the textarea and press CHECK button or they can click the ADD button to add another textarea and then click CHECK button. There is no restriction to no of textarea they can add.
When they press the CHECK button I the code hits my MVC controller method and does some validations on the input. If the input is fine I proceed else show error.
My issue is highlighting and displaying the correct error. Right now I am just using one generic error label to show error. I want to show error to the textarea which has the error not a generic one. I have my code as below.
<form id="form" asp-controller="Home" asp-action="FilterMaintenance" method="post">
<div class="form-horizontal" style="margin-top:50px">
<div data-bind="foreach: { data: records }">
<div class="form-group">
<div class="col-md-6 ">
<textarea rows="1" class="required text-danger form-control textbox-wide" data-bind="value: $data.input, attr: { name: 'Records[' + $index() + '].Input', id: 'Records[' + $index() + '].Input'}"></textarea>
<span class="help-block-msg field-validation-valid" data-valmsg-replace="true" data-bind="attr: { 'data-valmsg-for': 'Records[' + $index() + '].Input'}"></span>
</div>
<div class="col-md-2">
<input type="button" value="Add Record" class="btn btn-primary" data-bind="click: $parent.addRecord, visible: function(){ return !($index() != $parent.records().length - 1) }()" />
</div>
</div>
</div>
<div class="form-group">
<div class="col-md-2">
<input type="button" value="Check Input" class="btn btn-primary" data-bind='click: checkInput' />
</div>
</div>
<div class="form-group" data-bind="visible: errorFlag">
<span data-bind="text: errorText"></span>
</div>
</div>
Knockout code:
(function () {
var viewModel = function (vmData) {
var self = this;
self.errorFlag = ko.observable(false);
self.errorText = ko.observable();
self.records = ko.observableArray();
self.records([{
input: ""
}]);
self.addRecord = function () {
self.records.push(
{
input: ""
});
};
self.checkInput = function () {
var returnVal = false;
var records = self.records();
var input = JSON.stringify(records);
//With abc as first and def as second texarea entry I get as below
//"[{"input":"abc"},{"input":"def"}]"
$.ajax({
type: "POST",
async: false,
url:"/Home/CheckRecord",
contentType: 'application/x-www-form-urlencoded; charset=utf-8',
data: {
"input": input
},
success: function (data) {
self.errorFlag(true);
if (data == '') {
self.errorText('Input is correct');
returnVal = true;
}
else {
self.errorText('Input is not correct: ' + data);
returnVal = false;
}
}
});
return returnVal;
}
}
var pageVM = new viewModel();
ko.applyBindings(pageVM, $("form")[0]);
})();
MVC Controller:
[HttpPost]
public IActionResult CheckRecord(string input)
{
string parseError = string.Empty;
bool inputCheck = false;
string returnValue = string.Empty;
inputCheck = false;//doing some checks here then return true or false
returnValue = inputCheck ? "" : "error";
var ret = Json(returnValue);
return ret;
}
Also I get the below generated HTML for 2 textarea added:
<div class="form-group">
<div class="col-md-6 ">
<textarea rows="1" class="required text-danger form-control textbox-wide" data-bind="value: $data.input, attr: { name: 'Records[' + $index() + '].Input', id: 'Records[' + $index() + '].Input'}" name="Records[0].Input" id="Records[0].Input"></textarea>
<span class="help-block-msg field-validation-valid" data-valmsg-replace="true" data-bind="attr: { 'data-valmsg-for': 'Records[' + $index() + '].Input'}" data-valmsg-for="Records[0].Input"></span>
</div>
<div class="col-md-2">
<input type="button" value="Add Record" class="btn btn-primary" data-bind="click: $parent.addRecord, visible: function(){ return !($index() != $parent.records().length - 1) }()" style="display: none;">
</div>
</div>
<div class="form-group">
<div class="col-md-6 ">
<textarea rows="1" class="required text-danger form-control textbox-wide" data-bind="value: $data.input, attr: { name: 'Records[' + $index() + '].Input', id: 'Records[' + $index() + '].Input'}" name="Records[1].Input" id="Records[1].Input"></textarea>
<span class="help-block-msg field-validation-valid" data-valmsg-replace="true" data-bind="attr: { 'data-valmsg-for': 'Records[' + $index() + '].Input'}" data-valmsg-for="Records[1].Input"></span>
</div>
<div class="col-md-2">
<input type="button" value="Add Record" class="btn btn-primary" data-bind="click: $parent.addRecord, visible: function(){ return !($index() != $parent.records().length - 1) }()">
</div>
</div>
Sorry for the long post but wanted to show the full code for explanation.
Any inputs are appreciated.
Update:
I have a added this in jsfiddle as well:
https://jsfiddle.net/gmmmh873/
I would recommend looking at Knockout-Validation. It can handle most of this for you, and has great documentation to get you started. This injects error messages per input field when the rules are not met. You can also trigger these from the response back from your xhr call as well.

creating textbox element dynamically and bind different model

I am working in angular js application, where i need to create textbox with buttons dynamically that means
<div class="col-sm-4 type7" style="font-size:14px;">
<div style="margin-bottom:5px;">NDC9</div>
<input type="text" name="ndc9" class="form-control txtBoxEdit" ng-model="ndc9">
</div>
<div class="col-sm-4 type7 " style="font-size:14px;">
<div style="padding-top:20px; display:block">
<span class="red" id="delete" ng-class="{'disabled' : 'true'}">Delete</span> <span>Cancel </span> <span id="addRow" style="cursor:pointer" ng-click="ndcCheck(0)">Add </span>
</div>
</div>
this will create below one
i will enter some value in above textbox and click add ,it needs to be created in next line with same set of controls that means (textbox with above 3 buttons need to be created again with the entered value).
Entering 123 in first textbox and click add will create new textbox with delete,cancel,add button with entered value.
Again am adding new value 243 then again it needs to create new textbox down to next line with the entered value (and also the same controls).
finally i want to get all the entered values. how can i achieve this in angular js
You could use ng-repeat with an associative array. Add Would basically push the model value to an array and and also an empty object in the array.
<div ng-repeat ="ndc in NDCarray">
<div class="col-sm-4 type7" style="font-size:14px;">
<div style="margin-bottom:5px;">NDC9</div>
<input type="text" name="ndc9" class="form-control txtBoxEdit" ng-model="ndc.val">
</div>
</div>
<div class="col-sm-4 type7 " style="font-size:14px;">
<div style="padding-top:20px; display:block">
<span class="red" id="delete" ng-class="{'disabled' : 'true'}" ng-click="NDCdelete($index)">Delete</span>
<span>Cancel </span>
<span id="addRow" style="cursor:pointer" ng-click="NDCadd ()">Add </span>
</div>
</div>
</div>
In the controller:
$scope.NDCarray = [{val: ''}];
$scope.NDCadd = function() {
$scope.NDCarray.unshift(
{val: ''}
);
};
$scope.NDCdelete = function(index) {
$scope.NDCarray.splice(index, 1);
};
Plunker: https://plnkr.co/edit/3lklQ6ADn9gArCDYw2Op?p=preview
Hope this helps!!
<html ng-app="exampleApp">
<head>
<title>Directives</title>
<meta charset="utf-8">
<script src="angular.min.js"></script>
<script type="text/javascript">
angular.module('exampleApp', [])
.controller('defaultCtrl', function () {
vm = this;
vm.numbers = [1, 2, 3];
vm.add = function (number) {
vm.numbers.push(number);
}
vm.remove = function (number) {
var index = vm.numbers.indexOf(number);
if(index>-1){
vm.numbers.splice(index, 1);
}
}
});
</script>
</head>
<body ng-controller="defaultCtrl as vm">
<div ng-repeat="num in vm.numbers">
<span>Number : {{num}}</span>
</div>
<div>
<input type="number" ng-model="vm.newNumber">
<button ng-click="vm.add(vm.newNumber)">Add</button>
<button ng-click="vm.remove(vm.newNumber)">Remove</button>
</div>
</body>
</html>

Angular error : Expected array but received: 0

I'm getting this error when I open up a model partial:
<form action="" novalidate name="newGroupForm">
<div class="modal-body">
<div class="row">
<!-- SELECT THE NUMBER OF GROUPS YOU WANT TO CREATE -->
<label>Select number of groups</label>
<a class="btn" ng-click="newGroupCount = newGroupCount + 1" ng-disabled="newGroupCount == 10" ><i class="fa fa-plus-circle"></i></a>
<input class="groupCounter input-sm" ng-model="newGroupCount" type="number" min="1" max="10" disabled>
<a class="btn" ng-click="newGroupCount = newGroupCount - 1" ng-disabled="newGroupCount == 1"><i class="fa fa-minus-circle"></i></a>
</div>
<br>
<table class="table table-striped">
<thead>
<tr>
<th>#</th>
<th>Group Name</th>
<th>Group Description (optional)</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="i in getNumber(newGroupCount) track by $index">
<td>{{$index+1}}</td>
<td>
<input class= input-sm type="text" required="true" autofocus="true" placeholder="Group name" ng-model="groupData.title[$index]">
</td>
<td>
<input class="form-control input-sm" type="textarea" ng-model="groupData.desc[$index]" placeholder="Group Description">
</td>
</tr>
</tbody>
</table>
</div>
<div class="modal-footer">
<button class="btn btn-warning" ng-click="cancel()">Cancel</button>
<button class="btn btn-primary" type="submit" ng-click="submit()" ng-disabled="newGroupForm.$invalid">Create</button>
</div>
</form>
The modal controller looks like this:
spApp.controller('newGroupCtrl',
function newGroupCtrl($scope, $uibModalInstance, GroupService){
$scope.groupData = {
title: [],
desc: []
}
$scope.newGroupCount = 1;
$scope.getNumber = function(num) {
//console.log(num);
return new Array(num);
}
$scope.submit = function(){
$uibModalInstance.close($scope.groupData);
}
$scope.cancel = function (){
$uibModalInstance.dismiss('Cancelled group creation');
};
}
);
Every question I've seen refers to the use of filterbut I'm not using filter. The error repeats whenever I hit the increment button:
<a class="btn" ng-click="newGroupCount = newGroupCount + 1" ng-disabled="newGroupCount == 10" ><i class="fa fa-plus-circle"></i></a>
$scope.getNumber calls new Array(num), which will return an array of undefined values directly proportional to the value of newGroupCount.
For example:
new Array(5) // => [undefined, undefined, undefined, undefined, undefined]
Browsers don't handle that well, since it appears to be an empty array.
You're using ng-repeat in a way that it wasn't quite meant to be used. If I were you, I'd refactor to look something like this:
$scope.groups = [];
$scope.addGroup = function() {
// implement this, and use it in your button that increments the groups
$scope.groups.push(/* whatever */);
}
$scope.removeGroup = function() {
// implement this to remove a group
$scope.groups.splice(/* whatever */);
}
Then in your HTML:
<tr ng-repeat="group in groups">
<!-- display group info -->
</tr>
It may make your life easier here to work with angular (use it how it was intended) instead of fighting against how ng-repeat is meant to work.
The data is generally meant to be in the form of a collection (i.e. [{},{},{}]). Formatting it as such will make it easier for you. See the docs for ng-repeat.

add and delete json list inside ng-repeat

i want to create a page where someone adds 1 or more locations to a contact and right now i have something that looks like this.
<div class="input-append" ng-repeat="location in newPartner.partner_location">
<input class="input-large" type="text" ng-model="location">
<button class="btn" type="button" ng-click="delLocation1({{$index}})">- {{$index}}</button>
</div>
<div class="input-append">
<input class="input-large" type="text" ng-model="new_location">
<button class="btn btn-primary" type="button" ng-click="addLocation1()">+</button>
</div>
This is the HTML and the controller looks like this.
$scope.newPartner = {'partner_name':'newname','partner_location':['X','Y','Z']};
$scope.addLocation1 = function() {
$scope.newPartner.partner_location.push($scope.new_location);
$scope.new_location = "";
}
$scope.delLocation1 = function(id) {
$scope.newPartner.partner_location.splice(id, 1);
}
Now it works great on begin but if i delete some items and add some it suddenly bugs out and starts to delete the previous item instead the one i press - (minus) on.
Is there something i did wrong? Thank you in advance, Daniel!
First off remove {{}} from ng-click="delLocation1({{$index}})". It should be:
ng-click="delLocation1($index).
Second, I suggest you to add some basic debugger to see what happens with our model when we add new value: <pre>{{newPartner.partner_location|json}}</pre>
Third, I would change the model to:
$scope.newPartner = {
'partner_name': 'newname',
'partner_location': [{value:'X'}, {value:'Y'}, {value:'Z'}]
};
because, by this way: ['X','Y','Z'] we can't modify our data.
Demo Fiddle
Finally this is our fixed code:
HTML
<div ng-controller="fessCntrl">
<div ng-repeat="location in newPartner.partner_location">
<input class="input-large" type="text" ng-model="location.value">
<button class="btn" type="button" ng-click="delLocation1(newPartner.partner_location, $index)">{{$index}}</button>
</div>
<div class="input-append">
<input class="input-large" type="text" ng-model="new_location">
<button class="btn btn-primary" type="button" ng-click="addLocation1()">+</button>
</div>
<pre>{{newPartner.partner_location|json}}</pre>
</div>
JS
var fessmodule = angular.module('myModule', []);
fessmodule.controller('fessCntrl', function ($scope) {
$scope.new_location = "empty";
$scope.newPartner = {
'partner_name': 'newname',
'partner_location': [{value:'X'}, {value:'Y'}, {value:'Z'}]
};
$scope.addLocation1 = function () {
$scope.newPartner.partner_location.push({value:$scope.new_location});
$scope.new_location = "empty";
}
$scope.delLocation1 = function (locations, index) {
locations.splice(index, 1);
}
});
fessmodule.$inject = ['$scope'];

Categories

Resources