最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

How to stay DRY when using both Javascript and ERB templates (Rails) - Stack Overflow

programmeradmin8浏览0评论

I'm building a Rails app that uses Pusher to use web sockets to push updates to directly to the client. In javascript:

channel.bind('tweet-create', function(tweet){ //when a tweet is created, execute the following code:
  $('#timeline').append("<div class='tweet'><div class='tweeter'>"+tweet.username+"</div>"+tweet.status+"</div>");
});

This is nasty mixing of code and presentation. So the natural solution would be to use a javascript template. Perhaps eco or mustache:

//store this somewhere convenient, perhaps in the view folder:
tweet_view = "<div class='tweet'><div class='tweeter'>{{tweet.username}}</div>{{tweet.status}}</div>"

channel.bind('tweet-create', function(tweet){ //when a tweet is created, execute the following code:
    $('#timeline').append(Mustache.to_html(tweet_view, tweet)); //much cleaner
});

This is good and all, except, I'm repeating myself. The mustache template is 99% identical to the ERB templates I already have written to render HTML from the server. The intended output/purpose of the mustache and ERB templates are 100% the same: to turn a tweet object into tweet html.

What is the best way to eliminate this repetition?

UPDATE: Even though I answered my own question, I really want to see other ideas/solutions from other people--hence the bounty!

I'm building a Rails app that uses Pusher to use web sockets to push updates to directly to the client. In javascript:

channel.bind('tweet-create', function(tweet){ //when a tweet is created, execute the following code:
  $('#timeline').append("<div class='tweet'><div class='tweeter'>"+tweet.username+"</div>"+tweet.status+"</div>");
});

This is nasty mixing of code and presentation. So the natural solution would be to use a javascript template. Perhaps eco or mustache:

//store this somewhere convenient, perhaps in the view folder:
tweet_view = "<div class='tweet'><div class='tweeter'>{{tweet.username}}</div>{{tweet.status}}</div>"

channel.bind('tweet-create', function(tweet){ //when a tweet is created, execute the following code:
    $('#timeline').append(Mustache.to_html(tweet_view, tweet)); //much cleaner
});

This is good and all, except, I'm repeating myself. The mustache template is 99% identical to the ERB templates I already have written to render HTML from the server. The intended output/purpose of the mustache and ERB templates are 100% the same: to turn a tweet object into tweet html.

What is the best way to eliminate this repetition?

UPDATE: Even though I answered my own question, I really want to see other ideas/solutions from other people--hence the bounty!

Share Improve this question edited Feb 28, 2011 at 1:50 user94154 asked Feb 25, 2011 at 19:36 user94154user94154 16.6k20 gold badges81 silver badges116 bronze badges 2
  • 1 for future reference: Mustache (that you already mentioned) also allows for server-side rendering (See: mustache.github. for implementations). The flow now bees: 1 inital server-side rendering using a mustache-template + N consecutive client-side renderings using the very same mustache-template – Geert-Jan Commented Jul 26, 2011 at 9:31
  • fyi: I've asked a similar question just now (looking for alternatives to Mustache bc. of limiations) although the flow would stay the same. Perhaps that helps as well. see: stackoverflow./questions/6831718/… – Geert-Jan Commented Jul 26, 2011 at 18:29
Add a ment  | 

5 Answers 5

Reset to default 5 +50

imo the easiest way to do this would involve using AJAX to update the page when a new tweet is created. This would require creating two files, the first being a standard html.erb file and the second being a js.erb file. The html.erb will be the standard form which can iterate through and display all the tweets after they are pulled from the database. The js.erb file will be your simple javascript to append a new tweet upon creation, i.e.:

$('#timeline').append("<div class='tweet'><div class='tweeter'><%= tweet.username %></div><%= tweet.status %></div>")

In your form for the new tweet you would need to add:

:remote => true

which will enable AJAX. Then in the create action you need to add code like this:

def create
...Processing logic...
  respond_to do |format|
    format.html { redirect_to tweets_path }
    format.js
  end
end

In this instance, if you post a tweet with an AJAX enabled form, it would respond to the call by running whatever code is in create.js.erb (which would be the $('#timeline').append code from above). Otherwise it will redirect to wherever you want to send it (in this case 'Index' for tweets). This is imo the DRYest and clearest way to acplish what you are trying to do.

Thus far, the best solution I found was Isotope.

It lets you write templates using Javascript which can be rendered by both the client and server.

I would render all tweets with Javascript. Instead of rendering the HTML on the server, set the initial data up as JS in the head of your page. When the page loads, render the Tweets with JS.

In your head:

%head
  :javascript
    window.existingTweets = [{'status' : 'my tweet', 'username' : 'glasner'}];

In a JS file:

$.fn.timeline = function() {
  this.extend({
    template: "<div class='tweet'><div class='tweeter'>{{tweet.username}}</div>{{tweet.status}}</div>",
    push: function(hash){
      // have to refer to timeline with global variable
      var tweet = Mustache.to_html(timeline.template, hash)     
      timeline.append(tweet);
    }
  });  

  window.timeline = this;

  channel.bind('tweet-create', this.push);  

  // I use Underscore, but you can loop through however you want
  _.each(existingTweets,function(hash) {
    timeline.push(hash);
  });

  return this
};  


$(document).ready(function() {
  $('#timeline').timeline();
});

I haven't tried this, but this just occurred to me as a possible solution:

In your view create a hidden div which contains an example template (I'm using HAML here for brevity):

#tweet-prototype{:style => "display:none"}
    = render :partial => Tweet.prototype

Your tweet partial can render a tweet as you do now.

.tweet
    .tweeter
        = tweet.username
    .status
        = tweet.status

When creating a tweet prototype you set the fields you want to the js-template replacement syntax, you could definitely dry this up, but I'm including it here in full for example purposes.

# tweet.rb
def self.prototype
    Tweet.new{:username => "${tweet.username}", :status => "${tweet.status}"}
end

On the client you'd do something like:

var template = new Template($('#tweet-prototype').html());
template.evaluate(.. your tweet json..);

The last part will be dependent on how you're doing your templating, but it'd be something like that.

As previously stated, I haven't tried this technique, and it's not going to let you do stuff like loops or conditional formatting directly in the template, but you can get around that with some creativity I'm sure.

This isn't that far off what you're looking to do using Isotope, and in a lot of ways is inferior, but it's definitely a simpler solution. Personally I like haml, and try to write as much of my mark up in that as possible, so this would be a better solution for me personally.

I hope this helps!

To be able to share the template between the javascript and rails with a mustache template there is smt_rails: https://github./railsware/smt_rails ("Shared mustache templates for rails 3") and also Poirot: https://github./olivernn/poirot.

发布评论

评论列表(0)

  1. 暂无评论