I'm currently doing a partial template for any table with search fields. I need to generate passed from controller function names with options as links in my template.
I have already tried solution I pasted as code below. It doesn't work and I have no clue why.
Controller:
#table_links [{:show, []}, {:edit, []}, {:delete, [method: :delete, data: [confirmation: "Are you sure?"]]}]
This module attribute is assigned to conn and passed to the template.
Rendering partial:
<%= render BaseAppWeb.SharedView, "table_partial.html",
Map.merge(assigns,
%{action: Routes.admin_users_path(#conn, :index),
opts: [method: :get],
columns: #searchable_columns,
table_links: #table_links,
links_path: &Routes.admin_users_path/3}) %>
Partial links generation:
<%= for {function, options} <- #table_links do %>
<%= case function do
:show -> {:safe, "<i class = \"mdi mdi-magnify\">"}
:edit -> {:safe, "<i class = \"mdi mdi-pencil\">"}
:delete -> {:safe, "<i class = \"mdi mdi mdi-trash-can-outline\">"}
_ -> {:safe, "<i class = \" mdi mdi-arrow-right-bold-circle-outline\">"}
end%>
<%= link(Atom.to_string(function) |> String.capitalize(), to: #links_path.(#conn, function, entity), options) %>
<% end %>
Error that is showing up:
lib/base_app_web/templates/shared/table_partial.html.eex:42: syntax error before: options
Without including options, as shown below, everything works fine, but I haven't got method: as link option, which is necessary in my case.
<%= link(Atom.to_string(function) |> String.capitalize(), to: #links_path.(#conn, function, entity)) %>
I'll really be grateful for any advice that will help me pass link options from controller to links in templates!
Edit:
Here is a table_partial.html.eex which you asked for:
<div class = "col-12">
<%= form_for #conn, #action, #opts, fn f -> %>
<%= Enum.reduce #columns, [], fn {function, {key, value}}, acc -> %>
<%= case function do
:date_input -> [acc] ++ [
build_form(f, {:label, {:from_date, "#{value} from"}}),
build_form(f, {function, {:from_date, value}}),
build_form(f, {:label, {:from_date, "#{value} to"}}),
build_form(f, {function, {:to_date, value}})]
_other -> [acc] ++ [build_form(f, {:label, {key, value}}), build_form(f, {function, {key, value}})]
end %>
<% end %>
<%= submit "Search", name: "order_by", value: "" %>
<hr>
<div class = "row">
<div class = "col-12">
<table class = "table dt-responsive nowrap talbe-borderless table-hover">
<thead class = "thead-light">
<tr>
<%= for {_function, {_key, value}} <- #searchable_columns do %>
<th><%= submit "#{value}", name: "order_by", value: "#{value}" %> </th>
<% end %>
<th>Options</th>
</tr>
</thead>
<% end %>
<tbody>
<%= for entity <- #entities do %>
<tr>
<%= for {_function, {key, _value}} <- #searchable_columns do %>
<td><%= Map.get(entity, key) %></td>
<% end %>
<td>
<%= for {function, options} <- #table_links do %>
<%= case function do
:show -> {:safe, "<i class = \"mdi mdi-magnify\">"}
:edit -> {:safe, "<i class = \"mdi mdi-pencil\">"}
:delete -> {:safe, "<i class = \"mdi mdi mdi-trash-can-outline\">"}
_ -> {:safe, "<i class = \" mdi mdi-arrow-right-bold-circle-outline\">"}
end%>
<%= link(Atom.to_string(function) |> String.capitalize(), to: #links_path.(#conn, function, entity), options) %>
<% end %>
</td>
</tr>
<% end %>
</tbody>
</table>
</div>
</div>
I can't build them normally, because I'm building multiple different tables with this template and different tables Options may vary.
Ok, if someone will look for something like this in the future - here is solution I just found to pass arguments:
Controller:
#table_links [{:show, %{:method => :get, :data => []}}, {:edit, %{:method => :get, :data => []}}, {:delete, %{:method => :delete, :data => [confirm: "Are you sure?"]}}]
table_partial.html.eex
<td>
<%= for {function, %{:method => method, :data => data}} <- #table_links do %>
<%= case function do
:show -> {:safe, "<i class = \"mdi mdi-magnify\">"}
:edit -> {:safe, "<i class = \"mdi mdi-pencil\">"}
:delete -> {:safe, "<i class = \"mdi mdi mdi-trash-can-outline\">"}
_ -> {:safe, "<i class = \" mdi mdi-arrow-right-bold-circle-outline\">"}
end%>
<%= link(Atom.to_string(function) |> String.capitalize(), to: #links_path.(#conn, function, entity), method: method, data: data) %>
<% end %>
</td>
That was pretty easy, however confirmation still does not work. It was because it is confirm:, not confirmation: - working good now :)
The issue is that you are trying to pass options into a keyword list. You will want to merge the lists in one of two ways. You can either do
Keyword.merge([to: #links_path.(#conn, function, entity)], options)
or
[to: #links_path.(#conn, function, entity)] ++ options
in
<%= link(Atom.to_string(function) |> String.capitalize(), to: #links_path.(#conn, function, entity), options) %>
Related
I am trying to create a form with a list of addresses and allowing the user to delete or create new addresses through Javascript. I’ve used Rails’ fields_for with a child_index: 'new_record' for a similar functionality where I replaced the new_record string with a timestamp when appending the form fields through Javascript (as explained by this Pluralsight tutorial: https://www.pluralsight.com/guides/ruby-on-rails-nested-attributes).
The trick I am looking for is to have similar rendering functionality in Phoenix is how to generate a nested form with a given / custom child index. Currently I am struggling with something like this;
<%= form_for #changeset, Routes.user_path(#conn, :update, #user.id), [data: [controller: "nested-form", action: "nested-form#submit"]], fn f -> %>
<div data-target="nested-form.records">
<%= inputs_for f, :addresses, fn fp -> %>
<%= render __MODULE__, "_address_fields.html", form: fp %>
<% end %>
</div>
<template data-target="nested-form.template">
<%# How do I get a template input group here? %>
<%# In Rails I could do `f.fields_for :addresses, Address.new, child_index: 'NEW_RECORD'` %>
</template>
<% end %>
But I can't figure out how to generate a inputs_for with Phoenix to create such template.
As #TheAnh commented, the answer is discussed in this gist (https://gist.github.com/mjrode/c2939ee7786b157aab131761c8fb89a9). To provide an answer, I've solved it with the following helper method;
defmodule MyAppWeb.UserView do
use MyAppWeb, :view
def template_inputs_for(changeset, field, fun) do
form = Phoenix.HTML.FormData.to_form(changeset, [])
inputs_for(form, field, fn form ->
id = String.replace(form.id, ~r/\d$/, "NEW_RECORD")
name = String.replace(form.name, ~r/\[\d\]$/, "[NEW_RECORD]")
fun.(%{form | id: id, name: name})
end)
end
end
Which can then be used to generate a "template" field group;
<%= form_for #changeset, Routes.user_path(#conn, :update, #user.id), [data: [controller: "nested-form", action: "nested-form#submit"]], fn f -> %>
<div data-target="nested-form.records">
<%= inputs_for f, :addresses, fn fp -> %>
<%= render __MODULE__, "_address_fields.html", form: fp %>
<% end %>
</div>
<template data-target="nested-form.template">
<%= template_inputs_for User.changeset(%User{addresses: [%Address{}]}), :addresses, fn f -> %>
<%= render __MODULE__, "_address_fields.html", form: f %>
<% end %>
</template>
<% end %>
I have a problem. I have made some filters using ransack and they work fine, but I have no idea how to update the pie chart that I have implemented under the table that I filter. The chart is made using Chartkick. I am fairly new to ror and programing in general. Here is my index file:
<h1>Lista Przychodów</h1>
<h2><%= link_to 'Nowy Przychód', new_income_path %></h2>
<%= search_form_for #q do |f| %>
<div class="field">
<%= f.label :title_cont, "Tytuł zawiera" %>
<%= f.search_field :title_cont %>
</div>
<div class="field">
<%= f.label :text_cont, "Opis zawiera" %>
<%= f.search_field :text_cont %>
</div>
<div class="field">
<%= f.label :category_name_cont, "Kategoria zawiera" %>
<%= f.search_field :category_name_cont %>
</div>
<div class="field">
<%= f.label :amount_gteq, "Kwota pomiędzy" %>
<%= f.search_field :amount_gteq %>
<%= f.label :amount_lteq, "a" %>
<%= f.search_field :amount_lteq %>
</div>
<%= f.submit "Szukaj" %>
<div class="button">
<%= link_to 'Wyczyść filtry', request.path, class:"cancel-button" %>
</div>
<% end %>
<div style="overflow-x:auto;">
<table class="center">
<tr>
<th><%= sort_link(#q, :title, 'Tytuł', default_order: :desc) %></th>
<th><%= sort_link(#q, :text, 'Opis') %></th>
<th><%= sort_link(#q, :amount, 'Kwota') %></th>
<th><%= sort_link(#q, :category_id, 'Kategoria') %></th>
<th colspan="3"></th>
</tr>
<% #incomes.each do |income| %>
<tr>
<td><%= income.title %></td>
<td><%= income.text %></td>
<td><%= number_to_currency(income.amount, :unit => "zł", :format => "%n %u") %></td>
<td><%= Category.find(income.category_id).name %></td>
<td><%= link_to 'Pokaż', income_path(income) %></td>
<td><%= link_to 'Edytuj', edit_income_path(income) %></td>
<td><%= link_to 'Usuń', income_path(income),
method: :delete,
data: { confirm: 'Czy na pewno chcesz usunąć ten wpis?' } %></td>
</tr>
<% end %>
</table>
</div>
<%= pie_chart Income.joins(:category).group(:name).sum(:amount),suffix: " zł", thousands: ",", messages: {empty: "Nie ma wpisów"}, download: {filename: "wykres"}, title: "Podział przychodów na kategorie" %>
<footer><%= link_to 'Strona główna', controller: 'welcome' %></footer>
And my controller file if it is needed:
class IncomesController < ApplicationController
helper_method :sort_column, :sort_direction
http_basic_authenticate_with name: "admin", password: "admin", except: [:index, :show]
def index
#q = Income.search(params[:q])
#incomes = #q.result.includes(:category)
end
def show
#income = Income.find(params[:id])
end
def new
#income = Income.new
#categories = Category.all.map { |c| [c.name, c.id]}
end
def edit
#income = Income.find(params[:id])
#categories = Category.all.map { |c| [c.name, c.id]}
end
def create
#income = Income.new(income_params)
#categories = Category.all.map { |c| [c.name, c.id]}
if #income.save
redirect_to #income
else
render 'new'
end
end
def update
#income = Income.find(params[:id])
if #income.update(income_params)
redirect_to #income
else
render 'edit'
end
end
def destroy
#income = Income.find(params[:id])
#income.destroy
redirect_to incomes_path
end
private
def income_params
params.require(:income).permit(:title, :text, :amount, :category_id)
end
def sort_column
params[:sort] || "title"
end
def sort_direction
params[:direction] || "asc"
end
end
I already looked on the internet for a similar problem but didn't find anything. I would appreciate some help with that or at lest some directions what to do. I got really hooked on ror and would like to learn more about it, that's why I came here for help. Thanks in advance for answers.
If you mean the filters are applied on the table but not on the chart, this is because you are making another query for the chart only, in this line:
<%= pie_chart Income.joins(:category).group(:name).sum(:amount),suffix: " zł", thousands: ",", messages: {empty: "Nie ma wpisów"}, download: {filename: "wykres"}, title: "Podział przychodów na kategorie" %>
You should use the same instance variable that has the filtered data:
#incomes
So it should be something like:
<%= pie_chart #incomes.sum(:amount), suffix: " zł", thousands: ",", messages: { empty: "Nie ma wpisów" }, download: { filename: "wykres" }, title: "Podział przychodów na kategorie" %>
I want, that a user with the admin role can edit other users from users/index.html.erb view. I'm not the first one asking this question, but all the given answers lack clear instructions.
So the problem is; after editing the fields, when I click on the 'Updtate' button in users/index.html.erb view, the page is reloaded but no changes appens.
Aditionnal infos: I'm using Devise and Omniauth facebook.
The users are display in the users/index via a partial:
<td>
<div class="field">
<%= f.text_field :name, class: 'form-control' %>
</div>
</td>
<td>
<div class="field">
<%= f.text_field :email, class: 'form-control' %>
</div>
</td>
<td>
<div class="actions">
<%= f.submit I18n.translate('control.update'), class: 'btn sign-up-button' %>
</div>
</td>
<td>
<!-- display a delete button if the user is not the current_user -->
<%= link_to(I18n.translate('control.delete'), user_path(user), :data => { :confirm => "Are you sure?" }, :method => :delete, :class => 'btn') unless user == current_user %>
</td>
users/index.html.erb:
<div class="container">
<div class="row">
<div class="col-sm-6 col-sm-offset-3">
<h2 class="text-center"><%= I18n.translate('user.users_list') %></h2>
<div class="column">
<table class="table">
<tbody>
<% #users.each do |user| %>
<tr>
<%= render user %>
</tr>
<% end %>
</tbody>
</table>
</div>
<ul class=”pagination”>
<li><%= will_paginate(#users) %></li>
</ul>
</div>
</div>
</div>
My user_controller.rb
class UsersController < ApplicationController
before_action :ensure_admin, :except => :show
def index
#users = User.paginate(:page => params[:page], :per_page => 8)
end
def show
#user = User.find_by_name(params[:id])
end
def edit
#user = User.find(params[:id])
end
def update
#user = User.find(params[:id])
if #user.update(secure_params)
redirect_to users_path, :notice => "User updated."
else
redirect_to users_path, :alert => "Unable to update user."
end
end
def destroy
user = User.find(params[:id])
user.destroy
redirect_to users_path, :notice => "User deleted."
end
private
def ensure_admin
if(current_user.role == 'admin')
return
end
redirect_to root_path, :alert => "Access denied."
end
def secure_params
params.require(:user).permit(:role)
end
def user_params
params.require(:user).permit(:email, :name, :password)
end
end
My routes:
Rails.application.routes.draw do
root to: 'pages#index'
get 'users/index'
# you can type '/users' to view the users
match '/users', to: 'users#index', via: 'get'
devise_for :users, :controllers => { :registrations => "registrations",
:path_prefix => 'd',
:omniauth_callbacks => "users/omniauth_callbacks" }
resources :users
scope '/:locale' do
devise_scope :user do
get 'login', to: 'devise/sessions#new'
end
devise_scope :user do
get 'signup', to: 'devise/registrations#new'
end
end
Here in the user_controller update action you are calling secure_params def, which is permitting only "role" field.
I think you should to use user_params in update action like below
def update
#user = User.find(params[:id])
if #user.update(user_params)
redirect_to users_path, :notice => "User updated."
else
redirect_to users_path, :alert => "Unable to update user."
end
end
Or you will have to permit other params also in secure_params def like
def secure_params
params.require(:user).permit(:role,:name,:email)
end
Hope it would help you.
I'm implementing a voting system where when the course is already upvoted/downvoted, clicking downvote/upvote will switch to that vote respectively and clicking again will delete that vote (similar to Reddit).
While voting and destroying vote perform correctly, I'm having trouble trying to update the vote with form_for and patch method, where the value of the vote_type returns nil
My Votes_Controller
class VotesController < ApplicationController
def create
#vote = Vote.new(secure_params)
#vote.course = Course.find(params[:course_id])
if #vote.save
respond_to do |format|
format.html { redirect_to :courses }
format.js
end
end
def update
vote = Vote.find_by(user: current_user)
vote.update_attribute(:vote_type, update_vote_params)
redirect_to :courses
end
def destroy
vote = Vote.find_by(user: current_user)
vote.destroy
redirect_to :courses, :notice => 'Unvoted!'
end
private
def secure_params
params.require(:vote).permit( :user_id, :vote_type )
end
def update_vote_params
params.require(:vote).permit(:vote_type)
end
end
My index.html.erb
<% if Vote.exists?(user: current_user) && Vote.find_by(user: current_user).vote_type.equal?(-1) %>
<%= form_for course.votes.build, url: course_vote_path(course, Vote.find_by(user: current_user).id), method: :patch do |f| %>
<%= f.hidden_field :vote_type, value: 1 %>
<%= f.submit 'Upvote', class: 'btn btn-default' %>
<% end %>
<% elsif Vote.exists?(user: current_user) && Vote.find_by(user: current_user).vote_type.equal?(1) %>
<%= form_for course.votes.build, url: course_vote_path(course, Vote.find_by(user: current_user).id), method: :delete do |f| %>
<%= f.submit "Upvote", class: 'btn btn-default' %>
<% end %>
<% else %>
<%= form_for course.votes.build, url: course_votes_path(course) do |f| %>
<%= f.hidden_field :user_id, value: current_user.id %>
<%= f.hidden_field :vote_type, value: 1 %>
<%= f.submit 'Upvote', class: 'btn btn-default' %>
<% end %>
<% end %>
My routes.rb
Rails.application.routes.draw do
get 'vote/create'
get 'vote/destroy'
get 'courses/new'
get 'users/new'
resources :courses do
resources :votes, only: [:create, :destroy, :update]
end
resources :users
end
I expect the param to pass 1 for vote_type, however, the actual output is nil
I have Some error in Room section at index method please see error is
NoMethodError in Rooms#index
Showing /home/kingdark/Repos/glowfish/app/views/rooms/_room.html.erb where line #2 raised:
undefined method `model_name' for NilClass:Class
Extracted source (around line #2):
1: <h1> Rooms List <span class='pull-right'> <%= link_to "New Room", new_room_path, class: 'btn btn-primary' %> </span> </h1>
2: <%= content_tag_for :div , room, class: 'row-fluid' do %>
3: <div id="r-index_content">
4: <li>
5: <span class='span9' >
Trace of template inclusion: app/views/rooms/index.html.erb
Rails.root: /home/kingdark/Repos/glowfish Application Trace | Framework Trace | Full Trace
app/views/rooms/_room.html.erb:2:in `_app_views_rooms__room_html_erb__3875627950727331108_69928712927400' app/views/rooms/index.html.erb:1:in `_app_views_rooms_index_html_erb__2888541332730611846_47144000'
and this code is:
in rooms_controller.rb
class RoomsController < ApplicationController
respond_to :js, :html, :json
def index
#rooms = Room.all
end
in model "room.rb"
class Room < ActiveRecord::Base
attr_accessible :capacity, :description, :is_active, :room_name
belongs_to :roometable, polymorphic: true
has_many :events
end
in views>rooms>index.html.erb
<h1> Rooms List <span class='pull-right'> <%= link_to "New Room", new_room_path, class: 'btn btn-primary' %> </span> </h1>
<%= content_tag_for :div , room, class: 'row-fluid' do %>
<div id="r-index_content">
<li>
<span class='span9' >
<%= link_to room.room_name, room ,{:style => 'color: #000000'} %>
</span>
<span class='span3'>
<span class='pull-right'>
<%= link_to "Edit", edit_room_path(room), class: 'btn btn-mini' %>
<%= link_to "Delete", room_path(room), method: :delete, data: { confirm: "Are you sure to delete this room?" }, class: 'btn btn-mini btn-danger' %>
</span>
</span>
</li>
</div>
<% end %>
in routes.rb
Glowfish::Application.routes.draw do
match '/calendar(/:year(/:month))' => 'calendar#index', :as => :calendar, :constraints => {:year => /\d{4}/, :month => /\d{1,2}/}
devise_for :users
resources :pages
resources :areas
resources :rooms
match '/:id' => 'high_voltage/calendar#index', :as => :static, :via => :get
root :to => 'high_voltage/calendar#index', :id => 'index'
end
I try to fix this but nothing happen something is wrong.
help me please and thank for you time to see this.
looks like your room is nil inside this code:
content_tag_for :div , room, class: 'row-fluid'
Have not you forget to add iterator?
<h1> Rooms List <span class='pull-right'> <%= link_to "New Room", new_room_path, class: 'btn btn-primary' %> </span> </h1>
<% #rooms.each do |room| %>
<%= content_tag_for :div , room, class: 'row-fluid' do %>
...
<% end %>