I'm new to Backbone and I'm trying to build a Backbone app that graphs and maps data in real-time. I implemented a websocket following this example code. The problem is, I would like to use a more extensive data set than the example code, and if I understand the code, it is just creating one model -- a single array of points. I want a collection of models in which each model has latitude, longitude, and amount (just a numerical value).
How do I implement a websocket such that when my backend sends some JSON, my app creates a new model with those attributes? I've read on blogs about this that I need to override Backbone.sync and implement an event aggregator, but the only examples I've seen of this use socket.io. Socket.io is not an option because of the language/framework I'm using on backend. Moreover, eventually I'll be switching out the backend to another language that also isn't supported by socket.io, so I'd like to find a more general way to implement the websocket on the frontend that does not involve a library like socket.io.
I have found a solution to my own question that works. Again, I'm new to Backbone, so I'm not sure if this is the best way -- would be interested in feedback on whether this solution is following best practices. The code is based on this example by Andrew Cholakian. I kept some print statements in that are helpful when you run the code.
The code assumes your backend is sending JSON data in the form of
{data: "{"lat": latitude, "long": longitude, "amt": amount}"}
// this function opens the websocket and will trigger add_point when
// a new message is received
Stream = function () {
_.extend(this, Backbone.Events);
var self = this;
self.socket = new WebSocket("ws://" + document.domain + ":5000/websocket");
console.log("Using a standard websocket");
self.socket.onopen = function(e) {
self.trigger('open', e);
console.log('socket opened');
};
self.socket.onerror = function(e) {
self.trigger('error', e);
};
self.socket.onmessage = function(e) {
self.trigger('message', e);
self.trigger('data', e.data);
self.trigger('add_point', JSON.parse(e.data));
};
self.socket.onclose = function(e) {
self.trigger('close', e);
console.log('socket closed');
};
};
DataPoint = Backbone.Model.extend({
defaults: {
lat: null,
long: null,
amt: null
}
});
DataSet = Backbone.Collection.extend({
model: DataPoint,
initialize: function(options) {
this.stream = options.stream;
var self = this;
this.stream.on("add_point", function(pt) {
self.add( new DataPoint({
lat: pt.lat,
long: pt.long,
amt: pt.amt
}));
console.log('updated collection');
console.log(self.models);
});
}
});
MapView = Backbone.View.extend({
initialize: function(options) {
this.dataSet = options.dataSet;
}
});
$(function() {
var stream = new Stream();
var dataSet = new DataSet({ stream: stream });
var mapView = new MapView({ dataSet: dataSet });
});
You can use Backbone.WS which lets you use Backbone resources over native WebSockets. No extra dependencies!
You can use backbone.wamp, based on WAMP protocol over WebSocket
Related
Good morning guys. I have a little understanding problem with backbone.js. i have a javascript sdk from a backend as a service with some getter and setter methods to get datas from this platform.
I have load this javascript sdk with require.js an it´s work fine. Now i need to create some models that work with this getter and setter methods to get this data to my collection an finally to my view. I do not have any clue...maybe someone have the right idea for me.
This is my current model:
define(['jquery','underscore','backbone'], function($,_,Backbone) {
var holidayPerson = Backbone.Model.extend({
initialize: function() {
console.log("init model holidayPerson");
this.on("change", function(data) {
console.log("change model holidayPerson"+JSON.stringify(data));
});
}
});
return holidayPerson;
});
Actually i create an instance of my model in my view:
define(['jquery','underscore','backbone','text!tpl/dashboard.html','holidayPerson','apio'], function($,_,Backbone,tpl, holidayperson, apio) {
template = _.template(tpl);
var usermodel = new holidayperson();
var dashboardView = Backbone.View.extend({
id: 'givenname',
initialize: function() {
console.log("dashboard view load");
usermodel.on('change', this.render);
var user = new apio.User();
user.setUserName('xxx');
user.setPassword('xxx');
apio.Datastore.configureWithCredentials(user);
apio.employee.getemployees("firstName like \"jon\" and lastName like \"doe\"", {
onOk: function (objects) {
console.log("apio: " + JSON.stringify(objects));
usermodel.set({mail: objects[0]['data']['mail'],lastname: objects[0]['data']['lastName'], username: objects[0]['data']['userName'], superior: objects[0]['data']['superior']});
}
});
},
render: function() {
console.log("render dashboard view");
console.log(usermodel.get('mail'));
console.log(usermodel.get('lastname'));
this.$el.html(template());
return this;
}
});
return dashboardView;
});
I think this not the right way...can i override the getter and setter method from this model ? Or maybe the url function ? Anyone now what is the best practice ?
Thanks a lot :-)
First of all, make sure that your render operation is asynchronous, as your API call will be and the usermodel params won't be set until that operation completes. If you render method fires before that, it will render the empty usermodel, since the data will not be there yet.
Second, a model need not fetch its own data, in my opinion. If you are going to have multiple users, you could use a collection to hold those users and then override the collection's sync method to handle the fetching of data from the API, but if there's no collection, it seems logical to me to have a method that does the data fetching and setting thereafter, as you've done.
I'm attempting to call render on multiple instances of a view. However they kind-of need to happen in order (not really). Here is my view code which hopefully will explain itself.
var SiteView = Backbone.View.extend({
el: '#contentArea',
render: function(siteId) {
console.log("SiteView.render running",arguments);
var that = this;
var siteCollection = new SiteCollection();
this.collection = siteCollection;
this.collection.fetch({
data: {
returnType: "VERTEX",
startId: siteId,
},
success: function(model, response, options) {
console.log("SiteView.render.success",arguments);
var templateHtml = _.template(siteHtml, response);
that.$el.empty();
that.$el.html(templateHtml);
that.renderServersList(response.ID);
that.renderContainersList(response.ID);
}
});
},
renderServersList: function(siteId) {
console.log("SiteView.getServersList running", arguments);
var that = this; // Removed this line which seemed to be causing the issue :-/
// This is a view in a separate file provided by require.js
var serverListView = new VertexListView({el: "#siteServersListBody"});
serverListView.render("SERVERS", siteId);
},
renderContainersList: function(siteId) {
console.log("SiteView.getContainersList running", arguments);
// This is a view in a separate file provided by require.js
var contListView = new VertexListView({el: "#siteContainersListBody"});
contListView.render("CONTAINERS", siteId);
}
});
Both the getContainersList and getServersList is working, as in it's getting the proper data. However, as the server list is rendering, it also contains the entire containers list and then the containers list only contains what it is supposed to contain.
The reason why I'm separating out these requests instead of including the containers and servers in the original request is mainly because of the size of the containers. It can be rather large.
EDIT: Got it working, but it seems wrong.
I ending up adding a listener to the sync event on the main app view, then render a player. I also added a global variable PgaPlayersApp.CurrentPlayer.
Am I going about this the wrong way? What is the correct way to do this? Is there a reason I can't use reset: true and then listen for the reset event? (It doesn't work)
ROUTER:
// js/router.js
var PgaPlayersApp = PgaPlayersApp || {};
var Router = Backbone.Router.extend({
routes:{
'player/:id': 'loadPlayer'
},
loadPlayer: function(id)
{
PgaPlayersApp.CurrentPlayer.set('id', id);
PgaPlayersApp.CurrentPlayer.fetch();
}
});
PgaPlayersApp.Router = new Router();
Backbone.history.start();
VIEW:
//js/views/app.js
var PgaPlayersApp = PgaPlayersApp || {};
PgaPlayersApp.AppView = Backbone.View.extend({
el: '#pga_players_profile_app',
initialize: function()
{
this.listenTo(PgaPlayersApp.Players, 'reset', this.addAll);
this.listenTo(PgaPlayersApp.CurrentPlayer, 'sync', this.loadPlayer);
PgaPlayersApp.Players.fetch({reset: true});
},
...
loadPlayer: function()
{
new PgaPlayersApp.PlayerCardView({ model: PgaPlayersApp.CurrentPlayer }).render();
}
});
Backbone.js is a library that doesn't really enforce how you'd like to structure your App (Or the relationship between your Controller, Model, Router, etc.)
Below is one of the many ways to do it.
Couple highlights:
Router kicks off the fetch process.
When model has been fetched, Router then asks the View to render data (Instead of having the View listening to change events from the Model.)
This assumes that PlayerCardView is a "read only" view, as the View doesn't listen to change events from the Model. Depending on your use case, this might not be desirable, so it ultimately depends on how you'd like to handle it.
Here are some sample code:
(function (export) {
var App = export.App = {};
// Stores state/current views of the App
App.state = {};
App.state.currentPlayer = null;
// Model containing the player
App.PlayerModel = Backbone.Model.extend({});
// Single Player View (Assuming you have a collection view for list of players)
App.PlayerCardView = Backbone.View.extend({
model: App.PlayerModel,
template: _.template('<%= id %>'),
render: function(parentEl) {
// Render player
this.$el.html(this.template(this.model.toJSON()));
// Append player view to parent container
if (parentEl) {
parentEl.append(this.$el);
}
return this;
}
// Don't forget to clean up as well!
});
// Router
App.Router = Backbone.Router.extend({
routes: {
'player/:id': 'showPlayer'
},
showPlayer: function(id) {
// Unload current player view, if necessary
// Construct model
var player = App.state.currentPlayer = new App.Player({
id: id
});
// Pass model to the new player view
var view = App.state.currentPlayerView = new App.PlayerCardView({
model: App.state.currentPlayer
});
// At this time, you should probably show some loading indicator as you're fetching data from the server
// Fetch data
player.fetch({
success: function() {
// This would be called when data has been fetched from the server.
// Render player on screen
view.render($('#parentContainerId'));
}
});
}
});
// Initializes the App
App.init = function() {
// Kick off router
App.state.router = new App.Router();
export.Backbone.history.start();
};
})(window);
// Start the app!
window.App.init();
Gist: https://gist.github.com/dashk/5770073
i'm trying to connect model and collection using parse.com but i'm confused. I'm tring to fetch by collection using backbone and javascript api parse.com but compare this error:POST https://api.parse.com/1/classes 404 (Not Found).
Model:
var Person = Backbone.Model.extend({
defaults:{
},
initialize:function(){
console.log("inperson");
this.validate();
this.send();
},
validate:function(){
console.log("validate");
},
send:function(){
var user = new Parse.User();
user.set("username", this.get("username"));
user.set("password", this.get("password"));
user.set("email", this.get("email"));
user.signUp(null, {
success: function(user) {
// Hooray! Let them use the app now.
},
error: function(user, error) {
// Show the error message somewhere and let the user try again.
alert("Error: " + error.code + " " + error.message);
}
});
}
});
return Person;
});
Collection:
var Usercollection = Parse.Collection.extend({
model:Person,
initialize:function(){
}
});
return Usercollection;
});
and finally the view that call the colletion and fetch:
var HomeView = Backbone.View.extend({
template: Handlebars.compile(template),
events: {
},
initialize: function() {
console.log("inhomeview");
var amici = new Usercollection();
amici.fetch({
success: function(collection) {
amici.each(function(object) {
console.warn(object);
});
},
error: function(amici, error) {
// The collection could not be retrieved.
}
});
},
render: function() {
}
});
return HomeView;
});
Cant you just swap the backbone collection and model to Parse's ones? (You only used the Parse type of the collection, not the model!)
Try switch that Backbone model to a Parse.Object .
Step by step below:
First of all Lets create a new app on Parse.com, mine is called FunkyAppartments.
Insert the script tag for loading Parse javascript lib into index.html or whathever:
<script src="http://www.parsecdn.com/js/parse-1.5.0.min.js"></script>
Switch the backbone model and collection to use parse types instead (and rename the fetch method if you have extended backbones, since we do not want to overide the one of parse):
//var Appartment = Backbone.Model.extend(); Backbone wo. Parse.com
var Appartment = Parse.Object.extend("Appartment");
//var Appartments = Backbone.Collection.extend({ Backbone wo. Parse.com
var Appartments = Parse.Collection.extend({
model: Appartment,
loadAppartments: function(callback){
debugger;
this.query = new Parse.Query(Appartment);
this.fetch();
}
});
I added a debugger tag in the load appartments so that developer tools breaks in the middle of the controller, here I have access to the Appartment private type of the controller, hence i can store some data on the parse server and verify by pasting the below in the developer tools console.
var testAppartment = new Appartment();
testAppartment.save({name: "foobars"}).then(function(object) {
alert("yay! it worked");
});
Yei, the data shows up in the parse.com UI for the app we just added there. And more importantly it shows up in our frontend. That was easy!
UPDATE: PROBLEMS W BACKBONE 1.2.1, MARIONETTE 2.4.2, UNDERSCORE 1.8.3
I noticed that I actually had been using old versions of marionette, backbone and underscore.js. An initial update appeared to break the application.
After some research i found that it was the parse part that did not return objects that would successfully render. Hence I changed the collection type back to an extension of: Backbone.collection instead of Parse.collection.
I also had to override the query method, since the objects would not save on the correct id, updating an object resulted in a new object being added instead of an old one being updated.
var Apartment = Parse.Object.extend('Appartment');
var Apartments = Backbone.Collection.extend({
model: Apartment,
query: new Parse.Query(Apartment),
initialize: function(){
MyApp.vent.on('search:param', function(param){self.search(param); });
var self = this;
this.query.find({
success: function(results){
self.reset();
results.forEach(function(result){
result.attributes.id__ = result.id
var ap = new Apartment(result.attributes);
self.add(ap);
});
}
});
}
});
I added an attribute: id__ to hold the parse id (naming it just id did not work since it backbone interfered with it, making it disappear).
Finally in saving the model to parse i utilized id__ as id in the save call:
var ApartmentEditView = Backbone.Marionette.ItemView.extend({
template: "#apartment-edit-template",
className: "apartmentDetail",
events: {
"click .store": "storeEdit",
"click .close": "closeEdit"
},
storeEdit: function(){
var priceNum = Number($('#price_field').val().replace(/\s/g, ''));
this.model.set({
id: this.model.attributes.id__,
name:$('#name_field').val(),
price:priceNum,
description:$('#desc_field').val(),
url:$('#url_field').val()
});
this.model.save();
this.closeEdit();
},
closeEdit: function(){
var detailView = new ApartmentDetailView({model: this.model});
MyApp.Subletting.layout.details.show(detailView);
}
});
Now the object is updated correctly in the database.
I'm using backbone-tastypie from https://github.com/PaulUithol/backbone-tastypie, and I can't fetch a collection data.
Thats my code:
var User = Backbone.Model.extend({
url: '/api/v1/user'
});
var HoraExtra = Backbone.Model.extend({
url: '/api/v1/horasextra/'
});
var HorasExtra = Backbone.Collection.extend({
url: '/api/v1/horasextra/',
model: HoraExtra
});
var Horas = new HorasExtra();
var activeUser = new User();
var HorasExtraView = Backbone.View.extend({
initialize: function() {
_.bindAll(this, "render");
},
render: function() {
var plantilla = Handlebars.compile($("#horas_extra_template").html());
var html = plantilla(Horas);
this.$el.html(html);
console.log(JSON.stringify(Horas));
}
});
var HorasExtraWidget = new HorasExtraView({el: $('#base')});
Horas.fetch({
data: {
"usuario__id": 2,
"hor_com__month": 11
}
});
HorasExtraWidget.render();
And that's the result of console.log(JSON.stringify(Horas):
[]
Many thanks
I would suggest to you that you take tastypie, backbone and build your own simple project in 1-2 days.
Then you will get to know basic issues of coupling those 2 frameworks.
Without that knowledge it is pointless you try out other people's projects and then wonder "what isn't working".
And from my personal experience, both tastypie and javascript are pretty straightforward and are easy to couple.
And I am NOT an expert.
couple of points: try explicitly fetching models with fetch, manipulate fetched model from success callback, and watch your model url's, forward slashes in them etc.
Seems your collection is empty.
See in console if there is a GET call to server.
If not, you're not actually fetching anything from the server.
I used https://github.com/amccloud/backbone-tastypie and then your code should look like this, I guess. Not tested.
var HorasCollection = Backbone.Tastypie.Collection.extend({
url: '/api/v1/horasextra/'
});
var HorasExtraView = Backbone.View.extend({
el: $('#base'),
entries: new HorasCollection(),
render: function() {
var that = this;
this.entries.fetch({
success: function(entries){
console.log("Entries", entries.models);
// var t = _.template(template, {hello: 'world'});
// that.$el.html(t);
},
error: function(model, response){
console.log("Error", response);
}
});
}
});
If you're trying to create a table for your data, try my jquery plugin :)
https://github.com/sspross/tastybackbonepie