Showing posts with label v10. Show all posts
Showing posts with label v10. Show all posts

Monday, February 25, 2013

Check before jumping

I took this from the client script utilities in the Validators.

function csValidateFormExists(strValue) {
    try {
        var ids = lims.CallServer("Enterprise_Data_Providers.FormProvider.GetIds", strValue.Split("."));       
        return ids && ids.length >= 3;
    }catch(e) {
        return false;
    }
}

I find it very useful to check to make sure the form I'm about to call actually exists.  Especially when I am jumping applications.

Tuesday, February 19, 2013

TSQL function "IDENT_Current"

I find this TSQL function to be terribly useful, especially in server calls where I need to insert a record and then need its Origrec.

To use it, do an insert into a table


bSuccess := SqlExecute("INSERT INTO TABLENAME...");

then, to find out the Origrec of the row you just entered, use:

 iOrigRec := SqlExecute("SELECT IDENT_Current('TABLENAME')");


Form.isDirty

Found this one the other day.  Its a Boolean variable that flips when something changes on the form.  The problem is it seems almost everything let's it change.  Honestly I prefer to just set a variable I can control, usually as a form variable or navigator variable.  It just seems easier.



Wednesday, February 6, 2013

Source Control Manager

So,

when the day comes that you mess up a form accidentally, please do better than I.  Instead of recreating the code in the form, go to the Source Control Manager and recover it!

I'm not sure why I forgot Starlims had that capability but it does.

The SCM is under the Utilities tab in the Console and you can compare versions, recover previous versions or create labels to attach to them.

It also lets you look at everything you have checked out in in a tree view.  Very handy if you have a lot of things checked out and need to check them back in.

You can also export items from this location as well.

Friday, February 1, 2013

LimsTransactions

Just a short note on these two short but very important pieces of code.

BeginLimsTransaction()

EndLimsTransaction()

Both of these fellow are worth their weight in gold when it comes to performing updates, inserts or deletes.  Like they sound like, they wrap your transactions to the database.

Use them!  

Even for single actions, use them!  Most especially, though, use them for anything complex or anything with dependencies.


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.