knockout get observables bound to DOM element - javascript

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>

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/

Subscribe to bindings of an existing DOM element in KnockoutJS

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.

Hiding Text inside of an input element

I have a text input, and I want to hide the text inside, on a given event(I disable the input, when it is not needed). I would like to display the hidden text, when the given event is reversed.
I know I can store the value and retrieve as needed. I'd like to avoid moving data, since this is a purely cosmetic operation.
Can the input text be hidden, or is manipulating the data in the input the only way? I would like the simplest solution.y?
I can use pure JS and jQuery.
I would use "value" attribute of the same input object, since the attribute is the default value. In this case you don't even need any additional variables. The idea of this approach comes from the difference between properties and attributes. It means that if you change value property of the object, the attribute value remains the same as it was before.
var input = document.querySelector('input');
function hide() {
input.value = "";
}
function show() {
input.value = input.getAttribute('value');
}
<input type="text" value="Some text">
<button onclick="hide()">Hide</button>
<button onclick="show()">Show</button>
An example on how to store the value of an input element inside the dataset of the element.
and show/hide it on hover.
var hello = document.getElementById('hello');
hello.dataset.value = hello.value;
hello.value = '';
hello.addEventListener('mouseover', function() {
hello.value = hello.dataset.value;
});
hello.addEventListener('mouseout', function() {
hello.value = '';
});
<input id="hello" value="Hello World" />

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.

Pure Javascript target label

I have some checkboxes and I want the text of the label to change when the checkbox is selected:
JSFIDDLE
var listener = function() {
document.addEventListener('change', function(e) {
if (e.target.checked) {
e.target.label.className = "option-selected";
}
}
}
}
HTML if you are interested:
<input id="0A" class="individual-checkbox" type="checkbox" value="A">
<label for="0A">A</label>
<br>
<input id="0B" class="individual-checkbox" type="checkbox" value="B">
<label for="0B">B</label>
Obviously, target.label doesn't work. How do I access the label of the target and give it a CSS class (purely in JavaScript)?
To make your jsFiddle work, you have to change the following:
Fix the syntax error (missing a closing paren for the addListener() function call and you have an extra closing brace.
Actually call the listener() function to make it run.
Target the actual label, not the checkbox with your class
There are several different ways to target the label. The simplest would be to enclose the input inside the label and then just use .parentNode to get the label from the checkbox.
HTML:
<label for="0A">
<input id="0A" class="individual-checkbox" type="checkbox" value="A">
A</label>
<br>
<label for="0B">
<input id="0B" class="individual-checkbox" type="checkbox" value="B">
B</label>
code:
var listener = function() {
document.addEventListener('change', function(e) {
if (e.target.checked) {
e.target.parentNode.className = "option-selected";
}
});
}
listener();
Working jsFiddle: http://jsfiddle.net/jfriend00/s2A7W/
If you don't want to change your HTML, then you just need to find the label element that is right after your input element.
You can do that like this:
var listener = function() {
document.addEventListener('change', function(e) {
var label;
if (e.target.checked) {
label = next(e.target, "label");
if (label) {
label.className = "option-selected";
}
}
});
}
listener();
function next(src, tag) {
tag = tag.toUpperCase();
while (src && src.tagName !== tag) {
src = src.nextSibling;
}
return src;
}
Working jsFiddle: http://jsfiddle.net/jfriend00/3wQKa/
FYI, you should probably also restrict the action of your listener to only when a checkbox is actually the target or an element with a particular classname or some other distinguishing feature that makes sure it's a checkbox you want targeted by this code. You are probably safe with e.target.checked, but I don't like the fact that this event listener responds to all propagated change events in the entire page.
Assuming there is only one label element associated with the element:
e.target.labels[0].className = "option-selected";
This is an HTML5 property though, I don't know how well it is supported in older browsers. Source: MDN.
Alternatively, if IE8 support is enough for you, you can explicitly search for it with document.querySelector:
var label = document.querySelector('[for=' + e.target.name + ']');
This only works if you give the input elements name attributes (which you really want to do, otherwise the labels are not properly connected to the input elements).
And finally, if the label always comes after the input, you can traverse the DOM:
var label = e.target.nextSibling;
while (label.nodeName !== 'LABEL') {
label = label.nextSibling;
}
If you'd restructure your HTML so that the input element is a child of the label element:
<label for="0A">
<input id="0A" class="individual-checkbox" type="checkbox" value="A">
A
</label>
then you could simply use e.target.parentNode to get the label. Putting the input element inside the label also connects the label to the input.
this will do the trick:
function addListener(elem) {
// remember: checkboxes never change trair value
elem.addEventListener('click', function () {
var ls = document.getElementsByTagName('label');
for (var l = 0, ll = ls.length, lbl; l < ll; ++l) {
lbl = ls[l];
if ( this.id == lbl.getAttribute("for") )
{
lbl.className = ( this.checked ? "option-selected" : "" );
}
}
});
}
see http://jsfiddle.net/fGSCH/10/
be aware that addListener() might not work in every browser. but since you used it in the example i will use it too.

Categories