<?xml version='1.0' encoding='UTF-8'?>

<?xml-stylesheet href="./_c74_tut.xsl" type="text/xsl"?>

<chapter name="Max JS Tutorial 4: Designing User Interfaces in JavaScript">

<setdocpatch name="04jJavascriptUI" patch="04jJavascriptUI.maxpat"/>
<previous name="javascriptchapter03">Tasks, Arguments and Globals</previous>
<parent name="00_maxindex">Max Tutorials</parent>

<indexinfo category="JavaScript" title="Designing User Interfaces">Designing graphical user interfaces with JavaScript</indexinfo>

<h1>JavaScript Tutorial 4: Designing User Interfaces in JavaScript</h1>

<h2>Introduction</h2>

<p>The <o>jsui</o> object allows you use JavaScript to design graphical user interface objects for use in the Max environment. The JavaScript implementation for the <o>jsui</o> object is similar to that used in the <o>js</o> object, with an added API that supports two- and three-dimensional vector graphics drawn with OpenGL commands. It also includes methods for handling mouse interaction in the <o>jsui</o> object window.</p>

<p>In addition to the advantages provided by JavaScript, <o>jsui</o> provides a number of built-in features that make UI development flexible:
<ul>
<li><o>jsui</o> objects draw their geometries relative to the size of the <o>jsui</o> object box; resizing a <o>jsui</o> object will correctly resize all of the drawn elements inside of it.</li>
<li><o>jsui</o> objects work with a vector graphics language (OpenGL) that supports a wide variety of simple shape and drawing primitives. In addition, a number of higher-level graphics functions are available. The <o>jsui</o> object can also perform anti-aliasing on the image to give you as smooth an object as possible, though this comes with a decrease in performance.</li>
<li>The <o>jsui</o> object allows you to draw a scene that exceeds the boundaries of the object box. By adjusting the camera orientation in the OpenGL space, you can create and manage different ‘views’ of the same UI object.
In addition to using the <o>jsui</o> object for user-interface design, one could use the object simply as an OpenGL graphics engine built into the Max patcher for algorithmic drawing operations.</li></ul></p>

<p>This Tutorial assumes that you’ve already looked at the other JavaScript Tutorials. The <o>jsui</o> object bases most of its graphics language on OpenGL functions, the specifics of which are beyond the scope of this Tutorial. The OpenGL ‘Redbook’ is the standard reference for these functions. An online version is available at:

<a href="http://www.opengl.org/documentation/red_book/">http://www.opengl.org/documentation/red_book/</a>   </p>

<p>The OpenGL API supported by <o>jsui</o> is contained in an object called <o>jsui</o>  <b>sketch</b>. This object understands most OpenGL commands and symbolic constants. Converting between OpenGL code (e.g. as given in C in the ‘Redbook’ code examples) and <b>sketch</b> methods and properties for <o>jsui</o> JavaScript code is quite straightforward if you observe the following guidelines:
<ul>
<li>All OpenGL commands are lowercase in the <o>jsui</o> sketch object, e.g. <m>glColor()</m> becomes <m>sketch.glcolor()</m>.</li>
<li>OpenGL symbolic constants, in addition to being lowercase, lose their ‘GL_’ prefix, so that <m>GL_CLIP_PLANE1</m> becomes <m>clip_plane1()</m>, for example.</li></ul></p>

<p>A number of higher-level drawing and shape commands are available which may speed up user interface development. The <o>jsui</o> sketch reference found in the <link type="vignette" module="js" name="javascriptinmax">Javascript in Max</link> guide (and the help patch for the <o>jsui</o> object) contain lists of these commands.</p>

<p>To open the tutorial patch, click on the <b>Open Tutorial</b> button in the upper right-hand corner of the documentation window.</p>

<h2><o>jsui</o> in Action</h2>

<p>Take a look at our tutorial patcher. You will see a <o>jsui</o> object containing a grid of light red circles against a green background. Clicking on a circle inside the object will change the circle’s color to a darker red. Clicking on the same circle again will change the color back to light red.</p>

<p>Click on some circles so that they are highlighted (in dark red). At the right of the patch, manipulate some of the <o>slider</o> objects above the <o>router</o> object. Note the correspondence between which circles are clicked and which <o>slider</o> objects below the <o>router</o> echo the values from above. Click on the <o>message</o> box labeled clear attached to the <o>jsui</o> object. The circles should all go to light red and the <o>router</o> object will no longer pass messages from the <o>slider</o> objects above. Click on the <o>message</o> box containing the list <m>0 0 1, 1 1 1</m>, etc. The <o>jsui</o> object should update to show a diagonal row. The <o>router</o> object will now pass messages from the first <o>slider</o> above to the first <o>slider</o> below, and so on.</p>

<p>Our <o>jsui</o> object uses JavaScript code to emulate some of the functionality of the Max <o>matrixctrl</o> object. The columns represent the input to the <o>router</o> object; the rows specify the output. Our <o>jsui</o> communicates with the <o>router</o> object by sending lists (in the format input output state) that tell the <o>router</o> object to pass messages received at an inlet to all the appropriate outlets given the <o>jsui</o> object’s current configuration.</p>

<p>Like the <o>js</o> object, the <o>jsui</o> object gets its program from a file written in JavaScript saved somewhere in the search path. Because it is a graphical object, there is no object box to type in the name of the file. Instead, we set the JavaScript source file using the <o>jsui</o> object’s <b>Inspector</b>.</p>

<p>Unlock the tutorial patch and highlight the <o>jsui</o> object. Under the <b>Object</b> menu, select <b>Get Info…</b>. An Inspector should appear with the name of our <o>jsui</o> source file (‘mymatrix.js’) in the text field labeled ‘JavaScript File.’ </p>

<p>You can also set the size of the object in the Inspector, as well as turn on or off a border around the object. Disabling the object border, combined with setting the background color of your Max patch to match that of your <o>jsui</o> object, can help you design a seamless user interface.</p>

<h2>The Drawing Code</h2>

<p>The <o>jsui</o> object is graphical, so double-clicking the object will not open the text editor as it does with the <o>js</o> object. Instead, click the <o>message</o> box labeled <m>open</m>. The text editor containing our JavaScript file (‘mymatrix.<o>js</o>’) will appear. Our JavaScript file is saved on disk in the same folder as the tutorial patch.</p>

<p>As with a <o>js</o> script, our code for the <o>jsui</o> object starts with a global block that allows us to define inlets and outlets for the object and global variables for our code. It is also where we type commands that we want to occur when the object is initialized:

<pre><code language="javascript">// inlets and outlets
inlets = 1;
outlets = 1;
// global variables
var ncols=4; // default columns
var nrows=4; // default rows
var vbrgb = [0.8,1.,0.8,0.5];
var vmrgb = [0.9,0.5,0.5,0.75];
var vfrgb = [1.,0.,0.2,1.];
// initialize state array
var state = new Array(8);
for(var i=0;i &lt; 8;i++)
{
   state[i] = new Array(8);
   for(var j=0;j &lt; 8;j++)
   {
      state[i][j] = 0;
   }
}
// set up <o>jsui</o> defaults to 2d
sketch.default2d();
// initialize graphics
draw();
refresh();</code></pre>
</p>

<p>Our JavaScript code defines two global variables (<m>ncols</m> and <m>nrows</m>) which can be accessed by all functions, as well as a number of global <b>Array</b> objects that define colors for the drawing and a state array that we will use to hold information about which circles are ‘on’ and which are ‘off’ in our user interface.</p>

<p>Multi-dimensional arrays in JavaScript are allocated by an <b>Array</b> object in which each element of the Array is itself an Array object (and so on, if more than two dimensions are needed). This may seem somewhat unusual if you’ve worked in other programming languages where multi-dimensional arrays can be declared directly (e.g. C). The <m>for()</m> loops in our global block accomplish this allocation and initialize all the elements of the state array to <m>0</m>. Once a multi-dimensional array is created, it can be referenced using common bracket notation, e.g. <m>state[4][2]</m>.</p>

<p>Following our variable and array declarations, we find three commands that refer to the graphical behavior of the <o>jsui</o> object. The first, <m>sketch.default2d()</m>, tells our <o>jsui</o> object to initialize a number of default behaviors with the assumption that we will be giving it graphics commands for a two-dimensional scene. It sets a default view upon the OpenGL rendering context and performs a number of utility routines to make it easy for us to simply start placing graphical elements in the window. The <m>draw()</m> command (which could be named anything) refers to our main graphics function which we write to contain all the commands needed to draw the user interface of the <o>jsui</o> object. The <m>refresh()</m> command copies the OpenGL backbuffer (where the drawing is done initially to prevent flicker) to the actual screen display. Commenting out the <m>refresh()</m> command will prevent our <o>jsui</o> object from ever showing us anything.</p>

<p>Below the global block, examine the <m>draw()</m> function. This is the function that provides <o>jsui</o> with all the commands it needs to draw our screen interface:

<pre><code language="javascript">// draw -- main graphics function
function draw()
{
   with (sketch)
   {
      // set how the polygons are rendered
      glclearcolor(vbrgb[0],vbrgb[1],vbrgb[2],vbrgb[3]); // set the clear color
      glclear(); // erase the background
      var colstep=2./ncols; // how much to move over per column
      var rowstep=2./nrows; // how much to move over per row
      for(var i=0;i &lt; ncols;i++) // iterate through the columns
      {
         for(var j=0;j &lt; nrows;j++) // iterate through the rows
         {
            moveto((i*colstep + colstep/2)-1.0, 1.0 - (j*rowstep + rowstep/2), 0.); // move the drawing point
            if(state[i][j]) // set 'on' color
            {
               glcolor(vfrgb[0],vfrgb[1],vfrgb[2],vfrgb[3]);
            }
            else // set 'off' color (midway between vbrgb and vfrgb)
            {
               glcolor(vmrgb[0],vmrgb[1],vmrgb[2],vmrgb[3]);
            }
            circle(0.7/Math.max(nrows,ncols)); // draw the circle
         }
      }
   }
}</code></pre>
</p>

<p>The graphics commands (everything beginning with ‘gl’, as well as the <m>circle()</m> command) are all methods and properties of the <b>sketch</b> object, which encapsulates most of the OpenGL API, much as the <b>Math</b> object encapsulates most common math functions.</p>

<p>The JavaScript <m>with()</m> statement allows us to use properties and methods belonging to an object (in this case the <b>sketch</b> object that provides us with our OpenGL functionality) without having to reference ‘sketch’ in every command. Without the <m>with()</m>, we would have to write <m>sketch.glcolor()</m> instead of <m>glcolor()</m>, <m>sketch.circle()</m> instead of <m>circle()</m>, etc. This useful trick could also be implemented in functions that rely heavily on other objects (e.g. <b>Task</b> or <b>Patcher</b>).</p>

<p>Our <m>draw()</m> function sets some default drawing behaviors and clears the window with the color defined by the <m>vbrgb</m> array. It then iterates through our state array based on how many columns (<m>ncols</m>) and rows (<m>nrows</m>) we’ve defined for our object. If a particular state element is <m>0</m> (off), it draws a circle in the color defined by <m>vmrgb</m>. If an element is <m>1</m> (on), the circle is drawn in the <m>vfrgb</m> color. The position of the circles is determined by the number of rows and columns, and is based on boundaries of our OpenGL world, which are set to be between <m>–1.0</m> and <m>1.0</m> on both axes (i.e. the middle of our <o>jsui</o> window in OpenGL coordinates is <m>0</m>, <m>0</m>).</p>

<p>The size of the world is not limited to coordinates in the range of <m>–1.0</m> to <m>1.0</m>; our default viewport merely sets us at the center of the scene whose y range is <m>–1.0</m> to <m>1.0</m> and whose x range is scaled based on the aspect ration of the object. Because our object box happens to be square, our ranges are the same on both axes. Changing the viewport (by manipulating the position of our virtual “camera”, for example) will change what coordinates are visible in the <o>jsui</o> object’s box.</p>

<h2>Setting Parameters</h2>

<p>In the Tutorial patch, change the <o>number</o> box objects that set the rows and columns. Note that the object will dynamically add up to eight rows and columns of circles based on those values. Look at the <m>rows()</m> and <m>cols()</m> functions in the JavaScript code. Note that they call a <m>bang()</m> function after setting their variables.

<pre><code language="javascript">// rows -- change number of rows in <o>jsui</o>
function rows(val)
{
   if(arguments.length)
   {
      nrows=arguments[0];
      <m>bang</m>(); // draw and refresh display
   }
}
// cols -- change number of columns is <o>jsui</o>
function cols(val)
{
   if(arguments.length)
   {
      ncols=arguments[0];
      <m>bang</m>(); // draw and refresh display
   }
}
</code></pre></p>

<p>The <m>bang()</m> function, which we call after nearly every change made to the object from Max (including mouse events), simply calls <m>draw()</m> and <m>refresh()</m> as we did in our global block, causing the <o>jsui</o> object to update its window to reflect any graphical changes.

<pre><code language="javascript">// bang -- draw and refresh display
function bang()
{
   draw();
   refresh();
}
</code></pre></p>

<p>By only doing the drawing when necessary, we are able to reduce the amount of processor time the object uses.</p>

<p>In the tutorial patcher, change the <o>swatch</o> objects that set the <m>frgb</m> and <m>brgb</m> messages to our <o>jsui</o> object. Look at the corresponding functions (<m>frgb()</m> and <m>brgb()</m>) in the JavaScript code. Note that the array for the ‘off’ circle’s color (<m>vmrgb</m>) is midway between the colors set by frgb and brgb messages:

<pre><code language="javascript">// frgb -- change foreground (clicked) circle color
function frgb(r,g,b)
{
   vfrgb[0] = r/255.;
   vfrgb[1] = g/255.;
   vfrgb[2] = b/255.;
   vmrgb[0] = 0.5*(vfrgb[0]+vbrgb[0]);
   vmrgb[1] = 0.5*(vfrgb[1]+vbrgb[1]);
   vmrgb[2] = 0.5*(vfrgb[2]+vbrgb[2]);
   <m>bang</m>(); // draw and refresh display
}
// brgb -- change background color
function brgb(r,g,b)
{
   vbrgb[0] = r/255.;
   vbrgb[1] = g/255.;
   vbrgb[2] = b/255.;
   vmrgb[0] = 0.5*(vfrgb[0]+vbrgb[0]);
   vmrgb[1] = 0.5*(vfrgb[1]+vbrgb[1]);
   vmrgb[2] = 0.5*(vfrgb[2]+vbrgb[2]);
   <m>bang</m>(); // draw and refresh display
}</code></pre>
</p>

<p>Color in OpenGL is represented as four floating-point values in the range <m>0.0</m>-<m>1.0</m>, corresponding to the red, green, blue, and alpha (transparency) amounts, respectively. This is in contrast to many video systems that commonly refer to color in the integer <m>0</m>-<m>255</m> (with no alpha value). Most of the work in our <m>frgb()</m> and <m>brgb()</m> functions is to convert from the latter (used by the <o>swatch</o> object) into the former (understood by the <o>jsui</o> object).</p>

<h2>Mouse Interaction</h2>

<p>Unlock the tutorial patcher and resize the <o>jsui</o> object (the circles should resize dynamically). Lock the patcher and notice that the mouse clicks still change the states of the correct circles. Lock the patch again and look at the <m>onclick()</m> function in the JavaScript code.</p>

<p>The <m>onclick()</m>, <m>ondblclick()</m>, and <m>ondrag()</m> functions, when defined, tell our <o>jsui</o> object what to do when a user clicks, double-clicks, or drags the mouse across the object. The function is called with arguments for where in the object’s window the action occurred, as well as a number of flags (such as whether the mouse was depressed, the state of the shift key, etc.). In our onclick() function, we only use first two arguments, corresponding to the x and the y of the mouse click.

<pre><code language="javascript">// onclick -- deal with mouse click event
function onclick(x,y)
{
   var worldx = sketch.screentoworld(x,y)[0];
   var worldy = sketch.screentoworld(x,y)[1];
   var colwidth = 2./ncols; // width of a column, in world coordinates
   var rowheight = 2./nrows; // width of a row, in world coordinates
   var x_click = Math.floor((worldx+1.)/colwidth); // which column we clicked
   var y_click = Math.floor((1.-worldy)/rowheight); // which row we clicked
   state[x_click][y_click] = !state[x_click][y_click]; // flip the state of the clicked point
   outlet(0, x_click, y_click, state[x_click][y_click]); // output the coordinates and state of the clicked point
   <m>bang</m>(); // draw and refresh display
}</code></pre>
</p>

<p>Our OpenGL graphics world is defined in terms of floating-point coordinates (in our case, between <m>–1.0</m> and <m>1.0</m>). The <o>jsui</o> mouse functions return coordinates based on which <i>pixel</i> (counting away from the upper left-hand corner of the object box) the mouse event occurred. We need to convert between these two systems (of world coordinates and screen coordinates, respectively) in order to properly evaluate the mouse events for our grid of circles. The sketch methods <m>worldtoscreen()</m> and <m>screentoworld()</m> perform these conversions for us:

   <pre><code language="javascript">worldx = sketch.screentoworld(x,y)[0];
worldy = sketch.screentoworld(x,y)[1];</code></pre>
   </p>

<p>Once we know the width and height where we clicked, we can subdivide it based on how many circles we have on each axis:

   <pre><code language="javascript">colwidth = 2./ncols; // width of a column, in world coordinates
rowheight = 2./nrows; // width of a row, in world coordinates</code></pre>
   </p>

<p>We can then plug in the coordinates of the mouse click to figure out which circle we clicked nearest to:

   <pre><code language="javascript">x_click = Math.floor((worldx+1.)/colwidth); // which column we clicked
y_click = Math.floor((1.-worldy)/rowheight); // which row we clicked</code></pre>
   </p>

<p>We then reverse the element of the state array corresponding to the circle we clicked:

   <pre><code language="javascript">state[x_click][y_click] = !state[x_click][y_click];</code></pre>
   </p>

<p>After we’ve set the state array correctly, we send out a list corresponding to the change out our <o>jsui</o> object’s outlet into Max. We then <m>bang</m> our own <o>jsui</o> object, updating the graphics to reflect the change:

   <pre><code language="javascript">outlet(0, x_click, y_click, state[x_click][y_click]);
bang();</code></pre>
   </p>

<p>Note that we have set our <m>onclick()</m> function to be local, so that it can’t be triggered from an <m>onclick</m> message sent from our Max patch.</p>

<p>Familiarize yourself with the JavaScript code and how it relates to the behavior of the <o>jsui</o> object in the patcher. Click on the <o>toggle</o> object to activate the <o>metro</o> object at the right of the patch. This will simulate some input from the <o>slider</o> objects. Place <m>post()</m> statements in the JavaScript code to help navigate the values as they are passed from mouse click to list output.</p>

<h2>Summary</h2>

<p>The <o>jsui</o> object is a powerful tool to allow you to design and implement customizable user interfaces using JavaScript as a programming language. The key points in the program involve the main drawing function (which defines a sequence of commands to describe the <o>jsui</o> object’s graphical display) and the mouse interaction functions <m>onclick()</m>, <m>ondblclick()</m>, and <m>ondrag()</m>. Important things to note are the differences in color representation (floating point vs. integer) and spatial coordinates (floating-point world coordinates vs. Cartesian pixel coordinates) between the OpenGL API used in the <o>jsui</o> sketch object and the Max environment, respectively.</p>

<h2>Code Listing</h2>

<p>
<pre><code language="javascript">// mymatrix.<o>js</o>
//
// simulates a simple grid of clickable widgets (a la matrixctrl)
//
// rld, 5.04
//
// inlets and outlets
inlets = 1;
outlets = 1;
// global variables
var ncols=4; // default columns
var nrows=4; // default rows
var vbrgb = [0.8,1.,0.8,0.5];
var vmrgb = [0.9,0.5,0.5,0.75];
var vfrgb = [1.,0.,0.2,1.];
// initialize state array
var state = new Array(8);
for(var i=0;i &lt; 8;i++)
{
   state[i] = new Array(8);
   for(var j=0;j &lt; 8;j++)
   {
      state[i][j] = 0;
   }
}
// set up <o>jsui</o> defaults to 2d
sketch.default2d();
// initialize graphics
draw();
refresh();
// draw -- main graphics function
function draw()
{
   with (sketch)
   {
      // set how the polygons are rendered
      glclearcolor(vbrgb[0],vbrgb[1],vbrgb[2],vbrgb[3]); // set the clear color
      glclear(); // erase the background
      var colstep=2./ncols; // how much to move over per column
      var rowstep=2./nrows; // how much to move over per row

      for(var i=0;i &lt; ncols;i++) // iterate through the columns
      {
         for(var j=0;j &lt; nrows;j++) // iterate through the rows
         {
            moveto((i*colstep + colstep/2)-1.0, 1.0 - (j*rowstep + rowstep/2), 0.); // move the drawing point
            if(state[i][j]) // set 'on' color
            {
               glcolor(vfrgb[0],vfrgb[1],vfrgb[2],vfrgb[3]);
            }
            else // set 'off' color (midway between vbrgb and vfrgb)
            {
               glcolor(vmrgb[0],vmrgb[1],vmrgb[2],vmrgb[3]);
            }
            circle(0.7/Math.max(nrows,ncols)); // draw the circle
         }
      }
   }
}
// bang -- draw and refresh display
function bang()
{
   draw();
   refresh();
}
// rows -- change number of rows in <o>jsui</o>
function rows(val)
{
   if(arguments.length)
   {
      nrows=arguments[0];
      bang(); // draw and refresh display
   }
}
// cols -- change number of columns is <o>jsui</o>
function cols(val)
{
   if(arguments.length)
   {
      ncols=arguments[0];
      bang(); // draw and refresh display
   }
}

// list -- update our state to respond to a change from Max
function list(v)
{
   if(arguments.length==3) // bail if incorrect number of arguments
   {
      state[arguments[0]][arguments[1]]=arguments[2]; // update our internal state based on the list
      outlet(0, arguments[0], arguments[1], arguments[2]); // echo the list out the outlet
   }
   bang(); // draw and refresh display
}
// clear -- wipe the state clean
function clear()
{
   for(var i=0;i &lt; ncols;i++)
   {
      for(var j=0;j &lt; nrows;j++)
      {
         state[i][j]=0; // wipe the state
      }
   }
   outlet(0, “clear”); // clear the <o>router</o> or matrix~ downstream
   bang(); // draw and refresh display
}
// frgb -- change foreground (clicked) sphere color
function frgb(r,g,b)
{
   vfrgb[0] = r/255.;
   vfrgb[1] = g/255.;
   vfrgb[2] = b/255.;
   vmrgb[0] = 0.5*(vfrgb[0]+vbrgb[0]);
   vmrgb[1] = 0.5*(vfrgb[1]+vbrgb[1]);
   vmrgb[2] = 0.5*(vfrgb[2]+vbrgb[2]);
   bang(); // draw and refresh display
}
// brgb -- change background color
function brgb(r,g,b)
{
   vbrgb[0] = r/255.;
   vbrgb[1] = g/255.;
   vbrgb[2] = b/255.;
   vmrgb[0] = 0.5*(vfrgb[0]+vbrgb[0]);
   vmrgb[1] = 0.5*(vfrgb[1]+vbrgb[1]);
   vmrgb[2] = 0.5*(vfrgb[2]+vbrgb[2]);
   bang(); // draw and refresh display
}
// onresize -- deal with a resized <o>jsui</o> box
function onresize(w,h)
{
   bang(); // draw and refresh display
}
onresize.local = 1; // make function private to prevent triggering from Max

// onclick -- deal with mouse click event
function onclick(x,y)
{
   var worldx = sketch.screentoworld(x,y)[0];
   var worldy = sketch.screentoworld(x,y)[1];
   var colwidth = 2./ncols; // width of a column, in world coordinates
   var rowheight = 2./nrows; // width of a row, in world coordinates
   var x_click = Math.floor((worldx+1.)/colwidth); // which column we clicked
   var y_click = Math.floor((1.-worldy)/rowheight); // which row we clicked
   state[x_click][y_click] = !state[x_click][y_click]; // flip the state of the clicked point
   outlet(0, x_click, y_click, state[x_click][y_click]); // output the coordinates and state of the clicked point
   bang(); // draw and refresh display
}
onclick.local = 1; // make function private to prevent triggering from Max
// ondblclick -- pass buck to onclick()
function ondblclick(x,y)
{
   onclick(x,y);
}
ondblclick.local = 1; // make function private to prevent triggering from Max</code></pre>
</p>

<seealsolist>
<seealso name="jsui">JavaScript UI object</seealso>
<seealso name="swatch">Color <o>swatch</o> for RGB color selection and display</seealso>
<seealso name="jsintro" type="vignette" module="js" >javascriptinmax</seealso>
</seealsolist>

</chapter>
