Tuesday, November 19, 2013

Browser Identification Insanity

As if programming for IE and Chrome and Safari and Firefox was not challenging enough; a few months back Windows Update brought a change into our development web server that convinced the .Net framework that Chrome, Safari, and other non-IE and non-Firefox browsers, were down level browsers. 

This led me to create the new function ShowClientInfo which displays information about the current browser.



The Version Information, Capabilities Detail, and Screen Information sections show how ASP.NET is treating the client browser.  This is not based upon realtime information from the browser but is looked up from tables in C:\Windows\Microsoft.NET\Framework\v4.0.30319\Config\Browsers based on ASP.NET's parsing of the User Agent string which does come 'live' from the browser, as does the Accept Types and top user language setting.


The CSS Device Type Detection section shows the results of the m2kMobile.css media queries and is only available on mobile devices (when the m2kMobile.css is rendered).  This is useful for verifying which RWD CSS rules are being applied.


The Javascript Diagnostics are based on the qunit.js javascript unit test framework and provide visibility of which versions of libraries are loaded, as well as, unit tests of key Manage 2000 web client javavscript operations along with some performance timings.  These diagnostics can provide a level of confidence in the javascript capabilities and performance of new or unique client hardware, as well as, confirmation of proper javascript library configuration.

So it is a bit of a smorg-as-borg, but hopefully will tell you everything you need to know about what the Manage 2000 8.0 web site thinks about that device you hold in your hand... or wherever.

Thursday, November 7, 2013

Selecting From PC Most Recently Used Files

Taking the last two posts to a final conclusion, can we show the user the most recent PC files they have been working with in most recent order instead of showing their My Documents files in alphabetical order?

The answer is yes; using another CSIDL constant that represents the Recent Files directory, CSIDL_RECENT.

 * 
250* Pick a MRU file 
*
ANSWER = ''
GOSUB SelectFromMRU ;* Cross Reference using PCDirectoryInformation MRU
IF ANSWER # '' THEN
MSO.USER.VARS = '"':DirectoryPath:'\':ANSWER:'"'
CALL SUB.EXECUTE.MSO('2324C', '', '', '')
GO 250; *Let user choose another until none selected
END
*
RETURN
*
SelectFromMRU:* Cross Reference from PC File System MRU
*
DirectoryPath = SHGetFolderPath(CSIDL_RECENT)
Mask = '*.*'
ANSWER = ''
*
* Ask PC for Directory listing
*
HiddenFiles = False;SystemFiles = False;DoCheckSum = False;Recursive = False     
DIR.TEXT = PCDirectoryInformation(DirectoryPath,Mask,HiddenFiles,SystemFiles,DoCheckSum,Recursive)
*
* Format directory listing as OPTION list for OPEN.LIST.BOX
*
OPTION.TEXT = '';OPTION.RETURN.VALUES = ''; OPTION.SORT.KEYS = ''
DIR.ENTRIES.CNT = DCOUNT(DIR.TEXT<1>,@VM)
FOR DIR.IDX = 1 TO DIR.ENTRIES.CNT
DIR.ENTRY = DIR.TEXT<1>
DIR.ENTRY.END = DIR.ENTRY[1]
IF DIR.ENTRY.END # ":" AND DIR.ENTRY.END # "\" AND DIR.ENTRY.END # "/" THEN 
FILE.NAME = GetPCFileName(DIR.ENTRY)
IF FILE.NAME[1,1] # '~' THEN ;* Ignore Temp Files from currently open Office docs
DISPLAY.FILE.NAME = FILE.NAME[1, INDEX(FILE.NAME, ".lnk", 1)-1]
SWAP "~" WITH "_" IN DISPLAY.FILE.NAME
FILE.DATE.STAMP = DIR.TEXT<3>
FILE.TIME.STAMP = DIR.TEXT<4>
IF NOT(NUM(FILE.DATE.STAMP)) THEN FILE.DATE.STAMP = 0
IF NOT(NUM(FILE.TIME.STAMP)) THEN FILE.TIME.STAMP = 0
SORT.KEY = FILE.DATE.STAMP * 86400 + FILE.TIME.STAMP
LOCATE SORT.KEY IN OPTION.SORT.KEYS<1> BY "DR" SETTING POS ELSE
END
INS SORT.KEY BEFORE OPTION.SORT.KEYS
FILE.DATE.STAMP = OCONV(FILE.DATE.STAMP,"D2-")
FILE.TIME.STAMP = OCONV(FILE.TIME.STAMP, "MT")
BYTE.CNT = OCONV(DIR.TEXT<5>, "MD0,")
INS DISPLAY.FILE.NAME:"~":FILE.DATE.STAMP "L#8":"~":FILE.TIME.STAMP "L#8":"~":BYTE.CNT "L#15" BEFORE OPTION.TEXT<pos>
INS FILE.NAME BEFORE OPTION.RETURN.VALUES<pos>
END
END
NEXT
IF OPTION.RETURN.VALUES = '' THEN RETURN;* No Files Found
*  
* Setup call to OPEN.LIST.BOX
*
PROMPT.FOR.ANSWER = 1;DELETE.ALLOWED = 0;MULTI.SELECT = 0;FORCE.SELECT = 1;AUTOSELECT = 1;INFO.BUTTON = 0
INFO.SUB.NAME = '';INFO.SUB.KEYS = '';ADD.TEXT = '';BOTTOM.TITLE = ""
TOP.TITLE = DirectoryPath:" File Listing":@AM:"File Name~Date~Time~Size":@AM:"~Modified~Modified~"
NBR.COLS = DCOUNT(OPTION.TEXT<1>,'~')
CALL OPEN.LIST.BOX(OPTION.TEXT, OPTION.RETURN.VALUES, MULTI.SELECT, FORCE.SELECT, AUTOSELECT,  INFO.BUTTON, TOP.TITLE, BOTTOM.TITLE,  NBR.COLS, PROMPT.FOR.ANSWER, ANSWER, DELETE.ALLOWED, INFO.SUB.NAME, INFO.SUB.KEYS, ADD.TEXT)
IF ANSWER # '' THEN 
* Remove ok/delete flag from answer 
ANSWER = ANSWER<1> 
END
*
RETURN

You can find all the available CSIDL constants defined for SHGetFolderPath in the COPY.TOOLS.BP item PWS.CODES, which is where functions such as SHGetFolderPath and PCDirectoryInformation are declared.

To make them available in your code just include the copy item after the STANDARD.COMMON.VARIABLES declaration at the top of your code:

* Release Information
* MANAGE-2000 - PMN - Release 8.0
* PMN.TEST - TESTING MSO SUBROUTINE API
* Written                                                pmn 11-01-13
*#* COPY COPY.TOOLS.BP STANDARD.VARIABLES.1 (REPLACING PGM.NAME BY PMN.EXECUTE.MSO, FN.NAME BY PMN.TEST, IO.OPEN.OPTS BY TERM.DATA) ;*#* Copied Source Follows (11-01-13)
$INCLUDE STANDARD.COMMON.VARIABLES FROM COPY.TOOLS.BP
$INCLUDE STANDARD.COMMON.APP.PROGRAMS FROM COPY.TOOLS.BP
$INCLUDE STANDARD.VARIABLES.END FROM COPY.TOOLS.BP
PGM.NAME='PMN.EXECUTE.MSO'; FN.NAME ='PMN.TEST'
CALL IO.OPEN('TERM.DATA',PASSWORDS)
*#*
*
* Other Variables
*
$INCLUDE TrueFalse FROM COPY.TOOLS.BP
$INCLUDE PWS.CODES FROM COPY.TOOLS.BP

Wednesday, November 6, 2013

Cross Referencing PC File Directory

In the last post I casually suggested putting the selected filename in the Last Keys store for the IO.SCRATCH file.  This begs the question "How do I let the user select a file from their PC while in PWS?"

The answer to this question is the PCDirectoryInformation function which can access a directory listing and OPEN.LIST.BOX which can present the user with a CROSS.REF-like listbox.

*
200* Pick a file from a pc directory and run it
*
DirectoryPath = SHGetFolderPath(CSIDL_PERSONAL)
Mask = '*.*'
ANSWER = '' 
GOSUB CrossRefPCDir ;* Cross Reference using PCDirectoryInformation
IF ANSWER # '' THEN
MSO.USER.VARS = '"':DirectoryPath:'\':ANSWER:'"'
CALL SUB.EXECUTE.MSO('2324C', '', '', '')
GO 200; *Let user choose another until none selected
END
*
RETURN
*
CrossRefPCDir:* Cross Reference from PC File System
*
* Ask PC for Directory listing
*
HiddenFiles = False;SystemFiles = False;DoCheckSum = False;Recursive = False
DIR.TEXT = PCDirectoryInformation(DirectoryPath,Mask,HiddenFiles,SystemFiles,DoCheckSum,Recursive)
*
* Format directory listing as OPTION list for OPEN.LIST.BOX
*
OPTION.IDX = 0;OPTION.TEXT = '';OPTION.RETURN.VALUES = ''
DIR.ENTRIES.CNT = DCOUNT(DIR.TEXT<1>,@VM)
FOR DIR.IDX = 1 TO DIR.ENTRIES.CNT
DIR.ENTRY = DIR.TEXT<1,DIR.IDX>
DIR.ENTRY.END = DIR.ENTRY[1]
IF DIR.ENTRY.END # ":" AND DIR.ENTRY.END # "\" AND DIR.ENTRY.END # "/" THEN 
FILE.NAME = GetPCFileName(DIR.ENTRY)
IF FILE.NAME[1,1] # '~' THEN ;* Ignore Temp Files from currently open Office docs
OPTION.IDX += 1
FILE.DATE.STAMP = OCONV(DIR.TEXT<3,DIR.IDX>,"D2-")
FILE.TIME.STAMP = OCONV(DIR.TEXT<4,DIR.IDX>, "MT")
BYTE.CNT = OCONV(DIR.TEXT<5,DIR.IDX>, "MD0,")
OPTION.TEXT<OPTION.IDX> = FILE.NAME:"~":FILE.DATE.STAMP "L#8":"~":FILE.TIME.STAMP "L#8":"~":BYTE.CNT "L#15"
OPTION.RETURN.VALUES<OPTION.IDX> = FILE.NAME
END
END
NEXT
IF OPTION.IDX = 0 THEN RETURN;* No Files Found
*  
* Setup call to OPEN.LIST.BOX
*
PROMPT.FOR.ANSWER = 1;DELETE.ALLOWED = 0;MULTI.SELECT = 0;FORCE.SELECT = 1;AUTOSELECT = 1;INFO.BUTTON = 0INFO.SUB.NAME = '';INFO.SUB.KEYS = '';ADD.TEXT = '';BOTTOM.TITLE = ""
TOP.TITLE = DirectoryPath:" File Listing":@AM:"File Name~Date~Time~Size":@AM:"~Modified~Modified~"
NBR.COLS = DCOUNT(OPTION.TEXT<1>,'~')
CALL OPEN.LIST.BOX(OPTION.TEXT, OPTION.RETURN.VALUES, MULTI.SELECT, FORCE.SELECT, AUTOSELECT,  INFO.BUTTON, TOP.TITLE, BOTTOM.TITLE,  NBR.COLS, PROMPT.FOR.ANSWER, ANSWER, DELETE.ALLOWED, INFO.SUB.NAME, INFO.SUB.KEYS, ADD.TEXT)
IF ANSWER # '' THEN 
* Remove ok/delete flag from answer
ANSWER = ANSWER<1,1> 
END
*
RETURN



Monday, November 4, 2013

Programmatically Launching Related Documents

Manage 2000 includes support for attaching documents and other objects to records in the ERP database and having them available for users to render.  This is normally implemented through links defined in the OBJECT.IDX file and that is a good way to go, whether you attach objects manually or generate entries in OBJECTS and OBJECT.IDX files programmatically.

But sometimes you may want a more dynamic mechanism that lets the user drill from Manage 2000 screens into external documents without building a permanent link in OBJECTS.IDX. Here is one approach:

PC.EXECUTE.MSO is an MSO subroutine that requests PWS to perform a DDE.START.  It is meant to be wrapped by an MSO like 15S which launches Notepad.

You can copy 15S in MSO.BUILD to make a custom MSO that will launch whatever program or document you want on the user's PC.



To make an MSO for launching PDFs set PC_Execute_Name to a PDF viewer execute-able. (This depends on all PC's using this MSO having the same PDF viewer installed at the same location.)

To run any MSO out of basic code you can call

CALL SUB.EXECUTE.MSO(OBJECT.LIST, CAPTION, CLOSE.OPTIONS, PROPERTIES)

So in your program (perhaps a PWS button subroutine) concatenate together the file path for the currently selected external document and tuck it into the last keys for IO.SCRATCH (just using this as work space to pass the variable into the MSO).

DMY = SetLastKey("IO.SCRATCH", "C:\Temp\redpages_net.pdf");*  (this is 7.3 code for update last keys)
CALL SUB.EXECUTE.MSO('2324C', '', '', '')

If you find hijacking the IO.SCRATCH last key distasteful there is a more legitemate mechanism for passing program variables into MSOs:

In your program you can store attributes in the MSO.USER.VARS variable (delcared in  STANDARD.COMMON.VARIABLES)

MSO.USER.VARS = "C:\Temp\redpages_net.pdf"
CALL SUB.EXECUTE.MSO('2324C', '', '', '')

 and then in your MSO properties refer to them using the ampersand reference &USR<1>&, &USR<2>&...


If you want a more general approach you can use LAUNCH.URL.MSO in place of PC.EXECUTE.MSO.  LAUNCH.URL.MSO starts a document rather than a program and allows Windows to decide which application should be used to open that document.  The "Address" property of LAUNCH.URL.MSO can be a URL or it can be a disk location preceded by "FILE://" in which case Windows will lookup an application from the file extension.


    MSO.USER.VARS = "C:\TEMP\redpages_net.pdf"
    CALL SUB.EXECUTE.MSO('2324C', '', '', '')



Wednesday, September 25, 2013

Multi Level Grids

Infragistics is sunsetting the UltraWebxxx series of webcontrols in their ASP.NET suite of controls.  The last version that supports their classic controls is 2011.  So Manage 2000 is going to need some new patterns.

I've been playing around with GridView and DetailsView controls and experimenting with templated columns containing child views.  Here is one possible pattern for Manage 2000 multi-level grid displays:




It is not an exact replacement and certainly not the only possible option with Microsoft webcontrols.  But I rather like the vertical display of the current single valued record with the multi-valued children to the right.  This emphasizes the relationship and my eyes don't get quite so buggy trying to follow two different sets of columns over the same vertical space.

Tuesday, June 25, 2013

RSS Feed Readers for Firefox and Chrome

I've been going through some cross browser testing and came to the issue of newsfeed readers. Historically we have been focused on IE, Outlook 2007+, and SharePoint.  But now that the Manage 2000 web site is supporting other browsers, I started wondering about the news feed reader choices that work well with Firefox and Chrome.

What I found was a plethora of website based news aggegators.  These do not work well for Manage 2000 purposes.  We would like to read rss feeds coming out of Manage 2000 on our intranet without providing access from some Internet hosted rss feed reading site.

These animals do exist.  I poked around with searches like "local RSS reader".

For Firefox users there has been a suitable and popular reader available as an extension for a long time called 'Sage':



For Chrome users I found a couple of extensions that work hand in hand.  'Slick RSS Feed Finder' alerts you to the available feeds on a page and lets you subscribe to them. And 'Slick RSS' lets you actually read and manage your feeds:



Sunday, June 9, 2013

Manage 2000 Release 8.0 Beta

Well Allan grabbed the beta copy last week to start cleaning.  So no more development sneaking into that branch.  Cleaning up a release usually happens over several weeks so beta sites should be able to load up early in July.

In the mean time I am going back over the mobile styling options. JQueryMobile has 5 color swatches to their standard look.  And it is a good look.  But it isn't IPad-ish, and it isn't Android Holo-ish, and it isn't Microsoft Metro-ish or whatever the new "Metro" is.

What seems natural and obvious in hindsight is that if I am using my IPad I would be most comfortable and the M2k feature would be most seamless if the Manage 2000 web functions fit in with that style, and if I am using a Galaxy tablet I would be most comfortable if the Manage 2000 web functions came up looking like Holo, etc...

So I spent last week working on integrating some new stylesheets to emulate some of the major mobile device styles and revising the web preferences to support an "AutoSelect" option that will pick a style based on the browser UserAgent identification in the HTML header.

The various styles and swatches (colorization schemes) need some polishing to eliminate things like dark blue titles on black backgrounds and so forth, but the basic mechanism is in place and the effect is pure magic.

Tuesday, March 12, 2013

Manage 2000 Control Extenders Explained

M2kDataBinderControlExtender:

Originally this was an alternative to placing single valued fields in FormView containers.  The VS2005-2008 MS databinding changes mandated that a table be bound to a FormView and that textboxes and such in that FormView could then be bound to a field in that table. There are a bunch of things I find objectionable here.

Anyway, I stole the code for this extender from Rick Strahl at WestWind (Hawaii, poor chap), a great source for .Net intelligent thought http://www.west-wind.com/weblog/.  The databinder moves fields into and out of datasets from web controls.

Visual Studio 2003 only did one way binding using the ‘eval’ method, and we had to write code to move data back. VS 2005/8 added the ‘bind’ method to move data from web controls back into datasets.

I prefered the method name ‘Fill’ to describe moving fields into the dataset.  The name was always used in DataAdapters to mean moving the fields into the dataset from the datasource.  So I chose to use the DBCE.Fill expression to request the databinder to fill up the dataset with field values from web controls.

While working on the m2kScrolledSetControlExtender I enhanced this extender to bind fields from dataset to columns in a JSON array stored in a hidden field (Bind To Client).    The scrolledset then moves fields between a hidden field and textboxes inside the browser using javascript. The hiddenfield acts like FILE.ITEM(1) providing working storage at the browser end.   Together these extenders manage the transmission from datasets to scrolledsets and back.

Since then, based on application work by the development team, I have been adding additional support in the data binder for other situations like an easy way to bind SA MV sets to multi-line textboxes and just lately support for RadioButtonLists.

The Microsoft databinding patterns can still be used and, in fact, for grid displays and other sorts of table level binding I expect we will continue to use ObjectDataSource controls.

M2kScrolledSetControlExtender:

My original goal for the scrolled set extender was to create a way to have scrolled set processing in web pages that matched the keyboarding and development efficiencies of SUB.MT500 and SCROLL.MAINT. Yeah, not quite there yet :)

In one sense the scrolled set extender stands in for SCREEN.BUILD C4.  It provides a design time place for the developer to specify scrolled set behavior.  It also serves to emit the required javascript for instantiating a javascript object with the operational mechanics implementing the scrolled set experience.  The definition for the javascript object and all of its methods actual exists in roiGlobal.js.  The scrolledset extender just needs to create an object of that class in the browser world and initialize it with parameters supplied by the application developer.

One of the principal design decisions was to leverage all the data entry power of the roiTextBox, and to loosely package an arbitrary number of entry fields in a table that would contain the set entry controls and display cells. I wrote up a how to with more details on creating scrolled sets named WEB.SCROLLED.SET and added it to the SYS.WEB.HOWTOS menu.

M2kMetaDataControlExtender:

This extender is basically a re-write of the EditParams features that we originally built into roiTextBox and the metadata data access component. It provides PMT like definitions that developers can use to describe the behavior they want and then the Tools layer can take care of the implementation.


When Microsoft broke the component design surface into a separate tab in Visual Studio 2005/2008 they COMPLETELY isolated WebControl design time from Data Access Component design time.  This meant that the roiTextBox had no way of talking to the typed datasets or the metadata component.  So the whole metadata world and the databinding got shoe-horned into “WebControls” (which the control extenders are) so they can sit in the same designer and interact with webcontrols.

Friday, January 25, 2013

Manage 2000 Version 8.0

The major version has officially rolled in the development environment.  Manage 2000 8.0 here we come. Whoo Hooo!!!

The development team decided a couple of months ago that with all the mobile web changes plus other un-celebrated technology changes that the 7.x branch should end with 7.3 and the upcoming release should begin the 8.x dynasty.  Look for it towards year-end of 2013.

So now that changing the numbering is done, it is time to change some more of the machinery as well.  I am currently hip deep in ASP.NET 4.5, HTML 5, and VS 2012, trying to solve too many equations with way too many unknowns.

The VS 2012 IDE has a lot to be said for it.  It feels faster, and seems to get confused less often by our sub-project pattern web architecture. The solution explorer drill down into project classes, the highlighting of the current variable in neighboring code, the javascript highlighting during edits are all wonderful developer productivity improvements.  There is a project level option to run a code analyzer at compile time that functions much  like the Manage 2000 BX Audits to provide developers with coding standards compliance feedback.

We are going to have to make a few mass changes to vbproj files and  a few references in aspx and ascx files.  Infragistics references will have to target ASP.NET 4 instead of 3.5. But it doesn't seem like the Visual Studio Tools Team broke anything critical to Manage 2000 this time around.  Whew!

There is an interesting convergence of client side technology happening as Manage 2000 web tools provide lots of client side javascript and JSON validation and editing features which run into some of the unobtrusive validation being supplied with VS 2012 and ASP.NET 4.5 with the inclusion of jquery.  And as we proceed down the HTML 5 path we are both running into the validation constraints being coded natively into browsers as they process HTML 5 markup.  So it seems I will be spending some time trying to smooth over some redundant repetition of validation on the client end.