lib/netzke/basepack/columns.rb in netzke-basepack-0.8.1 vs lib/netzke/basepack/columns.rb in netzke-basepack-0.8.2

- old
+ new

@@ -3,79 +3,83 @@ module Columns extend ActiveSupport::Concern COLUMN_METHOD_NAME = "%s_column" + included do + class_attribute :declared_columns + self.declared_columns = [] + end + module ClassMethods - # Overrides a column config, e.g.: + def inherited(klass) + klass.class_attribute :declared_columns + klass.declared_columns = [] + end + + # Adds/overrides a column config, e.g.: # # column :title do |c| # c.flex = 1 # end + # + # If a new column is declared, it gets appended to the list of default columns. def column(name, &block) method_name = COLUMN_METHOD_NAME % name define_method(method_name, &block) + self.declared_columns << name end end - # Returns the list of (non-normalized) columns to be used. By default returns the list of model column names. + # Returns the list of (non-normalized) columns to be used. By default returns the list of model column names and declared columns. # Can be overridden. def columns - config.columns || data_adapter.model_attributes + config.columns || default_columns end + # Columns from model + columns declared with DSL + def default_columns + (data_adapter.model_attributes + self.class.declared_columns).uniq + end + # An array of complete columns configs ready to be passed to the JS side. # The +options+ hash can have the following keys: # * :with_excluded - when true, include the columns that are marked as excluded # * :with_meta - when true, include the meta column def final_columns(options = {}) + # memoize @_final_columns ||= {} @_final_columns[options] ||= [].tap do |cols| - initial_columns(true).each do |c| - name = c.name + has_primary_column = false + columns.each do |c| + c = ColumnConfig.new(c, data_adapter) + # merge with column declaration - send(:"#{name}_column", c) if respond_to?(:"#{name}_column") + send(:"#{c.name}_column", c) if respond_to?(:"#{c.name}_column") - # set the defaults as lowest priority - augment_column_config(c) + # detect primary key column + has_primary_column ||= c.primary? - cols << c if options[:with_excluded] || !c.excluded + if !c.excluded || options[:with_excluded] + # set the defaults as lowest priority + augment_column_config(c) + + cols << c # if options[:with_excluded] || !c.excluded + end end + insert_primary_column(cols) if !has_primary_column append_meta_column(cols) if options[:with_meta] end end # Columns as a hash, for easier access to a specific column def final_columns_hash @_final_columns_hash ||= final_columns.inject({}){|r,c| r.merge(c[:name].to_sym => c)} end - # Columns from config.columns or the `columns` method, after normalization - def initial_columns(with_excluded = false) - @_initial_columns ||= {} - @_initial_columns[with_excluded] ||= [].tap do |cols| - has_primary_column = false - - columns.each do |c| - # normalize: - # * :title => {name: 'title'} - # * {name: :some_column} => {name: 'some_column'} - c = ActiveSupport::OrderedOptions.new.replace(c.is_a?(Symbol) ? {name: c.to_s} : c.merge(name: c[:name].to_s)) - - cols << c if with_excluded || !c.excluded - - # detect primary key column - has_primary_column ||= c.name == data_adapter.primary_key_name - end - - # automatically add a column that reflects the primary key - cols.insert(0, ActiveSupport::OrderedOptions.new.replace(:name => data_adapter.primary_key_name)) unless has_primary_column - end - end - def append_meta_column(cols) cols << {}.tap do |c| c.merge!( :name => "meta", :meta => true, @@ -85,123 +89,60 @@ ) c[:default_value] = meta_default_data if meta_default_data.present? end end + def insert_primary_column(cols) + c = ColumnConfig.new(data_adapter.primary_key, data_adapter) + augment_column_config(c) + cols.insert(0, c) + end + # default_value for the meta column; used when a new record is being created in the grid def meta_default_data - default_association_values(final_columns_hash).present? ? { :association_values => default_association_values(final_columns_hash).literalize_keys } : {} + default_association_values(final_columns_hash).present? ? { :association_values => default_association_values(final_columns_hash).netzke_literalize_keys } : {} end # Override it when you need extra meta data to be passed through the meta column def meta_data(r) - { :association_values => data_adapter.assoc_values(r, final_columns_hash).literalize_keys } + { :association_values => data_adapter.assoc_values(r, final_columns_hash).netzke_literalize_keys } end + protected + + # Default fields that will be displayed in the Add/Edit/Search forms + # When overriding this method, keep in mind that the fields inside the layout must be expanded (each field represented by a hash, not just a symbol) + def default_fields_for_forms + columns_taken_over_to_forms.map do |c| + (c[:editor] || {}).tap do |f| + f[:name] = c.name + f[:field_label] = c.text || c.header + f[:read_only] = c.read_only + f[:setter] = c.setter + f[:getter] = c.getter + + # scopes for combobox options + f[:scopes] = c[:editor][:scopes] if c[:editor].is_a?(Hash) + end + end + end + private # Based on initial column config, e.g.: # # {:name=>"author__name", :attr_type=>:string} # # augment it with additional configuration params, e.g.: # - # {:name=>"author__name", :attr_type=>:string, :editor=>{:xtype=>:netzkeremotecombo}, :assoc=>true, :virtual=>true, :header=>"Author name", :editable=>true, :sortable=>false, :filterable=>false} + # {:name=>"author__name", :attr_type=>:string, :editor=>{:xtype=>:netzkeremotecombo}, :assoc=>true, :virtual=>true, :header=>"Author name", :sortable=>false, :filterable=>false} # # It may be handy to override it. def augment_column_config(c) - set_default_attr_type(c) - set_default_xtype(c) - set_default_virtual(c) - set_default_text(c) - set_default_editable(c) - set_default_editor(c) - set_default_width(c) - set_default_hidden(c) - set_default_sortable(c) - set_default_filterable(c) - c[:assoc] = association_attr?(c) # needed on the JS side + c.set_defaults! end - def set_default_attr_type(c) - c[:attr_type] ||= association_attr?(c) ? :integer : data_adapter.attr_type(c.name) - end - - def set_default_xtype(c) - return if c[:renderer] || c[:editor] # if user set those manually, we don't mess with column xtype - c[:xtype] ||= attr_type_to_xtype_map[c[:attr_type]] - end - - def set_default_text(c) - c[:text] ||= c[:label] || data_adapter.human_attribute_name(c[:name]) - end - - def set_default_editor(c) - # if shouldn't be editable, don't set any default editor; also, specifying xtype takes care of the editor - return if c[:read_only] || c[:editable] == false - - if association_attr?(c) - set_default_association_editor(c) - else - c[:editor] ||= editor_for_attr_type(c[:attr_type]) - end - - end - - def set_default_width(c) - c[:width] ||= 50 if c[:attr_type] == :boolean - c[:width] ||= 150 if c[:attr_type] == :datetime - end - - def set_default_hidden(c) - c[:hidden] = true if data_adapter.primary_key_attr?(c) && c[:hidden].nil? - end - - def set_default_editable(c) - if c[:editable].nil? - c[:editable] = is_editable_column?(c) - end - end - - def set_default_sortable(c) - # this *has* to be set to false if we don't want the column to be sortable (it's sortable by default in Ext) - c[:sortable] = !(c[:virtual] && !c[:sorting_scope]) if c[:sortable].nil? - end - - def set_default_filterable(c) - c[:filterable] = !c[:virtual] if c[:filterable].nil? - end - - - # Detects an association column and sets up the proper editor. - def set_default_association_editor(c) - assoc, assoc_method = c[:name].split('__') - return unless assoc - - assoc_method_type = data_adapter.get_assoc_property_type assoc, assoc_method - - # if association column is boolean, display a checkbox (or alike), otherwise - a combobox (or alike) - if c[:nested_attribute] - c[:editor] ||= editor_for_attr_type(assoc_method_type) - else - c[:editor] ||= assoc_method_type == :boolean ? editor_for_attr_type(:boolean) : editor_for_association - end - end - - # If the column should be editable - def is_editable_column?(c) - not_editable_if = data_adapter.primary_key_attr?(c) - not_editable_if ||= c[:virtual] && !association_attr?(c[:name]) - not_editable_if ||= c[:read_only] - - editable_if = data_adapter.attribute_names.include?(c[:name]) - editable_if ||= data_class.instance_methods.map(&:to_s).include?("#{c[:name]}=") - editable_if ||= association_attr?(c[:name]) - - editable_if && !not_editable_if - end - def initial_columns_order final_columns.map do |c| # copy the values that are not null {name: c[:name]}.tap do |r| r[:width] = c[:width] if c[:width] @@ -223,63 +164,19 @@ init_column_names = initial_columns_order.map{ |c| c[:name].to_s }.sort stored_column_names = (state[:columns_order] || initial_columns_order).map{ |c| c[:name].to_s }.sort init_column_names != stored_column_names end - # Column editor config for attribute type. - def editor_for_attr_type(type) - {:xtype => attr_type_to_editor_xtype_map[type] || :textfield} - end - - # Column editor config for one-to-many association - def editor_for_association - {:xtype => :netzkeremotecombo} - end - - # Hash that maps a column type to the editor xtype. Override if you want different editors. - def attr_type_to_editor_xtype_map - { - :integer => :numberfield, - :boolean => :checkbox, - :date => :datefield, - :datetime => :xdatetime, - :text => :textarea, - :string => :textfield - } - end - - def attr_type_to_xtype_map - { - # :integer => :numbercolumn, # don't like the default formatter - :boolean => :checkcolumn, - :date => :datecolumn, - #:datetime => :datecolumn # TODO: replace with datetimepicker - } - end - - # Default fields that will be displayed in the Add/Edit/Search forms - # When overriding this method, keep in mind that the fields inside the layout must be expanded (each field represented by a hash, not just a symbol) - def default_fields_for_forms - selected_columns = final_columns.select do |c| + # Selects those columns that make sense to be shown in forms + def columns_taken_over_to_forms + final_columns.select do |c| + (!c.getter.nil? || !c.setter.nil? || data_adapter.attribute_names.include?(c[:name]) || - data_class.instance_methods.include?("#{c[:name]}=") || - association_attr?(c[:name]) + data_class.instance_methods.include?(c[:name].to_sym) || + data_class.instance_methods.include?(:"#{c[:name]}=") || + association_attr?(c[:name])) && c[:type] != :action end - - selected_columns.map do |c| - field_config = { - :name => c[:name], - :field_label => c[:text] || c[:header] - } - - # scopes for combobox options - field_config[:scopes] = c[:editor][:scopes] if c[:editor].is_a?(Hash) - - field_config.merge!(c[:editor] || {}) - - field_config - end end def columns_default_values final_columns.inject({}) do |r,c| assoc_name, assoc_method = c[:name].split '__' @@ -289,19 +186,9 @@ if assoc_method r.merge(data_adapter.foreign_key_for(assoc_name) || data_adapter.foreign_key_for(assoc_name) => c[:default_value]) else r.merge(c[:name] => c[:default_value]) end - end - end - end - - # Recursively traversess items (an array) and yields each found field (a hash with :name set) - def each_attr_in(items) - items.each do |item| - if item.is_a?(Hash) - each_attr_in(item[:items]) if item[:items].is_a?(Array) - yield(item) if item[:name] end end end end end