Displaying different <tr> depending on radio button (in Knockout) - javascript

OK, here goes.
Problem 1:
I want to, based on the radio button checked, display a tr-element or not. Threre will be 3 buttons, displaying 'unlocked achievements', 'locked', and 'all' (both locked + unlocked).
The code below shows how it looks when I try to call three different functions, each setting the tr:s visibility to true/false depending on radio button checked. (D.R.Y, I know, but right now I'm just looking for the functionality).
Problem 2:
Making the for-loop run. itemsListForFilter is declared globally, outside the filter function. itemsListForFilter is a copy of an object arrayMap which is initiallized and filled elsewhere in the code. The array contains items with - amongst other things - the boolean "radioCheck", with the default value "true", which I want to check.
When I access itemsListForFilter in the function where the copying takes place it's filled with items but...
When I try to access itemsListForFilter in the filter function it displays as having the value of null. So the copy is "lost" somewhere :)
View / HTML:
<div class="widget-header-container">
<h3 class="widget-header">Achievements</h3>
<div class="wrapper">
<input type="radio" name="appliedFilter" value="all" data-bind="checked: filterAll"/><label for="all">Show all</label>
<input type="radio" name="appliedFilter" value="unlocked" data-bind="checked: filterUnlocked"/><label for="unlocked">Unlocked</label>
<input type="radio" name="appliedFilter" value="locked" data-bind="checked: filterLocked"/><label for="locked">Locked</label>
</div>
<div><div class="widget-header-line-game1"></div><div class="widget-header-line-game2"></div><div class="widget-header-line-shadow"></div></div>
</div>
<div>
<div class="rounded-box" style="padding:15px;padding-top: 0;background-color:#fff;overflow:hidden;">
<table id="game-achievements" class="table table-condensed" style="margin-top:10px;">
<tbody data-bind="foreach: viewGame.achievements()">
<tr data-bind="visible: radioCheck" style="display: none">
Viewmodel / JS:
filterUnlocked: function(){
return filter('unlocked');
},
filterLocked: function(){
return filter('locked');
},
filterAll: function(){
return filter('all');
},
filter: function(x){
for (var item in itemsListForFilter){
if (x === 'locked'){
item.radioCheck = '!achieved';
}
if (x === 'unlocked') {
item.radioCheck = 'achieved';
}
else {item.radioCheck = true;}
}
Observe that the viewmodel is an object and not a function:
var gamesViewModel = {
self: this,
settings: null,
gameId: null,
authorized: false,
(...)
Right now the functions named filterUnlocked, etc (except filter) displays as "unused properties" in the JS file. What should i do to call them from the HTML? Or is there a better way to accomplish what I'm looking for?
Thank you.

You can get the effect you want with less complexity.
First, the radio group. The checked binding in Knockout kind of replaces (or acts like) the name attribute for a radio group: all the radio buttons should share one. The bound variable will be updated to the value of the selected radio button, which will cause others bound to the same variable to de-select. Now you have one variable to look at instead of three.
Instead of doing filtering by hiding rows, it is more usual to have a computed filter the data, and the table just displays it. The computed can get the data from "outside"; it doesn't have to be an observable or part of the viewmodel (although if it's not an observable, you'll need to tell the computed when to update, if the source changes).
Here's a little working example:
var achievements = [{
name: 'The locked one',
locked: true
}, {
name: 'The unlocked one',
locked: false
}];
var vm = {
appliedFilter: ko.observable('all'),
viewGame: {}
};
vm.viewGame.achievements = ko.computed(function() {
var filter = vm.appliedFilter();
if (filter === 'all') {
return achievements;
}
return ko.utils.arrayFilter(achievements, function(item) {
return item.locked === (filter === 'locked');
});
});
ko.applyBindings(vm);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div class="widget-header-container">
<h3 class="widget-header">Achievements</h3>
<div class="wrapper">
<input type="radio" value="all" data-bind="checked: appliedFilter" />
<label for="all">Show all</label>
<input type="radio" value="unlocked" data-bind="checked: appliedFilter" />
<label for="unlocked">Unlocked</label>
<input type="radio" value="locked" data-bind="checked: appliedFilter" />
<label for="locked">Locked</label>
</div>
</div>
<div>
<div class="rounded-box" style="padding:15px;padding-top: 0;background-color:#fff;overflow:hidden;">
<table id="game-achievements" class="table table-condensed" style="margin-top:10px;">
<tbody data-bind="foreach: viewGame.achievements()">
<tr><td data-bind="text: name"></td></tr>
</tbody>
</table>
</div>
</div>

Related

Why is my boolean always true when passing it as value to a radio component?

so I am learning vue and have spent some time going through the documents and haven't seen the answer that solves my question. A lot of this is due to the nomenclature between using the CLI(which I am) and not.
I am trying to make it so that when one radio button is clicked it shows a div and when the other one is clicked it shows the other. Here is what I have.
Template:
<div id="daySelection">
<div class="o-m-day">
<div id="oneDay">
<p>One day?</p><input v-model="selected" type="radio" name="oneDay" id="" class="r-button" value="true">
</div>
<div id="multipleDays">
<p>Multiple days?</p> <input v-model="selected" type="radio" name="multDays" id="" class="r-button" value="false">
</div>
</div>
<!-- the div where the conditional render will be rendered -->
<div>
<!-- multiple days -->
<div v-show="selected" id="ta-multDays">
<textarea rows="10" cols="80" name="multDays" type="text" />
</div>
<!-- one day -->
<div v-show="!selected" id="i-oneDay">
<input type="text" name="r-oneDay">
</div>
</div>
</div>
Here is the script:
export default {
name: 'CreateTournamentForm',
data: function(e) {
return {
selected: Boolean,
}
},
}
above I was getting an error in the console that was saying that data needs to be a function that returns a new instance. I see many people and examples using vue instances differently where it is:
const app = new Vue({
el: '#app',
data: {
selected: true,
}
});
However whenever trying this Vue sends me a warning saying that it needs to be a function.
[Vue warn]: The "data" option should be a function that returns a per-instance value in component definitions.
I am also aware that v-show toggles the display so I have tried both setting the display of the divs to:
display: none;
as well as not.
The problem is that the value of selected is a string, whereas you expect it to be a boolean.
The following watcher:
watch: {
selected(newValue) {
console.log("selected changed to", newValue, "which is a", typeof newValue);
}
}
Will tell you this:
selected changed to true which is a string
selected changed to false which is a string
The reason is that you give the fields value a string instead of a boolean. To fix this, instead of writing value="true", write :value="true".
You can play with a live example here.
There are two problems as far as I can see here:
In a component, the data key must be a function and the value for the selected key in the object returned by the data function must be an actual boolean value true or false (which will be initial value)
export default {
name: 'CreateTournamentForm',
data: function(e) {
return {
selected: true,
}
},
}
By default, v-model values are treated as strings so your true and false values are actually the strings "true" and "false" which are both truthy. Changing your template code to the below (or alternatively using a number or string value instead) should fix it
<div v-show="selected === 'true'" id="ta-multDays">
<textarea rows="10" cols="80" name="multDays" type="text" />
</div>
I solved it by changing it from a 'v-show' to 'v-if'
<div>
<p>One day?</p>
<input
v-model="selected"
type="radio"
name="oneDay"
id="oneDay"
class="r-button"
value="onlyOneDay" />
</div>
<div id="multipleDays">
<p>Multiple days?</p>
<input
v-model="selected"
type="radio"
name="multDays"
id="multDays"
class="r-button"
value="multipleDays" />
</div>
then the div to be shown as follows:
<div v-if="selected === 'multipleDays'" id="ta-multDays">
<textarea rows="10" cols="80" name="" type="text" />
</div>
<div v-if="selected === 'onlyOneDay'" id="i-oneDay">
<input type="text" name="">
</div>

Angular 2 - Add Value of Checked Radio Button to Array

I am trying to get the checked radio button and add the value to an Array. Currently, i cannot remove the previously checked radio buttons, so basically it keeps adding to the array every time i select a radio button.
item.component.ts
displaySelectedConditions(event) {
if(event.target.checked) {
this.selectedConditionsArr.push(event.target.value);
}
}
item.component.html
<ul class="dropdown-menu">
<li *ngFor="let item of filteredItems | funder "> //generates 4 items
<a><input type="radio" (change) = "displaySelectedConditions($event);"
name="funder" id="{{item}}" value="{{item}}">
<label for="{{item}}" >{{item}}</label></a>
</li>
</ul><!-- Dropdown Menu -->
I would suggest if you want to have the values neatly stored somewhere, then make use of a form. Simple template driven form works well here, then you would have all your values stored in an object, here's a sample:
<form #radioGroup="ngForm">
<div *ngFor="let str of strings">
<input type="radio" [value]="str" name="str" ngModel />{{str}}
</div>
<hr>
<div *ngFor="let num of numbers">
<input type="radio" [value]="num" name="num" ngModel />{{num}}
</div>
</form>
This would create an object like:
{
str: "value here",
num: "value here"
}
And if you declare the form like the following, you can easily access the object values:
#ViewChild('radioGroup') radioGroup: NgForm;
Somewhere in component:
console.log(this.radioGroup.value);
Plunker

Radio buttons not changing how javascript selects elements my table?

Problem
How to use radio buttons to control selection behavior by JQuery UI?
Right now, I can select all the green boxes (success) as intended with Check Out. Please note that when I select something it is orange.
My problem is that if I choose Check In, I cannot select any of the blue boxes. Also, I probably need a way to "refresh" when I change radio buttons so the green boxes are not still selected when I want to change radio buttons.
Code (view)
I am using JQuery selectable to get the job done. You can read more about it from the api documentation found here. I tried to get jquery to "filter" based on whether or not the radio box was checked. Clearly, this approach is failing.
Edit I think the approach is failing because it is loading the javascript once? So when I change the radio buttons, my filter definition is not changing.
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Details</title>
<link rel="stylesheet" href="~/css/table.css" />
<script>
$(document).ready(function(){
//Have this be a conditional based on radio input
var getRad1 = document.getElementById('optionsRadios1');
if (getRad1.checked)
{
console.log("Check has passed for Check in");
$("#selectable").selectable({
filter: ".success"
});
//getter
var filter = $(".selector").selectable("option", "filter");
}
//option 2
var getRad2 = document.getElementById('optionsRadios2');
if (getRad2.checked) {
//$("#selectable").selectable("refresh");
console.log("Check has passed for Check in");
$("#selectable").selectable({
filter: ".info"
});
//getter
var filter = $(".selector").selectable("option", "filter");
}
//I am not sure what this is doing
var select_range = false;
//queue of objects to deselect
var deselect_queue = [];
//http://stackoverflow.com/questions/3691773/jquery-ui-event-and-ui-object-properties
$("#selectable").selectable({
selecting: function(event, ui)
{
var selecting = $(ui.selecting);
if (selecting.hasClass('ui-selected')) {
deselect_queue.push(selecting);
}
},
//This is what keeps previous selected items as selected?
unselecting: function (event, ui) {
$(ui.unselecting).addClass('ui-selected');
},
//Triggered at the end of the select operation
stop: function () {
if (!select_range) {
for (var i = 0; i < deselect_queue.length; i++) {
deselect_queue[i]
.removeClass('ui-selecting')
.removeClass('ui-selected')
}
}
select_range = false;
deselect_queue = [];
//Something else went here that I didn't understand
}
});
});
</script>
</head>
<body>
#{ int columnN = Model.columnNumber;}
#{ int rowN = Model.rowNumber;}
#{ int index = 1;}
<table class="table table-striped table-hover">
<thead>
<tr>
<th>#</th>
#for (int j = 1; j <= columnN; j++)
{
<th>#j.ToString()</th>
}
</tr>
</thead>
<tbody id="selectable">
#for (int i = 1; i <= rowN; i++)
{
<tr>
<th>#i.ToString()</th>
#for (int k = 1; k <= columnN; k++)
{
if (k % 2 == 0)
{
<td class="book success" data-id=#index> #index.ToString() </td>
}
else
{
<td class="book info" data-id=#index> #index.ToString() </td>
}
index++;
}
</tr>
}
</tbody>
</table>
<form class="form-horizontal">
<fieldset>
<legend>Legend</legend>
<div class="form-group">
<label class="col-lg-2 control-label">Radios</label>
<div class="col-lg-10">
<div class="radio">
<label>
<input name="optionsRadios" id="optionsRadios1" type="radio" checked="" value="option1">
Check out (Depopulate)
</label>
</div>
<div class="radio">
<label>
<input name="optionsRadios" id="optionsRadios2" type="radio" value="option2">
Check in (populate)
</label>
</div>
</div>
</div>
</fieldset>
</form>
</body>
</html>
Attempts
I am trying to use conditionals inside the main javascript function. It works for the initial time and sets everything up and then fails.
I could have two separate views, but that defeats the purpose of having dynamic page content.
I think I am close with trying to read the dom elements and change my javascript behavior, but I can't quite get it to work right (I might be way off too). I deeply appreciate any assistance that could be rendered.
I am writing this solution in hopes that someone else might find this post useful.
Solution
The radio buttons need a listener. So it is necessary to add onclick="" to them. However, what I need is two functions to have the behavior I seek. Otherwise, the javascript will load and then become useless to us, even with radio buttons.
Select only elements are green
Select only elements who are blue
Thus, the following functions need to be added to javascript:
function populate() {
//$("#selectable").selectable("refresh");
console.log("Check has passed for Check in");
$(".selector").selectable("refresh");
$("#selectable").selectable({
filter: ".info"
});
var filter = $(".selector").selectable("option", "filter", ".info");
};
function depopulate() {
console.log("Check has passed for Check in");
$(".selector").selectable("refresh");
$("#selectable").selectable({
filter: ".success"
});
var filter = $(".selector").selectable("option", "filter", ".success");
};
The radio button needs to be modified as follows:
<input name="optionsRadios" id="optionsRadios1" type="radio" checked="" value="option1" onclick="depopulate()">
<input name="optionsRadios" id="optionsRadios2" type="radio" value="option2" onclick="populate()">
After this, clicking on the radio buttons will give us the behavior we desired.

How to dynamically create multiple form input fields with incremented ng-models?

After reading this article, I understand how to dynamically add a form field using ng-repeat.
I am wondering how can multiple form elements be dynamically created with incrementing ng-model values.
For example, the following would be created from a button click.
<input ng-model="vm.foo.bar1.first">
<input ng-model="vm.foo.bar1.second">
<input ng-model="vm.foo.bar1.third">
<input ng-model="vm.foo.bar1.fourth">
<input ng-model="vm.foo.bar2.first">
<input ng-model="vm.foo.bar2.second">
<input ng-model="vm.foo.bar2.third">
<input ng-model="vm.foo.bar2.fourth">
<input ng-model="vm.foo.bar3.first">
<input ng-model="vm.foo.bar3.second">
<input ng-model="vm.foo.bar3.third">
<input ng-model="vm.foo.bar3.fourth">
How can this be done?
I would suggest to restructure your ViewModel to make vm.foo.bar an array. Then this would be trivial:
<div ng-repeat="item in barItems">
<input ng-model="vm.foo.bar[$index].first">
<input ng-model="vm.foo.bar[$index].second">
<input ng-model="vm.foo.bar[$index].third">
<input ng-model="vm.foo.bar[$index].fourth">
</div>
Or, if you insist, then also
<div ng-repeat="item in barItems" ng-init="outerIdx = $index">
<input ng-repeat='p in ["first", "second", "third", "fourth"]'
ng-model="vm.foo.bar[outerIdx][p]">
</div>
(I'm assuming here, that unlike with first, second, etc..., the number of bars is not known - hence an array is a better option)
EDIT:
If you really want, you could also make vm.foo an object that holds properties bar1, bar2, etc...:
<div ng-repeat="item in [1, 2, 3, 4]">
<input ng-model="vm.foo['bar' + item].first">
<input ng-model="vm.foo['bar' + item].second">
<input ng-model="vm.foo['bar' + item].third">
<input ng-model="vm.foo['bar' + item].fourth">
</div>
but don't forget to first create vm.foo object in the controller:
this.foo = {};
When I have to do this I use the $index to control the names of things. Although I've never tried this exact code, this should work.
<input ng-model='vm.foo.bar3[$index]'></input>
$index comes along whenever you do ng-repeat and is just the index of the list item. So that should end up making ng-models that are vm.foo.bar3.0 to whatever.
To my point of view, you should create arrays of models in your controller.
$scope.vm.foo = [{
bar1: [{
first: '',
second: '',
...
},
bar2: ...
],
}]
And then in your view iterate on your tab :
<div ng-repeat="elem in foo">
<div ng-repeat="input in elem">
<input ng-model="input">
</div>
</div>
Hope it will help you !

Angular - Binding checkboxes to ng-model from ng-repeat

I have some data coming back from a resource that looks like:
$scope.phones = [
{
Value: <some value>,
IsDefault: true
},
{
Value: <some value>
IsDefault: false
}
];
And for simplicity sake, here's the repeater:
<div ng-repeat="phone in phones">
<input type="radio" name="phone" ng-model="phone.IsDefault" />
</div>
I would like whichever radio is checked to update the model accordingly - this is not happening. On page load, nothing is checked. I can use ng-checked - but without ng-model it wont bind back to the array. Am I missing something simple or am I stuck writing an ng-change event to manually update the array?
As of now, I wrote a ng-change event as well, it currently looks like:
ng-model="phone.IsDefault" ng-value="true" ng-change="newPhoneSelected($index)"
$scope.newPhoneSelected = function (index) {
for (var i = 0; i < $scope.phones.length; i++) {
if (i == index) $scope.phones[i].IsDefault = true;
else $scope.phones[i].IsDefault = false;
}
}
You are missingng-value... it is radio button they work based on value to mark a value as selected. You need either [value="" | ng-value=""]
<input type="radio"
ng-model=""
[value="" |
ng-value=""]>
Like:
<input type="radio" ng-value="true" name="boolean" ng-model="myValue" /> True
<input type="radio" ng-value="false" name="boolean" ng-model="myValue" /> False
Here is a plunker demo
Or with strings values:
$scope.myValue = 'Car'
html
<input type="radio" ng-value="'Car'" name="string1" ng-model="myValue" /> Car
<input type="radio" ng-value="'Airplane'" name="string1" ng-model="myValue" /> Airplane
Here is the second demo
This is probably the closest sample to what you have:
http://jsfiddle.net/JbMuD/
A radio button is used to select one out of many values. You want to change the property of one item (isDefault = true) and simultaneously of another one (isDefault = false).
The semantically correct way would be to have some kind of defaultPhone value:
<div ng-repeat="phone in phones">
<input type="radio" name="phone" ng-model="temp.defaultPhone" ng-value="phone"/>
</div>
Since your model requires that each phone "knows" itself wether it's default or not, you can add a listener:
$scope.$watch('temp.defaultPhone', function(defaultPhone) {
$scope.phones.forEach(function(phone) {
phone.isDefault = phone === defaultPhone;
});
});
Here's a working example.

Categories

Resources