I want to show different json after click into any radio button. 'Option 1' will get option1.json etc.
Is is possible to do it without page refresh?
$('#my_radio_box').change(function(){
selected_value = $("input[name='my_options']:checked").val();
if (selected_value == 'option 2'){
const products =
[
{id: 11,title: '2Macbook Pro', price: 2500.00, qty: 1, image: 'http://lorempixel.com/150/150/'},
{id: 12,title: '2Asus ROG Gaming',price: 1000.00, qty: 1,image: 'http://lorempixel.com/150/150/'},
{id: 13,title: '2Amazon Kindle',price: 150.00,qty: 1,image: 'http://lorempixel.com/150/150/'},
{id: 14,title: '2Another Product',price: 10, qty: 1, image: 'http://lorempixel.com/150/150/'},
];
}
});
$(document).ready(function(){
$('#my_radio_box').change(function(){
selected_value = $("input[name='my_options']:checked").val();
if (selected_value == 'option 2'){
// const products = ...
}
});
});
const products =
[
{id: 1,title: 'Macbook Pro', price: 2500.00, qty: 1, image: 'http://lorempixel.com/150/150/'},
{id: 2,title: 'Asus ROG Gaming',price: 1000.00, qty: 1,image: 'http://lorempixel.com/150/150/'},
{id: 3,title: 'Amazon Kindle',price: 150.00,qty: 1,image: 'http://lorempixel.com/150/150/'},
{id: 4,title: 'Another Product',price: 10, qty: 1, image: 'http://lorempixel.com/150/150/'},
];
function formatNumber(n, c, d, t){
var c = isNaN(c = Math.abs(c)) ? 2 : c,
d = d === undefined ? '.' : d,
t = t === undefined ? ',' : t,
s = n < 0 ? '-' : '',
i = String(parseInt(n = Math.abs(Number(n) || 0).toFixed(c))),
j = (j = i.length) > 3 ? j % 3 : 0;
return s + (j ? i.substr(0, j) + t : '') + i.substr(j).replace(/(\d{3})(?=\d)/g, '$1' + t) + (c ? d + Math.abs(n - i).toFixed(c).slice(2) : '');
};
// Allow the formatNumber function to be used as a filter
Vue.filter('formatCurrency', function (value) {
return formatNumber(value, 2, '.', ',');
});
// The shopping cart component
Vue.component('shopping-cart', {
props: ['items'],
computed: {
Total() {
let total = 0;
this.items.forEach(item => {
total += (item.price * item.qty);
});
return total;
}
},
methods: {
// Remove item by its index
removeItem(index) {
this.items.splice(index, 1)
}
}
})
const vm = new Vue({
el: '#app',
data: {
cartItems: [],
items : products
},
methods: {
// Add Items to cart
addToCart(itemToAdd) {
let found = false;
// Add the item or increase qty
let itemInCart = this.cartItems.filter(item => item.id===itemToAdd.id);
let isItemInCart = itemInCart.length > 0;
if (isItemInCart === false) {
this.cartItems.push(Vue.util.extend({}, itemToAdd));
} else {
itemInCart[0].qty += itemToAdd.qty;
}
itemToAdd.qty = 1;
}
}
})
.container{
padding:20px;
max-width:600px;
}
.input-qty {
width: 60px;
float: right
}
.table-cart > tr > td {
vertical-align: middle !important;
}
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css">
<div id="app" class="container">
<div class="text-right"><button class="btn btn-primary" data-toggle="modal" data-target="#cartModal">Cart ({{cartItems.length}})</button></div>
<form id="my_radio_box">
<input type="radio" name="my_options" value="option 1" checked="checked" /> Option 1
<input type="radio" name="my_options" value="option 2" /> Option 2
<input type="radio" name="my_options" value="option 3" /> Option 3
</form>
<!-- Modal -->
<div class="modal fade" id="cartModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"> <span aria-hidden="true">×</span></button>
<h4 class="modal-title" id="myModalLabel">Cart</h4>
</div>
<div class="modal-body">
<shopping-cart inline-template :items="cartItems">
<div>
<table class="table table-cart">
<tr v-for="(item, index) in items">
<td>{{item.title}}</td>
<td style="width:120px">QTY:
<input v-model="item.qty" class="form-control input-qty" type="number">
</td>
<td class="text-right">${{item.price | formatCurrency}}</td>
<td>
<button #click="removeItem(index)"><span class="glyphicon glyphicon-trash"></span></button>
</td>
</tr>
<tr v-show="items.length === 0">
<td colspan="4" class="text-center">Cart is empty</td>
</tr>
<tr v-show="items.length > 0">
<td></td>
<td class="text-right">Cart Total</td>
<td class="text-right">${{Total | formatCurrency}}</td>
<td></td>
</tr>
</table>
</div>
<!-- /.container -->
</shopping-cart>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<div class="container">
<div class="row">
<div class="col-xs-3 text-center" v-for="item in items">
<img class="img-responsive" :src="item.image" alt="">
<h5>{{ item.title }}</h5>
<h6>${{ item.price | formatCurrency }}</h6>
<p class="text-center"><input v-model="item.qty" type="number" class="form-control" placeholder="Qty" min="1"/></p>
<button #click="addToCart(item)" class="btn btn-sm btn-primary">Add to Cart</button>
</p>
</div>
</div>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.10/vue.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script>
<script src="app.js"></script>
I have created a dynamic table that allows the user to add rows to the table. Now I want to create multiple tables that allow the user to add multiple rows.
The problem with my code is that the 'variable' choice is shared and making any changes to any of the fields in any of the tables causes all the table fields to replicate the behaviour. Can someone tell me how to create tables that are capable of holding different values?
Angular.html
<html>
<head>
<link rel="stylesheet" type="text/css" href="createQuiz.css" />
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.7.8/angular.min.js">
</script>
<script src="quizMgmt.js"></script>
</head>
<body>
<div ng-app="angularjs-starter" ng-controller="MainCtrl">
<form action="/createQuiz">
<ul class="list-group" data-ng-repeat="path in path">
<span>
<h1 ng-model="colNum" >Path {{colNum}}</h1>
<button class="btn btn-primary" ng-click="addNewPath()">Add New Path</button>
</span>
<li class="list-group-item" data-ng-repeat="choice in choices">
<span>
<span class="col-sm-2">
<select class="form-control" ng-model="choice.type" name="item" ng-options="item for item in selectOptions">
</select>
</span><br>
<span class="col-sm-9">
<input class="form-control" type="text" ng-model="choice.name" name="value" placeholder="Enter mobile number">
</span>
<button class="btn btn-danger" ng-click="removeChoice(choice.id)" ng-if="choice.id!=index">-</button>
<button class="btn btn-danger" ng-click="addNewChoice()" ng-if="choice.id===index">+</button>
</span>
</li>
</ul>
<br>
<button class="btn btn-primary" ng-click="addNewChoice()">Add fields</button>
<input type="submit">
</form>
<br>
<br>
<div id="choicesDisplay">
<!-- {{ choices }} -->
{{path}}
</div>
</div>
</body>
quizMgmt.js
var app = angular.module('angularjs-starter', []);
app.controller('MainCtrl', function($scope) {
$scope.selectOptions = ["Mobile",
"Office",
"Home"
];
$scope.choices = [{"id": 1,"type":"Mobile","name":""},
{"id": 2,"type":"Mobile","name":""}];
$scope.path =[{"NumPath":1, 'path':$scope.choices}];
$scope.colNum=1;
$scope.index = $scope.choices.length;
$scope.addNewChoice = function() {
var newItemNo = ++$scope.index;
$scope.choices.push({'id':newItemNo, "type":"Mobile","name":""});
};
$scope.addNewPath= function() {
var NumPath=$scope.path.length;
console.log($scope.path.length)
$scope.colNum=NumPath;
$scope.path.push({"NumPath": NumPath,'path':$scope.choices});
};
$scope.removeChoice = function(id) {
if($scope.choices.length<=1){
alert("input cannot be less than 1");
return;
}
var index = -1;
var comArr = eval( $scope.choices );
for( var i = 0; i < comArr.length; i++ ) {
if( comArr[i].id === id) {
index = i;
break;
}
}
if( index === -1 ) {
alert( "Something gone wrong" );
}
$scope.choices.splice( index, 1 );
};
});
Issue is with the $scope.choices which is common object , So it is getting replicated.
$scope.choices must be isolated and restrict it to that respective path like below.
I believe by default you need to two choices while adding new path. Hope this helps.
var app = angular.module('angularjs-starter', []);
app.controller('MainCtrl', function($scope) {
$scope.selectOptions = ["Mobile",
"Office",
"Home"
];
$scope.getDefaultChoices = function() {
return [{"id": 1,"type":"Mobile","name":""},
{"id": 2,"type":"Mobile","name":""}];
};
$scope.choices = $scope.getDefaultChoices();
$scope.paths = [{"NumPath":1, 'choices':$scope.choices}];
$scope.colNum=1;
$scope.index = $scope.choices.length;
$scope.addNewChoice = function(path) {
var newItemNo = ++$scope.index;
path.choices.push({'id':newItemNo, "type":"Mobile","name":""});
};
$scope.addNewPath= function() {
var NumPath=$scope.paths.length;
console.log($scope.paths.length)
$scope.colNum=NumPath;
$scope.paths.push({"NumPath": NumPath,'choices': $scope.getDefaultChoices() });
};
$scope.removeChoice = function(path, id) {
if(path.choices.length<=1){
alert("input cannot be less than 1");
return;
}
var index = -1;
var comArr = eval( path.choices );
for( var i = 0; i < comArr.length; i++ ) {
if( comArr[i].id === id) {
index = i;
break;
}
}
if( index === -1 ) {
alert( "Something gone wrong" );
}
path.choices.splice( index, 1 );
};
});
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.7.8/angular.min.js">
</script>
</head>
<body>
<div ng-app="angularjs-starter" ng-controller="MainCtrl">
<form>
<ul class="list-group" data-ng-repeat="path in paths">
<span>
<h1 ng-model="colNum" >Path {{colNum}}</h1>
<button class="btn btn-primary" ng-click="addNewPath()">Add New Path</button>
</span>
<li class="list-group-item" data-ng-repeat="choice in path.choices">
<span>
<span class="col-sm-2">
<select class="form-control" ng-model="choice.type" name="item" ng-options="item for item in selectOptions">
</select>
</span><br>
<span class="col-sm-9">
<input class="form-control" type="text" ng-model="choice.name" name="value" placeholder="Enter mobile number">
</span>
<button class="btn btn-danger" ng-click="removeChoice(path, choice.id)" ng-if="choice.id!=index">-</button>
<button class="btn btn-danger" ng-click="addNewChoice(path)" ng-if="choice.id===index">+</button>
</span>
</li>
</ul>
<br>
<button class="btn btn-primary" ng-click="addNewChoice(path)">Add fields</button>
</form>
<br>
<br>
<div id="choicesDisplay">
<!-- {{ choices }} -->
{{path}}
</div>
</div>
</body>
The issue is with choices object. As you are using same instance in multiple objects so as new table created data get copied.
The solution of this is to create new instance of choices when new table gets created using angular.copy
NOTE: I didn't understand why you added "Add Fields" outside the list as it doesn't make any sense when you create new tables dynamically.
I have made few changes in the code kindly refer below.
var app = angular.module('angularjs-starter', []);
app.controller('MainCtrl', function($scope) {
$scope.selectOptions = ["Mobile",
"Office",
"Home"
];
$scope.choices = [{"id": 1,"type":"Mobile","name":""},
{"id": 2,"type":"Mobile","name":""}];
$scope.path = [{"NumPath":1, 'path':angular.copy($scope.choices)}];
$scope.colNum=1;
$scope.addNewChoice = function(path) {
var newItemNo = path.path.length;
path.path.push({'id':++newItemNo, "type":"Mobile","name":""});
};
$scope.addNewPath= function() {
var NumPath=$scope.path.length;
console.log($scope.path.length)
$scope.colNum=NumPath+1;
$scope.path.push({"NumPath": NumPath+1,'path':angular.copy($scope.choices)});
};
$scope.removeChoice = function(id, path) {
path.splice(id-1, 1);
// re-initialize choice id
angular.forEach(path, function(o, index){
o.id = index+1;
})
};
});
<html>
<head>
<link rel="stylesheet" type="text/css" href="createQuiz.css" />
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.7.8/angular.min.js">
</script>
<script src="quizMgmt.js"></script>
</head>
<body>
<div ng-app="angularjs-starter" ng-controller="MainCtrl">
<ul class="list-group" data-ng-repeat="path in path">
<span>
<h1 ng-model="colNum" >Path {{colNum}}</h1>
<button class="btn btn-primary" ng-click="addNewPath()">Add New Path</button>
</span>
<li class="list-group-item" data-ng-repeat="choice in path.path">
<span>
<span class="col-sm-2">
<select class="form-control" ng-model="choice.type" name="item" ng-options="item for item in selectOptions">
</select>
</span><br>
<span class="col-sm-9">
<input class="form-control" type="text" ng-model="choice.name" name="value" placeholder="Enter mobile number">
</span>
<button class="btn btn-danger" ng-click="removeChoice(choice.id, path.path)" ng-if="choice.id!=path.path.length">-</button>
<button class="btn btn-danger" ng-click="addNewChoice(path)" ng-if="choice.id===path.path.length">+</button>
</span>
</li>
<br/>
<br/>
<button class="btn btn-primary" ng-click="addNewChoice(path)">Add fields</button>
</ul>
<br>
<br>
<br>
<div id="choicesDisplay">
<!-- {{ choices }} -->
{{path}}
</div>
</div>
</body>
Good day :)
I am using below code.
HTML code
<form name="profInfoForm" ng-submit="saveInfo(profInfoForm)" novalidate>
<div class="wrapper">
<div class="container">
<section class="main-ctr">
<div class="error-msg" ng-show="showErrorMsg">
<div class="text">Please fix the validation error(s) below and try again.<br />{{serviceErrorMsg}}</div>
</div>
<div ng-include="'views/header.html'"></div>
<main class="sm-main">
<section class="fix-section">
<h1>Professional information</h1>
<div class="lg-form">
<div class="form-group">
<label class="text-label lg-label">Salutation</label>
<div class="select-wrap lg-select">
<select class="form-input select" name="salutation" required ng-init="profInfo.salutation = item[0]"
ng-options="item.name as item.name for item in salutations"
ng-model="profInfo.salutation">
</select>
<div class="error" ng-show="profInfoForm.$submitted || profInfoForm.salutation.$touched">
<span ng-show="profInfoForm.salutation.$error.required">Required Field</span>
</div>
</div>
</div>
</div>
</section>
<div class="clear"></div>
<div class="button-ctr">
<button class="button" ng-class="profInfoForm.$valid ? 'active' : 'disable'">Next</button>
</div>
</main>
</section>
</div>
</div>
<div id="loading" ng-if="showLoader">
<img src="images/loader.gif" id="loading-image">
</div>
</form>
My constant.js
.constant('APP_CONSTANTS', {
SALUTATIONS: [{ id: 0, name: 'Select' }, { id: 1, name: 'Mr.' }, { id: 2, name: 'Mrs.' }, { id: 3, name: 'Miss' }, { id: 4, name: 'Dr.' }, { id: 5, name: 'Ms'}]
})
Controller code is given below
.controller('professionalInfoCtrl', ['$rootScope', '$scope', '$state', 'globalService', 'APP_CONSTANTS', 'dataServices', function ($rootScope, $scope, $state, globalService, APP_CONSTANTS, dataServices) {
$scope.showLoader = false;
$scope.profInfo = userData;
$scope.salutations = APP_CONSTANTS.SALUTATIONS;
}])
I want to set default value of Salutation drop down list.
For this I am using ng-init but this is not working. I did not to find out the problem.
Please consider using ui-select of AngularUI:
<ui-select ng-model="$parent.company">
<!-- using $parent - https://github.com/angular-ui/ui-select/issues/18 -->
<ui-select-match>{{$select.selected.name}}</ui-select-match>
<ui-select-choices repeat="company in companies>{{company.name}}</ui-select-choices>
</ui-select>
I resolved my issue by using below code
<select class="form-input select" ng-model="profInfo.salutation" required
ng-options="item.name as item.name for item in salutations">
<option value="">Select salutation...</option>
</select>
<div class="error" ng-show="profInfoForm.$submitted || profInfoForm.salutation.$touched">
<span ng-show="profInfoForm.salutation.$error.required">Required Field</span>
</div>
As simple I am using
<option value="">Select salutation...</option>
Thanks everyone.
In the dynamic input field is there any option to implement plusSign = true only for the last item ?
(function() {
var app = angular.module('managementApp', []);
// var app = angular.module('managementApp', ['ngRoute']);
app.controller('phonebookController', function($scope, $http) {
$scope.dynamicField = function(buttonStatus, inputIndex) {
if (!buttonStatus) {
$scope.currentContact.contacts.push({
"phone": ""
});
} else {
$scope.currentContact.contacts.splice(inputIndex, 1);
}
};
$scope.currentContact = [{
"phone": "07875 506 426"
}, {
"phone": "+91 9895 319991"
}, {
"phone": "+44 7875 506 426"
}];
$scope.dynamicField = function(buttonStatus, inputIndex) {
if (!buttonStatus) {
$scope.currentContact.push({
"phone": ""
});
} else {
$scope.currentContact.splice(inputIndex, 1);
}
};
$scope.checkIndex = function(totalCount, indexCount) {
indexCount++;
// alert(indexCount);
/*if (totalCount === indexCount) {
//alert("last one");
$scope.plusSign = true;
}else{
$scope.plusSign = false;
}*/
};
});
})();
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css" rel="stylesheet" />
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div class="container" ng-app="managementApp">
<div class="row" ng-controller="phonebookController">
<div class="form-group" ng-repeat="contact in currentContact" ng-init="checkIndex(currentContact.length, $index);">
<label for="contact-number3" class="col-sm-3 control-label">Contact number {{$index + 1 }}</label>
<div class="col-sm-9">
<div class="input-group">
<input type="tel" class="form-control" placeholder="Contact number {{$index + 1 }}" ng-model="contact.phone">
<span class="input-group-btn">
<button class="btn btn-default" type="button" ng-init="plusSign = true"
ng-click="plusSign = !plusSign; dynamicField(plusSign, $index);">
<i class="glyphicon " ng-class="plusSign ? 'glyphicon-plus' : 'glyphicon-minus'"></i>
</button>
</span>
</div>
</div>
</div>
</div>
</div>
You can use $last inside ng-repeat which is true if the repeated element is last in the iterator. Or you can do it with css only with .row:last-of-type {/**/}.
check $last in your function for example:-
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css" rel="stylesheet" />
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div class="container" ng-app="managementApp">
<div class="row" ng-controller="phonebookController">
<div class="form-group" ng-repeat="contact in currentContact" ng-init="checkIndex($last);">
<label for="contact-number3" class="col-sm-3 control-label">Contact number {{$index + 1 }}</label>
<div class="col-sm-9">
<div class="input-group">
<input type="tel" class="form-control" placeholder="Contact number {{$index + 1 }}" ng-model="contact.phone">
<span class="input-group-btn">
<button class="btn btn-default" type="button" ng-init="plusSign = true"
ng-click="plusSign = !plusSign; dynamicField(plusSign, $index);">
<i class="glyphicon " ng-class="plusSign ? 'glyphicon-plus' : 'glyphicon-minus'"></i>
</button>
</span>
</div>
</div>
</div>
</div>
</div>
controller method
$scope.checkIndex = function(last) {
(last === true)
$scope.plusSign = true;
else
$scope.plusSign = false;
};
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();
});