lib/hotwire_combobox/helper.rb in hotwire_combobox-0.1.37 vs lib/hotwire_combobox/helper.rb in hotwire_combobox-0.1.38

- old
+ new

@@ -13,40 +13,45 @@ def hw_combobox_style_tag(*args, **kwargs) stylesheet_link_tag HotwireCombobox.stylesheet_path, *args, **kwargs end hw_alias :hw_combobox_style_tag - def hw_combobox_tag(name, options_or_src = [], render_in: {}, **kwargs) - options, src = hw_extract_options_and_src(options_or_src, render_in) + def hw_combobox_tag(name, options_or_src = [], render_in: {}, include_blank: nil, **kwargs) + options, src = hw_extract_options_and_src(options_or_src, render_in, include_blank) component = HotwireCombobox::Component.new self, name, options: options, async_src: src, **kwargs render "hotwire_combobox/combobox", component: component end hw_alias :hw_combobox_tag - def hw_combobox_options(options, render_in: {}, display: :to_combobox_display, **methods) + def hw_combobox_options(options, render_in: {}, include_blank: nil, display: :to_combobox_display, **methods) if options.first.is_a? HotwireCombobox::Listbox::Option options else - render_in_proc = ->(object) { render(**render_in.merge(object: object)) } if render_in.present? - hw_parse_combobox_options options, render_in: render_in_proc, **methods.merge(display: display) + render_in_proc = hw_render_in_proc(render_in) if render_in.present? + + hw_parse_combobox_options(options, render_in: render_in_proc, **methods.merge(display: display)).tap do |options| + options.unshift(hw_blank_option(include_blank)) if include_blank.present? + end end end hw_alias :hw_combobox_options - def hw_paginated_combobox_options(options, for_id:, src: request.path, next_page: nil, render_in: {}, **methods) - this_page = render("hotwire_combobox/paginated_options", for_id: for_id, options: hw_combobox_options(options, render_in: render_in, **methods)) - next_page = render("hotwire_combobox/next_page", for_id: for_id, src: src, next_page: next_page) + def hw_paginated_combobox_options(options, for_id: params[:for_id], src: request.path, next_page: nil, render_in: {}, include_blank: {}, **methods) + include_blank = params[:page] ? nil : include_blank + options = hw_combobox_options options, render_in: render_in, include_blank: include_blank, **methods + this_page = render "hotwire_combobox/paginated_options", for_id: for_id, options: options + next_page = render "hotwire_combobox/next_page", for_id: for_id, src: src, next_page: next_page safe_join [ this_page, next_page ] end hw_alias :hw_paginated_combobox_options alias_method :hw_async_combobox_options, :hw_paginated_combobox_options hw_alias :hw_async_combobox_options - protected # library use only + # private library use only def hw_listbox_id(id) "#{id}-hw-listbox" end def hw_pagination_frame_wrapper_id(id) @@ -55,26 +60,37 @@ def hw_pagination_frame_id(id) "#{id}__hw_combobox_pagination" end - def hw_combobox_next_page_uri(uri, next_page) + def hw_combobox_next_page_uri(uri, next_page, for_id) if next_page - hw_uri_with_params uri, page: next_page, q: params[:q], format: :turbo_stream + hw_uri_with_params uri, + page: next_page, + q: params[:q], + for_id: for_id, + format: :turbo_stream end end def hw_combobox_page_stream_action params[:page] ? :append : :update end - private - def hw_extract_options_and_src(options_or_src, render_in) - if options_or_src.is_a? String - [ [], hw_uri_with_params(options_or_src, format: :turbo_stream) ] + def hw_blank_option(include_blank) + display, content = hw_extract_blank_display_and_content include_blank + + HotwireCombobox::Listbox::Option.new display: display, content: content, value: "", blank: true + end + + def hw_extract_blank_display_and_content(include_blank) + if include_blank.is_a? Hash + text = include_blank.delete(:text) + + [ text, hw_render_in_proc(include_blank).(text) ] else - [ hw_combobox_options(options_or_src, render_in: render_in), nil ] + [ include_blank, include_blank ] end end def hw_uri_with_params(url_or_path, **params) URI.parse(url_or_path).tap do |url_or_path| @@ -83,13 +99,27 @@ end.to_s rescue URI::InvalidURIError url_or_path end + private + def hw_render_in_proc(render_in) + ->(object) { render(**render_in.reverse_merge(object: object)) } + end + + def hw_extract_options_and_src(options_or_src, render_in, include_blank) + if options_or_src.is_a? String + [ [], options_or_src ] + else + [ hw_combobox_options(options_or_src, render_in: render_in, include_blank: include_blank), nil ] + end + end + def hw_parse_combobox_options(options, render_in: nil, **methods) options.map do |option| - HotwireCombobox::Listbox::Option.new **hw_option_attrs_for(option, render_in: render_in, **methods) + HotwireCombobox::Listbox::Option.new \ + **hw_option_attrs_for(option, render_in: render_in, **methods) end end def hw_option_attrs_for(option, render_in: nil, **methods) case option @@ -120,10 +150,45 @@ def hw_call_method_or_proc(object, method_or_proc) if method_or_proc.is_a? Proc method_or_proc.call object else - object.public_send method_or_proc + hw_call_method object, method_or_proc end + end + + def hw_call_method(object, method) + if object.respond_to? method + object.public_send method + else + hw_raise_no_public_method_error object, method + end + end + + def hw_raise_no_public_method_error(object, method) + if object.respond_to? method, true + header = "`#{object.class}` responds to `##{method}` but the method is not public." + else + header = "`#{object.class}` does not respond to `##{method}`." + end + + if method.to_s == "to_combobox_display" + header << "\n\nThis method is used to determine how this option should appear in the combobox options list." + end + + raise NoMethodError, <<~MSG + [ACTION NEEDED] – Message from HotwireCombobox: + + #{header} + + Please add this as a public method and return a string. + + Example: + class #{object.class} < ApplicationRecord + def #{method} + name # or `title`, `to_s`, etc. + end + end + MSG end end end