AndyObtiva/glimmer-dsl-swt

View on GitHub
lib/glimmer/swt/custom/refined_table.rb

Summary

Maintainability
C
7 hrs
Test Coverage
# Copyright (c) 2007-2024 Andy Maleh
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

require 'glimmer/ui/custom_widget'

module Glimmer
  module SWT
    module Custom
      # RefinedTable is a customization of Table with support for Filtering/Pagination
      class RefinedTable
        include Glimmer::UI::CustomWidget
        
        option :per_page, default: 10
        option :page, default: 0
        option :model_array
        option :query, default: ''
        
        attr_accessor :filtered_model_array
        attr_accessor :refined_model_array
        attr_reader :table_proxy, :page_text_proxy, :first_button_proxy, :previous_button_proxy, :next_button_proxy, :last_button_proxy
        
        before_body do
          self.query ||= ''
          @last_query = self.query
          self.model_array ||= []
          self.filtered_model_array = []
          self.refined_model_array = []
        end
        
        after_body do
          Glimmer::DataBinding::Observer.proc do |new_widget_bindings|
            new_widget_bindings.each do |new_widget_binding|
              if new_widget_binding.property.to_s == 'model_array' && !@data_bound
                @data_bound = true
                model_binding = new_widget_binding.model_binding
                configure_sorting
                observe(self, :model_array) do
                  clear_query_cache
                  filter_and_paginate
                end
                @table_proxy.content {
                  items <=> [self, :refined_model_array, model_binding.binding_options]
                }
                filter_and_paginate
              end
            end
          end.observe(body_root.widget_bindings)
        end
        
        body {
          composite {
            text(:search, :border) {
              layout_data(:fill, :center, true, false)
              
              text <=> [self, :query, after_write: -> { filter_and_paginate }]
            }
            
            pagination
            
            @children_owner = @table_proxy = table(*swt_style_symbols) {
              layout_data(:fill, :fill, true, true)
            }
          }
        }
        
        def pagination
          composite {
            layout_data(:fill, :center, true, false)
            
            fill_layout(:horizontal) {
              margin_width 0
              margin_height 0
            }
            
            @first_button_proxy = button {
              text '<<'
              enabled <= [self, :page, on_read: ->(value) {value > first_page}]
              
              on_widget_selected do
                self.page = first_page
                filter_and_paginate
              end
            }
            
            @previous_button_proxy = button {
              text '<'
              enabled <= [self, :page, on_read: ->(value) {value > first_page}]
              
              on_widget_selected do
                self.page -= 1
                filter_and_paginate
              end
            }
            
            @page_text_proxy = text(:border, :center) {
              text <= [self, :page, on_read: ->(value) { "#{value} of #{page_count}" }]
              
              on_focus_gained do
                @page_text_proxy.select_all
              end
              
              on_focus_lost do
                self.page = @page_text_proxy.text.to_i
                filter_and_paginate
              end
              
              on_key_pressed do |key_event|
                if key_event.keyCode == swt(:cr)
                  self.page = @page_text_proxy.text.to_i
                  filter_and_paginate
                end
              end
            }
            
            @next_button_proxy = button {
              text '>'
              enabled <= [self, :page, on_read: ->(value) {value < last_page}]
              
              on_widget_selected do
                self.page += 1
                filter_and_paginate
              end
            }
            
            @last_button_proxy = button {
              text '>>'
              enabled <= [self, :page, on_read: ->(value) {value < last_page}]
              
              on_widget_selected do
                self.page = last_page
                filter_and_paginate
              end
            }
          }
        end
        
        def table_block=(block)
          @table_proxy.content(&block)
        end
        
        def page_text_block=(block)
          @page_text_proxy.content(&block)
        end
        
        def first_button_block=(block)
          @first_button_proxy.content(&block)
        end
        
        def previous_button_block=(block)
          @previous_button_proxy.content(&block)
        end
        
        def next_button_block=(block)
          @next_button_proxy.content(&block)
        end
        
        def last_button_block=(block)
          @last_button_proxy.content(&block)
        end
                
        def page_count
          (filtered_model_array && (filtered_model_array.count / per_page.to_f).ceil) || 0
        end
        
        def corrected_page(initial_page_value = nil)
          correct_page = initial_page_value || page
          correct_page = [correct_page, page_count].min
          correct_page = [correct_page, 1].max
          correct_page = (filtered_model_array&.count.to_i > 0) ? (correct_page > 0 ? correct_page : 1) : 0
          correct_page
        end
        
        def first_page
          (filtered_model_array&.count.to_i > 0) ? 1 : 0
        end
        
        def last_page
          page_count
        end
        
        def filter_and_paginate
          filter
          paginate
        end
        
        def filter
          new_query = query.to_s.strip
          new_filtered_model_array = query_to_filtered_model_array_hash[new_query]
          if new_filtered_model_array.nil?
            if new_query.empty?
              query_to_filtered_model_array_hash[new_query] = new_filtered_model_array = model_array.dup
            else
              new_filtered_model_array = model_array.select do |model|
                @table_proxy.cells_for(model).any? do |cell_text|
                  cell_text.to_s.downcase.include?(new_query.downcase)
                end
              end
              query_to_filtered_model_array_hash[new_query] = new_filtered_model_array
            end
          end
          self.filtered_model_array = new_filtered_model_array
          restore_query_page
          @last_query = new_query
        end
        
        def paginate
          self.page = corrected_page(page)
          self.refined_model_array = filtered_model_array[(page - 1) * per_page, per_page]
        end
        
        def restore_query_page
          new_query = query.to_s.strip
          last_query = @last_query.to_s.strip
          if last_query != new_query
            query_to_page_hash[last_query] = page
          else
            query_to_page_hash[new_query] = page
          end
          if last_query != new_query && last_query.include?(new_query)
            new_page = query_to_page_hash[new_query]
            self.page = corrected_page(new_page) if new_page
          end
        end
        
        def clear_query_cache
          @query_to_filtered_model_array_hash = {}
          @query_to_page_hash = {}
        end
        
        def query_to_filtered_model_array_hash
          @query_to_filtered_model_array_hash ||= {}
        end
        
        def query_to_page_hash
          @query_to_page_hash ||= {}
        end
        
        private
        
        def configure_sorting
          @table_proxy.sort_strategy = lambda do
            new_sort = @table_proxy.sort_block || @table_proxy.sort_by_block || @table_proxy.sort_property
            new_sort_direction = @table_proxy.sort_direction
            return if new_sort == @last_sort && new_sort_direction == @last_sort_direction
            array = model_array.dup
            array = array.sort_by(&:hash) # this ensures consistent subsequent sorting in case there are equivalent sorts to avoid an infinite loop
            # Converting value to_s first to handle nil cases. Should work with numeric, boolean, and date fields
            if @table_proxy.sort_block
              sorted_array = array.sort(&@table_proxy.sort_block)
            elsif @table_proxy.sort_by_block
              sorted_array = array.sort_by(&@table_proxy.sort_by_block)
            else
              sorted_array = array.sort_by do |object|
                @table_proxy.sort_property.each_with_index.map do |a_sort_property, i|
                  value = object.send(a_sort_property)
                  # handle nil and difficult to compare types gracefully
                  if @table_proxy.sort_type[i] == Integer
                    value = value.to_i
                  elsif @table_proxy.sort_type[i] == Float
                    value = value.to_f
                  elsif @table_proxy.sort_type[i] == String
                    value = value.to_s
                  end
                  value
                end
              end
            end
            sorted_array = sorted_array.reverse if @table_proxy.sort_direction == :descending
            self.model_array = sorted_array
            @last_sort = new_sort
            @last_sort_direction = new_sort_direction
          end
        end
      end
    end
  end
end