Knockout JS - binding data objects of select lists - javascript

Objective
I have the desire to create a Claims Form. This claims form must support the following:
Add(create) a Claim Line
Store(read) all Claim Lines
Edit(update) a Claim Line
Delete(destroy) a Claim Line
Display a variable number of fields, based on user selection
Requirement #5 was handled here
Retrieve boolean value from selected array object
Issues
The current code has a broken edit and update process, I know I have an issue in binding my data to the appropriate select list, but I cannot see it.
Desired Result
The answer from the above SO question has to remain intact, if possible.
When the user add a claim line the form should revert to its state onLoad.
When the user edits a claim line, it should rebuild the form to accommodate the data
When the user updates the claim line it should update the line in the saved claim list
Javascript
var myViewModel = window["myViewModel"] = {};
(function () {
myViewModel = function () {
var self = this;
self.claimLines = ko.observableArray(ko.utils.arrayMap(claimLines, function (claimLine) {
return new ClaimLine(new ClaimLine("","","","","",""));
}));
// Changed newClaimLine to observable with empty ClaimLine
self.newClaimLine = ko.observable(new ClaimLine("","","","","",""));
self.editClaimLine = function (claimLineItem) {
var editable = new ClaimLine(claimLineItem.serviceStartDate(), claimLineItem.serviceEndDate(), claimLineItem.planType(), claimLineItem.expenseType(), claimLineItem.amount(), claimLineItem.provider());
claimLineBeingEdited = claimLineItem;
self.newClaimLine(editable);
var test = 'test';
};
// The only thing the update method does is emptying the editor form
self.updateClaimLine = function (claimLineBeingUpdated) {
var test = 'test';
claimLineBeingEdited.serviceStartDate(claimLineBeingUpdated.serviceStartDate());
claimLineBeingEdited.serviceEndDate(claimLineBeingUpdated.serviceEndDate());
claimLineBeingEdited.planType(claimLineBeingUpdated.planType());
claimLineBeingEdited.expenseType(claimLineBeingUpdated.expenseType());
claimLineBeingEdited.amount(claimLineBeingUpdated.amount());
claimLineBeingEdited.provider(claimLineBeingUpdated.provider());
self.newClaimLine(new ClaimLine("","","","","",""));
isClaimFor = false;
isExpenseType = false;
};
// This method can only be used for adding new items, not updating existing items
self.addClaimLine = function (claimLineBeingAdded) {
self.claimLines.push(new ClaimLine(claimLineBeingAdded.serviceStartDate(), claimLineBeingAdded.serviceEndDate(), claimLineBeingAdded.planType(), claimLineBeingAdded.expenseType(), claimLineBeingAdded.amount(), claimLineBeingAdded.provider()));
self.newClaimLine(new ClaimLine("","","","","",""));
};
//remove an existing claim line
self.removeClaimLine = function (claimLine) {
self.claimLines.remove(claimLine);
}
//aggregate claim amounts
self.grandTotal = ko.computed(function() {
var total = 0;
$.each(self.claimLines(), function() {
total += parseFloat(this.amount())
});
return "$" + total.toFixed(2);
});
};
function ClaimLine(serviceStartDate, serviceEndDate, planType, expenseType, amount, provider) {
var line = this;
line.serviceStartDate = ko.observable(ko.utils.unwrapObservable(serviceStartDate));
line.serviceEndDate = ko.observable(ko.utils.unwrapObservable(serviceEndDate));
line.planType = ko.observable(ko.utils.unwrapObservable(selectedPlanTypeId));
line.expenseType = ko.observable(ko.utils.unwrapObservable(selectedExpenseTypeId));
line.amount = ko.observable(ko.utils.unwrapObservable(amount));
line.provider = ko.observable(ko.utils.unwrapObservable(provider));
line.expenseTypeName = ko.computed(function() {
return ko.utils.arrayFirst(self.expenseTypes, function (expenseTypeSomething) {
return expenseTypeSomething.id == line.expenseType();
});
});
line.planTypeName = ko.computed(function() {
return ko.utils.arrayFirst(self.planTypes, function (planTypeSomething) {
return planTypeSomething.id == line.planType();
});
});
}
var claimLines = [
];
self.planTypes = [
{ id: 1, name: 'The EBC HRA - Deductible', hasClaimFor: true, hasExpenseType: false },
{ id: 2, name: 'FSA - Health Care FSA', hasClaimFor: false, hasExpenseType: true },
{ id: 3, name: 'FSA - Dependent Care FSA', hasClaimFor: false, hasExpenseType: true }
];
self.claimForWhom = [
{ id: 1, name: "Self"},
{ id: 2, name: "Boston Allen (Dependent)"},
{ id: 3, name: "Bishop Allen (Dependent)"},
{ id: 4, name: "Billy Allen Jr (Dependent)"},
{ id: 5, name: "Billy Allen Sr (Dependent)"},
{ id: 6, name: "Name not listed"}
];
self.expenseTypes = [
{ id: 1, name: "Chiropractic"},
{ id: 2, name: "Dental"},
{ id: 3, name: "Massage Therapy"},
{ id: 4, name: "Medical"},
{ id: 5, name: "Medical Mileage"},
{ id: 6, name: "Office Visit"},
{ id: 7, name: "Optical"},
{ id: 8, name: "Orthodontic"},
{ id: 9, name: "OTC"},
{ id: 10, name: "Prescription"},
{ id: 11, name: "Supplement/Vitamin"},
{ id: 12, name: "Therapy"}
];
self.providers = [
"Dean",
"Mercy Health",
"UW Health",
"Aurora"
];
self.selectedPlanTypeId = ko.observable();
self.selectedExpenseTypeId = ko.observable();
self.selectedClaimForWhomId = ko.observable();
self.selectedPlanType = ko.computed(function () {
var selectedPlanTypeId = self.selectedPlanTypeId();
return ko.utils.arrayFirst(self.planTypes, function (planType) {
return planType.id == selectedPlanTypeId;
});
});
self.selectedExpenseType = ko.computed(function () {
var selectedExpenseTypeId = self.selectedExpenseTypeId();
return ko.utils.arrayFirst(self.expenseTypes, function (expenseType) {
return expenseType.id == selectedExpenseTypeId;
});
});
self.isClaimFor = ko.computed(function(){
var selectedPlanType = self.selectedPlanType();
return selectedPlanType && !!selectedPlanType.hasClaimFor;
});
self.isExpenseType = ko.computed(function(){
var selectedPlanType = self.selectedPlanType();
return selectedPlanType && !!selectedPlanType.hasExpenseType;
});
})();
$(document).ready(function(){
myViewModel = new myViewModel();
ko.applyBindings(myViewModel);
$('.datepicker').datepicker();
});
HTML
<h3 class="body">Enter Claim Lines</h3>
<form class="form-horizontal col-xs-12 col-sm-12 col-md-12 col-lg-12" role="form" data-bind="with: newClaimLine">
<div class="form-group">
<label for="serviceStartDate" class="col-sm-4 control-label">Service Start Date</label>
<div class="col-sm-4">
<input id="serviceStartDate" type="date" class="form-control datepicker" data-bind="value: serviceStartDate" placeholder="mm/dd/yyyy" />
</div>
</div>
<div class="form-group">
<label for="serviceEndDate" class="col-sm-4 control-label">Service End Date</label>
<div class="col-sm-4">
<input id="serviceEndDate" type="date" class="form-control datepicker" data-bind="value: serviceEndDate" placeholder="mm/dd/yyyy" />
</div>
</div>
<div class="form-group">
<label for="planType" class="col-sm-4 control-label">Plan Type</label>
<div class="col-sm-4">
<select id="planType" class="form-control" data-bind="options: planTypes, optionsText: 'name', optionsCaption: 'Choose Plan Type', optionsValue: 'id', value: selectedPlanTypeId">
</select>
</div>
</div>
<div data-bind="if: isClaimFor">
<div class="form-group">
<label for="claimForWhom" class="col-sm-4 control-label">Claim For</label>
<div class="col-sm-4">
<select id="claimForWhom" class="form-control" data-bind="options: claimForWhom, optionsText : 'name', optionsCaption: 'Select Dependent', optionsValue: 'id', value: selectedClaimForWhomId"></select>
</div>
</div>
</div>
<div data-bind="if: isExpenseType">
<div class="form-group">
<label for="expenseType" class="col-sm-4 control-label">Expense Type</label>
<div class="col-sm-4">
<select id="expenseType" class="form-control" data-bind="options: expenseTypes, optionsText : 'name', optionsCaption: 'Select Expense Type', optionsValue: 'id', value: selectedExpenseTypeId"></select>
</div>
</div>
</div>
<div class="form-group">
<label for="amount" class="col-sm-4 control-label">Amount</label>
<div class="col-sm-4">
<input id="amount" type="date" class="form-control" data-bind="value: amount" placeholder="Enter Amount" />
</div>
</div>
<div class="form-group">
<label for="provider" class="col-sm-4 control-label">Provider</label>
<div class="col-sm-4">
<select id="provider" class="form-control" data-bind="options: providers, optionsCaption: 'Choose Provider Type', value: provider">
</select>
</div>
</div>
<div class="form-group">
<div class="col-sm-6 col-sm-offset-4">
<button class="btn btn-default" data-bind="click: $root.addClaimLine"><i class="fa fa-plus fa-lg fa-fw"></i> Add Claim Line</button>
<button class="btn btn-default" data-bind="click: $root.updateClaimLine"><i class="fa fa-refresh fa-lg fa-fw"></i> Update Claim Line</button>
</div>
</div>
</form>
<!-- Desktop saved claim lines -->
<table class="hidden-xs table table-responsive table-condensed" data-bind="visible: claimLines().length > 0">
<thead>
<tr>
<th colspan="2">Saved Claim Lines
<span class="pull-right">Claim Total = <span data-bind="text: grandTotal()"></span></span>
</th>
</tr>
</thead>
<tbody data-bind="foreach: claimLines">
<tr>
<td>
<p><strong><span data-bind="text: planTypeName().name"></span> - <span data-bind="text: expenseTypeName().name"></span><br /></strong><strong data-bind="text: $root.grandTotal()"></strong> claim incurred between <strong data-bind="text: serviceStartDate"></strong> and <strong data-bind="text: serviceEndDate"></strong>.</p>
</td>
<td class="text-right">
<button data-bind="click: $root.editClaimLine" class="btn btn-link">
<i class="fa fa-edit fa-2x"></i>
</button>
<button data-bind="click: $root.removeClaimLine" class="btn btn-link">
<i class="fa fa-times fa-2x"></i>
</button>
</td>
</tr>
</tbody>
<tfoot>
<tr>
<th class="text-right" colspan="2">
<span>Claim Total = <span data-bind="text: grandTotal()"></span></span>
</th>
</tr>
</tfoot>
</table>

There are a few issues here.
Your select arrays are declared as self.planTypes = .... self is a variable inside the constructor of myViewModel. You should be getting an exception that this point but something has declared a self variable to equal window.
Your selected... observables are also all on window scope, and not enclosed in myViewModel.
When you add a new claim line, I'm getting a javascript errors depending on what you select, like if expenseType is null.
Solution
I have created a top level namespace call Models and attached everything to that.
I have created an explicit class for the edit claim line. This allows you to added various help functions and observables without polluting the claimlines themselves.
I have changed all the options bindings to remove the Id parameter. I find it a lot easier to work with the object instead of constantly looking up the array member.
I have implemented the Add and Update functions.
I also removed the datapicker jQuery call, as you need to do more work to update the observable when using this plugin. DatePicker plugin and Knockout do not work side-by-side without assistance ( ie custom binding ).
I also added the letters E and R ( Edit and Remove ) in your claim line's buttons as I wasn't getting any UI ( missing CSS in your fiddle? )
HTML
<section class="row top10">
<div class="col-xs-12 col-sm-12 col-md-8 col-lg-8 col-md-offset-2 col-lg-offset-2">
<form class="form-horizontal col-xs-12 col-sm-12 col-md-12 col-lg-12" role="form" data-bind="with: newClaimLine">
<div class="form-group">
<label for="serviceStartDate" class="col-sm-4 control-label">Service Start Date</label>
<div class="col-sm-4">
<input id="serviceStartDate" type="date" class="form-control datepicker" data-bind="value: serviceStartDate" placeholder="mm/dd/yyyy" />
</div>
</div>
<div class="form-group">
<label for="serviceEndDate" class="col-sm-4 control-label">Service End Date</label>
<div class="col-sm-4">
<input id="serviceEndDate" type="date" class="form-control datepicker" data-bind="value: serviceEndDate" placeholder="mm/dd/yyyy" />
</div>
</div>
<div class="form-group">
<label for="planType" class="col-sm-4 control-label">Plan Type</label>
<div class="col-sm-4">
<select id="planType" class="form-control" data-bind="options: Models.planTypes, optionsText: 'name', optionsCaption: 'Choose Plan Type', value: planType"></select>
</div>
</div>
<div data-bind="if: isClaimFor">
<div class="form-group">
<label for="claimForWhom" class="col-sm-4 control-label">Claim For</label>
<div class="col-sm-4">
<select id="claimForWhom" class="form-control" data-bind="options: Models.claimForWhom, optionsText : 'name', optionsCaption: 'Select Dependent', value: claimFor"></select>
</div>
</div>
</div>
<div data-bind="if: isExpenseType">
<div class="form-group">
<label for="expenseType" class="col-sm-4 control-label">Expense Type</label>
<div class="col-sm-4">
<select id="expenseType" class="form-control" data-bind="options: Models.expenseTypes, optionsText : 'name', optionsCaption: 'Select Expense Type', value: expenseType"></select>
</div>
</div>
</div>
<div class="form-group">
<label for="amount" class="col-sm-4 control-label">Amount</label>
<div class="col-sm-4">
<input id="amount" type="number" class="form-control" data-bind="value: amount" placeholder="Enter Amount" />
</div>
</div>
<div class="form-group">
<label for="provider" class="col-sm-4 control-label">Provider</label>
<div class="col-sm-4">
<select id="provider" class="form-control" data-bind="options: Models.providers, optionsCaption: 'Choose Provider Type', value: provider"></select>
</div>
</div>
<div class="form-group">
<div class="col-sm-6 col-sm-offset-4">
<button class="btn btn-default" data-bind="click: $root.addClaimLine, enable: !claimId()"><i class="fa fa-plus fa-lg fa-fw"></i> Add Claim Line</button>
<button class="btn btn-default" data-bind="click: $root.updateClaimLine, enable: claimId"><i class="fa fa-refresh fa-lg fa-fw"></i> Update Claim Line</button>
</div>
</div>
</form>
<!-- Desktop saved claim lines -->
<table class="hidden-xs table table-responsive table-condensed" data-bind="visible: claimLines().length > 0">
<thead>
<tr>
<th colspan="2">Saved Claim Lines <span class="pull-right">Claim Total = <span data-bind="text: grandTotal()"></span></span>
</th>
</tr>
</thead>
<tbody data-bind="foreach: claimLines">
<tr>
<td>
<p><strong><span data-bind="text: planTypeName().name"></span> - <span data-bind="text: expenseTypeName().name"></span><br /></strong><strong data-bind="text: $root.grandTotal()"></strong> claim incurred between <strong data-bind="text: serviceStartDate"></strong> and <strong data-bind="text: serviceEndDate"></strong>.</p>
</td>
<td class="text-right">
<button data-bind="click: $root.editClaimLine" class="btn btn-link"> <i class="fa fa-edit fa-2x">E</i>
</button>
<button data-bind="click: $root.removeClaimLine" class="btn btn-link"> <i class="fa fa-times fa-2x">R</i>
</button>
</td>
</tr>
</tbody>
<tfoot>
<tr>
<th class="text-right" colspan="2"> <span>Claim Total = <span data-bind="text: grandTotal()"></span></span>
</th>
</tr>
</tfoot>
</table>
<!-- Mobile saved claim lines -->
<div class="hidden-sm hidden-md hidden-lg" data-bind="visible: claimLines().length > 0">
<h3 class="body">Saved Claim Lines</h3>
<div data-bind="foreach: claimLines">
<div>
<p>Your <strong data-bind="text: planTypeName().name"></strong> incurred a <strong data-bind="text: expenseTypeName().name"></strong> claim for <strong data-bind="text: $root.grandTotal()"></strong> between <strong data-bind="text: serviceStartDate"></strong> - <strong data-bind="text: serviceEndDate"></strong>.</p>
<p>
<button data-bind="click: $root.editClaimLine" class="btn btn-default"> <i class="fa fa-edit fa-lg"></i> Edit Claim Line</button>
<button data-bind="click: $root.removeClaimLine" class="btn btn-default"> <i class="fa fa-times fa-lg"></i> Delete Claim Line</button>
</p>
</div>
</div>
</div>
<h3 class="body">Attach Supporting Documentation</h3>
<button class="btn btn-default btn-lg" role="button"> <i class="fa fa-cloud-upload fa-3x fa-fw pull-left"></i>
<span class="pull-left text-left">Upload<br />Documentation</span>
</button>
<hr />
<div class="pull-right">
<button class="btn btn-link btn-lg">Cancel</button>
<button class="btn btn-default btn-lg" role="button"> <i class="fa fa-check fa-fw"></i>Verify Claim</button>
</div>
</div>
</section>
Javascript
var Models = window["Models"] = {};
(function () {
Models.ViewModel = function () {
var self = this;
var newClaimId = 0;
self.claimLines = ko.observableArray(ko.utils.arrayMap(claimLines, function (claimLine) {
return new Models.ClaimLine("","","","","","", "");
}));
// Changed newClaimLine to observable with empty ClaimLine
self.newClaimLine = new Models.EditClaimLine();
self.editClaimLine = function(claimLineItem) {
self.newClaimLine.edit(claimLineItem);
};
/*
self.editClaimLine = function (claimLineItem) {
var editable = new ClaimLine(claimLineItem.serviceStartDate(), claimLineItem.serviceEndDate(), claimLineItem.planType(), claimLineItem.expenseType(), claimLineItem.amount(), claimLineItem.provider());
claimLineBeingEdited = claimLineItem;
self.newClaimLine(editable);
var test = 'test';
};
*/
// The only thing the update method does is emptying the editor form
self.updateClaimLine = function (claimLineBeingUpdated) {
var foundClaim = ko.utils.arrayFirst( self.claimLines(), function(item) { return item.claimId() == claimLineBeingUpdated.claimId(); } );
var test = 'test';
foundClaim.serviceStartDate(claimLineBeingUpdated.serviceStartDate());
foundClaim.serviceEndDate(claimLineBeingUpdated.serviceEndDate());
foundClaim.planType(claimLineBeingUpdated.planType());
foundClaim.expenseType(claimLineBeingUpdated.expenseType());
foundClaim.amount(claimLineBeingUpdated.amount());
foundClaim.provider(claimLineBeingUpdated.provider());
foundClaim.claimFor(claimLineBeingUpdated.claimFor());
self.newClaimLine.reset(); //(new ClaimLine("","","","","",""));
};
// This method can only be used for adding new items, not updating existing items
self.addClaimLine = function (claimLineBeingAdded) {
var newClaim = new Models.ClaimLine(claimLineBeingAdded.serviceStartDate, claimLineBeingAdded.serviceEndDate, claimLineBeingAdded.planType, claimLineBeingAdded.expenseType, claimLineBeingAdded.amount, claimLineBeingAdded.provider, claimLineBeingAdded.claimFor);
newClaim.claimId(++newClaimId);
self.claimLines.push(newClaim);
self.newClaimLine.reset(); //(new ClaimLine("","","","","",""));
};
//remove an existing claim line
self.removeClaimLine = function (claimLine) {
self.claimLines.remove(claimLine);
}
//aggregate claim amounts
self.grandTotal = ko.computed(function() {
var total = 0;
$.each(self.claimLines(), function() {
total += parseFloat(this.amount())
});
return "$" + total.toFixed(2);
});
};
Models.EditClaimLine = function() {
var self = this;
self.claimId = ko.observable();
self.serviceStartDate = ko.observable();
self.serviceEndDate = ko.observable();
self.planType = ko.observable();
self.claimFor = ko.observable();
self.expenseType = ko.observable();
self.amount = ko.observable();
self.provider = ko.observable();
self.isClaimFor = ko.computed(function(){
var selectedPlanType = self.planType();
return selectedPlanType && !!selectedPlanType.hasClaimFor;
});
self.isExpenseType = ko.computed(function(){
var selectedPlanType = self.planType();
return selectedPlanType && !!selectedPlanType.hasExpenseType;
});
self.reset = function(){
self.claimId(undefined);
self.serviceStartDate(undefined);
self.serviceEndDate(undefined);
self.planType(undefined);
self.claimFor(undefined);
self.expenseType(undefined);
self.amount(undefined);
self.provider(undefined);
};
self.edit = function(claim) {
self.claimId(claim.claimId());
self.serviceStartDate(claim.serviceStartDate());
self.serviceEndDate(claim.serviceEndDate());
self.planType(claim.planType());
self.claimFor(claim.claimFor());
self.expenseType(claim.expenseType());
self.amount(claim.amount());
self.provider(claim.provider());
};
self.reset();
}
Models.ClaimLine = function(serviceStartDate, serviceEndDate, planType, expenseType, amount, provider, claimFor) {
var line = this;
var getName = function(value){
return (ko.unwrap(value) || { name: '' }).name;
};
line.claimId = ko.observable();
line.serviceStartDate = ko.observable(ko.unwrap(serviceStartDate));
line.serviceEndDate = ko.observable(ko.unwrap(serviceEndDate));
line.planType = ko.observable(ko.unwrap(planType));
line.expenseType = ko.observable(ko.unwrap(expenseType));
line.amount = ko.observable(ko.unwrap(amount));
line.provider = ko.observable(ko.unwrap(provider));
line.claimFor = ko.observable(ko.unwrap(claimFor));
line.expenseTypeName = ko.computed(function() {
return getName(line.expenseType);
});
line.planTypeName = ko.computed(function() {
return getName(line.planType);
});
}
var claimLines = [
];
Models.planTypes = [
{ id: 1, name: 'The EBC HRA - Deductible', hasClaimFor: true, hasExpenseType: false },
{ id: 2, name: 'FSA - Health Care FSA', hasClaimFor: false, hasExpenseType: true },
{ id: 3, name: 'FSA - Dependent Care FSA', hasClaimFor: false, hasExpenseType: true }
];
Models.claimForWhom = [
{ id: 1, name: "Self"},
{ id: 2, name: "Boston Allen (Dependent)"},
{ id: 3, name: "Bishop Allen (Dependent)"},
{ id: 4, name: "Billy Allen Jr (Dependent)"},
{ id: 5, name: "Billy Allen Sr (Dependent)"},
{ id: 6, name: "Name not listed"}
];
Models.expenseTypes = [
{ id: 1, name: "Chiropractic"},
{ id: 2, name: "Dental"},
{ id: 3, name: "Massage Therapy"},
{ id: 4, name: "Medical"},
{ id: 5, name: "Medical Mileage"},
{ id: 6, name: "Office Visit"},
{ id: 7, name: "Optical"},
{ id: 8, name: "Orthodontic"},
{ id: 9, name: "OTC"},
{ id: 10, name: "Prescription"},
{ id: 11, name: "Supplement/Vitamin"},
{ id: 12, name: "Therapy"}
];
Models.providers = [
"Dean",
"Mercy Health",
"UW Health",
"Aurora"
];
})();
$(document).ready(function(){
var myViewModel = new Models.ViewModel();
ko.applyBindings(myViewModel);
//$('.datepicker').datepicker();
});

Related

angularJs inifinite-scroll directive - shows only first 20 rows

I am trying to use inifinite-scroll directive for angularJS. The examples show usage of div inside the div, but in my case I'm trying to use it in a table. Here is my html:
<div class="scrolling-table-body">
<table class="table table-bordered table-hover table-list">
<thead search-table-header data-table="duplicatesTable"
data-search="sort(column)"
data-show-row-selector="true"
data-hide-sorting-indicator="true"
data-row-selector-click="selectAllRows(allSelected)"
data-column="column">
</thead>
<tbody infinite-scroll="loadMore()">
<tr ng-repeat="row in duplicatesArray"
ng-click="selectedDuplicateIndex=$index;"
ng-class="{selected: $index === selectedDuplicateIndex}">
<td style="text-align:center;">
<input type="checkbox"
name="checkRow"
ng-model="row.isSelected"
ng-change="selectRow(row, $index);" />
</td>
<td>
<span ng-if="row.barcode>0">{{row.barcode}}</span>
<span>{{$index}}</span>
<span class="pull-right">
<i class="fa fa-trash"
style="color:red;"
ng-click="removeRow($index)"
title="#Labels.delete"></i>
</span>
</td>
<td>
<div class="col-xs-12">
<input type="text"
name="assetNo"
id="assetNo"
ng-model="row.assetNo"
class="form-control"
ng-change="checkAssetNo(row)"
ng-maxlength="100"
sm-duplicate-validator
validate-duplicates="true"
error-message="row.errorMessage"
api-method="api/rentalEquipments/checkForDuplicate"
primary-key-value="row.equipmentId"
ng-model-options="{ debounce: { default : 500, blur: 0 }}" />
</div>
</td>
<td>
<input type="text"
name="serialNo1"
id="serialNo1"
ng-model="row.serialNo1"
class="form-control"
ng-maxlength="100" />
</td>
The above is used inside the modal form (bootstrap modal).
I initially load 10 rows into my duplicatesArray and I have the following code for loadMore function:
$scope.loadMore = function () {
const last = $scope.duplicatesArray.length;
if (last < $scope.numberOfDuplicates) {
for (let i = 1; i <= 10; i++) {
self.logInfo("Loading more duplicates...");
const newEquipment = {
equipmentId: (last + i) * -1,
descrip: self.model.descrip,
homeShopId: self.model.homeShopId,
ruleId: self.model.ruleId,
manufacturerId: self.model.manufacturerId,
modelId: self.model.modelId,
typeId: self.model.typeId,
levelId: self.model.levelId,
equipSize: self.model.equipSize,
bootMm: self.model.bootMm,
bindingManufacturerId: self.model.bindingManufacturerId,
bindingModelId: self.model.bindingModelId,
cost: self.model.cost,
bindingCost: self.model.bindingCost,
unitCost: self.model.unitCost,
errorMessage: "",
duplicateForm: true,
duplicatedId: self.model.equipmentId,
isDuplicate: true,
barcode: 0,
assetNo: "",
serialNo1: "", serialNo2: "", serialNo3: "", serialNo4: "",
isSelected: false
};
$scope.duplicatesArray.push(newEquipment);
}
}
};
There is currently an issue in this js code (I moved check for last < numberOfDuplicates before the loop thinking it may be the issue).
When I open my modal I see 20 items in the list and when I scroll I don't see more items.
Do you see what am I doing wrong?
Also, does it matter that I have the following markup for the modal:
<ng-form name="equipmentDuplicatesForm">
<div class="modal-body">
<div id="fixed-header-table">
<div class="fixed-header-bg">
</div>
<div class="scrolling-table-body">
table goes here
</div>
<div class="modal-footer hundred-percent padTop padBottom">
<button type="button" class="btn btn-warning"
data-dismiss="modal" aria-hidden="true"
ng-click="$dismiss()">
#Labels.cancel
</button>
</div>
</ng-form>

How to add text field dynamically with angular2

Hi guys I'm using angular2 in my project and I'm trying to add an input text dymanically, and it works heres the code :
TS:
initArray() {
return this._fb.group({
title: ['', Validators.required]
})
}
addArray() {
const control = <FormArray>this.myForm.controls['myArray'];
//this.myGroupName.push(newName);
control.push(this.initArray());
}
HTML:
<div formArrayName="myArray">
<div *ngFor="let myGroup of myForm.controls.myArray.controls; let i=index">
<div [formGroupName]="i">
<span *ngIf="myForm.controls.myArray.controls.length > 1" (click)="removeDataKey(i)" class="glyphicon glyphicon-remove pull-right" style="z-index:33;cursor: pointer">
</span>
<!--[formGroupName]="myGroupName[i]"-->
<div [formGroupName]="myGroupName[i]">
<div class="inner-addon left-addon ">
<i class="glyphicon marker" style="border: 5px solid #FED141"></i>
<input type="text" style="width:50% !important" formControlName="title" class="form-control" placeholder="Exemple : Maarif, Grand Casablanca" name="Location" Googleplace (setAddress)="getAddressOnChange($event,LocationCtrl)">
<br/>
</div>
</div>
<!--[formGroupName]="myGroupName[i]"-->
</div>
<!--[formGroupName]="i" -->
</div>
</div>
<br/>
<a (click)="addArray()" style="cursor: pointer">+ add text Field</a>
it gives me an error when i add a new text field, any suggestions please??
ERROR Error: Cannot find control with path: 'myArray -> 1 ->
Do not manupulate DOM with AngularJS
In controller create array of inputs
$scope.inputs = [];
$scope.inputs.push({ id: 'input1', value: 'input1' });
$scope.inputs.push({ id: 'input2', value: 'input2' });
$scope.inputs.push({ id: 'input2', value: 'input2' });
<input ng-repeat="input in inputs" ng-model="input.value">
Example

How to modify this demo of vue.js?

demo:
https://jsfiddle.net/8hh0p0ej/
In this demo,there is a selected attribute in items,which control the item selected or not and "allSelected".
Question:
When I use it in my project,the content of items will be gotten form mysql,there is no selected field,the status of one item doesn't need to be recorded,so I don't want to add this field,how to accomplish "Allselected" in this situation?
You can add a selected member to the data after it is fetched.
var vm = new Vue({
el: "#app",
data: {
items: []
},
methods: {
fillIn: function(index, n) {
this.items[index].num = n;
}
},
computed: {
nums: function() {
return [1, 2, 3, 4, 5];
},
allSelected: {
get: function() {
for (var i = 0, length = this.items.length; i < length; i++) {
if (this.items[i].selected === false) {
return false;
}
}
return true;
},
set: function(val) {
for (var i = 0, length = this.items.length; i < length; i++) {
this.items[i].selected = val;
}
}
},
sum: function() {
var totalAmount = 0;
for (var i = 0, length = this.items.length; i < length; i++) {
var item = this.items[i];
if (item.selected === true) {
totalAmount += item.price * item.num;
}
}
return totalAmount;
}
}
});
// Data as it might come from mysql
mysqlData = [{
message: 'Apple',
num: 1,
price: 5
}, {
message: 'Peach',
num: 1,
price: 10
}, {
message: 'Orange',
num: 1,
price: 15
}, {
message: 'Pear',
num: 1,
price: 20
}];
// Modify the data when we put it in the vm
vm.$set('items', mysqlData.map((item) => {
item.selected = false;
return item;
}));
<link href="//cdnjs.cloudflare.com/ajax/libs/tether/1.3.7/css/tether.min.css" rel="stylesheet" />
<script src="//cdnjs.cloudflare.com/ajax/libs/tether/1.3.7/js/tether.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<link href="//cdn.bootcss.com/bootstrap/4.0.0-alpha.3/css/bootstrap.min.css" rel="stylesheet" />
<script src="//cdn.bootcss.com/bootstrap/4.0.0-alpha.3/js/bootstrap.min.js"></script>
<script src="//cdn.bootcss.com/vue/1.0.26/vue.min.js"></script>
<div class="container">
<div class="card">
<h3 class="card-header">Cart</h3>
<div class="card-block">
<div id="app">
<div class="row">
<div class="col-xs-3">
<label class="c-input c-checkbox">
<input type="checkbox" v-model="allSelected">Select All
<span class="c-indicator"></span>
</label>
</div>
<div class="col-xs-2">
Goods
</div>
<div class="col-xs-5">
Number
</div>
<div class="col-xs-2">
Money
</div>
</div>
<form>
<div class="row" v-for="(index, item) in items">
<div class="col-xs-3">
<label class="c-input c-checkbox">
<input type="checkbox" v-model="item.selected">
<span class="c-indicator"></span>
</label>
</div>
<div class="col-xs-2">
{{ item.message }}
</div>
<div class="col-xs-5">
<div class="dropdown">
<button class="btn btn-secondary dropdown-toggle" type="button" id="dropdownMenu1" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" v-text="item.num">
</button>
<ul class="dropdown-menu" aria-labelledby="dropdownMenu1">
<li v-for="n in nums">
<a class="dropdown-item" #click="fillIn(index, n)">{{n}}个</a>
</li>
</ul>
</div>
</div>
<div class="col-xs-2">
{{ item.price * item.num }}
</div>
</div>
<div class="row">
<div class="col-xs-3">
Sum
</div>
<div class="col-xs-2">
</div>
<div class="col-xs-5">
</div>
<div class="col-xs-2">
{{ sum }}
</div>
</div>
<button type="submit" class="btn btn-primary" :disabled="sum === 0">Submit</button>
</form>
</div>
</div>
</div>
</div>
You could keep a separate object with the id of the object as a key and a Boolean value, that represents if the item is selected or not.
The items data would only have, message, num and price
While the selected data would have itemid and selected

Angularjs Filter data with dropdown

I am kinda new in angularjs and javascript so please be kind, I have two dropdown items (Ionic Select) both of them holds data from a service. The issue is that I need to filter them in order to work together like: if I choose a company in the first dropdown list, only the reps inside of that company should display in the other dropdown list.
I tried using | filter: byID as I followed in Angularjs documentation but I do not think it is the right way of doing this don't know.
HTML:
<label class="item item-input item-select"">
<div class="input-label">
Company:
</div>
<select>
<option ng-repeat="x in company">{{x.compname}}</option>
<option selected>Select</option>
</select>
</label>
<div class="list">
<label class="item item-input item-select">
<div class="input-label">
Rep:
</div>
<select>
<option ng-repeat="x in represent">{{x.repname}}</option>
<option selected>Select</option>
</select>
</label>
Javascript:
/*=========================Get All Companies=========================*/
$http.get("http://localhost:15021/Service1.svc/GetAllComp")
.success(function(data) {
var obj = data;
var SComp = [];
angular.forEach(obj, function(index, element) {
angular.forEach(index, function(indexN, elementN) {
SComp.push({compid: indexN.CompID, compname: indexN.CompName});
$scope.company = SComp;
});
});
})
/*=========================Get All Companies=========================*/
/*=========================Get All Reps=========================*/
$http.get("http://localhost:15021/Service1.svc/GetAllReps")
.success(function(data) {
var obj = data;
var SReps = [];
angular.forEach(obj, function(index, element) {
angular.forEach(index, function(indexN, elementN) {
SReps.push({repid: indexN.RepID, repname: indexN.RepName, fkc :indexN.fk_CompID});
$scope.represent = SReps;
});
});
})
/*=========================Get All Reps=========================*/
You may solve this problem like my solution process:
my solution like your problem. at first show District list and show Thana list according to selected District. using filter expression
In HTML:
<div>
<form class="form-horizontal">
<div class="form-group">
<div class="col-md-3"><label><i class="fa fa-question-circle fa-fw"></i> District List</label></div>
<div class="col-md-4">
<select class="form-control" ng-model="selectedDist" ng-options="district.name for district in districts">
<option value="">Select</option>
</select>
</div>
</div>
<div class="form-group">
<div class="col-md-3"><label><i class="fa fa-question-circle fa-fw"></i> Thana List</label></div>
<div class="col-md-4">
<select class="form-control" ng-model="selectedThana" ng-options="thana.name for thana in thanas | filter: filterExpression">
<option value="">Select</option>
</select>
</div>
</div>
</form>
</div>
In controller:
$scope.selectedDist={};
$scope.districts = [
{id: 1, name: 'Dhaka'},
{id: 2, name: 'Goplaganj'},
{id: 3, name: 'Faridpur'}
];
$scope.thanas = [
{id: 1, name: 'Mirpur', dId: 1},
{id: 2, name: 'Uttra', dId: 1},
{id: 3, name: 'Shahabag', dId: 1},
{id: 4, name: 'Kotalipara', dId: 2},
{id: 5, name: 'Kashiani', dId: 2},
{id: 6, name: 'Moksedpur', dId: 2},
{id: 7, name: 'Vanga', dId: 3},
{id: 8, name: 'faridpur', dId: 3}
];
$scope.filterExpression = function(thana) {
return (thana.dId === $scope.selectedDist.id );
};
N.B: Here filterExpression is a custom function that return values when selected district id equal dId in thana.
var app = angular.module('myApp', []);
app.controller('customersCtrl', function($scope,$window, $http) {
$scope.myFunc = function(x) {
$scope.name = x.Name;
$scope.country = x.Country;
$scope.city = x.City;
$scope.x = x;
$("#myModal").modal();
};
$scope.saveData = function(x) {
if(x == ''){
x = angular.copy($scope.names[0]);
x.Name = $scope.name;
x.Country = $scope.country;
x.City = $scope.city;
$scope.names.push(x);
}
else{
x.Name = $scope.name;
x.Country = $scope.country;
x.City = $scope.city;
}
};
$scope.newData = function() {
$scope.name = "";
$scope.country = "";
$scope.city = "";
$scope.x = "";
$("#myModal").modal();
};
$scope.myDelete = function(x) {
if(x.Name=='' && x.Country=='' && x.City==''){
var index = $scope.names.indexOf(x);
$scope.names.splice(index, 1);
}
else{
var deletedata = $window.confirm('are you absolutely sure you want to delete?');
if (deletedata) {
alert('i am');
var index = $scope.names.indexOf(x);
$scope.names.splice(index, 1);
}
}
};
$scope.filterExpression = function(x) {
//alert($scope.country.id);
return (x.countryId === $scope.country.countryId );
};
$scope.names = [{"Name":"Alfreds Futterkiste","City":"","Country":""},{"Name":"Ana Trujillo Emparedados y helados","City":"","Country":""}]
$scope.countryList = [
{countryName : "Pakistan", countryId : 1},
{countryName : "UK", countryId : 2},
{countryName : "Sweden", countryId : 3}
];
$scope.cityList = [
{cityName : "Karachi", cityId : 1, countryId:1},
{cityName : "London", cityId : 2, countryId:11},
{cityName : "Sweden City", cityId : 3, countryId:3}
];
});
<!DOCTYPE html>
<html lang="en">
<head>
<title>Bootstrap Example</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.0/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.0/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.0/js/bootstrap.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.9/angular.min.js"></script>
</head>
<body>
<style>
.table tr {
cursor: pointer;
}
</style>
<div class="container" ng-app="myApp" ng-controller="customersCtrl">
<pre>{{names}}</pre>
<h2>Hover Rows</h2>
<p>The .table-hover class enables a hover state on table rows:</p>
<table class="table table-hover">
<thead>
<tr>
<th>Sr.No</th>
<th>Name</th>
<th>Country</th>
<th>City</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="x in names" ng-dblclick="myFunc(x)" >
<td>{{ $index + 1 }}</td>
<td>{{ x.Name }}</td>
<td>{{ x.Country.countryName }}</td>
<td>{{ x.City.cityName }}</td>
<td><span class="glyphicon glyphicon-remove" ng-click="myDelete(x)"></span></td>
</tr>
</tbody>
<tfoot>
<tr ng-click="newData()">
<td colspan="4">
</td>
<td>
<span class="glyphicon glyphicon-plus" ng-click="newData()" cursor="" ></span>
</td>
</tr>
</tfoot>
</table>
<!-- Modal -->
<div class="modal fade" id="myModal" role="dialog">
<div class="modal-dialog">
<!-- Modal content-->
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">×</button>
<h4 class="modal-title" ng-if="x!=''">Edit Record</h4>
<h4 class="modal-title" ng-if="x==''">Save Record</h4>
</div>
<div class="modal-body">
<div class="form-group">
<label for="name">Name:</label>
<input type="text" class="form-control" id="name" ng-model="name">
</div>
<div class="form-group">
<label for="country">Country:</label>
<!-- <input type="text" class="form-control" id="country" ng-model="country"> -->
<select class="form-control" ng-model="country" ng-options="x.countryName for x in countryList"></select>
</div>
<div class="form-group">
<label for="country">City:</label>
<select class="form-control" ng-model="city" ng-options="x.cityName for x in cityList | filter:filterExpression"></select>
</div>
<input type="hidden" ng-model="x" />
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal" ng-click="saveData(x)">Save</button>
<button type="button" class="btn btn-default" data-dismiss="modal" ng-click="myDelete(x)" ng-if="x!=''">Delete</button>
</div>
</div>
</div>
</div>
</div>
</body>
</html>

Load data from Database to DropDownList using Knockout

I'm creating editing a variable length list using knockout.
When I click Add Button, it will add a DropDownList and a TextBox to the screen. I've successfully load the data from database to DropDownList, but it always populating the data every time I clicked Add Button.
Code:
<div class="form-horizontal" data-bind="with: purchaseOrder">
<h4>Purchase Order</h4>
<hr />
<div class="form-group">
<label class="control-label col-md-2" for="PurchaseOrderDate">PO Date</label>
<div class="col-md-10">
<input class="form-control" data-bind="value: PurchaseOrderDate" placeholder="Purchase Order Date" />
</div>
</div>
<div class="form-group">
<label class="control-label col-md-2" for="InvoiceNo">Invoice No</label>
<div class="col-md-10">
<input class="form-control" data-bind="value: InvoiceNo" placeholder="Invoice No" />
</div>
</div>
<div class="form-group">
<label class="control-label col-md-2" for="Memo">Memo</label>
<div class="col-md-10">
<input class="form-control" data-bind="value: Memo" placeholder="Enter Memo" />
</div>
</div>
</div>
<h4>Details</h4>
<hr />
<table class="table">
<thead>
<tr>
<th>Item Name</th>
<th>Qty Order</th>
<th></th>
</tr>
</thead>
<tbody data-bind="foreach: purchaseOrderDetails">
<tr>
<td>
<select class="form-control" data-bind="options: AX_INVENTSUMs, optionsText: 'ITEMNAME', optionValue: 'ITEMID'"></select>
</td>
<td>
<input class="form-control" data-bind="value: QuantityOrder" placeholder="Enter Quantity Order">
</td>
<td>
<a class="btn btn-sm btn-danger" href='#' data-bind=' click: $parent.removeItem'>X</a>
</td>
</tr>
</tbody>
</table>
<p>
<button class="btn btn-sm btn-primary" data-bind='click: addItem'>Add Item</button>
</p>
#section Scripts {
#Scripts.Render("~/bundles/knockout")
<script>
$(function () {
var PurchaseOrder = function (purchaseOrder) {
var self = this;
self.PurchaseOrderID = ko.observable(purchaseOrder ? purchaseOrder.PurchaseOrderID : 0);
self.PurchaseOrderDate = ko.observable(purchaseOrder ? purchaseOrder.PurchaseOrderDate : '');
self.InvoiceNo = ko.observable(purchaseOrder ? purchaseOrder.InvoiceNo : '');
self.Memo = ko.observable(purchaseOrder ? purchaseOrder.Memo : '');
};
var PurchaseOrderDetail = function (purchaseOrderDetail, items) {
var self = this;
self.PurchaseOrderDetailID = ko.observable(purchaseOrderDetail ? purchaseOrderDetail.PurchaseOrderDetailID : 0);
self.PurchaseOrderID = ko.observable(purchaseOrderDetail ? purchaseOrderDetail.PurchaseOrderDetailID : 0);
self.ItemID = ko.observable(purchaseOrderDetail ? purchaseOrderDetail.ItemID : 0);
self.QuantityOrder = ko.observable(purchaseOrderDetail ? purchaseOrderDetail.QuantityOrder : 0);
self.QuantityBonus = ko.observable(purchaseOrderDetail ? purchaseOrderDetail.QuantityBonus : 0);
self.AX_INVENTSUMs = ko.observableArray(items);
};
var PurchaseOrderCollection = function () {
var self = this;
self.purchaseOrder = ko.observable(new PurchaseOrder());
self.purchaseOrderDetails = ko.observableArray([new PurchaseOrderDetail()]);
self.CashedArray = ko.observableArray([]);
$.getJSON("/AX_INVENTSUM/GetAX_INVENTSUMs", null, function (data) {
var array = [];
$.each(data, function (index, value) {
array.push(value);
});
self.CashedArray(array);
});
self.addItem = function () {
self.purchaseOrderDetails.push(new PurchaseOrderDetail(null, self.CashedArray));
};
self.removeItem = function (purchaseOrderDetail) {
self.purchaseOrderDetails.remove(purchaseOrderDetail);
};
};
ko.applyBindings(new PurchaseOrderCollection());
});
</script>
}
As you can see in the code above, how to make this occurs only once a time?
You must cache your list somewhere. I prefer to do it in something like parent view model. See bellow.
var OrderList = function(){
var self = this;
...
self.CashedArray = ko.observableArray(new Array());
$.getJSON("/AX_INVENTSUM/GetAX_INVENTSUMs", null, function (data) {
var array = [];
$.each(data, function (index, value) {
array.push(value);
});
self.CashedArray(array);
});
self.AddButtonClick = function (){
var orderDetails = new PurchaseOrderDetail(self.CashedArray());
};
};
var PurchaseOrderDetail = function (items) {
var self = this;
...
self.AX_INVENTSUMs = ko.observableArray(items);
};

Categories