Fully populated ko.observableArray will not show in Template using foreach - data-binding

I am trying to separate a list of JSON data into segments ("sliders") and have succeeded in creating a data object in the format I want, however the foreach binding is not working as expected.
HTML Template:
<div class="slide" data-bind="foreach: actionSliders">
Stuff
</div>
Here is my relevant Knockout code:
function Slider() {
this.actions = ko.observableArray([]);
}
var viewModel = {
actionSliders: ko.observableArray([])
};
viewModel.loadData = function() {
//LOAD Actions from API
jQuery.ajax({
type: 'GET',
url: 'http://'+window.location.hostname+'/api/actions/get_author_posts/',
dataType: 'json',
success: function (ActionData) {
console.log('getJSON data - Actions',ActionData.posts);
var actionSlidersCount = 0;
viewModel.actionSliders([]);
//create the first slider array
viewModel.actionSliders().push(new Slider());
viewModel.actionSliders()[0].actions([]);
jQuery.each(ActionData.posts, function(index) {
// add each action to the current slider
viewModel.actionSliders()[actionSlidersCount].actions().push(new Action(this));
//add a new slider every 5 records
var calc = (parseInt(index)+1)%5;
if(calc ==0 ){
//new slider
actionSlidersCount++;
viewModel.actionSliders().push(new Slider());
viewModel.actionSliders()[actionSlidersCount].actions([]);
}
});
console.log('ActionSliders',viewModel.actionSliders());
},
data: { },
async: true
});
};
This is what my data looks like in the console:
ActionSliders
[Slider, Slider, Slider, Slider, Slider, Slider, Slider, sortNum: function, random: function, sum: function, max: function, min: function…]
0: Slider
actions: Object[0]
__proto__: Slider
1: Slider
2: Slider
3: Slider
4: Slider
5: Slider
6: Slider
length: 7
__proto__: Array[0]
* I can access all the data with console commands:
> viewModel.actionSliders()[0].actions()[0]
Action {id: 197, title: "Turned off the tap while brushing my teeth"…}
> viewModel.actionSliders()
[ Slider, Slider, Slider, Slider, Slider, Slider, Slider]
So, as you can see, in the working model (no errors in console, no data-bind errors), the object is fully populated with data, and in the template, "stuff" should repeat 6 times - once for each Slider, but the loop isn't even working. Is there a problem with having observable arrays inside of others? Am I missing something in the way I am creating the Slider objects? Any advice is most welcome, please.

Since you pushed new Slider object into your actionSliders observableArray I guessing the structure might be like this:
actionSliders = [
{
actions = {}
},
{
actions = {}
}
];
I'm sorry if this doesn't work for your, but how if you try to bind it like this ? :
<div class="slide" data-bind="foreach: actionSliders().actions">
Stuff
</div>

I found the answer, it was a two part issue.
The reason why foreach wasn't working was because I was pushing to the function, instead of the array. I needed to use:
viewModel.actionSliders.push(new Slider());
instead of:
viewModel.actionSliders().push(new Slider());
Once I did that, the foreach worked for the main object
Then, I realized I needed to inject the data inside the class, instead of from outside. To remedy that, I modified the code like this:
var tempActionArray = [];
jQuery.each(ActionData.posts, function(index) {
//add an action to the current slider
var tempAction = new Action(this);
tempActionArray.push(tempAction);
var calc = (parseInt(index)+1)%5;
if(calc ==0 ){
//add a new slider
actionSlidersCount++;
viewModel.actionSliders.push(new Slider(tempActionArray));
//reset temp array
tempActionArray = [];
//viewModel.actionSliders[actionSlidersCount].actions([]);
}
});
function Slider(data) {
var data = data || [];
this.actions = ko.observableArray([]);
var Actions = [];
//console.log("slider data",data)
jQuery.each(data, function(index) {
//console.log("action index",data[index])
Actions.push(data[index]);
});
this.actions = Actions;
}
and now all is good in the world! :) Moving on...

Related

dojo enhanced grid server side pagination not working

I'm facing an issue when trying to perform server side pagination using an enhanced datagrid (dojo v1.10).
The first page is correctly displayed, but the widget (store ? grid ? plugin ?) seems to ignore the 'Content-Range' header value in response and does not allow to get next page.
For example with response header containing 'Content-Range: items 0-9/17', pagination displays '1 to 10 of 10 items', and next page is not available.
After some debug I see that range value is correctly read from JsonRest store (query function)
results.total = results.then(function(){
var range = results.ioArgs.xhr.getResponseHeader("Content-Range");
return range && (range = range.match(/\/(.*)/)) && +range[1];
});
...
But in fetch method from ObjectStore, totalCount value is undefined, results.length is then used:
var results = this.objectStore.query(query, args);
Deferred.when(results.total, function(totalCount){
Deferred.when(results, function(results){
if(args.onBegin){
args.onBegin.call(scope, totalCount || results.length, args);
...
Any idea ?
Thanks,
My code:
// get grid store
var restStore = new JsonRest(
{
target: "ks2/api/workflow/...",
});
var memoryStore = new Memory();
var store = Cache(restStore, memoryStore);
/*set up layout*/
var layout = [{
name: "id",
field: 'id',
width: '5%',
datatype:"string"
},
....
];
/*create a new grid*/
this.workflowGridWidget = new EnhancedGrid({
id: 'workflowGridWidget',
store: new ObjectStore({objectStore: store}),
structure: layout,
rowSelector: '20px',
plugins: {
pagination: {
pageSizes: ["10", "25", "50"],
defaultPageSize: 10,
description: true,
sizeSwitch: true,
pageStepper: true,
gotoButton: true,
maxPageStep: 4,//page step to be displayed
position: "bottom" //position of the pagination bar
}
}
});
/*append the new grid to the div*/
this.workflowGridWidget.placeAt("workflowDataGrid");
/*Call startup() to render the grid*/
this.workflowGridWidget.startup();
I found the issue: I was using a non dojo restful compliant API, and I needed to add JSON response post-processing using
aspect.after(store, "query", this.processResponse);
...
processResponse: function ks2ProcessMonitor_datagrid_WorkflowDataGrid_processResponse(deferred) {
return deferred.then(function(response) {
//process response content
return processedResponse;
});
},
This was working properly but for some reason, it has an impact on pagination. Removing this post-processing (using another API which is dojo compliant) fix the pagination issue.
Maybe I should try response post-processing using an Observable as suggested by Layke.

Get Twitter Bloodhound Typeahead suggestions without opening dropdown

I am able to get all the Twitter Typeahead suggestions with the code below after user enters an input and the typeahead:render is called. I would like to hide the dropdown all the time and get the suggestions only in an array. Is there a way to achieve this since typeahead:render would probably require the dropdown be opened.
var bloodhoundData = new Bloodhound({
datumTokenizer: Bloodhound.tokenizers.whitespace,
queryTokenizer: Bloodhound.tokenizers.whitespace,
local: localData
});
$('filter .typeahead').typeahead({
hint: true,
highlight: true,
minLength: 1
},
{
source: bloodhoundData,
limit: 99999
}).on('typeahead:render', getSuggestions);
function getSuggestions() {
var suggestions = Array.prototype.slice.call(arguments, 1);
}
Since Bloodhound.js is a standalone library, you don't have to use typeahead with it. You could tie the input for bloodhound to an ordinary text input and examine the result of the get method.
Something like this might work, with q being the text from the input (borrowed from the NFL Teams example):
var myBloodhound = new Bloodhound({
datumTokenizer: Bloodhound.tokenizers.obj.whitespace('name'),
queryTokenizer: Bloodhound.tokenizers.whitespace,
identify: function(obj) { return obj.name; },
local: localData
});
function getMyData(q, sync) {
myBloodhound.search(q, sync);
}
You can check out the bloodhound documentation here and the examples here.

Meteor: Collection loads when I navigate to a page, but not when URL is directly inputted

I'm a Meteor newb and would appreciate any help here.
I'm creating a flashcard app where you can create decks of cards. It saves the progress that one has made through a deck of cards.
It appears that when I navigate into a deck of cards by clicking on the name of a deck (a link), everything works fine. But when I directly paste the URI in, the Deck collection fails to load.
I believe that this is because of routes.js, in the lib folder, is the first to load so there is no data. I tried using the waitOn function in Router.configure, but it's still not working.
Thanks in advance for your help!
Router.route ('/:_id/:wordIndex', {
template: 'wordPage',
data: function () {
var index = this.params.wordIndex;
var id = this.params._id;
console.log(Decks);
var word = Decks.findOne ( id,
{ fields: { 'wordIds': 1 } }
);
}
}
);
I tried using the waitOn function within the router already as well:
Router.configure ( {
layoutTemplate: 'layout',
waitOn: function () { return [
Meteor.subscribe ('words'),
Meteor.subscribe ('decks')
]}
})
EDIT
Ok, it seems like I found a solution by getting the data client side instead of at the route. But I dunno. There's gotta be a better way? This seems excessive to me.
Template.wordItem.helpers({
wordObj: function () {
var controller = Iron.controller();
var index = controller.params.wordIndex;
var id = controller.params._id;
var wordsObject = Decks.findOne ( id,
{ fields: { 'wordIds': 1 } }
);
var wordId = wordsObject.wordIds[index];
return Words.findOne ( wordId );
},
Here is a sample repository I've created which is very similar to what you are trying to do.
http://meteorpad.com/pad/xKS38qEZAieEPDyLs/Leaderboard
It seems to work. I suspect your problem is elsewhere.

fullCalendar adding a class to events

I am trying to select events on fullcalendar, based on user selection.
Example: if user selects class A, then all classes with the same ID should turn green (using applied className).
I am having trouble applying classes to the other events that I can successfully select by ID. I guess my issue is combining the event objects with jQuery objects.
sample code:
eventClick: function(event) {
$(this).addClass("reg_selected"); //this works fine on selected event
var selectedID = event.id
alert(selectedID); //get event.ID, and use it to find similar ones.
var similarEvents = $("#calendar").fullCalendar('clientEvents',selectedID).addClass("reg_selected");
the error I get is:
addClass is not a function
I also tried this method of looping, and got the same error:
for (var i = 0; similarEvents.length > i ; i++){
alert(similarEvents[i].title);
similarEvents[i].className("reg_selected");
}
the alert() worked, but the className() generated the same error as above
This answer for a very similar situation, but when event classes are selected with round-trip to the event source for possible persistence in the db or checks.
Class name can be specified in the event object in the source as follows (start and end given for the context only):
[{
...
"className": "selected-event",
"start": '2017-05-01T08:30:00.0',
"ends": '2017-05-01T09:00:00.0',
...
}, ...]
The idea is that user clicks the event; ajax call to select events goes to backend; onsuccess, frontend javascript does$calendar.fullCalendar('rerenderEvents'); and receives the event source with events' classes. The immediate child of .fc-event-container gets the specified class, in the example above - selected-event.
As a result, the selection can be persisted on the backend.
clientEvents returns an array of matching objects. You need to iterate through the array (in your case similarEvents) and call addClass for each item
Update:
There is also issues using an id to update multiple events, using a filter function instead is a better way to go.
eventClick: function(event) {
var similarEvents = $("#calendar").fullCalendar('clientEvents', function(e) { return e.test === event.test });
for (var i = 0; similarEvents.length > i ; i++){
similarEvents[i].className = 'reg_selected';
$('#calendar').fullCalendar('updateEvent', similarEvents[i]);
}
},
See jsfiddle
For fullcalendar add event class, id and title see this.
if($('#eventTitle').val() == "Avilable") {
eventClass = "avilable";
}else {
eventClass = "unavilable";
}
$myCalendar.fullCalendar('renderEvent', {
id:response,
title: title.val(),
start: start.val(),
end: end.val(),
allDay: true,
className: eventClass,
color: color
}, true
);
I was able to get it working with the following code:
eventRender: function (eventObj, $el) {
$el.addClass(eventObj.ClassName);
},
eventObj.ClassName = "calendar-priority-warning"

How to update a Dojo Grid cell value using a TooltipDialog (and DropDownButton)

I have a dojo grid which is using some editable dijit form fields. All is well, until I try ot implement an country (multi) select cell as an Tooltip Dialog; i.e., show a drop down button which opens the tooltip dialog populated with a checkbox array to select one or more country. Once checked and clicked OK, the cell should update with a list of selected countries. Obviously I'll take care of updating the server via the store later on.
I've implemented a country select tooltip dialog which works fine like so:
dojo.provide("CountrySelector");
dojo.declare(
"CountrySelector",
[dijit.form.DropDownButton],
{
label: 'Countries',
dropDown: new dijit.TooltipDialog({ execute: function() {
console.log("EXECUTE : ", arguments[0]);
this.value = arguments[0].country;
}, href:'/cm/ui/countries' }),
postCreate: function() {
this.inherited(arguments);
this.label = this.value;
dojo.connect(this.dropDown, 'onClose', function() { console.log('close'); });
console.log("CountrySelect post create", this);
},
}
);
And the grid cell is typed as:
{ name: 'Countries', field: 'targeting.countries', editable: true, hidden: false, type:dojox.grid.cells._Widget, widgetClass: CountrySelector },
All is working fine but I can't figure out how to update cell's content and store once the widget is executed. As well, I don't seem to have the row id of the updated row.
Any ideas?
Thanks,
Harel
//Layout:
gridLayout: {rows: [{name: 'Coll Name',field: 'colField', type: dojox.grid.cells.ComboBox, editable:'true', width:'8%',options: [], alwaysEditing:false}]}
//Grid Store:
this.gridStore = new dojo.data.ItemFileReadStore({data: {items: data}});
//
var setOptions = function(items, request){
this.gridLayout.rows[0].options.push('Val 1','Val 2');
this.gridLayout.rows[0].values.push('1','2');
dojo.connect(this.gridLayout.rows[0].type.prototype.widgetClass.prototype, "onChange",this, "_onComboChange");
}
this.gridStore.fetch({onComplete: dojo.hitch(this,setOptions)});
_onComboChange: function (selectedOption) {
console.info("_onComboChange: ",selectedOption);
},
// If you need to populate combos with different values you can use onItem
var getArray = function(item, request){
// populate one by one
// attach an event to each combo
}
this.gridStore.fetch({onItem: dojo.hitch(this,getArray)});
This is what i used to update my grid
var idx = yourGrid.getItemIndex(item);
if (idx >- 1) {
yourGrid.updateRow(idx);
}
More detail
every row is identified by its identifier
yourGrid.store.fetchItemByIdentity({
identity: <yourIdentity>,
onItem: function(item){
// Update your attributes in the store depending on the server response
// yourGrid.store.setValue(item, <attribute>,<value>);
var idx = yourGrid.getItemIndex(item);
if (idx >- 1) {
yourGrid.updateRow(idx);
}
}
});
I didn't set up a test with your code but you should be able to do it by just creating a method named getValue in your widget that returns the value.
Take a look at the other examples (like dojox.grid.cells.ComboBox) to get an idea of what getValue should look like.

Resources