PageRenderTime 56ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 1ms

/app/helpers/application_helper.rb

https://github.com/steventen/fat_free_crm
Ruby | 508 lines | 373 code | 59 blank | 76 comment | 44 complexity | fb799331eb52be58e483ea28b7832dcd MD5 | raw file
  1. # Copyright (c) 2008-2013 Michael Dvorkin and contributors.
  2. #
  3. # Fat Free CRM is freely distributable under the terms of MIT license.
  4. # See MIT-LICENSE file or http://www.opensource.org/licenses/mit-license.php
  5. #------------------------------------------------------------------------------
  6. module ApplicationHelper
  7. def tabs(tabs = nil)
  8. tabs ||= controller_path =~ /admin/ ? FatFreeCRM::Tabs.admin : FatFreeCRM::Tabs.main
  9. if tabs
  10. @current_tab ||= tabs.first[:text] # Select first tab by default.
  11. tabs.each { |tab| tab[:active] = (@current_tab == tab[:text] || @current_tab == tab[:url][:controller]) }
  12. else
  13. raise FatFreeCRM::MissingSettings, "Tab settings are missing, please run <b>rake ffcrm:setup</b> command."
  14. end
  15. end
  16. #----------------------------------------------------------------------------
  17. def tabless_layout?
  18. %w(authentications passwords).include?(controller.controller_name) ||
  19. ((controller.controller_name == "users") && (%w(create new).include?(controller.action_name)))
  20. end
  21. # Show existing flash or embed hidden paragraph ready for flash[:notice]
  22. #----------------------------------------------------------------------------
  23. def show_flash(options = { :sticky => false })
  24. [:error, :warning, :info, :notice].each do |type|
  25. if flash[type]
  26. html = content_tag(:div, h(flash[type]), :id => "flash")
  27. flash[type] = nil
  28. return html << content_tag(:script, "crm.flash('#{type}', #{options[:sticky]})".html_safe, :type => "text/javascript")
  29. end
  30. end
  31. content_tag(:p, nil, :id => "flash", :style => "display:none;")
  32. end
  33. #----------------------------------------------------------------------------
  34. def subtitle(id, hidden = true, text = id.to_s.split("_").last.capitalize)
  35. content_tag("div",
  36. link_to("<small>#{ hidden ? "&#9658;" : "&#9660;" }</small> #{text}".html_safe,
  37. url_for(:controller => :home, :action => :toggle, :id => id),
  38. :remote => true,
  39. :onclick => "crm.flip_subtitle(this)"
  40. ), :class => "subtitle")
  41. end
  42. #----------------------------------------------------------------------------
  43. def section(related, assets)
  44. asset = assets.to_s.singularize
  45. create_id = "create_#{asset}"
  46. select_id = "select_#{asset}"
  47. create_url = controller.send(:"new_#{asset}_path")
  48. html = tag(:br)
  49. html << content_tag(:div, link_to(t(select_id), "#", :id => select_id), :class => "subtitle_tools")
  50. html << content_tag(:div, "&nbsp;|&nbsp;".html_safe, :class => "subtitle_tools")
  51. html << content_tag(:div, link_to_inline(create_id, create_url, :related => dom_id(related), :text => t(create_id)), :class => "subtitle_tools")
  52. html << content_tag(:div, t(assets), :class => :subtitle, :id => "create_#{asset}_title")
  53. html << content_tag(:div, "", :class => :remote, :id => create_id, :style => "display:none;")
  54. end
  55. #----------------------------------------------------------------------------
  56. def load_select_popups_for(related, *assets)
  57. js = generate_js_for_popups(related, *assets)
  58. content_for(:javascript_epilogue) do
  59. raw "$(function() { #{js} });"
  60. end
  61. end
  62. def generate_js_for_popups(related, *assets)
  63. assets.map do |asset|
  64. render(:partial => "shared/select_popup", :locals => { :related => related, :popup => asset })
  65. end.join
  66. end
  67. # We need this because standard Rails [select] turns &#9733; into &amp;#9733;
  68. #----------------------------------------------------------------------------
  69. def rating_select(name, options = {})
  70. stars = Hash[ (1..5).map { |star| [ star, "&#9733;" * star ] } ].sort
  71. options_for_select = %Q(<option value="0"#{options[:selected].to_i == 0 ? ' selected="selected"' : ''}>#{t :select_none}</option>)
  72. options_for_select << stars.map { |star| %(<option value="#{star.first}"#{options[:selected] == star.first ? ' selected="selected"' : ''}>#{star.last}</option>) }.join
  73. select_tag name, options_for_select.html_safe, options
  74. end
  75. #----------------------------------------------------------------------------
  76. def link_to_inline(id, url, options = {})
  77. text = options[:text] || t(id, :default => id.to_s.titleize)
  78. text = (arrow_for(id) + text) unless options[:plain]
  79. related = (options[:related] ? "&related=#{options[:related]}" : '')
  80. link_to(text,
  81. url + "#{url.include?('?') ? '&' : '?'}cancel=false" + related,
  82. :remote => true,
  83. :onclick => "this.href = this.href.replace(/cancel=(true|false)/,'cancel='+ ($('##{id}').css('display') != 'none'));",
  84. :class => options[:class]
  85. )
  86. end
  87. #----------------------------------------------------------------------------
  88. def arrow_for(id)
  89. content_tag(:span, "&#9658;".html_safe, :id => "#{id}_arrow", :class => :arrow)
  90. end
  91. #----------------------------------------------------------------------------
  92. def link_to_edit(record, options = {})
  93. object = record.is_a?(Array) ? record.last : record
  94. name = (params[:klass_name] || object.class.name).underscore.downcase
  95. link_to(t(:edit),
  96. options[:url] || polymorphic_url(record, :action => :edit),
  97. :remote => true,
  98. :onclick => "this.href = this.href.split('?')[0] + '?previous='+crm.find_form('edit_#{name}');".html_safe
  99. )
  100. end
  101. #----------------------------------------------------------------------------
  102. def link_to_delete(record, options = {})
  103. object = record.is_a?(Array) ? record.last : record
  104. confirm = options[:confirm] || nil
  105. link_to(t(:delete) + "!",
  106. options[:url] || url_for(record),
  107. :method => :delete,
  108. :remote => true,
  109. :confirm => confirm
  110. )
  111. end
  112. #----------------------------------------------------------------------------
  113. def link_to_discard(object)
  114. current_url = (request.xhr? ? request.referer : request.fullpath)
  115. parent, parent_id = current_url.scan(%r|/(\w+)/(\d+)|).flatten
  116. link_to(t(:discard),
  117. url_for(:controller => parent, :action => :discard, :id => parent_id, :attachment => object.class.name, :attachment_id => object.id),
  118. :method => :post,
  119. :remote => true
  120. )
  121. end
  122. #----------------------------------------------------------------------------
  123. def link_to_cancel(url, params = {})
  124. url = params[:url] if params[:url]
  125. link_to(t(:cancel),
  126. url + "#{url.include?('?') ? '&' : '?'}cancel=true",
  127. :remote => true
  128. )
  129. end
  130. #----------------------------------------------------------------------------
  131. def link_to_close(url)
  132. link_to("x", url + "#{url.include?('?') ? '&' : '?'}cancel=true",
  133. :remote => true,
  134. :class => "close",
  135. :title => t(:close_form)
  136. )
  137. end
  138. # Bcc: to dropbox address if the dropbox has been set up.
  139. #----------------------------------------------------------------------------
  140. def link_to_email(email, length = nil, &block)
  141. name = (length ? truncate(email, :length => length) : email)
  142. if Setting.email_dropbox && Setting.email_dropbox[:address].present?
  143. mailto = "#{email}?bcc=#{Setting.email_dropbox[:address]}"
  144. else
  145. mailto = email
  146. end
  147. if block_given?
  148. link_to("mailto:#{mailto}", :title => email) do
  149. yield
  150. end
  151. else
  152. link_to(h(name), "mailto:#{mailto}", :title => email)
  153. end
  154. end
  155. #----------------------------------------------------------------------------
  156. def jumpbox(current)
  157. tabs = [ :campaigns, :accounts, :leads, :contacts, :opportunities ]
  158. current = tabs.first unless tabs.include?(current)
  159. tabs.map do |tab|
  160. link_to_function(t("tab_#{tab}"), "crm.jumper('#{tab}')", "html-data" => tab, :class => (tab == current ? 'selected' : ''))
  161. end.join(" | ").html_safe
  162. end
  163. #----------------------------------------------------------------------------
  164. def styles_for(*models)
  165. render :partial => "shared/inline_styles", :locals => { :models => models }
  166. end
  167. #----------------------------------------------------------------------------
  168. def hidden; { :style => "display:none;" }; end
  169. def exposed; { :style => "display:block;" }; end
  170. def invisible; { :style => "visibility:hidden;" }; end
  171. def visible; { :style => "visibility:visible;" }; end
  172. #----------------------------------------------------------------------------
  173. def one_submit_only(form='')
  174. { :onsubmit => "$('#'+this.id+' input[type=submit]').prop('disabled', true)".html_safe }
  175. end
  176. #----------------------------------------------------------------------------
  177. def hidden_if(you_ask)
  178. you_ask ? hidden : exposed
  179. end
  180. #----------------------------------------------------------------------------
  181. def invisible_if(you_ask)
  182. you_ask ? invisible : visible
  183. end
  184. #----------------------------------------------------------------------------
  185. def confirm_delete(model, params = {})
  186. question = %(<span class="warn">#{t(:confirm_delete, model.class.to_s.downcase)}</span>).html_safe
  187. yes = link_to(t(:yes_button), params[:url] || model, :method => :delete)
  188. no = link_to_function(t(:no_button), "$('#menu').html($('#confirm').html());")
  189. text = "$('#confirm').html( $('#menu').html() );\n"
  190. text << "$('#menu').html('#{question} #{yes} : #{no}');"
  191. text.html_safe
  192. end
  193. #----------------------------------------------------------------------------
  194. def spacer(width = 10)
  195. image_tag "1x1.gif", :width => width, :height => 1, :alt => nil
  196. end
  197. # Reresh sidebar using the action view within the current controller.
  198. #----------------------------------------------------------------------------
  199. def refresh_sidebar(action = nil, shake = nil)
  200. refresh_sidebar_for(controller.controller_name, action, shake)
  201. end
  202. # Refresh sidebar using the action view within an arbitrary controller.
  203. #----------------------------------------------------------------------------
  204. def refresh_sidebar_for(view, action = nil, shake = nil)
  205. text = ""
  206. text << "$('#sidebar').html('#{ j render(:partial => "layouts/sidebar", :locals => { :view => view, :action => action }) }');"
  207. text << "$('##{j shake.to_s}').effect('shake', { duration:200, distance: 3 });" if shake
  208. text.html_safe
  209. end
  210. # Display web presence mini-icons for Contact or Lead.
  211. #----------------------------------------------------------------------------
  212. def web_presence_icons(person)
  213. [ :blog, :linkedin, :facebook, :twitter, :skype ].map do |site|
  214. url = person.send(site)
  215. unless url.blank?
  216. if site == :skype then
  217. url = "callto:" << url
  218. else
  219. url = "http://" << url unless url.match(/^https?:\/\//)
  220. end
  221. link_to(image_tag("#{site}.gif", :size => "15x15"), url, :"data-popup" => true, :title => t(:open_in_window, url))
  222. end
  223. end.compact.join("\n").html_safe
  224. end
  225. # Ajax helper to refresh current index page once the user selects an option.
  226. #----------------------------------------------------------------------------
  227. def redraw(option, value, url = send("redraw_#{controller.controller_name}_path"))
  228. if value.is_a?(Array)
  229. param, value = value.first, value.last
  230. end
  231. %Q{
  232. if ($('##{option}').html() != '#{value}') {
  233. $('##{option}').html('#{value}');
  234. $('#loading').show();
  235. $.post('#{url}', {#{option}: '#{param || value}'}, function () {
  236. $('#loading').hide();
  237. });
  238. }
  239. }
  240. end
  241. #----------------------------------------------------------------------------
  242. def options_menu_item(option, key, url = send("redraw_#{controller.controller_name}_path"))
  243. name = t("option_#{key}")
  244. "{ name: \"#{name.titleize}\", on_select: function() {" +
  245. %Q{
  246. if ($('##{option}').html() != '#{name}') {
  247. $('##{option}').html('#{name}');
  248. $('#loading').show();
  249. $.get('#{url}', {#{option}: '#{key}', query: $('#query').val()}, function () {
  250. $('#loading').hide();
  251. });
  252. }
  253. } + "}}"
  254. end
  255. # Ajax helper to pass browser timezone offset to the server.
  256. #----------------------------------------------------------------------------
  257. def get_browser_timezone_offset
  258. unless session[:timezone_offset]
  259. "$.get('#{timezone_path}', {offset: (new Date()).getTimezoneOffset()});"
  260. end
  261. end
  262. # Entities can have associated avatars or gravatars. Only calls Gravatar
  263. # in production env. Gravatar won't serve default images if they are not
  264. # publically available: http://en.gravatar.com/site/implement/images
  265. #----------------------------------------------------------------------------
  266. def avatar_for(model, args = {})
  267. args = { :class => 'gravatar', :size => :large }.merge(args)
  268. if model.respond_to?(:avatar) and model.avatar.present?
  269. image_tag(model.avatar.image.url(args[:size]), args)
  270. else
  271. args = Avatar.size_from_style!(args) # convert size format :large => '75x75'
  272. gravatar_image_tag(model.email, args)
  273. end
  274. end
  275. # Returns default permissions intro.
  276. #----------------------------------------------------------------------------
  277. def get_default_permissions_intro(access, text)
  278. case access
  279. when "Private" then t(:permissions_intro_private, text)
  280. when "Public" then t(:permissions_intro_public, text)
  281. when "Shared" then t(:permissions_intro_shared, text)
  282. end
  283. end
  284. # Render a text field that is part of compound address.
  285. #----------------------------------------------------------------------------
  286. def address_field(form, object, attribute, extra_styles)
  287. hint = "#{t(attribute)}..."
  288. if object.send(attribute).blank?
  289. form.text_field(attribute,
  290. :style => "margin-top: 6px; #{extra_styles}",
  291. :placeholder => hint
  292. )
  293. else
  294. form.text_field(attribute,
  295. :style => "margin-top: 6px; #{extra_styles}",
  296. :placeholder => hint
  297. )
  298. end
  299. end
  300. # Return true if:
  301. # - it's an Ajax request made from the asset landing page (i.e. create opportunity
  302. # from a contact landing page) OR
  303. # - we're actually showing asset landing page.
  304. #----------------------------------------------------------------------------
  305. def shown_on_landing_page?
  306. !!((request.xhr? && request.referer =~ %r|/\w+/\d+|) ||
  307. (!request.xhr? && request.fullpath =~ %r|/\w+/\d+|))
  308. end
  309. # Helper to display links to supported data export formats.
  310. #----------------------------------------------------------------------------
  311. def links_to_export(action=:index)
  312. token = current_user.single_access_token
  313. url_params = {:action => action}
  314. url_params.merge!(:id => params[:id]) unless params[:id].blank?
  315. url_params.merge!(:query => params[:query]) unless params[:query].blank?
  316. url_params.merge!(:q => params[:q]) unless params[:q].blank?
  317. url_params.merge!(:view => @view) unless @view.blank? # tasks
  318. url_params.merge!(:id => params[:id]) unless params[:id].blank?
  319. exports = %w(xls csv).map do |format|
  320. link_to(format.upcase, url_params.merge(:format => format), :title => I18n.t(:"to_#{format}")) unless action.to_s == "show"
  321. end
  322. feeds = %w(rss atom).map do |format|
  323. link_to(format.upcase, url_params.merge(:format => format, :authentication_credentials => token), :title => I18n.t(:"to_#{format}"))
  324. end
  325. links = %W(perm).map do |format|
  326. link_to(format.upcase, url_params, :title => I18n.t(:"to_#{format}"))
  327. end
  328. (exports + feeds + links).compact.join(' | ')
  329. end
  330. def user_options
  331. User.all.map {|u| [u.full_name, u.id]}
  332. end
  333. def group_options
  334. Group.all.map {|g| [g.name, g.id]}
  335. end
  336. def list_of_entities
  337. ENTITIES
  338. end
  339. def entity_filter_checkbox(name, value, count)
  340. checked = (session["#{controller_name}_filter"].present? ? session["#{controller_name}_filter"].split(",").include?(value.to_s) : count.to_i > 0)
  341. url = url_for(:action => :filter)
  342. onclick = %Q{
  343. var query = $('#query').val(),
  344. values = [];
  345. $('input[name=&quot;#{name}[]&quot;]').filter(':checked').each(function () {
  346. values.push(this.value);
  347. });
  348. $('#loading').show();
  349. $.post('#{url}', {#{name}: values.join(','), query: query}, function () {
  350. $('#loading').hide();
  351. });
  352. }.html_safe
  353. check_box_tag("#{name}[]", value, checked, :id => value, :onclick => onclick)
  354. end
  355. # Create a column in the 'asset_attributes' table.
  356. #----------------------------------------------------------------------------
  357. def col(title, value, last = false, email = false)
  358. # Parse and format urls as links.
  359. fmt_value = (value.to_s || "").gsub("\n", "<br />")
  360. fmt_value = if email
  361. link_to_email(fmt_value)
  362. else
  363. fmt_value.gsub(/((http|ftp|https):\/\/[\w\-_]+(\.[\w\-_]+)+([\w\-\.,@?^=%&amp;:\/\+#]*[\w\-\@?^=%&amp;\/\+#])?)/, "<a href=\"\\1\">\\1</a>")
  364. end
  365. %Q^<th#{last ? " class=\"last\"" : ""}>#{title}:</th>
  366. <td#{last ? " class=\"last\"" : ""}>#{fmt_value}</td>^.html_safe
  367. end
  368. #----------------------------------------------------------------------------
  369. # Combines the 'subtitle' helper with the small info text on the same line.
  370. def section_title(id, hidden = true, text = nil, info_text = nil)
  371. text = id.to_s.split("_").last.capitalize if text == nil
  372. content_tag("div", :class => "subtitle show_attributes") do
  373. content = link_to("<small>#{ hidden ? "&#9658;" : "&#9660;" }</small> #{text}".html_safe,
  374. url_for(:controller => :home, :action => :toggle, :id => id),
  375. :remote => true,
  376. :onclick => "crm.flip_subtitle(this)"
  377. )
  378. content << content_tag("small", info_text.to_s, {:class => "subtitle_inline_info", :id => "#{id}_intro", :style => hidden ? "" : "display:none;"})
  379. end
  380. end
  381. #----------------------------------------------------------------------------
  382. # Return name of current view
  383. def current_view_name
  384. controller = params['controller']
  385. action = (params['action'] == 'show') ? 'show' : 'index' # create update redraw filter index actions all use index view
  386. current_user.pref[:"#{controller}_#{action}_view"]
  387. end
  388. #----------------------------------------------------------------------------
  389. # Get template in current context with current view name
  390. def template_for_current_view
  391. controller = params['controller']
  392. action = (params['action'] == 'show') ? 'show' : 'index' # create update redraw filter index actions all use index view
  393. template = FatFreeCRM::ViewFactory.template_for_current_view(:controller => controller, :action => action, :name => current_view_name)
  394. template
  395. end
  396. #----------------------------------------------------------------------------
  397. # Generate buttons for available views given the current context
  398. def view_buttons
  399. controller = params['controller']
  400. action = (params['action'] == 'show') ? 'show' : 'index' # create update redraw filter index actions all use index view
  401. views = FatFreeCRM::ViewFactory.views_for(:controller => controller, :action => action)
  402. return nil unless views.size > 1
  403. content_tag :ul, :class => 'format-buttons' do
  404. views.collect do |view|
  405. classes = if (current_view_name == view.name) or (current_view_name == nil and view.template == nil) # nil indicates default template.
  406. "#{view.name}-button active"
  407. else
  408. "#{view.name}-button"
  409. end
  410. content_tag(:li) do
  411. url = (action == "index") ? send("redraw_#{controller}_path") : send("#{controller.singularize}_path")
  412. link_to('#', :title => t(view.name, :default => view.title), :"data-view" => view.name, :"data-url" => url, :"data-context" => action, :class => classes) do
  413. icon = view.icon || 'fa-bars'
  414. content_tag(:i, nil, class: "fa #{icon}")
  415. end
  416. end
  417. end.join('').html_safe
  418. end
  419. end
  420. #----------------------------------------------------------------------------
  421. # Generate the html for $.timeago function
  422. # <span class="timeago" datetime="2008-07-17T09:24:17Z">July 17, 2008</span>
  423. def timeago(time, options = {})
  424. options[:class] ||= "timeago"
  425. content_tag(:span, time.to_s, options.merge( title: time.getutc.iso8601)) if time
  426. end
  427. #----------------------------------------------------------------------------
  428. # Translate List name to FontAwesome icon text
  429. def get_icon(name)
  430. case name
  431. when "tasks" then "fa-check-square-o"
  432. when "campaigns" then "fa-bar-chart-o"
  433. when "leads" then "fa-tasks"
  434. when "accounts" then "fa-users"
  435. when "contacts" then "fa-user"
  436. when "opportunities" then "fa-money"
  437. when "team" then "fa-globe"
  438. end
  439. end
  440. #----------------------------------------------------------------------------
  441. # Ajaxification FTW!
  442. # e.g. collection = Opportunity.my.scope
  443. # options = { renderer: {...} , params: {...}
  444. def paginate(options = {})
  445. collection = options.delete(:collection)
  446. options = { renderer: RemoteLinkPaginationHelper::LinkRenderer }.merge(options)
  447. will_paginate(collection, options)
  448. end
  449. end