This this question here
KnockoutJS - Databind to a dictionary collection
I'm creating a drop down select from JSON coming from the server.
However at some point after creating it I wish to update the data.
I've created a fiddle
http://jsfiddle.net/LPrf3/
Which shows where I'm at at present. I successfully update the select's observable array.
However... for some reason you need to click into the select from the drop down in order for it to refresh
Javascript:
$(function() {
var destinationsFromServer = {"Europe":"Europe incl Egypt, Turkey & Tunisia","ANZO":"Australia & New Zealand","WorldwideUSA":"Worldwide (incl USA & Canada)"};
var updatedDestinationsFromServer = {"Arctic":"Includes Polar bears and seals","Antarctic":"Just Penguins"};
function mapDictionaryToArray(dictionary) {
var result = [];
for (var key in dictionary) {
if (dictionary.hasOwnProperty(key)) {
result.push({ key: key, value: dictionary[key] });
}
}
return result;
}
function viewModel () {
destinations= ko.observableArray(mapDictionaryToArray(destinationsFromServer));
selectedDestination= ko.observable();
updateDestinations = function()
{
destinations= ko.observableArray(mapDictionaryToArray(updatedDestinationsFromServer));
};
};
ko.applyBindings(new viewModel());
});
HTML
<select data-bind="options: destinations, optionsText: 'key', optionsValue: 'value', value: selectedDestination"></select>
<hr />
<div data-bind="text: selectedDestination"></div>
<button data-bind="click:updateDestinations">UPDATE</button>
How can I get the select to update?
You are reassinging destinations to a new observabelArray instead of updating the array. See this fiddle. When updating any observable, always pass the new value in as a parameter, never assign a new value with the = operatior.
Wrong Way:
updateDestinations = function(){
destinations=ko.observableArray(mapDictionaryToArray(updatedDestinationsFromServer));
};
Right Way:
updateDestinations = function(){
destinations(mapDictionaryToArray(updatedDestinationsFromServer));
};
Related
I am trying to fetch data from the static json file but the data is not getting displayed at all. What could be the possible reason for it.
Below is my code:
var Collection = Backbone.Collection.extend({
url: "names_of_people.json",
initialize: function() {
this.fetch();
}
});
collections = new Collection();
console.log("the length "+collections.length);
for (i=1;i<collections.length;i++)
{
console.log("done "+ collections.at(i).get("name"));
}
The problem is that this code:
console.log("the length "+collections.length);
for (i=1;i<collections.length;i++)
{
console.log("done "+ collections.at(i).get("name"));
}
ends up being executed before this.fetch() has completed. You'll need to either put your code in this.fetch's success callback, like this:
var Collection = Backbone.Collection.extend({
url: '/data.json',
initialize: function() {
this.fetch({
success: function() {
console.log(collections, 'the length ' + collections.length);
for (var i = 0; i < collections.length; i++) {
console.log('done ' + collections.at(i).get('name'));
}
}
});
}
});
var collections = new Collection();
or by listening to the collection's sync event, which occurs when this.fetch has completed successfully. This pattern is more commonly used in Backbone applications.
var Collection = Backbone.Collection.extend({
url: '/data.json',
initialize: function() {
this.listenTo(this, 'sync', this.syncExample);
this.fetch();
},
syncExample: function() {
console.log(collections, 'the length ' + collections.length);
for (var i = 0; i < collections.length; i++) {
console.log('done ' + collections.at(i).get('name'));
}
}
});
var collections = new Collection();
You can read more about Backbone's event system and the listenTo function here.
check backbone parse function. after fetch it will also call vlidate and parse if they exist.
EDIT: more detail
The key thing here I think is, the fetch() is asynchronous, so by the time you start loop, the data is not here yet. So you need to execute the code when you are sure the collection is ready. I usually listen to a "reset" event, and let the fetch to fire a reset event by collection.fetch({reset:true}).
Backbone Collection, whenever fetch, and get an array of data from server in a format
[obj1,obj2],
it will pass each of these into a parse function, described here
For debug purpose you can simply do:
var MyCollection=Backbone.Collection.extend({
parse:function(response){
console.log(response);
return response;
}
})
This can check if the fetch indeed get the json.
On a side note, it is always a good practise to fetch it after you initialized the collection, means you don't put the this.fetch() inside initialize(), you do this outside.
for example, if you want to print out all the element name, you can do
var c=MyCollection();
c.fetch({reset:true}); // this will fire 'reset' event after fetch
c.on('reset',printstuff());
function printstuff(){
_.forEach(c,function(e){
console.log(e.get('name'));
});
}
Note this 'reset' event fires after all the collection is set, means it is after the parse() function. Apart from this parse(), there is also a validate function that is called by model. You collection must have a model parameter, you can make your own model, and give it a validate(), it also print out stuff.
I am looking to change the original text input value to a JSON value.
This randoms one of the similar artists that is searched. But I need that value to replace the input value whenever I hit the button to search.
So for example if I search Metallica, the code below would randomly pick 1 similar artist and then I want that to replace Metallica search originally set.
<input id="artists" type="text"</input>
<button id="searchy">Search Now</button>
function newartist(artist) {
var url = 'http://developer.echonest.com/api/v4/artist/similar';
var args = {
format:'json',
api_key : apikey,
name: artist,
results : 5,
};
$.getJSON(url, args,
function(data) {
var artist = data.response.artists[Math.floor(Math.random()*data.response.artists.length)];
console.log(artist.name);
}
);
}
function start() {
var artist = $.trim($("#artists").val());
}
$(document).ready(function() {
$("#searchy").click(start);
});
Is there a way to replace the original input value to my new JSON value?
Any help would be grateful, Thanks.
It sounds like you want to search on Metallica (for example), then randomly pick an artist from the results, and search again. I presume you don't want to keep looping.
If so, just call newartist from your success callback, with a flag saying not to do it again:
function newartist(artist, dontRepeat) {
var url = 'http://developer.echonest.com/api/v4/artist/similar';
var args = {
format:'json',
api_key : apikey,
name: artist,
results : 5,
};
$.getJSON(url, args,
function(data) {
var artist;
if (dontRepeat || data.response.artists.length === 0) {
// ...do something useful with the results
} else {
// Do the second search
artist = data.response.artists[Math.floor(Math.random()*data.response.artists.length)];
newartist(artist.name, true);
}
}
);
}
I'm using the following code to load all Json data.
$.getJSON("/Home/GetSortedLists", function (allData) {
var mappedSortedLists = $.map(allData, function (item) { return new SortedLists(item) });
viewModel.sortedlists(mappedSortedLists);
});
I also need to load a single record from the same Json data; the record with the highest SortedListsID value (i.e. the last record entered).
Can anybody suggest the best way to do this? I've considered adding viewModel.lastsortedlist and amending the above code somehow. I've also considered creating a last custom binding to do something like:
<tbody data-bind="last: sortedlists.SortedListID">
All advice welcome.
Unless you want to do more ui-related stuff with the record, I don't think you need the custom binding.
It should be enough to compute it in the getJSON callback and save it in the viewModel:
$.getJSON("/Home/GetSortedLists", function (allData) {
var mappedSortedLists = $.map(allData, function (item) { return new SortedLists(item) });
viewModel.sortedlists(mappedSortedLists);
//correct the sort function if it's bad, or drop it if allData is already sorted
var sortedData = allData.sort(function(a,b){ return a.SortedListID - b.SortedListID})
viewModel.lastSortedList(sortedData[sortedData.length - 1])
});
Or, if it can change outside the getJSON callback, you could also make it a computed observable:
viewModel.lastSortedList = ko.computed(function(){
//correct the sort function if it's bad, or drop it
var sortedData = mappedSortedLists().sort(function(a,b){ return a.SortedListID - b.SortedListID})
return sortedData[sortedData.length - 1]
}, this)
for populating search form with data i'm using following ViewModel:
function AppViewModel() {
var self = this;
self.Countries =[{"id": "1","name": "Russia"},{"id": "2","name": "Qatar"}];
self.selectedCountryId =ko.observable();
}
I need Countries list for populate dropdwonlist.
When user fills the form and clicks "send", i need to submit the data, but i do not need to send the Countries list!
(only SelectedCountryId)
var vm=new AppViewModel();
ko.applyBindings(vm);
$('button').click(function(){
console.log(ko.mapping.toJSON(vm));
});
Is there the way to get rid of countries list without build new ViewModel for sending?
Observables are just like normal functions, so you just need to call it from outside.
function AppViewModel() {
var self = this;
self.Countries =[{"id": "1","name": "Russia"},{"id": "2","name": "Qatar"}];
self.selectedCountryId = ko.observable('1');
}
$(function() {
var vm = new AppViewModel();
ko.applyBindings(vm);
$('button').click(function(){
console.log(vm.selectedCountryId()); // plain
console.log(ko.toJSON(vm.selectedCountryId())); // json
});
});
http://jsfiddle.net/DiegoVieira/6kZMj/4/
Please take a look into this demo I've created for you Click here for the DEMO
Updated Demo Click here for the updated Demo
HTML Code
<select data-bind="options: countries, optionsText: 'name', optionsValue: 'id', value: selectedChoice, optionsCaption: 'Choose..'"></select>
<br/>
<label data-bind="text: selectedChoice"></label>
Javascript Code
var CountryModel = function(data){
var self = this;
self.id = ko.observable(data.id);
self.name = ko.observable(data.name);
};
var viewModel = function(data) {
var self = this;
self.selectedChoice = ko.observable();
self.countries = ko.observableArray([
new CountryModel({id: "1", name: "Russia"}),
new CountryModel({id: "2", name: "Qatar"})]);
};
ko.applyBindings(new viewModel());
START UPDATE
What is updated:
HTML Code:
Added button on event Click it calls sendMe function which returns json object of selectedCountryId
<input type="button" data-bind="click: sendMe, enable: selectedChoice" Value="Click Me"/>
Javascript Code
self.sendMe = function(){
alert(ko.toJSON({ selectedCountryId: this.selectedChoice()}));
};
END UPDATE
START UPDATE1
This is update for the last comment, regarding avoid addition model, in this case let's skip CountryModel
So, the Javascript Code will be the following:
var viewModel = function(data) {
var self = this;
self.selectedChoice = ko.observable();
self.countries = ko.observableArray([
{id: "1", name: "Russia"},
{id: "2", name: "Qatar"}]);
self.sendMe = function(){
alert(ko.toJSON({ selectedCountryId: this.selectedChoice()}));
};
};
ko.applyBindings(new viewModel());
END UPDATE1
Hope, it will help you.
Thanks.
I have json array of the form:
[{"label":<some-label>,"spellings":[<list of spellings>]}, ...]
I need to parse the above array using jquery ui autocomplete. However, there are few constraints:
The autocomplete suggestions should involve matches from "spellings" but should suggest corresponding "label" only. e.g. if there are n "spellings" for a "label" then the autocomplete should show only that particular "label" for n "spellings".
On selecting from the suggestions provided, the corresponding "label" should only be reflected in the text input box.
How should I proceed with it? Any pointers?
And, how to iterate over list of "spellings" for a corresponding "label"?
This is what I'm trying to do, but giving garbled output.
var labels = []
var values = []
$.getJSON($url, function(data) {
$.each(data, function(key, val) {
for (var v in val.value)
values.push(val.value[v])
labels.push(val.label)
});
$("#text1").autocomplete({
minLength: 2,
source: values,
focus: function(event, ui) {
$("#text1").val(ui.item.label);
return false;
},
select: function(event, ui) {
$("#text1").val(ui.item.label);
return false;
}
});
});
I would build up a single source array of items, one for each spelling, where the label property is the label for each spelling and the value property is the spelling itself. This will enable you to quickly filter down results without having to iterate over each object's spelling array and check for matches which could take awhile.
Then, inside a function you define for source, you can do your own filtering logic, only allowing one instance of each "label" in the suggestions list.
Something like this should work (note that the autocomplete is initialized inside of the $.getJSON callback. This is necessary to make sure the source data is loaded before the widget is initialized):
$.getJSON($url, function(data) {
$.each(data, function (i, el) {
source.push({ label: el.label, value: el.label });
$.each(el.spellings, function (j, spelling) {
source.push({ label: el.label, value: spelling });
});
});
/* initialize the autocomplete widget: */
$("input").autocomplete({
source: function (request, response) {
var matcher = new RegExp($.ui.autocomplete.escapeRegex(request.term), "i")
, results = [];
/* Make sure each entry is only in the suggestions list once: */
$.each(source, function (i, value) {
if (matcher.test(value.value) && $.inArray(value.label, results) < 0) {
results.push(value.label);
}
});
response(results);
}
});
});
Example: http://jsfiddle.net/MaMZt/