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>
Related
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
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.
what i'm trying to do is call a funcation like below, further i want to pass current element (item) to function as parameter.
<tr rv-each-item="items:models">
<td>{ item:Name }</td>
</tr>
var selectedItem = function (item)
{
console.log(item);
}
while searching around i found below discussion helpful but could not solve my problem as it does not implement backbone
https://github.com/mikeric/rivets/issues/554
Rivets.js: When button is clicked, call a function with an argument from a data binding
While working around i found different approaches that can help, posting here if someone can get help or improve if there is any thing needs to.
Option 1
<body>
<div rv-each-book="model.books">
<button rv-on-click="model.selectedBook | args book">
Read the book {book}
</button>
</div>
</body>
<script type="text/javascript">
rivets.formatters["args"] = function (fn) {
var args = Array.prototype.slice.call(arguments, 1);
return function () {
return fn.apply(null, args);
};
};
rvBinder = rivets.bind(document.body, {
model: {
selectedBook: function (book) {
alert("Selected book is " + book);
},
books: ["Asp.Net", "Javascript"]
}
});
</script>
Option 2
Create a custom binder
<body>
<div rv-each-book="books">
<a rv-cust-href="book">
Read the book {book}
</a>
</div>
</body>
<script type="text/javascript">
rivets.binders['cust-href'] = function (el, value) {
//el.href = '/Books/Find/' + value;
//OR
el.onclick = function() { alert(value);};
}
rvBinder = rivets.bind(document.body, {
books: ["Asp.Net", "Javascript"]
});
</script>
Option 3
As I was using rivetsjs with backbone, i can also get advantage of events on backbone view
// backbone view
events: {
'click #linkid': 'linkclicked'
},
linkclicked: function (e){
console.log(this.model.get("Name"));
},
<td><a id="linkid" href="#">{ item:Name }</a></td>
I'm trying to call a function for an html element using jquery. I'm struggling with this has been hours and I can't figure out what is wrong.
I have an answer which I want to mark as solved. A question have multiple answers.
As far as I know the function is being fired the number of answers I have in the question. If I have two answers, the function will run twice and so on.
$(document).ready(function () {
$(".accepted.ans").on('click', function (e) {
e.preventDefault();
var parent = $(this).closest('.accept');
console.log(parent);
var current = $(this);
console.log(current);
var url = parent.data('url');
var qid = parent.data('question');
var aid = parent.data('answer');
$.get(url + '?question=' + qid + '&answer=' + aid, function (data) {
console.log("Reading...");
data = $.parseJSON(data);
console.log(aid);
console.log(e);
setAcceptedStatus(current, data.result);
});
});
});
function setAcceptedStatus(object, status) {
if (status === true) {
object.addClass('active');
}
}
This is my jQuery function. The one that I want to be called exactly once for each answer when I press the accept ans div element, which is:
<blockquote class="accept-answer text-right {if !$isMine} hidden{/if}" >
<div class="accept"
title="Accept this answer"
data-url="{url('controller/api/questions/mark_as_solved')}"
data-refresh="{url('controller/api/questions/refresh_accepted_answers')}"
data-answer="{$answer['answerid']}"
data-question="{$question['publicationid']}">
<div class="accepted ans"
id="{$answer['answerid']}"
title="Unnacept this answer">
</div>
</div>
</blockquote>
I thought this was happening because everytime I want to call that function , since I have $(".accepted.ans") it will apply to all the accepted ans it can find on the document. So I thought in adding an id to the class, something like:
id="{$answer['answerid']}"
but as I read somewhere here, it is supposed to work without that.
I really don't know why it triggers more than once, I did all kind of debug, checked the html structure and everything seems flawless.
Any kind soul got an idea in what is wrong?
Regards
I suspect that somehow the $(document).ready() function is executed as many times as your answers, so the click event handler is binded the same number of times for each answer. Perhaps it's the template that does that?
If that's indeed the problem and you can't find a solution for the template, try changing
$(".accepted.ans").on('click', function (e) {
to
$(".accepted.ans").off('click').on('click', function (e) {
to unbind all click event handlers but the last one.
try changing .closest(... to .parent(). Snippet below...
$(document).ready(function() {
$(".accepted.ans").on('click', function(e) {
e.preventDefault();
var current = $(this),
parent = current.parent();
var url = parent.data('url');
var qid = parent.data('question');
var aid = parent.data('answer');
console.log(aid);
$.get(url + '?question=' + qid + '&answer=' + aid, function(data) {
console.log("Reading...");
data = $.parseJSON(data);
console.log(aid);
console.log(e);
setAcceptedStatus(current, data.result);
});
});
});
function setAcceptedStatus(object, status) {
if (status === true) {
object.addClass('active');
}
}
.ans {
position: static;
width: 90%;
height: 5em;
background: #f90;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<blockquote class="accept-answer text-right {if !$isMine} hidden{/if}">
<div class="accept" title="Accept this answer" data-url="{url('controller/api/questions/mark_as_solved')}" data-refresh="{url('controller/api/questions/refresh_accepted_answers')}" data-answer="one" data-question="{$question['publicationid']}">
<div class="accepted ans" id="{$answer['answerid']}" title="Unnacept this answer">
</div>
</div>
</blockquote>
<blockquote class="accept-answer text-right {if !$isMine} hidden{/if}">
<div class="accept" title="Accept this answer" data-url="{url('controller/api/questions/mark_as_solved')}" data-refresh="{url('controller/api/questions/refresh_accepted_answers')}" data-answer="two" data-question="{$question['publicationid']}">
<div class="accepted ans" id="{$answer['answerid']}" title="Unnacept this answer">
</div>
</div>
</blockquote>
<blockquote class="accept-answer text-right {if !$isMine} hidden{/if}">
<div class="accept" title="Accept this answer" data-url="{url('controller/api/questions/mark_as_solved')}" data-refresh="{url('controller/api/questions/refresh_accepted_answers')}" data-answer="three" data-question="{$question['publicationid']}">
<div class="accepted ans" id="{$answer['answerid']}" title="Unnacept this answer">
</div>
</div>
</blockquote>
Using jQuery, you need to prevent it's function listened by other event attached to its parental tags. So try the stopPropagation(); instead.
$(document).ready(function () {
$(".accepted.ans").on('click', function (e) {
e.stopPropagation();
e.preventDefault();
var parent = $(this).closest('.accept');
console.log(parent);
var current = $(this);
console.log(current);
var url = parent.data('url');
var qid = parent.data('question');
var aid = parent.data('answer');
$.get(url + '?question=' + qid + '&answer=' + aid, function (data) {
console.log("Reading...");
data = $.parseJSON(data);
console.log(aid);
console.log(e);
setAcceptedStatus(current, data.result);
});
});
});
function setAcceptedStatus(object, status) {
if (status === true) {
object.addClass('active');
}
}
I am new to knockout. For my problem, I am trying to make it so that for each project, there is a button and textarea. The textarea will be hidden upon page load. If I click the button, it will show the textarea (toggle). Currently, if I click the button, ALL textareas on the page will show, rather than just the corresponding textarea.
I'm hoping the fix for this isn't too dramatic and involving a complete reworking of my code as by some magic, every other functionality has been working thus far. I added the {attr id: guid} (guid is a unique identifier of a project retrieved from the database) statement in an attempt to establish a unique ID so that the right controls were triggered...although that did not work.
Sorry I do not have a working jfiddle to show the issue... I tried to create one but it does not demonstrate the issue.
JS:
//if a cookie exists, extract the data and bind the page with cookie data
if (getCookie('filterCookie')) {
filterCookie = getCookie('filterCookie');
var cookieArray = filterCookie.split(",");
console.log(cookieArray);
$(function () {
var checkboxes = new Array();
for (var i = 0; i < cookieArray.length; i++) {
console.log(i + cookieArray[i]);
checkboxes.push(getCheckboxByValue(cookieArray[i]));
//checkboxes.push(document.querySelectorAll('input[value="' + cookieArray[i] + '"]'));
console.log(checkboxes);
checkboxes[i].checked = true;
}
})
filterCookie = getCookie('filterResultsCookie');
cookieArray = filterCookie.split(",");
filterCookieObj = {};
filterCookieObj.action = "updateProjects";
filterCookieObj.list = cookieArray;
$.ajax("/api/project/", {
type: "POST",
data: JSON.stringify(filterCookieObj)
}).done(function (response) {
proj = response;
ko.cleanNode(c2[0]);
c2.html(original);
ko.applyBindings(new ProjectViewModel(proj), c2[0]);
});
}
//if the cookie doesn't exist, just bind the page
else {
$.ajax("/api/project/", {
type: "POST",
data: JSON.stringify({
action: "getProjects"
})
}).done(function (response) {
proj = response;
ko.cleanNode(c2[0]);
c2.html(original);
ko.applyBindings(new ProjectViewModel(proj), c2[0]);
});
}
View Model:
function ProjectViewModel(proj) {
//console.log(proj);
var self = this;
self.projects = ko.observableArray(proj);
self.show = ko.observable(false);
self.toggleTextArea = function () {
self.show(!self.show());
};
};
HTML:
<!-- ko foreach: projects -->
<div id="eachOppyProject" style="border-bottom: 1px solid #eee;">
<table>
<tbody>
<tr>
<td><a data-bind="attr: { href: '/tools/oppy/' + guid }" style="font-size: 25px;"><span class="link" data-bind=" value: guid, text: name"></span></a></td>
</tr>
<tr data-bind="text: projectDescription"></tr>
<%-- <tr data-bind="text: guid"></tr>--%>
</tbody>
</table>
<span class="forminputtitle">Have you done project this before?</span> <input type="button" value="Yes" data-bind="click: $parent.toggleTextArea" class="btnOppy"/>
<textarea placeholder="Tell us a little of what you've done." data-bind="visible: $parent.show, attr: {'id': guid }" class="form-control newSessionAnalyst" style="height:75px; " /><br />
<span> <input type="checkbox" name="oppyDoProjectAgain" style="padding-top:10px; padding-right:20px;">I'm thinking about doing this again. </span>
<br />
</div><br />
<!-- /ko -->
Spencer:
function ProjectViewModel(proj) {
//console.log(proj);
var self = this;
self.projects = ko.observableArray(proj);
self.projects().forEach(function() { //also tried proj.forEach(function())
self.projects().showComments = ko.observable(false);
self.projects().toggleComments = function () {
self.showComments(!self.showComments());
};
})
};
It's weird that
data-bind="visible: show"
doesn't provide any binding error because context of binding inside ko foreach: project is project not the ProjectViewModel.
Anyway, this solution should solve your problem:
function ViewModel() {
var self = this;
var wrappedProjects = proj.map(function(p) {
return new Project(p);
});
self.projects = ko.observableArray(wrappedProjects);
}
function Project(proj) {
var self = proj;
self.show = ko.observable(false);
self.toggleTextArea = function () {
self.show(!self.show());
}
return self;
}
The problem is that the show observable needs to be defined in the projects array. Currently all the textareas are looking at the same observable. This means you'll have to move the function showTextArea into the projects array as well.
Also you may want to consider renaming your function or getting rid of it entirely. Function names which imply they drive a change directly to the view fly in the face of the MVVM pattern. I'd recommend a name like "toggleComments" as it doesn't reference a view control.
EDIT:
As an example:
function ProjectViewModel(proj) {
//console.log(proj);
var self = this;
self.projects = ko.observableArray(proj);
foreach(var project in self.projects()) {
project.showComments = ko.observable(false);
project.toggleComments = function () {
self.showComments(!self.showComments());
};
}
};
There is probably a much cleaner way to implement this in your project I just wanted to demonstrate my meaning without making a ton of changes to the code you provided.