Array with dropdowns in Knockout JS - javascript

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());

Related

How to filter the elements in dropdown?

I have a scenario, in that I have to filter the elements in dropdown based on input given in text box. Is it possible, so how to do that? I tried with ng-change but it is not getting.
Html code:
<input type="text" ng-model="somvar" ng-change="someFunc(somvar)" />
<div id="dropdown" class="dropdownClass">
<ul class="predtsct">
<li class="prfdwn" ng-repeat="list in countries| filter: somevar" ng-click="countryIdFunc(countriesLst.countryId)">{{list.countryName}}</li>
</ul>
</div>
From the fiddle I posted in the comments: A dropdown works no differently that an ordinary list, except that the styling is different. This example uses Bootstrap to do the dropdown, but you can find many other examples if you google them.
Working version: https://jsfiddle.net/jorgthuijls/6gvp2z9h/
This is the entire view:
<div ng-controller="MyCtrl">
city: <input type="text" ng-model="search.city">
<div class="dropdown">
<button class="btn btn-default dropdown-toggle" type="button" id="dropdownMenu1" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
Dropdown
<span class="caret"></span>
</button>
<ul class="dropdown-menu" aria-labelledby="dropdownMenu1">
<!-- the 'search' bit in the filter maps to the 'search' variable on $scope -->
<li ng-repeat="user in users | filter:search:strict">{{user.name}}, {{user.city}}</li>
</ul>
</div>
</div>
The controller is also pretty small:
var myApp = angular.module('myApp', []);
function MyCtrl($scope) {
$scope.search = {};
$scope.search.city = 'new york';
$scope.users = [{
name: 'bob',
city: 'hong kong'
}, {
name: 'jack',
city: 'new york'
}, {
name: 'john',
city: 'new hampshire'
}]
}
You can use Typeahead.js it provides very flexible control of drop-down lists and filtering of it.

Show Data on click a parent angular

please am very new new to angular and am trying to do something i don't know if its possible. I have a json data and i want to render the option on click of an item. What i want to achieve is to show models of a phone on click of the name, and on click of a phone model show all the phone parts. But whenever i click on a phone i get undefined in my console.
my app.js file
(function(){
var app=angular.module('repairshop',[]);
app.controller('phoneController',function($scope){
this.phones = [
{
name: 'Apple',
model: [{ name: 'Iphone 5'}, {name: 'Iphone 6'},{name: 'Iphone 6s'}],
part: [{name:'ear phones'},{name:'external speakers'},{name:'Screen Guard'},{name:'Charger'}]
},
{
name: 'Samsung',
model: [{ name: 'S4'}, {name: 'S5'},{name: 'S6'}],
part: [{name:'ear phones'},{name:'external speakers'},{name:'Screen Guard'},{name:'Charger'}]
},
{
name: 'Nokia',
model: [{ name: 'Lumia'}, {name: '3310'},{name: 'Asha'}],
part: [{name:'ear phones'},{name:'external speakers'},{name:'Screen Guard'},{name:'Charger'}]
},
{
name: 'Blackberry',
model: [{ name: 'Passport'}, {name: 'Touch 10'},{name: 'Asha'}],
part: [{name:'ear phones'},{name:'external speakers'},{name:'Screen Guard'},{name:'Charger'}]
}
];
$scope.loadModels=function(phone)
{
var phone=phone.name;
console.log (phone);
}
});
})();
My Html View
<div class="phones_wrapper row">
<!--begin container-->
<div class="container">
<form class="" action="#" method="post" ng-controller="phoneController as phone" >
<div class="row">
<div class="col-md-3 phone_display center" ng-repeat="p in phone.phones">
<label>
<input type="radio" ng-click="loadModels(phone)" ng-model="phone.name" name="phone" ng-value="{{p.name}}" />
<img src="http://placehold.it/200x200">
</label>
<p class="text text-center phone_name">{{p.name}}</p>
</div>
</div>
</form>
</div>
<!--begin container-->
</div>
Here is a simple example plnkr on how you can click on the list of the items you describe to view more details such as models and parts.
<body ng-controller="MainCtrl">
<ul>
<li ng-repeat="phone in phones" ng-click="showModels = true">{{phone.name}}
<ul ng-show="showModels">
<li ng-repeat="model in phone.model" ng-click="showParts = true">{{model.name}}
<ul ng-show="showParts">
<li ng-repeat="part in phone.part">
{{part.name}}
</li>
</ul>
</li>
</ul>
</li>
</ul>
</body>
Basically what you have to do is add repeaters and ng-click events to expand the content. I am pretty sure that from this example you will be able to fix it with your own styling and markup.
Edit:
A better example where you can toggle the viewing of models and parts.
<body ng-controller="MainCtrl">
<ul>
<li ng-repeat="phone in phones" ng-click="showModels = !showModels; $event.stopPropagation()">{{phone.name}}
<ul ng-show="showModels">
<li ng-repeat="model in phone.model" ng-click="showParts = !showParts; $event.stopPropagation()">{{model.name}}
<ul ng-show="showParts">
<li ng-repeat="part in phone.part">
{{part.name}}
</li>
</ul>
</li>
</ul>
</li>
</ul>
</body>
I found it, i changed the loadModels function to
$scope.loadModels=function(phone)
{
phone.parent=phone.name;
console.log (phone);
}
and also changed the phone to p based on #Praneeth Gudumasu recommendation. Thank you
it should be like this
<div class="col-md-3 phone_display center" ng-repeat="p in phone.phones">
<label>
<input type="radio" ng-click="loadModels(p)" ng-model="p.name" name="phone" ng-value="{{p.name}}" />
<img src="http://placehold.it/200x200">
</label>
<p class="text text-center phone_name">{{p.name}}</p>
</div>
you should use the repeating object inside the repeating template and not the scope object(in your case variable "p" not "phone")

knockout js prevents existing html observable from updating

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>

Not able to bind static data using Knockout JS

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/

Durandal widgets, dynamic templated parts

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.

Categories