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
Related
How to manipulate the DOM to utilize the MVVM pattern, I want to implement this snippet using Knockout's click binding. I don't want to use jQuery nor JavaScript DOM methods. I am taking help from http://knockoutjs.com/documentation/click-binding.html
var viewModel = function() {
var self = this;
var geocoder = new google.maps.Geocoder();
document.getElementById('search-me').addEventListener('click', function() {
self.geoAddr(geocoder, map);
});
}
<input class="text-box" id="location" type="text" value="Delhi, India">
<button class="my-button" id="search-me" type="submit">
<img class="search-img" src="image/mybutton.png" alt="search" />
</button>
not sure exactly what you are after but something like
function model() {
var self = this;
this.location = ko.observable('Delhi, India');
this.geoAddr = function() {
var location = self.location();
alert('my location is ' + location);
}
}
var mymodel = new model();
$(document).ready(function() {
ko.applyBindings(mymodel);
});
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="form-group">
<label class="control-label col-sm-2" for="location">Location:</label>
<div class="col-sm-9">
<input type="text" class="form-control" id="location" data-bind="textInput: location" />
</div>
</div>
<input type="button" class="btn btn-info" value="Search" data-bind="click: geoAddr">
I've tried many different methods, and even tried searching on SO. No answer was what I was looking for.
What I want is to have two input buttons that do some things in pure javascript.
Button one: Have it say "Add" when the page loads. When clicked, the value changes to "Cancel." Also, when it's clicked, have it display a form with three fields. When it's clicked again, have the form disappear. One named 'name', the second named 'location', the third named 'type'. I want the user to be able to submit these three things and have them be stored in the code.
Button two: Take the user input from the form and each time the user clicks, it displays all three information values, but have the button act as random generator. Let's say the code has 5 separate entries, I want them to be randomly selected and displayed when the button is clicked.
Like I said, I tried to make this work, but couldn't quite get over the top of where I wanted to go with it. If you want to see my original code, just ask, but I doubt it will be of any assistance.
Thanks in advance.
EDIT: Added the code.
function GetValue() {
var myarray = [];
var random = myarray[Math.floor(Math.random() * myarray.length)];
document.getElementById("message").innerHTML = random;
}
var testObject = {
'name': BWW,
'location': "Sesame Street",
'type': Bar
};
localStorage.setItem('testObject', JSON.stringify(testObject));
var retrievedObject = localStorage.getItem('testObject');
function change() {
var elem = document.getElementById("btnAdd1");
if (elem.value == "Add Spot") {
elem.value = "Cancel";
} else elem.value = "Add Spot";
}
window.onload = function() {
var button = document.getElementById('btnAdd1');
button.onclick = function show() {
var div = document.getElementById('order');
if (div.style.display !== 'none') {
div.style.display = 'none';
} else {
div.style.display = 'block';
}
};
};
<section>
<input type="button" id="btnChoose" value="Random Spot" onclick="GetValue();" />
<p id="message"></p>
<input type="button" id="btnAdd1" value="Add Spot" onclick="change();" />
<div class="form"></div>
<form id="order" style="display:none;">
<input type="text" name="name" placeholder="Name of Resturant" required="required" autocomplete="on" />
<input type="text" name="type" placeholder="Type of Food" required="required" autocomplete="off" />
<input type="text" name="location" placeholder="Location" required="required" autocomplete="off" />
<input type="submit" value="Add Spot" />
</form>
</div>
</section>
The randomizer works, and so does the appear/hide form. Only thing is storing the input and switching the input value.
Here's one way to do this. Each form submission is stored as an object in an array. The random button randomly selects an item from the array and displays it below.
HTML:
<section>
<input type="button" id="btnChoose" value="Random Spot" />
<p id="message"></p>
<input type="button" id="btnAdd1" value="Add Spot" />
<div class="form">
<form id="order" style="display:none;">
<input id="orderName" type="text" name="name" placeholder="Name of Resturant" required="required" autocomplete="on" />
<input id="orderType" type="text" name="type" placeholder="Type of Food" required="required" autocomplete="off" />
<input id="orderLocation" type="text" name="location" placeholder="Location" required="required" autocomplete="off" />
<input type="submit" value="Add Spot" />
</form>
</div>
</section>
<div id="randomName"></div>
<div id="randomLocation"></div>
<div id="randomType"></div>
JS:
var formData = [];
var formSubmission = function(name, location, type) {
this.name = name;
this.location = location;
this.type = type;
}
var spotName = document.getElementById("orderName"),
spotLocation = document.getElementById("orderLocation"),
spotType = document.getElementById("orderType");
var addClick = function() {
if (this.value === 'Add Spot') {
this.value = "Cancel";
document.getElementById('order').style.display = 'block';
}
else {
this.value = 'Add Spot';
document.getElementById('order').style.display = 'none';
}
}
document.getElementById("btnAdd1").onclick = addClick;
document.getElementById('order').onsubmit = function(e) {
e.preventDefault();
var submission = new formSubmission(spotName.value, spotLocation.value, spotType.value);
formData.push(submission);
submission = '';
document.getElementById('btnAdd1').value = 'Add Spot';
document.getElementById('order').style.display = 'none';
this.reset();
}
var randomValue;
document.getElementById('btnChoose').onclick = function() {
randomValue = formData[Math.floor(Math.random()*formData.length)];
document.getElementById('randomName').innerHTML = randomValue.name;
document.getElementById('randomLocation').innerHTML = randomValue.location;
document.getElementById('randomType').innerHTML = randomValue.type;
}
I was working on something since you first posted, and here is my take on it:
HTML:
<section>
<p id="message">
<div id="name"></div>
<div id="location"></div>
<div id="type"></div>
</p>
<input type="button" id="btnAdd" value="Add" onclick="doAdd(this);" />
<input type="button" id="btnShow" value="Show" onclick="doShow(this);" />
<div class="form">
<script id="myRowTemplate" type="text/template">
<input type="text" name="name" placeholder="Name of Resturant" required="required" autocomplete="on" onchange="onChanged(this, {{i}})" />
<input type="text" name="type" placeholder="Type of Food" required="required" autocomplete="off" onchange="onChanged(this, {{i}})" />
<input type="text" name="location" placeholder="Location" required="required" autocomplete="off" onchange="onChanged(this, {{i}})" />
</script>
<form id="order" style="display:none;">
<div id="formItems">
</div>
<input type="button" value="Add Spot" onclick="addSpot()" />
</form>
</div>
</section>
JS:
function GetValue() {
if (enteredItems.length) {
var entry = enteredItems[Math.floor(Math.random() * enteredItems.length)];
document.getElementById("name").innerHTML = entry.name;
document.getElementById("location").innerHTML = entry.location;
document.getElementById("type").innerHTML = entry.type;
}
}
function doAdd(elem) {
switch (elem.value) {
case "Add":
document.getElementById('order').style.display = "";
elem.value = "Cancel";
break;
case "Cancel":
document.getElementById('order').style.display = "none";
elem.value = "Add";
break;
}
}
function doShow(elem) {
GetValue();
}
function addSpot(index) { // (note: here, index is only for loading for the first time)
if (index == undefined) index = enteredItems.length;
var newRowDiv = document.createElement("div");
newRowDiv.innerHTML = document.getElementById("myRowTemplate").innerHTML.replace(/{{i}}/g, index); // (this updates the template with the entry in the array it belongs)
if (enteredItems[index] == undefined)
enteredItems[index] = { name: "", location: "", type: "" }; // (create new entry)
else {debugger;
newRowDiv.children[0].value = enteredItems[index].name;
newRowDiv.children[1].value = enteredItems[index].location;
newRowDiv.children[2].value = enteredItems[index].type;
}
document.getElementById("formItems").appendChild(newRowDiv);
}
function onChanged(elem, index) {
enteredItems[index][elem.name] = elem.value;
localStorage.setItem('enteredItems', JSON.stringify(enteredItems)); // (save each time
}
// update the UI with any saved items
var enteredItems = [];
window.addEventListener("load", function() {
var retrievedObject = localStorage.getItem('enteredItems');
if (retrievedObject)
enteredItems = retrievedObject = JSON.parse(retrievedObject);
for (var i = 0; i < enteredItems.length; ++i)
addSpot(i);
});
https://jsfiddle.net/k1vp8dqn/
It took me a bit longer because I noticed you were trying to save the items, so I whipped up something that you can play with to suit your needs.
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}
Have a drill down form field with fields progressively appearing. While I've figured out the logic for making them appear one-at-a-time down the page, including a simple toggle, the issue is resetting all observables to their original state when "No" is clicked (and with all fields clearing). Currently if "Yes" is clicked a second time (after completing the form, then deciding "No") all the fields reappear at once instead of progressively. http://jsfiddle.net/hotdiggity/JJ6f6/
Knockout model:
var ViewModel = function () {
var self = this;
self.showField1 = ko.observable(true);
self.showField2 = ko.observable(false);
self.showField3 = ko.observable(false);
self.showField4 = ko.observable(false);
self.yesOrNo = ko.observable("");
self.hasValue = ko.observable("");
self.toggleCalc = ko.observable("");
self.showField2 = ko.computed(function () {
return self.yesOrNo() == 'yes';
});
self.showField3 = ko.computed(function () {
self.showField4(false);
return ( !! self.hasValue());
});
self.toggleCalc = function () {
self.showField4(!self.showField4());
};
};
ko.bindingHandlers.fadeVisible = {
init: function (element, valueAccessor) {
var value = valueAccessor();
$(element).toggle(ko.utils.unwrapObservable(value));
},
update: function (element, valueAccessor) {
var value = valueAccessor();
ko.utils.unwrapObservable(value) ? $(element).fadeIn() : $(element).fadeOut();
}
};
ko.applyBindings(new ViewModel());
HTML:
<div class='list-inputs'>
<h2>Drilldown toggle interaction</h2>
<!--LEVEL 1-->
<div data-bind='fadeVisible: showField1'>(L1) Choose one to display more options:
<p>
<label>
<input type='radio' name="type" value='yes' data-bind='checked: yesOrNo' />Yes</label>
<label>
<input type='radio' name="type" value='no' data-bind='checked: yesOrNo' />No</label>
</p>
<!--LEVEL 2-->
<div data-bind='fadeVisible: showField2'>
<p>(L2) Enter input and click off:
<input type="text" data-bind='value: hasValue' />
</p>
<!--LEVEL 3-->
<div data-bind='fadeVisible: showField3'>
<p><span>(L3) Earnings:</span>
<input type="text" /> <a data-bind="click: toggleCalc" href="#">Toggle calculator</a>
</p>
<!--LEVEL 4-->
<div data-bind='fadeVisible: showField4'>
<br />
<p><b>(L4) Calculator</b>
</p>
<p><span>Input 1:</span>
<input type="text" />
</p>
<p><span>Input 2:</span>
<input type="text" />
</p>
<p><span><b>Total:</b></span>
<input type="text" />
</p>
</div>
</div>
</div>
</div>
</div>
I got this working (I think the way you want) by making two changes:
I initialized the yesOrNo field to "no".
Changed showField2 to clear out the value of the "L2" textbox whenever the user changes their L1 response to "No". This causes the form to "re-initialize" so the next time they select yes, it will start clean.
self.showField2 = ko.computed(function () {
var isNo = self.yesOrNo() == 'no';
if( isNo )
{
self.hasValue('');
}
return !isNo;
});
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;
...