简体   繁体   English

在rails 3.1中更改视图格式(提供移动html格式,在普通html上回退)

[英]Changing view formats in rails 3.1 (delivering mobile html formats, fallback on normal html)

I'm creating a mobile site next to our normal html site. 我正在我们正常的html网站旁边创建一个移动网站。 Using rails 3.1. 使用rails 3.1。 Mobile site is accessed in subdomain m.site.com. 移动站点在子域m.site.com中访问。

I have defined mobile format (Mime::Type.register_alias "text/html", :mobile). 我已经定义了移动格式(Mime :: Type.register_alias“text / html”,:mobile)。

In ApplicationController I have "before_filter :mobile_site_before_filter" that recognizes mobile site and sets format according to it. 在ApplicationController中,我有“before_filter:mobile_site_before_filter”来识别移动站点并根据它设置格式。

def mobile_site_before_filter
  if request.subdomains.first == 'm'
    original_format = request.format
    request.format = :mobile
    @mobile_site = true
    request.formats.push(original_format)
  end
end

In layouts I have 'main.html.erb' and 'main.mobile.erb'. 在布局中,我有'main.html.erb'和'main.mobile.erb'。 So with mobile sites mobile layout is used. 因此,对于移动网站,使用移动布局。

Now it kind of works ok. 现在有点工作了。

In UserController I have index action which chooses index.html.erb or index.mobile.erb automatically. 在UserController中,我有索引操作,它自动选择index.html.erb或index.mobile.erb。 No extra coding in top of mobile-view needed. 无需在移动视图顶部进行额外编码。 Success. 成功。

But I have lots of other views where the same template could be used to serve inside mobile layouts but with minor changes. 但是我有很多其他视图,其中相同的模板可以用于在移动布局内部进行,但只需稍作修改。

Eg in MessagesController the same view would be almost fine for mobile 例如,在MessagesController中,相同的视图几乎可以用于移动设备

In index.html.erb
Normal user info, common for mobile and html
<%= render(:partial => 'messages') %>

Rendering messages.html.erb, no need for messages.mobile.erb. 呈现messages.html.erb,不需要messages.mobile.erb。 mobile view can be done with css 移动视图可以用css完成

<%# By default self.formats = [:html] because this is .html.erb partial, even in mobile site %>
<%= self.formats = [:mobile, :html] if @mobile_site # How to get rid of this? %>
<%= render(:partial => 'vote_form') %>
<!-- rest of page ... -->

Now i want to render vote_form.[mobile|html].erb depending on the site... 现在我要渲染vote_form。[mobile | html] .erb取决于网站...

Now the index.html.erb partial would be fine to use with mobile if I could just choose between vote_form.html.erb or vote_form.mobile.erb. 现在,如果我可以在vote_form.html.erb或vote_form.mobile.erb之间进行选择,那么index.html.erb partial可以用于移动设备。 I can choose to use mobile partial with using "self.formats = [:mobile, :html] if @mobile_site" in the beginning of index.html.erb . 我可以选择使用mobile partial ,在index.html.erb的开头使用“self.formats = [:mobile,:html] if @mobile_site”。 But it feels stupid to write this in beginning of all templates. 但是在所有模板的开头写这个都感觉很愚蠢。

So the question is: 所以问题是:

  • Where does the self.formats come in views (what sets it originally) and how can I set that it's always [:mobile, :html] inside the mobile site? self.formats在视图中的位置(最初设置的是什么)以及如何在移动站点内设置它始终是[:mobile,:html]? Why isn't it the same as i set in controller before_filter? 为什么它与我在控制器before_filter中设置的不一样? Can I set it somehow in the controller already? 我可以在控制器中以某种方式设置它吗?

(minor bonus questions) (小额奖金问题)

  • Is there something wrong in this approach? 这种方法有什么问题吗? Using mostly the same html-views and in some specific cases using mobile.erb views instead. 使用大多数相同的html视图,在某些特定情况下使用mobile.erb视图。 Why doesn't this work by default in rails? 为什么默认情况下这不适用于rails?

  • Is there other ways to choose to render :mobile views if found with fallback to normal html view? 是否还有其他方法可以选择渲染:如果找到移动视图并回退到正常的html视图? Even across the partials so that html.erb-view tries to use _partial.mobile.erb partials. 甚至跨越部分,以便html.erb-view尝试使用_partial.mobile.erb部分。

How can I set that it's always [:mobile, :html] inside the mobile site? 如何在移动网站内设置始终为[:mobile,:html]? Can I set it somehow in the controller already? 我可以在控制器中以某种方式设置它吗?

Yes. 是。

Here's a simple solution, but it's a bit gross. 这是一个简单的解决方案,但它有点粗略。

class ApplicationController
    ...
    def formats=(values)
        values << :html if values == [:mobile]
        super(values)
    end
    ...
end

It turns out Rails (3.2.11) already adds an :html fallback for requests with the :js format. 事实证明,Rails(3.2.11)已经为:js格式的请求添加了一个:html回退。 Here's ActionView::LookupContext#formats= 这是ActionView::LookupContext#formats=

# Override formats= to expand ["*/*"] values and automatically
# add :html as fallback to :js.
def formats=(values)
  if values
    values.concat(default_formats) if values.delete "*/*"
    values << :html if values == [:js]
  end
  super(values)
end

So you can override formats yourself and it will be conceivably no more gross and hacky than the existing Rails implementation. 因此,您可以自己覆盖格式,并且可以想象它不会比现有的Rails实现更糟糕。

Where does the self.formats come in views (what sets it originally) self.formats在视图中的位置(最初设置的是什么)

ActionController::Rendering#process_action assigns the formats array from the request (see action_controller/metal/rendering.rb ) ActionController::Rendering#process_action从请求中分配格式数组(请参阅action_controller/metal/rendering.rb

# Before processing, set the request formats in current controller formats.
def process_action(*) #:nodoc:
  self.formats = request.formats.map { |x| x.ref }
  super
end

Why isn't it the same as i set in controller before_filter? 为什么它与我在控制器before_filter中设置的不一样?

It's not the same as what you set in your before_filter because before filters are run before #process_action (as you'd expect). 它与您在before_filter中设置的内容不同,因为 #process_action之前运行过滤器(正如您所期望的那样)。 So whatever you set gets clobbered by what #process_action pulls off the request. 所以无论你设置什么都会被#process_action拉出请求所破坏。

I think I've found the best way to do this. 我想我找到了最好的方法。 I was attempting the same thing that you were, but then I remembered that in rails 3.1 introduced template inheritance , which is exactly what we need for something like this to work. 我正在尝试与你相同的事情,但后来我记得在rails 3.1中引入了模板继承 ,这正是我们需要这样的东西才能工作。 I really can't take much credit for this implementation as its all laid out there in that railscasts link by Ryan Bates. 我真的不能为这个实现付出太多的荣誉,因为它全部都是由Ryan Bates在railscasts链接中提出的。

So this is basically how it goes. 所以这基本上就是这样。

Create a subdirectory in app/views . app/views创建一个子目录。 I labeled mine mobile . 我把我的mobile标记为

Nest all view templates you want to override in the same structure format that they would be in the views directory. 将要覆盖的所有视图模板嵌套在与它们在views目录中相同的结构格式中。 views/posts/index.html.erb -> views/mobile/posts/index.html.erb

Create a before_filter in your Application_Controller and do something to this effect. Application_Controller创建一个before_filter并执行此操作。

 before_filter :prep_mobile
 def is_mobile?
   request.user_agent =~ /Mobile|webOS|iPhone/
 end 
 def prep_mobile
   prepend_view_path "app/views/mobile" if is_mobile?
 end

Once thats done, your files will default to the mobile views if they are on a mobile device and fallback to the regular templates if a mobile one is not present. 完成后,如果移动设备位于移动设备上,您的文件将默认为移动视图,如果移动设备不存在,则会回退到常规模板。

Rails 4.1 includes a pretty neat feature: Rails 4.1包含一个非常简洁的功能:

Variants 变种

Allows you to have different templates and action responses for the same mime type (say, HTML). 允许您为相同的mime类型(例如,HTML)使用不同的模板和操作响应。 This is a magic bullet for any Rails app that's serving mobile clients. 这是任何为移动客户端提供服务的Rails应用程序的灵丹妙药。 You can now have individual templates for the desktop, tablet, and phone views while sharing all the same controller logic. 您现在可以拥有桌面,平板电脑和手机视图的各个模板,同时共享所有相同的控制器逻辑。

Now you can do something like this: 现在你可以这样做:

class PostController < ApplicationController
  def show
    @post = Post.find(params[:id])

    respond_to do |format|
      format.json
      format.html               # /app/views/posts/show.html.erb
      format.html.phone         # /app/views/posts/show.html+phone.erb
      format.html.tablet do
        @show_edit_link = false
      end
    end
  end
end

You simply need to set the variant depending on your needs, for example within a before_filter : 您只需根据需要设置变量,例如在before_filter

class ApplicationController < ActionController::Base
  before_action :detect_device_variant

  private

    def detect_device_variant
      case request.user_agent
      when /iPad/i
        request.variant = :tablet
      when /iPhone/i
        request.variant = :phone
      end
    end
end

What's new in Rails 4.1 Rails 4.1中有什么新功能

You can register new format for the whole application in your mime type initializers: 您可以在mime类型初始值设定项中注册整个应用程序的新格式:

 Mime::Type.register_alias "text/html", :mobile

Now you can do something like this in your templates to specify format priority(see How do I render a partial of a different format in Rails? ): 现在,您可以在模板中执行类似的操作以指定格式优先级(请参阅如何在Rails中呈现不同格式的部分内容? ):

<% self.formats = [:mobile, :html] %>

In this case if there is mobile template it will be used for rendering with fallback to ordinary html template. 在这种情况下,如果有移动模板,它将用于渲染回退到普通的html模板。 Now you should only determine if user is browsing via mobile browser and conditionally execute this code. 现在,您应该只确定用户是否通过移动浏览器进行浏览并有条件地执行此代码。 Or you can just assign formats value in ApplicationController filter so correct template will be chosen automaticaly . 或者您可以在ApplicationController过滤器中指定格式值,以便自动选择正确的模板。

UPDATE: 更新:

It seems like by this time there is no "legal" way to solve this problem using Rails. 好像到了这个时候,没有“合法”的方法来使用Rails解决这个问题。 Here is unclosed issue in the rails repository. 这是rails存储库中未公开的问题 There you can find patch that can solve your problem, but it uses private Rails API, so it can be unstable. 在那里你可以找到可以解决你的问题的补丁,但它使用私有的Rails API,所以它可能不稳定。

Also you can try implement your own view resolver that possibly can solve the problem: http://jkfill.com/2011/03/11/implementing-a-rails-3-view-resolver/ 您也可以尝试实现自己的视图解析器,它可能解决问题: http//jkfill.com/2011/03/11/implementing-a-rails-3-view-resolver/

Why don't you look into Responsive Web Design instead, use CSS and media queries to render the page. 为什么不改为使用响应式Web设计 ,使用CSS和媒体查询来呈现页面。 That way your controller need never know whether the view is mobile or not. 这样你的控制器就不需要知道视图是否可移动。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM