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.

Craftsman bungalow with gable-ended porch

Edges to Rubies The Complete SketchUp Tutorial


Screenshot of JavaScript Console in operation.

Chapter 19—Fixed WebDialogs

There are two types of WebDialogs: fixed and model-dependent. The characteristic of the fixed is that HTML can be used to display the dialog. If the dialog is model-dependent, Ruby needs to call JavaScript and pass whatever information JavaScript needs. The HTML (or HTML-equivalent work) is then created by JavaScript. If you guess that this is not as simple as creating a fixed dialog, you are right. If you think fixed WebDialogs will be simple, you are wrong.

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.

JavaScript for Ruby Programmers

JavaScript is a lot like Ruby. Here we'll concentrate on the differences. Before we begin, I should state that we are not covering all of JavaScript here. We are covering the subset that JavaScript guru Doug Crockford identifies in his book, JavaScript: The Good Parts. (Actually, he includes an unstructured "goto" type construct. I don't.)

When you get to JavaScript objects you may begin to think, as I do, that this is a very interesting language.

Comments

Comments in JavaScript are the same as comments in C++ and Java.

// This is a comment. The "//" works like Ruby's "#".
foo = expression; // comment here

/* Block comments
are done 
this way. */

Constants

JavaScript has string constants. Like Ruby they may be enclosed in single or double quotes. Unlike Ruby, they are identical—both translate escaped characters.

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
}

Variables

JavaScript variable names follow the the normal pattern: a letter followed by zero or more letters, digits and underscore characters. JavaScript does not use punctuation characters to form special names.

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.

Expressions

Generally, JavaScript and Ruby expressions are the same. The operators are the same with these exceptions.

Statements

There are twelve statements in JavaScript. You already know most of them. First, general remarks.

JavaScript's Statements
Name Example Description
break
while ( condition1 ) {
    ...
    if (condition2) {
        break;
    }
    ...
}
         
Stop a loop or exit a switch. Same as Ruby.
do
do {
    ...
} while (condition);
Loop until a condition becomes true (or until break).
# in Ruby
begin
    code
end while condition
expression
x = expression;
function_call();
        
Evaluate an expression; call function(s). Same as Ruby.
for
for (v in array) {
...
}
        
JavaScript Ruby
for (v in array) { // parens required
    // use array[v] here
}
                    
for v in array # no parens
    # use v here
end
                     
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.

            # in Ruby
            case expr
                when val1 then code for val1
                when val2 then code for val2
                else default code
            end
            
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.

Arrays

JavaScript arrays are comparable to Ruby arrays. Same syntax, same subscripting by array index. JavaScript arrays are also hashes (sets of key/value pairs):

arr['dog'] = 'woof'; 
arr['cat'] = 'meow'; 
alert( arr['dog'] ) // alerts 'woof'

Objects

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).

Events

HTML tags accept JavaScript function calls. Important events include 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>

getElementById(), innerHTML

Every tag can have an id. If I give a tag an id, I place it as the first attribute, so it stands out: <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.

The Lumberman Axes Box

Do you have to Google every task? I started my JavaScripting that way. Wish I'd known this secret. If you grab an HTML element, consider its tag's attributes. For example, the <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.)

Screenshot, JavaScript Console

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.

The Length Axis Dropdown

Any questions? No? OK, get coding. Completely stuck? Look at my code. When you are done, if you choose length axis "blue", the picture will ask "Width Axis?"; the labels for the width axis radio buttons will be "red" and "green"; the values for those <input type=radio> will be "r" and "g"; neither radio button will be checked and the "Thickness Axis:" text will be blank.

The Width Axis Radio Buttons

OK, so how did you do? (That was your first non-trivial JavaScript. I doubt it was easy.)

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?

Important point about switches: The C language created switches in which the code continues, dropping through from one case to the next. If your case ends with a 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 );
    }
If you forget those 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) ).

The Thickness Text

You did set the "Thickness Axis:" text on the width axis button click, didn't you? Good. This one's done.

The Lumberman Lumber Size Box

The <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]' ) returns an array of two numbers. 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:

Portion of syllabus mind map. 

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.

Syllabus with operators folded.  

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.

The Lumberman Length Box

Here, the job is simple. The user types something into the "inches" box, you make sure that there is nothing in the "mm" box, and vice-versa. I'll let you write it. Good luck!

The Lumberman Materials Box

This is a menu. The user chooses one. The old choice is redrawn without highlight. The new choice is highlighted. You almost never program menus anymore; you use pre-programmed menu classes and objects. Well, here's your chance!

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>
The JavaScript starts by creating global variables that hold the name of the selected material and the ID of the selected <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';
    
A <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:

Your code shouldn't be any longer than this list.

The Lumberman Buttons

We are almost done. We need to gather up the data and hand it back to the Ruby that created the WebDialog. This would be very simple if we didn't need to check that the user had provided what was needed.

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.

On into Plugins

A well-mannered Ruby plugin creates and runs from a subdirectory below the 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.

Into the Plugins/lumberman/ Subdirectory

You now have a lumberman.rb file in Plugins/lumberman/. To complete the subdirectory add these files: We are just about ready to rock and roll. All we need now is a lumberman.rb that is smart enough to launch our webdialog.

Launch the Lumberman WebDialog

This one's easy, except that the constructor has a lot of arguments. I'll explain those shortly. For now, edit your 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:

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.

JavaScript to Ruby Communication

To hear from the WebDialog, Ruby needs to provide at least one callback function that JavaScript can use to send it data. More accurately, to send it a string, not to exceed about 2KB, in which the data is contained.

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;
    }
With that final addition, Ruby reports:

And that is cause for celebration! Your WebDialog is gathering the data that Ruby needs to do some modeling.

Making a Board

By now you know enough Ruby to make a board, and you know enough of the SketchUp Ruby API to make a board. Just one little hint and then you are on your own:

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!


The axes box in the Lumberman WebDialog. View of apartment contents. Corner of VisMap Ruby plugin.