Module: Ruflet::Rails::ViewHelpers

Defined in:
lib/ruflet/rails/view_helpers.rb

Overview

ERB view helpers for dropping a Ruflet native UI into a server-rendered Rails page. Auto-included into ActionView (see Railtie), so they are available in any .erb template.

A Ruflet web frontend is a Flutter app mounted at a route (see Ruflet::Rails.web_app in config/routes.rb). To embed that native UI inside an HTML page, render it in an isolated frame pointed at the mount:

<%= ruflet_frame "/products", height: 640 %>
<%= ruflet_frame "/showcase", height: "80vh", width: "100%" %>

The frame is same-origin by default (a relative path), so the Ruflet WebSocket and assets resolve against this host with no extra config. Pass a full URL to embed a Ruflet app on another host.

Instance Method Summary collapse

Instance Method Details

#ruflet_appbar(title = nil, payload: {}, leading: nil, actions: nil, **attrs, &block) ⇒ Object

A header the native shell hides and re-renders as a native AppBar. Place action items inside with ruflet-icon (use ruflet_appbar_action).

<%= ruflet_appbar "Inbox" do %>
<%= ruflet_appbar_action "search", search_path %>
<% end %>


62
63
64
65
66
67
68
69
70
# File 'lib/ruflet/rails/view_helpers.rb', line 62

def ruflet_appbar(title = nil, payload: {}, leading: nil, actions: nil, **attrs, &block)
  appbar_payload = ruflet_stringify_keys(payload)
  appbar_payload["leading"] = ruflet_stringify_keys(leading) unless leading.nil?
  appbar_payload["actions"] = ruflet_stringify_keys(actions) unless actions.nil?
  data = { "data-ruflet-appbar" => appbar_payload.empty? ? "" : ruflet_json(appbar_payload),
           "data-ruflet-title" => title,
           "hidden" => true }
  ruflet_build_tag("header", ruflet_capture(&block), data.merge(ruflet_dash_attrs(attrs)))
end

#ruflet_appbar_action(icon, href = nil, nav: :push, payload: {}, **attrs) ⇒ Object

An AppBar action button (icon taps navigate).



73
74
75
76
77
# File 'lib/ruflet/rails/view_helpers.rb', line 73

def ruflet_appbar_action(icon, href = nil, nav: :push, payload: {}, **attrs)
  payload = ruflet_compact_hash({ "icon" => icon, "action" => nav.to_s }.merge(ruflet_stringify_keys(payload)))
  data = { "data-ruflet-icon" => ruflet_json(payload), "href" => href }
  ruflet_build_tag("a", "", data.merge(ruflet_dash_attrs(attrs)))
end

#ruflet_bottom_nav(name: "bottom-navigation", payload: {}, **attrs, &block) ⇒ Object

A nav the native shell hides and re-renders as a native bottom NavigationBar. Items are ruflet_nav_item links.

<%= ruflet_bottom_nav do %>
<%= ruflet_nav_item "Home", root_path, icon: "house", selected: true %>
<%= ruflet_nav_item "Profile", profile_path, icon: "person" %>
<% end %>


86
87
88
89
90
91
92
# File 'lib/ruflet/rails/view_helpers.rb', line 86

def ruflet_bottom_nav(name: "bottom-navigation", payload: {}, **attrs, &block)
  nav_payload = ruflet_stringify_keys(payload)
  ruflet_build_tag("nav", ruflet_capture(&block), {
    "data-ruflet-tabs" => nav_payload.empty? ? name : ruflet_json(nav_payload),
    "hidden" => true
  }.merge(ruflet_dash_attrs(attrs)))
end

#ruflet_copy_button(label, text:, toast: "Copied", haptic: true, **attrs) ⇒ Object



170
171
172
173
# File 'lib/ruflet/rails/view_helpers.rb', line 170

def ruflet_copy_button(label, text:, toast: "Copied", haptic: true, **attrs)
  payload = ruflet_compact_hash(component: "clipboard", text: text, toast: toast, haptic: haptic)
  ruflet_action_tag("button", label, payload, { "type" => "button" }.merge(ruflet_dash_attrs(attrs)))
end

#ruflet_drawer(action: :root, payload: {}, **attrs, &block) ⇒ Object

A drawer the native shell hides and re-renders as a native NavigationDrawer. Items are ruflet_drawer_item links.

<%= ruflet_drawer do %>
<%= ruflet_drawer_item "Home", root_path, icon: "home" %>
<%= ruflet_drawer_item "Settings", settings_path, icon: "settings", nav: :push %>
<% end %>


113
114
115
116
117
118
# File 'lib/ruflet/rails/view_helpers.rb', line 113

def ruflet_drawer(action: :root, payload: {}, **attrs, &block)
  ruflet_build_tag("nav", ruflet_capture(&block), {
    "data-ruflet-drawer" => ruflet_json(ruflet_compact_hash(ruflet_stringify_keys(payload).merge(action: action.to_s))),
    "hidden" => true
  }.merge(ruflet_dash_attrs(attrs)))
end

#ruflet_drawer_item(label, href, icon:, selected: false, nav: nil, payload: {}, **attrs) ⇒ Object

A destination inside ruflet_drawer.



121
122
123
124
125
126
127
128
129
130
# File 'lib/ruflet/rails/view_helpers.rb', line 121

def ruflet_drawer_item(label, href, icon:, selected: false, nav: nil, payload: {}, **attrs)
  item_payload = ruflet_stringify_keys(payload)
  data = {
    "href" => href,
    "data-ruflet-icon" => ruflet_json(ruflet_compact_hash(item_payload.merge(icon: icon, label: label,
                                                                             action: nav&.to_s)))
  }
  data["data-ruflet-selected"] = "true" if selected
  ruflet_build_tag("a", label, data.merge(ruflet_dash_attrs(attrs)))
end

#ruflet_frame(path, height: 600, width: "100%", title: "Ruflet", style: nil, **attrs) ⇒ Object

Embed a mounted Ruflet route as an isolated, responsive frame.

path - the Ruflet mount route ("/products") or a full URL. height - CSS height; a number is treated as pixels (default 600). width - CSS width; a number is treated as pixels (default "100%"). title - iframe title for accessibility (default "Ruflet"). style - extra CSS appended to the frame's inline style. attrs - any other iframe attributes (underscores become dashes).



31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/ruflet/rails/view_helpers.rb', line 31

def ruflet_frame(path, height: 600, width: "100%", title: "Ruflet", style: nil, **attrs)
  base_style = "border:0;width:#{ruflet_css_dimension(width)};height:#{ruflet_css_dimension(height)};"

  attributes = {
    "src" => path.to_s,
    "title" => title.to_s,
    "loading" => "lazy",
    "style" => [base_style, style].compact.join(" ").strip
  }
  attrs.each { |key, value| attributes[key.to_s.tr("_", "-")] = value }

  markup = +"<iframe"
  attributes.each { |key, value| markup << %( #{key}="#{ERB::Util.html_escape(value)}") }
  markup << "></iframe>"
  ruflet_html_safe(markup)
end

#ruflet_haptic_button(label, style: "selection", **attrs) ⇒ Object



180
181
182
183
# File 'lib/ruflet/rails/view_helpers.rb', line 180

def ruflet_haptic_button(label, style: "selection", **attrs)
  payload = ruflet_compact_hash(component: "haptic", style: style)
  ruflet_action_tag("button", label, payload, { "type" => "button" }.merge(ruflet_dash_attrs(attrs)))
end


175
176
177
178
# File 'lib/ruflet/rails/view_helpers.rb', line 175

def ruflet_launch_link(label, href, mode: nil, **attrs)
  payload = ruflet_compact_hash(component: "url_launcher", url: href, mode: mode)
  ruflet_action_tag("a", label, payload, { "href" => href }.merge(ruflet_dash_attrs(attrs)))
end

#ruflet_nav_item(label, href, icon:, selected: false, color: nil, size: 24, payload: {}, **attrs) ⇒ Object

A destination inside ruflet_bottom_nav.



95
96
97
98
99
100
101
102
103
104
# File 'lib/ruflet/rails/view_helpers.rb', line 95

def ruflet_nav_item(label, href, icon:, selected: false, color: nil, size: 24, payload: {}, **attrs)
  item_payload = ruflet_stringify_keys(payload)
  data = {
    "href" => href,
    "data-ruflet-icon" => ruflet_json(ruflet_compact_hash(item_payload.merge(icon: icon, label: label,
                                                                             color: color, size: size)))
  }
  data["data-ruflet-selected"] = "true" if selected
  ruflet_build_tag("a", label, data.merge(ruflet_dash_attrs(attrs)))
end

#ruflet_navigation_rail(action: :root, extended: nil, label_type: nil, breakpoint: nil, payload: {}, **attrs, &block) ⇒ Object Also known as: ruflet_rail

Desktop/tablet navigation the native shell hides and re-renders as a native NavigationRail beside the WebView body.

<%= ruflet_navigation_rail extended: true do %>
<%= ruflet_rail_item "Home", root_path, icon: "home" %>
<%= ruflet_rail_item "Inbox", inbox_path, icon: "mail" %>
<% end %>


139
140
141
142
143
144
145
146
# File 'lib/ruflet/rails/view_helpers.rb', line 139

def ruflet_navigation_rail(action: :root, extended: nil, label_type: nil, breakpoint: nil, payload: {}, **attrs, &block)
  payload = ruflet_compact_hash(ruflet_stringify_keys(payload).merge(action: action.to_s, extended: extended,
                                                                     label_type: label_type, breakpoint: breakpoint))
  ruflet_build_tag("nav", ruflet_capture(&block), {
    "data-ruflet-rail" => ruflet_json(payload),
    "hidden" => true
  }.merge(ruflet_dash_attrs(attrs)))
end

#ruflet_rail_item(label, href, icon:, selected: false, nav: nil, payload: {}, **attrs) ⇒ Object

A destination inside ruflet_navigation_rail.



151
152
153
154
155
156
157
158
159
160
# File 'lib/ruflet/rails/view_helpers.rb', line 151

def ruflet_rail_item(label, href, icon:, selected: false, nav: nil, payload: {}, **attrs)
  item_payload = ruflet_stringify_keys(payload)
  data = {
    "href" => href,
    "data-ruflet-icon" => ruflet_json(ruflet_compact_hash(item_payload.merge(icon: icon, label: label,
                                                                             action: nav&.to_s)))
  }
  data["data-ruflet-selected"] = "true" if selected
  ruflet_build_tag("a", label, data.merge(ruflet_dash_attrs(attrs)))
end

Native platform services from ERB. These are normal HTML elements in a browser and native service calls inside Ruflet::Rails.native_app.



164
165
166
167
168
# File 'lib/ruflet/rails/view_helpers.rb', line 164

def ruflet_share_link(label, href = "#", text: nil, title: nil, subject: nil, url: nil, files: nil, **attrs)
  payload = ruflet_compact_hash(component: "share", text: text, title: title, subject: subject,
                                url: url || href, files: files)
  ruflet_action_tag("a", label, payload, { "href" => href }.merge(ruflet_dash_attrs(attrs)))
end