Knockout options with forms not binding to model - javascript

I am having problems with KnockoutJS and the options binding.
What I'm trying to achieve is functionality to display a form based on selected option with the options binding.
The odd thing is that it works with the first form-field but the model is not value binded for the others.
Here is my markup:
<div id="page">
<form data-bind="submit: executeTask">
<select data-bind="options: availableTasks, value: selectedTask, optionsText: 'description', optionsCaption: 'Select...',"></select>
<div data-bind="visible: selectedTask">
<input type="text" data-bind="value:selectedTask().assignee">
<input type="text" data-bind="value:selectedTask().estimatedTime">
</div>
<button class="btn" type="submit">Submit</button>
</form>
</div>
<script>
$(function() {
var taskController = new TaskController(document.getElementById("page"));
});
</script>
And here is my model:
(function () {
function Task(id, description) {
var model = this;
model.id = ko.observable(id);
model.description = ko.observable(description);
model.assignee = ko.observable();
model.estimatedTime = ko.observable();
};
window.TaskController = function (element) {
var model = this;
model.availableTasks = [
new Task("0", "Laundry"),
new Task("1", "Dinner")];
model.selectedTask = ko.observable();
model.executeTask = function (form) {
console.log(ko.toJSON(model.selectedTask()));
};
ko.applyBindings(this, element);
};
})();
The full code can be found at the following fiddle:
http://jsfiddle.net/LkqTU/17057/
As you can see in the console, only the "assignee" property is getting binded, but not the "estimatedTime" property.
What am I doing wrong?
Thanks,
Bj Blazkowicz

Solution 1:
Replace the visible binding with an if binding:
<div data-bind="visible: selectedTask">
Should be
<div data-bind="if: selectedTask">
Explanation
You were getting an error because it could not find selectedTask().assignee. The problem was that selectedTask() returned undefined at page load. Even though the inputs were not visible due to visible: selectedTask, the binding was interpreted.
With the if binding, what is inside the tag is ignored when selectedTask() returns undefined.
Solution 2:
As nemesv pointed out, you can use the with binding (see his answer):
<div data-bind="with: selectedTask">
<input type="text" data-bind="value: assignee">
<input type="text" data-bind="value: estimatedTime">
</div>

Related

Referencing properties of object constructor outside view model in knockout

So, I'm not entirely sure how to phrase this question as it's sort of two in one. I'm having a weird issue where I have an object constructor to create new 'projects' from an HTML form which are then pushed into an observableArray when the form is submitted. Everything works fine but to reference the related observables I have to use 'value: Project.title' or 'value: Project.whatever'. I haven't seen 'value: NameOfConstructor.property' used in any of the examples I've seen. I assume this works this way because the constructor is outside of my view model.
My question is this: Is there a better way to assign the value of a property in a constructor that is not in my view model? In other words, is there a better or more correct way than 'Project.title', ect?
I ask partially because one thing in my code doesn't work currently; the knockout enable property doesn't work on my "New Project" button, it stays disabled even if there is something written in the 'title' input box. I have the feeling it's because it's written as data-bind='enable: Project.title' but I can't figure how else to write it.
I've included a jsfiddle for reference though it obviously isn't working because of external dependencies.
https://jsfiddle.net/bmLh0vf1/1/
My HTML:
<form id='addBox' action='#' method='post'>
<label for='pTitle'> Title: </label>
<input id='pTitle' data-bind='value: Project.title' />
<br/>
<label for='pPriority'> Priority </label>
<select id='pPriority' data-bind='options: priorityOptions, value: Project.priority'></select>
<br/>
<button data-bind='enable: Project.title, click: newProject'>New Project</button>
</form>
And my Javascript:
function Project(title, priority) {
this.title = ko.observable(title);
this.priority = ko.observable(priority);
};
function ProjectViewModel() {
var self = this;
this.priorityOptions = ko.observableArray(['High', 'Medium', 'Low'])
this.projectList = ko.observableArray([
new Project('Create App', 'High')
]);
this.newProject = function() {
var np = new Project(Project.title, Project.priority);
self.projectList.push(new Project(Project.title, Project.priority));
console.log(self.projectList().length);
if (self.projectList().length > 1) {
console.log(self.projectList()[1].title());
};
}
};
var viewModel = new ProjectViewModel();
$(document).ready(function() {
ko.applyBindings(viewModel);
});
Lastly, I apologize if I've missed any posting conventions or if my code is especially bad. I'm very new and still teaching myself.
Your code is setting title and priority properties on the object created by new Project, but then later you're expecting to see those properties on Project itself. It doesn't have them; Project is the function, not the object created by new Project. So Project.title and Project.priority will give you undefined (and not an observable, and so not useful targets for the value binding).
Instead, have an "editing" Project instance that you use, binding the value of the inputs to the editing' instances title and priority, and then in newProject grab that instance and replace it with a new, fresh one.
Roughly speaking, in ProjectViewModel's constructor:
this.editing = ko.observable(new Project());
Update Project to default title and priority:
function Project(title, priority) {
this.title = ko.observable(title || "");
this.priority = ko.observable(priority || "Medium");
}
And in the bindings:
<input id='pTitle' data-bind='value: editing().title' />
<select id='pPriority' data-bind='options: priorityOptions, value: editing().priority'></select>
And in newProject:
var np = this.editing();
this.editing(new Project());
Then use np (instead of another new Project) when adding to the array.
Here's a simplified example:
function Project(title, priority) {
this.title = ko.observable(title || "");
this.priority = ko.observable(priority || "Medium");
}
function ProjectViewModel() {
var self = this;
this.priorityOptions = ko.observableArray(["High", "Medium", "Low"]);
this.projects = ko.observableArray();
this.editing = ko.observable(new Project());
this.addProject = function() {
this.projects.push(this.editing());
this.editing(new Project());
};
}
ko.applyBindings(new ProjectViewModel(), document.body);
<div>
<div>
<label>
Title:
<input type="text" data-bind="value: editing().title, valueUpdate: 'input'">
</label>
</div>
<div>
<label>
Priority:
<select data-bind='options: priorityOptions, value: editing().priority'></select>
</label>
</div>
<div>
<button type="button" data-bind="click: addProject, enable: editing().title">Add Project</button>
</div>
<hr>
<div>Projects:</div>
<div data-bind="foreach: projects">
<div>
<span data-bind="text: title"></span>
(<span data-bind="text: priority"></span>)
</div>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>

AngularJS not sending hidden input value

I'm trying to get data from my form in AngularJS, this all works fine except for the field I did not type anything in. I changed the field from hidden to text, but both do not work, however if you inspect element you can see the correct value in it. Here's my HTML:
<div ng-controller="postMessageCtrl as Ctrl">
<form ng-submit="processMessage()">
<div class="form-group">
<input type="message" class="form-control" placeholder="Message" ng-model="formData.message">
a{{data.receiver.id}}a
<input type="hidden" class="form-control" ng-model="formData.receiver" ng-value="data.receiver.id" />
</div>
<button type="submit" class="btn btn-primary btnq-lg btn-block">Verzenden</button>
</form>
</div>
And here's my controller:
app.controller('postMessageCtrl', function ($scope, $http, $state, localStorageService) {
$scope.formData = {};
//$scope.formData = localStorageService.get('userKey');
$scope.formData = {
key: localStorageService.get('userKey'),
message: '',
receiver: ''
};
console.log($scope.formData);
});
The key and message are filled correctly, but the receiver id is not. any suggestions?
From the answer AngularJS does not send hidden field value:
You cannot use double binding with hidden field. The solution is to use brackets:
<input type="hidden" name="someData" value="{{data}}" /> {{data}}
See this thread on GitHub: https://github.com/angular/angular.js/pull/2574
Since Angular 1.2, you can use ng-value directive to bind an expression to the value attribute of input. This directive should be used with input radio or checkbox but works well with hidden input.
Here is the solution using ng-value:
<input type="hidden" name="someData" ng-value="data" />
Update:
Another solution could be to directly set the value in $scope.formData rather using the hidden input field when you are initializing it:
$scope.formData = {};
//$scope.formData = localStorageService.get('userKey');
$scope.formData = {
key: localStorageService.get('userKey'),
message: '',
receiver: ''
};
$scope.formData.receiver = $scope.data.receiver.id // Set the value directly in your `formData` since you are using Angular;
console.log($scope.formData);
The simple solution is to use ngInit directive:
<input type="hidden" class="form-control"
ng-model="formData.receiver"
ng-init="formData.receiver = data.receiver.id" />
Avoid submit complexion by just handling things with a function call on a button click, like on this Plunk.
Html:
<div ng-controller="postMessageCtrl as Ctrl">
<form>
<div class="form-group">
<input type="message" class="form-control" placeholder="Message" ng-model="messageInput">
<button ng-click="Add()">Add</button>
<p></p>
<button type="submit" class="btn btn-primary btnq-lg btn-block" ng-click="Send()">Send</button>
</div>
<p></p>
<b>Messages</b>
<ul>
<li ng-repeat="message in formData.messages">{{message}}</li>
</ul>
</form>
</div>
AngularJS Controller:
app.controller("postMessageCtrl", [
"$scope",
"$http",
function($scope, $http){
var self = {};
$scope.messageInput = '';
$scope.formData = {
key: 'someUserKey',
messages: [],
receiver: null
};
$scope.Add = function(){
console.log($scope.messageInput);
if($scope.messageInput.length > 0) {
$scope.formData.messages.push($scope.messageInput);
}
};
$scope.Send = function() {
console.log($scope.formData);
$http.post("/somehost/action/", $scope.Person).success(function(data, status) {
$scope.hello = data;
});
};
}]);
The sample will have a 400 bad request error in console, because the url used is obviously not going to work, but the principle is correct.
This way you don't even need to add hidden fields, because they aren't needed (you always have their value from $scope.Person).
Conclusion:
There are 2 things that didn't make sense from your original question:
a{{data.receiver.id}}a
You should use formData here, data isn't defined.
JSON is incorrect
Receiver doesn't contain id, given your sample code, it should be defined like so:
$scope.formData = {
key: localStorageService.get('userKey'),
message: '',
receiver: {
id: 1,
name: 'SomeReceiver'
}
};
So if your receiver is set like this:
$scope.formData.receiver = $scope.formData.messages[0].receiver;
You will need to implement some way of providing that receiver through messages[0];
You'll notice that the receiver becomes an Object in the console log.

DataPicker not getting binded to textbox ? fiddle provided

Well in other cases i will get datepicker binded to my textbox which will be straight forward but not in this case .
Fiddle link : http://jsfiddle.net/JL26Z/1/ .. while to setup perfect seanrio i tried but unable to bind datepicker to textboxes . except that everything is in place
My code :
**<script id="Customisation" type="text/html">** // here i need to have text/html
<table style="width:1100px;height:40px;" align="center" >
<tr>
<input style="width:125px;height:auto;" class="txtBoxEffectiveDate" type="text" id="txtEffective" data-bind="" />
</tr>
</script>
The above code is used for my dynamic generation of same thing n no of time when i click each time on a button . So above thing is a TEMPLATE sort of thing .
My knockout code :
<div data-bind="template:{name:'Customisation', foreach:CustomisationList},visible:isVisible"></div>
<button data-bind="click:$root.CustomisatioAdd" >add </button>
I tried same old way to bind it with datepicker
$('#txtEffective').datepicker(); // in document.ready i placed
Actually to test this i created a textbox with some id outside script with text/html and binded datepicker to it and It is working fine sadly its not working for the textbox inside text/html and i want to work at any cost.
PS: well i haven't posted my view model as it is not required in this issue based senario
View model added with Js
var paymentsModel = function ()
{
function Customisation()
{
var self = this;
}
var self = this;
self.isVisible = ko.observable(false);
self.CustomisationList = ko.observableArray([new Customisation()]);
self.CustomisationRemove = function () {
self.CustomisationList.remove(this);
};
self.CustomisatioAdd = function () {
if (self.isVisible() === false)
{
self.isVisible(true);
}
else
{
self.CustomisationList.push(new Customisation());
}
};
}
$(document).ready(function()
{
$('#txtEffective').datepicker();
ko.applyBindings(new paymentsModel());
});
Any possible work around is appreciated
Regards
The best way I've found to do this is create a simple bindingHandler.
This is adapted from code I have locally, you may need to tweak it...
** code removed, see below **
Then update your template:
** code removed, see below **
By using a bindingHandler you don't need to try to hook this up later, it's done by knockout when it databinds.
Hope this is helpful.
EDIT
I created a fiddle, because I did indeed need to tweak the date picker binding quite a lot. Here's a link to the Fiddle, and here's the code with some notes. First up, the HTML:
<form id="employeeForm" name="employeeForm" method="POST">
<script id="PhoneTemplate" type="text/html">
<div>
<span>
<label>Country Code:</label>
<input type="text" data-bind="value: countryCode" />
</span>
<span><br/>
<label>Date:</label>
<input type="text" data-bind="datepicker: date" />
</span>
<span>
<label>Phone Number:</label>
<input type="text" data-bind="value: phoneNumber" />
</span>
<input type="button" value="Remove" data-bind="click: $parent.remove" />
</div>
</script>
<div>
<h2>Employee Phone Number</h2>
<div data-bind="template:{name:'PhoneTemplate', foreach:PhoneList}">
</div>
<div>
<input type="button" value="Add Another" data-bind="click: add" />
</div>
</div>
</form>
Note I removed the id=... from in your template; because your template repeats per phone number, and ids must be unique to be meaningful. Also, I removed the datepicker: binding from the country code and phone number elements, and added it only to the date field. Also - the syntax changed to "datepicker: ". If you need to specify date picker options, you would do it like this:
<input type="text" data-bind="datepicker: myObservable, datepickerOptions: { optionName: optionValue }" />
Where optionName and optionValue would come from the jQueryUI documentation for datepicker.
Now for the code and some notes:
// Adapted from this answer:
// https://stackoverflow.com/a/6613255/1634810
ko.bindingHandlers.datepicker = {
init: function(element, valueAccessor, allBindingsAccessor) {
//initialize datepicker with some optional options
var options = allBindingsAccessor().datepickerOptions || {},
observable = valueAccessor(),
$el = $(element);
// Adapted from this answer:
// https://stackoverflow.com/a/8147201/1634810
options.onSelect = function () {
if (ko.isObservable(observable)) {
observable($el.datepicker('getDate'));
}
};
$el.datepicker(options);
// set the initial value
var value = ko.unwrap(valueAccessor());
if (value) {
$el.datepicker("setDate", value);
}
//handle disposal (if KO removes by the template binding)
ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
$el.datepicker("destroy");
});
},
update: function(element, valueAccessor) {
var value = ko.utils.unwrapObservable(valueAccessor()),
$el = $(element);
//handle date data coming via json from Microsoft
if (String(value).indexOf('/Date(') === 0) {
value = new Date(parseInt(value.replace(/\/Date\((.*?)\)\//gi, "$1")));
}
var current = $el.datepicker("getDate");
if (value - current !== 0) {
$el.datepicker("setDate", value);
}
}
};
function Phone() {
var self = this;
self.countryCode = ko.observable('');
self.date = ko.observable('');
self.phoneNumber = ko.observable('');
}
function PhoneViewModel() {
var self = this;
self.PhoneList = ko.observableArray([new Phone()]);
self.remove = function () {
self.PhoneList.remove(this);
};
self.add = function () {
self.PhoneList.push(new Phone());
};
}
var phoneModel = new PhoneViewModel();
ko.applyBindings(phoneModel);
Note the very updated binding handler which was adapted from this answer for the binding, and this answer for handling onSelect.
I also included countryCode, date, and phoneNumber observables inside your Phone() object, and turned your model into a global variable phoneModel. From a debugger window (F12 in Chrome) you can type something like:
phoneModel.PhoneList()[0].date()
This will show you the current value of the date.
I notice that your form is set up to post somewhere. I would recommend instead that you add a click handler to a "Submit" button and post the values from your phoneModel using ajax.
Hope this edit helps.
Dynamic entities need to have datepicker applied after they are created. To do this I'd use an on-click function somewhere along the lines of
HTML
<!-- Note the id added here -->
<button data-bind="click:$root.CustomisatioAdd" id="addForm" >add </button>
<script>
$(document).on('click', '#addForm', function(){
$('[id$="txtEffective"]').datepicker();
});
</script>

dynamic radio button behaviour with knockout js

I have a button to create radio buttons like this:
<input value="&uparrow;" type="button" data-bind=" click: moveUp"/>
<input value="&downarrow;" type="button" data-bind="click: moveDown"/>
<input data-bind="value: selectedradio" />
<div data-bind="foreach: radios">
<div data-bind="attr:{id:radioid} ">
<input type="radio" name="group" data-bind="value:radioid, checked:$parent.selectedradio"/>
</div>
</div>
<div>
<input type="button" value="+" data-bind="click: addRadio"/>
</div>
when the radio buttons are created they are each assigned a unique index and i have bound the checked attribute to a property on the parent called selectedradio
I am able to modify selectedradio though an html input and the selected radio will change but If I modify the value progmmatically it will not.
I created a fiddle here: http://jsfiddle.net/8vVeU/
here is the javascript for reference:
function Radio(id){
this.radioid = ko.observable(id);
}
function ViewModel() {
var self = this;
self.selectedradio = ko.observable(0);
self.radios = ko.observableArray([new Radio(0),new Radio(1),new Radio(2),new Radio(3)]);
self.addRadio = function() {
self.radios.push(new Radio());
self.reIndex();
};
self.moveUp = function() {
self.selectedradio(self.selectedradio()-1)
self.reIndex();
};
self.moveDown = function() {
self.selectedradio(self.selectedradio()+1)
self.reIndex();
};
self.reIndex = function() {
$.each(self.radios(), function(i, r) {
r.radioid(i);
});
}
}
ko.applyBindings(new ViewModel());
How can I resolve this issue?
In the case of radio buttons the checked binding by default compares the input's value attribute - which is a string - to the model property.
So you need to store strings in your selectedradio :
self.moveUp = function(r) {
r=parseInt(r,10);
self.selectedradio((parseInt(self.selectedradio())-1).toString())
self.reIndex();
};
self.moveDown = function(r) {
r=parseInt(r,10);
self.selectedradio((parseInt(self.selectedradio())+1).toString())
self.reIndex();
};
It works with the textbox because it will fill in the selectedradio with the entered string.
Demo JSFiddle.
Or you can change the default behavior and you can tell KO to use a different property as the checkbox's value with the checkedValue option:
<input type="radio" name="group" data-bind="value:radioid,
checked:$parent.selectedradio, checkedValue: radioid"/>
In this case you don't need to alter your moveUp and moveDown methods because now the selectedradio will always contain integers however because your textbox still sets the selectedradio to string it won't work any more.
Demo JSFiddle.

Knockout is generating two bindings for single foreach

I'm facing headache issue that led me to spend two days looking for a solution for it. I hope anyone would help me with it. I'm using knockout to generate bindings with json data for HTML markups. However, I'm not able to change the css of the element because I realized the element is generated twice and assigned the same id. Here's snippet of my code
<div id = 'divBinder' data-bind="foreach: Results" >
<div id='rowStyle' class='eligibilitydivContentTableRow' >
<div class='eligibilitydivContentLeftCell' style="float:left;" data-bind=" text: RequirementDescription"></div>
<div class='eligibilitydivContentMiddleCell' style="float:left;">
<span class='spanClass'></span>
<input class='inputRadio' type='radio' value:'true' data-bind="attr: { checked: IsChecked,'name': $index() }" />
<span class='spanClass'></span>
</div>
<div class='eligibilitydivContentRightCell' style="float:left;"><span class='spanClass'></span>
<input class='inputRadio2' type='radio' value:'false' data-bind="attr: { checked: IsChecked, 'name': $index(), onclick:'testFunction.apply(this);return true;'}" />
<span class='spanClass'></span>
</div>
</div>
<div data-bind=" attr: {'id': getSuffixRowID($index())}" style="display:none;" >
<div style="float:left;">
<textarea > </textarea>
</div>
<div>
<input type='text' id='dateField' onfocus='showDate()' /></div>
</div>
</div>
Here are the javascript function I'm using to generate ids
function getSuffixRowID(suffix) {
// alert(suffix);
return 'hiddenRows' + suffix;
}
Here's my binding
viewModel = [];
viewModel.Results = ko.mapping.fromJS(globalizedData.Results);
ko.cleanNode(document.getElementById("parentDivElement"));
ko.applyBindings(viewModel, document.getElementById("parentDivElement"));
Note that the RequirementDescription is binded correctly. The only problem is setting the css through testFunction being called when button is checked
function testFunction() {
// jQuery('#' + getSuffixRowID(this.attributes[6].nodeValue)).hide();
var nodeId = this.attributes['name'].nodeValue;
var stringValue = this.value;
switch (stringValue) {
case ('true'):
viewModel.Results()[nodeId].IsCompleted(true);
viewModel.Results()[nodeId].IsChecked(true);
break;
case ('false'):
viewModel.Results()[nodeId].IsCompleted(false);
viewModel.Results()[nodeId].IsChecked(false);
var idName = getSuffixRowID(nodeId);
$('#' + idName).css('display', 'block !important;');
break;
}
}
The id for checkbox elements are assigned via $index variable inside foreach. I realized the duplicate generation through taking a look at the generate html page. It has two duplicate foreach markups. Any help is really appreciated.
Thanks
This is not the way you should code with KnockoutJS :
onclick:'testFunction.apply(this);return true;'}
The Result object should have two properties (one for each checkbox).
So assuming your Result object looks like :
var Result = function() {
var self = this;
self.checkbox1 = ko.observable();
self.checkbox2 = ko.observable();
};
The binding the checkbox will be :
onclick: $parent:testFunction, value : checkbox2
You can add the id binding if you want.
And the TestFunction :
function testFunction(result/* the result item */) {
if(result.checkbox2()) {
}
[...]
};
With Knockout you souldn't modify the view directly. You have to leverage the viewModel and knockout will modify the view for you.
Please take a look at the visible binding too.
I hope it helps.

Categories