So, I load my page and I have my list items with their unique id's.
<ul class="list-group" data-bind="template: { name: 'item-template', data: $root.items}">
<li class="list-group-item">
<span id="123" data-bind="text: item_name, attr: {'id': item_id}">Americanino</span>
<span class="glyphicon glyphicon-remove-circle" data-bind="click: $parent.removeItem"></span>
</li>
<li class="list-group-item">
<span id="223" data-bind="text: item_name, attr: {'id': item_id}">Asos</span>
<span class="glyphicon glyphicon-remove-circle" data-bind="click: $parent.removeItem"></span>
</li>
</ul>
And I bind the attributes after page load
jQuery(document).ready(function($) {
var itemListModel = function() {
(...)
self.item_id = ko.observable();
(...)
}
ko.applyBindings(new itemListModel());
});
But when I try to remove a list item that is loaded from server
// Remove item
self.removeItem = function(item) {
alert(self.item_id());
//self.items.remove(item);
}
then I'm not able to retrieve the ID.
If I add a new item, then I can get the ID. But then I also get the same ID if I click any other list item as well.
So how can I bind "static" content?
Is it a problem that I have a hidden output that also has data-bind="value: item_id"?
See my fiddle here
Modify your create_list method
function create_list(exiting_list){
var arr_list = [];
$(exiting_list).find('li').each(function(e,li){
var id = $(li).find('span').prop('id');
var name = $(li).find('span').html();
arr_list.push(new item(id, name));
});
return arr_list;
}
Updated fiddle here - http://jsfiddle.net/sherin81/JF55A/10/
Implemented the solution for sort .. updated Fiddle - http://jsfiddle.net/sherin81/JF55A/11/
Related
I'm just starting learning knockout.js and am a bit stuck on managing observables and updating them correctly. See the jsfiddle for the code.
self.addItem = function(item, event){
ptitle = $(event.currentTarget).prev('h3').text();
self.items.push({
productQty: self.productQty(),
productClip: self.productClip(),
productTitle: ptitle
});
}
http://jsfiddle.net/L61qhrc1/
What I have is a list of existing html elements. I want to create another list from that list with some input fields that can be set. It's mostly working but I cannot figure out from the examples around the net I've been looking at.
when one field updates all the fields in the list update but I only want the field I'm currently updating to be updated not the whole list.
Can any kind person point me in the right direction?
Cheers.
As I said in my comment, your user interface has to be a logical consequence of your data and viewmodel. Your viewmodel must never be concerned with the details of the view.
Also, your fiddle looks pretty over-engineered to me.
The following is what I have gathered from your sample, but in a less byzantine way.
Make separate self-contained viewmodels. Ideally make them so that they can bootstrap themselves from the data you pass to the constructor.
Working with templates keeps the HTML of the view clean and improves modularity and reusability.
For more complex data, consider the the mapping plugin to bootstrap your models.
Consult Unique ids in knockout.js templates for a way to create working <input> / <label> pairs.
function ListItem(data, parent) {
var self = this;
data = data || {};
self.productQty = ko.observable(data.productQty);
self.productClip = ko.observable(!!data.productClip);
}
function Product(data) {
var self = this;
data = data || {};
self.title = ko.observable(data.title);
self.id = ko.observable(data.id);
self.items = ko.observableArray();
self.newItem = new ListItem();
self.addItem = function () {
self.items.push(new ListItem(ko.toJS(self.newItem), self));
};
self.removeItem = function (item) {
self.items.remove(item);
};
}
function ProductList(data) {
var self = this;
data = data || {};
self.products = ko.observableArray(ko.utils.arrayMap(data.products, function (p) {
return new Product(p);
}));
}
var vm = new ProductList({
products: [
{title: "ProductName 1", id: "ProductId 1"},
{title: "ProductName 2", id: "ProductId 2"}
]
});
ko.applyBindings(vm);
ul {
list-style-type: none;
padding: 5px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div class="container">
<div class="m-col-6">
<ul data-bind="foreach: products">
<li data-bind="template: 'productTemplate'"></li>
</ul>
</div>
</div>
<hr />
<pre data-bind="text: ko.toJSON($data, null, 2)"></pre>
<script type="text/html" id="productTemplate">
<h1 data-bind="text: title"></h1>
<p data-bind="text: id"></p>
<div data-bind="with: newItem">
<input type="checkbox" data-bind="checked: productClip" />
<label>has pump clips</label>
<input type="number" data-bind="value: productQty" />
<button data-bind="click: $parent.addItem">add to list</button>
</div>
<ul data-bind="foreach: items">
<li>
<!-- ko template: 'itemsTemplate' --><!-- /ko -->
<button data-bind="click: $parent.removeItem">Remove</button>
</li>
</ul>
</script>
<script type="text/html" id="itemsTemplate">
<b data-bind="text: $parent.title"></b>
<span data-bind="text: productClip() ? 'has' : 'does not have'"></span> pump clips
(<span data-bind="text: productQty"></span>)
</script>
This is the for each loop to render the data
<ul data-bind="foreach: { data: PersonData, as: 'ref' }">
<li>
<a data-bind="attr: { data: ref.Filter }" class="filterbtn">
<span data-bind="html: ref.Name"></span>
<span data-bind="text: ref.Age" class="age"></span>
</a>
</li>
</ul>
I want to hide if data attribute value is data="people" and display it in another div.
How can I achieve this?
Thanks in advance!
You need to have a computed setup to make things working
view:
<ul data-bind="foreach: { data: PersonData}">
<li> <a data-bind="attr: { data: Filter },visible:Filter!='people'" class="filterbtn">
<span data-bind="html: Name"></span>
<span data-bind="text: Age" class="age"></span>
</a>
</li>
</ul>
<div data-bind="foreach:data"> <span data-bind="html: Name"></span>
<span data-bind="text: Age" class="age"></span>
</div>
viewModel:
var ViewModel = function () {
var self = this;
self.PersonData = ko.observableArray([{
'Filter': 'people',
'Name': 'cool',
'Age': '1'
}, {
'Filter': 'nope',
'Name': 'cooler',
'Age': '2'
}, {
'Filter': 'people',
'Name': 'hotter',
'Age': '3'
}])
self.data = ko.computed(function () {
return ko.utils.arrayFilter(self.PersonData(), function (item) {
return item.Filter === "people"; //do a case check here(if)
});
});
};
ko.applyBindings(new ViewModel());
working fiddle up here
In my KnockoutJS app, I am looping over an observable array and displaying some stuff like this:
<div id="user-list-container" data-bind="foreach: users">
<div class="row order-line list-row">
<div class="medium-7 small-10 columns">
<i class="fi-torso tip-right"></i>
</div>
<div class="medium-3 columns">
<a href="#" class="button split tiny info radius">
<i data-bind="text:role"></i>
<span data-dropdown="leftDrop" data-options="align:left"></span>
</a>
</div>
<div class="medium-2 small-2 columns">
<i class="fi-trash" title="#Texts.Remove"></i>
</div>
<ul id="leftDrop" class="f-dropdown" data-dropdown-content>
<li>Foreman</li>
<li>Worker</li>
</ul>
</div>
</div>
Everything works fine and all the elements are shown, but when I click one item to operate on that particular item, each item then has the value of last item in the array.
In my Javascript function:
self.makeWorker = function (user) {
var project = self.selectedProject();
var account = self.accounts.findByKey(user.accountId);
var ur = user;
console.log(user);
console.log(this);
if (project.role() != "Worker") {
var data = {
role: "Worker",
organizationId: project.organizationId,
projectId: project.id,
accountId: user.accountId
}
TippNett.Project.ChangeRole(data, function (result) {
project.users.findByKey(user.id).role("Worker");
ur.role("Worker");
account.roles.findByKey(user.id).role("Worker");
});
}
}
The value passed to the function is always the last value in 'users' observable array.
Any input on why this is happening? See the image for more briefing:
Good Afternoon everyone.
I've tried to search it, but either I'm searching for a wrong thing or there is no answer to that yet. I'm trying to make a list in knockout with a list of drop-downs, and when those drop-down values change, the corresponding value in the list needs to be updated. Here is my current "vision" of that, which doesn't work.
http://jsfiddle.net/Lypmnspz/6/
Here is the code from Fiddle:
function pageModel(){
var self = this
// Create an observalbe array of options
self.languages = ko.observableArray(["English","English","English"]);
// Languages
self.availableLanguages = ko.observableArray(["English", "Spanish", "German", "Russian"]);
}
ko.applyBindings(new pageModel());
<h4>When I update it here</h4>
<ul data-bind="foreach: languages">
<li><select data-bind="options: $parent.availableLanguages, value: $data"></select></li>
</ul>
<h4>I want to see the update here</h4>
<ul data-bind="foreach: languages">
<li data-bind="text: $data"></li>
</ul>
Can anyone suggest something? Thank you.
Maybe you need another data model - let's call it a LanguageSelection:
var LanguageSelection,
PageModel,
pageModel;
LanguageSelection = function LanguageSelection() {
this.language = ko.observable();
};
PageModel = function PageModel(){
this.availableLanguages = ko.observableArray([
"English",
"Spanish",
"German",
"Russian"
]);
this.languageSelections = ko.observableArray([]);
}
pageModel = new PageModel();
pageModel.languageSelections.push( new LanguageSelection() );
pageModel.languageSelections.push( new LanguageSelection() );
pageModel.languageSelections.push( new LanguageSelection() );
ko.applyBindings( pageModel );
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<h4>When I update it here</h4>
<ul data-bind="foreach: languageSelections">
<li>
<select data-bind="options: $parent.availableLanguages, value: language"></select>
</li>
</ul>
<h4>I want to see the update here</h4>
<ul data-bind="foreach: languageSelections">
<li data-bind="text: language"></li>
</ul>
I've ended up with ugly solution, but it works. There should be more elegant way. Here is the link:
http://jsfiddle.net/Lypmnspz/10/
HTML
<h4>When I update it here</h4>
<ul data-bind="foreach: languages">
<li><select data-bind="options: $parent.availableLanguages, value: $data, event: {change: $parent.updateOtherLanguage}, attr: {id: 'langopt-'+$index() }"></select></li>
</ul>
<h4>I want to see the update here</h4>
<ul data-bind="foreach: languages">
<li data-bind="text: $data"></li>
</ul>
Javascript
function pageModel(){
var self = this
// Create an observalbe array of options
self.languages = ko.observableArray([ko.observable("English"),ko.observable("English"),ko.observable("English")]);
// Languages
self.availableLanguages = ko.observableArray(["English", "Spanish", "German", "Russian"]);
self.updateOtherLanguage = function(lang, event){
// console.log(event.target.id.split('-')[1],event.target.value)
self.languages()[Number(event.target.id.split('-')[1])]((event.target.value))
}
}
ko.applyBindings(new pageModel());
I'm building a wizard widget with Durandal, and I'd like to use it like so:
<div data-bind="wizard: options">
<!-- Step 1 -->
<span data-part="step-header-1">
Step 1
</span>
<div data-part="step-content-1">
step content here
</div>
<!-- Step 2 -->
<span data-part="step-header-2">
Step 2
</span>
<div data-part="step-content-2">
step content here
</div>
</div>
This is the actual widget (cut down for brevity):
<div class="wizard-container">
<ul class="steps" data-bind="foreach: steps">
<li>
<span data-bind="html: heading"></span>
</li>
</ul>
<!-- ko foreach: steps -->
<div class="wizard-step" data-bind="css: { active: isActive }">
<div data-bind="html: content">
</div>
</div>
<!-- /ko -->
</div>
I've sort of gotten it working, using jQuery to grab the data-parts, assign the data-part's inner HTML to a property on my step model, and then use the html-binding to bind the content to each step. This works on the DOM side of things, but doing it this way means that my step content won't get data-bound.. I am pretty sure it's because I use the html binding, which does not bind the content.
Is there a way to do this with Durandal widgets, without separating each step into a new view?
Here's an implementation that uses a traditional Durandal master/detail approach in combination with a Tab widget. The tab widget only implements the tabbing functionality, while the Master controls what's pushed into it and the Detail controls the behavior/layout of itself.
Master
Viewmodel
define(['./tab', 'plugins/widget', 'knockout'], function (Tab, widget, ko) {
return {
tabs: ko.observableArray([
new Tab('Durandal', 'A ...', true),
new Tab('UnityDatabinding', 'A ...'),
new Tab('Caliburn.Micro', 'C ...')
]),
addNewTab: function() {
this.tabs.push(new Tab('New Tab ', 'A test tab.'));
}
};
});
View
<div>
<h1>Tabs sample</h1>
<!-- ko widget : {kind: 'tabs', items : tabs} -->
<!-- /ko -->
<button class="btn" data-bind="click: addNewTab">Add</button>
</div>
Detail
Viewmodel
define(['durandal/events', 'knockout'], function(events, ko) {
return function(name, content, isActive) {
this.isActive = ko.observable(isActive || false);
this.name = name;
this.content = content;
};
});
view
<div>
<div data-bind="html: description"></div>
</div>
Tab widget
Viewmodel
define(['durandal/composition', 'jquery'], function(composition, $) {
var ctor = function() { };
ctor.prototype.activate = function(settings) {
this.settings = settings;
};
ctor.prototype.detached = function() {
console.log('bootstrap/widget/viewmodel: detached', arguments, this);
};
ctor.prototype.toggle = function(model, event){
this.deactivateAll();
model.isActive(true);
};
ctor.prototype.deactivateAll = function(){
$.each(this.settings.items(), function(idx, tab){
tab.isActive(false);
});
};
return ctor;
});
View
<div class="tabs">
<ul class="nav nav-tabs" data-bind="foreach: { data: settings.items }">
<li data-bind="css: {active: isActive}">
<a data-bind="text: name, click: $parent.toggle.bind($parent)"></a>
</li>
</ul>
<div class="tab-content" data-bind="foreach: { data: settings.items}">
<div class="tab-pane" data-bind="html: content, css: {active: isActive}"></div>
</div>
</div>
Live version available at: http://dfiddle.github.io/dFiddle-2.0/#extras/default. Feel free to fork.
As I suspected, the problem with my bindings not applying, was due to the fact that I used the html binding to set the step content. When Knockout sets the HTML, it does not apply bindings to it.
I wrote my own HTML binding handler, that wraps the HTML and inserts it as a DOM-node - Knockout will hapily apply bindings to this.
(function(window, $, ko) {
var setHtml = function (element, valueAccessor) {
var $elem = $(element);
var unwrapped = ko.utils.unwrapObservable(valueAccessor());
var $content = $(unwrapped);
$elem.children().remove().end().append($content);
};
ko.bindingHandlers.htmlAsDom = {
init: setHtml,
update: setHtml
};
}(window, jQuery, ko));
Please note, this only works when the binding value is wrapped as a node - e.g within a div tag. If not, it won't render it.