A wizard is a familiar user-interface mechanism for scrolling through a sequence of steps of more generally scrolling through a series of content. In this post I illustrate how this functionality can be implemented through the use of an object-oriented framework.
Object Orientation
Most programming tasks require operations to be performed on data. In object-oriented programming, the goal is to create re-usable packages that combine both data and the functions that operate on the data. In JSL this can conveniently be done using namespaces to act as the container for both data and function definitions.
In the code below a namespace is used to deliver the object-oriented functionality for a navigation wizard. The code pattern is based on the code-base described by Drew Foglia, Principal Software Developer with JMP.
The Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 |
NavigatorClass = New Namespace("NavigatorClass-pega-analytics.co.uk"); NavigatorClass:newNavigator = function({maxIndex=1},{default local}, // create a unique namespace for this object instance ns = newNamespace(); ns:maxIndex = maxIndex; // maximum number of steps in the wizard ns:index = 1; // current index of the navigation wizard ns:traceFlow = 0; // enables diagnostic output when true ns:getIndex = evalexpr(function({}, {this=namespace(expr(ns<<getname))}, if (this:traceFlow,print("getIndex()")); return(this:index); )); ns:enableTraceMode = evalexpr(function({enable}, {this=namespace(expr(ns<<getname))}, // enable tracing of method calls if (this:traceFlow,print("enableTraceMode()")); this:traceFlow = enable; )); ns:nextStep = evalexpr(function({}, {this=namespace(expr(ns<<getname))}, // move forward to the next step of the wizard if (this:traceFlow,print("nextStep()")); this:index++; this:index = min(this:index,this:maxIndex); this:_enableButtons(); this:_updateContent(); )); ns:previousStep = evalexpr(function({}, {this=namespace(expr(ns<<getname))}, // move backward to the next step of the wizard if (this:traceFlow,print("previousStep()")); this:index--; this:index = max(this:index,1); this:_enableButtons(); this:_updateContent(); )); ns:_enableButtons = evalexpr(function({}, {this=namespace(expr(ns<<getname))}, // perform logic to correctly enable/disable navigation buttons if (this:traceFlow,print("_enableButtons()")); if (this:index >= this:maxIndex, this:btnNext << enable(0) , this:btnNext << enable(1) ); if (this:index <=1, this:btnBack << enable(0) , this:btnBack << enable(1) ) )); ns:_reportContent = evalexpr(function({}, {this=namespace(expr(ns<<getname)),content}, // display box content for the current step of the navigation wizard if (this:traceFlow,print("_reportContent()")); content = Text Box("content here for report " || char(this:index)); return(content); )); ns:_updateContent = evalexpr(function({}, {this=namespace(expr(ns<<getname))}, // refresh content if (this:traceFlow,print("_updateContent()")); this:pb << set title(char(this:index) || " of " || char(this:maxIndex)); this:bb << delete; this:pb << sib append( this:bb = Border Box(top(20), this:_reportContent() ) ); )); ns:_createNavigatorWindow = evalexpr(function({}, {this=namespace(expr(ns<<getname)),win,self}, // create the window containing navigation controls and content if (this:traceFlow,print("_createNavigatorWindow()")); win = New Window("Navigator", Border Box(left(20),right(20),bottom(10), V List Box( this:pb = Panel Box( char(this:index) || " of " || char(this:maxIndex) , Border Box(top(0),bottom(0),left(10),right(10), H List Box( this:btnBack = Button Box("< back",, <<Enable(0)), this:btnNext = Button Box("next >"), Text Box(" ") ) ) ), this:bb = Border Box(Top(20), this:_reportContent() ) ) ) ); self = this << get name; eval(parse(evalinsert("\[ this:btnNext << set script( this=namespace("^self^"); this:nextStep() ); this:btnBack << set script( this=namespace("^self^"); this:previousStep() ); ]\"))); return(win); )); // on creating a new instance of the class, render the navigation window ns:_createNavigatorWindow(); // final step - return a reference to the class instance in the form of a namespace 'object' return(ns); ); |
Using the Code
Assume the code is written in a file “NavigatorClass.jsl”. To use the class definition I would write the following code:
1 2 3 |
Include("NavigatorClass.jsl"); numSteps = 5; nav = NavigatorClass:newNavigator(numSteps); |
Currently the content that is displayed in the wizard is defined by a single line of code within the function _reportContent:
1 2 3 4 5 6 |
ns:_reportContent = evalexpr(function({}, {this=namespace(expr(ns<<getname)),content}, // display box content for the current step of the navigation wizard if (this:traceFlow,print("_reportContent()")); content = Text Box("content here for report " || char(this:index)); return(content); )); |
To make the navigator useful the code needs to be customised to display relevant content.
Final Thoughts
The code pattern for each function within the namespace looks complex. This complexity is required so that the object has the concept of self. This becomes apparent when you create 2 two simultaneous instances of the navigation wizard. Even though both wizards use the functions nextStep and previousStep to navigation, they successfully navigate the correct window. For simpler code implementations this would not be the case – you could click the next button in one window and t he content would change in a second window!
One thought on “Navigation Wizard”