sketchup / htmldialog-inputbox Goto Github PK
View Code? Open in Web Editor NEWUI::HtmlDialog example recreating UI.inputbox functionality in the SketchUp Ruby API
License: MIT License
UI::HtmlDialog example recreating UI.inputbox functionality in the SketchUp Ruby API
License: MIT License
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.
Making out example more consistent.
Following the general HtmlDialog example: https://github.com/SketchUp/htmldialog-examples
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
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;
}
HtmlUI::Input
subclass constructors force use of positional arguments.
This is becoming a problem in adding more features.
One is adding a key: "keyname"
argument for hash output.
Is there any way to use the VueJs DevTools in UI::HtmlDialog
?
@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.)
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.
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 ...
I am observing the following behavior, which I think might be a bug:
I am currently trying to track this down some more.
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.)
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.
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'.
A PR of customizing the title of the buttons will be accepted.
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.
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.)
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.