Knockout is generating two bindings for single foreach - javascript

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.

Related

Writing HTML5 data- to JSON in anonymous function in jQuery

I have a click-through app using data- properties in HTML, then using jQuery, I want to save each selected option to either an array or as an object. But I'm not sure the best way to approach this - I've tried .data() and .map() with no luck.
Essentially I want to just store the data-app value that is clicked (and keep it as JSON format) through each .step iteration. But I'm not sure how to save the data either to an object or array and I can't seem to find a great solution elsewhere. Any help is greatly appreciated!
HTML:
<div class="start step" id="">
<h2>Select Property Type?</h2>
<div id="choice" data-app='{"PropertyType" : "single_family"}'>Single Family</div>
<div id="choice" data-app='{"PropertyType" : "apartment"}'>Apartment</div>
<div id="choice" data-app='{"PropertyType" : "condo"}'>Condo</div>
<div id="choice" data-app='{"PropertyType" : "hovel"}'>Hovel</div>
</div>
<div class="step" id="">
<h2>Are you looking to live in Toronto?</h2>
<div id="choice" data-app='{"LiveInToronto" : "yes"}'>Yes</div>
<div id="choice" data-app='{"LiveInToronto" : "no"}'>No</div>
</div>
jQuery
var currentStep = 1;
var num = 1;
var formType = '';
var propertyType = '';
var app = [];
$(this).find('.step').each(function(){
$(this).attr('id', 'step'+num);
num++;
$(this).hide();
});
$('.step > #choice').click(function(){
// propertyType = $(this).data('property-type');
data = $(this).data('app');
app = $.map(data, function(value, key){
return (key + ' ' + value);
});
nextStep();
});
console.log(app);
function nextStep() {
$('#step' + currentStep).hide();
currentStep++;
$('#step' + currentStep).fadeIn('fast');
return currentStep;
}
When using console.log(app) outside of the anonymous function, it returns undefined.
Some issues:
console.log(app) will execute on page load, not when the user clicks, so obviously it will not output what is happening in the anonymous function. If you want to output the result there, you must move the console.log(app) statement there.
In HTML the id attribute must have unique values, which is not the case for choice in your code. So replace with a class attribute instead.
You are not collecting data into app, but overwriting it at each step. You should use app.push to not lose the previously stored data
this in top-level code is the window object (or undefined in strict mode), so $(this).find('.step') will not return anything. Use $(document) instead.
It should not be necessary to dynamically assign id attribute values. Instead use some smarter selectors.
Here is some modified code:
var app = [];
// hide all steps, except first (don't use this, but document)
$(document).find('.step').slice(1).hide();
$('.step > .choice').click(function(){
// propertyType = $(this).data('property-type');
data = $(this).data('app');
app.push($.map(data, function(value, key){
// ^^^^^^
return (key + ' ' + value);
}));
console.log(app); // log here!
nextStep();
});
function nextStep() {
var $next = $('.step:visible').next('.step');
$('.step').hide(); // just hide all of them
$next.fadeIn('fast');
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="start step" id="">
<h2>Select Property Type?</h2>
<div class="choice" data-app='{"PropertyType" : "single_family"}'>Single Family</div>
<div class="choice" data-app='{"PropertyType" : "apartment"}'>Apartment</div>
<div class="choice" data-app='{"PropertyType" : "condo"}'>Condo</div>
<div class="choice" data-app='{"PropertyType" : "hovel"}'>Hovel</div>
</div>
<div class="step" id="">
<h2>Are you looking to live in Toronto?</h2>
<div class="choice" data-app='{"LiveInToronto" : "yes"}'>Yes</div>
<div class="choice" data-app='{"LiveInToronto" : "no"}'>No</div>
</div>
NB: I doubt that the concatenation of key and value into your array is very useful. You lose the structure that you have in your JSON data attributes.

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>

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>

Knockout options with forms not binding to model

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>

Issue in getting index value

I am trying to store value in attribute in input tag which would like as follows.
The following code will repeat several times and the value for different radio can be saved using index.
<div data-bind="attr : { name : 'ex['+$index()+']' }>
<input type="radio" name="value" data-target="#modal" data-bind="click:fun.fill($index())"/>
</div>
From the above text box i can get the index of that input.And it points to a common modal function from bootstrap which opens a popup.
<div class="modal fade">
<select data-bind="attr:{name:'assignedResources['+$index()+'][repeatedType]'},
options : $root.repeats,value : repeatedType"></select>
</div><!-- /.modal -->
This is a sample of modal target.I want to call this modal as common.(i.e)it will be called from several places.I want to show appropriate popup for appropriate click from radio button.
But what i get is value of last index.Thats my problem
I'm not 100% sure I understand your question, but something like this...
this.abc = ko.observable('');
this.fun = {
var that = this;
fill: function(index) {
console.log(that.abc());
}
}
As far as I understand your problem, you need an observable to store your value, and then you can access it by any other function on your viewModel.
This is a jsFiddle with my initial approach, let me know if is what you needed or clarify instead:
http://jsfiddle.net/rdarioduarte/X8Rc4/
With a model like this:
var viewModel = function() {
this.abc = ko.observable('Value to store');
this.fun = function() {
alert(this.abc());
}
}
ko.applyBindings(new viewModel());
Thanks,
Dario
I do not quite understand about your question. But, maybe this may help:
1) Obtains index of your input element automatically on page load to viewModel function maybe need custom binding to handle that:
e.g custom binding:
ko.bindingHandlers.saveIndex = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
var index = ko.utils.unwrapObservable(valueAccessor());
viewModel.fun(index); // accessing fun function on viewModel
// You can also access that function using bindingContext.$root
// bindingContext.$root.fun(index);
}
};
e.g html:
<input type="text" data-bind="saveIndex: $index()"/>
2) Store index to input value attribute using attr binding:
<input type="text" data-bind="attr: { value: $index() }">
3) Send your input attr value to viewModel function onClick:
<input type="text" value="thisIsExampleValue" data-bind="click: function() { $root.fun($element.value); }">
You also can use event binding for this behavior, Knockout.Event-binding
UPDATE:
Try this:
<div data-bind="attr : { name : 'ex['+$index()+']' }>
<input type="radio" name="value" data-target="#modal" data-bind="click: function() { fun.fill($index()) }"/>

Categories