Subscribe to bindings of an existing DOM element in KnockoutJS - javascript

I need to subscribe to an existing binding of a DOM element. As an example, I have the following input element:
<div id="MyDiv">
<input id="MyInput" data-bind="enable: isEnabled()" />
</div>
Now, assuming I only have access to the DOM element, I need to do something like this:
var inputElement = $("#MyInput");
var bindings = ko.utils.getBindings(inputElement); // getBindings does not exist
var enableBinding = bindings["enable"];
if (enableBinding != undefined) {
enableBinding.subscribe(function (value) {
if (value == false)
$("#MyDiv").addClass("disabled");
else
$("#MyDiv").removeClass("disabled");
})
}
Is there a way to do this?
Update: I've extended the sample so that you see my use case for this: The div here is automatically generated by a preprocessor and needs the disabled class on it when the input is disabled. It does not work if the attribute is only changed on the input element. The addition/removal must be transparent...

Short answer: Don't do this. There is a reason that getBindings is not a particularly visible function in the Knockout toolkit.
Long answer: You can, through a bit of indirection, get at the original binding.
HTML:
<div id="MyDiv">
<input id="MyInput" data-bind="enable: isEnabled" />
</div>
<input type="checkbox" data-bind="checked: isEnabled" />
JS:
var viewModel = function() {
self.isEnabled = ko.observable(true);
}
ko.applyBindings(new viewModel());
var input = $('#MyInput')[0];
function getBinding(element, name) {
var bindings = ko.bindingProvider.instance.getBindings(input, ko.contextFor(input));
return bindings.hasOwnProperty(name) ? bindings[name] : null;
}
var binding = getBinding(input, 'enable');
binding.subscribe(function(value) {
if (value == false)
$("#MyDiv").addClass("disabled");
else
$("#MyDiv").removeClass("disabled");
});
Working JSFiddle
EDIT: Found a shorter way
Again, if there is any way you can convince your preprocessor to add a CSS observable, do so. Mucking about with bindings in this manner relies on the particular quirks of Knockout 3.3.0's internal implementation, which can change in future releases.

Checkout the answer provided here.
In short, you can use
var viewModel = ko.dataFor(domElement);
to get the viewmodel that is bound to that DOM element. You can then, subscribe to any observables attached to that viewmodel.

Related

Get multiple attributes with .attr() to get both "id" and ":checked" from element using "this" | jQuery

my question is very simple and doesn't seem to be around as often as setting or applying value in multiple cases.
Using $(this) how can I achieve to get multiple attributes from a single element using as the title informs simply .attr().
$(this).attr('id', "checked") // Pseudo code
For the use to be stored in an array or variable for example. Thank you.
The syntax you used will set the value to the attribute. Hence, you can use
something like ['id', 'checked'].map(a => $el.attr(a)) as mentioned by lxe in the comment or you can create a jQuery wrapper attrs as below.
To get the value of checked property, you can use prop as below instead of attr.
Working snippet:
$(document).ready(function() {
$.fn.attrs = function() {
return Object.keys(arguments).map(k => $(this).prop(arguments[k]));
};
$('input[type="checkbox"]').click(function() {
globalCallback($(this).attrs('id', 'checked'));
});
});
function globalCallback(attrs) {
console.log(attrs);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Click the checkbox and see the console:
<br/>
<input type="checkbox" id="checkbox1" /> Checkbox 1
<input type="checkbox" id="checkbox2" /> Checkbox 2
<input type="checkbox" id="checkbox3" /> Checkbox 3
You can't do this, according to jQuery documentation:
Get the value of an attribute for the first element in the set of matched elements.
Moreover your $(this).attr('id', "checked") code will set to the id attribute the checked value, since attr can be used to set values with exactly such syntax.
However you can create a helper methods like the one mentioned by lxe
You can get the attributes using Array#reduce on the Element.attributes to create an object of attributes:
// get all elements attributes, and convert them to array
var attributes = [].slice.call($('#v-box').get(0).attributes, 0);
// reduce the array to key value pairs
var object = attributes.reduce(function(r, attr) {
r[attr.name] = attr.value;
return r;
}, {});
console.log(object);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input id="v-box" type="checkbox" checked="checked">
You can easily extend the jQuery function to accommodate what your looking for.
If you aren't interested in extending you could create a new function/ plugin as well
Here's an example:
(function($)
{
var oldAttr = $.fn.attr;
$.fn.attr = function() {
var args= arguments[0];
if(Object.prototype.toString.call(args)=="[object Array]") {
return [args.map((e=> oldAttr.apply(this, [e])))];
//if you want to get a string you could just try `return args.map((e=> oldAttr.apply(this, [e])))`
}
return oldAttr.apply(this, arguments);
};
})(jQuery);
Demo: https://jsfiddle.net/eztukwj9/

Dynamically Inserting a Div Breaks KnockoutJS DataBinding

I have created a KnockoutJS application, and I also must use some third-party stuff with it. My third-party stuff uses vanilla Javascript to insert a div into the markup rendered by Knockout. Once this happens, Knockout stops working.
Here's a fiddle that encapsulates the problem: http://jsfiddle.net/p5o8842w/1/
HTML:
<div style="margin-bottom:50px;">
<button onclick='BindVM();' >Bind</button>
<button onclick='ThrowWrench();'>Throw a Wrench Into It</button>
</div>
<div id="ViewModel" data-bind="template: 'Template'">
</div>
<script type="text/html" id='Template'>
<div style="margin-bottom:20px;">
<span data-bind="text: Name"></span>
</div>
<div id="infoDiv">
<input type="text" data-bind="text: Name, value: Name, valueUpdate: 'keyup'" />
</div>
</script>
JavaScript:
function BasicVM () {
var self = this;
self.Name = ko.observable('The Name');
self.Title = ko.observable('The Title');
}
function BindVM() {
var vm = new BasicVM();
var element = document.getElementById('ViewModel');
ko.cleanNode(element);
ko.applyBindings(vm, element);
}
function ThrowWrench() {
var element = document.getElementById('infoDiv');
element.innerHTML = "<div class='container'>" + element.innerHTML + '</div>';
}
First, click 'Bind.' Notice that the textbox is bound to the span; change the box, you change the span.
Then, click 'Throw a Wrench Into It.' Now, the textbox is no longer data-bound to the ViewModel, and typing into it doesn't impact the span.
Things I can't do:
Take the third-party code and refactor/integrate it into my Knockout stuff.
Run the third-party code before I render with Knockout (which I think would help).
Call ko.applyBindings again after running the third-party code. I can do this, but then Knockout destroys what the third-party code did, so I'd have to run it again, which would cause the same problem again.
Is there any way around this?
Because replacing element.innerHTML it's losing is binding. In order to overcome this. Two method are available:
1- Rebind the new element
2- Else
var element = document.getElementById('infoDiv');
var parent = element.parentNode;
var wrapper = document.createElement('div');
parent.replaceChild(wrapper, element);
wrapper.appendChild(element);
This is updated url: http://jsfiddle.net/p5o8842w/5/

How do I pass a html value into an AngularJS method?

I am trying to pass a value from a html element angularJS method bound to the $scope.
The code is...
<div ng-app ng-controller="miniC">
<input name="someitem" ng-blur="CalculateTotals(this)"/>
</div>
function miniC($scope){
$scope.myAccounts = new Array();
$scope.CalculateTotals = function(amt){
console.log(amt);
}
}
codepane link
But I cannot seem to extract the value from the element. I do not want to bind in this case as I am trying some things out and this was one of the approaches I had....
Angular expressions are all properties of the scope they are evaluated on, so this will just be the current scope.
With this in mind, you have to make this available on the scope. The simplest way is to use ng-model="someValue", then use ng-blur="CalculateTotals(someValue)".
However I also note in your question you explicitly don't want to bind, and this does add some overheads if you don't use anything else, so you would have to create a custom directive instead.
Append ng-model="testVal" to your input field and console.log($scope.testVal); to log your value.
The easiest way is to turn an input into a model's representation with ng-model directive, allowing to access its value directly. But if, for some reasons, you don't want to do this, you still can access this element - via target property of $event variable exposed by ngBlur directive. For example:
HTML:
<div ng-app ng-controller="miniC">
<input name="someitem" ng-blur="AddToTotal($event.target)" />
<button type="button" ng-click="Reset()">Reset</button>
<br />
<span>Totals: {{ CalculateTotals() }}</span>
</div>
JS:
function miniC($scope) {
$scope.myAccounts = [];
$scope.Reset = function() {
this.myAccounts.length = 0;
}
$scope.CalculateTotals = function() {
return this.myAccounts.reduce(function(sum, el) {
return sum + el;
}, 0);
};
$scope.AddToTotal = function(inp) {
var value = inp.value;
this.myAccounts.push(+value);
};
}
Demo. It works somewhat funny, as a new value is added each time focus is moved out of that input.

knockout get observables bound to DOM element

I'm making a readonly binding for knockout.
I've got it working nicely if I apply a binding to each element, as demonstrated in this fiddle: http://jsfiddle.net/paulinfrancis/wDJ9n/
However, I'd like to be able to set the binding to the view's root element, instead of on every form element:
<div data-bind="readonly: isReadonly">
<!-- My form elements here -->
</div>
ko.bindingHandlers.readonlyView = {
init: function(element, valueAccessor){
var isReadOnly = ko.unwrap(ko.unwrap(valueAccessor()));
if (isReadOnly) {
var $elements = $(':text, :radio, :checkbox, :input', $(element));
$elements.each(function(){
var domElement = this;
ko.cleanNode(domElement);
var $domElement = $(domElement);
if ($domElement.is(':text')) {
//I need the observable bound to the textbox text
} else if ($domElement.is(':radio')) {
//I need to determine if the radio btn is checked
} else if ($domElement.is(':checkbox')) {
//I need to determine if the checkbox is checked
} else if($domElement.is(':input')) {
$domElement.prop('disabled', true);
}
})
}
}
}
I'm not sure how to get the viewmodel property names that the elements are bound to, so that I can replace the existing elements with their readonly counterparts of my choice.
I can access the values I need using dataFor or contextFor, but I need to know the bound property names first:
ko.dataFor(domElement)['observableOne']()
ko.contextFor(domElement).$data['observableOne']()
I could of course parse the data-bind attribute on each element, but that seems dirty. Are there other better ways?
Are you sure the Knockout "enable" or "disable" bindings would not work for you?
These could all share the same "readOnly" observable flag.
http://knockoutjs.com/documentation/enable-binding.html
<p>
Your cellphone number:
<input type='text' data-bind="value: cellphoneNumber, enable: hasCellphone" />
</p>
...
var viewModel = {
hasCellphone : ko.observable(false),
cellphoneNumber: ""
};
I think, based on your comment to 7zark7, that what you really want are the if and ifnot bindings:
<label>Had caffeine fix
<img src="checkmark.png" data-bind="if: isReadOnly() && coffeeConsumed()">
<input type="checkbox" data-bind="ifnot: isReadonly, checked: coffeeConsumed">
</label>

How to dodge a DRY issue when calling a function with a constant?

I am still new to Javascript. I need to attach a function to handle events on some of my HTML elements.
I am doing the following:
$('#iinp0').keyup(function(){keyReleased('iinp0');});
$('#iinp1').keyup(function(){keyReleased('iinp1');});
$('#iinp2').keyup(function(){keyReleased('iinp2');});
$('#iinp3').keyup(function(){keyReleased('iinp3');});
$('#iinp4').keyup(function(){keyReleased('iinp4');});
$('#iinp5').keyup(function(){keyReleased('iinp5');});
$('#iinp6').keyup(function(){keyReleased('iinp6');});
$('#iinp7').keyup(function(){keyReleased('iinp7');});
I was hoping I could apply the Don't Repeat Yourself (DRY) principle with the following:
for (i=0;i<=7;i++) {
var tmp = 'iinp' + i;
$('#'+tmp).keyup(function(){keyReleased(tmp);});
}
but keyReleased() is not called with the proper values.
Is there a solution to this issue? I mean is there a simple way to attach my functions having a constant parameter?
Why not simply this:
$('#iinp0, #iinp1, #iinp2, #iinp3, #iinp4, #iinp5, #iinp6, #iinp7').keyup(function()
{
keyReleased(this.id);
});
You could even replace that long selector with an attribute selector:
$('[id^=iinp]').keyup(function()
{
keyReleased(this.id);
});
which will select any element who's id starts with iinp.
Note: This selector is a tad slower than the pure ID selectors - but is much easier to read and maintain (if you could qualify it with a tag selector, it'll be a bit faster).
In your case this would be the best:
$('[id^="iinp"]').keyup(function()
{
keyReleased(this.id);
});
But you may like to hear the reason it doesn't work: it's because JavaScript binds the tmp var to the bigger scope.
The following code works because we are explicitly binding the current value of tmp to the new function being created:
for (i=0;i<=7;i++) {
var tmp = 'iinp' + i;
$("#"+tmp).keyup((function(xtmp){ return function(){keyReleased(xtmp);} })(tmp));
}
Don't use numbered ids.
Instead use a class.
$('.iinp').keyup(function() {
var index = $(this).index('.iinp');
keyReleased('iinp', index);
});
HTML
<input class="input" id="iinp0" />
<input class="input" id="iinp1" />
<input class="input" id="iinp2" />
JS
$(function(){
$('.input').keyup(function() {
keyReleased(this.id.replace('iinp', ''));
});
function keyReleased(key) {
console.log(key)
}
})
Assuming each one of your inputs have the same class, or are the same element type (like input), you can assign them all to the same function using a selector and the on() function, and pass the id of the element to the keyReleased() function:
Example HTML:
<div id="formData">
<input type="text" id="iinp0" \>
<input type="text" id="iinp1" \>
</div>
jQuery JavaScript:
$("#formData").on("keyup", "input", function() {
keyReleased($(this).attr('id'));
});
http://jsfiddle.net/4SKgU/
The rest of these answers will do what you want, but personally I'd go a step further to reduce the amount of anonymous functions being made (although, some approaches will not do that):
Use classes for your input
<input class="keyup" id="iinp01" />
Bind using class and non-anonymous event handler
(function ($) { // closure
$(function () { // on document ready
$("input.keyup").keyup(keyReleased);
});
function keyReleased(e) {
var id = this.id,
$input = $(this);
// Do whatever you want
}
})(jQuery);
Hopefully that will help. If you aren't familiar with closures, learn about them!
If possible, I'd also provide a parent element for context:
<div id="keyup-container"><!-- inputs here --></div>
$("#keyup-container input.keyup")
It will be more efficient (if you are worried about that).

Categories