motion-prime/sections/form.rb in motion-prime-0.2.1 vs motion-prime/sections/form.rb in motion-prime-0.3.0

- old
+ new

@@ -1,8 +1,11 @@ motion_require './table.rb' +motion_require '../helpers/has_style_chain_builder' module MotionPrime class FormSection < TableSection + include HasStyleChainBuilder + # MotionPrime::FormSection is container for Field Sections. # Forms are located inside Screen and can contain multiple Field Sections. # On render, each field will be added to parent screen. # == Basic Sample @@ -16,50 +19,104 @@ # end # end # class_attribute :text_field_limits, :text_view_limits - class_attribute :fields_options - attr_accessor :fields, :field_indexes, :keyboard_visible + class_attribute :fields_options, :section_header_options + attr_accessor :fields, :field_indexes, :keyboard_visible, :rendered_views, :section_headers def table_data - fields.values + if @groups_count == 1 + fields.values + else + section_indexes = [] + fields.inject([]) do |result, (key, field)| + section = self.class.fields_options[key][:group].to_i + + section_indexes[section] ||= 0 + result[section] ||= [] + result[section][section_indexes[section]] = field + section_indexes[section] += 1 + result + end + end end + def form_styles + base_styles = [:base_form] + base_styles << :base_form_with_sections unless flat_data? + item_styles = [name.to_sym] + {common: base_styles, specific: item_styles} + end + + def field_styles(field) + suffixes = [:field] + if field.is_a?(BaseFieldSection) + suffixes << field.class_name_without_kvo.demodulize.underscore.gsub(/\_section$/, '') + end + + styles = { + common: build_styles_chain(form_styles[:common], suffixes), + specific: build_styles_chain(form_styles[:specific], suffixes) + } + + if field.respond_to?(:container_styles) && field.container_styles.present? + styles[:specific] += Array.wrap(field.container_styles) + end + styles + end + + def header_styles(header) + suffixes = [:header, :"#{header.name}_header"] + styles = { + common: build_styles_chain(form_styles[:common], suffixes), + specific: build_styles_chain(form_styles[:specific], suffixes) + } + + if header.respond_to?(:container_styles) && header.container_styles.present? + styles[:specific] += Array.wrap(header.container_styles) + end + styles + end + def render_table init_form_fields - set_data_stamp(self.field_indexes.values) - self.table_view = screen.table_view( - styles: [:base_form, name.to_sym], delegate: self, dataSource: self - ).view + reset_data_stamps + options = { + styles: form_styles.values.flatten, + delegate: self, + dataSource: self, + style: (UITableViewStyleGrouped unless flat_data?)} + self.table_element = screen.table_view(options) end def render_cell(index, table) - item = data[index.row] - styles = [:base_form_field, :"#{name}_field"] - if item.respond_to?(:container_styles) && item.container_styles.present? - styles += Array.wrap(item.container_styles) + field = rows_for_section(index.section)[index.row] + screen.table_view_cell styles: field_styles(field).values.flatten, reuse_identifier: cell_name(table, index), parent_view: table_view do |cell_view| + field.cell_view = cell_view if field.respond_to?(:cell_view) + field.render(to: screen) end - screen.table_view_cell styles: styles, reuse_identifier: cell_name(table, index) do |cell_element| - item.cell_element = cell_element if item.respond_to?(:cell_element) - item.render(to: screen) - end end def reload_cell(section) field = section.name.to_sym - path = table_view.indexPathForRowAtPoint(section.cell.center) # do not use indexPathForCell here as field may be invisibe + index = field_indexes[field].split('_').map(&:to_i) + path = NSIndexPath.indexPathForRow(index.last, inSection: index.first) table_view.beginUpdates - section.cell.removeFromSuperview + section.cell.try(:removeFromSuperview) fields[field] = load_field(self.class.fields_options[field]) @data = nil - set_data_stamp([field_indexes[field]]) + set_data_stamp(field_indexes[field]) table_view.reloadRowsAtIndexPaths([path], withRowAnimation: UITableViewRowAnimationNone) table_view.endUpdates end + def reset_data_stamps + set_data_stamp(self.field_indexes.values) + end + # Returns element based on field name and element name # # Examples: # form.element("email:input") # @@ -87,20 +144,27 @@ def fields_hash fields.to_hash end + def register_elements_from_section(section) + self.rendered_views ||= {} + section.elements.values.each do |element| + self.rendered_views[element.view] = {element: element, section: section} + end + end + # Set focus on field cell # # Examples: # form.focus_on(:title) # # @param String field name # @return MotionPrime::BaseFieldSection field def focus_on(field_name, animated = true) # unfocus other field - data.each do |item| + data.flatten.each do |item| item.blur end # focus on field field(field_name).focus end @@ -115,10 +179,23 @@ return unless keyboard_visible self.table_view.height += KEYBOARD_HEIGHT_PORTRAIT self.keyboard_visible = false end + def keyboard_will_show + return if table_view.contentSize.height + table_view.top <= UIScreen.mainScreen.bounds.size.height + current_inset = table_view.contentInset + current_inset.bottom = KEYBOARD_HEIGHT_PORTRAIT + (self.table_element.computed_options[:bottom_content_offset] || 0) + table_view.contentInset = current_inset + end + + def keyboard_will_hide + current_inset = table_view.contentInset + current_inset.bottom = self.table_element.computed_options[:bottom_content_offset] || 0 + table_view.contentInset = current_inset + end + # ALIASES def on_input_change(text_field); end def on_input_edit(text_field); end def on_input_return(text_field) text_field.resignFirstResponder @@ -157,23 +234,61 @@ def load_field(field) klass = "MotionPrime::#{field[:type].classify}FieldSection".constantize klass.new(field.merge(form: self)) end - def render_field?(name) - true + def render_field?(name, options) + condition = options.delete(:if) + if condition.nil? + true + elsif condition.is_a?(Proc) + self.instance_eval(&condition) + else + condition + end end + def render_header(section) + return unless options = self.class.section_header_options.try(:[], section) + self.section_headers[section] ||= BaseHeaderSection.new(options.merge(form: self)) + end + + def header_for_section(section) + self.section_headers ||= [] + self.section_headers[section] || render_header(section) + end + + def tableView(table, viewForHeaderInSection: section) + return unless header = header_for_section(section) + wrapper = MotionPrime::BaseElement.factory(:view, styles: header_styles(header).values.flatten, parent_view: table_view) + wrapper.render(to: screen) do |cell_view| + header.cell_view = cell_view if header.respond_to?(:cell_view) + header.render(to: screen) + end + end + + def tableView(table, heightForHeaderInSection: section) + header_for_section(section).try(:container_height) || 0 + end + class << self - def field(name, options = {}) + def field(name, options = {}, &block) options[:name] = name options[:type] ||= :string + options[:block] = block self.fields_options ||= {} self.fields_options[name] = options self.fields_options[name] end + def group_header(name, options) + options[:name] = name + self.section_header_options ||= [] + section = options.delete(:id) + self.section_header_options[section] = options + end + def limit_text_field_length(name, limit) self.text_field_limits ||= {} self.text_field_limits[name] = limit end def limit_text_view_length(name, limit) @@ -184,15 +299,18 @@ private def init_form_fields self.fields = {} self.field_indexes = {} - index = 0 + section_indexes = [] (self.class.fields_options || []).each do |key, field| - next unless render_field?(key) + next unless render_field?(key, field) + section_id = field[:group].to_i + section_indexes[section_id] ||= 0 + @groups_count = [@groups_count || 1, section_id + 1].max self.fields[key] = load_field(field) - self.field_indexes[key] = index - index += 1 + self.field_indexes[key] = "#{section_id}_#{section_indexes[section_id]}" + section_indexes[section_id] += 1 end end end end \ No newline at end of file