Simple example

From WeBWorK
Jump to: navigation, search

Before and After Flash Applet

We take a "stand alone" graphing applet and customize it for use in WW in a few easy steps.

ActionScript code for stand-alone (no WW) version:

On the stage should be an input textbox called txtFun for this version. Hitting the "enter" key after editing this box will update the screen. The ww version will get the expression to graph through a call to the config function, so we will be deleting this input textbox in the subsequent version.

import flashandmath.as3.tools.SimpleGraph;
var sxMin:String = "-5";
var sxMax:String = "5";
var syMin:String = "-5"; 
var syMax:String = "5";
var sFun:String =  txtFun.text;

var g:SimpleGraph = new SimpleGraph(480,360);
g.x = 20;
g.y = 40;
addChild(g);

var spPoint:Sprite = new Sprite();
spPoint.x = 5;
spPoint.y = 5;
spPoint.graphics.lineStyle(1,0);
spPoint.graphics.beginFill(0xAA0000);
spPoint.graphics.drawEllipse(-5,-5,10,10);
spPoint.graphics.endFill();
spPoint.addEventListener(MouseEvent.MOUSE_DOWN, pointClicked)
g.addChildToBoard(spPoint);

stage.addEventListener(KeyboardEvent.KEY_DOWN, keyPressed);
function keyPressed(kevt:KeyboardEvent):void {
   if (kevt.keyCode == Keyboard.ENTER) {
       sFun = txtFun.text;
       drawGraph();
   }
}

function drawGraph():void {
   g.setWindow(sxMin, sxMax, syMin, syMax);
   g.board.drawAxes();
   g.board.drawTicks();
   g.board.drawGrid();
   g.board.addLabels();
   g.graphRectangular(sFun,"x",1,2,0x0000CC);
}

drawGraph();

function pointClicked(mevt:MouseEvent):void {
   stage.addEventListener(MouseEvent.MOUSE_MOVE, pointMoved);
   stage.addEventListener(MouseEvent.MOUSE_UP, pointReleased);
}

function pointMoved(mevt:MouseEvent):void {
   spPoint.x = goodX(g.mouseX);
   spPoint.y = goodY(g.mouseY);
   mevt.updateAfterEvent();
}

function pointReleased(mevt:MouseEvent):void {
   stage.removeEventListener(MouseEvent.MOUSE_MOVE, pointMoved);
   stage.removeEventListener(MouseEvent.MOUSE_UP, pointReleased);
}

function goodX(nx:Number):Number {
   if (nx > 480) {
      return 480;
   }
   if (nx < 0) {
      return 0;
   }
   return nx;
}

function goodY(ny:Number):Number {
   if (ny > 360) {
      return 360;
   }
   if (ny < 0) {
      return 0;
   }
   return ny;
}

ActionScript code for WW version:

Everything discussed below will go into the code above below the initial import statement. We will have to edit the existing code a very small amount to accommodate the fact that the txtFun input textbox is omitted from this version.

First. We add the external interface calls so that WW can communicate with our applet.

ExternalInterface.addCallback("sendData", sendData);
ExternalInterface.addCallback("getXML", getXML);
ExternalInterface.addCallback("setXML", setXML);
ExternalInterface.addCallback("setConfig", setConfig);
ExternalInterface.addCallback("isActive", isActive);
ExternalInterface.addCallback("debug", set_debug);

Second. We need functions and variables that will allow WW to test that the applet is loaded and ready before trying to send data to it. This can be copied verbatim with the applet_name matching the applet name specified in the WW problem file.

var appletName = "PointGraph";
var applet_loaded = 1;   // applet does not need to 
                                           // load additional files 
                                          // it is ready as soon as the applet 
                                          // itself is loaded

function isActive() {
   return(applet_loaded);
}
//  reportAppletLoaded can be used 
// to proactively report to the webwork question that
// the applet is loaded.  (Call with loaded=1)

function reportAppletLoaded(appletName, loaded) {
   ExternalInterface.call("applet_loaded", appletName, loaded);
}

Third. We need functions and variables that will allow WW to interact with the applet while in debug mode. The applet does not have to incorporate any independent debug features, but the set_debug function/variable must be in place.

We have also added a text window which can receive debugging messages placed with the feedback function. Typically these messages simply indicate whether a subroutine is being executed. This can be very useful in determining whether messages from the webwork question are coming through to the applet.

var debugMode:Boolean = false;
function set_debug(n:Number) {
   debugMode = (n)? true:false;
}
// create error reporting window for use in debug mode

var txtFeedback:TextArea = new TextArea();
txtFeedback.text ="no message yet";
txtFeedback.move(100,0);
txtFeedback.setSize(400,100);
txtFeedback.visible = debugMode; // visible in debugMode only

function feedback(str:String):void{
       if (debugMode) {
              txtFeedback.appendText( "\n" + str);
       }
}


Fourth. The functions config, getXML, setXML, and sendData are instrumental in the communication between the applet and WW for normal interaction by students. The most important things about these functions is (a) consistency between getXML and setXML, and (2) documentation of config and sendData within the WW problem file. Each function is explained a little more with comments below:

/* 
Data string that initializes the applet problem should have the form <xml expr='expression in x' />
*/
function setConfig(str:String) {
   var xmlData:XML = XML( str );
  feedback("received xml string "+xmlData.toString() ); //debug check
   spPoint.x = 5;
   spPoint.y = 5;

   if (str == "") {
      sFun = "0";
      return;
   }	
   sFun = xmlData.@expr.toString();
   drawGraph();
}

// setXML takes the xml string from the webwork question and uses it to  initialize the applet

function setXML(str:String):void {   
   var xmlData:XML = XML( str );

   if (str == "") {
      spPoint.x = 5;
      spPoint.y = 5;
      return;
   }
   spPoint.x =  g.board.xtoPix(Number(xmlData.pt.@xval.toString()));
   spPoint.y =  g.board.ytoPix(Number(xmlData.pt.@yval.toString()));
}
 
// getXML gets the xml string that describes the applet state

function getXML():String {
   var xmlString:String;
   xmlString = "<xml> <pt xval = '" + String(g.board.xfromPix(spPoint.x)) + "'  yval = '" + String(g.board.yfromPix(spPoint.y)) + "' /> </xml>";
   return xmlString;
}

// The getAnswer function produces the user-friendly data to be copied into the answer box, eventually to be graded

function getAnswer():String {
   return ("(" + String(g.board.xfromPix(spPoint.x)) + "," + String(g.board.yfromPix(spPoint.y)) + ")" );
}

Finally. You will have to edit the original example to remove the ability of changing the expression in the input text box and consequently the need to have the "enter" key trigger a new graph. For completeness, we include the entire remaining (shorter) code:

var sxMin:String = "-5";
var sxMax:String = "5";
var syMin:String = "-5";
var syMax:String = "5";
var sFun:String;

var g:SimpleGraph = new SimpleGraph(480,360);
g.x = 20;
g.y = 40;
addChild(g);
g.setWindow(sxMin, sxMax, syMin, syMax);

var spPoint:Sprite = new Sprite();
spPoint.x = 5;
spPoint.y = 5;
spPoint.graphics.lineStyle(1,0);
spPoint.graphics.beginFill(0xAA0000);
spPoint.graphics.drawEllipse(-5,-5,10,10);
spPoint.graphics.endFill();
g.addChildToBoard(spPoint);

function drawGraph():void {
   g.setWindow(sxMin, sxMax, syMin, syMax);
   g.board.drawAxes();
   g.board.drawTicks();
   g.board.drawGrid();
   g.board.addLabels();
   g.graphRectangular(sFun,"x",1,2,0x0000CC);
}

////////////////////////////////////////////////////
spPoint.addEventListener(MouseEvent.MOUSE_DOWN, pointClicked);

function pointClicked(mevt:MouseEvent):void {
   stage.addEventListener(MouseEvent.MOUSE_MOVE, pointMoved);
   stage.addEventListener(MouseEvent.MOUSE_UP, pointReleased);
}

function pointMoved(mevt:MouseEvent):void {
   spPoint.x = goodX(g.mouseX);
   spPoint.y = goodY(g.mouseY);
   mevt.updateAfterEvent();
}

function pointReleased(mevt:MouseEvent):void {
   stage.removeEventListener(MouseEvent.MOUSE_MOVE, pointMoved);
   stage.removeEventListener(MouseEvent.MOUSE_UP, pointReleased);
}

function goodX(nx:Number):Number {
   if (nx > 480) {
       return 480;
   }
   if (nx < 0) {
       return 0;
   }
   return nx;
}

function goodY(ny:Number):Number {
   if (ny > 360) {
       return 360;
   }
   if (ny < 0) {
       return 0;
   }
   return ny;
}
follow us