Thursday, December 27, 2012

Looping through like controls

Here is a simple approach to loop through like controls on a form and set (in this case visible) properties.

arrLabel is an array that holds a reference to all controls of type label.  Switch label for button, datagrid, or whatever you want.

    var arrLabel = The_Form_Ref.All(Label);     

Here we start the looping by declaring a variable equal to the elements in the array.  This its a matter of assigning another variable to the actual label and then toggling its visibility.
                     
    for (var lbl : Element in arrLabel)
        {
            var top_lbl : Label = Label(lbl);
            top_lbl.Visible = true;
           
        }

Playing with Form Arguments


Form arguments can be useful.  Of course figuring out when to use form arguments over navigator arguments or the other way around can be a trick.  It really depends on your focus and where that information needs to go within your application.  Still, for now, let's talk about form arguments.

The definition in the documentation that came with starlims says, "  The form parameters are received in the formArguments property of the form object".  Okay, that's useful.  Now we know we need to reference it via the form.formArguments notation.  Okay, what's next.

It further goes on to say: "On loading the form, you must be sure that a value has been passed inside the
formArguments, before trying to retrieve its value, otherwise you will generate an error."

Okay.  So, I need to know ahead of time that the argument exists before I can call it.  Makes sense so far.  Be nice if I could catch what arguments are out there in a general way.  Hmmm.  form.formArguments != null? 

Nope!  That fails when used.  Interesting.  What about assigning it in some fashion?  form.formArguments == null works but I'm looking for something a bit more robust.  How about:

var openArgs = form.formArguments;

Success!  If we do this, it will return a value we can check for and use.  Its null if no formArguments were passed and returns a zero (0) value if we measure its length with lims.len().

Let's see what else the documentation says.

"A programmer must be aware of the way the arguments are sent between forms, as the formArguments property type is Object, and it can hold different types of values inside."

Okay.  Good info.

"So if you send an array as the form argument, you will expect an array in the formArguments property. If you send a string, you will receive a string and so on."

Better info.  So, my resultant form arguments can have different variable types.  Good.  The text gives a warning, though.

"But, for example, if you send a string and treat the formArguments as an array, indexing it (formArguments[2]), if the string has at least two character, you will obtain the second character, otherwise you will generate an error."

Hmmm, so treating the form arguments as a default array might not be the best approach.  Check.

"So before starting to get the form arguments, first you have to assure that arguments have been sent, and if you are expecting an array, the length of that array must be validated, before indexing the array. These are just a few rules a programmer must follow to reduce the possibility of creating errors."

Good points. So, if I use the openArgs approach above, let's try a simple test of passing an array and text.


    var openArgs = frmTest.formArguments;
    if (openArgs != null)
    {
        //code goes here
    }

This gives me a value of 2, letting me know I have two variables.  I can then iterate through those variables and figure out what they are.



With statement work?

So far, much to my unhappiness the WITH keyword doesn't seem to be implemented in V10.  I used it often in other programming and its missed.  Perhaps in a future update.

Making a Client Script

Making a client script follows the same guidelines as putting code in the code behind of a form.   I would suggest pretty strict coding requirements and some hefty comments.  Anyway, making them couldn't be easier:

  1. Go to your Client Scripts tab
  2. Choose the Category of your choice
  3. Right click and Choose "New Client Script" from the context menu
What you end up with is screen similar to the Code Behind window.  Add code there.  Keep in mind there is not visual side to this code so you will need to pull it in as a reference to another portion of your application to truly test it.

Wednesday, December 26, 2012

Client Scripts basics

It took me a while to get to these but once I started to realize their use, I began to use them constantly.  You can find client scripts in the XFD Designer under a tab with the same name.  After a quick perusal you'll realize, like I did, that these contain common repeatable tasks.  Tasks you should code once and then just call over and over. 

Seeing how I am always looking for good ways to be efficient, I took the bait and started playing with them.  First was figuring out how to use them.

Let's start there.

Go to the form properties view, under MISC in the Scripts collection.  Click to open up a reference form that allows you to choose one or more scripts to add.  You'll get a view into the same scripts you just saw under the Client Scripts tab.  Take a look around and find one you want to add.

This may seem dumb to note but make sure you hit the "add" button on the top right hand of the RefPickerForm to actually add your script.  Double clicking to the view script doesn't actually add it, though it does let you look at what's in the script.  Once done (you can add more than one), hit okay.  Review the scripts you have added and then save.  Did I mention you want to save?

Anyway, once you have the client script referenced you can use it's code like you had it in the code behind.  Very useful when your goal is to maintain code reuse.  Its worth your time to define some of your more common actions and then just reference them.  The Utilities category of the Applications in the XFD Designer is full of good scripts and the Client Scripts tab gives you a full view of existing client scripts.

WS_UpdateProvider



Seen often on Panel controls and as a default when controls are created.  Its connected to the Data property.  Allows bound fields associated with it to be updated automatically as soon as the user moves from the record.

Function to gen linkbar items

I was tinkering with a linkbar the other day and wanted to share some tidbits I found useful.  I wanted to dynamically load it so I set up a server script to pull a list of items for me to populate.  Since I had multiple groups to add, I created two functions to load them, one for the groups, the other for the items:

//region LINKBAR FUNCTIONS
function CreateGroup(Grptext)
{
    //Creates a group item for a Link Bar
    //add a variable for the commandbargroup if this is desired to be multifunctional
    if (Grptext!= null)
        {
            var bargrp : CommandBarGroup = lnkIntake.CreateNewGroup();
            bargrp.Text = Grptext;
            lnkIntake.Groups.Add(bargrp);
            return bargrp;       
   
        }

}

function CreateGroupItem(bar : CommandBarGroup, Itemtext, Itemvalue)
{
    //Creates a group item for the Link Bar
    //add a variable for the commandbargroup if this is desired to be multifunctional

    if (bar!= null)
        {
            var baritem : CommandBarItem = bar.CreateNewItem();
            baritem.Text = Itemtext;
            baritem.Value = Itemvalue;
            bar.Items.Add(baritem);
   
        }

}
//endregion LINKBAR FUNCTIONS

Saturday, December 22, 2012

FindbyText versus FindbyIndex

I was working on this the other day.  At least, working a way to use the FindbyText and FindbyIndex features to get some of the heavy lifting I wanted done.  In this case I was using a combobox to manage selection options, had a dbgrid slaved to it to show deeper details for the one-line selection it provided, and have an option for individuals to indicate they wanted to save their option (out of a possible many) to a nearby listbox.  Now I wanted to be sure they didn't add multiples of the same type of selection from the combobox, a very common requirement, so I used FindbyText to search the item collection of the listbox.  In this case, I was looking for a "no match" result so I would know to add an item.  Here's a sample of the code:

    if (listbox.Items.FindByText(combobox.SelectedText) == null)
    {
        listbox.Items.Add(combobox.SelectedText);
    }

As you can guess, it returns a null value if nothing is found (not always the case, I might add).  If you are looking for a "match" then switch it to the following:

if (listbox.Items.FindByText(combobox.SelectedText) != null)

Just as a note, if you want to remove something that's been selected, say for a context menu or button that allows for it; or, for times when you have are looking for a positive find and want to take action, like removing a selected item from a list box, use the following line of code:

         listbox.Items.RemoveBySelection('Y');

It takes a boolean value, obviously and you could easily flip it to 'N'.  Good when you might need to toggle the ability to remove an item based on permissions.

Now, I realize I haven't covered FindbyIndex yet. you can use it to find items in a list or combo box thought its not as handy necessarily.  You would need to do the comparison outside first and then match it up by index and then perform a listbox.Items.FindbyIndex(Index).


Here is a much better use of findbyindex.  As a way to reset a combo/listbox back to its first entry:

    if(combobox.Items.Count > 0)
    {
        combobox.SelectedText = combobox.Items.FindByIndex(0);
    }

Friday, December 21, 2012

Simple Debugging of Server Scripts

Server scripts don't really lend towards debugging well.  Its the nature of how they are written.  Still you can do a few things to get back values and figure out if the code you have generated is acting like you want.

Normally I employ :RETURN statements like you would use alert or dialogs to give back localized variable information.  That allows you to at least watch a variable and check its output.

You can also run it like you do a form (CTRL + F5) and get the final outcome though you'll probably want something more flexible -- hence the use of :RETURN statements at strategic intervals to get some basic info.  Its not as good as setting a watch on variables and capturing outputs but you can add some basic code to help.

If you have constrained ranges you need to watch for then use :IF statements with :RETURN statements for good effect.

I've tried to employ the :CHECKPARAM statement but it doesn't seem to work as advertised in the server scripts.

--- Update ---

So, :RETURN works in a flexible fashion.  Use it to find variable values but also remember it can provide a true or false return for sqlexecute functions to make sure they are working properly.

Also, it ends the execution of the server script where ever you place it so its a good way to isolate errors.

Pointers on posting text via scripts

Its often useful to check the size of the column before you post text to it.  For varchar columns, use Len().  For text, use Datalength().

So:  SELECT LEN(mytextfield) AS varcharsize

or

SELECT DATALENGTH(mytextfield) AS textsize

Of course this only gives you the length of what's been put in there and not its max ALLOWABLE size.  To do that, use a query like the following:

SELECT     COLUMNPROPERTY(OBJECT_ID('dbo.yourtablename'), 'yourcolumnname', 'precision') AS colLength

This can be really, really useful when you are posting notes back to your database, for example, to make sure you do not overflow the allowed size.  Here I check against my NOTES table.

checklen := SqlExecute("SELECT     COLUMNPROPERTY(OBJECT_ID('dbo.NOTES'), 'NOTES', 'precision') AS col


:IF  val(checklen[1,1]) < texttoupdate;
     //commit the text data
:ENDIF;

Personally, I just split the data into chunks allowed by the column and enter it into the table.  I use a management field as the id to track the split note so it appears as a single note to the user and to other scripts that need an unique value.

Thursday, December 20, 2012

Simple Server Script Thoughts

Server scripts are handy things.

We use them frequently, often for any type of interaction between the data and the UI that you can think of and toss into starlims.  Need to commit some new information to your database tables?  Run a server script with some SQL insert/update statements.  Pull information?  The same, just with Select statements.

Server scripts are wonderful and handy but require a little care and feeding when they are set up.  People tend to fall into two groups when they use them:  either set them up as single activity, e.g. the purpose of the server script is to handle a single update/select/insert/create or a singular activity: a couple of SQL commands centered around processing data to come to a conclusion --like performing checks based on provided data and then performing an update or series of updates; or to build complex multifunctional scripts, often with DoProc() statements to handle process flow.

Neither approach is good/bad or even better than the other: it really depends on your programming goal and philosophy.  I can't say I've noticed any performance impacts taking one approach over the other, though I do find the highly complex server scripts to be harder to code review and debug.

So, in the path of building your server scripts, let me put out a few things for you to keep in mind.

Don't make them brittle

Changes in your tables can break even the most well-written scripts.  Even small changes have large impacts, like setting a column to suddenly be non-nullable or to have a different value.  You can code for some of these potential issues by making great use of parameters.  In fact, for simple updates or data pulls via select statements, I highly suggest you create a flexible server script which accepts an array of column names, an array of values, and a tablename.  Having a script like this in hand allows you to utilize it to handle any single table and juggle updates, selects or insert statements.   You can code to read the values in the arrays of each and then build the SQL statement appropriately.  If changes to the tables are a concern, use some preliminary scripting to check:

arrCols := sqlExecute("SELECT * FROM sys.columns WHERE object_id = object_id('dbo.Tablename')");

That will give you the list of columns in the table.  Don't forget to use your table's name and not the placeholder I put in there. 

If you think nullable might be an issue for a particular column, you could query to find out:

arrIsNullable := SqlExecute("select IS_NULLABLE from INFORMATION_SCHEMA.COLUMNS
where TABLE_SCHEMA='dbo' AND TABLE_NAME='Tablename' and COLUMN_NAME='Columnname'");

Obviously you need to plug in your table and column.

Think Security

It doesn't show up out of nowhere.  Even though you are programing in system and not wide open to the world, you need to keep in mind what your server scripts are updating, especially if you have layers of admins who are potentially coding.  It may not be in your best interest to let everyone code to access the underlying tables.  You could enforce some protection by creating views that contain the columns you only want exposed and have those accessed instead.  Also, resist concatenating things together.  It can leave you open to SQL injection attacks.  Use parameters instead.

Write Maintainable Code

Focus on writing code that will age well and be understandable a year after you crafted it.  Worry less about about performance and focus on well factored code.  If performance is a requirement or a problem, analyze your code to find the trouble spots and optimize them.

Also, remember when I mentioned you might considering using a flexible server script and passing columns by parameters?  Keep in mind that only 15% of an application's life is spent in development and the rest is in maintenance.  That means you need to make your job easier since the code you right today is going to be around plaguing you for years to come.  Using parameters instead of passing columns and values means when you update your table with something new, you only need to adjust code in one area instead of re-writing in multiple ones.

Wednesday, December 19, 2012

Difference between the debugger and player

In truth I use both of these though probably the player the most.  CTRL + F5 is your friend!  Of course, so is F5.  Anyway, I've outlined a couple of the reasons for why you would need to use one over the other and why you would want to use both. 

Debugger

  • The ability to set breakpoints cannot be overstated.  Seeing how the program flows and executes allows you to make good decisions.  Nothing new there: its the basics of any good IDE.
  • The ability to see variables as they execute in the local space.  Those two things alone would make it worth it.  This lets you know the values you expect are actually in place.
  • While not obvious, the debugger gives you different error messages than the player.

Player

  • It  allows you to quick run a form and doesn't require the same permissions as a person who needs to run the debugger.
  • You can't set breakpoints but effective user of alerts, dialogs and other feedback can give you a lot of information.
  • Its actually more effective in the short run to use it, especially if you are coding for something that is graphic intensive or requires exactness in the user interface.  Timewise you can often bring something up in the player, check it and get an answer before the debugger even loads up.  That time savings is invaluable.

Tuesday, December 18, 2012

Coding to handling upgrades among versions

Okay, so as a point, its important to remember that you are coding, as a developer in the same code space that the Starlims developers are coding in.  That means, that we need to prepare carefully. 

Why?

Well, thinking about it in the light I cast above should tell you that when they (starlims) releases a new update its going to overwrite anything you've put in the same space when you apply that update.  Its prime among the causes of why I segregate code from their namespaces whenever possible so as to protect it as much as possible.

So, when I start coding inside a LIMS I define a new area (appropriately named to the LIMS or the place I'm developing in) and build, import or recreate forms, server scripts and data sources as I need them.  The same applies to building new tables as well, re-naming and localizing them as needed. 

That keeps updates from overwriting them.  It also allows you to utilize some good database design.  I realize the Starlims folks left a lot from v9 in the database tables to keep backwards compatibility but ouch.

Anyway, the point is build your own space so when you update the application it doesn't overwrite or undo your hard work.



Playing with frames

Frames are terribly versatile.   Placing them allow you to build in a strong pattern of re-usability.   You use it, of course, via the XFD designer like you would with any .NET component in an IDE. 
If fact, its obvious that the starlims folks went heavily out of their way to make it look and feel like a traditional programming interface.  Which is nice, of course, since making it look opposite would make it that much harder to code for -- no thanks that.

So, getting back on topic, XFD Frames can do a ton of heavy lifting for you, both in designing reuse and portability.  For example, instead of laying out a tab control and then coding for each tab and then having the user jump back and forth between them, you can do the same thing with frames, especially if you need to define some data that needs sharing between the figurative tabs.

Side frameBODY FRAME

For example, I like to keep the design of the LIMS I am working similar to webpages for user ease of learning and use.  In doing so, I tend to frame the left side into a console, like the one present in the default system.  When I don't, I put it above to form a menu of navigation.

The other frame I use to revolve information.  It becomes handy when you need to update or adjust links on the fly or to keep track of information handily.  While starlims provides form and navigator variables for such things, I find that putting defined variables there is pretty handy and much more accessible across multiple forms when I'm revolving them through the frames.  You can access them by concatenating the frame's name + form + variable.  Which, if you are re-using linklabels or buttons in your navigation, writing and re-writing dynamic events (probably the best use of the sender, event args duo I have found) based on the form in the other frame becomes very useful.

Referencing is done like follows:

var Base_Form : Form = form.Parent.ParentForm.Id;




  • id is the name of the base form
  • ParentForm is the form that contains the frame (i.e., its parent)
  • Parent is the frame
  • form is the actual form
So, if I wanted to grab a variable off the frame, I'd use form.Parent.ParentForm.Variables["NAME of Variable"] to get it.


User / Unit Association

Even though the UI will allow you to delete a site/lab- DON'T.
Actually removing a lab is a big pain in the culo, but if you do manage to remove all the associations to the lab, you can delete it. Your users will have to be associated with another lab AND unit. If not, you will still be allowed to delete and close the UI. You won't be able to get back in, but ...
So to fix the issue, go to the DB backend. The tables that you will be concerned with are SERVGANALYST, SERVGRPTYPES, SERVEGRP, BUILDINGS, LOCATIONS (lab) and ROOMS. On the up side, making mistakes gives one the opportunity to get intimately familiar with the application and database.

Wednesday, March 21, 2012

log in issues

Okay, I'm still not clear on why they happened to implement user restriction in the manner they did.  Still scratching my head.  I swear they sold us a development version instead of a production one. 

Okay.

Deep breath.  Whining done.

So, my co-developer and I are working on bringing starlims10 up to a running state.  We've been in its guts, turning things on and off, writing new code, etc.  So, we are finally ready to showcase a little of what we have been working on and we pull a few people in to test it out.  As they log in, we quickly find that they start getting an error message about UTC issues.

That started some head scratching. 

After some digging (global search works nicely!) we tracked it down.  Its was in the user session and connection procedures ran during the log in process.  The way it was written was...different.  It called itself, which is no problem: overloading is a natural programming function.  It didn't pass the right parameters, though, on the second call...which may have been why it was breaking: hard to tell.   A few modification, however, and we were in business.  Issue solved.

So if you start getting UTC error messages start looking in the user session and log in code.  You'll probably find your answer.

Sunday, February 26, 2012

Suggestion

If you are going to develop on STARLIMS i highly suggest you familiarize yourself with the interface and how it works first.

Tuesday, February 21, 2012

Positive starlims

As negative as I may seem at times about the app it does allow someone to tailor it, which puts it 10 steps farther than most.

Starlims Fun

So I'm sitting here with my fellow coder, who between the two of us we've probably got 30, maybe 40 of formal coding experience and we're frustrated.  See, Starlims v10 has all these neat, nifty ways to see the designer and its contents.  I mean we've got color coded code.  Nice crisp blue and red for starlims code (SSL) and different hues for Jscript.

Of course, we were trying to simply figure out how it had loaded a link list with data and that took, oh about an hour to figure its code check in and check out maze.  Hmmm, not the most intuitive necessarily or maybe I just haven't had enough coffee?  *shrugs*

We did finally figure it out, of course.  Through old fashioned trial and error.  You, know, click until you break something!  Which we did and found our answer.

So to get an application open, it looks like you:

  • Pull up the item in the Applications Explorer Pane
  • Which pulls it up in the XFD Design surface and editors pane
  • Then you find it in the Workspace Explorer Pane and right click to "check out" the item
  • Which puts you back in the XFD Design surface and editors pane with something you can edit

Simple as pi!

And, of course, the Using Starlims v10 IDE.pdf that comes with programs says, and I quote:

"With STARLIMS v10 ENTERPRISE DESIGNER you can do the following very easy and intuitively, with reduced development time and effort..."

Sigh.

Wednesday, February 15, 2012

since I feel like bitching...

like it says.

I always wonder why the hell they can't get someone to spell check their damn books.  I mean  few typos and grammatical mix ups are part and parcel of any kind of writing.  I don't mind a few and give generous allowance.  When it gets pathetic I start getting annoyed.  And pathetic, to me, means I start seeing one or more typos per page, page after page, in a professional document.

Really?  Come one!

random thoughts about the XFD

While I'm thinking about it (and yes, all these posts may show up in one day since i'm regenerating notes that I previously lost due to idiots) the XFD does have a few points I like.

The ability to mass comment or uncomment was seriously lacking in previous editions and having it now is pleasant.  Not too mention setting bookmarks.  geeze.  It took them forever to update this product but at least they have put in a nice thing or two.

Of course, in opposite to that, its obvious the version they sent me was in process of completion since its full of bugs.  Just like last time.

hmmm, first things first

Well, since I started down this road a few years ago, I've thought more than once I would document my time spent hammering my head against our LIMS software.

Guess the time has come.

I've administered, updated, coded and complained about a starlims v9 implementation for nearly 6 years.  Its been...a journey.  One I'm about to repeat, it looks like, as we migrate to a v10 app.

Of course, in typical fashion I was given no support, by my agency or otherwise (looking at all you'll dammit!) so I'm going it alone.

crossing my fingers that the installation will be more sunny than the past.