Knockout click binding doesnt fire on click - javascript

Just setup a basic knokcout view model and respective html view, but the click function doesnt fire.
<script>
new myModel.XYZ();
</script>
<div id="bar-1">
<button
title="Get Document"
data-toggle="tooltip"
data-bind="click: getDocument">
<span class="fas fa-file-alt"></span>
</button>
</div>
and my view model is setup as;
myModel.XYZ = function (par) {
var self = this;
self.getDocument = function(submission) {
alert('');
}
ko.applyBindings(self, $("#bar-1")[0]);
};
There's no console error or anything else that could help me find out the issue.

I mostly agree with #erpfast, But if you still want to implement your way, you forgot to declare myModel and added method on Object.
var myModel ={};
myModel.XYZ = function (par) {
var self = this;
self.getDocument = function(submission) {
alert('');
}
ko.applyBindings(self, $("#bar-1")[0]);
};
new myModel.XYZ();
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="bar-1">
<button
title="Get Document"
data-toggle="tooltip"
data-bind="click: getDocument">
Button
<span class="fas fa-file-alt"></span>
</button>
</div>

First, define your model
var myModel = function(par) {
var self = this;
self.getDocument = function(submission) {
alert('');
}
}
Then set your binding
ko.applyBindings(new myModel(document.getElementById("#bar-1")));
JSFiddle

Related

You cannot apply bindings multiple times to the same element in Knockout.Js

I'v applied binding only once, but still getting error
You cannot apply bindings multiple times to the same element.
This is my script.
<script>
var self = this;
var vm = function viewModel() {
self.getAppointment = function () {
$("#dialog-confirm ").dialog({
resizable: false,
height: 250,
width: 500,
modal: true
});
self.checkAppointmentListSelect(true);
}
self.checkAppointmentListSelect = ko.observable(false);
self.btnSelectAppointmentClick = function () {
self.checkAppointmentListSelect(true);
}
debugger;
}
ko.applyBindings(vm);
</script>
This is the html data
<div id="dialog-confirm" title="Select Appointment">
<div class="modal-body" data-bind="visible: checkAppointmentListSelect">
<button class="btn btn-primary" id="btnSelectAppointment" data-bind="click: btnSelectAppointmentClick">Select</button>
<button class="btn btn-primary" id="btnCancel">Cancel</button>
</div>
<div class="modal-body" data-bind="visible: checkAppointmentListSelect">
<button class="btn btn-primary" id="btnSelectAppointment">Select </button>
<button class="btn btn-primary" id="btnCancel">Cancel</button>
</div>
</div>
A couple of things to note:
var self = this; should be inside the constructor function. Outside, this refers to the window object.
You should pass an object containing observable properties to ko.applyBindings(). Not the function itself.
You either use Function Expression or Function Declaration to create a function in javascript. viewModel in your code is not required. It's either
var vm = function() {} or function vm(){}.
You have set checkAppointmentListSelect to false by default. Your buttons won't be displayed on load for you to click.
Change your code to:
function vm() {
var self = this; // this should be inside the vm function
self.getAppointment = function() {
$("#dialog-confirm ").dialog({
resizable: false,
height: 250,
width: 500,
modal: true
});
self.checkAppointmentListSelect(true);
}
self.checkAppointmentListSelect = ko.observable(true);
self.btnSelectAppointmentClick = function() {
self.checkAppointmentListSelect(true);
}
}
ko.applyBindings(new vm()); // `new vm()` creates an object of type vm
Here's a fiddle. Make all these changes and let me know if you're still facing any issue.

Unable to call JS function from onclick event

I am trying to call a JavaScript function from the onclick event of two different buttons. I have dug around and searched for like problems but have not found a solutions. When I click either button I get the error
Error: 'RemoveCode' is undefined'
What am I doing wrong?
<script type="text/javascript">
$(document).ready(function ()
{
function RemoveCode(codeType)
{
var selectedProjectsField = $("#SelectedProjects");
var selectedProjectCodesField = $("#SelectedProjectCodes");
var selectedTasksField = $("#SelectedTasks");
var selectedTaskCodesField = $("#SelectedTaskCodes");
var selectedOption;
if (codeType = "Project")
{
selectedOption = $("#SelectedProjects :selected").index();
}
else
{
selectedOption = $("#SelectedTasks :selected").index();
}
alert(selectedOption);
}
});
</script>
Code for my buttons:
<li>
<label for="SelectedProjects">Selected Projects:</label>
<select size="1" id="SelectedProjects" name="SelectedProjects" multiple></select> <button class="removeButton" onclick="RemoveCode('Project')" type="button">-</button>
</li>
<li>
<label for="SelectedTasks">Selected Tasks:</label>
<select size="1" multiple id="SelectedTasks" name="SelectedTasks"></select> <button class="removeButton" onclick="RemoveCode('Task')" type="button">-</button>
</li>
I should note that on the same page there are multiple change events for the other elements on the page and they all work fine. It is just this `onclickP that is failing.
Firstly note that in your if condition you need to use == (not =) to compare values.
To solve your issue you have two options. Firstly you could simply move the RemoveCode function out of the scope of the document.ready handler so that it can be accessed from the onclick attribute:
<script type="text/javascript">
function RemoveCode(codeType)
{
// your code...
}
$(document).ready(function ()
{
// your code...
});
</script>
Alternatively, it would be much better practice to add your event handlers using unobtrusive Javascript. As you're using jQuery, here's how you can do that:
$(function() {
$('button').click(function() {
var $selectedProjectsField = $("#SelectedProjects");
var $selectedProjectCodesField = $("#SelectedProjectCodes");
var $selectedTasksField = $("#SelectedTasks");
var $selectedTaskCodesField = $("#SelectedTaskCodes");
var selectedOption;
if ($(this).data('codetype') == "Project") {
selectedOption = $selectedProjectsField.find(':selected').index();
} else {
selectedOption = $selectedTasksField.find(':selected').index();
}
alert(selectedOption);
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul>
<li>
<label for="SelectedProjects">Selected Projects:</label>
<select size="1" id="SelectedProjects" name="SelectedProjects" multiple></select>
<button class="removeButton" data-codetype="Project" type="button">-</button>
</li>
<li>
<label for="SelectedTasks">Selected Tasks:</label>
<select size="1" multiple id="SelectedTasks" name="SelectedTasks"></select>
<button class="removeButton" data-codetype="Task" type="button">-</button>
</li>
</ul>
You are defining your RemoveCode method inside a closure. This function will thus not be available as onclick callbacks of your HTML elements.
You can just update your code to this and it should work:
<script type="text/javascript">
function RemoveCode(codeType)
{
var selectedProjectsField = $("#SelectedProjects");
var selectedProjectCodesField = $("#SelectedProjectCodes");
var selectedTasksField = $("#SelectedTasks");
var selectedTaskCodesField = $("#SelectedTaskCodes");
var selectedOption;
if (codeType = "Project")
{
selectedOption = $("#SelectedProjects :selected").index();
}
else
{
selectedOption = $("#SelectedTasks :selected").index();
}
alert(selectedOption);
}
</script>
put your function out side of document.ready()
<script type="text/javascript">
$(document).ready(function () // No Need of this Function here
{ });
function RemoveCode(codeType) // Automatically load when Your page is getting loaded on Browser.
{
var selectedProjectsField = $("#SelectedProjects");
var selectedProjectCodesField = $("#SelectedProjectCodes");
var selectedTasksField = $("#SelectedTasks");
var selectedTaskCodesField = $("#SelectedTaskCodes");
var selectedOption;
if (codeType = "Project")
{
selectedOption = $("#SelectedProjects :selected").index();
}
else
{
selectedOption = $("#SelectedTasks :selected").index();
}
alert(selectedOption);
}
</script>
You are defining your ready() method inside of a closure.
You then have two approaches you can use. First is you can not use $(document).ready() as the buttons that call ready() can't be clicked until the document is ready anyway.
Second is you could bind the onclick inside of your $(document).ready().
$(document).ready(function() {
$('#firstItem').click(function() { Ready('Project'); });
....
});

Binding a div row to a single item in an observableArray

I'm developing a 3 part upload form, where users can upload 3 sets of files
So far, I've got the following viewModel
var FileGroupViewModel = function (id) {
var self = this;
self.id = ko.observable(id);
self.files = ko.observableArray();
self.removeFile = function (item) {
self.files.remove(item);
}
self.fileUpload = function (data, e) {
var file = e.target.files[0];
self.files.push(file);
};
}
var ViewModel = function () {
var self = this;
self.fileGroups = ko.observableArray();
self.getFileGroupById = function (id) {
ko.utils.arrayFilter(self.fileGroups(), function (item) {
return item.id == id;
});
};
self.uploadFiles = function () {
alert('Uploading');
}
}
var viewModel = new ViewModel();
viewModel.fileGroups.push(new FileGroupViewModel(1));
viewModel.fileGroups.push(new FileGroupViewModel(2));
viewModel.fileGroups.push(new FileGroupViewModel(3));
ko.applyBindings(viewModel);
I have 3 'groups' of files a user can upload to.
(I will do the actual upload functionality later)
I'm struggling with how to bind my row to a specific item of the array?
Maybe I shouldn't use an observable array?
<div class="row files" id="files1" data-bind="???">
<h2>Files 1</h2>
<span class="btn btn-default btn-file">
Browse <input data-bind="event: {change: fileUpload}" type="file" />
</span>
<br />
<div class="fileList" data-bind="foreach: files"> <span data-bind="text: name"></span>
Remove
</div>
</div>
The idea is when a user selects files, they appear in a list under the button:
..with a link to remove the file from the upload queue.
I've set up a fiddle here - https://jsfiddle.net/alexjamesbrown/c9fvzjte/
There are few important modifications required to make your code work independently across files 0,1,2
KeyNote
event: { change: function(){fileUpload($data,$element.files[0])}}
here we are passing our selected file i.e filedata using $element in
change event not in usual click event . Filedata will have complete file information .
view:
<div class="row files" id="files1" data-bind="foreach:fileGroups">
<h2>Files 0</h2>
<span class="btn btn-default btn-file">
Browse <input data-bind="event: { change: function() { fileUpload($data,$element.files[0]) } }" type="file" />
</span>
<div class="fileList" data-bind="foreach: files"> <span data-bind="text: name"></span>
Remove
</div>
viewModel:
var SubFunction = function (data) {
var self = this;
self.name = data.name;
self.removeFile = function (item1) {
item1.files.remove(this); //current reference data & item1 has parent reference data
}
}
var FileGroupViewModel = function (id) {
var self = this;
self.id = ko.observable(id);
self.files = ko.observableArray([new SubFunction({
'name': 'Test'
})]);
self.fileUpload = function (item1, item2) {
self.files.push(new SubFunction(item2));
};
}
var ViewModel = function () {
var self = this;
self.fileGroups = ko.observableArray();
self.getFileGroupById = function (id) {
ko.utils.arrayFilter(self.fileGroups(), function (item) {
return item.id == id;
});
};
self.uploadFiles = function () {
alert('Uploading');
}
}
var viewModel = new ViewModel();
viewModel.fileGroups.push(new FileGroupViewModel(1));
ko.applyBindings(viewModel);
working sample up for grabs here
Working sample if you are planning to reuse your Html

Two-way data binding after an Ajax call

I have a problem with the AngularJS two-way data binding. I'll try to explain the issue as clearly as possible: I have a list of contact names. For each element I have an Edit button. When I click on that button I load the "clicked" full Contact from an Ajax call and then I show a window with some input fields linked to the Contact just retrieved ("phone", "email" etc.). This is the interesting piece of the view:
<div>
<div class="contact" data-ng-repeat="contact in contacts">
<span>{{contact.label}}</span>
<a href="" class="btn btn-xs btn-default" data-ng-click="openContactModal(contact.ID)">
Edit
</a>
</div>
</div>
The click on the Edit button fires this function (present in the Controller):
var newContact = null;
$scope.openContactModal = function(contactID){
newContact = new Contact(contactID);
newContact.onLoad().then(
function(){
//loading OK
$('#myModal').modal('show');
},
function(){
//loading Error
}
);
$scope.newContact = newContact;
};
The call to new Contact(contactID) loads a contact from the Database with an Ajax call. I open the modal window at the end of the Ajax call (waiting for the AngularJS promise). In the modal, though, all fields are empty even though they are linked to the contact model (newContact.phone, newContact.email etc.). I've already checked that the Ajax call works fine (printing the resulted JSON). I suppose I'm missing something in the two-way data binding issue. The strange fact is that, if I try to fill the empty modal fields, the newContact model reacts well, as if the two-way data binding works well from the view to the model, but not the contrary. Thank you in advance!
EDIT: this is the service that retrieves the contact:
angular.module("app").factory("Contact", ["ContactDBServices", "$q",
function(ContactDBServices, $q){
return function(contactID){
//the contact itself
var self = this;
var contactPromise = $q.defer();
//attributi del contatto
this.firstName = null;
this.ID = null;
this.lastName = null;
this.phone = null;
this.fax = null;
this.mobile = null;
this.email = null;
this.web = null;
//the Ajax call
var metacontact = ContactDBServices.find({ID:contactID},
function(){
this.ID = contactID;
this.firstName = metacontact.contact_name;
this.lastName = metacontact.contact_last_name;
this.phone = metacontact.contact_phone;
this.fax = metacontact.contact_fax;
this.mobile = metacontact.contact_mobile;
this.email = metacontact.contact_email;
this.web = metacontact.contact_web;
//!!!THE FOLLOWING PRINTS ARE GOOD!!!!
console.log(this.ID);
console.log(this.firstName);
console.log(this.lastName);
console.log(this.phone);
console.log(this.fax);
contactPromise.resolve("OK");
},
function(){
contactPromise.reject("Error");
}
);
this.onLoad = function(){
return contactPromise.promise;
};
}
}]);
If I print the same values in the controller, though, all that values are undefined:
var newContact = null;
$scope.openContactModal = function(contactID){
newContact = new Contact(contactID);
newContact.onLoad().then(
function(){
//!!!!!!!!THE FOLLOWING PRINTS ARE ALL UNDEFINED!!!!
console.log(newContact.firstName);
console.log(newContact.lastName);
console.log(newContact.phone);
console.log(newContact.fax);
$('#myModal').modal('show');
},
function(){
//loading Error
}
);
$scope.newContact = newContact;
};
This is strange. It seems a sort of synchronization issue :-/ to be thorough here is an example piece of the modal:
<div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog modal-sm">
<div class="modal-content">
<div class="modal-header">
<h2>Contact</h2>
</div>
<div class="modal-body">
<label>
Name
<input class="form-control" id="new_contact_name" data-ng-model="newContact.firstName" placeholder="Name">
</label>
<!-- ...and so on -->
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary" data-dismiss="modal" data-ng-click="createContact()">Crea</button>
</div>
</div>
</div>
Eventually I found the mistake. It was a mistake of mine and it doesn't belong to AngularJS, but to Javascript: you'll note that, in the Contact service, I did:
//the Ajax call
var metacontact = ContactDBServices.find({ID:contactID},
function(){
this.ID = contactID;
this.firstName = metacontact.contact_name;
this.lastName = metacontact.contact_last_name;
this.phone = metacontact.contact_phone;
this.fax = metacontact.contact_fax;
this.mobile = metacontact.contact_mobile;
this.email = metacontact.contact_email;
this.web = metacontact.contact_web;
},
function(){
contactPromise.reject("Error");
}
);
clearly, writing this. in the callback function, I didn't affect the Contact values, but the function attributes! To solve this issue I had to change the callback this way:
//the Ajax call
var metacontact = ContactDBServices.find({ID:contactID},
function(){
self.ID = contactID;
self.firstName = metacontact.contact_name;
self.lastName = metacontact.contact_last_name;
self.phone = metacontact.contact_phone;
self.fax = metacontact.contact_fax;
self.mobile = metacontact.contact_mobile;
self.email = metacontact.contact_email;
self.web = metacontact.contact_web;
},
function(){
contactPromise.reject("Error");
}
);
where
var self = this;
outside the callback.

Passing parameter using onclick or a click binding with KnockoutJS

I have this function:
function make(place)
{
place.innerHTML = "somthing"
}
I used to do this with plain JavaScript and html:
<button onclick="make(this.parent)">click me</button>
How can I do this using idiomatic knockout.js?
Use a binding, like in this example:
<a href="#new-search" data-bind="click:SearchManager.bind($data,'1')">
Search Manager
</a>
var ViewModelStructure = function () {
var self = this;
this.SearchManager = function (search) {
console.log(search);
};
}();
If you set up a click binding in Knockout the event is passed as the second parameter. You can use the event to obtain the element that the click occurred on and perform whatever action you want.
Here is a fiddle that demonstrates: http://jsfiddle.net/jearles/xSKyR/
Alternatively, you could create your own custom binding, which will receive the element it is bound to as the first parameter. On init you could attach your own click event handler to do any actions you wish.
http://knockoutjs.com/documentation/custom-bindings.html
HTML
<div>
<button data-bind="click: clickMe">Click Me!</button>
</div>
Js
var ViewModel = function() {
var self = this;
self.clickMe = function(data,event) {
var target = event.target || event.srcElement;
if (target.nodeType == 3) // defeat Safari bug
target = target.parentNode;
target.parentNode.innerHTML = "something";
}
}
ko.applyBindings(new ViewModel());
I know this is an old question, but here is my contribution. Instead of all these tricks, you can just simply wrap a function inside another function. Like I have done here:
<div data-bind="click: function(){ f('hello parameter'); }">Click me once</div>
<div data-bind="click: function(){ f('no no parameter'); }">Click me twice</div>
var VM = function(){
this.f = function(param){
console.log(param);
}
}
ko.applyBindings(new VM());
And here is the fiddle
A generic answer on how to handle click events with KnockoutJS...
Not a straight up answer to the question as asked, but probably an answer to the question most Googlers landing here have: use the click binding from KnockoutJS instead of onclick. Like this:
function Item(parent, txt) {
var self = this;
self.doStuff = function(data, event) {
console.log(data, event);
parent.log(parent.log() + "\n data = " + ko.toJSON(data));
};
self.doOtherStuff = function(customParam, data, event) {
console.log(data, event);
parent.log(parent.log() + "\n data = " + ko.toJSON(data) + ", customParam = " + customParam);
};
self.txt = ko.observable(txt);
}
function RootVm(items) {
var self = this;
self.doParentStuff = function(data, event) {
console.log(data, event);
self.log(self.log() + "\n data = " + ko.toJSON(data));
};
self.items = ko.observableArray([
new Item(self, "John Doe"),
new Item(self, "Marcus Aurelius")
]);
self.log = ko.observable("Started logging...");
}
ko.applyBindings(new RootVm());
.parent { background: rgba(150, 150, 200, 0.5); padding: 2px; margin: 5px; }
button { margin: 2px 0; font-family: consolas; font-size: 11px; }
pre { background: #eee; border: 1px solid #ccc; padding: 5px; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.0/knockout-min.js"></script>
<div data-bind="foreach: items">
<div class="parent">
<span data-bind="text: txt"></span><br>
<button data-bind="click: doStuff">click: doStuff</button><br>
<button data-bind="click: $parent.doParentStuff">click: $parent.doParentStuff</button><br>
<button data-bind="click: $root.doParentStuff">click: $root.doParentStuff</button><br>
<button data-bind="click: function(data, event) { $parent.log($parent.log() + '\n data = ' + ko.toJSON(data)); }">click: function(data, event) { $parent.log($parent.log() + '\n data = ' + ko.toJSON(data)); }</button><br>
<button data-bind="click: doOtherStuff.bind($data, 'test 123')">click: doOtherStuff.bind($data, 'test 123')</button><br>
<button data-bind="click: function(data, event) { doOtherStuff('test 123', $data, event); }">click: function(data, event) { doOtherStuff($data, 'test 123', event); }</button><br>
</div>
</div>
Click log:
<pre data-bind="text: log"></pre>
**A note about the actual question...*
The actual question has one interesting bit:
// Uh oh! Modifying the DOM....
place.innerHTML = "somthing"
Don't do that! Don't modify the DOM like that when using an MVVM framework like KnockoutJS, especially not the piece of the DOM that is your own parent. If you would do this the button would disappear (if you replace your parent's innerHTML you yourself will be gone forever ever!).
Instead, modify the View Model in your handler instead, and have the View respond. For example:
function RootVm() {
var self = this;
self.buttonWasClickedOnce = ko.observable(false);
self.toggle = function(data, event) {
self.buttonWasClickedOnce(!self.buttonWasClickedOnce());
};
}
ko.applyBindings(new RootVm());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.0/knockout-min.js"></script>
<div>
<div data-bind="visible: !buttonWasClickedOnce()">
<button data-bind="click: toggle">Toggle!</button>
</div>
<div data-bind="visible: buttonWasClickedOnce">
Can be made visible with toggle...
<button data-bind="click: toggle">Untoggle!</button>
</div>
</div>
Knockout's documentation also mentions a much cleaner way of passing extra parameters to functions bound using an on-click binding using function.bind like this:
<button data-bind="click: myFunction.bind($data, 'param1', 'param2')">
Click me
</button>

Categories

Resources