<@component name="Wizard"> The wizard framework control enables the user to create wizard control in a descriptive way via Xml.

A wizard let users to perform tasks that otherwise be more complicated and take longer. Wizard offers:

Simplification when the steps for completing a task are very complex. Support when expertise is required. Mental relief when the user has to pay attention to large number of associated factors.

A wizard accomplishes these goals by:

Dividing the task to into just a few logical steps. Offering the user sensible defaults values for selection. Making decision for the user so they can only perform the task in the order of the specified steps. Activating the wizard

The wizard framework is activated by executing the MODAL global function.

MODAL('core.lib.wizard.htm',{src:'com.sap.mypackage:myWizardPage.xml',...});

The parameters for the wizard page are:

Param name |Type |Description src | String |(required) Url for wizard definition file. width |Integer | The width of the wizard window. height | Integer |The height of the wizard window. title | String |The title of the wizard window. Implementation Issues

You can access the wizard methods and properties by the $WIZ prefix.

In order to implement a wizard you should follow this steps:

Define the wizard pages and the navigation between them, see the next section for the xml definition of a wizard xml. For each wizard page implement an appropriate smartform editor in xml format(see @lib:smartform for How To define an editor in xml format). When opening the wizard (via MODAL global functions ) add to the MODAL the src param pointing to the wizard page.

The finish page is the default next page of all the pages that don’t have any NEXTPAGE tag attached to them, The finish page collects all the data from the pages the user has navigated in. This action is done automatically if you follow the next steps:

In the smartform xml file, you add a child element called ‘showInSummary’ with the value ~true to any editor element. If the id of the editor element is also a property of the data object of the page. Data binding

Every page has it’s own data object, you can define that data object in a declarative way via the xml(for details please see the structure of wizard xml file). This data object is attached automatically to the data property of the smartform, this enables for data binding of the smartform to operate correctly on page data object. Please note , that for the data binding to operate the id of the editor element should be identical for the property name of the data.

For example:

For the following editor definition: . . . . . . . . There should be a corresponding page definition: true . . . Error handling

Error handling could be done by the following steps:

Gather one or more errors by executing the ~$WIZ.addError('error description') method. Exit from the current trail by executing the ~$WIZ.abort() method. Wizard page definition (XML format) ? #Define the callback for the wizard ? #Define a callback function that will be called at the start up of the wizard ? #Define a callback function that will be called at the ending of the wizard ? #Define a callback function that will be called when the user press the cancel button #Define the wizard page #Title of the page <src/> #A url for smartform editor in xml format, see @lib:smartform. ? <description/> #Description for the page. </PROPERTIES> <DATA> #Define the page data structure. <child1/> #Child tag name represent the data item name , and the value of the child represent the # default value of the date item </DATA> ? <CALLBACKS> #Define callback functions for the page. ? <onExit/> ? <onEnter/> ? <onEnterBack/> ? <onExitBack/> </CALLBACKS> * <NEXTPAGE id="2" default="[true|false]" /> #Declare the next pages for the current page, default attribute is optional and if absent then the first next page is the default page. </PAGE> </WIZARD> </sample> </remarks> <copyright>(c) SAP AG 2003-2006. All rights reserved.</copyright> </component@> <HEAD> <META HTTP-EQUIV="MSThemeCompatible" CONTENT="no"> #DEPENDENCIES[ lib:Basic.js lib:EnumMgr.js ] #USING[lib:Xml.js] #USING[lib:Box.htc] #USING[lib:Button.htc] #USING[lib:Choice.htc] #USING[lib:Dialog.htc] #USING[lib:SmartForm.htc] <SCRIPT languague="JavaScript" > var MAXW=WPARAM('width')||600, MAXH=WPARAM('height')||400; WSETUP(WPARAM('title')||'Wizard', MAXW, MAXH); /// internal variables /////////////////////////////// var $WIZ = new Controller(); var _onEvent = false; /////////////////////////////////////////////////////// /////////// Controller class function Controller(){ var base = this ; // properties this.header = null ; // A reference to the header editor. this.history = []; // the path the user as chosen. this.pages = {} ; //A key value collection ,key= pageId ,value=page; this.nextPageId = null ; // Id of the next page id. this.currPage = null ; // a reference to the current page. this.trailBackground = WPARAM('backgroundSrc') || '#URL[~skin:images.drawing.gif]' ; this.src = WPARAM('src') ; this.headerSrc = '#URL[xml.wizardHeader.xml]'; this.finishPageSrc = '#URL[xml.wizardFinishPage.xml]'; this.finishPageId = 'WIZARD_FINISH_PAGE'; this.doc = document ; // callbacks this.onStart = null; this.onFinish = null; // buttons this.btnNext = null; this.btnBack = null; this.btnFinish = null; this.btnCancel = null; this.errorList = []; this.Error = function(str){ base.addError(str); this.description = str ; this.number = 0 ; } } ///////////////////////////////////////////////////////// ///////// Initialization of Controller <@method scope="private" name="init"> Initializes the controller after the html file has been loaded. </method@> Controller.prototype.init = function(){ var base = this; this.btnNext = btnNext; this.btnBack = btnBack; this.btnFinish = btnFinish; this.btnCancel = btnCancel; this.errorArea = _error; this.form = _form; _image.src = this.trailBackground ; this.header = this.form.loadEditor(this.headerSrc); this.defaultVisibleFn = function(date,def){ if ( def.id != $WIZ.currPage.getEditor().id )return false; return true; }; this.doc.body.onunload = function(){ base.form.fullReset(); delete $WIZ ; }; } ////////////////////////////////////////////////// /////////Public method of Controller. <@method name="start" scope="private" > Starts the wizard. The first page is the first page that is defined in the xml file. </method@> Controller.prototype.start= function() { try{ if(_onEvent) return; this.disableButtons(); var tmp = this.currPage; this.currPage = null; if (this.onStart) this.onStart(); this.currPage = tmp; if (!this.currPage) return false ; this.history[0] = this.currPage ; this.configButtons(); this.setNextPageId(this.currPage.defaultPageId) raiseEvent(this.currPage, 'onEnter'); var A = []; A.push(this.currPage.getEditor().id); this.refresh(A); return true; }catch(e){ if (e.constructor != $WIZ.Error){ var msg = 'Could not start the wizard, ' + e.description + ', please review your code and try again'; #LOG[4,msg] $WIZ.addError('#TEXT[XMSG_WIZARD_ACTION_FAILED]'); } this.endWizard(); } } <@method name="next" scope="private" > Advances to the next page in the current trail <remarks>Do not use this function inside callbacks function ,This function assume that all the callbacks function has been already called. To change the next page then use the setNextPageId method. </remarks> </method@> Controller.prototype.next= function(){ try{ if(_onEvent) return; this.disableButtons(); // handle next action. var A =[]; raiseEvent(this.currPage, 'onExit'); A.push(this.currPage.getEditor().id); if (!this.jump(this.nextPageId ,true)) return; A.push(this.currPage.getEditor().id); this.configButtons(); raiseEvent(this.currPage, 'onEnter'); this.refresh(A); }catch(e){ if (e.constructor != $WIZ.Error){ var msg = 'Could not execute next action, ' + e.description + ', Please review your code and try again'; #LOG[4,msg] $WIZ.addError('#TEXT[XMSG_WIZARD_ACTION_FAILED]'); } this.endWizard(); } } <@method name="back" scope="private"> Returns to the previous page within the current trail <remarks>Do not use this function inside callbacks function ,This function assume that all the callbacks function has been already called. </remarks> </method@> Controller.prototype.back = function (){ try{ if(_onEvent) return; this.disableButtons(); var index = this.history.length -2 ; if (index < 0 ) return ; var backPage = this.history[index]; if (!backPage) return; // handle back action. var A = []; raiseEvent(this.currPage, 'onExitBack'); A.push(this.currPage.getEditor().id); if (!this.jump(backPage.id,false)) return; A.push(this.currPage.getEditor().id); this.configButtons(); raiseEvent(this.currPage, 'onEnterBack'); this.refresh(A); }catch(e){ if (e.constructor != $WIZ.Error){ var msg = 'Could not execute back action, ' + e.description + ', Please review your code and try again'; #LOG[4,msg] $WIZ.addError('#TEXT[XMSG_WIZARD_ACTION_FAILED]'); } this.endWizard(); } } <@method name="finish" scope="private"> Finishes the current trail. <remarks>Do not use this function inside callbacks function ,This function assume that all the callbacks function has been already called. To change the next page then use the setNextPageId method. </remarks> </method@> Controller.prototype.finish = function(){ try{ if(_onEvent) return; this.disableButtons(); if (!this.currPage) return; raiseEvent(this.currPage, 'onExit'); var result = null; this.currPage = null; if (this.onFinish) result = this.onFinish(); WCLOSE(result); }catch(e){ if (e.constructor != $WIZ.Error){ var msg = 'Could not execute finish action, ' + e.description + ', Please review your code and try again'; #LOG[4,msg] $WIZ.addError('#TEXT[XMSG_WIZARD_ACTION_FAILED]'); } this.endWizard(); } } <@method name="cancel" scope="private"> Cancels The currnet trail </method@> Controller.prototype.cancel = function(){ if(_onEvent) return; var result = null ; if (this.onCancel) result = this.onCancel(); WCLOSE(result); } <@method name="abort"> Aborts the current trail <param name="desc:s"> The reason that caused the abort action. This message will be displayed to the user.</param> <remarks>Please make sure that this method is not encapsulates inside try catch block</remarks> </method@> Controller.prototype.abort = function(desc){ throw new $WIZ.Error(desc); } <@method name="setNextPageId"> Assigns the next page for the trail <param name="id:s">The ID of the page to go to next</param> </method@> Controller.prototype.setNextPageId = function(id){ // check that the page is defined in the framework if (!(id in this.pages )){ throw new this.Error( 'could not find the next page id specified,'+ ' the id may be misspelled, Please verify the id and try again'); } // check that the page is part of the defined next pages of the current page. var flag = false; for(var i=0 ; i< this.currPage.nextPages.length ; i++){ if ( id == this.currPage.nextPages[i] ){ flag = true; break; } } if(!flag) { var msg = 'The next page you are trying to reach is not defined as a valid next page of the current page'; #LOG[4,msg] throw new this.Error('#TEXT[XMSG_WIZARD_ACTION_FAILED]'); } // check that the page has not been visited yet for (var i=0; i < this.history.length;i++){ if (id == this.history[i].id){ throw new this.Error( 'The next page you are trying to reach has already been visited,'+ ' the id may be misspelled, Please verify the id and try again'); } } this.nextPageId = id; } <@method name="printErrors"> Prints all the errors that were added by the addError method. </method@> Controller.prototype.printErrors = function(){ //this.disableButtons(); var str = ''; for ( var i = 0 ; i < this.errorList.length ; i++){ str += '[E] '+ this.errorList[i] + '<br/>'; } this.errorArea.innerHTML = str ; this.errorList = []; } <@method name="endWizard" scope="private"> Performs all the necesarry task that should be performed in case of terminiation of the wizard in the case of errors. </method@> Controller.prototype.endWizard = function(){ this.disableButtons(); this.printErrors(); } <@method name="addError"> Adds an error to the controller stack <param name="errorMsg:s"> The error message to add </param> <remarks>To print and reset the stack of the Controller use the printErrors method</remarks> </method@> Controller.prototype.addError = function(error){ this.errorList.push(error); } <@method name="getErrors"> Gets an array of all the registered errors in the wizard. <return type="String[]" >An array of errors description</return> </method@> Controller.prototype.getErrors = function(){ return this.errorList; } <@method name="disableButtons"> Disables The Next, Back and Finish buttons. </method@> Controller.prototype.disableButtons = function (){ this.btnBack.enabled = false; this.btnNext.enabled = false; this.btnFinish.enabled = false; } <@method name="enableButtons"> Enables The Next, Back and Finish buttons. </method@> Controller.prototype.enableButtons = function (){ this.btnBack.enabled = true; this.btnNext.enabled = true; this.btnFinish.enabled = true; } <@method name="getTrailEditors" scope="private"> Gets an array of the smartform editors, which the user has navigated through. <return type="@env:KitEditors!EDITOR_DEF[]">An array of of smartform editors </return> </method@> Controller.prototype.getTrailEditors = function(){ var A = []; for(var i=0 ; i < this.history.length ; i++){ A.push(this.history[i].getEditor()); } return A; } <@method name="getWizardData"> Gets a collection of all the data from the pages the user has navigated through, this collection reflects all the data from the data property of the pages. <remarks>If the same property is declared in several pages then only the value that the last page that assigned that value will be shown.</remarks> <return type="{}">Returns a property value collection of all the pages the user has been.</return> </method@> Controller.prototype.getWizardData = function(){ var C = {}; for (var i=0;i<this.history.length ; i++){ var data = this.history[i].data; for ( var prop in data){ C[prop] = data[prop]; } } return C ; } <@method name="getTrailPages"> Gets an array of all the pages, which the user has navigated through. <return type="@WIZARD_PAGE[]" >An array of @WIZARD_PAGE objects</return> </method@> Controller.prototype.getTrailPages = function(){ return this.history; } <@method name="getAllPages"> Gets a collection of all the pages in the wizard. <return type="@WIZARD_PAGE{id}">Return a collection of all the pages of the wizard </return> </method@> Controller.prototype.getAllPages = function(){ return this.pages ; } <@method name="getCurrentPage"> Gets the current page. <return type="@WIZARD_PAGE">Return the current page. </return> </method@> Controller.prototype.getCurrentPage = function(){ return this.currPage; } <@method name="getSmartform"> Gets the main smartform. <return type="@lib:SmartForm">A reference to the smartform defined in the wizard</return> </method@> Controller.prototype.getSmartform = function (){ return this.form; } /////////////////////////////////////////////////////// //////// Private method of Controller. <@method name="jump" scope="private"> Jumps to the specified pageId <param name="pageId:s"> The page id to jump to </param> <param name="forward:b"> Indicates whether the jump is forward or back </param> <return type="b">Returns ~true iff the jump opertaion has succeeded </return> </method@> Controller.prototype.jump = function(pageId , forward) { //PROMPT(); var nextPage = this.pages[pageId]; this.assignNextPage(nextPage); if (forward) this.history[this.history.length] = this.currPage ; else this.history = this.history.slice(0,-1); return true; } <@method name="refresh" scope="private"> Refreshes all the editors specified in ~editors <param name="editors:String[]">An array of editors id to be refreshed.</param> </method@> Controller.prototype.refresh = function(editors){ this.form.data = this.currPage.data ; for(var i=0 ;i< editors.length ; i++){ this.form.repaintEditor(editors[i]); } this.form.repaintEditor(this.header.id); } <@method name="assignNextPage" scope="private"> Updates the current page and the next page due to transiton to another page. <param name="nextPage:s">The ID of the next page</param> </method@> Controller.prototype.assignNextPage = function(nextPage){ this.currPage = nextPage ; if (nextPage.defaultPageId) this.nextPageId = nextPage.defaultPageId ; } <@method name="configButtons" scope="private"> Configures the buttons accourding to the current page. <remarks> Execute this fuction only when the currPage property is updated</remarks> </method@> Controller.prototype.configButtons = function (){ // first page; if ( this.history.length == 1 ){ this.btnNext.enabled = true; this.btnBack.enabled = false; this.btnFinish.enabled = false; // last page }else if (this.currPage.id == this.finishPageId){ this.btnBack.enabled = true; this.btnNext.enabled = false; this.btnFinish.enabled = true; // any other page ; }else{ this.btnBack.enabled = true; this.btnNext.enabled = true; this.btnCancel.enabled = true; this.btnFinish.enabled = false; } } /////////////////////////////////////////////////////// // Controller properties functions /////////////////////////////// <@prop name="trailBackground" scope="private"> Gets the Url for the trail background of the wizard </prop@> Controller.prototype.trailBackground = '' ; <@prop name="finishPageId"> Gets the Finish Page Id. </prop@> Controller.prototype.finishPageId = ''; <@prop name="btnNext" type="@lib:button"> Gets the next button. </prop@> Controller.prototype.btnNext = null; <@prop name="btnBack" type="@lib:button"> Gets the back button. </prop@> Controller.prototype.btnBack = null; <@prop name="btnFinish" type="@lib:button"> Gets the finish button. </prop@> Controller.prototype.btnFinish = null; <@prop name="btnCancel" type="@lib:button"> Gets the cancel button. </prop@> Controller.prototype.btnCancel = null; ////////////////////////////////////////////// // Parser class function Parser(controller){ this.root = null; this.controller = controller; this.src = controller.src; this.xml = GETXML(this.src); if (!this.xml) throw new this.controller.Error("Could not parse xml file,Xml file may be bad formatted, Verify the xml file"); } // The main function for building the wizard UI by extracting the // information from an XML file Parser.prototype.buildWizard = function(){ try{ this.root = this.xml.selectSingleNode('WIZARD'); if (!this.root) throw new this.controller.Error(0,"Could not find WIZARD node, Xml file may be bad formatted, Verify the xml file"); var map = {} ; var props = this.root.selectNodes('PROPERTIES/*'); this.mapProperties(this.controller,this.root,props); props = this.root.selectNodes('CALLBACKS/*'); this.mapProperties(this.controller, this.root,props, true); var pages = this.root.selectNodes('PAGE') || []; for(var i=0; i< pages.length ; i++){ var page = this.parsePage(pages[i]); if (i==0) this.controller.currPage = page ; } this.controller.nextPage = this.controller.currPage.defaultPageId ; var fin = GETXML(this.controller.finishPageSrc); var finishPageNode = fin.selectSingleNode('PAGE'); this.parsePage(finishPageNode); }catch(e){ var msg = 'Could not build wizard, ' + e.description + ', Please review your code and/or xml and try again.'; #LOG[4,msg]; throw new $WIZ.Error('#TEXT[XMSG_WIZARD_OPEN_FAILED]'); } } Parser.prototype.parsePage = function(pageNode){ var page = new Page(this.controller); var props = pageNode.selectNodes('PROPERTIES/*'); this.mapProperties(page,pageNode,props); if (!page.id) page.id = this.controller.doc.uniqueID; props = pageNode.selectNodes('CALLBACKS/*'); this.mapProperties(page,pageNode,props, true); props = pageNode.selectNodes('DATA/*'); this.mapData(page.data,pageNode,props); props = pageNode.selectNodes('NEXTPAGE') || []; var i=0; for ( i=0 ; i< props.length ; i++){ page.nextPages[i] = props[i].getAttribute('id'); var isDefault = props[i].getAttribute('default')|| false; if(isDefault) page.defaultPageId = page.nextPages[i] ; } // If a page has no next page then the its next page will be the finish page. if (page.id != this.controller.finishPageId && i==0 ){ page.nextPages[0] = this.controller.finishPageId; page.defaultPageId =this.controller.finishPageId; } if (page.nextPages[0] > 0 && !page.defaultPageId)page.defaultPageId = page.nextPages[0]; this.controller.pages[page.id] = page ; return page; } Parser.prototype.mapProperties = function (map,node,props,isFunc){ if (!node || !props) return map; var id = node.getAttribute('id'); if (id && !map.id ) map['id'] = id ; return this.populateMap(map,node,props,isFunc) } Parser.prototype.mapData = function (map,node,props,isFunc){ if (!node || !props) return map; return this.populateMap(map,node,props,isFunc); } Parser.prototype.populateMap = function(map,node,props,isFunc){ for ( var i=0;i<props.length; i++){ var name = props[i].tagName ; if (name){ var value = props[i].text ; var type = props[i].getAttribute('type') || null; if (type == 'function' || isFunc ){ try{ value = TRIM(value); if (value) eval('value='+value); else value = null ; }catch(e){ #LOG[4,'Could not evaluate the function in property ' + name + ' in Page '+map.id+', ' + e.description + ', Please review your code and/or xml and try again.']; throw new $WIZ.Error('Could not evaluate the function in property '+ name+ ', ' + e.description + ', Please review your code and/or xml and try again.'); } } map[name] = value; } } return map; } /////////////////////////////////////////////////// ////////// Page class <@struct name="WIZARD_PAGE" group="I. Structures"> Defines a wizard page. <remarks> <title>Wizard page properties Category | Type | Description | Datatype src | STR | Gets the url for the xml editor page | String id | STR | Gets the uniq id of the page | String title | STR | Gets the title of the page | String description | STR | Gets the description of the Page | String data | Object | Gets or sets the data object of the page|Varient Wizard page methods Category | Type | Description | Datatype getEditor() | @env:KitEditors!EDITOR_DEF | Gets a reference to the attached smartform editor of the page | N.A. | | |
function Page(ctl){ this.editor = null; // A reference to the smartform editor element. this.src = ''; // Url for an xml editor page. this.id = null; this.title = ''; this.description = ''; this.defaultPageId = '' ; // id of the default next page. this.nextPages = [] ; //An array of id's of the next pages. this.controller = ctl; this.data = {}; // The data object of the page } ///////////////////////////////////////////////////// ////////// Page pulic methods Page.prototype.getEditor = function(){ if (this.editor) return this.editor; var map = {}; map.visible = this.controller.defaultVisibleFn; this.editor = this.controller.form.loadEditor(this.src, map); return this.editor ; } ///////////////////////////////////////////////////// //// Internal functions function raiseEvent(obj, evt) { try { var fn = eval('obj.'+evt ) ; if (typeof fn == 'function') { _onEvent = true; fn(); _onEvent = false; } } catch(e) { _onEvent = false; var name = obj.constructor || '' ; var id = obj.id || '' ; var msg = 'Could not execute callback function ' + evt + ' on '+ name +' id '+ id+', ' + e.description + ', Please review your code and try again.'; #LOG[4,msg] throw new $WIZ.Error('#TEXT[XMSG_WIZARD_ACTION_FAILED]'); } } /////////////////////////////////////////////////////// // Main initialization functions ////////////////////////// function init(){ try{ $WIZ.init(); var parser = new Parser($WIZ); parser.buildWizard(); $WIZ.start(); }catch(e){ if (e.constructor != $WIZ.Error){ var msg = 'Could not Initialize wizard, ' + e.description + ', Please review your code and try again'; #LOG[4,msg] $WIZ.addError('#TEXT[XMSG_WIZARD_OPEN_FAILED]'); } $WIZ.endWizard(); } } // ====================================== < Back Next > Finish Cancel