Skip to the content Back to Top

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: }

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: }

A couple days back I implemented ComponentArt's new ComboBox for a project. Worked great and brought some nice functionality (look ahead filtering and linked dropdowns: a second ComboBox populated via an AJAX callback according to the value selected in the first). However, I noticed at the very end that the controls do not degrade AT ALL when JavaScript is turned off. They look the same, but are completely disabled and useless.

At that point, even if I could duplicate the functionality another way it wasn't worth it; at the very least however, I needed to provide obvious notification that if JavaScript is disabled, the dropdowns are completely non-functional. So heh, easy enough: create a div nicely styled with the obligatory "JavaScript must be enabled to...blah blah blah..." and then set up a startup script for everyone with JavaScript enabled so that this message is not visible.... If you aren't laughing right now, you should be.

It took me a couple days to remember that that's what <noscript>fancy "If you don't have JavaScript enabled" message here</noscript> is for.

After I wrote my previous post on handling script errors, it occurred to me I had taken knowledge about about tracing and exceptions for granted. So, here is a description of how I add a trace log and exception handling to my scripts in DB/ or CS/Textworks.

Make an Inmagic DB/Textworks Trace Log

First, add a form box and two buttons to the form, named boxDebugLog, btnBugs, and btnClearLog.

Add code to handle the two buttons and and a log method, which writes a new line into the form box whenever called. The bugs button ensures there is no syntax error lurking in the code, which would halt any code from running on the form, and therefore would stop "no syntax bugs" from being written into the log (no news is bad news). The isLogging boolean variable can be turned on and off globally so that trace statements don't have to be ripped out of deployed code.

var isLogging = true;
function btnBugs_onClick()
{
  log("no syntax bugs");
}
 
function btnClearLog_onClick()
{
  Form.boxes("boxDebugLog").content = ''
}
 
function log(val)
{
  if (!isLogging)
  {
    return
  }
 
  var box = Form.boxes("boxDebugLog");
  if (box.content != '')
  {
    box.content += "\n"
  }  
  box.content += val;
}

Within the code, insert trace statements wherever you want to see the results of an operation.

log("GetRequestorsByQuery() " + commandQuery);

The result of the above would look like this in the Debug Log form box:

Extend the Trace Log to Handle Exceptions

The above is good for viewing the results of expected steps in a series of operations and can help you debug. Of course, when you work with code the real bugbear is the unexpected. And, further, there are operational errors (not bugs) which may crop up long after you're done building the code.

Inmagic DB/Textworks presents us with an obstacle in that any error encountered simply halts the process and does not in any way indicate there has been an error. To the average user this is perhaps a blessing (ignorance being bliss), but to the developer it is nothing but an impediment. Fortunately there is a way to free the errors from their airless dungeon so as to inform you of what is really going on.

The try/catch/finally statement attempts operations with instructions (the catch block) on what to do if an exception is encountered in the try block. The finally block tells what to do after try and catch have been dealt with, and is useful for closing objects or releasing resources that might otherwise have been stuck as a result of the exception encountered.

In our case, we can use try/catch to handle exceptions, and log the error messages to the trace log.

  var item = new catalogItem();

  var isInserted = false;

 

  try

  {

    isInserted = InsertCatalogRecord(item); 

  }

  catch (err)

  {

 

    log("Unable to add to catalog. " + err.name + ": " + err.message);

  }

Note that the err variable is an Error object, and so has predictable properties to call upon, name being the exception type and message being the short description. (More about exception types.Error is a built-in javascript object. I should mention that it is normally good practice to isolate specific exception types with multiple catch blocks, but I think that's overkill for Inmagic scripting.

You can also generate custom exceptions using the throw statement.

throw new Error("something went wrong");

throw "something went wrong";

Both statements are valid, because javascript is extremely lenient about what you can throw. I prefer to throw an Error object instead of a string, to be consistent with other exception types that might come up. Then I can ask for err.name and err.message without having to first check and see if the exception is actually a string instead.

So the throw statement allows you to "throw" (i.e. raise) an error whenever you like. Why would you want to, if the whole point of this exercise is to eliminate errors from the script? The reason becomes clear when you are working with operations whose scope is beyond your immediate control. In the scenario above where try/catch is demonstrated, we are adding a new record to a catalog. It's possible the catalog might not be there, or might reject a new record for some reason, or a field name changed since we wrote the script. If any of these happens, or anything else we can't now predict, we can still catch the problem and deal with it in a predictable way so that the script fails gracefully and passes on vital information before it expires.

If you wanted to force the Inmagic recordset object to throw any error it encountered, you could do that, for instance (and I do).

      if (rs.Errors.Count > 0)

      {

        throw new Error(rs.Errors(0).Description);

      }

It takes a bit of an adjustment at first to deliberately throw an error, or to let an error bubble up to another, calling function and deal with it there, but it is a powerful and freeing concept once you get it.

The scripting environment within Inmagic DB/Textworks (or CS/Textworks) is extremely primitive. Controls you want to interact with have to be declared in an environment separate from the form designer and it's hard to write code longer than 100 lines because there's no tab character or ctrl-a select all, let alone syntax highlighting or intellisense. It's very easy to lose your way if your script does anything beyond "hello world."

The most difficult thing to deal with is that any javascript error encountered does not actually throw an error. Things just progress... until they don't. No error thrown, no feedback, no nothing. It makes debugging a nightmare. What I have done until recently is insert a million-and-one trace statements that write to a trace box over on the side of the form, which at least tells me how far progress went and gives me a clue as to the general area things went wrong.

But I've recently realized I *can* throw (and handle) an error, if I wrap logical blocks of code in try/catch/finally statements. Can't believe it took me so long to cotton on to this. Now I can get detailed info about unanticipated exceptions as well as the anticipated ones. It makes my life better until I can boot up Visual Studio and bask once again in its warm developer-friendly glow.

Play "spot da error":

Yeah, "rs" is an undeclared variable. That's what happens when you refactor manually. Without try/catch this little problem becomes extremely elusive.

UPDATE: The picture has syntax highlighting because I do all scripting in UltraEdit. (Also features code folding, a jump-to-function list, etc.)

Categories

Let Us Help You!

We're Librarians - We Love to Help People