Trigger.io Backbone and OAuth2 using coffeescript - backbone.js

I have a rails app serving data on localhost:3000 using the Doorkeeper gem to provide a secure API
The client is a Trigger.io app I'm testing with my android phone, the Raffler from Railscasts Backbone episodes.
Problems>
I have a function that returns the correct oauth token & url for accessing the server. I'm confused as to where I should call that function and how I should store the return value so as it is available to the Collection class before a new collection is created.
When the client queries the server it returns 200 and seems to pass the requested object back but my view doesn't give the expected result - it returns zero for the length when it should be three.
To test this out I inputted the url in my browser, copied the returned json object and passed it directly into the function that instantiates #collection in router.coffee/initialize. This gets the desired result in the view.
I tried fetching the json object in Trigger.io's catalyst debugging console, no joy. Fetch returns an object but the length is 0
Not sure how to debug beyond what I've tried, new to coffee/Backbone. Appreciate your help, thanks!
raffler.coffee
window.Raffler ?= {
Models: {}
Collections: {}
Views: {}
Routers: {}
init: ->
new Raffler.Routers.Entries()
Backbone.history.start()
}
$(document).ready ->
Raffler.init()
entries.coffee
class Raffler.Collections.Entries extends Backbone.Collection
url: 'http://192.168.1.14:3000/api/v1/entries?access_token=022f854...
initialize: -> # this returns a valid url&token for accessing the server
entries_router.coffee
class Raffler.Routers.Entries extends Backbone.Router
routes:
'': 'index'
initialize: ->
#collection = new Raffler.Collections.Entries()
#collection.fetch()
index: ->
view = new Raffler.Views.EntriesIndex(collection: #collection)
$('#container').append(view.render().el)
entries_index.coffee
class Raffler.Views.EntriesIndex extends Backbone.View
template: _.template( $('#item-template').html() )
initialize: ->
#collection.on('fetch', #render, this)
render: ->
$(#el).html(#template(entries: #collection))
this
index.html
.....
<head>
<script id="item-template" type="text/x-underscore-template">
<h1> Raffler </h1>
<%- entries.length %>
</script>
</head> etc...
RE: Problem 1 here's what I'm currently trying:
entries_router.coffee
initialize: ->
#collection = new Raffler.Collections.Entries()
#collection.fetch()
entries.coffee
class Raffler.Collections.Entries extends Backbone.Collection
url: #url
#url = () ->
return "http://192.168.1.14:3000/api/v1/entries?access_token=#{params.access_token}"
results in 'url must be specified' error.

Here are two methods that would solve the first problem. You can set the url attribute on the Collection instance. So rather than returning your generated URL, you can do something like:
class Raffler.Collections.Entries extends Backbone.Collection
initialize: (args...) ->
#url = 'http://192.168.1.14:3000/api/v1/entries?access_token=022f854'
super(args...)
entries = new Raffler.Collections.Entries()
entries.fetch() # will use the url attribute on the collection instance
You can also specify the URL as a parameter to fetch:
entries.fetch(url: 'http://somewhereelse.com/') # will use a different URL
For the second part, I suspect you're having problems due to Same origin policy for HTTP requests from JavaScript. The usual solution when using Trigger.io Forge is to use the forge.requests module to make cross domain requests, a simple way to populate your collection would be:
entries = new Raffler.Collections.Entries()
forge.requests.ajax(
url: 'http://192.168.1.14:3000/api/v1/entries?access_token=022f854'
type: 'GET'
dataType: 'json'
success: (data) ->
entries.reset(data)
error: (e) ->
forge.logging.error("Failed to get entries: #{e.message}")
)
A more useful way might be to override Backbone.sync to back onto forge.requests.ajax. This is probably just a case of changing the last line of Backbone.sync from $.ajax as the two APIs are pretty similar.

Just in case it helps anyone - https://github.com/martindavis/trigger-backbone.sync

Related

Backbone go to same route won't refresh the page

I have a very simple tag which will go the current route, since I need to refresh the page to clear some messages.
<span>Click here to go get your password back</span>
<-- I am at /resetpassword route now -->
But this won't work, since I believe is Backbone route detects it is the same route, therefore just returns.
I found this question is related to mine, but there is a difference that I am not using functions, just tag.
(CMD + R) works
So is there any way I can refresh the same route page in tag?
Thanks!
In the view I would do this.
In the view
events:
"click span": "resetPage"
resetPage: ->
#trigger "reset:page"
Then in the controller
#listenTo currentView, "reset:page", (args) ->
model = args.model
App.vent.trigger "reset:page", model
Then in your router you listen for that message and call your API route again, essentially refreshing the page, but if you have something you would need to re-fetch you could just pass that along with each message and avoid having the memory/unresponsive feeling that a navigate({trigger: true}) would cause :).
#SampleApp.module "PostsApp", (PostsApp, App, Backbone, Marionette, $, _) ->
class PostsApp.Router extends Marionette.AppRouter
appRoutes:
"" : "list"
":id" : "show"
API =
list: ->
new PostsApp.List.Controller
show: (id, post) ->
new PostsApp.Show.Controller
id: id
post: post
App.vent.on "posts:list:clicked", ->
App.navigate "/"
API.list()
App.vent.on "reset:page", (post) ->
App.navigate "/" + post.id
API.show post.id, post
App.addInitializer ->
new PostsApp.Router
controller: API
To avoid re-fetching the post you'd do this in the initialize function in your controller:
#SampleApp.module "PostsApp.Show", (Show, App, Backbone, Marionette, $, _) ->
class Show.Controller extends App.Controllers.Application
initialize: (options) ->
{ post, id } = options
post or= App.request "post:entity", id
App.execute "when:fetched", post, =>
#layout = #getLayoutView()
#listenTo #layout, "show", =>
#panelRegion post
#postRegion post
#bannerRegion post
#show #layout
That will keep your app very responsive - note this is assuming you don't need to reset your models/collections. If you need to just reset the model/collection data the navigate({trigger: true}) route might be the way to go. Unless you want to keep track of say items you added and in the event do some manual resetting and then re-render the view.
I suppose you also could implement a "resetModel" function in your model that will re-fetch it and trigger a custom event which your view could listen to as a modelEvents and then re-render itself.

Backbone Marionette: CompositeView replacing items instead of appending them

I've got a Marionette CompositeView that I'm using to fill in a dropdown. The JSON response is clean when I call collection.fetch() from within the CompositeView, but instead of appending the new ItemViews, CompositeView seems to be replacing them in the DOM.
Here's my code (coffeescript):
class #PDCollectionItemView extends Backbone.Marionette.ItemView
el: 'li'
template: Handlebars.compile('{{ title }}')
class #PDCollectionsView extends Backbone.Marionette.CompositeView
id: 'pd_collections'
className: 'selection'
itemView: PDCollectionItemView
itemViewContainer: '.scroll ul'
template: HandlebarsTemplates['connections/collection_select'] #handlebars_assets gem
ui:
modalTrigger: '#pd_collection_selector'
modal : '#pd_selection_modal'
selectBtn : '#select_collection'
initialize: ->
#selectedCollection = undefined
Connectors.App.vent.on "connections:collectionStaged", #assignSelectedCollection
return #PDCollectionsView
And the parent layout where the fetch is called:
class #IndexLayout extends Backbone.Marionette.Layout
initialize: ->
#collections = new PDCollectionsCollection
#collectionsView = new PDCollectionsView
collection: #collections
onRender: ->
#collectionSelect.show #collectionsView
#collections.fetch
success: (collection, response, options) =>
Connectors.App.vent.trigger "connections:collectionsLoaded"
Connectors.App.vent.trigger "loadComplete"
error: (collection, response, options) =>
console.log response
I've tried manually appending the items with an appendHTML call, but I get the same behavior. I can log each itemView with a call to onAfterItemAdded on the #PDCollectionsView, and the item views are distinct; different cids, and the appropriate models.
I think the problem is in your use of Backbone's fetch operation. fetch "syncs" the collection with its state on the server. Without specifying any customization it will intelligently add new items, update changed items, and remove items no longer on the server. I'm guessing that if you examine the collection after you call fetch you'll see it's only got the items that are being rendered in the CompositeView.
You can modify fetch's behavior to sync to the server without removing anything by passing {remove: false}. This should yield the results you're looking for:
#collections.fetch
remove: false
success: (collection, response, options) =>
Connectors.App.vent.trigger "connections:collectionsLoaded"
Connectors.App.vent.trigger "loadComplete"
error: (collection, response, options) =>
console.log response

backbone.js - how can i fetch local or server data

I am very new to backbone.js, to try it out I made a function to append an element using my array. But I don't know how to fetch the same from server or my local path.
I tried with some tutorial but still I couldn't get any good result. Can any one correct my function to fetch the data from local folder or server?
code :
this is my local path: '..data/data.json'
(function($){
var student = [
{name:'student1'},
{name:'student2'},
{name:'student3'}
]
var model = Backbone.Model.extend({
defaults:{
name:'default name'
}
});
var collection = Backbone.Collection.extend({
model:model
});
var itemViews = Backbone.View.extend({
tagname:'li',
render:function(){
this.$el.html(this.model.get('name'));
return this;
}
})
var view = Backbone.View.extend({
el: $("#contacts"),
initialize:function(){
this.collection = new collection(student);
this.render();
},
render:function(){
var that = this;
_.each(this.collection.models, function(item){
that.renderName(item);
})
},
renderName:function(item){
var itemView = new itemViews({model:item});
this.$el.append(itemView.render().el);
}
});
Collections have the url property which links to the url where the models can be fetched (in a proper JSON format of course).
If you have a RESTful API you can tie them directly to a collection on your backend.
In your case you can modify the collection definition as follows
var collection = Backbone.Collection.extend({
model:model,
url: '..data/data.json' //OR WHATEVER THE URL IS
});
Then when instantiating your collection you have to call the fetch() method to fill in the collection.
myCollection = new collection();
myCollection.fetch();
For more information see this post
Backbone will call GET, POST, DELETE, etc on that same url depending on what needs to be sent or received from the server. So if you are building the REST api, your controller or router should route the appropriate functions to those methods. See below for exact mapping:
create → POST /collection
read → GET /collection[/id]
update → PUT /collection/id
delete → DELETE /collection/id
As a side note, if you have no control over the JOSN that is returned from the server, you can format it in any format you like in the parse function.
parse: function(response) { return response.itemYouWantAdded; }
This would be the case if your server is returning an object within an object, such as twitter feeds.
Or you could simply populate the model from the response manually in the parse function
parse: function(response) { var tmpModel = {
item1:response.item1,
item2:response.item2
};
return tmpModel; }
Hope this helps.
You can set a URL for your model or for your collection. Then when you run fetch it will read your JSON straight into either a single model or a collection of models. So in your case, your collection might look like
var collection = Backbone.Collection.extend({
url: '..data/data.json',
model: model
});
collection.fetch(); // Could also use collection.reset()
You just need to make sure your JSON is formatted properly to match your model's attributes.

Backbone 0.9.2 view initialize not being called

I am new to Backbone.js. I am using CoffeScript on a v0.9.2 app. The app works "fine" but the initialize() method of the views is not being called. Events are not being properly binded either. I am trying to figure out why this is not the case. I am using other (manual) ways to bind events to elements but that should not be the case.
The app is instantiated with this:
window.Site =
Models: {}
Collections: {}
Views: {}
Routers: {}
init: ->
new Site.Routers.MyRouter()
Backbone.history.start()
$(document).ready ->
Site.init()
The router:
class Site.Routers.MyRouter extends Backbone.Router
routes:
'': 'index'
initialize: ->
# some code here (this IS being called)
index: =>
# this is also being called since I am trying mysite.com/
view = new Site.Views.MyView()
$('#someId').html(view.render().el)
The view:
class Site.Views.MyView extends Backbone.View
template: JST['views/index']
events:
'click .someElement': 'someMethod'
inititalize: ->
console.log "hello" # NOT CALLED
_.bindAll #
#
render: =>
# draw stuff (this works)
#
The view gets drawn fine. Why is initialize not being called?
Thanks!
You have to spell initialize correctly =p
inititalize: -> # should be `initialize: ->`
console.log "hello" # NOT CALLED
For future readers, also check you don't have two initialize functions.
Backbone.View.extend({
initialize: function () {
// not called
},
// stuff
initialize: function () {
// overwrites previous
}
})
I don't write CoffeeScript, but the only place I see an instance of your view initailized is in the router:
index: =>
view = new Site.Views.MyView()
I suspect the router's index is not being called and as a result your view's initialize isn't being called. Extending a view doesn't create an instance of the view, rather it creates a customized definition of a view.
HTH.

Query Database with Backbone Collection

I need to query the database using a backbone collection. I have no idea how to do this. I assume that I need to set a url somewhere, but I don't know where that is. I apologize that this must be a very basic question, but I took a backbone course on CodeSchool.com and I still don't know where to begin.
This is the code that I have for the collection:
var NewCollection = Backbone.Collection.extend({
//INITIALIZE
initialize: function(){
_.bindAll(this);
// Bind global events
global_event_hub.bind('refresh_collection', this.on_request_refresh_collection);
}
// On refresh collection event
on_request_refresh_collection: function(query_args){
// This is where I am lost. I do not know how to take the "query_args"
// and use them to query the server and refresh the collection <------
}
})
The simple answer is you would define a URL property or function to your Backbone.Collection like so:
initialize: function() {
// Code
},
on_request_refresh_collection: function() {
// Code
},
url: 'myURL/whateverItIs'
OR
url: function() {
return 'moreComplex/' + whateverID + '/orWhatever/' + youWant;
}
After your URL function is defined all you would have to do is run a fetch() on that collection instance and it will use whatever you set your URL to.
EDIT ------- Making Collection Queries
So once you set the URL you can easily make queries using the native fetch() method.
fetch() takes an option called data:{} where you can send to the server your query arguments like so:
userCollection.fetch({
data: {
queryTerms: arrayOfTerms[], // Or whatever you want to send
page: userCollection.page, // Pagination data
length: userCollection.length // How many per page data
// The above are all just examples. You can make up your own data.properties
},
success: function() {
},
error: function() {
}
});
Then on your sever end you'd just want to make sure to get the parameters of your request and voila.

Resources