I am using Struts 2 and want to include an editable server side paging and sorting grid.
I need to sublclass the QueryReadStore to implement the write and notification APIs. I do not want to inlcude server side REST services so i do not want to use JsonRest store. Any idea how this can be done.? What methods do i have to override and exactly how. I have gone through many examples but i am not getting how this can be done exactly.
Also is it possible to just extend the ItemFileWriteStore and just override its methods to include server side pagination? If so then which methods do i need to override. Can i get an example about how this can be done?
Answer is ofc yes :)
But do you really need to subclass ItemFileWriteStore, does it not fit your needs? A short explaination of the .save() follows.
Clientside does modify / new / delete in the store and in turn those items are marked as dirty. While having dirty items, the store will keep references to those in a has, like so:
store._pending = { _deletedItems: [], _modifiedItems: [], _newItems: [] };
On call save() each of these should be looped, sending requests to server BUT, this does not happen if neither _saveEverything or _saveCustom is defined. WriteStore simply resets its client-side revert feature and saves in client-memory.
See source search "save: function"
Here is my implementation of a simple writeAPI, must be modified to use without its inbuilt validation:
OoCmS._storeAPI
In short, follow this boiler, given that you would have a CRUD pattern on server:
new ItemFileWriteStore( {
url: 'path/to/c**R**ud',
_saveCustom: function() {
for(var i in this._pending._newItems) if(this._pending._deletedItems.hasOwnProperty(i)) {
item = this._getItemByIdentity(i);
dxhr.post({ url: 'path/to/**C**rud', contents: { id:i }});
}
for(i in this._pending._modifiedItems) if(this._pending._deletedItems.hasOwnProperty(i)) {
item = this._getItemByIdentity(i);
dxhr.post({ url: 'path/to/cr**U**d', contents: { id:i }});
}
for(i in this._pending._deletedItems) if(this._pending._deletedItems.hasOwnProperty(i)) {
item = this._getItemByIdentity(i);
dxhr.post({ url: 'path/to/cru**D**', contents: { id:i }});
}
});
Now; as for paging, ItemFileWriteStore has the pagination in it from its superclass mixins.. You just need to call it with two setups, one being directly on store meaning server should only return a subset - or on a model with query capeabilities where server returns a full set.
var pageSize = 5, // lets say 5 items pr request
currentPage = 2; // note, starting on second page (with *one* being offset)
store.fetch({
onComplete: function(itemsReceived) { },
query: { foo: 'bar*' }, // optional filtering, server gets json urlencoded
count: pageSize, // server gets &count=pageSize
start: currentPage*pageSize-pageSize // server gets &start=offsetCalculation
});
quod erat demonstrandum
Related
I am trying to use the deleteConfimation function option but I find that the default confirmation box pops up before I even get into the deleteConfimation function - what am I missing?
In the code below I can set break points and watch the data object being set up correctly with its new defaultConfirmMessage, but the basic jtable default delete confirmation box has already appeared and I never see an altered one.
$(container).jtable({
title: tablename,
paging: true,
pageSize: 100,
sorting: true,
defaultSorting: sortvar + ' ASC',
selecting: false,
deleteConfirmation: function(data) {
var defaultMessage = 'This record will be deleted - along with all its assignments!<br>Are you sure?';
if(data.record.Item) { // deleting an item
// Check whether item is in any preset lists
var url = 'CampingTablesData.php?action=CheckPresets&Table=items';
$.when(
ReturnAjax(url, {'ID':data.record.ID}, MyError)
).done(
function(retdata, status) {
if(status=='success') {
if(retdata.PresetList) {
data.deleteConfirmMessage = 'Item is in the following lists: ' + retdata.PresetList + 'Do you still want to delete it?';
}
} else {
data.cancel = true;
data.cancelMessage = retdata.Message;
}
}
);
} else {
data.deleteConfirmMessage = defaultMessage;
}
},
messages: {
addNewRecord: 'Add new',
deleteText: deleteTxt
},
actions: {
listAction: function(postData, jtParams) {
<list action code>
},
createAction: function(postData) {
<create action code>
},
updateAction: 'CampingTablesData.php?action=update&Table=' + tablename,
deleteAction: 'CampingTablesData.php?action=delete&Table=' + tablename
},
fields: tableFields --- preset variable
});
==========
After further testing the problem is only when deleting an item and it goes through the $.when().done() section of code. The Ajax call to the deletion url does not wait for this to complete - how do I overcome this?
i don't think you can get your design to work. What does the A in ajax stand for? Asynchronous! Synchronous Ajax has been deprecated for all sorts of good design and performance reasons.
You need to design you application to function asynchronously. Looking at your code, it feels you are misusing the deleteConfirmation event.
Consider changing the default deleteConfirmation message to inform the user, that the delete might not succeed if certain condition are met. Say
messages: {
deleteConfirmation: "This record will be deleted - along with all its assignments, unless in a preset list. Do you wish to try to delete this record?"
},
Then on the server, check the preset lists, and if not deletable, return an error message for jTable to display.
Depending on how dynamic your preset lists are, another approach might be to let the list function return an additional flag or code indicating which, if any, preset lists the item is already in, then your confirmation function can check this flag / indicator without further access to the server.
Thanks to MisterP for his observation and suggestions. I also considered his last approach but ended up setting deleteConfirmation to false (so as not to generate a system prompt) then writing a delete function that did not actually delete, but returned the information I needed to construct my own deleteConfimation message. Then a simple if confirm(myMessage) go ahead and delete with another Ajax call.
I'm using MongoDB with NodeJS. Therefore I use mongoose.
I'm developing a multi player real time game. So I receive many requests from many players sometimes at the very same time.
I can simplify it by saying that I have a house collection, that looks like this:
{
"_id" : 1,
"items": [item1, item2, item3]
}
I have a static function, called after each request is received:
house.statics.addItem = function(id, item, callback){
var HouseModel = this;
HouseModel.findById(id, function(err, house){
if (err) throw err;
//make some calculations such as:
if (house.items.length < 4){
HouseModel.findByIdAndUpdate(id, {$push: {items: item}}, cb);
}
});
}
In this example, I coded so that the house document can never have more than 4 items. But what happens is that when I receive several request at the very same time, this function is executed twice by both requests and since it is asynchronous, they both push a new item to the items field and then my house has 5 items.
I am doing something wrong? How can I avoid that behavior in the future?
yes, you need better locking on the houseModel, to indicate that an addItem
is in progress.
The problem is that multiple requests can call findById and see the same
house.items.length, then each determine based on that (outdated) snapshot
that it is ok to add one more item. The nodejs boundary of atomicity is the
callback; between an async call and its callback, other requests can run.
One easy fix is to track not just the number of items in the house but the
number of intended addItems as well. On entry into addItem, bump the "want
to add more" count, and test that.
One possible approach since the release of Mongoose 4.10.8 is writing a plugin which makes save() fail if the document has been modified since you loaded it. A partial example is referenced in #4004:
#vkarpov15 said:
8b4870c should give you the general direction of how one would write a plugin for this
Since Mongoose 4.10.8, plugins now have access to this.$where. For documents which have been loaded from the database (i.e., are not this.isNew), the plugin can add conditions which will be evaluated by MongoDB during the update which can prevent the update from actually happening. Also, if a schema’s saveErrorIfNotFound option is enabled, the save() will return an error instead of succeeding if the document failed to save.
By writing such a plugin and changing some property (such as a version number) on every update to the document, you can implement “optimistic concurrency” (as #4004 is titled). I.e., you can write code that roughly does findOne(), do some modification logic, save(), if (ex) retry(). If all you care about is a document remaining self-consistent and ensuring that Mongoose’s validators run and your document is not highly contentious, this lets you write code that is simple (no need to use something which bypasses Mongoose’s validators like .update()) without sacrificing safety (i.e., you can reject save()s if the document was modified in the meantime and avoid overwriting committed changes).
Sorry, I do not have a code example yet nor do I know if there is a package on npm which implements this pattern as a plugin yet.
I am also building a multiplayer game and ran into the same issue. I believe I have solved it my implementing a queue-like structure:
class NpcSaveQueue {
constructor() {
this.queue = new Map();
this.runQueue();
}
addToQueue(unitId, obj) {
if (!this.queue.has(unitId)) {
this.queue.set(String(unitId), obj);
} else {
this.queue.set(String(unitId), {
...this.queue.get(unitId),
...obj,
})
}
}
emptyUnitQueue(unitId) {
this.queue.delete(unitId);
}
async executeUnitQueue(unitId) {
await NPC.findByIdAndUpdate(unitId, this.queue.get(unitId));
this.emptyUnitQueue(unitId);
}
runQueue() {
setInterval(() => {
this.queue.forEach((value, key) => {
this.executeUnitQueue(key);
})
}, 1000)
}
}
Then when I want to update an NPC, instead of interacting with Mongoose directly, I run:
npcSaveQueue.addToQueue(unit._id, {
"location.x": newLocation.x,
"location.y": newLocation.y,
});
That way, every second, the SaveQueue just executes all code for every NPC that requires updating.
This function never executes twice, because update operation is atomic on a level of single document.
More info in official manual: http://docs.mongodb.org/manual/core/write-operations-atomicity/#atomicity-and-transactions
I'm displaying a list of articles in a page that are fetched using the Ember Data RESTAdapter. I need to implement a bootstrap'esque paginator (see: http://twitter.github.com/bootstrap/components.html#pagination) and cant seem to find a sane pattern for returning pagination data such as, page count, article count, current page, within a single request.
For example, I'd like the API to return something like:
{
articles: [{...}, {...}],
page: 3,
article_count: 4525,
per_page: 20
}
One idea was to add an App.Paginator DS.Model so the response could look like:
{
articles: [{...}, {...}],
paginator: {
page: 3,
article_count: 4525,
per_page: 20
}
}
But this seems like overkill to hack together for something so trivial. Has anyone solved this problem or found a particular pattern they like? Is there a simple way to manage the RESTAdapter mappings to account for scenarios such as this?
Try to use Ember Pagination Support Mixin and provide your own implementation of the following method. Instead of loading all the content, you can fetch the required content when the user is navigating the pages. All what you need initially is the total account of your records.
didRequestRange: function(rangeStart, rangeStop) {
var content = this.get('fullContent').slice(rangeStart, rangeStop);
this.replace(0, this.get('length'), content);
}
With ember-data-beta3 you can pass a meta-property in your result. The default RESTSerializer looks for that property and stores it.
You can access the meta-data like this:
var meta = this.get("store").metadataFor("post");
If you are not able to change the JSON returned from the server you could override the extractMeta-hook on the ApplicationSerializer (or any other Model-specific serializer).
App.ApplicationSerializer = DS.RESTSerializer.extend({
extractMeta: function(store, type, payload) {
if (payload && payload.total) {
store.metaForType(type, { total: payload.total }); // sets the metadata for "post"
delete payload.total; // keeps ember data from trying to parse "total" as a record
}
}
});
Read more about meta-data here
I'm building a real-time feed application using Backbone.js, node.js and socket.io.
My Feed is a collection of Update models. Displaying these, overriding Backbone.sync for integration with socket.io works fine.
The complication comes in that each Update has a set of comments associated with it. When I show each Update in the Feed view, I want to show a summary of the associated comments (number of comments and a single 'most poular' comment), and also have the ability to click through to a different view to display each Update on its own with a paginated list of comments with further data.
I'm using backbone-relational to model the relationship between the Update model and Comment model, as follows:
Feed (collection) -> Update (model) -(has many)-> Comment (model)
I've been following this backbone-relational tutorial, but it seems to assume that I'd want to have all related data in memory at once in my Feed view, which I don't as there are potentially thousands of comments updating in real-time:
http://antoviaque.org/docs/tutorials/backbone-relational-tutorial/
My questions are:
How can I bring in summary data for comments to each Update in my Feed view without loading all comment data, and also maintain the ability to show paginated full data in my Update view?
I'm using backbone.layoutmanager for rendering my views. How best should I break my views up to accomplish the above?
For Q1:
I'm assuming you're using something like ioSync to use socket.io in Backbone.sync instead of REST API, or a similar solution.
Include metadata (such as # of comments) as an attribute on Update. If your Update object is heavy weight in itself, you could update the count using ioBind and custom server-side socket.io events instead of sending the whole object every time.
Include an attribute topComment as an additional one-to-one relation in Update. When initially loading Update from the server, include topComment in the response, but not the other comments.
Lazy-load the rest of the comments using custom socket.io events. You will likely want a server-side handler that takes as parameters updateId, startIndex, maxComments, which returns a list of comments for the given Update starting at the given index. If the result is sent to the client as JSON, then it's easy to do something like this on the client:
// Assume `model` is an instance of `Update`.
socket.emit('get_comments_page', {
updateId: model.get('id'),
startIndex: 1,
maxComments: 10
}, function(err, data) {
if (err) {
alert('Unable to fetch comments: ', err);
} else {
model.get('messages').reset(data)
}
});
Avoid sending ID for all comments when fetching Update then trying to use fetchRelated to resolve them. I learned this one the hard way :O/
You could also store the comments collection directly on the view without associating it as relationship of Update
For Q2:
I don't have any experience with layoutmanager as I use Backbone.Marionette for managing my views. Marionette has an async extension (disclaimer: I'm a co-maintainer). I encourage to see how Marionette.async does the delayed rendering, waiting for the data to arrive from the server.
The main idea is to use jquery's Deferred objects that resolve when the data comes back from the server. Extending the above example with deferred:
var MyView = Backbone.View.extend({
// ... normal stuff that views need ...
initialize: function() {
var deferred = $.Deferred();
// Assume `model` is an instance of `Update`.
var that = this;
socket.emit('get_comments_page', {
updateId: that.model.get('id'),
startIndex: that.options.pageNumber,
maxComments: 10
}, function(err, data) {
if (err) {
alert('Unable to fetch comments: ', err);
} else {
that.model.get('messages').reset(data)
}
deferred.resolve();
});
this.promise = deferred.promise();
},
render: function() {
var that = this;
this.promise.done(function() {
// Do your normal rendering code here, for instance:
$(that.el).html(that.template(that.model.toJSON()));
});
return this;
}
});
Note: the code snippets above are not tested as is.
I have been looking at the Todo list example (source) for Backbone.js. The code uses local storage, and I wanted to try and convert it so that it operated via a RESTful webservice.
Suppose the webservice already exists at the route todos/. I figured I need to add in a url piece into Backbone.Model.extend and remove the localStorage: new Store("todos") line when we perform Backbone.collection.extend.
window.Todo = Backbone.Model.extend({
url : function() {
return 'todos/'+this.id;
}
// Default attributes for a todo item.
defaults: function() {
return {
done: false,
order: Todos.nextOrder()
};
},
// Toggle the `done` state of this todo item.
toggle: function() {
this.save({done: !this.get("done")});
}
});
What is the proper way to do this?
Url should be set in Collection, if you have need for diferent urls than those created by collection than declare url in model.
You need to remove
<script src="../backbone-localstorage.js"></script>
from index.html since it is linked after backbone.js and effectively overrides Backbone's sync method to store in localStorage.
I would leave the model as it is in the Todos example. In the collection class add this property:
window.TodoList = Backbone.Collection.extend({
...
url: '/todos',
...
}
Calling fetch() on the collection should retrieve a list of Todo objects.
If you are using Rails you need to set ActiveRecord::Base.include_root_in_json = false otherwise Backbone.js will not be able to pull out the Todo objects from the returned json.