class Gridify::Grid

Constants

OPS
OPS_PATTERN

[‘eq’,‘ne’,‘lt’,‘le’,‘gt’,‘ge’,

'bw','bn','in','ni','ew','en','cn','nc']

[‘equal’,‘not equal’, ‘less’, ‘less or equal’,‘greater’,‘greater or equal’,

'begins with','does not begin with','is in','is not in','ends with','does not end with','contains','does not contain']
STRING_OPS

Attributes

add_button[RW]

non persistent options:

:build_model
:only
:except
alt_rows[RW]

non persistent options:

:build_model
:only
:except
arranger[RW]

non persistent options:

:build_model
:only
:except
case_sensitive[RW]

non persistent options:

:build_model
:only
:except
collapsed[RW]

non persistent options:

:build_model
:only
:except
collapsible[RW]

non persistent options:

:build_model
:only
:except
columns[RW]

non persistent options:

:build_model
:only
:except
current_page[RW]

non persistent options:

:build_model
:only
:except
data_format[RW]

non persistent options:

:build_model
:only
:except
data_type[RW]

non persistent options:

:build_model
:only
:except
delete_button[RW]

non persistent options:

:build_model
:only
:except
dom_id[RW]

non persistent options:

:build_model
:only
:except
edit_button[RW]

non persistent options:

:build_model
:only
:except
editable[RW]

non persistent options:

:build_model
:only
:except
error_container[RW]

non persistent options:

:build_model
:only
:except
error_handler[RW]

non persistent options:

:build_model
:only
:except
finder[RW]

non persistent options:

:build_model
:only
:except
height[RW]

non persistent options:

:build_model
:only
:except
jqgrid_nav_options[RW]

non persistent options:

:build_model
:only
:except
jqgrid_options[RW]

non persistent options:

:build_model
:only
:except
load_once[RW]

non persistent options:

:build_model
:only
:except
name[RW]

non persistent options:

:build_model
:only
:except
pager[RW]

non persistent options:

:build_model
:only
:except
paging_choices[RW]

non persistent options:

:build_model
:only
:except
paging_controls[RW]

non persistent options:

:build_model
:only
:except
refresh_button[RW]

non persistent options:

:build_model
:only
:except
resizable[RW]

non persistent options:

:build_model
:only
:except
resource[RW]

non persistent options:

:build_model
:only
:except
restful[RW]

non persistent options:

:build_model
:only
:except
row_numbers[RW]

non persistent options:

:build_model
:only
:except
rows_per_page[RW]

non persistent options:

:build_model
:only
:except
search_advanced[RW]

non persistent options:

:build_model
:only
:except
search_button[RW]

non persistent options:

:build_model
:only
:except
search_rules[RW]
search_rules_op[RW]
search_toolbar[RW]

non persistent options:

:build_model
:only
:except
searchable[RW]

non persistent options:

:build_model
:only
:except
select_rows[RW]

non persistent options:

:build_model
:only
:except
sort_by[RW]

non persistent options:

:build_model
:only
:except
sort_order[RW]

non persistent options:

:build_model
:only
:except
sortable[RW]

non persistent options:

:build_model
:only
:except
table_to_grid[RW]

non persistent options:

:build_model
:only
:except
title[RW]

non persistent options:

:build_model
:only
:except
url[RW]

non persistent options:

:build_model
:only
:except
view_button[RW]

non persistent options:

:build_model
:only
:except
width[RW]

non persistent options:

:build_model
:only
:except
width_fit[RW]

non persistent options:

:build_model
:only
:except
z[RW]

non persistent options:

:build_model
:only
:except

Public Class Methods

new( klass, *args, &block ) click to toggle source

todo: change this so klass is optional, decouple from active record

# File lib/gridify/grid.rb, line 14
def initialize( klass, *args, &block )
  #debugger
  options = args.extract_options! # => args.last.is_a?(Hash) ? args.pop : {}
  assert_exclusive_keys( options, :only, :except)
  
  @resource = klass.to_s.tableize.pluralize
  @name = args.first || :grid
  
  # non-persistent options
  # generate model unless explicitly say no
  build_model = options.delete(:build_model) == false ? false : true
  only = options.delete(:only)
  except = options.delete(:except)
  col_presets = options.delete(:columns)
  
  # assign options
  update options
  
  # build columns from ActiveRecord model (klass)
  if klass.present? && build_model
    @model = build_columns klass, only, except, col_presets
  end
  
  instance_eval(&block) if block
  #(note instance_eval cannot access things outside its scope; otherwise do this:
  #yield self if block_given?
end

Public Instance Methods

arranger_options(type) click to toggle source
# File lib/gridify/grid_options.rb, line 159
def arranger_options(type) #read-only
  (arranger[type] if arranger.is_a?(Hash)) || {}
end
arranger_type() click to toggle source
# File lib/gridify/grid_options.rb, line 151
def arranger_type #read-only
  if arranger.is_a?(Hash)
    arranger.keys
  else
    Array(arranger)
  end
end
column( name, options={} ) click to toggle source
# File lib/gridify/grid.rb, line 47
def column( name, options={} )
  name = name.to_s
  klass = resource.classify.constantize
  # TODO: set edit options based on ar_column
  # TODO: edit
  # TODO: handle file input types
  # TODO: custom input types
  if col = columns_hash[name]
    # update an existing column
    col.update options
    
  elsif ar = klass.columns.detect {|c| c.name==name}
    #debugger
    # create column from database schema
    edit = editable && 
      # only edit accessible attributes
      (klass.accessible_attributes.nil? || klass.accessible_attributes.include?(ar.name))
    args = {
      :ar_column => ar,
      :name => ar.name,
      :value_type => ar.type,
      :searchable => searchable,
      :sortable => sortable,
      :editable => edit
    }.merge(options)
    columns << GridColumn.new( args)
    
  else
    # create column from scratch
    args = {
      :name => name,
      :value_type => :string,
      :searchable => searchable,
      :sortable => sortable,
      :editable => edit
    }.merge(options)
    columns << GridColumn.new( args)
  end
end
column_model() click to toggle source
# File lib/gridify/grid.rb, line 91
def column_model
  columns.collect {|col| col.properties }
end
column_names() click to toggle source
# File lib/gridify/grid.rb, line 87
def column_names
  columns.collect {|col| col.name.titleize }
end
columns_hash() click to toggle source

normally we need to keep columns an ordered array, sometimes its convenient to have a hash

# File lib/gridify/grid.rb, line 96
def columns_hash
  columns.inject({}) {|h, col| h[col.name] = col; h }
end
current_scope() click to toggle source

return find args (scope) for current settings

# File lib/gridify/grid_finder.rb, line 26
def current_scope
  #debugger
  find_args = {}
  if sort_by.present? && col = columns_hash[sort_by]
    if case_sensitive || !([:string, :text].include?(col.value_type))
      find_args[:order] = "#{sort_by} #{sort_order}" 
    else
      find_args[:order] = "upper(#{sort_by}) #{sort_order}" 
    end
  end
  if rows_per_page.present? && rows_per_page > 0
    find_args[:limit] = rows_per_page
    offset = (current_page.to_i-1) * rows_per_page if current_page.present?
    find_args[:offset] = offset if offset && offset > 0
  end
  cond = rules_to_conditions
  find_args[:conditions] = cond unless cond.blank?
  find_args
end
encode_records( records, total_count=nil ) click to toggle source
# File lib/gridify/grid_finder.rb, line 54
def encode_records( records, total_count=nil )
  #debugger
  klass = resource.classify.constantize
  total_count ||= klass.count
  total_pages = total_count / rows_per_page + 1
  #TODO: :only => [attributes], :methods => [virtual attributes]
  case data_type
  when :xml then
    xml = records.to_xml( :skip_types => true, :dasherize => false ) do |xml|
      if rows_per_page > 0
        xml.page          current_page
        xml.total_pages   total_pages
        xml.total_records total_count
      end
    end
    
  when :json then
    #debugger
    data = { resource => records }
    if rows_per_page > 0       
      data.merge!( 
        :page => current_page, 
        :total_pages => total_pages, 
        :total_records => total_count 
      )
    end
    
    save = ActiveRecord::Base.include_root_in_json
    ActiveRecord::Base.include_root_in_json = false
    json = data.to_json
    ActiveRecord::Base.include_root_in_json = save
    json

  #others...
  else #nop ?
    records.to_s
  end
end
error_handler_return_value() click to toggle source
# File lib/gridify/grid_options.rb, line 252
def error_handler_return_value
  error_handler ? error_handler : 'true;'
end
find( params ) click to toggle source
# File lib/gridify/grid_finder.rb, line 46
def find( params )
  #debugger
  update_from_params params
  find_args = current_scope
  klass = resource.classify.constantize
  records = klass.send( finder, :all, find_args )
end
find_and_encode( params ) click to toggle source
# File lib/gridify/grid_finder.rb, line 93
def find_and_encode( params )
  encode_records( find( params ) )
end
member_params( params ) click to toggle source

grid doesnt nest attributes inside the resource could change this behavior in jqGrid, see grid.postext.js ?

http://www.trirand.com/jqgridwiki/doku.php?id=wiki:post_data_module
# File lib/gridify/grid_finder.rb, line 101
def member_params( params )
  params.inject({}) {|h, (name, value)| h[name] = value if columns_hash[name]; h }
end
to_javascript( options={} ) click to toggle source

generate the grid javascript for a view options:

:script => true generates <script> tag (true)
:ready  => true generates jquery ready function (true)
# File lib/gridify/grid_view.rb, line 11
def to_javascript( options={} )
  options = { :script => true, :ready => true }.merge(options)
  
  s = ''
  if options[:script]
    s << %Q^<script type="text/javascript">^       
  end

  s << js_helpers

  if options[:ready]
    s << %Q^jQuery(document).ready(function(){^    
  end
  
  s << jqgrid_javascript(options)
              
  if options[:ready]
    s << %Q^});^ 
  end
  if options[:script]
    s << %Q^</script>^
  end
  s     
end
to_json() click to toggle source
# File lib/gridify/grid_view.rb, line 37
def to_json
  jqgrid_properties.to_json_with_js
end
to_s( options={} ) click to toggle source

alias :to_s, :to_javascript

# File lib/gridify/grid_view.rb, line 42
def to_s( options={} )
  to_javascript( options )
end
update( options ) click to toggle source
# File lib/gridify/grid.rb, line 42
def update( options )
  options.each {|atr, val| send( "#{atr}=", val )}
  # exception "invalid option..."
end
update_from_params( params ) click to toggle source

finds records based on request params e.g. params from jqGrid

:_search      do search (true/false)  ["false"]
:sidx         sort index (column to search on)  [""]
:sord         sort direction (desc/asc)  ["asc"]
:nd           ?
:rows         number of items to get   ["20"]
:page         page number (starts at 1) ["1"]
# File lib/gridify/grid_finder.rb, line 16
def update_from_params( params )
  params.symbolize_keys!
  params_to_rules params
  self.sort_by       = params[:sidx] if params[:sidx]
  self.sort_order    = params[:sord] if params[:sord]
  self.current_page  = params[:page].to_i if params[:page]
  self.rows_per_page = params[:rows].to_i if params[:rows]
end

Protected Instance Methods

add_button_options() click to toggle source
# File lib/gridify/grid_view.rb, line 67
def add_button_options
  # 'url' => '/notes', 'mtype' => 'POST'
  merge_options_defaults( add_button, 
    'reloadAfterSubmit' => false, 
    'closeAfterEdit' => true,
    'afterSubmit' => "javascript: function(r,data){return #{error_handler_return_value}(r,data,'add');}"
  )
end
build_columns( klass, only, except, presets ) click to toggle source

generate list of columns based on AR model option: :only or :except

:col_options  hash of hash of preset values for columns (eg from cookie) { :title => {:width => 98}}
# File lib/gridify/grid.rb, line 115
def build_columns( klass, only, except, presets )
  #debugger
  # stringify
  only = Array(only).map {|s| s.to_s }
  except = Array(except).map {|s| s.to_s }
  presets ||= {}
  presets.stringify_keys!
  
  self.columns = klass.columns.collect do |ar|
    #debugger
    next if only.present? && !only.include?(ar.name)
    next if except.present? && except.include?(ar.name)
    is_key = (ar.name=='id')
    edit = editable && !is_key && 
      # only edit accessible attributes
      (klass.accessible_attributes.nil? || klass.accessible_attributes.include?(ar.name))
    args = {
      :ar_column => ar,
      :name => ar.name,
      :value_type => ar.type,
      :key => is_key,
      :hidden => is_key,
      :searchable => searchable,
      :sortable => sortable,
      :editable => edit
    }

    # create column with default args merged with options given for this column
    GridColumn.new args.merge( presets[ar.name]||{} )
  end.compact      
end
delete_button_options() click to toggle source
# File lib/gridify/grid_view.rb, line 76
def delete_button_options
  # 'url' => '/notes/{id}', 'mtype' => 'DELETE'
  merge_options_defaults( delete_button, 
    'reloadAfterSubmit' => false,
    'afterSubmit' => "javascript: function(r,data){return #{error_handler_return_value}(r,data,'delete');}"
  )
end
edit_button_options() click to toggle source

get the button options

# File lib/gridify/grid_view.rb, line 55
def edit_button_options
  # 'url' => '/notes/{id}', 'mtype' => 'PUT'
  #         {afterSubmit:function(r,data){return #{options[:error_handler_return_value]}(r,data,'edit');}},
  
  # note, closeAfterEdit will not close if response returns a non-empty string (even if "success" message)
  merge_options_defaults( edit_button, 
    'reloadAfterSubmit' => false, 
    'closeAfterEdit' => true,
    'afterSubmit' => "javascript: function(r,data){return #{error_handler_return_value}(r,data,'edit');}"
  )
end
jqgrid_javascript( options={} ) click to toggle source

# File lib/gridify/grid_view.rb, line 192
def jqgrid_javascript( options={} )
  s = ''

  if table_to_grid
    s << %Q^ tableToGrid("##{dom_id}", #{to_json});^
    s << %Q^ grid = jQuery("##{dom_id}") ^ 
  else
    s << %Q^ grid = jQuery("##{dom_id}").jqGrid(#{to_json})^
  end

  # tag the grid as fluid so we can find it on resize events
  if width_fit == :fluid 
    s << %Q^ .addClass("fluid")^                    
  end

  # override tableToGrid colmodel options as needed (sortable)
  #s << %Q^ .jqGrid('setColProp','Title',{sortable: false})^

  # resize method
  if resizable
    s << %Q^ .jqGrid('gridResize', #{resizable.to_json})^  
  end

  # pager buttons (navGrid)
  if pager
    nav_params = { 
      'edit' => edit_button.present?,
      'add' => add_button.present?,
      'del' => delete_button.present?,
      'search' => search_button.present? || search_advanced.present?,
      'view' => view_button.present?,
      'refresh' => refresh_button.present?
    }.merge(jqgrid_nav_options||{})
    
    s << %Q^ .navGrid('##{pager}',
                  #{nav_params.to_json},
                  #{edit_button_options.to_json_with_js},
                  #{add_button_options.to_json_with_js},
                  #{delete_button_options.to_json_with_js},
                  #{search_button_options.to_json_with_js},
                  #{view_button_options.to_json_with_js}
                )^
  end
  
  if arranger_type.include?(:hide_show)
    s << %Q^ .jqGrid('navButtonAdd','##{pager}',{ 
                  caption: "Columns", 
                  title: "Hide/Show Columns", 
                  onClickButton : function (){ jQuery("##{dom_id}").jqGrid('setColumns',
                    #{arranger_options(:hide_show).to_json_with_js} );
                    }
                  })^
  end
  if arranger_type.include?(:chooser)
    # hackey way to build the string but gets it done
    chooser_code = %Q^ function (){ jQuery('##{dom_id}').jqGrid('columnChooser', {
                      done : function (perm) {
                        if (perm)  {
                          this.jqGrid('remapColumns', perm, true);
                          var gwdth = this.jqGrid('getGridParam','width');
                          this.jqGrid('setGridWidth',gwdth);
                        }
                      } })}^
    chooser_opts = {
      'caption' => 'Columns',
      'title' => 'Arrange Columns',
      'onClickButton' => 'chooser_code'
    }.merge(arranger_options(:chooser))
    s << %Q^ .jqGrid('navButtonAdd','##{pager}', #{chooser_opts.to_json.gsub('"chooser_code"', chooser_code)} )^
  end
  
  if search_toolbar
    # I wish we could put this in the header rather than the pager
    s << %Q^ .jqGrid('navButtonAdd',"##{pager}", { caption:"Toggle", title:"Toggle Search Toolbar", buttonicon: 'ui-icon-pin-s', onClickButton: function(){ grid[0].toggleToolbar() } })  
             .jqGrid('navButtonAdd',"##{pager}", { caption:"Clear", title:"Clear Search", buttonicon: 'ui-icon-refresh', onClickButton: function(){ grid[0].clearToolbar() } }) 
             .jqGrid('filterToolbar')^
  end
  
  # TODO: built in event handlers, eg
  # loadError
  # onSelectRow, onDblClickRow, onRightClickRow etc
  
  s << '; '
  
  unless search_toolbar == :visible
    s << %Q^ grid[0].toggleToolbar(); ^
  end

  # # keep page controls centered (jqgrid bug) [eg appears when :width_fit => :scroll]
  # s << %Q^ $("##{pager}_left").css("width", "auto"); ^
    
  s     
end
jqgrid_properties() click to toggle source

generate the jqGrid initial values in json

maps our attributes to jqGrid options; omit values when same as jqGrid defaults
# File lib/gridify/grid_view.rb, line 99
def jqgrid_properties
  vals                     = {}
 
  # data and request options
  vals['url']               = url if url
  vals['restful']           = true if restful
  vals['postData']          = { :grid => name } #identify which grid making the request
  # vals['colNames']          = column_names if columns.present?
  vals['colModel']          = column_model if columns.present?
  vals['datatype']          = data_type if data_type
  if data_format.present?
    if data_type == :xml
      vals['xmlReader']     = data_format
    elsif data_type == :json
      vals['jsonReader']    = data_format
    end
  end
    
  vals['loadonce']          = load_once if load_once

  vals['sortname']          = sort_by if sort_by
  vals['sortorder']         = sort_order if sort_order && sort_by
  vals['rowNum']            = rows_per_page if rows_per_page
  vals['page']              = current_page if current_page

   # grid options
   vals['height']           = height if height
   vals['gridview']         = true      # faster views, NOTE theres cases when this needs to be disabled
  
  case width_fit
    when :fitted then
      #vals[:autowidth]    = false #default
      #vals[:shrinkToFit]  = true #default
      vals['forceFit']      = true
      vals['width']         = width if width
    
    when :scroll then
      #vals[:autowidth]    = false #default
      vals['shrinkToFit']   = false
      #vals['forceFit']     = #ignored by jqGrid
      vals['width']         = width if width
    
    else #when :fluid
      vals['autowidth']     = true
      #vals['shrinkToFit']  = true #default
      vals['forceFit']      = true
      #vals['width']        = is ignored
      vals['resizeStop']    = 'javascript: gridify_fluid_recalc_width'
  end
  
  vals['sortable']          = true if arranger_type.include?(:sortable)
  
  # header layer
  vals['caption']           = title if title
  vals['hidegrid']          = false unless collapsible
  vals['hiddengrid']        = true if collapsed
  
  # row formatting
  vals['altrows']           = true if alt_rows
  vals['altclass']          = alt_rows if alt_rows.is_a?(String)
  
  vals['rowNumbers']        = true if row_numbers
  vals['rownumWidth']       = row_numbers if row_numbers.is_a?(Numeric)
  
  if select_rows.present?
    vals['scrollrows']      = true
    #handler...
  else
    vals['hoverrows']       = false
    vals['beforeSelectRow'] = "javascript: function(){ false; }"
  end
  
  # pager layer
  if pager
    vals['pager']           = "##{pager}" 
    vals['viewrecords']     = true     # display total records in the query (eg "1 - 10 of 25")
    vals['rowList']         = paging_choices
    if paging_controls.is_a?(Hash)
      # allow override of jqGrid pager options
      vals.merge!(paging_controls)
    elsif !paging_controls
      vals['rowList']       = []
      vals['pgbuttons']     = false
      vals['pginput']       = false
      vals['recordtext']    = "{2} records"
    end
  end
  
  # allow override of native jqGrid options
  vals.merge(jqgrid_options)
end
js_helpers() click to toggle source

# File lib/gridify/grid_view.rb, line 287
def js_helpers
  # just move this into appliaction.js?

  # gridify_fluid_recalc_width: allow grid resize on window resize events
  # recalculate width of grid based on parent container; handles multiple grids
  # ref: http://www.trirand.com/blog/?page_id=393/feature-request/Resizable%20grid/
  
  # afterSubmit: display error message in response
  
  %Q^ function gridify_fluid_recalc_width(){
      if (grids = jQuery('.fluid.ui-jqgrid-btable:visible')) {
        grids.each(function(index) {
          gridId = jQuery(this).attr('id');
          gridParentWidth = jQuery('#gbox_' + gridId).parent().width();
          jQuery('#' + gridId).setGridWidth(gridParentWidth);
        });
      }
    };

    jQuery(window).bind('resize', gridify_fluid_recalc_width);
    
    function gridify_action_error_handler(r, data, action){
      if (r.responseText != '') {
        return [false, r.responseText];
      } else  {
        return true;
      }
    }
  ^
end
merge_options_defaults( options, defaults={} ) click to toggle source

lets options be true or a hash, merges into defaults and returns a hash

# File lib/gridify/grid_view.rb, line 330
def merge_options_defaults( options, defaults={} )
  if options
    defaults.merge( options==true ? {} : options)
  else
    {}
  end
end
params_to_rules( params ) click to toggle source

params => {“groupOp”=>“AND”,

"rules"=>[{"data"=>"b", "op"=>"ge", "field"=>"title"}, {"data"=>"f", "op"=>"le", "field"=>"title"}] }
# File lib/gridify/grid_finder.rb, line 137
def params_to_rules( params )
  #debugger
  if params[:_search]=='true' || params[:_search]==true
    if params[:filters]
      # advanced search
      filters = ActiveSupport::JSON.decode( params[:filters] )
      self.search_rules = filters['rules']
      self.search_rules_op = filters['groupOp']
    elsif params[:searchField]
      # simple search
      self.search_rules = [{ "field" => params[:searchField], "op" => params[:searchOper], "data" => params[:searchString]}]
    else
      # toolbar search
      self.search_rules = []
      self.search_rules_op = :and
      columns.each do |col|  
        name = col.name
        data = params[name.to_sym]        
        self.search_rules << { "field" => name, "op" => "cn", "data" => data } if data
      end
    end
  end
  search_rules      
end
rules_to_conditions() click to toggle source
# File lib/gridify/grid_finder.rb, line 162
def rules_to_conditions
  # note: ignoring case_sensitive as an option, ActiveRecord find is insensitive by default (have to model the db to let it be case sensitive?)
  return nil if search_rules.blank?
  cond = nil
  expr = ''
  vals = []
  search_rules.each do |rule|
    expr << " #{search_rules_op} " unless expr.blank?
    if op = OPS[rule['op']]
      expr << "#{rule['field']} #{op} ?"
      vals << rule['data']
    elsif op = STRING_OPS[rule['op']]
      expr << "#{rule['field']} #{op} ?"
      vals << OPS_PATTERN[rule['op']].gsub('?', rule['data'])
    end
  end
  cond = [ expr ] + vals      
end
search_button_options() click to toggle source
# File lib/gridify/grid_view.rb, line 84
def search_button_options
  if search_advanced
    merge_options_defaults( search_advanced, 'multipleSearch' => true)
  else
    merge_options_defaults( search_button)
  end
end
view_button_options() click to toggle source
# File lib/gridify/grid_view.rb, line 92
def view_button_options
  merge_options_defaults( view_button)
end