Thursday, May 26, 2011
The requirement is to show a Find dialog to the user along with a Find button so that the user must perform a search for a record before they are allowed to enter a new one. Once the user clicks the Find button, an "Add New" button is made visible. Upon clicking the "Add New" button, the Find dialog is hidden altogether and an Add dialog is displayed. In ExtraView, this is all done within an Add layout.
Setup
- The Add screen on which this is occurring should only contain the 2 layouts: one for the Find dialog, and one for the Add dialog. We will call this the parent Add screen.
- The Find dialog contains a search layout which contains the Find button, and an Add New button, which is a simple, user-defined Custom field with the _BUTTON or _BTN suffix.
- In addition, a "Return to Find Dialog" button can be added to the Add dialog to switch the user's view back to the Find dialog only.
Layout Cell Attributes
- Create 2 data dictionary user-defined text fields to act as visibility flags: one for the Add New button and one for the Find dialog. Let's call them FIND_DIALOG_FLAG and ADD_BUTTON_FLAG. (There are no special properties to the _FLAG suffix -- it's only a naming convention.) Their default value should be 1.
- Place the Find dialog flag field on the parent Add screen. Add the following layout cell attributes:
- VISIBLE_IF ID# equals -99. This ensures the flag will never be visible. For debugging, you may want to make the field visible by removing this attribute.
- Place the Add Button flag field on the Find dialog and apply the same attribute as above.
- On the parent Add screen, apply the following attribute to the Find dialog layout:
- VISIBLE_IF FIND_DIALOG_FLAG equals 1
- On the parent Add screen, apply the following attribute to the Add dialog:
- VISIBLE_IF FIND_DIALOG_FLAG equals 0
- On the Add New button within the Find dialog, add the following attribute:
- VISIBLE_IF ADD_BUTTON_FLAG equals 1
Business Rules
In the business rules for the Area within which the above Add screen resides, add the following:
- Within the load directive, add:
- FIND_A_PROGRAM_FLAG='1';
- ADD_BUTTON_FLAG='0';
- Within the refresh directive, add:
- if(FIND_BTN.{changed})
{
FIND_DIALOG_FLAG='1';
ADD_BUTTON_FLAG='1';
}
if(ADD_NEW_BTN.{changed})
{
FIND_DIALOG_FLAG='0';
ADD_BUTTON_FLAG='0';
}
if(RETURN_TO_FIND_BTN.{changed})
{
FIND_DIALOG_FLAG='1';
ADD_BUTTON_FLAG='0';
}
Wednesday, June 16, 2010
I am currently working on a web application where the user steps (forward or back) through a series of pages with "Next" and "Previous" buttons, entering data until they reach a page with the "Finish" button. Until finished, all data is stored in Session state, then sent to the mainframe database via web services at the end of the process.
Some of the pages display data from previous pages in order to collect additional information. These pages can never be cached because they are different for every user. For pages that don't display this dynamic data, they can be cached, but only the first time they load. After that, the data that was previously entered needs to be displayed. This requires Page_Load to fire, which means the page can't be cached at that point.
A couple of weeks ago, I knew almost nothing about implementing page caching. Now I still don't know much, but I know a little bit, and here is the solution that I developed with the help of others on my team and a lot of reading and trial-and-error.
We have a base page class defined from which all pages inherit. In this class I have defined a method that sets the caching settings programmatically. For pages that can be cached, they call this base page method in their Page_Load event within a if(!IsPostBack) block, which ensures that only the page itself gets cached, not the data on the page.
if(!IsPostBack)
{
...
SetCacheSettings();
...
}
protected void SetCacheSettings()
{
Response.Cache.AddValidationCallback(new HttpCacheValidateHandler(Validate), null);
Response.Cache.SetExpires(DateTime.Now.AddHours(1));
Response.Cache.SetSlidingExpiration(true);
Response.Cache.SetValidUntilExpires(true);
Response.Cache.SetCacheability(HttpCacheability.ServerAndNoCache);
}
The AddValidationCallback sets up an HttpCacheValidateHandler method called Validate which runs logic when a cached page is requested. The Validate method signature is standard for this method type.
public static void Validate(HttpContext context, Object data, ref HttpValidationStatus status)
{
string visited = context.Request.QueryString["v"];
if (visited != null && "1".Equals(visited))
{
status = HttpValidationStatus.IgnoreThisRequest; //force a page load
}
else
{
status = HttpValidationStatus.Valid; //load from cache
}
}
I am using the HttpValidationStatus values IgnoreThisRequest or Valid which forces the Page_Load event method to run or allows the page to load from cache, respectively. Which one is set depends on the value in the querystring. The value in the querystring is set up on each page in the "Next" and "Previous" button click event methods based on whether the page that the button click is taking the user to has any data on it or not.
bool hasData = HasPageBeenVisited(url);
if (hasData)
{
url += VISITED;
}
Response.Redirect(url);
The HasPageBeenVisited method determines whether the destination page has any data on it by checking one of its required data fields. (I won't include it here because it is very system-dependent.) VISITED is a string constant containing "?v=1" and gets appended to the url if the destination page has been visited.
The reason this logic is within the "Next" and "Previous" button click event methods is because 1) the Validate method is static which doesn't allow it to access non-static data such as the data fields for a particular page, and 2) at the time at which the Validate method runs, either the data has not yet been deserialized from Session state or is not available (different AppDomain?) because anytime I accessed the Session state information from the Validate method, it was always empty.
Thursday, January 21, 2010
When exporting a Crystal Reports Basic for Visual Studio 2008 report in Excel, HTML, or PDF/RTF/Word format, you can specify formatting options for the export using an ExcelFormatOptions, HTMLFormatOptions, or PdfRtfWordFormatOptions object, respectively. Each FormatOptions object has 3 properties that can be set: UsePageRange, FirstPageNumber, and LastPageNumber. By default, their values are:
- UsePageRange = False
- FirstPageNumber = 1
- LastPageNumber = 1
If you want to display all pages of the report, do not specify any of these properties (in fact, you do not need a FormatOptions object at all). If UsePageRange = False, FirstPageNumber and LastPageNumber are ignored, and again the object is not used and can be removed. If UsePageRange = True, both FirstPageNumber and LastPageNumber are considered. If one or both of them are not specifed, their default values are used. As their names suggest, FirstPageNumber specifies on which page of the report to start and LastPageNumber specifies on which page to end.
I learned about these options and how they work because I had UsePageRange = True, FirstPageNumber = 1, and LastPageNumber unspecified, and only one of the pages of the report was displaying. By removing all of the options, all pages of the report then displayed. I also found that the Crystal Reports documentation was very terse on this subject, so with some experimenting, I compiled the above information. It may also apply to other FormatOptions objects with these properties in other versions of Crystal Reports, but the above was all I had to work with.
Thursday, December 31, 2009
The DropDownList control's SelectedIndexChanged event relies not only on the selected Index changing, but also on the Value of the new item being different than the previously-selected one. The bottom line is that all Values in a DropDownList should be unique if you are using its SelectedIndexChanged event.
Monday, December 14, 2009
Right off the bat I'll tell you I am a beginner when it comes to CodeSmith. Basically, the tech lead set up the templates and I know how to run them and that's about it.
That being said, we figured out how to get around some strange behavior with CodeSmith today that I thought I'd write a brief note about. I was going through a legacy project to convert all .NET Double and SQL Server FLOAT data types to Decimal (on both platforms) for greater precision. I changed all the fields in the tables and re-ran CodeSmith, but for some reason, it was not changing the data types for the handful of Views we had. (We have CodeSmith set up to use a View instead of the table to generate the business object class if the View has the same name as the table.)
By editing the Views and simply running the automatically-generated ALTER statement, the CodeSmith class generation started working correctly. It appears as though SQL Server keeps an internal representation of the data types of the columns represented by the View that doesn't get updated after a data type change until after the View is regenerated, and CodeSmith refers to those data types instead of referencing the underlying table's data types.
Wednesday, December 02, 2009
I have been using Agent Ransack for a few years and find it to be a really useful tool for not only finding files quickly but seeing the line in the file that contains the text you were searching for in the first place (assuming you're searching for files based on text contained in those files). Agent Ransack added itself to the folder context menu in Windows Explorer (the pop-up menu that appears when you right-click a folder) in XP, but in Vista it didn't. I'm not sure why. Determined to get this working again, I did a little research and got it working!
What I found was based on a post here: http://www.tech-recipes.com/rx/1176/how-to-add-menu-items-under-right-click-or-context-menu/ and on running AgentRansack.exe /? at the command line to find all of the command line options for it.
Steps:
- Open regedit.exe with the Run dialog on the Start menu.
- Navigate to HKEY_CLASSES_ROOT\Folder\shell
- (If the "shell" key doesn't exist within the Folder key, create it by right-clicking on the Folder key, and selecting New - key. Do not include the quotes in the name.)
- Create a key within the "shell" key named "Agent Ransack" or "Open with Agent Ransack", whatever you prefer. (No quotes.)
- Create a key within the key in Step 4 named "command". (No quotes.)
- Within this new "command" key, double-click the "Default" Value in the right pane, and place the following in its "Value data" field: "C:\Program Files (x86)\Mythicsoft\Agent Ransack\AgentRansack.exe" "-d" "%1" (this time, include the quotes).
- Click OK. This change should take effect immediately. No need for a reboot. Close regedit and enjoy!
Note: My setup is a 64-bit version of Vista.
Thursday, September 10, 2009
If you just need a quick and dirty DropDownList of, let's say, Month values, you can simply build an ArrayList to populate it with, then bind the DropDownList to the ArrayList. This is simple and examples are easy to find on the 'net, but here's the trick: when creating ListItems for your ArrayList, give the ListItems both a Text and Value. Then set the DataTextField of the DropDownList to "Text", and the DataValueField to "Value". Then both the Text and Value properties from the ArrayList are populated into the appropriate fields of the DropDownList!
Here's the VB code:
Dim months As New ArrayList
months.Add(New ListItem("January", "1"))
months.Add(New ListItem("February", "2"))
months.Add(New ListItem("March", "3"))
months.Add(New ListItem("April", "4"))
months.Add(New ListItem("May", "5"))
months.Add(New ListItem("June", "6"))
months.Add(New ListItem("July", "7"))
months.Add(New ListItem("August", "8"))
months.Add(New ListItem("September", "9"))
months.Add(New ListItem("October", "10"))
months.Add(New ListItem("November", "11"))
months.Add(New ListItem("December", "12"))
ddlMonth.DataSource = months
ddlMonth.DataTextField = "Text"
ddlMonth.DataValueField = "Value"
ddlMonth.DataBind()
Thursday, November 06, 2008
I recently wrote an internal memo identifying accessibility problems with Telerik RadWindows for the website I'm currently working on. I post the items here hoping that they might be useful for some of you out there. Some of this is specific to our environment (IE6) and design standards but may be helpful for you too. Some apply to other modal window implementations as well.
1. Modal windows complicate the page. One of the primary axioms of designing for accessibility is simplification. I also experienced this first-hand when working with a visually impaired user: more stuff on the screen makes it harder to create a mental picture of the screen, and to navigate around and work with the screen.
2. Cursor focus manipulation is inconsistent. The screen reader reads from the cursor focus location on the screen. In order to help the user not to get "lost" on the page, when for example, checking a checkbox causes the screen to refresh, we programmatically force the cursor focus to be placed back on that same checkbox. Otherwise the focus would just land at the top of the browser in the address bar and since the user can't see that a page refresh happened, the user gets confused as to why the cursor moved there. In modal windows, even though the program code specifies that the focus should be set at a specific location, it works inconsistently for some reason.
3. The same focus manipulation issue exists for our standard for error/success messages, but is even worse. Setting the focus requires something clickable to set the focus on. Our standard design for messages places text on the screen which does not accept the focus directly. Setting the focus on a clickable page element directly above the message works, but only maybe 10% of the time for some reason. This is not only inconsistent, but dramatically so. As a workaround, we implemented messages in another popup (using Javascript) when they occur on a modal window. This helped, but is now inconsistent with messages elsewhere in the system.
4. Accessible solutions for modal windows are inconsistent with our standard page designs. Another axiom of designing for accessibility is consistency. If all of the pages behave and are laid out the same, even if the pages are complex, it becomes easier to use the system because the pages are familiar. As noted above, and in other situations as well, the guidelines that were developed for accessibility within modal windows are different than for standard pages.
5. RadWindows are not truly modal. Non-visual users are keyboard users. When working with a RadWindow, users can still tab onto and work with the parent page with the keyboard. The definition of a modal window states that the user cannot interact with the parent page while the modal window is active. Unfortunately, Telerik's attempt at implementing a modal window in the web environment falls short. It works for mouse users, as you cannot click on the parent page or any of its controls with the mouse while the modal window is active. But when using only the keyboard, you can tab onto the parent page, edit values on the parent page, and even submit information on the parent page while the modal window is active.
6. Implementing accessibility for modal windows is more time consuming than for standard pages. In addition to all of our standard developer guidelines for implementing accessibility, there are a number of additional guidelines for working with modal windows. Additional testing must also occur in development and QA.
We did some brief testing with the ASP.NET AJAX ModalPopup control and with modal windows that included some complexity, it had problem #5 as well, even though the online example does not. The ModalPopup would also have problems #1 and #6. I'm not sure about #2 or #3, and #4 is possible if accessibility was not considered in your page designs from the ground up.
Wednesday, August 27, 2008
When using TFS within Visual Studio, if you undo pending changes on an ASPX file by right-clicking on that file in Solution Explorer and selecting "Undo Pending Changes", pay close attention to the dialog box about what files are going to have their changes undone. TFS will not only undo changes on the ASPX file you selected, but will also undo changes for the code-beside file. I have lost my work a couple of times with this feature. Unfortunately, I don't know of a way to change that behavior. If you do, let me know!
Monday, August 25, 2008
In the last couple of shops I've worked that actually have documentation, what I have tried to accomplish with the developer documentation for the code was to be able to reproduce the code from the documentation. The documentation should give an overview of the page, contain all of the requirements and business rules in the Analyst documentation, and address all of the events that can occur on the page. How are fields populated? What gets validated when a button is clicked? If the application has multiple layers, all layers should be documented including the database stored procedures, user-defined functions, etc.
It's good to be able to do this type of documentation prior to release to the QA folks. Reason being, by giving the code this close of a second look, you often find stuff that was forgotten, is incorrect, or could clearly be done a lot better. Then it becomes part of the development process itself, not just an afterthought.
This of course is in addition to method header comments, self-documenting code (for example, naming variables and methods in such a way as to make the code tell you what it does just by reading the code itself), other comments in the code for more complex code or assumptions, avoiding the use of "magic numbers" (numbers in the code that have meaning but are not explained), and other coding best practices.