I've managed to get a prototype working with the help of others to dynamically add new inputs and next to it that specific inputs settings. However I've been trying to get to grips how to remove what I've added dynamically. Any ideas?
HTML
<div class="input-row" data-bind="foreach: inputItems">
<div class="input-row-item">
<div>
<label data-bind="text: label"></label>
<input data-bind="attr:{ name: name, placeholder: placeholder, disabled: disabled() === 'true', value: value, type: type }">
</div>
<div>
<input type="text" class="nb-remove" data-bind="value: label" placeholder="input label">
<input type="text" value="text" class="nb-remove" data-bind="value: type" placeholder="input type">
<input type="text" class="nb-remove" data-bind="value: name" placeholder="input name">
<input type="text" class="nb-remove" data-bind="value: placeholder" placeholder="input placeholder">
<input type="text" class="nb-remove" data-bind="value: disabled" placeholder="input disabled">
<input type="text" class="nb-remove" data-bind="value: value" placeholder="input value">
</div>
<div>
<button data-bind="click: removeInput">Remove this</button>
</div>
</div>
</div>
THE JS
$(function(){
var InputItem = function InputItem(label, type, name, placeholder, disabled, value) {
this.label = ko.observable(label);
this.type = ko.observable(type);
this.name = ko.observable(name);
this.placeholder = ko.observable(placeholder);
this.disabled = ko.observable(disabled);
this.value = ko.observable(value);
}
var ViewModel = function ViewModel() {
var that = this;
this.inputItems = ko.observableArray([]);
this.addInput = function addInput() {
that.inputItems.push(new InputItem());
};
this.removeInput = function removeInput(){
//remove input here
}
}
ko.applyBindings(new ViewModel());
});
You should try something like this
View Model:
var ViewModel = function() {
var that = this;
that.inputItems = ko.observableArray([new InputItem()]);
that.addInput = function () {
that.inputItems.push(new InputItem());
};
that.removeInput = function (item){
that.inputItems.remove(item);
}
}
ko.applyBindings(new ViewModel());
Working fiddle here
Few Suggestions:
1) As you assigned var that=this you try to use that consistently across vm
2) You can create a function name simply like this var fun=function() else you can just do like this function fun(){//blah blah}
Related
I have the following knockout code that implements the folowing role:
field1 + field2 -field3 = field4
$(function () {
ko.applyBindings(new AppViewModel());
});
function AppViewModel() {
this.original = ko.observable(0);
this.improvements = ko.observable(0);
this.depreciation = ko.observable(0);
this.total= ko.computed(function () {
var total= 0;
total= this.original() + this.improvements() - this.depreciation();
return total;
}, this);
}
But for some reason it's not working properly, this.original is always multiplied by 10.
for example:
1 + 1 - 1 = 10
Any idea what can causes this?
This is my HTML:
<div class="calc-form">
<label>Original Purchase Price</label>
<input type="text" id="original" data-bind="value: original" />
<label>+ Improvements</label>
<input type="text" id="improvements" data-bind="value: improvements" />
<label>- Depreciation</label>
<input type="text" id="depreciation" data-bind="value: depreciation" />
<input type="button" class="calcbutton" value="Calculate" />
<input type="button" class="calcbuttonreset" value="reset" />
<p>= Total</p>
<span data-bind="text: total"></span>
</div>
Remember that the value of input elements is always a string. "1" + "1" - "1" is "11" - "1" is 10 because when either operand is a string, + is string concatenation, not addition; but - is always subtraction, so it coerces its operands to numbers.
You need to parse them, via +x or parseFloat(x) or Number(x) or (if they're meant to be whole numbers) parseInt(x, 10), etc.:
total = parseFloat(this.original())
+ parseFloat(this.improvements())
- parseFloat(this.depreciation());
Example:
$(function() {
ko.applyBindings(new AppViewModel());
});
function AppViewModel() {
this.original = ko.observable(0);
this.improvements = ko.observable(0);
this.depreciation = ko.observable(0);
this.total = ko.computed(function() {
var total = 0;
total = parseFloat(this.original())
+ parseFloat(this.improvements())
- parseFloat(this.depreciation());
return total;
}, this);
}
<div class="calc-form">
<label>Original Purchase Price</label>
<input type="text" id="original" data-bind="value: original" />
<label>+ Improvements</label>
<input type="text" id="improvements" data-bind="value: improvements" />
<label>- Depreciation</label>
<input type="text" id="depreciation" data-bind="value: depreciation" />
<input type="button" class="calcbutton" value="Calculate" />
<input type="button" class="calcbuttonreset" value="reset" />
<p>= Total</p>
<span data-bind="text: total"></span>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
If you're going to be working with numeric inputs a lot, you might want to give yourself a specific binding for them:
// "numValue" binding handler (just an example)
ko.bindingHandlers.numValue = {
init: function(element, valueAccessor) {
function numValueHandler() {
valueAccessor()(parseFloat(this.value));
}
$(element).on("input change", numValueHandler)
.val(ko.unwrap(valueAccessor()));
ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
$(element).off("input change", numValueHandler);
});
},
update: function(element, valueAccessor) {
element.value = ko.unwrap(valueAccessor());
}
};
Then:
<input type="text" id="original" data-bind="numValue: original" />
<!-- ---------------------------------------^^^^^^^^ -->
// "numValue" binding handler (just an example)
ko.bindingHandlers.numValue = {
init: function(element, valueAccessor) {
function numValueHandler() {
valueAccessor()(parseFloat(this.value));
}
$(element).on("input change", numValueHandler)
.val(ko.unwrap(valueAccessor()));
ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
$(element).off("input change", numValueHandler);
});
},
update: function(element, valueAccessor) {
element.value = ko.unwrap(valueAccessor());
}
};
$(function() {
ko.applyBindings(new AppViewModel());
});
function AppViewModel() {
this.original = ko.observable(0);
this.improvements = ko.observable(0);
this.depreciation = ko.observable(0);
this.total = ko.computed(function() {
var total = 0;
total = this.original() + this.improvements() - this.depreciation();
return total;
}, this);
}
<div class="calc-form">
<label>Original Purchase Price</label>
<input type="text" id="original" data-bind="numValue: original" />
<label>+ Improvements</label>
<input type="text" id="improvements" data-bind="numValue: improvements" />
<label>- Depreciation</label>
<input type="text" id="depreciation" data-bind="numValue: depreciation" />
<input type="button" class="calcbutton" value="Calculate" />
<input type="button" class="calcbuttonreset" value="reset" />
<p>= Total</p>
<span data-bind="text: total"></span>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
I have a form like so that collects information about a users car:
<form id="car" action="" method="">
<section class="inputContainer">
<section class="carInfo">
<input type="text" name="Make" class="make" />
<input type="text" name="Model" class="model" />
<input type="text" name="Year" class="year" />
<input type="text" name="Color" class="color" />
</section>
</section>
<input type="hidden" name="AllCarData" />
<a class="addAnotherCar" href="#">Add another car</a>
<input type="submit" value="Submit" />
</form>
When the user clicks the 'Add another car' link, my JS duplicates the 'carInfo' group of inputs and appends it to 'inputContainer'; creating a new set of form inputs like so:
<form id="car" action="" method="">
<section class="inputContainer">
<section class="carInfo">
<input type="text" name="Make" class="make" />
<input type="text" name="Model" class="model" />
<input type="text" name="Year" class="year" />
<input type="text" name="Color" class="color" />
</section>
<section class="carInfo">
<input type="text" name="Make" class="make" />
<input type="text" name="Model" class="model" />
<input type="text" name="Year" class="year" />
<input type="text" name="Color" class="color" />
</section>
</section>
<input type="hidden" name="AllCarData" />
<a class="addAnotherCar" href="#">Add another car</a>
</form>
Once the user clicks submit, I want to parse the form into a JSON object and inject it into a hidden input field. JSON for two cars should look like this:
[{ "Make" : "Mazda" , "Model": "Protege" , "Year" : "2002" , "Color" : "Red" } , { "Make" : "Toyota" , "Model": "Camery" , "Year" : "2012" , "Color" : "Blue" }]
I am currently getting the input's name to serve as the key and the entered value as the value. I have the following function built:
CreateJson: function () {
$.fn.serializeObject = function()
{
var o = {};
var a = this.serializeArray();
$.each(a, function() {
if (o[this.name] !== undefined) {
if (!o[this.name].push) {
o[this.name] = [o[this.name]];
}
o[this.name].push(this.value || '');
} else {
o[this.name] = this.value || '';
}
});
return o;
};
var carDataString = JSON.stringify($('.inputContainer input').serializeObject());
console.log(carDataString);
$("input[name='AllCarData']").val(carDataString);
}
};
********The only problem is that since the additional inputs that are duplicated when a user chooses to add another car use the same 'name', my JSON is only outputting one set of values insead of multiple (when multiple cars are added). http://jsfiddle.net/njacoy/jLopamk7/
Note: I am using the jQuery validate plugin to validate this form. It's set to look for input names.
Thanks!
Try this -
$.fn.serializeObject = function (data) {
var els = $(this).find(':input').get();
if (typeof data != 'object') {
// return all data
data = {};
$.each(els, function () {
if (this.name && !this.disabled && (this.checked || /select|textarea/i.test(this.nodeName) || /text|hidden|password/i.test(this.type))) {
data[this.name] = $(this).val();
}
});
return data;
}
};
$("#car").submit(function () {
var data = [];
$(this).find(".inputContainer section").each(function () {
data[data.length] = $(this).serializeObject();
})
var carDataString=JSON.stringify(data);
console.log(carDataString);
$("input[name='AllCarData']").val(carDataString);
return false
});
here's the working fiddle http://jsfiddle.net/vikrant47/jLopamk7/4/
You would serialise the inputs in each section separately, then get them as an array and use stringify on that:
var carDataString = JSON.stringify(
$('.inputContainer section').map(function(i, o){
return o.find('input').serializeObject();
}).get()
);
The following is my single page app using Knockout.js and MVC WebApi. I have no problem getting the data from WebApi service. However, I can't neither do a PUT nor a POST to the server. When I click the Update button which is bound to self.update(), it always says "baseApiUri/api/user/undefined". I thought the self.user(new userViewModel(user)) line would set the scope of the current user record, but it doesn't seem to be the case. I'd like to use the userViewModel instead of hard code the getting and setting of each of the properties in the user observable object. Thank you for your help.
#model App4MVC4.Controllers.UserViewModel
#section scripts{
<script type="text/javascript">
function viewModel() {
var baseApiUri = "#Model.apiBaseUrl";
var self = this;
/********** nested view model **********/
function userViewModel(user) {
var self = this;
self.user_ID = user.user_ID;
self.firstName = user.firstName;
self.lastName = user.lastName;
self.long_name = ko.computed(function(){return self.firstName + " " + self.lastName});
self.Login = user.Login;
self.dateAdded = user.dateAdded;
self.email = user.email;
self.alertOptIn = user.alertOptIn ? "Yes" : "No";
self.active = user.active ? "Yes" : "No";
}
/********** properties **********/
self.newUser = ko.observable();
self.userBeforeEdit = ko.observable();
self.users = ko.observableArray();
self.user = ko.observable();
self.operationStatus = ko.observable();
/********** methods **********/
// create new user (HttpPost)
self.create = function (formElement) {
self.operationStatus("Creating ...");
$(formElement).validate();
if ($(formElement).valid()) {
$.post(baseApiUri + "/api/user", $(formElement).serialize(), null, "json")
.done(function (o) {
self.users.push(o);
})
.fail(function (xhr, textStatus, err) {
self.operationStatus(err);
});
}
}
self.cancelAdd = function () {
self.newUser(new userViewModel());
}
}
// instantiate the above ViewModel and apply Knockout binding
ko.applyBindings(new viewModel());
// make jQuery tabs
$("#tabs").tabs();
</script>
}
<div id="tabs">
<ul>
<li>User Detail</li>
<li>New User</li>
</ul>
<div id="addNewTab">
<form>
<div>
<label for="firstNameNew">First Name</label>
<input type="text" title="First Name" data-bind="value:newUser.firstName"/>
</div>
<div>
<label for="lastNameNew">Last Name</label>
<input type="text" title="Last Name" data-bind="value: newUser.lastName"/>
</div>
<div>
<label for="fullNameNew">Full Name</label>
<input type="text" title="Full Name" data-bind="value: newUser.long_Name"/>
</div>
<div>
<label for="loginNew">Login</label>
<input type="text" title="Login" data-bind="value: newUser.Login"/>
</div>
<div>
<label for="emailNew">Email</label>
<input type="text" title="Email" data-bind="value: newUser.email"/>
</div>
<div>
<label for="emailAlertNew">Email Alert</label>
<span><input data-bind="checked: newUser.alertOptIn" type="radio" title="Send Email Alert" value="Yes" name="alertOptIn"/>Yes</span>
<span><input data-bind="checked: newUser.alertOptIn" type="radio" title="No Email Alert" value="No" name="alertOptIn"/>No</span>
</div>
<div>
<input type="button" value="Save" data-bind="click: create" />
<input type="button" value="Cancel" data-bind="click: cancelAdd" />
<p data-bind="text: operationStatus" class="status"></p>
</div>
</form>
</div>
</div>
you missed parentheses when retrieving a value for user here:
url: baseApiUri + "/api/user/" + self.user().user_ID
I am new in knockout js. I am trying to learn it. As my learning process i made a sample program. But i face a problem while i add a new item in observableArray. i can successfully add an item in observableArray but after add it does not show any text in my select. But an item is added. When I click that item all information is show below.
my HTML :
<div id="contener">
<div id="productListView">
<select multiple="multiple" id="MyproductListView" size="10" style="min-width: 120px;" data-bind="options: productCollection, value: listViewSelectedItem, optionsText: 'description'"></select>
</div>
<div id="productView" data-bind="with: selectedProduct">
<p>
SKU: <span data-bind="text: sku"></span>
</p>
<p>
Description: <span data-bind="text: description"></span>
</p>
<p>
SKU: <span data-bind="text: price"></span>
</p>
<p>
Description: <span data-bind="text: cost"></span>
</p>
<p>
Description: <span data-bind="text: quantity"></span>
</p>
</div>
<div id="NewProduct" data-bind="with: selectedProduct">
<form>
<fieldset>
<legend>Product Details</legend>
<label>SKU :
<input type="text" data-bind="value:sku" /></label>
<br />
<label>Description :
<input type="text" data-bind="value:description" /></label>
<br />
<label>Price :
<input type="text" data-bind="value:price" /></label>
<br />
<label>Cost :
<input type="text" data-bind="value:cost" /></label>
<br />
<label>Quantity :
<input type="text" data-bind="value:quantity" /></label>
</fieldset>
</form>
</div>
<div id="buttonContainer">
<button type="button" data-bind="click:addNewProduct">Add</button>
<button type="button" data-bind="click:RemoveProduct">Remove</button>
<button type="button" data-bind="click:DoneEditingProduct">Done</button>
</div>
</div>
My Knockout is :
window.myapp = {};
(function (myapp) {
var self = this;
function product() {
self.sku = ko.observable("");
self.description = ko.observable("");
self.price = ko.observable(0.00);
self.cost = ko.observable(0.00);
self.quantity = ko.observable(0);
}
myapp.Product = product;
}(window.myapp));
(function (myApp) {
function productsViewModel() {
var self = this;
self.selectedProduct = ko.observable();
self.productCollection = ko.observableArray([{ sku: 'sku', description: 'des', price: '5.0', cost: '8.0', quantity: '1' }]);
self.listViewSelectedItem = ko.observable(null);
self.addNewProduct = function () {
var p = new myApp.Product();
self.selectedProduct(p);
}.bind(self);
self.DoneEditingProduct = function () {
var p = self.selectedProduct();
if (!p)
return;
if (self.productCollection.indexOf(p) > -1)
return;
self.productCollection.push(p);
self.selectedProduct(null);
self.productCollection.valueHasMutated();
}.bind(self);
self.RemoveProduct = function () {
var p = self.selectedProduct();
if (!p)
return;
return self.productCollection.remove(p);
};
self.listViewSelectedItem.subscribe(function (product) {
if (product) {
self.selectedProduct(product);
}
});
}
myApp.ProductsViewModel = productsViewModel;
}(window.myapp));
(function (myApp) {
function app() {
this.run = function () {
var vm = new myApp.ProductsViewModel();
ko.applyBindings(vm);
};
}
myApp.App = app;
}(window.myapp));
var app = new myapp.App();
app.run();
I already 3 days for it and search a lot but i can`t explore why it is not update.
Must i do something wrong.
Update :
My Code in Fiddle:
http://jsfiddle.net/shuvo009/ReSUL/1/
http://jsfiddle.net/sujesharukil/ReSUL/3/
you are referencing the parent object
function product() {
self.sku = ko.observable("");
self.description = ko.observable("");
self.price = ko.observable(0.00);
self.cost = ko.observable(0.00);
self.quantity = ko.observable(0);
}
change that to
function product() {
this.sku = ko.observable("");
this.description = ko.observable("");
this.price = ko.observable(0.00);
this.cost = ko.observable(0.00);
this.quantity = ko.observable(0);
}
updated fiddle above.
i have a form and would like to give users the ability to duplicate a group of fields as many times as necessary. With one group it iterates correctly but when I add a second group the "current" variable iterates collectively instead of being unique to each group... i tried changing all of the "current" to "newFieldset.current" but that returns NAN... any ideas?
<script type="text/javascript">
$(document).ready(function() {
var current = 0;
//Add New Fieldset with Button
var newFieldset = {
init: function(groupIndex) {
current++;
$newPerson= $("#Template"+groupIndex).clone(true);
$newPerson.children("p").children("label").each(function(i) {
var $currentElem= $(this);
$currentElem.attr("for",$currentElem.attr("for")+current);
});
$newPerson.children("p").children("input").each(function(i) {
var $currentElem= $(this);
$currentElem.attr("name",$currentElem.attr("name")+current);
$currentElem.attr("value",$currentElem.attr("value")+groupIndex+current);
$currentElem.attr("id",$currentElem.attr("id")+current);
});
$newPerson.appendTo("#mainField"+groupIndex);
$newPerson.removeClass("hideElement");
},
currentID: null,
obj: null
};
$(".addButton").each(function() {
$(this).click(function() {
var groupIndex = $(this).attr("title");
//newFieldset.obj = this;
//var fieldIndex = $(this).attr("class");
newFieldset.init(groupIndex);
});
});
console.log('r');
});
</script>
<style>
.hideElement {display:none;}
</style>
<form name="demoForm" id="demoForm" method="post" action="#">
<div id="groupCtr1">
<fieldset id="mainField1">
<div id="Template1" class="hideElement">
<p>
<label for="firstname">Name</label> <em>*</em>
<input id="firstname" name="firstname" size="25" /> <input id="lastname" name="lastname" size="25" />
</p>
<p>
<label for="email">Email</label> <em>*</em><input id="email" name="email" size="25" />
</p>
</div>
<div>
<p>
<label for="firstname1">Name</label>
<em>*</em> <input id="firstname1" name="firstname1" size="25" /> <input id="lastname1" name="lastname1" size="25" />
</p>
<p>
<label for="email1">Email</label>
<em>*</em><input id="email1" name="email1" size="25" />
</p>
</div>
</fieldset>
<p>
<input type="button" class="addButton" title="1" value="Add Another Person">
</p>
</div>
<div id="groupCtr2">
<fieldset id="mainField2">
<div id="Template2" class="hideElement">
<p>
<label for="coname">Company Name</label> <em>*</em>
<input id="coname" name="coname" size="25" />
</p>
<p>
<label for="codesc">Description</label> <em>*</em><input id="codesc" name="codesc" size="25" />
</p>
</div>
<div>
<p>
<label for="coname1">Company Name</label>
<em>*</em> <input id="coname1" name="coname1" size="25" />
</p>
<p>
<label for="codesc1">Description</label>
<em>*</em><input id="codesc1" name="codesc1" size="25" />
</p>
</div>
</fieldset>
<p>
<input type="button" class="addButton" title="2" value="Add Another Company">
</p>
</div>
<input type="submit" value="Save">
</form>
Attach the value to the element with the jQuery data method. Increment it on click, and then pass it to the newFieldset.init method as the second param. Voila!
$(document).ready(function() {
//Add New Fieldset with Button
var newFieldset = {
init: function(groupIndex,current) {
$newPerson= $("#Template"+groupIndex).clone(true);
$newPerson.children("p").children("label").each(function(i) {
var $currentElem= $(this);
$currentElem.attr("for",$currentElem.attr("for")+current);
});
$newPerson.children("p").children("input").each(function(i) {
var $currentElem= $(this);
$currentElem.attr("name",$currentElem.attr("name")+current);
$currentElem.attr("value",$currentElem.attr("value")+groupIndex+current);
$currentElem.attr("id",$currentElem.attr("id")+current);
});
$newPerson.appendTo("#mainField"+groupIndex);
$newPerson.removeClass("hideElement");
},
currentID: null,
obj: null
};
$(".addButton").click(function() {
var groupIndex = $(this).attr("title");
var current = $(this).data('current');
$(this).data('current',++current);
//newFieldset.obj = this;
//var fieldIndex = $(this).attr("class");
newFieldset.init(groupIndex,current);
}).data('current',0);
console.log('r');
});
Happy jquery-ing to you sir.
if you dont make the current variable global it will should work. try this:
var newFieldset = {
current: 0,
init: function() {
this.current++;
//rest of init code
},
//all your other fieldset code here
};
/* all other code */
EDIT: After re-reading the question, I would take a completely different approach to what you're trying to achieve. The above code will still exhibit the same behavior for you. If the question hasn't been successfully answered I'll do a bigger writeup when i get home.
I would do something like that:
...
var currents = {};
var newFieldset = {
init: function(groupIndex) {
var current = 0;
if (currents[groupIndex]) {
current = currents[groupIndex];
}
++current;
currents[groupIndex] = current;
...