Scope in knockout binding - javascript

This is some mark-up for a list of answers in a question editor.
var Answers = ko.observableArray(["Answer 1","Answer 2","Answer 3"]);
<!-- ko foreach: Answers -->
<div class="qa-box" data-bind="event: { mousedown: mouseDown, dragend: dragEnd, dragstart: dragStart }">
<div class="qa-body">
<div class="radio">
<label>
<input type="radio" data-bind="attr: { name: 'Q' + $parentContext.$index(), value: $index }, checked: $parent.CorrectAnswer" /><span></span>
Tick the correct answer
</label>
<a href="#" data-bind="click: function(d,e){ $parent.remove(d,e); }">
<i class="fa fa-times"></i>
Remove this answer
</a>
<div class="form-control" contenteditable="true" data-placeholder="Put answer text here" data-bind="ckeditor: $data, attr: { id: 'Q' + $parentContext.$index() + 'A' + $index() }"></div>
</div>
</div>
</div>
<!-- /ko -->
Answers is a ko.observableArray containing strings. The remove link is on the template for each string because that captures which string is to be removed, but the remove operation is defined on the object that owns the list.
Originally I coded it like this:
data-bind="click: $parent.remove"
and remove was called, but unsurprisingly this was the string rather than the parent object. So I rewrote it like so:
data-bind="click: function(){ $parent.remove($data); }"
which failed to change anything. Then I tried this
data-bind="click: function(d,e){ $parent.remove(d,e); }"
which works. Inside the remove method, this is the parent object, and the parameter is the string.
Given that the final form works, why doesn't function(){ $parent.remove($data); change this to the value of $parent inside the call to remove?
On a related note, I tried writing it like this
function(){ $parent.remove.apply($parent, $data); }
but this produced a JS error complaining that something wasn't a function. It didn't say what wasn't a function, and when I looked up apply on mdn I couldn't see anything wrong. What's up with that?
A more elegant solution to this precise problem is available from Anders's answer to How to access $parent or $parents[] in knockout viewmodel click event?
Ignore the accepted answer (same as my solution) and scroll down a little to learn about bind.

Related

Knockout Click event not working

I am working on a weather page for myself (and maybe others in the future) and having an issue with a button that will show and hide weather alerts. You can view the page to see what I'm trying to do. (Sorry, I'm picking on FL, but they have a lot of alerts right now).
Page Source
JS Source
I have my alerts coming into an array and for each item, I need a button that will show and hide the alerts. My page source contains:
<div data-bind="foreach: alertsViewModel.features">
<div class="col-12">
<div class="alert alert-danger" role="alert">
<p>
<strong data-bind="text: properties.headline"></strong>
<button type="button" class="btn btn-link" data-bind="click: $root.showHideAlert">Show</button>
</p>
<div data-bind="attr: {id: properties.id}" style="display: none;">
<p data-bind="lineBreaks: properties.description"></p>
<p><strong>Instructions</strong></p>
<p data-bind="lineBreaks: properties.instruction"></p>
</div>
</div>
</div>
</div>
And my ViewModel looks like:
// ==================
// Alerts View Model
// ==================
var alertsViewModel = {
features: ko.observableArray([]),
hwoUrl: ko.observable(""),
hwoText: ko.observable(""),
showHideAlert: function(data, event){
alert('you clicked');
/*$('#hwo').toggle('slow',function(){
if ($('#showHwo').text() == "Show")
{
$('#showHwo').text("Hide");
}
else
{
$('#showHwo').text("Show");
}
});*/
}
};
ko.applyBindings(weatherViewModel, document.getElementById('weather-alerts'));
I have tried a few different methods and I can't get anything to work. (Thus the commented code and the alert). Which is strange, since I have done this a few times in the past with no issues. I'm sure it's something simple I missed. Any help would be appreciated.
Could it perhaps be because you used the weatherViewModel in your call to ko.applyBindings instead of alertsViewModel?
I think the $root in the button's bindings refers to weatherViewModel since that's the VM applied by ko.
Perhaps try changing the location of the function or simply use alertViewModel instead.

how to send through onclick="function(this)" [duplicate]

This question already has answers here:
passing $(this) from function to function
(3 answers)
Closed 7 years ago.
Okay, so I have got two problems here. First I will show you the html.
<div id="servicesAvailable">
<div>
<input type="text" name="servicesAvailable[]">
<i class="fa fa-plus-circle"></i>
<span>Add another service</span>
</div>
</div>
Okay so i want the user to be able to click the '.fa-plus-circle' class to add another div inside #servicesAvailable.
So i did this:(i know that people will say why dont you write $() instead of jQuery(), but its fine like this).
jQuery('fa-plus-circle').click(function(){
jQuery('#servicesAvailable').append(
'<div>
<input type="text" name="servicesAvailable[]" onclick="addServices(this)">
<i class="fa fa-plus-circle"></i>
<span> Add another service</span>
</div>'
);
jQuery(this).next().hide();
jQuery(this).hide();
});
Ok so with this it should work fine. It actually works the first time i click the class. Then when i click on the fa-plus-circle class on the new appended section it just doesn't do anything. Even if i put console.log('here'); inside the jQuery click function it doesn't log.
So to start of if anyone can point out something i did wrong there then great!.
However i have tried now to change the way i do this to get it to work which leads the the title question 'how to send through onclick="function(this)"'
<div id="servicesAvailable">
<div>
<input type="text" name="servicesAvailable[]">
<i class="fa fa-plus-circle" onclick="addServices(this)"></i>
<span>Add another service</span>
</div>
</div>
function addServices(this)
{
jQuery('#servicesAvailable').append(
'<div>
<input type="text" name="servicesAvailable[]" onclick="addServices(this)">
<i class="fa fa-plus-circle"></i>
<span> Add another service</span>
</div>'
);
this.next().hide();
this.hide();
}
now straight away in the console before i do any thing is has
Uncaught SyntaxError: Unexpected token this
function addServices(this)
Can anyone advise what i am doing wrong in either cases to get this to work please?
Ill admit i'm not 100% which the second way but i'm pretty sure the first attempt should work.
As the error is Syntax Error Uncaught SyntaxError: Unexpected token this.
Your string is not completed on the same line. For multiline strings use \ at the end of line
function addServices(el) {
jQuery('#servicesAvailable').append(
'<div>\
<input type="text" name="servicesAvailable[]" onclick="addServices(this)">\
<i class="fa fa-plus-circle"></i>\
<span> Add another service</span>\
</div>'
);
jQuery(el).hide().next().hide();
}
This is your working code:
$('#servicesAvailable').on("click", ".fa-plus-circle",function () {
$('#servicesAvailable').append('<div>\
<input type="text" name="servicesAvailable[]" onclick="addServices(this)">\
<i class="fa fa-plus-circle">click to add</i>\
<span> Add another service</span>\
</div>');
jQuery(this).next().hide();
jQuery(this).hide();
});
Here is the JSFiddle demo
The event was binded only to the existing element, and not with the dynamically added elements. To bind events with dynamically added elements you need to update
jQuery('fa-plus-circle').click(function(){
to
jQuery("#servicesAvailable").on("click", "fa-plus-circle", function(){
For reference - http://api.jquery.com/on/

How to get the bounded element DOM from an observable object

I need to get the Element DOM from an observable object.
Please look on this example.
I'm clicking on the button "Get Class" and then I want to get valueB DOM element.
Please pay attention that I want valueB DOM element and not buttonA
Example:
HTML
<button id="buttonA" data-bind="event:{click: getClassFromValueB}">Get Class</button>
<input id="valueB" class="Hello" data-bind="value: observables.idNumber"/>
VIEWMODEL
"
"
"
observables : {
idNumber: ko.observable('SomeText');
},
getClassFromValueB : function(child, event){
idNumber_DOM = this.getElementDOM(this.observables.idNumber);
},
getElementDOM : function(observable){
**//WHAT TO DO HERE????**
}
"
"
"
I seeking a solution without jQuery...
$(event.target).closest('#valueB')
UPDATE: The main reason for this question is to to clear a customized attribute in a input when one of the other inputs are change
Example:
<input id="InputA" class="Hello" data-bind="event:{change: clearInputB}"/>
<input id="InputB" class="Hello" data-bind="value: observables.idNumber"/>
<input id="InputC" class="Hello" data-bind="event:{change: clearInputB}"/>
<input id="InputD" class="Hello" data-bind="event:{change: clearInputB}"/>
Not exactly what you're asking for but maybe you can solve the problem in a different way. If you're after a DOM element's class, Knockout provides a css binding. valueB's data-bind statement would look something like this:
<input id="valueB" data-bind="css: observables.valueBClass, value: observables.idNumber"/>
Then you can have another observable named valueBClass where you can set valueB's class as well as retrieve it from other observables.
valueBClass = ko.observable('Hello');
getClassFromValueB = function() {
var theClass = observables.valueBClass();
}
You're on the right track to keep the DOM and the model separate. This is an important concept that is sometimes missed when using Knockout. As a result of keeping them separate, you would not reference any DOM elements anywhere in your Knockout model. You would only reference observables.

Knockout enable binding not working

I can't get the enable binding to work in Knockout JS. With the enabled property set to false, the button is not disabled and I can still click it.
see fiddle
<a class="btn btn-xl btn-primary"
href="#"
role="button"
data-bind="enable: enabled, click: clicked, visible: isVisible">
<i class="icon-only icon-ok bigger-130"></i>
</a>
var ViewModel = function(){
var self = this;
self.enabled = ko.observable(false);
self.isVisible = ko.observable(true);
self.clicked = function(){
alert('You clicked the button');
};
};
$(function(){
var model = new ViewModel();
ko.applyBindings(model);
})
Enable binding does not work with anything you want.
This is useful with form elements like input, select, and textarea
It also works with buttons. Like in my example http://jsfiddle.net/5CbnH/1/
But it does not work with your link. You are using twitter bootstrap and they enable/disable their "buttons" with css classes. So you have to use css binding like this:
data-bind="css: { yourClass: enabled }"
Check what class is responsible in bootstrap for showing your "button" and modify your code accordingly with css binding.
Right:
✅ enable✅ disable
Wrong:
❌ enabled❌ disabled
Make sure you use disable instead of disabled and enable instead of enabled.
<input type="text" data-bind="value: foo, enable: isEditing"/> YES!!
<input type="text" data-bind="value: foo, enabled: isEditing"/> NO!
Easy mistake to make :-)
For people who might find this in a search:
I had a problem getting the enable binding to work as well. My problem was trying to use a complex expression without referencing the observables like functions:
<input type="button" data-bind="enable:AreAllStepsVerified && IsFormEnabled, click:SubmitViewModel"/>
Should have been:
<input type="button" data-bind="enable:AreAllStepsVerified() && IsFormEnabled(), click:SubmitViewModel"/>
See: https://stackoverflow.com/a/15307588/4230970
What Salvador said in his answer.
You must understand that the enabled and disabled binding in knockout work by putting a disabled attribute on the target DOM element. Now if you look at the HTML documentation you'd notice that not all HTML element support this attribute.
Actually only form elements (e.g. <button>) do. <a> does not.
I got it to work by changing the anchor tag to a button, not really sure why this makes it work, but it works nonetheless.
Updated fiddle.
<button class="btn btn-xl btn-primary"
role="button"
data-bind="enable: enabled, click: clicked, visible: isVisible">
<i class="icon-only icon-ok bigger-130"></i>
</button>

ng-click does not fire javascript global function

I'm new to AngularJS, and I've put an ng-click on a radio button generated by
ng-repeat, and the click event refuses to fire. If I use a simple onclick,
that does work though.
This works, and I see the alert:
<div class="span2 left-justify"
ng-repeat="choice in physicalmode_choices">
<input type="radio"
name="physical_layer"
value="{{ choice.private }}"
onclick="alert('foo')"
required
ng-model="$parent.networkoptions.physicalmode" />
<span ng-bind="choice.public"></span>
</div>
But this does not:
<div class="span2 left-justify"
ng-repeat="choice in physicalmode_choices">
<input type="radio"
name="physical_layer"
value="{{ choice.private }}"
ng-click="alert('foo')"
required
ng-model="$parent.networkoptions.physicalmode" />
<span ng-bind="choice.public"></span>
</div>
Can I not do this with an ng-click? Or am I misunderstanding what an "expression" is?
Thanks,
Mike
To clarify DerekR's answer.
When angular sees
ng-click='alert("test")'
it looks for
$scope.alert
which is likely to be undefined.
You need to provide a proxy method on the scope, or possibly even rootscope.
For example:
$rootScope.log = function(variable) {
console.log(variable);
};
$rootScope.alert = function(text) {
alert(text);
};
See: http://deansofer.com/posts/view/14/AngularJs-Tips-and-Tricks-UPDATED#scopemethods
When you call something inside of an ng-click the parsing service evaluates expressions against the scope rather than the global window object.
If you wanted to do an alert inside of an ng-click then could write a method on the scope or parent scope that in turn calls the alert.
I created this jsFiddle to show how it works.
http://jsfiddle.net/tM56a/1/
<li ng-repeat="menu in menus" >
<a ng-click="test(menu)">click me</a>
</li>

Categories