I have a teams and rank scoring collection to track, say a relay race.
So as a user enter teams into collection, each team will be awarded points based on ranking.
https://jsfiddle.net/2ae3dv8d/
<strong>Teams:</strong>
<!-- ko foreach: teams -->
<div>
<input type="text" data-bind="value: name" />
</div>
<!-- /ko -->
function Team(name)
{
var self = this;
self.name = name'
}
function RankScoring(label, points){
var self = this;
self.label = label;
self.points = points;
}
function AppViewModel()
{
var self = this;
self.teams = ko.observableArray([
new Team('red'),
new Team('blue')
]);
self.rankscoring = ko.observableArray([
new RankScoring('1st', ''),
new RankScoring('2nd', ''),
new RankScoring('3rd', ''),
]);
}
In this particular example, let's say the game had 2 teams. So, I'm only going to need to use 2 items from RankScoring collection based on the number of teams involved. How do I data-bind RankScoring collection so user can enter points for rankings? And I welcome suggestions for easier way to accomplish the idea.
**Teams:**
Red
Blue
**Scoring**
1st place - 10
2nd place - 5
Add a computed to show textboxes for available ranks:
self.availablerankscoring = ko.computed(function(){
return self.rankscoring().slice(0,self.teams().length);
});
Add another foreach for available ranks:
<!-- ko foreach: availablerankscoring -->
<div>
<label data-bind="text: label"></label><input type="text" data-bind="value: points" />
</div>
<!-- /ko -->
Fiddle
Related
I've posted my fiddle here, that has comments with it.
How can I convert/map the AllCustomers array into another array of Customer objects??
I need to push the checked checkboxes objects in to self.Customer(), {CustomerType,checked}
Then I would loop through list of Customer object Array and return an array of all checked Customers - self.CheckedCustomers
function Customer(type, checked)
{
var self = this;
self.CustomerType = ko.observable(type);
self.IsChecked = ko.observable(checked || false);
}
function VM()
{
var self = this;
//dynamically populated - this is for testing puposes
self.AllCustomers = ko.observableArray([
{
code: "001",
name:'Customer 1'
},
{
code: "002",
name:'Customer 2'
},
{
code: "003",
name:'Customer 3'
},
]);
self.selectedCustomers = ko.observableArray([]);
self.Customer = ko.observableArray([]);
//How can I convert the AllCustomers array into an array of Customer object??
//I need to push the checked object in to self.Customer(), {CustomerType,checked}
//uncomment below - just for testing looping through Customer objects
/*
self.Customer = ko.observableArray([
new Customer("001"),
new Customer("002")
]);
*/
// This array should return all customers that checked the box
self.CheckedCustomers = ko.computed(function()
{
var selectedCustomers = [];
ko.utils.arrayForEach(self.Customer(), function (customer) {
if(customer.IsChecked())
selectedCustomers.push(customer);
});
return selectedCustomers;
});
}
ko.applyBindings(new VM());
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<!-- ko foreach: AllCustomers -->
<input type="checkbox" data-bind="value: $data.code, checked:$parent.selectedCustomers" />
<span data-bind="text: $data.name"></span>
<!-- /ko -->
<br />
<h4>selectedCustomers code</h4>
<div data-bind="foreach: selectedCustomers">
<span data-bind="text: $data"></span>
</div>
<h4>Checked boxes are??</h4>
<div data-bind="foreach: CheckedCustomers">
<span data-bind="text: CustomerType"></span>
<span data-bind="text: IsChecked"></span>
<span>,</span>
</div>
<!-- Use this to loop through list of Customer object Array, uncomment below to test-->
<!--
<!-- ko foreach: Customer --
<input type="checkbox" data-bind="checked: IsChecked" />
<span data-bind="text: CustomerType"></span>
<!-- /ko --
-->
You're trying to convert object with properties code and name to an object of properties CustomerType and IsChecked. I'm assuming you want code to be mapped to CustomerType when creating new Customer object.
Here's a working jsfiddle for more or less what you wanted.
https://jsfiddle.net/s9yd0e7o/10/
Added the following code:
self.selectedCustomers.subscribe(function() {
self.Customer.removeAll();
ko.utils.arrayForEach(self.selectedCustomers(), function(item) {
self.Customer.push(new Customer(item, true));
});
});
I have a model being used by multiple view models, and i need some other javascript components to update the model, observed by my vm's. I have no idea how to do this since in the tutorial, they "mix" the model in the viewmodel.
Here is my code :
var ConversationModel = {
conversations: ko.observableArray(),
open: function(userId){
for(var i = 0; i < this.conversations.length; i++){
if(this.conversations[i].userId == userId){
return;
}
}
var self = this;
var obj = ko.observable({
userId: userId
});
self.conversations.push(obj);
UserManager.getUserData(userId, function(user){
$.getJSON(Routes.messenger.getConversation, "receiver=" + userId, function(data){
obj.receiver = user;
obj.data = data;
});
});
}
};
function ConversationDialogViewModel() {
var self = this;
this.conversations = ko.computed(function(){
return ConversationModel.conversations;
});
console.log(this.conversations());
this.conversations.subscribe(function(context){
console.log(context);
});
}
You can find a (reasonably) good example here how to combine:
Components
Per page ViewModel
Central ServiceProviders (for example, to call APIs or to provide state information between different components)
Please note the code is ES2015 (new Javascript) but you can also write in plain Javascript if you want. The gulp script includes stringifying any html templates in the components, so they get combined and loaded as one file but are edited as separate elements.
An example component:
const ko = require('knockout')
, CentralData = require('../../service-providers/central-data')
, CentralState = require('../../service-providers/central-state')
, template = require('./template.html');
const viewModel = function (data) {
//Make service providers accessible to data-bind and templates
this.CentralData = CentralData;
this.CentralState = CentralState;
this.componentName = 'Component One';
this.foo = ko.observable(`${this.componentName} Foo`);
this.bar = ko.observableArray(this.componentName.split(' '));
this.barValue = ko.observable("");
this.bar.push('bar');
this.addToBar = (stuffForBar) => {
if(this.barValue().length >= 1) {
this.bar.push(this.barValue());
CentralData.pushMoreData({firstName: this.componentName,secondName:this.barValue()});
}
};
this.CentralState.signIn(this.componentName);
if (CentralData.dataWeRetrieved().length < 10) {
var dataToPush = {firstName : this.componentName, secondName : 'Foo-Bar'};
CentralData.pushMoreData(dataToPush);
}
};
console.info('Component One Running');
module.exports = {
name: 'component-one',
viewModel: viewModel,
template: template
};
and component template:
<div>
<h1 data-bind="text: componentName"></h1>
<p>Foo is currently: <span data-bind="text: foo"></span></p>
<p>Bar is an array. It's values currently are:</p>
<ul data-bind="foreach: bar">
<li data-bind="text: $data"></li>
</ul>
<form data-bind="submit: addToBar">
<input type="text"
name="bar"
placeholder="Be witty!"
data-bind="attr: {id : componentName}, value : barValue" />
<button type="submit">Add A Bar</button>
</form>
<h2>Central State</h2>
<p>The following components are currently signed in to Central State Service Provider</p>
<ul data-bind="foreach: CentralState.signedInComponents()">
<li data-bind="text: $data"></li>
</ul>
<h2>Central Data</h2>
<p>The following information is available from Central Data Service Provider</p>
<table class="table table-bordered table-responsive table-hover">
<tr>
<th>Component Name</th><th>Second Value</th>
</tr>
<!-- ko foreach: CentralData.dataWeRetrieved -->
<tr>
<td data-bind="text: firstName"></td><td data-bind="text: secondName"></td>
</tr>
<!-- /ko -->
</table>
<h3>End of Component One!</h3>
</div>
For your purposes, you can ignore the Central state provider and psuedo APIs, but you might find the model useful as your app gets more complicated.
So I'm trying to add content to an observable array, but it doesn't update. The problem is not the first level content, but the sub array. It's a small comments section.
Basically I've this function to declare the comments
function comment(id, name, date, comment) {
var self = this;
self.id = id;
self.name = ko.observable(name);
self.date = ko.observable(date);
self.comment = ko.observable(comment);
self.subcomments = ko.observable([]);
}
I've a function to retrieve the object by the id field
function getCommentByID(id) {
var comment = ko.utils.arrayFirst(self.comments(), function (comment) {
return comment.id === id;
});
return comment;
}
This is where I display my comments
<ul style="padding-left: 0px;" data-bind="foreach: comments">
<li style="display: block;">
<span data-bind="text: name"></span>
<br>
<span data-bind="text: date"></span>
<br>
<span data-bind="text: comment"></span>
<div style="margin-left:40px;">
<ul data-bind="foreach: subcomments">
<li style="display: block;">
<span data-bind="text: name"></span>
<br>
<span data-bind="text: date"></span>
<br>
<span data-bind="text: comment"></span>
</li>
</ul>
<textarea class="comment" placeholder="comment..." data-bind="event: {keypress: $parent.onEnterSubComment}, attr: {'data-id': id }"></textarea>
</div>
</li>
</ul>
And onEnterSubComment is the problematic event form
self.onEnterSubComment = function (data, event) {
if (event.keyCode === 13) {
var id = event.target.getAttribute("data-id");
var obj = getCommentByID(parseInt(id));
var newSubComment = new comment(0, self.currentUser, new Date(), event.target.value);
obj.subcomments().push(newSubComment);
event.target.value = "";
}
return true;
};
It's interesting, because when I try the same operation during initialization(outside of any function) it works fine
var subcomment = new comment(self.commentID, "name1", new Date(), "subcomment goes in here");
self.comments.push(new comment(self.commentID, "name2", new Date(), "some comment goes here"));
obj = getCommentByID(self.commentID);
obj.subcomments().push(subcomment);
If anyone can help me with this, cause I'm kind of stuck :(
You need to make two changes:
1st, you have to declare an observable array:
self.subcomments = ko.observableArray([]);
2nd, you have to use the observable array methods, instead of the array methods. I.e. if you do so:
obj.subcomments().push(subcomment);
If subcomments were declared as array, you'd be using the .push method of Array. But, what you must do so that the observable array detect changes is to use the observableArray methods. I.e, do it like this:
obj.subcomments.push(subcomment);
Please, see this part of observableArray documentation: Manipulating an observableArray:
observableArray exposes a familiar set of functions for modifying the contents of the array and notifying listeners.
All of these functions are equivalent to running the native JavaScript array functions on the underlying array, and then notifying listeners about the change
I have a simple webpage with a large list of products (20,000+). When you can click on a product, it will load (via AJAX) a list of colors and display them inline. Html...
<div data-bind="foreach: products">
<span data-bind="click: $root.loadColors($data), text: $name"></span>
<ul data-bind="foreach: colors">
<li data-bind="text:$data" />
</ul
</div>
Shop view model:
function shopViewModel()
{
var self = this;
self.products = ko.observableArray([]);
self.loadColors = function(product)
{
var data = GetColorsByAjax();
product.colors(data);
}
}
Product view Model:
function productModel(data)
{
var self = this;
self.name = data.name;
self.colors = ko.observableArray([]);
}
When I have 20,000+ products, it uses a lot of memory. Each product has a colors array, which is always empty/null, until the user clicks on it, but it still uses a lot of memory.
Ideally, I'd like to remove the colors observableArray and somehow create it dynamically when user clicks on the product. Or separate it into a new viewModel.
I want to eliminate the empty observableArrays to minimise memory, but can't figure out how it do it.
I would use one of Knockout's control-flow bindings (if, with) to only bind the colors:foreach when there is actually a colors property on the productModel().
HTML:
<div data-bind="foreach: products">
<span data-bind="click: $root.loadColors($data), text: $name"></span>
<div data-bind="if: hasColors">
<ul data-bind="foreach: colors">
<li data-bind="text:$data" />
</ul>
</div>
</div>
Product View Model:
function productModel(data)
{
var self = this;
self.name = data.name;
self.hasColors = ko.observable(false);
self.colors = null;
}
Shop View Model
function shopViewModel()
{
var self = this;
self.products = ko.observableArray([]);
self.loadColors = function(product)
{
var data = GetColorsByAjax();
if(product.colors == null) {
product.colors = ko.observableArray(data);
product.hasColors(true);
} else {
product.colors(data);
}
}
}
You don't have to store an empty observable array: you can default to undefined and Knockout will treat it as an empty array in a foreach binding.
Here's a demonstration: http://jsfiddle.net/zm62T/
I am using Knockout's forech data binding to render a template. The issue is that for every three items generted using foreach binding, I want to create a new div with class row. Essentially , I want only three items to be displayed in one row. For the fourth item, noew row should be created. But the foreach data binding has been applied to the div inside the row div. How do I achieve that? Following is the code.
HTML
<div class="row">
<!-- Item #1 -->
<div class="col-md-4 col-sm-6 col-xs-12" data-bind="foreach:items">
<div data-bind="attr: {id: ID}" class="item">
<!-- Use the below link to put HOT icon -->
<div class="item-icon"><span>HOT</span></div>
<!-- Item image -->
<div class="item-image">
<img data-bind="attr: {src: picture}" src="img/items/2.png" alt="" class="img-responsive"/>
</div>
<!-- Item details -->
<div class="item-details">
<!-- Name -->
<h5><a data-bind="text: itemname" href="single-item.html">HTC One V</a></h5>
<div class="clearfix"></div>
<!-- Para. Note more than 2 lines. -->
<!--p>Something about the product goes here. Not More than 2 lines.</p-->
<hr />
<!-- Price -->
<div data-bind="text: price" class="item-price pull-left">$360</div>
<!-- qty -->
<div data-bind="text: quantity" class="item-price text-center">$360</div>
<!-- Add to cart -->
<div class="pull-right">Add to Cart</div>
<div class="clearfix"></div>
</div>
</div>
</div>
</div>
Javascript:
function itemsKo()
{
var self=this;
self.query = ko.observable();
self.hide = ko.observable(false);
self.items = ko.observableArray();
self.subcat=function()
{
$.ajax({
url: "/items,
type: "get",
success: function(data){
ko.utils.arrayForEach(data, function(item) {
item.price = "Rs" + item.price;
self.items.push(item);
});
//console.log(JSON.stringify(window.vm.items()));
},
error:function(jqXHR, textStatus, errorThrown) {
alert("failure");
}
});
}
}
The easiest solution is to find a way to map your array into a structure that is rows/columns. So, an array of rows, where each row is an array of items in that row.
Here is an older answer that shows creating a computed in the VM to represent an array as a set of rows: Knockout.js - Dynamic columns but limit to a maximum of 5 for each row
Another option could be to create a custom binding that handles the plumbing of this computed for you. The advantage is that you do not need to bloat your view model with extra code and it is reusable. A possible implementation might look like:
ko.bindingHandlers.rows = {
init: function (element, valueAccessor, allBindings, data, context) {
var rows = ko.computed({
read: function() {
var index, length, row,
options = ko.unwrap(valueAccessor()),
items = ko.unwrap(options.items),
columns = ko.unwrap(options.columns)
result = [];
for (index = 0, length = items.length; index < length; index++) {
if (index % columns === 0) {
//push the previous row, except the first time
if (row) {
result.push(row);
}
//create an empty new row
row = [];
}
//add this item to the row
row.push(items[index]);
}
//push the final row
if (row) {
result.push(row);
}
//we know have an array of rows
return result;
},
disposeWhenNodeIsRemoved: element
});
//apply the real foreach binding with our rows computed
ko.applyBindingAccessorsToNode(element, { foreach: function() { return rows; } }, context);
//tell KO that we will handle binding the children
return { controlsDescendantBindings: true };
}
};
Here is a quick fiddle with it in action: http://jsfiddle.net/rniemeyer/nh6d7/
It is a computed, so the number of columns and the items can be observable and will cause it to re-render on changes. This could be a slight concern, if you are often updating the original items.