Extending Ready-Made Applications |
You can modify the functionality of SAS/EIS viewers by subclassing the viewers and then adding your own components or methods to them. You can use this technique to add logos, additional titles, or additional controls to the window. The subclassed viewer can then provide methods to control the interaction between the new controls and the SAS/EIS components.
The steps for subclassing a viewer are:
Determine which existing SAS/EIS class you want to subclass.
Determine the new methods, new attributes, and overridden methods.
Build the new class, specifying the existing SAS/EIS class as the parent. Add the new controls in the component definition window. Add the new methods, new attributes, and method overrides.
Build a SAS/EIS application, specifying your viewer as the new viewer.
The following example shows you how to create a viewer that displays a table and an area on the screen that enables you to enter notes for the currently selected data cell. The notes can then be saved and redisplayed as needed.
Note: This example has been simplified to illustrate the technique that you will use. You will need to extend the example code with additional error checking in order for it to be a complete customization.
Step 1: Determine which existing SAS/EIS class you want to subclass |
Because you are using a table and adding components, you will be subclassing the SASHELP.EIS.TABLET_V.CLASS viewer. You can view a list of the viewers that are supplied by SAS software by going to the build window for the object, clicking , and then selecting the down arrow next to the Viewer field.
Step 2: Determine which new components you will be adding |
First, look at which components are currently on the viewer. To do this, follow these steps:
Select the Attributes node on the left-hand side of the Class Editor.
In the table on the right-hand side of the Class Editor, select the componentDefinition attribute.
The TABLET_V class contains a table editor and an area for titles. You will be adding an Extended Text Entry field below the table editor.
Step 3: Determine the new methods, new attributes, and overridden methods |
To provide this functionality you will
Override the _postInit method to load any existing notes for a catalog entry into an SLIST entry. To simplify this example, assume that the cell notes are stored in SASUSER.EISNOTE.<applname>.SLIST, where applname is the 8-character application name.
Override the _refresh method to change the cell border for cells that contain notes.
Override the _objectLabel method to process a single click on a cell. If the user selects a cell that does not contain a note, the note area will be cleared. If the user selects a cell that currently contains a stored note, the stored note will be loaded into the note field. The selected cell will have its border changed. If there was an "active" cell and a new cell is selected, the contents of the note field will be saved.
Override the _term method to save the notes to the SLIST catalog entry.
Add a new method, updateNote, which will update an internal list with the contents of the note field.
New attributes are the following:
is a list that will be loaded in _postInit with the contents of the SLIST catalog entry. At _term, the contents of this list will be written back out to the catalog entry. updateNote will update the contents of the list.
is a list that will contain the address for the current note.
is a number indicating the position within notesList of the current note.
Step 4: Build the new class, adding the new controls, methods, and attributes |
To create the new class, type BUILD SASUSER.EIS.TABNOTE.CLASS on the command line. When the Class Editor appears, enter SASHELP.EIS.TABLET_V.CLASS for the parent and press ENTER. At this point, you can modify the attributes and methods of the class. See Class Editor for more information on creating new classes with the Class Editor.
Add the attributes that you defined in Step 3 by following these steps:
Select the Attributes node on the left-hand side of the Class Editor.
On the right-hand side of the Class Editor, open the pop-up menu over the table and select New Attribute. Enter the following information for the new attributes:
Attribute Name | Type | Auto Create | Initial Value |
---|---|---|---|
notesList | List | No | |
currentNote | List | No | |
notePosition | Number | 0 |
Override the methods by following these steps:
Select the Methods node on the left-hand side of the Class Editor.
On the right-hand side of the Class Editor, find the method that you want to override in the table. Open the pop-up menu over the method and select Override.
Override the following methods:
Note: Be sure to override both of the _term methods.
Add the new Notes field to the composite. Composites are custom components that consist of at least two existing components. For more information on composites, refer to Combining Components to Create Composites. To add the Notes field, follow these steps:
Select the Attributes node on the left-hand side of the Class Editor.
On the right-hand side of the Class Editor, open the pop-up menu over the table and select the componentDefinition attribute.
In the Composite Definition window, select the composite outline. Then select Layout Attach Define attachments.
In the Define Attachment window, remove the attachment from the table to the bottom of the region. Click .
Make the composite area bigger by dragging out the bottom of the region.
Add the Extended Text Entry (ETE) field to the bottom of the region. Resize the field.
Open the pop-up menu over the ETE and select Object Attributes. Change the name of the field to CELLNOTE. Click .
Go back to the Define Attachment window by selecting Layout Attach Define Attachment.
In the Define Attachment window, add the following attachments:
After adding the attachments, close the Define Attachment window and save the class.
Step 5: Code the methods |
Use the following code for the methods:
/* Copyright(c) 2004 by SAS Institute Inc., Cary, NC USA */ /*-------------------------------------------------------+ | Set SCL variable lengths and declare the model as an object. +-------------------------------------------------------*/ length class_name $ 32 applname $ 8 status $ 1 rc 8 ; dcl object modelid _frame_ cellnote; /*-------------------------------------------------------+ | Initialize attributes to remove warnings. +-------------------------------------------------------*/ modelid = modelid; _self_ = _self_; _frame_ = _frame_; cellnote = cellnote; postInit: method; call super(_self_,'_postInit_'); /*-------------------------------------------------------+ | Get the application name from the frame's arguments. +-------------------------------------------------------*/ args = makelist(); _frame_._getArglist(args); applname = getitemc(args,9); rc = dellist(args); /*-------------------------------------------------------+ | Check to see if sasuser.eisnote.<applname>.slist exists. | If not, gray the cellnote field. | If so, load the list. +-------------------------------------------------------*/ fullname = 'sasuser.eisnote.' || trim(applname) || '.slist'; _self_.notesList = makelist(); if ^cexist(fullname) then cellnote._gray(); else rc = fillist('slist',fullname,_self_.notesList); endmethod; refresh: method; /*-------------------------------------------------------+ | For each sublist, get the class address list and set | the border style, width, and color. | NOTE: This example will only work at a single drill | level. Additional code should be added to allow | for drill downs, expands, and so on. +-------------------------------------------------------*/ do i = 1 to listlen(_self_.notesList); sublist = getiteml(_self_.notesList,i); classAddress = getniteml(sublist,'address',1,1,0); if listlen(classAddress) <= 0 then continue; rc = sortlist(classAddress,'NAME'); notCurrent = comparelist(classAddress,_self_.currentNote); _self_.modelid._setCellBorderStyle(classAddress,'ALL', 'SOLID'); if notCurrent then _self_.modelid._setCellBorderColor(classAddress,'ALL', 'blue'); else _self_.modelid._setCellBorderColor(classAddress,'ALL', 'red'); _self_.modelid._setCellBorderWidth(classAddress,'ALL', 'thick'); end; call super(_self_,'_refresh'); endmethod; objlabel: method; call super(_self_,'_object_label_'); /*-------------------------------------------------------+ | Get the frame status. If not single click, return. +-------------------------------------------------------*/ _frame_._getStatus(status); if status ^= '' then return; /*-------------------------------------------------------+ | Check to see if you are in the data or on a class value. | Return if not in the data. +-------------------------------------------------------*/ if _self_.modelid.in_data ^= 'Y' then return; /*-------------------------------------------------------+ | Get the current active value. +-------------------------------------------------------*/ cell_list = makelist(); hlist = makelist(); _self_.modelid._getActiveValue(cell_list,class_name,hlist); rc = dellist(hlist); /*-------------------------------------------------------+ | Is the selected cell the current note cell? | If the same, return. +-------------------------------------------------------*/ rc = sortlist(cell_list,'NAME'); newCell = comparelist(cell_list,_self_.currentNote); if ^newCell then return; /*-------------------------------------------------------+ | Update the notes list for the current note. +-------------------------------------------------------*/ _self_.updateNote(); /*-------------------------------------------------------+ | Make the selected cell the current note cell. | Turn the cell border for the new current to red. +-------------------------------------------------------*/ _self_.currentNote = cell_list; _self_.modelid._setCellBorderStyle(_self_.currentNote, 'ALL','SOLID'); _self_.modelid._setCellBorderWidth(_self_.currentNote, 'ALL','thick'); _self_.modelid._setCellBorderColor(_self_.currentNote, 'ALL','RED'); /*-------------------------------------------------------+ | See if there is a stored note for this cell. +-------------------------------------------------------*/ found = 0; do i = 1 to listlen(_self_.notesList) until(found); sublist = getiteml(_self_.notesList,i); classList = getniteml(sublist,'address'); rc = sortlist(classList,'NAME'); newNote = comparelist(classList,_self_.currentNote); if ^newNote then found = 1; end; /*-------------------------------------------------------+ | If not found, clear the text entry field and | set _self_.notePosition to zero. +-------------------------------------------------------*/ if ^found then do; cellnote._clear(); _self_.notePosition = 0; end; /*-------------------------------------------------------+ | If found, set the note position and set the text. +-------------------------------------------------------*/ else do; _self_.notePosition = i; textList = getniteml(sublist,'text'); cellnote._setValue(textList); end; /*-------------------------------------------------------+ | Ungray the text entry fields. +-------------------------------------------------------*/ cellnote._ungray(); endmethod; updateNote: method; /*-------------------------------------------------------+ | Different cell. Save the current note, if any. +-------------------------------------------------------*/ textList = makelist(); cellnote._getValue(textList); hasText = 0; do i = 1 to listlen(textList) until(hasText); item = getitemc(textList,i); if item ^= '' then hasText = 1; end; if hasText then do; /*-------------------------------------------------------+ | Is this note already on the list? +-------------------------------------------------------*/ if _self_.notePosition then do; sublist = getiteml(_self_.notesList,_self_.notePosition); oldlist = getniteml(sublist,'text'); rc = dellist(oldlist); rc = setniteml(sublist,textlist,'text'); end; /*-------------------------------------------------------+ | Not on the list. Create a new sublist. +-------------------------------------------------------*/ else do; sublist = makelist(); rc = insertl(_self_.notesList,sublist,-1); rc = insertl(sublist,_self_.currentNote,-1,'address'); rc = insertl(sublist,textList,-1,'text'); end; /*-------------------------------------------------------+ | Return the cell borders to blue. +-------------------------------------------------------*/ if listlen(_self_.currentNote) > 0 then do; _self_.modelid._setCellBorderStyle(_self_.currentNote, 'ALL','SOLID'); _self_.modelid._setCellBorderWidth(_self_.currentNote, 'ALL','thick'); _self_.modelid._setCellBorderColor(_self_.currentNote, 'ALL','blue'); end; end; else do; /*-------------------------------------------------------+ | Note contains no text. If looking at a note | that was stored, delete it from the list. +-------------------------------------------------------*/ if _self_.notePosition then do; sublist = getiteml(_self_.notesList,_self_.notePosition); rc = dellist(sublist,'Y'); rc = delitem(_self_.notesList,_self_.notePosition); end; /*-------------------------------------------------------+ | Return the cell border to black, none. +-------------------------------------------------------*/ if listlen(_self_.currentNote) > 0 then do; _self_.modelid._setCellBorderStyle(_self_.currentNote, 'ALL','NONE'); _self_.modelid._setCellBorderColor(_self_.currentNote, 'ALL','BLACK'); _self_.modelid._setCellBorderWidth(_self_.currentNote, 'ALL','NORMAL'); end; end; endmethod; term: method optional = yorn $1; /*-------------------------------------------------------+ | Update the current note, if any. +-------------------------------------------------------*/ _self_.updateNote(); /*-------------------------------------------------------+ | Get the application name from the frame's arguments. +-------------------------------------------------------*/ args = makelist(); _frame_._getArglist(args); applname = getitemc(args,9); rc = dellist(args); fullname = 'sasuser.eisnote.' || trim(applname) || '.slist'; /*-------------------------------------------------------+ | Save the notesList, if there are notes. +-------------------------------------------------------*/ if listlen(_self_.notesList) > 0 then do; descript = 'Notes for ' || trim(applname); rc = savelist('slist',fullname,_self_.notesList,0,descript); end; /*-------------------------------------------------------+ | If there are no notes, delete the old .slist, if any. +-------------------------------------------------------*/ else do; if cexist(fullname) then rc = delete(fullname,'catalog'); end; rc = dellist(_self_.notesList,'Y'); rc = dellist(_self_.currentNote); call super(_self_,'_term_',yorn); endmethod;
Step 6: Build an EIS application, specifying a new viewer |
To build an EIS and specify a new viewer, follow these steps:
Double-click Build EIS in the EIS Main Menu to display the Build EIS window. Click .
In the Add window, select Multidimensional Reports, if not already selected, from the Object Databases list box, and select Multidimensional report from the Objects list box. Click .
In the Multidimensional Report window, type the name and description for your report. Select a table and the columns for your report. Click to display the Advanced window.
In the Advanced window, specify SASUSER.EIS.TABNOTE.CLASS as the Viewer.
Step 7: Test the application |
To test the application, click in the Build EIS window. The new viewer should appear. The table will contain the report, and the notes field should be initially grayed. Select any cell in the table and then enter a note. To test whether the notes are saved, end the report and test it again. The cells for which you entered notes should be highlighted. When you select any of the cells, the corresponding note should be displayed in the notes field.
In this example, you added components to an existing SAS/EIS viewer. You can use this same technique to create a new composite that contains one or more SAS/EIS base viewers. Or you could subclass a base (non-composite) viewer to override or add methods to the base functionality.
Copyright © 2007 by SAS Institute Inc., Cary, NC, USA. All rights reserved.