Checking if any checkbox is checked Knockout - PhoneJS - javascript

So in my mobile web app (using PhoneJS), I am using a dxList to display some records. I have a checkbox next to each list 'item', so that I can mass delete or send the records. I need to know how to figure out if there is one or more checkboxes checked.
I know I can do this with normal Knockout, but I don't the PhoneJS framework actually creates a 'real' HTML checkbox, but makes a clickable element that functions like a checkbox.
So if one or more checkboxes are checked, I need to show a send and delete button. I just need to know how to determine if there are any checked boxes.
I've looked everywhere online for this, but the solutions are for Knockout using REAL checkbox inputs...
Here's my code for the dxList:
<div data-bind="dxList:{dataSource: list_data, grouped:true }">
<div data-options="dxTemplate:{name:'group'}">
<b><span data-bind="text: $data.key"></span></b>
</div>
<div data-options="dxTemplate:{name:'item'}">
<span data-bind="text: $data.item_value"></span>
<div data-bind="dxCheckBox: { }" style="float:right"></div>
</div>
</div>
I've tried binding 'checked' to an observable array, but that affects all the checkboxes.
Can anyone help me with this? Thanks!

The most straightforward MVVM approach is to data-bind dxCheckBox.checked option to a boolean property of a list item view-model. Then you can iterate over the items and understand which are checked.
You mentioned that you
tried binding 'checked' to an observable array
It is not clear why you bind a scalar property to an array.
Actually it does not differ much from the pure HTML approach. You may treat PhoneJS widgets just as fat HTML tags.

So, I have pretty much the same question, but I think I can be more clear on my requirements.
I have a dxList that uses a SQLite table as a datasource. It is setup to allow the user to select from a list of templates to apply to another object. This new list of templates and the associated object ID will be saveed in a DIFFERENT table than the original data and as such, I need to be able to identify the items in the list that have been checked.
<div data-bind="dxList: { dataSource: templateList }">
<div data-bind="dxAction: ''" data-options="dxTemplate : { name: 'item' } ">
<table>
<tr>
<td>
<div data-bind="dxCheckBox: { }"></div>
</td>
<td>
<div style="font-weight: bold; padding-left: 10px;" data-bind="text: TemplateName"></div>
</td>
</tr>
</table>
</div>
</div>
I found this post during my initial search. I can't use the data-bind: {checked: ?} value of each check box, as that would do as the original poster found, setting all or none. I thought about an array. I'm going to try to use the dxAction to add/remove checked list item IDs from an array but I'm not sure how well that will work. Then there's the final parse to get all checked items. I will update this post once I get it working.
Resolution:
ViewModel objects:
selectedTemplates: ko.observableArray(),
selectTemplate: function (args) {
//If it's there. Remove it.
if (args.model.selectedTemplates.indexOf(args.itemData.TemplateID) > -1) {
args.model.selectedTemplates.pop(args.itemData.TemplateID);
args.itemElement[0].style.backgroundColor = '';
args.itemElement[0].style.color = 'Black';
}
//else Add
else {
args.model.selectedTemplates.push(args.itemData.TemplateID);
args.itemElement[0].style.backgroundColor = '#017AFF';
args.itemElement[0].style.color = 'White';
}
},
And the View:
<div data-options="dxView : { name: 'SelectSurveys', title: 'SelectSurveys' } ">
<div data-bind="dxCommand: { title: 'Save', id: 'create', action: saveSelections, icon: 'save' }"></div>
<div data-options="dxContent : { targetPlaceholder: 'content' } ">
<div data-bind="dxList: { dataSource: templateList, itemClickAction: selectTemplate }">
<div data-options="dxTemplate : { name: 'item' } ">
<div style="font-weight: bold; padding-left: 10px;" data-bind="text: SurveyName"></div>
</div>
</div>
</div>
</div>
And looping the selected values for saving to local DB:
$.each(args.model.selectedTemplates(), function (index, value) {
db.transaction(function (tx) {
console.log("Inserting Data");
tx.executeSql(sql, [value],
function (t, result1) {
if (result1 != null) {
console.log("New Item added." + result1.insertId);
}
},
function (t, error) {
console.log(error);
});
});
In the objects, I've added some coloring so you can tell which ones are selected, it doesn't use the dxSwitch or Checkbox, but it works just as well and I think it's more visually appealing as well as informative to the user.

Related

getting a cannot read property of undefined error from vue.js when using v-model with an array

I'm trying to v-model to an array item's property. When I load the page I see "[Vue warn]: Error in render function: 'TypeError: Cannot read property 'viewFood' of undefined' in the console and a blank page.
this is with vue.js 2.x.
https://codepen.io/jzaun/pen/YxYyJN/
html
<div id="ai-config">
<div class="info">
<div class="row">
<h1>Resource Points</h1>
</div>
<div class="row">
<label>Total:</label>
<div class="value">
{{maxResourcePoints}}
</div>
</div>
<div class="row">
<label>Remaining:</label>
<div class="value">
{{maxResourcePoints - usedResourcePoints}}
</div>
</div>
</div>
<div>
<table>
<tr>
<td></td>
<td v-for="(option, idx) in options">
{{option.title}}
</td>
</tr>
<tr v-for="n in directions">
<td>Direction {{n}}</td>
<td v-for="option in options">
<input type="checkbox", v-model="selected[n][option.key]" />
</td>
</tr>
</table>
</div>
</div>
javascript
new Vue ({
el: '#ai-config',
data: {
maxResourcePoints: 10,
usedResourcePoints: 0,
selected: [],
directions: 8,
options: [{
title: 'Food',
key: 'viewFood',
cost: 1
}, {
title: 'Water',
key: 'viewWater',
cost: 1
}, {
title: 'Own',
key: 'viewOwn',
cost: 1
}, {
title: 'Other',
key: 'viewOther',
cost: 1
}]
},
methods: {
},
created: function () {
this.selected = [];
for(i=0; i< 8; i++) {
this.selected.push({});
}
}
});
There are two primary issues.
First, Vue cannot detect when you add a property dynamically to an object that has been added to the Vue's data. When you do this:
v-model="selected[n][option.key]"
You are adding a property to the empty object you initialized in the create handler. To fix that, just initialize with actual properties (or use $set, which doesn't appear to be an option here).
this.selected.push({viewFood: false, viewOwn: false, viewWater: false, viewOther: false});
Second (and the cause of the error you quote in your question), when you use a range v-for the values start at 1. So
v-model="selected[n][option.key]"
has an off by one error because as you likely know, Javascript arrays are zero based. It should be
v-model="selected[n - 1][option.key]"
There was also a minor HTML error in the original pen
<input type="checkbox", v-model="selected[n][option.key]" />
where the comma should be removed.
Here is your pen updated.
I think that I have the code you need.
Check it out here https://codepen.io/spaquet/pen/MvrbLQ
I made the following change:
Adding value and id to all checkboxes and making sure that they are all different so you can identify which one is clicked.
Removed your created function. Useless as selected is defined in data (my opinion is that you may want to keep the state between relead...)
Added a click function to all the checkboxes. It is used to visualize the state of the selected at every iteration.
You can now have in selected the list of the elements that are selected with the following format direction-option.key (1-viewFood, etc.)
<tr v-for="n in directions">
directions should be Array not Nummber
You initialize selected to contain a bunch of {} objects. selected[n] will be an empty object, so naturally selected[n][option.key] is null.
Changing <input type="checkbox", v-model="selected[n][option.key]" /> to <input type="checkbox" v-model="option.key"> works to me - it renders. All the checkboxes in a column point to the same value though - this is probably not what you want. This is because they all reference the same v-model
Can you explain a little more about what this is supposed to do? I can help you fix it then. Vue is great framework once you understand how it works. Maybe a mockup of what this should do or a little more explanation. Thanks.

Update unrelated field when clicking Angular checkbox

I have a list of checkboxes for people, and I need to trigger an event that will display information about each person selected in another area of the view. I am getting the event to run in my controller and updating the array of staff information. However, the view is not updated with this information. I think this is probably some kind of scope issue, but cannot find anything that works. I have tried adding a $watch, my code seems to think that is already running. I have also tried adding a directive, but nothing in there seems to make this work any better. I am very, very new to Angular and do not know where to look for help on this.
My view includes the following:
<div data-ng-controller="staffController as staffCtrl" id="providerList" class="scrollDiv">
<fieldset>
<p data-ng-repeat="person in staffCtrl.persons">
<input type="checkbox" name="selectedPersons" value="{{ physician.StaffNumber }}" data-ng-model="person.isSelected"
data-ng-checked="isSelected(person.StaffNumber)" data-ng-change="staffCtrl.toggleSelection(person.StaffNumber)" />
{{ person.LastName }}, {{ person.FirstName }}<br />
</p>
</fieldset>
</div>
<div data-ng-controller="staffController as staffCtrl">
# of items: <span data-ng-bind="staffCtrl.infoList.length"></span>
<ul>
<li data-ng-repeat="info in staffCtrl.infoList">
<span data-ng-bind="info.staffInfoItem1"></span>
</li>
</ul>
</div>
My controller includes the following:
function getStaffInfo(staffId, date) {
staffService.getStaffInfoById(staffId)
.then(success)
.catch(failed);
function success(data) {
if (!self.infoList.length > 0) {
self.infoList = [];
}
var staffItems = { staffId: staffNumber, info: data };
self.infoList.push(staffItems);
}
function failed(err) {
self.errorMessage = err;
}
}
self.toggleSelection = function toggleSelection(staffId) {
var idx = self.selectedStaff.indexOf(staffId);
// is currently selected
if (idx >= 0) {
self.selectedStaff.splice(idx, 1);
removeInfoForStaff(staffId);
} else {
self.selectedStaff.push(staffId);
getStaffInfo(staffId);
}
};
Thanks in advance!!
In the code you posted, there are two main problems. One in the template, and one in the controller logic.
Your template is the following :
<div data-ng-controller="staffController as staffCtrl" id="providerList" class="scrollDiv">
<!-- ngRepeat where you select the persons -->
</div>
<div data-ng-controller="staffController as staffCtrl">
<!-- ngRepeat where you show persons info -->
</div>
Here, you declared twice the controller, therefore, you have two instances of it. When you select the persons, you are storing the info in the data structures of the first instance. But the part of the view that displays the infos is working with other instances of the data structures, that are undefined or empty. The controller should be declared on a parent element of the two divs.
The second mistake is the following :
if (!self.infoList.length > 0) {
self.infoList = [];
}
You probably meant :
if (!self.infoList) {
self.infoList = [];
}
which could be rewrited as :
self.infoList = self.infoList || [];

How to dynamically bind a WinJS list view within a repeater

I have a WinJS Repeater which is using a template. That template contains a WinJS list view. I can't seem to figure out how to use data-win-bind to set the inner list views itemDataSource.
My Repeater:
<section id="genreView" aria-label="Main content" role="main">
<div id="genreWrapper">
<div id="genreRows" data-win-control="WinJS.UI.Repeater"
data-win-options="{template: select('#genreRowTemplate')}">
</div>
</div>
</section>
My Template which contains a List View:
<div id="genreRowTemplate" data-win-control="WinJS.Binding.Template">
<div id="genreRow">
<h2 class="genreTitle" data-win-bind="innerText: genreTitle"></h2>
<div class="genreMovieListView"
data-win-control="WinJS.UI.ListView"
data-win-bind="itemDataSource : data"
data-win-options="{ layout: { type: WinJS.UI.GridLayout },
itemsDraggable: false,
selectionMode: 'single',
tapBehavior: 'none',
swipeBehavior: 'none',
itemsReorderable: false,
itemTemplate: select('#genreMovieTemplate')}">
</div>
</div>
</div>
My List View's template:
<div id="genreMovieTemplate" data-win-control="WinJS.Binding.Template">
<div class="genreMovieItem">
<img style="width: 175px; height: 250px;" data-win-bind="alt: title; src: posterUrl" />
</div>
</div>
The data for the repeater is similar to this (only it's a binding list):
var repeaterData = [{genreTitle: "titleA", data: new WinJS.Binding.List() },
{genreTitle: "titleB", data: new WinJS.Binding.List() }
{genreTitle: "titleC", data: new WinJS.Binding.List() }
{genreTitle: "titleD", data: new WinJS.Binding.List() }];
The data is created on the fly and each binding list is actually a lot more data so I can't get a good sample of real data.
What I DO get is a repeated control, repeated exactly the number of times as the records in my bound list. What I DON'T get is the binding list displayed. My inner binding list is not getting databound via the data-win-bind. I've tried a few things and I either get nothing or an error. Any help is appreciated. Thanks.
EDIT: I think the right way to bind would be data-win-bind="data-win-options.itemDataSource: data", but doing that throws the following confusing error: "JavaScript runtime error: WinJS.UI.Repeater.AsynchronousRender: Top level items must render synchronously".
I was able to solve my problem, but I don't think I went about it in the right way. I went ahead and set the data source for the repeater, which gives me my genreRows from above. This was always working, it was just the child dataSource I couldn't figure out how to get to. My solution... set it in code after it loads. Nothing fancy, just a solution that feels a bit hacky to me. Here's my code in case it helps someone else get past this problem.
var titleList = $(".genreRow > .genreTitle");
var genreLists = $(".genreRow > .genreMovieListView");
for (var i = 0; i < genreLists.length; i++) {
var title = titleList[i].innerText;
var listControl = genreLists[i].winControl;
tempData.forEach(function (element, i) {
if (element.genreTitle == title)
listControl.itemDataSource = element.data.dataSource;
});
};
Basically the above code assumes the title is unique (which it is). So it uses this as a key to traverse the data and set the itemDataSource that way.
I won't mark this as the accepted answer just in case someone can point out how to do this "right".
Old question, but still deserves a good answer:
I've modified your HTML to show the correct binding syntax.
<div id="genreRowTemplate" data-win-control="WinJS.Binding.Template">
<div id="genreRow">
<h2 class="genreTitle" data-win-bind="innerText: genreTitle"></h2>
<div class="genreMovieListView"
data-win-control="WinJS.UI.ListView"
data-win-bind="winControl.itemDataSource : data.dataSource"
data-win-options="{ layout: { type: WinJS.UI.GridLayout },
itemsDraggable: false,
selectionMode: 'single',
tapBehavior: 'none',
swipeBehavior: 'none',
itemsReorderable: false,
itemTemplate: select('#genreMovieTemplate')}">
</div>
</div>
Notice the winControl.itemDataSource and data.dataSource. Any WinJS control property that is not a standard HTML element property needs the winControl property in front of whatever property you are databinding. So the onclick property of a button element doesn't need it, but WinJS.UI.Command has icon which is not on the underlying button so needs the winControl prefix.
Also, ListView controls bind to a WinJS.Binding.List's dataSource property, not the List itself. For some reason, a repeater binds to the ListView itself however.

Angularjs toggle between input and span values in a table row

I have following code in my html where I want to toggle input and span fields. In a table row.
<table>
<tbody ng-repeat="(i, cont) in char.items">
<tr>
<td>
<div>
<a ng-click="choose()">
<input type="text" ng-model="item.desc" ng-show="sho==1" />
<span ng-show="sho==0">{{item.type}}</span></a>
</div>
</td>
</tr>
</tbody>
</table>
<div ng-click="addRows(char)" style="WIDTH: 974px">Add Row</div>
In my controller I have
app.controller("testCtrl", function($scope) {
$scope.sho=0;
$scope.addRows = function(char) {
if (typeof char.items == 'undefined') {
char.items = [];
}
char.items.push({ des: '', type: '', price: '', charge__id: ''});
};
$scope.choose= function() {
//some values are retrieved than I want to toggle so it shows the
//want to set sho=1 so input is hidden instead the span vaue is shown
$scope.sho=1;
};
});
Problem is when I set $scope.sho=1; it shows span value in all the row of the table.
While I add a new row I just want to show the input box leaving the other rows already inserted with span values.
Pleae let me know how can i set ng-show for each row in table.
Thanks
Since ng-repeat creates a child scope for each item you can leverage that within a directive. The parent scope of the directive will be the child scope created by ng-repeat and therefore isolated from other repeaters
Move your choose and sho out of main controller and put them into directive scope.
<div editable>
<a ng-click="choose()"></a>
<input type="text" ng-model="item.desc" ng-show="!sho" />
<span ng-show="sho">{{item.type}}</span>
</div>
app.directive('editable', function () {
return function (scope, elem, attrs) {
scope.sho = true;
scope.choose = function () {
scope.sho = !scope.sho;
}
}
});
This is the simplest version possible without going to isolated scope within the directive and without considering factors like more than one of these editables in a row.
For more insulated feature rich version would consider using a more robust directive like x-editable
I have trouble understanding what your code is actually used for. But my guess would be for you to pass the current item into the choose function and set a flag on the item itself. If you modify your ng-show and ng-hide attributes to react to this flag on each item, I guess you would reach your goal.
<a ng-click="choose(item)">
<input type="text" ng-model="item.desc" ng-show="item.sho==1" />
<span ng-show="item.sho==0">{{item.type}}</span></a>
</div>
And in your choose function you would do something like this:
$scope.choose= function(item) {
item.sho=1;
};
This is only a wild guess though, since it isn't quite clear to me what you are trying to accomplish.
Two things that come to mind immediately are:
1 - Pass in the item with the function and have the function accept an argument.
<a ng-click="choose(sho)">
and then in your controller
$scope.choose= function(sho) {
sho = 1;
};
2 - Just make ng-click set the value to one..
<a ng-click="sho = 1">

Passing in an observableArray as a function parameter

What I'm trying to achieve:
A form with one text input field(Name), two select fields with some options(Type & Column) with a submit button that creates a Widget with it's title set to Name, data-type set to Type and Column - being it's position in the page.
Type in this case has a couple of different options defined in the view, it works just dandy so I won't go into how I got that working, for now.
As of now I have a page with three columns, each as it's own observableArray - like so:
self.col0 = ko.observableArray([]);
self.col0.id = 'col0'
The same goes for col1 and col2. My goal here is to get the select field to point to these arrays. Atleast that's what I think I need to do.
I have a createWidget function, that looks at the Name and Type values in the DOM (Correct me if I'm wrong here, this is the very first thing I'm creating with KnockOut) and creates a new Widget from that data - like so:
var Widget = function(name, type) {
var self = this;
self.name = name;
self.type = type;
};
var ViewModel = function() {
var self = this;
self.newWidgetName = ko.observable();
self.newType = ko.observable();
// Unrelated code in between
self.createWidget = function(target) {
newName = self.newWidgetName();
newType = self.newType();
widget = new Widget(newName, newType);
target.unshift(widget)
self.newWidgetName("");
};
The input/select elements in the DOM
input.widget-name type="text" placeholder="wName" data-bind="value: newWidgetName"
select data-bind="value: newType"
option value="calendar" Calendar
option value="article" Article
option value="document" Document
select.colPick data-bind="value: colPick"
option value="col0" Column 1
option value="col1" Column 2
option value="col2" Column 3
And my click handler for the createWidget function - like so:
a.btn.create-widget data-bind="click: function(){ createWidget(col0); }"
Shazam, it works!
However, this will only ever output the new Widget into the first col (col0), it won't take the value of the column select field into account and unshift the new widget into the correct column. The errors I'm getting after much trial and error pretty much boils down to:
1) "Cannot call method unshift of undefined"
2) "Uncaught TypeError: Object function observable() .... has no method 'unshift'
So as of right now the code looks like the example above, It's not ideal by any means but with the different columns connected with knockout-sortable, it's not a huge deal if this functionality has to get scrapped. The user can still move the Widgets around from col0 to col2 and vice versa.
If anyone has a resource that would point me in the right direction, or the answer up their sleeve - feel free to share!
All the best, Kas.
Edit: Followup questions for Tyrsius
With the code you supplied I now have the three columns working in the select box, however I am struggling a bit when it comes to outputting the Widgets into view.
With my previous code, this is what the view looked like:
<div class="cols">
<div class="col col-25">
<ul data-bind="sortable: { template: 'widgetTmpl', data: col0, afterMove: $root.widgetData }">
</ul>
</div>
<div class="col col-50">
<ul data-bind="sortable: { template: 'widgetTmpl', data: col1, afterMove: $root.widgetData }">
</ul>
</div>
<div class="col col-25">
<ul data-bind="sortable: { template: 'widgetTmpl', data: col2, afterMove: $root.widgetData }">
</ul>
</div>
</div>
What I'm working with right now is this:
<div data-bind="foreach: columns">
<div data-bind="foreach: items" class="col">
<ul data-bind="sortable: { template: 'widgetTmpl', data: columns, afterMove: $root.widgetData }"></ul>
</div>
</div>
My first question, I realised I wasn't thinking at the time so skip that one.
Question #2: How would I now get these Widgets bound to the col that I picked in the form?
Would I do this?
<ul data-bind="sortable: { template: 'widgetTmpl', data: entryColumn, afterMove: $root.widgetData }"></ul>
Or am I way off? :)
I would keep a collection of the items on the column as a type, which would look like this:
var Column = function(header) {
this.header = ko.observable(header);
this.items = ko.observableArray([]);
};
The trick would be to bind the column select straight to the list of columns on your viewmodel:
<select data-bind="options: columns, optionsText: 'header', value: entryColumn"
Whats happening here is that the actual column object that is selected by the dropdown will be stored in the entryColumn property. Later we can put the item directly into its items list since we will have a reference to it. This also allows us to support any number of columns without changing the logic.
The add code would stay simple:
self.columns = ko.observableArray(columns);
self.entryName = ko.observable('');
self.entryType = ko.observable('');
self.entryColumn = ko.observable('');
self.types = ko.observableArray(['Small', 'Medium', 'Large']);
self.addWidget = function() {
var widget = new Widget(self.entryName(), self.entryType());
//Here is where we can directly add to the selected columns item list
self.entryColumn().items.push(widget);
self.resetForm();
};
Here is the fiddle demonstrating these.
FollowUp Question
You're close, but the data is the items not the EntryColumn
<div data-bind="foreach: columns">
<div data-bind="sortable: { template: 'widgetTmpl', data: items}" class="column">
</div>
</div>
<script type="text/html" id="widgetTmpl">
<p data-bind="text: name() + ' - ' + type()"></p>
</script>
Here is the updated fiddle.

Categories