Knockout visible binding not working - javascript

I've got a very simple View Model:
var ViewModel = function() {
this.showRow = ko.observable(false);
this.toggleVisibility = function() {
if(this.showRow == true){
this.showRow = false;
}
else{
this.showRow = true;
}
alert('showRow is now '+this.showRow); //only here for testing
};
};
with equally simple markup:
Toggle
<br />
<table>
<tr data-bind="visible: showRow">Some Text</tr>
</table>
My problem is, that when the link is clicked, the alert box shows (displaying the correct value - true/false)
However, the visible binding on the tr element doesn't seem to work - either initially (the row should be invisible on load) nor when the value of showRow toggles.
jsFiddle of above- http://jsfiddle.net/alexjamesbrown/FgVxY/3/

You need to modify your html as follows:
<table>
<tr data-bind="visible: showRow"><td>Some Text</td></tr>
</table>
And JavaScript as follows:
var ViewModel = function() {
var self = this;
self.showRow = ko.observable(false);
self.toggleVisibility = function() {
self.showRow(!self.showRow());
alert('showRow is now ' + self.showRow());
};
};
ko.applyBindings(new ViewModel());
Syntax to set the value to observable property is: self.showRow(value);
If need to have tags inside of tags.
I've also modified your fiddle to simplify the javascript and follow newer code practices with regard to "this". See http://jsfiddle.net/FgVxY/4/

Related

ng-change doesn't fire again after I change it's value

I have this in my HTML. Ignore any inline style, I'm testing,
<label class="toggle" style="float: right;">
<input id="check" type="checkbox" ng-model="check" ng-change="funCheck(check)">
<div class="track">
<div class="handle"></div>
</div>
</label>
<div ng-init="fromClock='01:00'; toClock='03:30';">
<clock-editor from="fromClock" to="toClock"
on-change="fromClock = from; toClock = to; funClock(from, to);">
</clock-editor>
<strong>{{fromClock}}</strong>
<strong>{{toClock}}</strong>
</div>
That's a toggle radio button and a clock.
Then I have these two functions in my controller:
$scope.funCheck = function(check) {
alert(check);
};
$scope.funClock = function(f_from, f_to) {
console.log(f_from + "---" +f_to)
$scope.check = false;
}
};
When the toggle is turned on I send the time from the clock somewhere. This works alright. However, what I want to do is uncheck the toggle if the time was changed.
I can do that with document.getElementById('check').checked = false; and the toggle moves back, but the ng-change on that radio won't fire again until I double check it. Like the value didn't change even if I can see how it's turned off visually.
ng-model does not work on strong element and there is no ng-change event for strong element. So basically you can achieve this by using two watch variables like this, Hope this will help you.
$watch(function(){
return $scope.fromClock;
}, function() {
$scope.funCheck();
})
$watch(function(){
return $scope.toClock;
}, function() {
$scope.funCheck();
})
The whole point of using angular is the fact that you shouldn't have to edit the DOM manually like you're doing.
Change this part of your code which unchecks the checkbox
$scope.funClock = function(f_from, f_to) {
if (document.getElementById('check').checked) {
document.getElementById('check').checked = false;
}
};
to
$scope.funClock = function(f_from, f_to) {
if($scope.check){
$scope.check = false;
}
};
Also, you shouldn't care about checking if it's already checked or not as if you set the checked to false and it's already false there will be no change so just remove the if statement completely.
Edit
Seems like ng-change will only fire if there is a change on the input itself and not if that change has happened programmatically, so there are two ways to do this.
Call the change function inside of the funClock.
This would be the code for that
$scope.funClock = function(f_from, f_to) {
if($scope.check){
$scope.check = false;
funCheck($scope.check);
}
};
Add a watch for check.
Or the code for the watch
$scope.$watch('check', function(newValue, oldValue) {
if (newValue != oldValue) {
funCheck(newValue);
}
});
In angularjs , we cann't directly change elements value. we need to
use $compile . First include it in controller and then make use of
it like wise -
var list = '<input id="check" type="checkbox" ng-model="check" checked ng-change="funCheck(check)">';
var selctr = $("#selector");
var ele = angular.element(list);
compiled = $compile(ele);
selctr.html(ele);
compiled($scope);

KnockOutJS Show Hide elements on dropdown selection change

Hi I am trying to get selected value of dropdown in Knockout js so that I can hide/ show other elements based on selection. Below is what I have tried.
What is happening that I am able to get right value on button click but not on dropdown selection change.
Below is my code. The button gives right value, but dropdown selection change event gives previous value & not the selected one.
JS
function ViewModel() {
var self = this;
self.optionValues= ko.observableArray(["Test1", "Test2", "Test3"]);
self.selectedValue = ko.observable();
self.save = function() {
alert(self.selectedValue());
}
}
ko.applyBindings(new ViewModel());
HTML
<select data-bind="event:{ change: save},options: optionValues, value: selectedValue"></select>
<button data-bind="click: save">Save</button>
Instead of the change event binding binding, you should subscribe directly on your selectedValue observable, and call your logic from there:
function ViewModel() {
var self = this;
self.optionValues = ko.observableArray(["Test1", "Test2", "Test3"]);
self.selectedValue = ko.observable();
self.selectedValue.subscribe(function(newValue) {
self.save();
});
self.save = function() {
alert(self.selectedValue());
}
}
ko.applyBindings(new ViewModel());
Html:
<select data-bind="options: optionValues, value: selectedValue"></select>
<button data-bind="click: save">Save</button>
Demo JSFiddle.

Unable to attach event listeners to dynamically created elements in IE8

OVERVIEW:
When I click a button i want to
insert a new row at the end of a table
copy the cells and contents from the first row of the table
give unique ids to the elements within the cells
assign a focus event listener to all inputs in the page.
PROBLEM:
The event handlers are not firing on the correct elements in IE8. For example if I focus on the last input in the table, the first input gets highlighted.
CLARIFICATION:
This works in IE10, Chrome.
Does not work in IE8 which is my target browser.
I know of ways
to get around this.My aim is NOT to find a workaround but to
understand what my mistake is, in the given code.
The example code is just a quick simplified version of the problem. I am not asking for code optimization thats not relevant to the question.
Change event does not work too.
CODE:
HTML:
<table width="200" border="1" id="myTable">
<tr>
<td>
<input type='text' id='row0col0' name='row0col0'>
</td>
</tr>
</table>
<button id="addRow">Add Row</button>
JS:
function addFocusListener() {
$("input").unbind();
$("input").each(function () {
var $this = $(this);
$this.focus(function () {
var newThis = $(this);
newThis.css('background-color', 'red');
});
});
}
function addRowWithIncrementedIDs() {
var table = document.getElementById("myTable");
var newRow = table.insertRow(-1);
var row = table.rows[0];
var rowNum = newRow.rowIndex;
for (var d = 0; d < row.cells.length; d++) {
var oldCell = row.cells[d];
newCell = oldCell.cloneNode(true);
newRow.appendChild(newCell);
for (var c = 0; c < newCell.childNodes.length; c++) {
var currElement = newCell.childNodes[c];
var id = "row" + rowNum + "col" + d;
$(currElement).attr('name', id);
$(currElement).attr('id', id);
}
}
}
$(document).ready(function () {
$("#addRow").click(function () {
addRowWithIncrementedIDs();
addFocusListener();
});
});
OTHER APPROACHES THAT WORK:
changing from jQuery binding to regular JS binding. I.e from
$this.focus(function () {....});
to
this.onfocus =function () {....};
Attaching the event handler as they are rendered.
FIDDLE:
http://jsfiddle.net/sajjansarkar/GJvvu/
RELATED LINKS IN SO:
jQuery event listener doesn't work in IE 7/8
EDIT
Sorry, I just noticed your comment that you want to understand the error in your code.
I can quickly tell you one error, and that is mixing jQuery and native DOM methods. If you have dedicated yourself to using a very powerful library, then use all of it's features, not just the ones you understand.
The below code uses event delegation (to fix your focusing problem) and jQuery methods to more simply add a row to the table than with native methods.
If you're going to use jQuery, then you might as well use it all the way:
var t = $('#myTable');
$(document)
.on('focus','#myTable input',function() {
$(this).css('background','red')
})
.on('click','#addRow',function() {
//create a new row
var
newIndex,
r = t.find('tr').eq(0).clone();
//append it to the table
r.appendTo(t);
//update newIndex - use it for later
newIndex = r.index();
//update the name/id of each of the inputs in the new row
r.find('input').each(function() {
var
el = $(this),
id = 'row'+newIndex+'col'+el.closest('td').index();
el.attr('id',id).attr('name',name);
});
});
http://jsfiddle.net/GJvvu/1/
You don't need to loop through your inputs and bind a focus handler to each of them, jQuery automatically collects all DOM elements that match the selector and performs it's focus API function on each of them:
Change this:
function addFocusListener() {
$("input").unbind();
$("input").each(function () {
var $this = $(this);
$this.focus(function () {
var newThis = $(this);
newThis.css('background-color', 'red');
});
});
}
To this
function addFocusListener() {
$('input')
.unbind()
.focus(function(){
$(this).css('background-color','red');
});
}
$("#addRow").live("click", function(){
addRowWithIncrementedIDs();
addFocusListener();
});
Try out above code... this should work..

Javascript modify parameters of an element's event function

I'm wondering if there is a more elegant means of modifying the parameter of an onclick event. I have a table that I am dynamically adding/removing elements from and I re-index the rows. Each row has a delete link that has the row's index (and a duplicate link) that needs to update its parameter to match the modified row id.
Currently my code looks like (simplified)
<a onclick="delRow(1)">delete</a>
and the javascript:
...
html = element.innerHTML;
html = html.replace(/dupRow(\\d+)/g, "dupRow(" + newIndex + ")");
html = html.replace(/delRow(\\d+)/g, "delRow(" + newIndex + ")");
element.innerHTML = html
and I would like it to become something along the lines of
if (element.onclick != null) {
element.onclick.params[0] = newIndex;
}
Any such way of accomplishing this? I also have jQuery if this helps.
Updates:
So thanks to the glorious help of #rich.okelly I have solved my issue
<script>
...
var newRow = '\
<tr>\
<td class="index" col="0">0</td>\
<td>this is content...</td>\
<td>Del</td>\
</tr>';
// re-index table indices in a non-efficient manner
function reIndexTable() {
$("#rpc-builder-table").find('.index').each(function (i) {
$(this).html(i)
})
}
// add row
function addRow() {
for (i = 0; i < $('#addRowCount').attr("value"); i++) {
$("#rpc-builder-table").append(newRow);
}
reIndexTable();
}
$(document).ready(function () {
// add row button
$('#addRowsButton').on('click', function () {
addRow();
});
// delete row
$('#rpc-builder-table').on('click', 'td a[row-delete="true"]', function () {
$(this).closest('tr').remove();
reIndexTable();
});
...
}
</script>
...
<div>
<label>Rows to add: </label>
<input id="addRowCount" value="1" size="2" />
<button id="addRowsButton">Add Row(s)</button>
</div>
<div><table id="rpc-builder-table"><tbody>
<tr>
<th>Idx </th>
<th>Some content (1)</td>
</tr>
</tbody></table></div>
...
I used the .on() function instead of the suggested .delegate() function since it is deprecated. Solution works well - hope it helps someone :)
If you change your html to something similar to:
<tr>
<td>
delete
</td>
</tr>
Then your javascript can be something like:
$('td a[data-delete="true"]').on('click', function() {
$(this).closest('tr').remove();
});
Update
If rows are added dynamically to a pre-exising table (table is interchangeable for any parent element), you can use the delegate method like so:
$('table').delegate('td a[data-delete="true"]', 'click', function() {
$(this).closest('tr').remove();
});
Instead of inline handlers, use event delegation to attach event handlers
$("#tableID").delegate("a", "click", delRow);
$("#tableID").on("click", "a", delRow); //jQuery 1.7
Inside the handler,
var row = $(this).closest("tr").index(); //Get the index of the parent row
Inline handlers get parsed into a function:
function onclick() {
delRow(1);
}
so changing them is difficult. Your example rewrites the entire row with the new parameter, which is bad practice.
The most brain dead solution is getting rid of the parameters and setting a variable isntead.
var row_to_dup = 42;
$("#a_row_dupper").bind('click', function (){
dupItem(row_to_dup);
});
//changing the row to dup
row_to_dup = 17;

How to restore Textbox Data

I have a small requirement,
We have restore the textbox data that was cleared previously.
Below is my HTMl code
<table>
<tr><td><input type="textbox"></td></tr>
<tr><td><input type="checkbox"></td></tr>
</table>
Here is my JQuery Code
$('TABLE TR TD').find(':checkbox').change(function()
{
if($(this).prop('checked'))
{
$(this).parents('TR').siblings('TR').find('input').val("")
}
if(!$(this).prop('checked'))
{
$(this).parents('TR').siblings('TR').find('input').val(?)
}
});
My Requirement is to clear the textbox content if checkbox is checked. And if i deselect it the textbox should be restored with previous data.
Please someone help me.
Use a global variable to store the previous data -
var prevData;
then modify your code this way -
$('TABLE TR TD').find(':checkbox').change(function()
{
if($(this).prop('checked'))
{
var $element = $(this).parents('TR').siblings('TR').find('input')
prevData = $element.val();
$element.val("");
}
else
{
$(this).parents('TR').siblings('TR').find('input').val(prevData);
}
});
When the checkbox is being checked, before clearing the value, store it using the jQuery .data() API.
<table>
<tr><td><input type="text"></td></tr>
<tr><td><input type="checkbox"></td></tr>
</table>
$('input:checkbox').change(function() {
var input = $(this).closest('table').find('input[type="text"]');
if ($(this).prop('checked')) {
input.data('text', input.val());
input.val('');
} else {
input.val(input.data('text'));
}
});
A demo which works if there were multiple pairs, so long as they exist in separate <table> parents. You could change the finder to get the previous sibling if that were not the case. This uses no global variables which are not really best practice - How to avoid global variables in JavaScript?.
Edit: Updated demo based on your other question Keydown event is not working properly but this will only for key events and not if someone pastes text into the <input>.
I'd suggest something a little less reliant on the mark-up remaining the same (though it does require that the checkbox follows the text input):
var prevData, textInputIndex;
$('input:checkbox').change(
function(){
thisIndex = ($(this).index('table input') - 1);
textInput = $('table input').eq(thisIndex);
if ($(this).is(':checked')) {
prevData = $(textInput).eq(thisIndex).val();
$(textInput).eq(thisIndex).val('');
}
else {
$(textInput).eq(thisIndex).val(prevData);
}
});
JS Fiddle demo.
Edited to remove the problem of having only one variable to store the text-input value:
var $textInputs = $('table input:text');
var prevData, textInputIndex, affectedTextInputIndex, textInputValues = [];
$('input:checkbox').change(
function(){
affectedTextInputIndex = $(this).index('table input') - 1;
textInputIndex = $('table input').eq(affectedTextInputIndex).index('table input:text');
if ($(this).is(':checked')) {
textInputValues[textInputIndex] = $textInputs.eq(textInputIndex).val();
$textInputs.eq(textInputIndex).val('');
}
else {
$textInputs.eq(textInputIndex).val(textInputValues[textInputIndex]);
}
});
JS Fiddle demo.
Edited to remove the explicit requirement that the input elements be contained in a table:
var $textInputs = $('input:text');
var prevData, textInputIndex, affectedTextInputIndex, textInputValues = [];
$('input:checkbox').change(
function(){
affectedTextInputIndex = $(this).index('input') - 1;
textInputIndex = $('ul input').eq(affectedTextInputIndex).index('input:text');
if ($(this).is(':checked')) {
textInputValues[textInputIndex] = $textInputs.eq(textInputIndex).val();
$textInputs.eq(textInputIndex).val('');
}
else {
$textInputs.eq(textInputIndex).val(textInputValues[textInputIndex]);
}
});
JS Fiddle demo.
References:
:checkbox selector.
change().
is().
:checked selector.
index().
val().

Categories