Knockout dropdown cascade option not update - ajax

I'm trying to populate default value for dependent dropdown using knockout.
When values are harcoded it works, but I need to get values from ajax request and then, the second dropdown option is not updated. The value self.selectedState is updated but I guess that as I haven't already the options populated, then value in select is not bind. This is my code so far:
function myViewModel(country, state) {
var self = this;
self.selectedCountry = ko.observable();
self.selectedState = ko.observable();
self.availableCountries = ko.observableArray([
{
id: 1, name: 'United States', states: [
{ id: 1, name: 'Alabama' },
{ id: 2, name: 'California' },
]
},
{
id: 2, name: 'Canada', states: [
{ id: 53, name: 'Alberta' },
]
}
]);
self.availableStates = ko.observableArray([]);
self.selectedCountry.subscribe(function() {
self.availableStates([]);
for (var i = 0; i < self.availableCountries().length; i++) {
if (self.availableCountries()[i].id == self.selectedCountry()) {
self.availableStates(self.availableCountries()[i].states);
break;
}
}
});
self.selectedCountry(1).selectedState(2);
}
var viewModel = new myViewModel();
ko.applyBindings(viewModel);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<select data-bind="options: availableCountries, optionsText: 'name', optionsValue: 'id', optionsCaption: 'Select a country...',
value: selectedCountry"></select>
<select data-bind="options: availableStates, optionsText: 'name',optionsValue: 'id', value: selectedState, visible: availableStates().length > 0" style="display:none"></select>
Is there something special that needs to be done when options are obtained from ajax?
jsfiddle

The issue isn't AJAX, specifically, but the fact that self.selectedState doesn't have a corresponding option for some time (while the options are being fetched).
From the docs:
Normally, when you use the value binding on a <select> element, it
means that you want the associated model value to describe which item
in the <select> is selected. But what happens if you set the model
value to something that has no corresponding entry in the list? The
default behavior is for Knockout to overwrite your model value to
reset it to whatever is already selected in the dropdown, thereby
preventing the model and UI from getting out of sync.
However, sometimes you might not want that behavior. If instead you
want Knockout to allow your model observable to take values that have
no corresponding entry in the <select>, then specify valueAllowUnset:
true. In this case, whenever your model value cannot be represented in
the <select>, then the <select> simply has no selected value at that
time, which is visually represented by it being blank.

This is a perfect opportunity to make use of a computed observable. A computed seems to a good fit for this job because it will save you from setting up your own manual subscriptions at track dependencies for you. Give it a try - I've changed very little of your code to accomplish this..
function myViewModel(country, state) {
var self = this;
self.selectedCountry = ko.observable();
self.selectedState = ko.observable();
self.availableCountries = ko.observableArray([{
id: 1,
name: 'United States',
states: [{ id: 1, name: 'Alabama' },
{ id: 2, name: 'California' }, ]
},
{ id: 2,
name: 'Canada',
states: [{ id: 53, name: 'Alberta' }, ]
}]);
self.availableStates = ko.computed(function() {
var states = [];
for (var i = 0; i < self.availableCountries().length; i++) {
if (self.availableCountries()[i].id == self.selectedCountry()) {
states = states.concat(self.availableCountries()[i].states);
break;
}
}
return states;
});
self.selectedCountry(1).selectedState(2);
}
var viewModel = new myViewModel();
ko.applyBindings(viewModel);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<select data-bind="options: availableCountries, optionsText: 'name', optionsValue: 'id', optionsCaption: 'Select a country...',
value: selectedCountry"></select>
<select data-bind="options: availableStates, optionsText: 'name',optionsValue: 'id', value: selectedState, visible: availableStates().length > 0" style="display:none"></select>

Related

How to make a string data type column as checkbox in Kendo Grid?

I have a string field IsEnabled, it’s string. Value can be Yes, No or null. I am binding this column to grid column. It’s working as expected. But I want to show this on UI as checkbox. For value Yes, it should be checked or No or null it should be unchecked. And user can check/uncheck, based on user’s action. Yes or NO will be inserted in database.
I couldn’t find proper way of doing this, so what is the best way to handle this scenario?
I have tried by by adding one more bool field and setting it based on value Yes, No or null. And binding this field to grid.
But I am looking for a clean approach
You can use the column template (documentation) to return a checkbox where the value is checked when the record's IsEnabled is Yes.
Then in the dataBound event of the grid (documentation) you would setup the event binding for the checkbox to get when the value changed.
Here is an example:
$('#grid').kendoGrid({
columns: [
{
field: 'Name'
},
{
field: 'name',
template: function(dataItem) {
var checkbox = $('<input />', {
checked: dataItem.IsEnabled === 'Yes',
type: 'checkbox'
});
return checkbox.prop('outerHTML');
}
}
],
dataSource: [
{
Id: 1,
Name: 'Person1',
IsEnabled: 'Yes'
},
{
Id: 2,
Name: 'Person2',
IsEnabled: 'No'
},
{
Id: 3,
Name: 'Person3',
IsEnabled: 'No'
}
],
dataBound: function(e) {
e.sender.table.on('change', 'input[type="checkbox"]', function() {
var checked = this.checked;
// send your AJAX request
});
}
});
Fiddle: https://dojo.telerik.com/igiTASAc
As I was looking for a Kendo MVC solution, So I have implemented it like the below.
Declare the property like this.
[UIHint("DropDownTemplate")]
public IsAllowedCls IsAllowed { get; set; }
public class IsAllowedCls
{
public int IsAllowedKey { get; set; }
public string IsAllowedValue { get; set; }
}
Add view under Views\Shared\EditorTemplates create the view named as DropDownTemplate with below content
#model FxTrader.Models.IsAllowedCls
#(Html.Kendo().DropDownList()
.Name("DropDownTemplate")
.DataValueField("IsAllowedKey")
.DataTextField("IsAllowedValue")
.BindTo((System.Collections.IEnumerable)ViewData["IsAllowedData"])
)
In the controller action method, add the below code.
ViewData["IsAllowedData"] = new List<IsAllowedCls>() { new IsAllowedCls { IsAllowedKey = 1, IsAllowedValue = "Yes" },
new IsAllowedCls { IsAllowedKey = 0, IsAllowedValue = "No" } };

Kendo UI grid column template with nested evaluations - how to?

Kendo UI v2015.2.805
I have a KendoGrid with a template column that does a conditional to determine if a set of buttons should be added, if so additional evaluations are needed, and I can't figure out how to nest them.
The below works but does not have the required additional evaluation:
{ field: "Served", title: "Served",
template: "<div>" +
"#= (Kind==0 || Kind==7) ? '" +
"<button type=\"button\" data-id=\"12345\">Yes</button>" +
"<button type=\"button\" data-id=\"54321\">No</button>" +
"' : " +
"'NO BUTTON HERE'" +
"#</div>"
I multi-lined it to try to get it to look good, which it does not. The idea is that if the Kind = 0 or 7 then show two buttons otherwise do not. Works great.
However I need to evaluate the data-id to #= ID #, so I try:
" <button type=\"button\" data-id=\"' #= ID # '\">Yes</button>"
I know I need to 'drop out' of the quoted string to get the evaluation to work and since I have used double quotes for the whole expression I am returning the button in the conditional as a single quoted string, and as such escaping the button attributes, but I can't get it to evaluate the #=.
I've tried so many different combinations I've lost track.
So what is the 'right-way' to do this?
A SOLUTION:
Accepting David's answer with a modification to use template evaluation in the function:
{ field: "Served", title: "Served",
template: function (data) {
switch (data.Kind) {
case 0:
case 7:
var template = kendo.template("<button type='button' data-id='#= ID #' >Yes</button><button type='button' data-id='#= ID #'>No</button>");
return template(data);
default:
return '';
}
}
Having the function perform the initial test removes one level and allows 'normal' evaluation to occur.
You can use a function instead I Beleive it will would make things so much easier for you.
your template can be "#= buildButtons(data) #"
function buildButtons(model) {
if (model.Kind == 0 || model.Kind == 7) {
return "hello world";
}
return "";
}
here is a code sample
https://dojo.telerik.com/UQuqAfuv
<div id="grid"></div>
<script>
var people = [
{ id: 1, firstName: 'David', lastName: 'Lebee' },
{ id: 2, firstName: 'John', lastName: 'Doe' }
];
$('#grid').kendoGrid({
dataSource: {
transport: {
read: function(options) {
options.success(people);
}
}
},
columns: [
{ field: 'firstName', title: 'First Name' },
{ field: 'lastName', title: 'Last Name' },
{ title: 'Actions', template: '#= buildActions(data) #'}
]
});
function buildActions(model) {
if (model.firstName == "David") {
return 'Hello David';
}
return '';
}
</script>

Emberjs advanced sort hasMany association as a computed property

I have asked a variant of this question here. But basically I need to create a computed property that operated on a hasMany association. I need to do sorting similar to the javascript sort function; where I can do something like
files = ["File 5", "File 1", "File 3", "File 2"];
files.sort(function(a,b){
return parseInt(b.split(' ').pop()) - parseInt(a.split(' ').pop())
});
result:
["File 5", "File 3", "File 2", "File 1"]
Here is my jsbin:
http://emberjs.jsbin.com/simayexose/edit?html,js,output
Any help would be greatly appreciated.
Note:
My jsbin presently is not working correctly (for reasons other then this question). I have posted a question about that here. I just did not want to hold up an answer to this question.
Update 1
Thanks #engma. I implemented the instructions. As a matter of fact, I copied and pasted what was posted. This is the new jsbin.
http://emberjs.jsbin.com/roqixemuyi/1/edit?html,js,output
I still do not get anything sorted, though. And even if it did, it still would not have sorted the way I would like it.
I need something like the following: (below are errors that I get when I try to implement this in my code, not from jsbin, since I can not get jsbin to work)
sortedFiles: function(){
return this.get('files').sort(function(a,b){
return parseInt(b.split(' ').pop()) - parseInt(a.split(' ').pop());
});
}.property('files.#each.name')
When I do this I get the following error:
Uncaught TypeError: this.get(...).sort is not a function
So since this.get('files') returns a promise, I figured I would try this;
sortedFiles: function(){
return this.get('files').then(function(files){
return files.sort(function(a,b){
return parseInt(b.split(' ').pop()) - parseInt(a.split(' ').pop());
});
});
}.property('files.#each.name')
But then I get the following error:
Uncaught Error: Assertion Failed: The value that #each loops over must be an Array. You passed {_id: 243, _label: undefined, _state: undefined, _result: undefined, _subscribers: }
BTW, I am using emberjs v1.11.0
And, the sortBy I am using is ember-cli/node_modules/bower-config/node_modules/mout/array/sortBy.js
Here is the code for it
var sort = require('./sort');
var makeIterator = require('../function/makeIterator_');
/*
* Sort array by the result of the callback
*/
function sortBy(arr, callback, context){
callback = makeIterator(callback, context);
return sort(arr, function(a, b) {
a = callback(a);
b = callback(b);
return (a < b) ? -1 : ((a > b) ? 1 : 0);
});
}
module.exports = sortBy;
Update 2
So to answer the question how to do an Emberjs advanced sort hasMany association as a computed property; I had to change
this.get('files').sort(function(a,b){
...
});
return this.get('files').toArray().sort(function(a,b){
...
});
This allowed me to use the javascript sort and return the desired sorted objects.
Ok first of all your JSBin had many issues so lets go throw them one by one
1- you did not include any Ember-Data build, so I included 1, this is needed for the fixtures and the models
<script src="http://builds.emberjs.com/tags/v1.0.0-beta.15/ember-data.js"></script>
2- Your Scripts
var App = window.App = Ember.Application.create({
});
//First this is how to register the adapter
App.ApplicationAdapter = DS.FixtureAdapter.extend({});
App.IndexRoute = Ember.Route.extend({
model: function() {
//Second with find you pass in the ID so I am using 1
//if you want to get all folders use findAll()
return this.store.find('folder',1);
}
});
App.IndexController = Ember.Controller.extend({
});
App.Router.map(function() {
});
App.Folder = DS.Model.extend({
name: DS.attr('string'),
files: DS.hasMany('file',{async:true}),
sortedFiles: function(){
//Sorty By has no second parameter, if you need more sorting power, do it your self
return this.get('files').sortBy('name');
}.property('files.#each.name')
});
App.File = DS.Model.extend({
name: DS.attr('string'),
folder: DS.belongsTo('folder',{async:true})
});
App.File.FIXTURES = [
{
id: 1,
name: 'File 5',
folder:1
},
{
id: 2,
name: 'File 1',
folder:1
},
{
id: 3,
name: 'File 3',
folder:1
},
{
id: 4,
name: 'File 2',
folder:2
},
{
id: 5,
name: 'File 6',
folder:2
},
{
id: 6,
name: 'File 4',
folder:2
}
];
App.Folder.FIXTURES = [
{
id: 1,
name: 'Folder 1',
files:[1,2,3]
},
{
id: 2,
name: 'Folder 2',
files:[4,5,6]
}
];
Your Template:
<div>
Folders: <br>
<ul>
<li>
Name: {{model.name}} <br>
Files:
{{!-- here we access the sorted files property in the model--}}
{{#each file in model.sortedFiles}}
{{file.name}} <br/>
{{/each}}
</li>
</ul>
</div>

backbone- go through collection, and render in correct order?

How to be sure that all of the view-s will be displayed in correct order. because of use of Ajax, first one finished will be displayed first, i want to always be displayed in right order...
_.each(view.collection.models, function (category) {
var productBlockListView = new ProductBlockListView({model: category});
productBlockListView.setElement(view.el).render();
}, this);
Use a comparator option to get a sorted collection.
var items = new Backbone.Collection([{
firstName: 'Steve',
lastName: 'Jobs'
}, {
firstName: 'Bill',
lastName: 'Gates'
}], {comparator: 'firstName'});
items.each(function(item) {
console.log('%s %s', item.get('firstName'), item.get('lastName'));
});
Documentation
Demo

ExtJS4 dataView - Select node id

I have an ExtJS 4 dataView and i would like to catch the id of a selected node.
It is the first time that i'm using the dataView, then, there are some troubles.
The store is loaded correctly and i see the datas into the view very well. The problem which i'm having, concern the "classic" actions of update and delete, particularly getting the id of a selected item.
For example into a grid i click, then select a record and through a button's pressing i open a window (or other actions) with a loaded form (by sending in AJAX to the store, the id of the selected row) and i update the datas.
I'm not still able to do it with the ExtJS 4 dataView.
Below my dataView:
dataView_player = Ext.create('Ext.Panel', {
id: 'images-view',
frame: true,
collapsible: false,
autoWidth: true,
title: 'Giocatori (0 items selected)',
items: [ Ext.create('Ext.view.View', {
id:'players-view',
store: store_player,
multiSelect: true,
height: 310,
trackOver: true,
overItemCls: 'x-item-over',
itemSelector: 'div.thumb-wrap',
emptyText: 'Nessun giocatore visualizzato',
tpl: [
'<tpl for=".">',
'<div class="thumb-wrap" id="{id}-{name}">',
'<div class="thumb">',
'<img src="/img/players/{picture}" title="{name} {surname}" alt="{name} {surname}" style="">',
'</div>',
'<span class="" style="height:30px;">{general_description}{name} {surname}</span>',
'</div>',
'</tpl>',
'<div class="x-clear"></div>'
],
plugins: [
Ext.create('Ext.ux.DataView.DragSelector', {}),
Ext.create('Ext.ux.DataView.LabelEditor', {dataIndex: 'name'})
],
prepareData: function(data) {
Ext.apply(data, {
name: data.name,
surname: data.surname,
general_description: Ext.util.Format.ellipsis(data.general_description, 15)
});
return data;
},
listeners: {
'selectionchange': function(record, item, index, e) {
var node = this.getNode(record); //this.getNode(record);
console.log(node.get('id'));
}
}
}) ],
dockedItems: [{
xtype: 'toolbar',
items: [{
iconCls: 'delete',
text: 'Cancella Selezionati',
scale: 'medium',
tooltip: 'Per <b>cancellare</b> i giocatori selezionati',
tooltipType: 'qtip',
id: 'delete-player',
disabled: true,
handler: delete_news
}, '-', {
iconCls: 'edit',
text: 'Aggiorna Selezionata',
scale: 'medium',
tooltip: 'Per <b>aggiornare</b> un giocatore selezionato',
tooltipType: 'qtip',
disabled: false,
id: 'update-player',
handler: function(nodes) {
var l = nodes.get('id');
console.log(l);
}
}
}
]
}]
});
Of course, this is a wrong example (because the listeners don't work) but it's just to make an idea.
There are two main things what i would like to do:
1) Catch the id (and other store's fields) of the selected item on the action "selectionchange". Obviously, now it doesn't work because of this: node.get('id'). Of course it's a wrong syntax but make up the idea of my will.
2) Catch the id of the selected item on the handler event of the "update-player" button. As above, the issue is the nodes.get('id'). Further trouble, is how to pass the selected item's features. in handler: function(nodes) { the nodes variable does not assume any value and i don't know how to pass the params from the dataview to the handler function.
I hope that somebody will able to help me.
According to the docs the selectionchange event provides the selection model as well as the array of selected records, so you are probably assuming the wrong parameters in your listener.
Without doing further testing, I think it should be something like this:
listeners: {
'selectionchange': function(selModel, selection, eOpts) {
var node = selection[0];
console.log(node.get('id'));
}
}
Note that you're using multiSelect: true, so it could be more than one record in the selection array.
Answer for second part of the question:
In button handler, you need to get selection model of the view and from it get information about selected records:
handler: function(nodes) {
// find view component
var view = dataView_player.down('dataview');
// get all selected records
var records = view.getSelectionModel().getSelection();
// process selected records
for(var i = 0; i < records.length; i++) {
console.log(records[i].getId());
}
}

Resources