Giter VIP home page Giter VIP logo

htmldialog-inputbox's People

Contributors

danrathbun avatar matthes-b avatar thomthom avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

htmldialog-inputbox's Issues

No Current Way to Specify Return Type Without a Default Value

No Current Way to Specify Return Type Without a Default Value.

As discussed in the last PR comments, when a coder passes nil or otherwise does not specify a default value (the Textbox class constructor uses nil as a default,) and the result returned is a String object by default.
This is the same behavior had the coder purposefully passed "" (an empty String) as the argument for the input's default. But in this latter case (the purposeful empty String) is an explicit directive that the coder wants a String object returned by type_convert().

But a coder may not want a String result but also may wish for the input to be blank, so they cannot pass a default value.

In those previous comments, I had entertained the idea that specific subclasses of Textbox such as TextboxLength, TextboxFloat, TextboxInteger, TextboxTime, etc. But this would be clunky, and the easiest solution came to me whilst sitting on the "thinking throne". ;)

1 - There needs to be a documentation note that informs coders that no default or nil for a default will produce a String (just as if they had passed "".)

2 - The answer for result typing without a default value is simple. (It's a "Doh!" forehead smacker.)

We make changes that allow coders to pass class identifiers in place of a default value. Ie ...

  def self.prompt_without_options
    title = 'Tell me about yourself'
    prompts = ['What is your name?', 'What is your age?', 'Pet?']
    defaults = ["", Integer, 'None']
    results = HtmlUI.inputbox(prompts, defaults, title)
    p results
  end

Advanced inputs ...

  def self.prompt_advanced
    options = {
      title: 'HtmlDialog Options',
      accept_button: 'Ok',
      cancel_button: 'Cancel',
      inputs: [
        HtmlUI::Textbox.new('Name'), # will be a blank field, String assumed
        HtmlUI::Textbox.new('Age', Integer),
        HtmlUI::Dropdown.new('Pet', 'Cat', [
          'None', 'Cat', 'Dog', 'Parrot (Resting)', 'Other'
        ]),
        HtmlUI::Listbox.new('Profession', 'Architect', [
          'None', 'Architect', 'Urban Planner', 'Model Railroad Designer', 'Other'
        ]),
      ]
    }
    dialog = HtmlUI::InputBox.new(options)
    results = dialog.prompt
    p results
  end

If the end user leaves these "typed" fields blank, then the value will be 0 (for Integer), 0.0 (for Float) and 0.000" (for Length).

The coder is then responsible for dealing with the zero values.

"inputbox.html" uses CSS zoom incorrectly

"inputbox.html" uses CSS zoom incorrectly

In the style element of the file ...

    /* Applying a global zoom to scale down the default size of Bootstrap. */
    html { zoom: 0.8; }

zoom does not work outside an @viewport at-rule. It is ignored according to the docs.

Ref: https://developer.mozilla.org/en-US/docs/Web/CSS/@viewport


EDIT: (2018-08-31)

After further tests, neither zoom nor -ms-zoom works, outside of a @viewport rule, in UI::WebDialog on MS Windows which runs in a MSIE WebBrowser Control.

It DOES work in Chromium dialogs, outside of a @viewport rule, which is used by the UI::HtmlDialog class on SU2017 or higher. (This example is only written to use the new UI::HtmlDialog class, so the lack of zoom working in a UI::WebDialog may not be considered a bug for this example.)

Also tested the following in a MSIE UI::WebDialog and it also does NOT work.

@viewport {
  -ms-min-width: auto;
  -ms-max-width: none;
  -ms-zoom: 0.75;
  -ms-user-zoom: fixed;
}

small request: messageBox

I can use these nice HTML_inputs simply by mixin.
Would like a messagebox also included.
Did try to get messagebox done, no success
Aling Ends HTML_ui
.

Compatibility shim does not allow 2 args or an empty window caption

@thomthom : But features/behaviour in UI.inputbox that was useful and not buggy can of course be adopted here.

Yes, the native UI.inputbox has the ability to pass 1 prompt array argument without a window title.
But it stupidly will not allow 2 args with the 2nd being the window title. (It raises an exception.)

Your code mimicked this. So I reordered the statements in the ArgumentParser to allow anywhere from 1..4 args.
A consumer can now do ...

HtmlUI::inpubox(["Name"], "Who are you?")

It is silly to force coders to stuff empty literal arrays into a method call, ie ...

HtmlUI::inpubox(["Name"], [], "Who are you?")

If there are no defaults, there is no good reason to force the passing of an empty array.
(This is forced inclusion of empty array arguments is a native UI::inputbox quirk.)


In testing this I also found that the argument parser sets the window :title to an empty string to mimic the native UI::inputbox which will display without a window caption. (Ie, the compatibility shim method does not allow the default Sketchup::app_name to be the default window caption.)

But this doesn't work with a Chromium window. If the window title is not set or is an empty string, then the window caption gets set to "inputbox.html".

The solution is to insert a space into an empty :title string if it is left empty (ie, not specified in the compatibility shim arguments.)

Dealing with type conversion errors on accept

Dealing with type conversion errors on accept.

When the user presses ENTER or clicks the "Accept" button, the app's "accept" function fires and passes the input values to the Ruby "accept" callback. The Ruby callback proc then passes this values array to the type_convert() method for conversion (from JS strings) back into the correctly typed Ruby objects.

But handling of incorrectly entered values by the user is needed.


Future JS side validation

NOTE: Down the road another "subproject" is to implement validation within the Vue JS form, so we need not go back and forth to Ruby - back to JS - repeat, ... etc.


String

No problems with string values. They remain as strings on both sides (Ruby and JS.)


Float and Integer

Currently if the user incorrectly enters values for Float or Integer, Ruby's conversion methods just default to 0.0 and 0 respectively. I will be looking at testing the values using regular expressions for these 2 situations. Something like:

value =~ /^(\d+\.?\d*|\.\d+)/

In testing, I also think it might be best to run the integer values though a float ... ie ...

value.to_f.round

... see next post regarding Length value conversion ...

Previous Input Values Returned on [Enter] with Single Input Control Only

I am observing the following behavior, which I think might be a bug:

  1. Clicking 'Accept' when there are multiple input controls works fine.
  2. Clicking 'Accept' when there is a single input control works fine.
  3. Pressing [Enter] when there are multiple input controls works fine.
  4. BUT: when pressing [Enter] when there is only a single input control in the dialog, then the dialog appears to always return the value from the previous dialog run, not the newly entered value.

I am currently trying to track this down some more.

Buttons are reversed.

Buttons are reversed for normal Windows pattern. (Have no info on what is normal for OSX.)

The normal order follows the left to right naming of the MB constants.
(See UI::messagebox for listing.)

Dropdown Boxes Do Not Show Default Selection

The dropdown input controls generally work as intended. However, dropdown controls always start out showing an empty selection. The user has to manually operate the dropdown and make a selection to see the current value.

The expected behavior (to mimic the original UI.inputbox) would be that the first value of the input array should be displayed as the dialog opens.

The obvious solution would be to control this in the <option> tag by adding something along the lines of <option v-for="option in input.options" **v-bind:selected="$index === 0"**>.

However, it appears this is not possible in vue if v-model is being used, as vue will always override this depending on model data.

Vue documentation indicates the default selection needs to happen somewhere inside here:

    let app = new Vue({
      el: '#app',
      data: {
        **selected: (correct value here...)**
        options: {
          title: 'Untitled',
          inputs: [],
         },
      },

However up to now I have not been able to determine exactly how to set the first element of the input array in this data structure so it gets displayed when the dialog opens.

Text for Accept button should be "OK" for `HtmlUI::inputbox` wrapper

The Accept button name does not match UI::messagebox button name "OK".

The ArgumentParser#get_options_args() method is used (usually) when mimicking UI::inputbox, so it affords an opportunity to override the default as set in HtmlUI::Inpubox#initialize.

The definition of the local options hash object in ArgumentParser#get_options_args(), at about line 84:

      # Convert to option hash.
      options = {
        title: arguments[:title],
        accept_button: 'OK', # mimic UI.inputbox
        inputs: [],
      }

@matthes-b said ...

I support that the 'Ok' button should be called 'Ok'.

@thomthom said ...

A PR of customizing the title of the buttons will be accepted.

Utility method to convert results to a hash

Both Thomas and I has the same idea about getting hash results instead of array results. After looking at the situation ...

The simplest solution is to add a public utility method that the consumer(s) can pass the results array into.

This means there is no extra constructor flag argument needed, nor a special prompt(with_hash) flag argument, nor a special type_convert method argument that causes it to map values to a hash instead of an array.

This also means consumer(s) can use this utility method with the old UI::inputbox argument signature if they forgo the HtmlUI::inputbox wrapper class method and use the HtmlUI::Inputbox::new constructor instead:

window = HtmlUI::Inputbox::new(["Name","Age","Occupation"], ["","",""], "Personal Data ...")
results = window.prompt
data = window.hash_convert(results) if results

The utility method:

      # Returns a hash using the input labels as keys and the results as values.
      #
      #   window = HtmlUI::Inputbox::new(["Name","Age"], ["",""], "Personal Data ...")
      #   results = window.prompt
      #   data = window.hash_from(results)
      #
      # @param values [Array] The results array returned from the {#prompt} method.
      # @return [Hash] The resultant hash.
      # @raise [TypeError] If the `values` argument is not an Array.
      def hash_from(values)
        fail(TypeError,'Array argument expected.',caller) if !values.is_a?(Array)
        hash = {}
        @options[:inputs].each_with_index { |input, index|
          label = input[:label].to_s
          hash[label]= values[index]
        }
        hash
      end

The only thing I worry about is if blank empty labels get through.
Ruby Hash accepts empty string labels as valid so if more than 1 blank label existed data might be lost.

Options hash defaults getting wiped (to `nil`) when named arguments are omitted.

Options hash defaults getting wiped (to nil) when named arguments are omitted.

There are 2 factors contributing:

(1) ArgumentParser#get_options_hash() is wiping out the defaults that are set in HtmlUI::Inputbox#initialize() when named arguments are omitted.

Currently in ArgumentParser#get_options_hash():

    def get_options_hash(options)
      options = {
        title: options[:title],
        accept_button: options[:accept_button],
        cancel_button: options[:cancel_button],
        inputs: options[:inputs].map(&:as_json),
      }
    end

What is happening is a brute force merge! with a modification replacement of the :inputs value.

When a named key/value pair is omitted in the argument hash, then calling options[:keyname] will return nil. So this pattern is forcing the existence of each of the named keys with the omitted keys set to a nil value.

(2) In HtmlUI::Inputbox#initialize() we have ...

        @options = defaults.merge(options)

Hash#merge does not handle nil values any differently than any other class value.
It simply replaces any value in the receiver hash, whose key exists in the argument hash, even if the new value is nil.

So, the @options then has valid values from defaults set to nil.


Two things can be done ...

Use a block with Hash#merge in HtmlUI::Inputbox#initialize(). The block is only called if duplicate keys exist in each hash. Non-duplicates in receiver hash are brought forward unchanged. Any extra keys in argument hash are brought forward unchanged.

      def initialize(*args)
        omit = caller_locations(2)[0].base_label == 'inputbox' ? 2 : 1
        callstack = caller(omit)
        fail(ArgumentError,'No arguments given. (1..4 expected)',callstack) if args.empty?
        defaults = {
          title: Sketchup.app_name,
          accept_button: 'Accept',
          cancel_button: 'Cancel',
          inputs: [],
        }
        options = ArgumentParser.new(callstack).parse(*args)
        @options = defaults.merge(options) do |key, oldval, newval|
          !newval.nil? ? newval : oldval
        end
      end

And (2), in ArgumentParser ...

  class ArgumentParser

    def initialize(callstack, *args)
      @callstack = callstack
    end

    def get_options_hash(options)
      if !options.key?(:inputs)
        fail(ArgumentError,'inputs array argument is required.',@callstack)
      elsif options[:inputs].empty?
        fail(ArgumentError,'inputs array argument is empty.',@callstack)
      end
      # Map :inputs Array members into property Hashes:
      options[:inputs].map!(&:as_json)
      #
      options
    end

NOTE: The @callstack should also be used when raising other exceptions from any of the other ArgumentParser methods. Normally in a library class we just use the Kernel#caller with it's default of omitting 1 item from the callstack, so that the consumer sees where they have made the error in calling the library at the top of the callstack.

But this library is complicated because it has a chain of nested method calls that originate from both a class constructor and a class wrapper that calls that constructor. (Hence the test of where the HtmlUI::Inputbox#initialize() method was called from.)

IMO, having argument parsing as a separate class is confusing and complicates the library. I also think it doesn't teach good coding. It does not encapsulate any state, ie, needs no instance variables (ignoring my addition of @callstack.) It only has processing code that receives and passes all data as method arguments.
Basically it acts like a mixin module, and actually could be better used as one mixed into the HtmlUI::Inputbox class if a separate code object is really needed. (But class definitions can span more than one physical file.)

In my edition, I'm going to move the conditional expression in parse() into HtmlUI::Inputbox#initialize() and then the other methods into the private section of the HtmlUI::Inputbox class. (I might leave it as a separate file.)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.