Dojo Drag and drop: how to retrieve order of items? - javascript

I've created a Source object and configured (via the creator) so that it renders a set of data for my users to order as they wish. This is all working fine. However, I cannot figure out how to retrieve the data once the user has re-ordered it. getAllNodes returns the dom nodes; I need the original data objects.

It is really easy — just use getItem() (described in the official documentation). Something like that will give you all data elements in order:
var source = new dojo.dnd.Source(...);
...
var orderedDataItems = source.getAllNodes().map(function(node){
return source.getItem(node.id).data;
});
That's it.

Related

How to add extender to existing observable in Knockout

I've looked at this:
http://knockoutjs.com/documentation/extenders.html
The issue is that I'm using fromJs to create my view model, so my observerables already exist. I would think I could do the following to add an extender:
var data = result.Data;
if (!window.vmRealTimeActivity) {
window.vmRealTimeActivity = ko.mapping.fromJS(data, mappingKeys);
ko.applyBindings(vmRealTimeActivity, $('#second-btm')[0]);
} else {
ko.mapping.fromJS(data, vmRealTimeActivity);
}
vmRealTimeActivity.MyExistingObservable.extend({ numeric: null });
vmRealTimeActivity.MyExistingObservable(9999); // doesn't call numeric extender
My extender gets called the first time the extender is attached, but not after trying to change the value.
I read another SO post that stated that .extend() creates a new observerable so you have to do this, but this doesn't work either:
vmRealTimeActivity.MyExistingObservable = vmRealTimeActivity.MyExistingObservable.extend({ numeric: null });
In addition to not calling my formatter a second time, the value starts coming back NaN.
How do I attach an extender the proper way to an existing observable?
Since you are using the mapping plugin, you could specify a create callback. If you add the following to the existing mappingKeys, it would probably work (I don't know your exact mapping, so you might need to change bits here and there):
'MyExistingObservable': {
create: function(options) {
return new ko.observable(options.data).extend({ numeric: null });
}
}
This result in an extended observable upon mapping from yor data.
Here's a jsFiddle with a working example (vm1) and your current non-working example (vm2) for comparison
The above answer is correct, but for anyone interested, I found the simpler approach is to just create your view models client side and use fromJs to refresh them rather than both create and refresh them. You can then apply the answer here to support adding extend to both your parent and child view models: Map JSON data to Knockout observableArray with specific view model type
With either approach you will have to create additional mappings.

Dojo 1.9: Dijit: Disabling option items in a dijit/Form/FilteringSelect that was populated using a store

I am trying to disable option items in a dijit/Form/FilteringSelect control that is populated using a store.
Following this guide: http://dojotoolkit.org/documentation/tutorials/1.9/selects_using_stores/
It seems to be only possible if the Select control was created without using a store. I have deduced this from debugging the FilteringSelect example. I have tried two methods to disable an item:
Following the advice in this thread: How to disable a single option in a dijit.form.Select?. However, the "stateStore" store object in the FilteringSelect example does not have an 'options' property.
Attempting to access the appropriate element in the store object. For example, in the FilteringSelect example, I do the following:
var optionItem = stateStore.get("AZ");
optionItem.disabled = true;
stateStore.put(optionItem);
select.startup();
Neither method seems to work, so it seems that the only way to have disabled items in Dijit Select controls is to use the options property instead.
Thanks in advance for a solution!
There is a difference between the data in your store (which is in fact the business data) and your rendered data (containing view logic). If you use a store, you're actually feeding your rendered data with your store.
To alter the rendered data (= the options in your select), you need to use the getOptions(idx) method of the dijit/form/Select as you can read in the API documentation. To alter the disabled state of the option you can use:
registry.byId("mySelect").getOptions(myId).disabled = true;
That's all you need. Changing the store data won't help, since it represents business data, not view data. I also made an example JSFiddle where the second option is disabled.
for dojo 1.10 and upto 1.x latest version, you need to add a line of code to update the selection UI:
registry.byId("mySelect").getOptions(myId).disabled = true;
registry.byId("mySelect").updateOption(myId);

Building pagination controls with Knockout.JS

I've inherited a project which uses Knockout.JS to render a listing of posts. The client has asked that this listing be paginated and I'm wondering if this is possible and appropriate using Knockout.JS. I could easily achieve this in pure JavaScript but I'd like to use Knockout (if appropriate) for consistency.
From what I can tell, the page uses a Native Template in the HTML of the page. There is a ViweModel which stores the posts in a ko.ObservableArray() and a post model.
The data is loaded via a jQuery ajax call where the returned JSON is mapped to post model objects and then passed into the ObservableArray which takes care of the databinding.
Is it possible to amend the ViewModel to bind pagination links (including "previous" and "next" links when required) or would I be better off writing this in plain JS?
It should be easy enough to build a computed observable in knockout that shows a "window" of the full pagelist. For example add to the view model:
this.pageIndex = ko.observable(1);
this.pagedList = ko.computed(function() {
var startIndex = (this.pageIndex()-1) * PAGE_SIZE;
var endIndex = startIndex + PAGE_SIZE;
return this.fullList().slice(startIndex, endIndex);
}, this);
Then bind the "foreach" binding showing the record to pagedList instead of the full list, and in the forward and back links, simply change the value of pageIndex. Starting from there, you should be able to make it more robust/provide more functionality.
Also, this assumes you preload all data to the client anyway. It's also possible to make JSON calls on the previous and next link and update the model with the returned items. The "next" function (to be added to the view model prototype), could look like this:
ViewModel.prototype.next = function() {
var self = this;
this.pageIndex(this.pageIndex()+1);
$.ajax("dataurl/page/" + this.pageIndex(), {
success: function(data) {
self.dataList(data);
}
});
}
(using jQuery syntax for the ajax call for brevity, but any method is fine)
Writing features in KO always tend to generate less code and cleaner code than doing the same in "plain JS", jQuery or similar. So go for it!
I implemented a combobox with paging like this
https://github.com/AndersMalmgren/Knockout.Combobox/blob/master/src/knockout.combobox.js#L229
In my blog post, I have explained in very detail how to do it. you can find it (here. http://contractnamespace.blogspot.com/2014/02/pagination-with-knockout-jquery.html). It's very easy to implement and you can do it with a simple JQuery plugin.
Basically, I have used normal knockout data binding with AJAX and after data has been retrieved from the server, I call the plugin. You can find the plugin here. its called Simple Pagination.

Collection sorting with comparator only works after fetch is complete?

I have a simple collection of messages that I want to reverse sort on time (newest on top), using comparator:
...
this.comparator = function(message) {
var time = new Date(message.get("time")).getTime();
return time;
}
...
In my view, I use fetch and add event:
messages = new MessageCollection();
messages.fetch({update: true});
messages.on("add", this.appendMessage);
...
appendMessage: function(message) {
var messageView = new MessageView({
model: message
});
this.$el.prepend(messageView.render().el);
}
Sadly, the messages are not rendered in the order I am looking for, but in the original order they were in coming from the server.
Now, after some testing I found out that when I add all the messages at once (using reset), the order is as I expected.
messages.fetch();
messages.on("reset", this.appendCollection);
...
appendCollection: function(messages) {
messages.each(function(message) {
this.appendMessage(message);
}, this);
}
Even though I can understand this process since a collection probably can only figure out how it's supposed to be sorted after all models are added, this (the on("add") configuration) used to work in Backbone 0.9.2.
Am I missing something? Did the comparator method change, or the event model in regard to add? Or am I going at it the wrong way? Thanks!
You call appendMessage method when you add a model in collection. the appendMessage is being called in the order of adding models and not the actual order in the collection.
In the "add" case, the model is inserted in the right position in the collection, as it should be by "comparator" documentation). But then you are doing
this.$el.prepend(messageView.render().el);
which will put the html from the MessageView rendering at the top of the $el (which I assume is the CollectionView container).
The best way to also keep the Html respecting the sorted order would be to re-render the collection view, or scroll the collection view children and insert the added messageView at the right place (a bit more difficult to do).

Persisting & loading metadata in a backbone.js collection

I have a situation using backbone.js where I have a collection of models, and some additional information about the models. For example, imagine that I'm returning a list of amounts: they have a quantity associated with each model. Assume now that the unit for each of the amounts is always the same: say quarts. Then the json object I get back from my service might be something like:
{
dataPoints: [
{quantity: 5 },
{quantity: 10 },
...
],
unit : quarts
}
Now backbone collections have no real mechanism for natively associating this meta-data with the collection, but it was suggested to me in this question: Setting attributes on a collection - backbone js that I can extend the collection with a .meta(property, [value]) style function - which is a great solution. However, naturally it follows that we'd like to be able to cleanly retrieve this data from a json response like the one we have above.
Backbone.js gives us the parse(response) function, which allows us to specify where to extract the collection's list of models from in combination with the url attribute. There is no way that I'm aware of, however, to make a more intelligent function without overloading fetch() which would remove the partial functionality that is already available.
My question is this: is there a better option than overloading fetch() (and trying it to call it's superclass implementation) to achieve what I want to achieve?
Thanks
Personally, I would wrap the Collection inside another Model, and then override parse, like so:
var DataPointsCollection = Backbone.Collection.extend({ /* etc etc */ });
var CollectionContainer = Backbone.Model.extend({
defaults: {
dataPoints: new DataPointsCollection(),
unit: "quarts"
},
parse: function(obj) {
// update the inner collection
this.get("dataPoints").refresh(obj.dataPoints);
// this mightn't be necessary
delete obj.dataPoints;
return obj;
}
});
The Collection.refresh() call updates the model with new values. Passing in a custom meta value to the Collection as previously suggested might stop you from being able to bind to those meta values.
This meta data does not belong on the collection. It belongs in the name or some other descriptor of the code. Your code should declaratively know that the collection it has is only full of quartz elements. It already does since the url points to quartz elements.
var quartzCollection = new FooCollection();
quartzCollection.url = quartzurl;
quartzCollection.fetch();
If you really need to get this data why don't you just call
_.uniq(quartzCollecion.pluck("unit"))[0];

Categories