|
This is another "high" bungalow. The angle at the peak of the roof is almost 90 degrees, to gain living space in the second floor.
A wonderful book for the architcture fan is 117 House Designs of the Twenties, Gordan-Van Tine Co. (a Dover reprint). This is a catalog from a manufacturer of build-it-yourself house kits. Page 8 brags about a customer starting his home in November and completing it before winter sets in. The home in question is a Craftsman bungalow that, excepting the pitch of the roof, could be this bungalow. The authoritative McAlesters date the Craftsman style 1905 through 1930. Although Stickley's Craftsman closed in 1916, at least the Gordon-Van Tine Co. was still selling Craftsman homes in the '20s. Craftsman bungalow, Florida, NY, c. 1910. |
|
|
Chapter 19—Fixed WebDialogs |
JavaScript or javascript or ...? There is no official name for this language. It's governing body, the European Computer Manufacturer's Association calls it ECMA Script. That name has not proved catchy. Browsers all are case-insensitive when it comes to javaScript or jAVAsCRIPT. I have, however, four good books from three different publishers and every one calls the language JavaScript.
Are you currently JavaScript-literate? If so, follow this link to the Lumberman programming section.
When you get to JavaScript objects you may begin to think, as I do, that this is a very interesting language.
// This is a comment. The "//" works like Ruby's "#". foo = expression; // comment here /* Block comments are done this way. */
Numeric constants are almost the same.
JavaScript has a special constant NaN that stands for "Not a Number". You can get NaN from trying to multiply a number by a string. Note: NaN is not equal to anything, not even itself. You test for NaN with the isNaN() function. In Ruby, 'fred'.to_i() returns 0. In the comparable JavaScript, parseInt( 'fred' ) returns NaN.
Division by zero yields another special constant, Infinity. (Infinity/2) equals Infinity. isNaN( Infinity ) is false. (Division by zero is an error in every other language I've ever used.)
foo = parseInt( userInput );
if ( isNaN(foo) ) {
// prompt user to try again
}
You should always "declare" your JavaScript variables as variables: var foo; or var foo, bar; or var foo='phoo', bar = 'tavern';. You can also declare your variables' types, although no one does. Without an explicit type declaration, a JavaScript variable is untyped, like a Ruby variable.
All variables declared outside a function are global. Variables used inside a function are global if they are not declared in a var declaration. Global variables are all properties of the "global object." In a browser, the "global object" is named "window."
JavaScript is entirely too reliant on the global object and, except in the case of WebDialogs, monstrously prone to name conflicts. A WebDialog, however, is launched on its own, so has its own "window" object. If your WebDialog and mine are launched at the same time, you have your "window" object and I have mine. Even if we picked all the same variable names there will be no conflict as your variables are all yourWindow.foo and my variables are all myWindow.foo. This is a happy accident.
Math.pow(2,10) == 1024. 3 == '3' is true; 3 === '3' is false.i++; or as a loop terminal action. (In days gone by, incrementing a value in a register was much faster than adding one to a value in a register.)'foo'+23 evaluates to 'foo23'. (Type coercion is dangerous in logical operations, but a nice time-saver in string concatenation.)
| Name | Example | Description | ||||
|---|---|---|---|---|---|---|
| break |
while ( condition1 ) {
...
if (condition2) {
break;
}
...
}
|
Stop a loop or exit a switch. Same as Ruby. | ||||
| do |
do {
...
} while (condition); |
|
||||
| expression |
x = expression;
function_call();
|
Evaluate an expression; call function(s). Same as Ruby. | ||||
| for |
for (v in array) {
...
}
|
|
||||
| for |
for (v in object) {
...
} |
Loops through each property of object. Value of v is name of property. Object properties may be retrieved via subscripting: object[propertyName].
|
||||
| for |
for ( var i = 0; i < max; i++ ) {
...
}
|
Loop given initializers; continuation condition; action at loop end. Straight from C. You need this one where you want to loop by a count. (No equivalent to Ruby's convenient range operator.) | ||||
| function |
function funcname( args ) {
...
}
|
Define a named function. Similar to Ruby def. |
||||
| if |
if ( condition1 ) {
...
} else {
if ( condition2 ) {
...
} else {
...
}
}
|
Execute code if condition is true; other code if false. Note parens around condition. Intermediate else, not elsif. No if suffix. No unless. |
||||
| return | return; return expression; |
Return from a subroutine or function.Same as Ruby. | ||||
| switch |
switch (expression) {
case val1:
...
break;
case val2:
...
break;
default:
...
} |
Execute code if a value is equal to an expression.
|
||||
| throw | throw "File not found"; |
Stop currently executing code. If in a try block, the object thrown will be caught in the catch block. The try/catch blocks may be in the current code, in code that calls this code, or anywhere up the return chain. An uncaught throw terminates the executing JavaScript. It's raise in Ruby. |
||||
| try |
try {
...
}
catch (e) {
...
} |
Attempt to process statements that could throw an error. Catch and react if the error occurs. It's begin...rescue => e...end in Ruby. |
||||
| var | var foo; var bear="Pooh", kanga="roo"; |
Declare and, optionally, assign values to variables. At the outside level, var foo is equivalent to Ruby $foo. |
||||
| while |
while (condition) {
...
}
|
Execute code while condition is true (or until break). Same as Ruby except for parens around condition. |
arr['dog'] = 'woof'; arr['cat'] = 'meow'; alert( arr['dog'] ) // alerts 'woof'
JavaScript does not have classes. Objects are created this way: dog = { talk:'woof', legs:4 };. Additional properties may be added: dog.class='canine'. Properties available through object.property notation or through subscripting: object[property]. JavaScript objects are also hashes (sets of key/value pairs).
onClick and onChange. Examples:
<button onCick='help_button_click()'> Help! </button>
<select onChange=selector( this )> <!-- "this" is the select tag object -->
<script type='text/JavaScript'>
function help_button_click() {
alert( 'There is no help for you!' );
}
function selector( who ) {
alert( 'You chose ' + who.value );
}
</script>
<button id='help_btn'> or <span id='thickness_axis_text'>green</span>. The getElementById() method, which does just what it says it will do, is a method of the document object.
Every tag that has a corresponding end tag has an innerHTML property. That is whatever comes between the tag and the end tag. Assume that your HTML includes <span id=thickness_axis_text>green</span>. Your user has chosen "green" for the length axis and "blue" for the width axis of a board. You want your thickness axis to say "red":
txt = document.getElementById( 'thickness_axis_text' ); txt.innerHTML = 'red';
You'll only type "document.getElementById" a few times before you're tired of it. Try starting your JavaScripting by entering this finger-saving function:
function gebi( id ) {
return document.getElementById( id );
}
When your JavaScript is complete, search/replace gebi with document.getElementById.
Choose meaningful names for your ids and your code becomes self-documenting.
The <span> above is the simple case. The innerHTML for a table is everything following <table> up to, but not including the closing </table>. You could swap out one table for another with a single innerHTML replacement, but there are easier ways to build tables. We'll get there.
And that's what you need to know to write JavaScript. I bet that's about as fast as a language was ever described. Complete? Of course not. Can you write JavaScript? Well, let's start writing some JavaScript. Those Lumberman widgets all need to talk to each other. I'll provide a lot of help, at first. The level of help will fade quickly. That's where you learn to write JavaScript.
<img> tag has a src attribute. How do you swap graphics in an <img>?
img = document.getElementById( 'image_id' ); img.src = 'my_new_image.jpg';
In general, object.attribute = new_value; will update your HTML's attributes. Styles are found in the style property. Hyphenated property names become lowerAndUpper property names, so you assign to object.style.backgroundColor, for example. Now you only need to Google about half your tasks.
Ready to write some code? Begin by creating a JavaScript area at the end of your lumberman.html's <body> section:
<script type='text/JavaScript'>
alert( 'JavaScript here!' );
</script>
</body>
Enter that code and reload in your browser. Got a popup message? Good. You've passed "Hello, World!" in JavaScript.
Technical note: If any item in a script area fails to compile, the browser discards the whole script area. Your browser will not tell you that there was a compile failure. You can have as many <script>...</script> sections as you like. You might use one section for the function you are writing and another section for debugged code. Or, you could test using the Opera browser, with its Tools/Advanced/Error Console open. That reports compile errors. (Line numbers are reported relative to the start of the script area.)
Where is the JavaScript Console and its handy input line? Mine's at www.MartinRinehart.com/models/tutorial/js_console.zip. This is HTML and JavaScript, not a Ruby plugin. Extract it to any handy directory. Open js_console.html in any handy browser. (Warning: I don't test in MSIE because I don't use MSIE.)
Try those queries in your own JavaScript Console. The JavaScript parseInt() function is similar to the Ruby .to_i method. Both return a one from '1fred'. However they are fundamentally different working on 'fred'. Ruby gives you a zero. JavaScript gives you a NaN. And NaN is not equal to anything, not even itself. You test for it with the isNaN() function.
Now lets go back to the axes box. Fold arms. Ask self, "Self, what do I need to do here?"
Here's my answer:
<script type='text/JavaScript'>
function length_axis_changed() {
alert( 'length axis changed' );
}
</script>
Now find the length axis <select> widget in your HTML. Add an id and a call to your new method, like this:
Length axis:
<select id='length_axis' onchange='length_axis_changed()'>
<option value='r'>red
<option value='g'>green
<option value='b'>blue
</select>
You did delete the alert you tested, right? OK, save and reload your browser window. (From now on I'll just say "run".) What happens? (Nothing, I hope.) Make a change in the length axis dropdown. Did you get a popup that tells you your axes dropdown was changed? Good. (You didn't? Opera Tools/Advanced/Error Console. Try again. It will explain the problem. Likely just a typo.) OK, fold arms again. You've now got a place to execute some code. What should that code do?
I'd say it's time to unfold arms and make some notes right in your new function. Mine looks like this:
function length_axis_changed() {
// change the picture to the one that says "Width axis?"
// change the width axes radio buttons labels
// change the width axes values to match the labels, and uncheck them
// blank out the thickness axis color text
}
Fold arms again. I'm about to ask you to turn that function into working code. I'd work one comment at a time, write some code and test. Got it? On to next comment. First, though, some hints and information you'll need, beginning with a working switch. Here's a helper function that uses a switch:
function full_name( letter ) {
switch( letter ) {
case 'r':
return 'red'
case 'g':
return 'green'
default:
return 'blue'
}
} // end of full_name()
Does that make sense? Copy it into your file. Write your own switch when you get to the width axes stuff. Speaking of that, I handle the second and third comments together, calling a function with two letters for arguments. User chose length red? reset_width_axis_buttons( 'g', 'b' );.
Is it safe to default to "blue"? Normally you'd have a case for "b" and would default to some error handling. In this case, however, your user picked a color from a dropdown with just three choices, so you know that it's "b" if it's not "r" or "g".
The secret to handling the radio buttons is to give IDs to the <input type=radio> tags (mine are, creatively, "width_axis_a" and "width_axis_b") and to put the texts into spans, which also have ids ("width_axis_a_text" and "width_axis_b_text" for me).
Critical info: your HTML showed the default "board.gif". That's the one with the dimensions labeled "length", "width" and "thickness". There are six more pictures with names like "board_rgb.gif". The letters are ordered length first, width second and thickness last. The final board picture is "board_width.gif" which asks the user "Width Axis?". That's the one to display after a change in the length axis.
One less Google: In HTML, you can mark one radio button "checked". In JavaScript you can set the "checked" attribute true or false.
<input type=radio> will be "r" and "g"; neither radio button will be checked and the "Thickness Axis:" text will be blank.
It's always more fun to do stuff than to undo stuff. This one's more fun than the last one. After the user chooses the width, (length already chosen) your JavaScript can, without too much trouble, deduce the thickness. Since you now know all three, you can pick the right picture and fill in the thickness text. Begin by copying this helper function:
function remainder( two_colors ) {
switch ( two_colors ) {
case 'rg':
return 'b';
case 'gr':
return 'b';
case 'rb':
return 'g';
case 'br':
return 'g';
default:
return 'r';
}
}
Aren't those switches nice? They seem to want to explain themselves, don't they?
return statement, that ends the switch and the function containing it. If your case assigns a value to a variable you must follow that assignment with a break statement.
function sample( letter ) {
var name;
switch ( letter ) {
case 'r':
name = 'red';
break;
case 'g':
name = 'green';
break;
default:
name = 'blue';
}
do_something_with( name );
}
breaks the above will have name == 'blue' every time, regardless of the letter. This is a regular source of bugs in C and in each language that followed the C convention.
Doing the handling is easier if the clicked button tells you who it is. Here's sample HTML:
<input id='width_axis_a'
type=radio
name='width_dimension'
value='g'
checked
onclick="width_axis_button_click('a')"
>
<span id='width_axis_a_text'>green</span>
</input>
The standard way to format HTML is to write the tag across the page, letting it wrap as needed. My way is to write long tags one attribute per line. I think my way is much more readable. Others find it strange. You decide how you want to do it.
This is how my handler function starts:
function width_axis_button_click( a_or_b ) {
var c1 = document.getElementById( 'length_axis' ).value;
var c2 = document.getElementById( 'width_axis_' + a_or_b ).value;
Your job is to finish this one. Mine goes on for six more lines. Hint: If you have two_colors the picture you want to show is 'board_' + two_colors + remainder( two_colors )+'.gif'. The ID of the thickness axis text is thickness_axis_text. You want to change it to full_name( remainder(two_colors) ).
<select> tag is much easier than radio buttons. You don't have to code for the options. You just respond to a change event in the <select> tag. And you can query the value property of the <select> tag.
(The above paragraph is true if you do not use the multiple attribute—the default is to permit exactly one choice. Another click changes the choice. The multiple attribute lets the user choose multiple options. If possible, a set of checkboxes is much more user friendly; you can see at a glance what is selected. If the list is very long, the <select multiple> may be your only choice. Google: JavaScript select multiple. Good luck.)
You want the user's click on a "mm" select to populate the text boxes. The user clicks "25x100" and you want the first box to show "25mm", the second to show "100mm". The user clicks the "1x8" choice in the "inches" box you want the first to show "0.75in" and the second to show "7.25in".
Begin by stealing my HTML <select> options. You won't learn much from retyping them. Notice that the value for the "1x8" choice looks a lot like an array:
<option value='[.75,7.25]'>1x8
Why is that? It's because JavaScript, like Ruby, can evaluate text and return JavaScript, if the text is valid JavaScript syntax. This call, eval( 'arr = [.75,7.25]' )arr[0] == .75; arr[1] == 7.25.
You need to get a handle on the <select> tag. Have it return a reference to itself in the HTML:
<select id='international_dimensions' onchange='new_dimensions(this)'>
With that introduction, here's the beginning of my new_dimensions() function:
function new_dimensions( who ) {
var dims = eval( who.options[who.selectedIndex].value );
dims[0] += ( who.id == 'american_dimensions' ) ? 'in' : 'mm';
Because the HTML returned this, a reference to the select object, you can create a parameter, here it's who that holds a reference to the calling object. You can figure out the options and selectedIndex properties of the select object from the second line. Now that third line.
Have we discussed the "ternary" yet? Here's a bit of my syllabus:
Ternary needs a check so I can fold up the "operators" topic.
We've been using unary (one argument) and binary (two arguments) operators. The C language invented a three-argument operator, and it's been forever after known as "the ternary" because, thankfully, no other ternaries have been invented. The ternary is useful if you don't abuse it:
condition ? value_if_true : value_if_false
Here we're just using it to pick either "in" or "mm" as the suffix to the dimension. (Remember, JavaScript does type coercion so number + text is adequate where in Ruby you'd need number.to_s() + text.)
Abusing the ternary? It works well when it's choosing between two constants. If those values involve expressions and especially if they need function calls, you are inviting the ants to your picnic.
Once the last item is checked, the topic is folded. We're nearly done!
Ready to complete the new_dimensions() function? Here's a hint: the top part that I showed you is half the function. Go for it.
To begin, you have six small images named "wood000.gif", "wood001.gif" and so on. My HTML looks like this:
<tr> <td colspan=6> <hr> </td> </tr>
<tr>
<td id='mat0' class='material'>
<img src='wood000.gif' title='lightest_cabinet_wood'
onclick="material_click( this )">
</td>
<td id='mat1' class='material'>
<img src='wood001.gif' title='lighter_cabinet_wood'
onclick="material_click( this )">
</td>
<td>. It also "selects" the default by painting the <td> black.
<script type='text/JavaScript'>
var material='[Wood_Cherry_Original]', mat_box_id='mat2';
document.getElementById( mat_box_id ).style.backgroundColor = '#000000';
<td> holding an <img> will be completely filled, except for your specified cellpadding. This means that simply changing style.backgroundColor will draw a border around the <img>. (There is no law that says you have to use black.)
It's up to you to create the material_click(). The onClick() call in the HTML passes a reference to the image clicked. With that, handle these:
material variable to the new material. (It's in the image's title.<td>'s style.backgroundColor to an empty string.bigger encloses smaller, a reference to bigger is found in smaller.parentNode. Use this to find your image's enclosing <td>.<td>'s id into the global mat_box_id.<td> in your highlighting color.
Each of the buttons calls closer() passing its text as an argument. Except for 'cancel' the data will be passed back to Ruby. Here's a method for you to type or copy into your code:
function closer( button_text ) {
if ( button_text == 'cancel' ) {
call_ruby( 'cancel' );
return;
}
if ( OK_to_close() ) {
call_ruby( collect_data(button_text) );
}
return;
} // end of closer()
If the "Cancel" button was clicked, we just pass this fact back to Ruby. (Reason? In Windows, if we try to close the WebDialog, MSIE gets in the way, asking if it's OK to close the window. If we answer, "Yes, it's OK." MSIE doesn't close the window. Ruby can close the window, however.)
Otherwise, we do some checks (is there length data in one of the length boxes?). If the checks are passed, the data is gathered and returned to Ruby. Simple enough?
It's simple in concept, but requires quite a bit of code to actually get the job done. I'll show you some code you can copy, then set you to write the remaining code. You'll be thinking you can really write JavaScript before you're done.
At the top level, the OK_to_close() function is just a bunch of calls to things that need checking:
function OK_to_close() {
return check_width_axis() &&
check_thickness() &&
check_width() &&
check_length();
}
Note: most modern languages, Ruby and JavaScript included, use "short-circuit" evaluation for compound logical checks. The above checks must all be true for the return value to be true. At the first false value, the value is false and the remaining functions are not executed.
Now let's look at two low-level functions, beginning with has_quote().
function has_quote( str ) {
return ( str.indexOf('"') != -1 ) || ( str.indexOf("'") != -1 )
}
JavaScript's indexOf() string method returns the location of the start of a substring within a string, or minus one if the substring is not found. This is always the first thing to check. Embedded quotes can ruin the remaining checks.
Given that there are no embedded quotes, we can check that the widget's text has useful data. This is very easy for us because of some well-designed JavaScript functions:
function has_data( str ) {
return Math.abs( parseFloat(str) ) > 0;
} // end of has_data()
The parseFloat() function returns NaN on an empty string or any string that doesn't start (after stripping leading whitespace) with a number. The Math.abs() returns NaN if called with NaN. Asking if NaN > 0 returns false.
Knowing how all this works, you and I could probably sneak Infinity past these checks. Should we add code to check for this?
Well, you have the top-level code that calls checking routines, and the bottom-level code that does checks. Now it's up to you to add the middle. For testing you'll want to write a call_ruby() that pops up the data in an alert box.
Plugins directory. On startup, SketchUp runs every Ruby program in the Plugins directory. That is the time to add your Ruby plugin to the Plugins menu, along with code to run the plugin when the menu item is clicked. To make this easy, I've created this cookbook approach:
# qqq_menu.rb - the one little bit of qqq in the PlugIns directory
# Copyright 2010, Martin Rinehart
# everything else is in Plugins/qqq/
require 'sketchup'
unless file_loaded?( "qqq_menu.rb" )
qqq = Sketchup.find_support_file( "qqq.rb", "Plugins/qqq/" )
UI.menu("Plugins").add_item( "qqq" ) { load( qqq ) }
file_loaded( "qqq_menu.rb" )
end
=begin
1) replace all "qqq" with "plugin_name"
2) change 'UI.menu("Plugins").add_item( "plugin_name" ) ... to
'UI.menu("Plugins").add_item( "Plugin Name" ) ...
3) File/Save As "plugin_name_menu.rb"
4) Create dir "Plugins/plugin_name/"
5) Create "Plugins/plugin_name/plugin_name.rb"
6) Delete this comment and save
7) Close and restart SketchUp
=end
Copy this code and save it in your Plugins directory but don't use the .rb extension. (You don't want SketchUp to try to run it.) Mine is qqq.template.
Carefully follow the steps in the comment at the end. In step 1, use "lumberman" as the "plugin_name". Step 2 capitalizes "Lumberman" where it appears in the Plugins menu. This file, in step 3, becomes lumberman_menu.rb which SketchUp will run when it starts up. In step 4, you create Plugins/lumberman/ where your files will live. In step 5 you create the beginning of the actual Ruby plugin. A good start would be to require 'sketchup' and UI.messagebox( 'lumberman here!' ). After steps 6 and 7 you will have a "Lumberman" option in your Plugins menu.
Let's look at the lumberman_menu.rb after the edits.
# lumberman_menu.rb - the one little bit of lumberman in the PlugIns directory
# Copyright 2010, Martin Rinehart
# everything else is in Plugins/lumberman/
require 'sketchup'
unless file_loaded?( "lumberman_menu.rb" )
lumberman = Sketchup.find_support_file( "lumberman.rb", "Plugins/lumberman/" )
UI.menu("Plugins").add_item( "Lumberman" ) { load( lumberman ) }
file_loaded( "lumberman_menu.rb" )
end
The first time this is run, lumberman_menu.rb has not been loaded. This begins by using the two-parameter version of Sketchup.find_support_file(). The first parameter is the file you want and the second parameter is the path from Plugins down. (The actual location of Plugins is different on PC and Mac, and different for every version of SketchUp.) The next line adds "Lumberman" to the Plugins menu. The code it runs is simply to load lumberman, which contains the full path and name of the lumberman.rb file. Last, the file_loaded() method (it's in sketchup.rb) is called to add itself to the list of loaded files.
Do you see why I made this a cookbook template? There's too much to remember and you do it too seldom to remember. For the next time you use the template, change the copyright notice to one that gives you the credit you deserve.
Plugins/lumberman/ Subdirectory lumberman.rb file in Plugins/lumberman/. To complete the subdirectory add these files:
lumberman.htmlboard.gifboard_bgr.gifboard_brg.gifboard_gbr.gifboard_grb.gifboard_rbg.gifboard_rgb.gifboard_width.gifruby_88_64.gifwood000.gifwood001.gifwood002.gifwood003.gifwood004.gifwood005.gifwood006.giflumberman.rb that is smart enough to launch our webdialog.
lumberman.rb so it looks like this:
# lumberman.rb
require 'sketchup'
wd = UI::WebDialog.new( 'Lumberman', true, 'lumberman',
1000, 500, 100, 100, true )
pathname = Sketchup.find_support_file( 'lumberman.html', 'Plugins/lumberman/' )
wd.set_file( pathname )
wd.show()
# end of lumberman.rb
Give it a try! Click "Plugins/Lumberman". You should now be looking at your WebDialog. This is a moment to celebrate!
Or it should be a moment to celebrate, if everything works and, for those using a PC, if MSIE doesn't mess things up too badly. In practice, this is usually a moment when you can start final debugging and HTML-tweaking. Get it running to your satisfaction.
Now for those constructor parameters. At present, the Google doc is not accurate (last checked: 3/4/10). The arguments to WebDialog.new() are:
<body scroll=no>.HKEY_CURRENT_USER/Software/Google/SketchUp7/WebDialog_ plus this key.true.
If you've clicked the WebDialog buttons, you've reminded yourself that the call_ruby() function was stubbed out to just report the data it had gathered. Now it's time to actually call Ruby.
That means one simple addition to lumberman.rb:
wd.set_file( pathname )
wd.add_action_callback( 'catch_data' ) do |another_wd, msg|
UI.messagebox( 'JavaScript says: ' + msg )
end
wd.show()
In the above, 'catch_data' is the name of the method that JavaScript will use to call Ruby. In the code block that follows, another_wd and msg are the two parameters that receive the arguments JavaScript supplies. The first is a reference to a WebDialog object. I think it's a reference to the same WebDialog that provides the action callback and I have no idea why it might be useful. The msg is the actual message that you want to process.
That is half the process. Ruby is ready to be called by JavaScript. Now we need JavaScript to actually call Ruby. That is probably simpler than you think. First, Sketchup invents its own protocol, skp:. To call a callback named catch_data with a message msg you place this into your dialog's location bar: "skp:catch_data@" + msg. The location bar is a global object named location. That's a lot of explanation for a very small bit of code. In your call_ruby() replace the alert message with this:
function call_ruby( msg ) {
location = 'skp:catch_data@' + msg;
}
And that is cause for celebration! Your WebDialog is gathering the data that Ruby needs to do some modeling.
require 'sketchup' def cancel( wd ) wd.close() end def make_board( msg ) args = eval( msg ) for a in args do puts a end end wd = UI::WebDialog.new( 'Lumberman', true, 'lumberman', 1000, 500, 100, 100, true ) pathname = Sketchup.find_support_file( 'lumberman.html', 'Plugins/lumberman/' ) wd.set_file( pathname ) wd.add_action_callback( 'catch_data' ) do |another_wd, msg| if msg == 'cancel' cancel( wd ) else make_board( msg ) end end
The hint is in the make_board() function. Our JavaScript's message was already an array in correct Ruby syntax. A simple eval() is all it takes to create a Ruby array. Good luck with that board!
|
|
|