rendrjs / rendr Goto Github PK
View Code? Open in Web Editor NEWRender your Backbone.js apps on the client and the server, using Node.js.
License: MIT License
Render your Backbone.js apps on the client and the server, using Node.js.
License: MIT License
what is a good, clean way to add a model/collection to a view in Rendr?
in the rendr-app-template there is the following example
show: function(params, callback) {
var spec = {
model: {model: 'Repo', params: params, ensureKeys: ['language', 'watchers_count']},
build: {model: 'Build', params: params}
};
this.app.fetch(spec, function(err, result) {
callback(err, result);
});
}
but what if one wants access to a model or collection without fetching it first?
Hey @spikebrehm - I started looking into rendr a bit more and had a few ideas myself about rendering things on the server...
I was thinking it might be nice if jQuery functionality could easily live in the view like it normally would, without needing a bunch of server/client specific methods... So I threw this together this morning: Fakequery...
The idea is to keep jQuery chaining consistent but as a no-op so you can continue to have animation/select other $ stuff (ajax, whatever) mixed in alongside your view methods and not have things break or require a server side dom.
Also, this is what I'm playing around extending my base server view with:
Backbone.$ = require('fakequery');
_.extend(Backbone.View.prototype, {
delegateEvents: function() { return this; },
undelegateEvents: function() { return this; },
// So we can neatly render templates on the server.
appendTo: function() {
return (_.isString(this.el) ? this.el : '');
},
// Set element will actually just create a fakequery this.$el
setElement: function(tmpl, isRendered) {
this.$el = this.$el instanceof Backbone.$ ? this.$el : Backbone.$();
if (isRendered) {
this.el = tmpl;
} else {
var view = this;
this.$el.html = function(str) { view.el = tmpl({viewArea: str}); };
}
return this;
},
// Ensure that the View has a DOM element to render into...
// but in the form of a template!
_ensureElement: function() {
if (!this.el) {
var placeholder = '<' + this.tagName;
if (this.id) placeholder += ' id="' + _.result(this, 'id') + '"';
if (this.className) placeholder += ' class="' + _.result(this, 'className') + '"';
placeholder += '><%= viewArea %></' + this.tagName + '>';
this.setElement(_.template(placeholder), false);
} else {
this.setElement(_.result(this, 'el'), true);
}
}
and my base client view:
// This allows us to re-use the same templates on the client and
// server, by calling appendTo when initially rendering, and then
// actually appending the view with $ otherwise.
Backbone.View.prototype.appendTo = function(selector) {
_.defer(_.bind(function() {
this.$(selector).append(this.el);
}, this));
return '';
};
So you can use view.appendTo(selector)
inside a template, and if it's on the server it'll just output the view as a string, otherwise it'll use jQuery to append the view's el
based on the selector after the template is rendered.
And finally, this is what my server router looks like:
Backbone.Router.prototype.route = function(route, handler) {
var router = this, fn = this[handler];
app.get('/' + route, function(req, res, next){
var args = req.route.keys.map(function(key){
return req.params[key.name];
});
// Call the route, assuming it responds with a view
// or a q.js promise resolving with a view to render.
Q(fn.apply(router, args)).then(function(view) {
view || (view = {});
if (view.el) return res.send(view.el);
return res.send(400, "Page Not Found");
}).done();
});
};
Not sure if you can use anything here for rendr, just thought you might find it interesting.
https://github.com/airbnb/rendr/blob/master/shared/base/model.coffee#L20
might be wrong, but it looks like you are overriding an add method on Backbone.Model that does not exist..
I am having some issues with passing models to views without going though collections. I have tried creating a simple model on the rendr-app-template
and it still does not add it the the views scope this.model
.
Heres my fork https://github.com/jacoblwe20/rendr-app-template
The model always is undefined. Actually on the backend side of Rendr
will get this variable and will render out the variable ( not in my example ). Then the same view will then have a undefined model in the client side.
Ive tried both fetch
ing a model then applying it or , in my example, just applying the model to a view in the view
helper from rendr-handlebars
If I create a new Model() with attributes and call model.save() with some attributes, the model should be POSTed to a restful collection with attributes in the body. However, If I do this on the server-side, the syncer seems to ignore a model's own attributes in favour of data passed as an options.data to the save() function. Is this the expected behaviour? Thanks!
What was the reasoning for switching away from Coffeescript to vanilla Javascript? I noticed that https://github.com/airbnb/rendr/commit/363cf385cd9b6f1d5a5ad0b1629a71da81b0bb2b was the final move, but I didn't see any reasoning for the switch.
In my particular case I'm trying to set a footer using a Handlebars partial. When rendered on server it works just fine, but it doesn't show on client side.
Most of my code is derived from the example app.
...
<body>
{{{body}}}
{{> footer}}
</body>
...
<div><p>Footer</p></div>
I'd like to store some status information which is permanent while handling a request and share it on both server and client views. What is the best place to put the data?
I'm trying to change RendrView.getTemplateName() behavior according to request-wide parameter (eg. app.requestIsMobile) so that view can handle both PC and mobile requests.
I tried to set app.req.isMobile at server.js and it could be referenced on server-side view rendering, but was not shared on client side view.
module.exports = function(match) {
// matches /s?product=shamwow on server, but not in client
match('s', 'products#search');
// matches /s?product=slapchop on client, but blows up on server
match('s/*wtf', 'products#search');
};
This is because Backbone and Express support different, slightly incompatible route formats. There's a workaround, but it's ugly:
module.exports = function(match) {
if(global.isServer) match('s', 'products#search');
else match('s/*wtf', 'products#search');
};
Possible fixes:
*name
routes for ExpressSecond seems preferable, since that way future incompatibilities won't break things.
Thoughts?
I need to access some response headers returned by the API I'm using, but the parse()
method only seems to return a response object and request options at the moment.
I tried many a times to do "npm install" in rendr-app-template repo, but during the ./scripts/postinstall.js execution, the rendr install fails abruptly. [On OS X, it went smoothly though.].
Not sure if its specific to my environment ..
(Using GitShell for shell environment)
Windows 8 x64,
$ node --version
v0.10.5
$ npm --version
1.2.9
<<<
Please confirm if this indeed an issue (i.e. not specific to my env), then I can volunteer to try fixing and provide a pull request.
Rendr rocks! thanks for releasing it!
One problem I see with the caching layer saving in JSON is that it gets hard to have changes to models update views sharing the same model (since they don't share the same instance). Example: ViewA and ViewB share model data (different instantiations from fetcher getModelBySpec). ViewA updates the model through some sort of event, it stores it back to the cache, but ViewB has a stale version of the model data until it fetches from the store again.
Is there a good way to handle this?
One way is to have the BaseModel listen on the model store on a unique key such as 'updated_-some-key-' and have the BaseModel update from the key.
Ideas?
Hi,
What's the best way to get a backbone plugin (specifically backbone.paginator) included into mergedAssets?
I'm not managing to get it's underscore dependency resolved at the moment. I get: Uncaught ReferenceError: _ is not defined
Thanks!
The "view" helper creates a view instance (which calls postInitialize), and then in .attach the view is re-constructed and attached to the dom element. This means postInitialize is called twice for each view. Would it make sense to have postInitialize not called by the handlebars helper? Perhaps "noPostInitialize" through the constructor.
Also, .remove is never called for the views initialized and then scrapped in the view helper.
I can whip up a PR if this makes sense.
I'd like to add a Handlebars helper, is there a hook of some sort that will allow me to do this so it will work on the client and server?
The following code enforces a controller filenaming structure that, I believe, should be optional.
BaseRouter.prototype.getController = function(controllerName) {
var controllerDir, controller;
controllerDir = this.options.paths.controllerDir;
try {
controller = require(controllerDir + "/" + controllerName + "_controller");
} catch (e) {
controller = undefined;
}
return controller;
};
translate to directory stuctures like so
app
controllers
foo_controller.js
bar_controller.js
baz_controller.js
I propose removing the hardcoded '_controller' requirement on controller filenames so we can DRY it up a bit and have a directory of controllers like so
app
controllers
foo.js
bar.js
baz.js
Additionally enforcing a search order so controllers could be organized, optionally, by the developer, into logical groups, would also be nice.
Hi,
I'm not sure if I just don't know how, but there seems to be no model/collection associated when view helper generated sub-view initialized.
I dumped 'this' object in postInitialize() of the sub-view and there was no model/collection included in the object.
Is it possible to associate model/collection to sub-views?
I need to execute fetch function on a collection to update content on client side after rendering it on server side.
I have the following routes in my routes.js
:
module.exports = function (match) {
match('', 'home#index');
match('posts', 'posts#index');
};
Inside my app I do pagination and therefore I would like to reflect the current page in the browsers address bar. But the following statement will not match my route:
App.router.redirectTo('/posts?page=3')
So how can I use the router to redirect to routes including query strings? Or how can I update the browsers address bar to reflect the current page dynamically using the rendr router?
I have a model book and a collection library with books in it.
library has /books
url, book has /books/:_id
url
So when i fetch books, all is OK. When i delete book, it is ok, but when i make collection.create({bla-bla})
, it POST
s to book url (/books/undefined), NOT library url. But it has to POST
to collections url. (/books
)
I'm trying to add https://github.com/joneath/infiniScroll.js, but having some trouble.
In the Gruntfile backbone is defined as an npm dependency so it gets included after the dependencies. Since infiniScroll depends on Backbone this is an issue.
Then I'm not sure how to access backbone in my view to init infiniScroll new Backbone.InfiniScroll(...)
. I've tried require('backbone')
but I get a backbone is undefined error.
I want to do something like this:
// app/controllers/posts_controller.js
module.exports = {
add: function(params, callback) {
var that, view;
that = this;
view = callback(null, 'posts_add_view');
view.on('done', function() {
that.redirectTo('/posts');
});
}
};
However, the callback
returns nothing.
How can I get the view in an action?
Line 49 in /rendr/shared/base/model.js:
throws an error of TypeError: this.app is undefined
when I call save in my view.
I'm doing something like this from the view
var ProjectModel = require('../models/project')
....
this.model = new ProjectModel();
....
this.model.save(null, {
wait: true,
success: function(model, res) {
...
How is one expected to expose data from a database (rather than an API)?
I've been hacking on server/lib/data_adapter.js
from the app template to talk directly to Mongo, but I'm not sure that is the "right way" to do it. Are there any examples/documentation for such a thing?
Hey,
So, I was writing my own version of a serverside Backbone implementation, but I guess the issue will persist here too.
The idea is that I want to use a different instance of the app per user. So, what I am doing is creating a key-value datastore for the session and writing the model attributes here. Whenever a request for a page is made, I create a new instance of the Model with the attributes from the datastore ( the datastore can store just pure values, and not functions), carry out whatever get and set operations I need, save the new attributes to the datastore, and render the view back.
Can you think of a cleaner way of going about this? My personal goal with this is to create a framework of sorts in which the same Backbone code will run seamlessly on both the client and the server. Do let me know your thoughts on the same.
Ameya
We include the entire async
module, and require it in the client, just to use async.parallel
in fetcher
:
https://github.com/airbnb/rendr/blob/master/shared/fetcher.js#L185
Find/write a tiny module that just provides the parallel
method.
I may be missing this functionality, so please feel free to correct me if I'm mistaken, but it would be nice to support, and see an example of, multiple api hosts / protocols / etc in rendr.
take, for example, the rendr-app-template
project:
config/development.js
contains the following
api: {
host: "api.github.com",
protocol: "https"
},
This is, naturally, quite convenient if you only have one host and our models can stay clean and tidy like so...
module.exports = Base.extend({
url: '/users/:login',
idAttribute: 'login'
});
However, often we need access to multiple hosts for data and, after digging through portions of the Rendr code, I don't currently see a supported way to do this.
I propose something akin to the following
config/development.js
may have multiple apis like so
apis: {
"default": {
host: "localhost",
protocol: "http",
port: "3022"
},
"api_one": {
host: "fancyapi.foo.com",
protocol: "https"
},
"api_two": {
host: "fancyapi.bar.com",
protocol: "http"
}
}
Then in our models if no api prop is defined we will use the 'default' api. Otherwise we'll use the api specified.
So if I wanted to use the api_one
api, my model would now look something like the following
module.exports = Base.extend({
url: '/users/:login',
idAttribute: 'login',
api: 'api_one'
});
Again, if this exists and I'm missing something, please accept my apologies, otherwise I'm happy discuss and implement
I haven't tested any of this, but my intuition tells me this could be a problem. Do correct me if I messed up somewhere
So, as far as I know, the bindings on backbone models are asynchronous. Now, when I am rendering a view after some changes to a model, if the changes are not computationally heavy or time consuming (say Ajax requests), it won't be a problem. However, whenever there is some phase lag, my view may render from data that is not updated. Do you think the bind method could be hacked for better use on the server?
One of the todos of this project is to
Support Browserify and streamline module packaging
I think you should consider using commonjs-everywhere. It is an alternative to browserify that I've happened to have more luck with and think is pretty awesome
Anyways, just a suggestion.
/cc @michaelficarra
Hey,
I'm testing out lazy loading of view data and I can't seem to get it to work twice.
Here is the scenario:
ViewA lazy loads a model, it is instantiated with model_name and model_id, and lazy=true
The first time ViewA is loaded the fetchLazy function is called since the following check is true in attach():
if @options.lazy is true && [email protected]? && [email protected]?
The model is fetched and the view re-rendered on success. The model is stored into model-store.
I navigate away from ViewA and come back to it. ViewA is again instantiated with model_name and model_id. This time the hydrate() function in attach() goes to the fetcher and grabs the model out of the store given the model_name and model_id paramaters.
The view now has an instantiated model from the store but is never re-rendered since the fetchLazy check (if @options.lazy is true && [email protected]? && [email protected]?) is false and fetchLazy is never called.
Am I going about this the wrong way somehow?
When Handlebars {{ view }} helper initiates a new view, it seems that that view instance doesn't get attached to the parentView while BaseView.attach happens. BaseView.attach just creates a new view instance which is weird. My problem is that if I have the child view, that gets created by Handlebars view helper, register any callbacks on for example this.app (I try to use this app as events mediator), these callbacks will never be removed when a controller renders some new view. Child view created by Handlebars doesn't get added to parentView child list therefore parent never removes it...
I've been trying to solve this issue for hours but it feels like I am stuck :/
The documentation is fragmented and needs unified into one location and expanded upon. Possibly something on https://readthedocs.org/ would be nice, that contains a full explanation of the API .
Change second arg from boolean to object.
https://github.com/airbnb/rendr/blob/master/shared/base/collection.coffee#L43
Would it be possible to merge this two awesome plugins?
This is only for the base view and the router because marionette doesn't overwrite models and collections.
Haven't tried yet but maybe by merging the code together would work (initialize, render, ..) or simply by _.extend with multiple source.
Hello,
I'm building an app that has models inside of models.
Model1 = Page
Model2 = Widget
Page contains a collection of widgets.
My app gets rendered on the server side fine. Then on the client, all pages except the first work correctly. The first page... if I navigate away from.. and then come back.. does not work.
It seems that the initial model being generated from the bootstrap data does not get parsed correctly to generate the embedded collection and models.
How can I go about doing this? When the code hits the client side, it doesn't look like parse gets called. Where can I re-set up my embedded models?
Khurram
I'm writing an example rendr blog. The following can get the model if the user comes to this view by clicking a link on another view. But it can't if the user comes to this view directly (e.g. by pressing F5 to reload the view).
https://github.com/trantorLiu/rendr-example-blog/blob/master/app/views/posts_del_view.js
How to get the model/collection of a view after loading the app?
Hi Spike,
I am trying to figure out what is the best way to do an api fetch from a plugin I have inserted in postRender() of a view the rendr way (of course I can just do the regular ajax call regardless of the rendr way). This plugin needs to do a url fetch but I see that in order to call the fetch() function properly I need some kind of pushState to be called, which would call the router and in turn call the controller for things to be hooked correctly.
I am trying to find the right method of doing this in the rendr context. Do you have an example of a client side only piece of code that goes through the postRender() but does a fetch with a model/collection etc? This would be so helpful for the documentation!!
Let me know if you need more details for this case.
Thanks,
Adelein
And add note in readme about using es5shim / es5sham for IE<=8
I dont know if this is an issues with rendr-stitch
or rendr
but I have had this happen to me two separate times on different environments.
I get this error
Running "rendr_stitch:compile" (rendr_stitch) task
Warning: Unable to read "node_modules/rendr/node_modules/inherit-component/index.js" file (Error code: ENOENT). Use --force to continue.
When running rendr-stitch
through grunt
. The reason I put it here is that it looks like its trying to access this module through rendr
's dependencies.
I can fix it manually installing it into rendr
. Should this module be here? Or should it be in rendr-stitch
When running npm install on a fresh project, I'm getting the following postInstall errors
npm WARN package.json [email protected] No repository field.
npm http GET https://registry.npmjs.org/underscore/1.4.4
npm http GET https://registry.npmjs.org/async/0.1.22
npm http 304 https://registry.npmjs.org/underscore/1.4.4
npm http 304 https://registry.npmjs.org/async/0.1.22
npm http GET https://registry.npmjs.org/optimist
npm http GET https://registry.npmjs.org/uglify-js
npm http 304 https://registry.npmjs.org/optimist
npm http 304 https://registry.npmjs.org/uglify-js
npm http GET https://registry.npmjs.org/wordwrap
npm http 304 https://registry.npmjs.org/wordwrap
npm WARN package.json [email protected] No repository field.
npm WARN package.json [email protected] No repository field.
npm WARN package.json [email protected] No repository field.
npm WARN package.json [email protected] No repository field.
npm WARN package.json [email protected] No repository field.
npm WARN package.json [email protected] No repository field.
npm WARN package.json [email protected] No repository field.
npm WARN package.json [email protected] No repository field.
npm WARN package.json [email protected] No repository field.
npm WARN package.json [email protected] No repository field.
npm WARN package.json [email protected] No repository field.
npm WARN unmet dependency /Users/a598063/Desktop/rendrCliTests/node_modules/rendr/node_modules/express/node_modules/connect requires debug@'*' but will load
npm WARN unmet dependency undefined,
npm WARN unmet dependency which is version undefined
npm WARN unmet dependency /Users/a598063/Desktop/rendrCliTests/node_modules/rendr/node_modules/express/node_modules/send requires debug@'*' but will load
npm WARN unmet dependency undefined,
npm WARN unmet dependency which is version undefined
I am trying to figure out how to do this but dont see an example in the rendr-app-template.
This is the typical scenario when you just added an element to a list and want to show the new element dynamically, so it would be nice to be able to reuse the partial already used to render each element.
Anyone know how to do this?
When retrieving data from 3rd party APIs using this.app.fetch, Rendr apps include a Content-Length header and entity body. While this isn't explicitly forbidden by RFC 2616, it is uncommon and can cause APIs to return an HTTP 400 Bad Request error.
An example of an API that breaks is Google's V3 YouTube API. GET requests to this API will result in a 400 error.
By adding a few logging lines to request, we can see that the entity body is:
<Buffer 7b 7d>
and it is sending a Content-Length header of 2.
As far as I can tell, the Content-Length header is automatically added by request because an entity body is included.
To show that this header is the problem with the YouTube API, you can implement a hacky fix by deleting the Content-Length header on GET requests in request/index.js. In request/index.js at line 244, add this:
if (self.method == "GET"){
delete self.headers['content-length']
}
After adding this, Google's YouTube API responds correctly to requests from Rendr. This hack illustrates the problem, but it obviously is not a good solution.
You can reproduce this by creating a simple Rendr project based on rendr-app-template using the YouTube API.
In your congig:
api: {
'default': {
host: 'www.googleapis.com/youtube/v3',
protocol: 'https'
},
},
Then create a model:
var Base = require('./base');
module.exports = Base.extend({
url: '/videos',
});
module.exports.id = 'Video';
In a controller, fetch a video:
var spec = {
model: {
model: 'Video',
params: {
'part' : 'id,snippet,contentDetails',
'id' : 'dQw4w9WgXcQ,
'key' : 'YOUR API KEY'
}}
};
self.app.fetch(spec, function(err, result) {
if (err){
console.log(err);
}
if (result){
console.log(result);
}
})
Separate HTML generation (templating, view model) from client side event handling. Make it easy to use Angular, Flight, etc.
I have a collection in which I set some custom properties in the collection.parse()
method which i need for rendering the pagination:
parse: function(res) {
this.page = res.page;
this.perPage = res.perPage;
this.total = res.total;
return res.items;
}
It works fine and I can use the collection.total
value to render the pagination on the server.
But after the view for this collection got rendered on the server and the same view then gets hydrated on the client, collection.total
is not set in the hydrated client side collection.
And it won't be set, because the fetch
/parse
method is not run, when the view was rendered server side.
Is there any way to include custom properties of collections/models when doing the view hydration?
Hi :-)
I need to access this.collection
inside the initialize() function of a view. It works well on the server, but if i try to access the view collection in the browser it is undefined
. (Only collection_name
is set in the browser)
Is this a bug, or am I doing it wrong?
Daniel
I'm trying to pass multiple collections to my view / template.
Here is what my index controller looks like:
module.exports = {
index: function(params, callback) {
var spec = {
collection: {
collection: 'Collection1',
params: params
},
conferences: {
collection: 'Collection2',
params: {}
}
};
this.app.fetch(spec, function(err, result) {
callback(err, 'my_index_view', result);
});
},
but I don't see how the conferences
collection is exposed to the view. Maybe it's not and there's a better way to do this?
Nabbed from the TODOs.
I'd love to integrate Teacup for more CoffeeScript bliss. It's hardly the most popular solution but integration should be simple, so I think it's a sensible first step. Including templates directly in the View modules could be fun, since they're already one to one.
Alternatively, we could go big and add pluggable support for the Consolidate.js signature.
Looks like I'll need to hook into view.getTemplate()
and maybe write a templateFinder
and some helpers.
Biggest questions are where and how to choose a template engine. App smells about right. We could borrow Express's app.engine(extension, function (path, options, callback))
signature. Requiring templates to map to paths seems limiting to me but I might be the minority.
It appears that the views/templates directories require their items to be immediate children of the directory like so.
views
foo_index_view.js
foo_show_view.js
foo_otheraction_view.js
bar_index_view.js
bar_show_view.js
bar_otheraction_view.js
templates
foo_index_view.hbs
foo_show_view.hbs
foo_otheraction_view.hbs
bar_index_view.hbs
bar_show_view.hbs
bar_otheraction_view.hbs
It would be nice, and arguably cleaner, to have the ability to structure the files like so
views
foo
index.js
show.js
otheraction.js
bar
index.js
show.js
otheraction.js
templates
foo
index.hbs
show.hbs
otheraction.hbs
bar
index.hbs
show.hbs
otheraction.hbs
It would be nice to have url helpers, notably similar to the Rails url_for
The clear win here is, you can change your URI structure and do it, presumably, by only editing your routes file(s).
Usage would look like the following
Assuming a Route
module.exports = function(match) {
...
match('foo/bar', 'foo#bar');
...
};
Calling the url_for method, either via Handlebars in a template
url_for({controller: 'foo', action: 'bar'});
or
url_for('foo#bar');
Or in a controller, if need be
SomeParentOb.url_for({controller: 'foo', action: 'bar'});
Would yield
'/foo/bar'
Additionally, having the ability to pass options that could yield nicely formatted query strings, resource ids, etc would be nice. (haven't thought through these bits too much yet).
I'm happy to throw some cycles at this and submit a pull request, but it'll be a few days before I can make this happen.
As I see it, there are a few ways to approach this.
rendrServer.router.buildRoutes()
to essentially create a mapping object keyed by the controller#action
w/ the resulting url as the val. and url_for can grab what it needs quite easilyrendrServer.router.buildRoutes()
and parse through it. This would make the url_for method more expensive, seems less optimalI suppose this could also be pulled into it's own module, but I'd need a little direction as to how to do this and make it plug in, optionally, in a very clean way to the existing Rendr system.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.