Andornot Consulting Inc.
Home Page
Home Page
 |  | 

Monday, December 01, 2008

Hit-a-Hint replacement: LoL fork of HaH

Back in June I posted about how to get Hit-a-Hint to work with Firefox 3.*. HaH is a Firefox extension that aims to make web surfing with a keyboard as usable as possible. However, HaH is no longer under active development (which actually was the impetus for the June post as it needed some tweaks just to get it to work with newer versions of Firefox).

But now there's a replacement! A new extension on the block, LoL, is a new and improved fork of HaH (and although I am a Nerd, I don't know why it's called LoL because I don't know what it would have to do with laughing out loud - can anybody enlighten me?). It's got everything HaH had, but also provides user-configurable keys and the ability to "intercept" link clicks with the space bar. Of course, it also work with Firefox 3 right out of the virtual box. Check out the developer's site at http://elder-gods.org/lol/. Thanks Larry!

Labels: , ,

Sunday, November 09, 2008

Enhancing the Genie OPAC Page Header

The options for including a header across the top of the Genie OPAC pages that are supported by Genie out-of-the-box  are limited to referencing an image in the myGenie.config file, using the following line:

<add key="MyLogo" value="images/your_logo.gif" />

The image referenced in this line appears at the top of all OPAC pages. The image can be a small logo, or can extend the full width of the OPAC pages (approx. 800 pixels) to provide an eye-catching header. For some sites, this is all that is required.

However, if you want to have the Genie OPAC header match an existing public or internal website, a simple image may not be sufficient. Typically a header includes text, images and site navigation, so HTML may be required, not just an image.

You can insert a custom header that includes HTML in the Genie header by editing the OPAC .aspx files directly. These include:

  • opac.aspx
  • opac_report.aspx
  • opac_cart.aspx
  • opac_items_search.aspx
  • opac_items_report.aspx
  • opac_loans_checkout.aspx
  • opac_loans_report.aspx
  • login.aspx
  • logout.aspx

While you may include the complete header HTML in these .aspx pages directly, it is more efficient to place it in a separate file, then use an ASP.net user control to pull it in. For example, if a file called header.ascx is created and contains the HTML for the site header, then add the following statement at the top of opac.aspx:

<%@ Register Src="~/include/header.ascx" TagName="Header" TagPrefix="uc" %>

and the following statement immediately after the <body> tag in opac.aspx:

<uc:Header ID="Header1" runat="server"></uc:Header>

This will result in the contents of header.ascx being included in the page when served to the user.

By separating the header HTML from the page, rather than embedding it in each of the OPAC pages, it is easy to subsequently edit the header to match changes to the rest of the site, without having to edit each OPAC page.

The same technique may be used to include a footer, or any other secondary content, within the OPAC pages.

You can see an example of this style of header at http://geniehost23.inmagic.com/InmagicGenie/opac.aspx

Catalog Search_1217479621728

One caveat of this technique is that if future versions of Genie include changes to any of the OPAC .aspx pages, the upgrade process will likely overwrite the modified versions. It is therefore important to keep a backup copy of those pages, and following the upgrade, to edit the new OPAC .aspx pages to re-insert the above code.

Note also that in the above example, in the path "~/include/header.ascx", the ~ means "resolve to application root", which is typically the folder in which Genie is installed, and /include/ is a new folder created to store all the files and images related to the header, to keep it separate from the Genie files themselves.

Friday, October 10, 2008

Genie en Francais!

When initially installed, Genie is set up with an English-language interface. However, Genie can also be set up with French-language screens so that staff and users can work in the language of their choice.

Genie_French

Installation is fairly straightforward using Inmagic's instructions and localization files, but does involve editing some Genie .config files.

From your library home page, you can direct end users to the OPAC and have it start in either language, simply by specifying the language in the URL.

e.g. http://216.187.67.111/InmagicGenie/opac.aspx?Language=fr-CA will start the OPAC in Canadian French (this site is Andornot's Genie demo).

Library staff can also choose a language for working with the Catalog, Serials and other modules.

In both the OPAC and library staff area, a cookie is set to retain the language choice.

Contact Andornot or Inmagic, Inc. to obtain the French localization files and step-by-step instructions for installation.

Wednesday, October 08, 2008

Use Genie Resource Files to Change Textual Elements

The Genie config files allow changes to many textual elements within Genie, from field names and labels to the names of the Browse buttons.

However, not everything that appears on the Genie screens is exposed in the config files. Other text is stored in a resource file, one for different languages, that can be copied and edited using the procedure below. Note that there is currently no documentation for changing these files.

1. Locate the file "GlobalResource.RESX ". It is typically installed in C:\Program Files\Inmagic\Genie\App_GlobalResources.

2. Create a new plain text file in this same folder called "GlobalResource.en-US.RESX" (or if your instance of Genie is configured for a different locale, change en-US to that locale).

3. Copy the following from the GlobalResource.RESX file to the new .RESX file:

    • The header information: Everything from the top of the file to just before the first <data> tag.
    • Only those <data> tags containing messages that you want to be different than the original message.
    • The </root> tag from the bottom of the file.

4. Within the <data> tags you copied, edit the text between <value> and </value> to change the message.

For example, to change the name of the InfoCart to "Record List", you would copy all of the <data> tags that refer to the InfoCart and edit their values to incorporate Records List.

resource_file

Wednesday, September 10, 2008

Index Popups 2008.2.9.10 Update

Index popups for Inmagic Webpublisher Pro have been updated to fix some subtle bugs:

  • Entering certain characters in find box causes invalid XML errors.
  • Previous Page and Last Page do not work with some secondary fieldnames, or when fieldname is provided whose sentence case does not match actual field exactly.
  • Passwords are not passed through after the initial page. (Ok, not so subtle this one.)
  • Code fields can cause invalid XML errors.
  • Last Page fails if icsweb.ini/dbtwpub.ini SoapFormat=0 or SoapFormat=n and fieldname requires modification for XML use.

Unfixable issue:

  • Previous Page fails if icsweb.ini/dbtwpub.ini SoapFormat=0 or SoapFormat=n and fieldname requires modification for XML use. (Can only fix by changing SoapFormat=1 due to Webpublisher restrictions on fetching index lists with a key delta.)

Download Index Popups 2008.2.9.10 (~600 kB)

Labels: ,

Friday, September 05, 2008

Virtual PC 2007 networking tip

If you want to interact with a virtual machine as part of your network, you need to adjust its networking settings to specifically use one of the host machine's network adapters.

Don't set the adapter to "Shared networking (NAT)". This isolates the virtual machine behind the virtual networking adapter. You'll find you can't even ping the VM from the host.

Good for interacting

vm_adapter_set

Bad for interacting

vm_nat

Tuesday, August 05, 2008

DB/Text trace log format method

Further to my post of May 2007, where I described how to create a trace log in DB/Text and handle exceptions, here is a method that is handy for building more complex log statements.

   1: // construct formatted log statement
   2: function logFormat(val, arg1, arg2, arg3)
   3: {
   4:     var formattedVal = val;
   5:     if (arg1 != null)    {
   6:         formattedVal = formattedVal.replace("{0}", arg1);
   7:     }
   8:     if (arg2 != null) {
   9:         formattedVal = formattedVal.replace("{1}", arg2);
  10:     }
  11:     if (arg3 != null) {
  12:         formattedVal = formattedVal.replace("{2}", arg3);
  13:     }
  14:     log(formattedVal);
  15: }
  16:  
  17: // write log statement to form box
  18: function log(val)
  19: {
  20:     var box = Form.boxes("boxDebugLog");
  21:     if (box == null)
  22:         return;    
  23:     
  24:     if (box.content != '')
  25:     {
  26:         box.content += "\n";    
  27:     }    
  28:     box.content += val;
  29: }

Overview

logFormat takes a string value and up to 3 arguments, and replaces tokens found in the string value with those arguments. It's based on C#'s string.Format method, and others like it.

Example

It's easier to read (and write) a format string that contains tokens than a string concatenated together with plus signs.

   1: var count = 10;
   2: var name = "Peter";
   3: var duration = 30;
   4:  
   5: function LogTheOldWay()
   6: {
   7:     log(name + " was lashed with a wet noodle " + count + " times for " + duration + " seconds.");
   8: }
   9:  
  10: function LogTheNewWay()
  11: {    
  12:     logFormat("{0} was lashed with a wet noodle {1} times for {2} seconds.", name, count, duration);
  13: }

Update

My example above only proves how piddly my skills really are. There's a *much* better way of doing this that allows for n arguments instead of a maximum of three. I had forgotten that every javascript function has a local property called arguments that contains all parameters passed to the function as an array.

   1: function logFormat(val)
   2: {
   3:     var formattedVal = val;
   4:   for(i = 1; i < arguments.length; i++)
   5:   {
   6:     formattedVal = formattedVal.replace("{" + (i - 1) + "}", arguments[i]);
   7:   }
   8:   return formattedVal;
   9: }

Labels: ,

"Static" properties in javascript. Kinda.

The following gives me the ability to access javascript properties in a static kind of way. Not really, since the object has been instantiated, but at least I don't have to define a specific object, then call its constructor all the time to new it up and get at its properties.

   1: // declare globally
   2: var Artist = {
   3:     Fields: {
   4:         Id: "ID",
   5:         FullName: "Term",
   6:         FirstName: "First Name",
   7:         LastName: "Last Name",
   8:         Birth: "Date of Birth",
   9:         Death: "Date of Death"
  10:     }
  11: };

Overview

I like to store DB/Text field names in this way, as they become (pseudo) strongly-typed and the magic strings are stored in one place only, instead of scattered across my code like dandelion seeds.

It can be difficult to work in javascript after C# and other full-featured languages. This is the closest I could come to static properties or a struct. If I weren't scripting in DB/Text, a proprietary closed system, I would be using jQuery or mootools, which put the joy back in joyvascript.

Example

Now I can access field names statically as follows. Look ma, no constructors!

   1: function SomeMethod()
   2: {
   3:     var idFieldName = Artist.Fields.Id;
   4: }

Labels: ,

A highly reusable recordset script for DB/Text

This javascript function performs a search against a DB/Text database and returns a sorted array of dictionary objects with fieldnames as keys.

Parameters

Search(commandQuery, textbase, password, fieldsToReturn, sortFields)

commandQuery (string): A query in command syntax.
textbase (string): The name of the textbase to query.
password (string): A textbase password that allows querying.
fieldsToReturn (array): An array of field names whose values the query will retrieve from result records.
sortFields (array): An array of field names to sort the recordset by.

Dependencies

SortByField(recordset, sortFields): A method that sorts a recordset by the fields provided.

   1: // returns array of dictionaries with fieldsToReturn as keys
   2: function Search(commandQuery, textbase, password, fieldsToReturn, sortFields)
   3: {
   4:     var rs;
   5:     var output = [];
   6:     
   7:     rs = Application.newRecordset(textbase, Application.activeTextbase.path, password);
   8:     if (rs != null)
   9:     {
  10:         try {
  11:             rs.Open(commandQuery);
  12:             if (rs.Errors.Count > 0)
  13:             {
  14:                     // log error
  15:             }
  16:             else if (rs.RecordCount > 0)
  17:             {
  18:                 // sort the recordset
  19:                 rs = SortByField(rs, sortFields);
  20:                 
  21:                 rs.MoveFirst();
  22:                 while (!rs.EOF)
  23:                 {                    
  24:                     // create dictionary of field values, add to output list
  25:                     var record = new Object();
  26:                     for (var i = 0;i < fieldsToReturn.length;i++)
  27:                     {
  28:                         record[fieldsToReturn[i]] = rs.Fields(fieldsToReturn[i]).Value;
  29:                     }
  30:                     output[output.length] = record;
  31:                     
  32:                     rs.MoveNext();                    
  33:                 }
  34:             }    
  35:         }
  36:         catch (e) {
  37:             // log exception
  38:         }
  39:         finally 
  40:         {
  41:             rs.Close();    
  42:         }
  43:     }
  44:     return output;
  45: }
  46:  
  47: // returns recordset sorted by fields provided
  48: function SortByField(recordset, sortFields)
  49: {
  50:     var sort = recordset.NewSortDescriptor();
  51:     for (var i = 0;i < sortFields.length;i++)
  52:     {
  53:         if (i >= (sort.MaxSORTFIELDS - 1))
  54:         {
  55:             break;    
  56:         }
  57:         sort.sortFieldName(i) = sortFields[i];
  58:     }
  59:     recordset.Sort(sort);    
  60:     return recordset;
  61: }

Overview

The main advantage of this function is its agnosticism towards the fields it is to retrieve. It relies on the fact that objects in javascript inherently behave as associative arrays, e.g.

var myObject = new Object();
myObject["myKey"] = "some value";

For each record in the query's result set, an object is created with fieldsToReturn as keys, and the function's return type is simply an array of these objects. The fact that I am using these objects solely for their key/value behaviour leads me to refer to them as dictionaries. They are not dictionaries in the sense of specialized collections as with other languages outside of javascript.

Example

The following is a simplified method that I would never use in production code. It grabs search text from a box, creates a command query, then searches the Catalog. The result set will be sorted by Title, and will contain values from Title and Author fields. It then shows an alert message with the title from the first result in the set.

Note that field values are retrieved by using field names as keys on the objects in the results array.

   1: function btnSearch_onClick()
   2: {
   3:     var searchText = Form.boxes("boxSearchText").content;
   4:     var query = "find (Title ct {0}) or (Author ct {0})".replace("{0}", searchText);
   5:     var results = Search(query, "Catalog", "password", ["Title", "Author"], ["Title"]);
   6:     
   7:     var msg = "First result Title is '{0}'".replace("{0}", results[0]["Title"];
   8:     Application.message(msg);
   9: }

Labels: ,