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.
Related
I want to share some data from template to another template using Meteor. I have a template i.e allInventory.html on which i am showing some data in table form i added three links there that is. one for view , edit and delete what i want iam getting all the data from backend into one of helper i.e productDetails and i bind an event with view button that will take the data of current user clicked on which product so i have successfully getting the data at my allinventory template but there is another template i.e productDetails on which i want to render or show that data. But stuck with that i have data on allInventory click event but not know how do ishare the same with productDetails template.
Here is my allInventory.js
Template.allInventory.rendered = function() {
Template.allInventory.events({
"click .btn":function (e){
data = $(e.target).attr('data');
Router.go('productDetail', {data: $(e.target).attr('data')}, {query: 'q=s', hash: 'hashFrag'});
console.log("button clicked.."+data);
console.log(data);
}
})
ProductDetails.js
Template.productDetail.rendered = function () {
Template.productDetail.helpers({
productDetails: function() {
return data;
}
});
allInvenrtory.html
<button type="button" data ="{{productInfo}}" class="btn btn-info btn-sm"><i class="fa fa-eye"></i>View</button>
I just simply want to share allInventory template data with productsDetails template.
Any help would be appriciated!
Thanks
I'd recommend avoiding Session for this purpose, since it is a global object, but more importantly, because there are better ways to do it.
You can pass data from the parent templates to the child template using helpers: https://guide.meteor.com/blaze.html#passing-template-content
You can pass data from the child to the parent templates using callbacks https://guide.meteor.com/blaze.html#pass-callbacks
I'd structure this app to have a container (page) template, which will have all the subscriptions and render one of your templates based on the URL.
You can use the Session variable if you want to share data between templates.
You can follow this guide:
http://meteortips.com/first-meteor-tutorial/sessions/
I would put both template in a third, parent template.
ParentTemplate
-SharedInfo
-KidTemplate1
-KidTemplate2
Then having this third template hold the information you want to share across templates.
For that you can use a ReactiveVar, ensuring that change by template1 code on the parent template is visible in template2 as well.
To access the parent template for the kids, you can do something along those lines :
Blaze.TemplateInstance.prototype.parentTemplate = function (levels) {
var view = Blaze.currentView;
if (typeof levels === "undefined") {
levels = 1;
}
while (view) {
if (view.name.substring(0, 9) === "Template." && !(levels--)) {
return view.templateInstance();
}
view = view.parentView;
}
};
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 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
I am developing an extension for all the browsers. How do I store tab specific values in the session? I solved this problem in Firefox with an NSISessionStore object. In Safari and Google Chrome, I used SessionStorage; this object stores values for a specific tab with a specific domain. I want a solution for how to store values for a specific tab.
If you're asking how to manage data throughout the life of a tab you can simply create an object for the tab when it's created and delete it when it is closed.
// Create data store
var tabDataStore = {};
// Create listeners
chrome.tabs.onCreated.addListener(function (tab) {
tabDataStore['tab_' + tab.id] = {
urls: []
};
});
chrome.tabs.onRemoved.addListener(function (tabId) {
delete tabDataStore['tab_' + tabId];
});
// Save something against that tab's data
function saveUrl(tab) {
tabDataStore['tab_' + tab.id].urls.push(tab.url);
}
// Load something from tab's data
function loadOriginalUrl(tab) {
tabDataStore['tab_' + tab.id].urls[0];
}
However, this is all an assumption and you may want something completely different. Also, it depends when and what exactly you want to store.
Further information on tabs can be found in the official documentation.
If you want to persist anything you can use localStorage.
a simple way to do it, though not ideal as it would look messy would be to store the values in the URL hash
say the URL of the tab was http://whatever.com/
you could store the value in the hash like so
http://whatever.com/#value1=12&value2=10&value3=15212
there will also be a problem if the website uses the hash object for anyhting such as "in page" anchors, or ajaxy type stuff
Safari Answer
In your global page save directly to the tab.. so for instance on message from injected script
// global page
safari.application.addEventListener("message", function(event){
switch(event.name){
case "saveData":
event.target.page.tabData = { data: myData }
break;
case "getData":
event.target.page.dispatchMessage("tabData", myData);
break;
}
}, false);
-
// injected page
// first save data
safari.self.tab.dispatchMessage("saveData", {firstname:"mike", age: 25} );
// setup listner to recevie data
safari.self.addEventListener("message", function(event){
switch(event.name){
case "tabData":
// get data for page
console.debug(event.message);
// { firstname: "mike", age: 25 }
break;
}
}, false);
// send message to trigger response
safari.self.tab.dispatchMessage("getData", {} );