Within FileMaker code, it is possible to make literal evaluations. A simple line such as the following can cause significant problems within any FileMaker solution.
If [ Get ( LayoutName ) = "Customer Details"]
As soon as you change the name of the layout to "Customer Detail" (without the "s") your code is invalid. To solve this issue:
- No objects should be referenced within calculation code via literal strings or absolute names. The ObjectID custom function (created by Fabrice Nordmann) is used as an abstraction to reference FileMaker objects.
If [ Get ( LayoutName ) = ObjectID ( 37 ; "Layout" ; null ; null ) ] Go To Layout [ "XYZ" ]
If [ Get ( LayoutName ) = "People: Customer Details" ] Go To Layout [ "XYZ" ]
The good code above will self-adjust when the name of the Layout is arbitrarily changed - as opposed to the literal reference of "People: Customer Details".
Using the ObjectID custom function should not be handled in a direct manner. Your code should not make the calls shown above. The goal of these standards is to create clear reading code.
The ObjectID custom function should be added as a private custom function using the tilde nomenclature. ~ObjectID. This provides the immediate indication that this function is called by other functions and not directly.
This best practice suggests you use a "class prefix" for custom functions which deal with certain FileMaker elements. Here is an example of script code:
If [ Get ( LayoutName ) = LayoutIsCustomerDetails ] # Do layout specific steps here... End If
Where LayoutIs[nameOfLayout] is the prefix to all custom functions used to verify the layout. Here is the LayoutIsCustomerDetails custom function.
// Object reference to Customer Detail layout ~ObjectID ( 4 ; "Layout" ; Null ; Null )
Where the value of "4" was determined by using the same ~ObjectID custom function within the Data Viewer in the format of ~ObjectID ( "Customer Details" ; "Layout" ; Null ; Null ) in order to determine the internal id of the Layout.
To optimize the code even further, your LayoutIsCustomerDetails custom function would directly evaluate if the current layout was the right target.
// Custom function code Get ( LayoutName ) = ~ObjectID ( 4 ; "Layout" ; Null ; Null ) // Resulting script If step If [ LayoutIsCustomerDetails ] # Do layout specific steps here... End If
ObjectID Custom function on Github
Other literal references
Make note that, while titled Layout references, this information and the ObjectID custom function work with many other FileMaker objects, such as Tables, Fields, Scripts and Value lists. Use this information to accommodate these objects in a similar fashion.
This would be a possibility if there was an option in FM to see the ObjectIDs, otherwise the route to get these into calcs is quite convoluted, and it makes error checking MUCH more difficult.
Do any of the 'Inspector' for DDR software offerings give a list of ObjectID to YourNames
The goal of the above code is to remove the need for error checking. When you use Fabrice Nordman's ObjectID custom function you can change element names at will (just like you naturally do with FileMaker) because you have not "hard coded" any literal values of dynamicly changeable elements.
Here is my adapted version of his function
and the original
Anyone wishing to comment about this item should be familiar with that function.
I agree with the thinking behind this.
However it does make the code harder to read (and possibly write as well). "People: Customer List" has more immediate meaning than ObjectID ( 37; "Layout"; null; null ). It would need a solution or utility to keep track of the ID numbers and extra commenting within the calculation.
Is it worth adopting a naming convention for layouts that includes the ID? "People: Customer List (37)" ? Not to replace this standard but to easily associate an ID with a layout's name. Would it be practical for all objects?
Working with unbreakable references to objects is a worthy goal to aspire to, but it does make code MUCH harder to read, and using ObjectIDs (LayoutIDs, FieldIDs, TableIDs, etc.) isn't actually unbreakable. Cloning a file will re-write all the ObjectIDs, and then you wont have the names to know what they were attached to when everything worked.
True on the clone issue, of course, when developing, a backup script should always be used as if it were Command-S (mac) or Control-S (win).
How about modifying the convention? I didn't put enforced comments within this one because I figured people would already do that. Something like this?
The only problem is that when you end up changing the name down the road you've got the same issue. Now your comment about the layout name does not match and has to be updated.
Personally, I do add a comment with the layout name, it's the "literal reference" that we want to avoid at all costs, especially in a larger solution. Being able to change FileMaker elements, yet take advantage of more advanced coding stands in contrast with each other, especially when you use literal references.
An ObjectID is just as literal a reference as a name. Avoiding literal references is generally a behavior consistent with writing de-coupled code, which is great. But referencing an object by ID still links that code to one specific object; it isn't any less coupled whatsoever. To be less coupled, the code either needs to be passed references to objects it will be working with, or infer those references based on context — the choice of name vs. ID is irrelevant to that aspect of the code.
The differences that matter to me are...
• References by name and by ID break under different circumstances.
- I clone files all the time. I clone files in backups. I clone files when migrating clients to newer versions of their own systems. I clone files when I use one system as the starting point for another. I clone files on Sundays when I have nothing else to do. With a solid set of naming conventions, I don't re-name things quite as often over the lifetime of a file. When I do re-name an object, it breaks name references to that one object, and I probably have a good idea where to look to fix them. When I clone a file, object ID references could get broken anywhere and everywhere — even if nothing breaks, I'll lose sleep thinking about what references might exist that I forgot to test.
• References by name are self-documenting.
- When I'm debugging something, and I find that "People: Customer List" is no longer the correct layout name, I can guess that I changed it to "Person: Customer List" pretty easily. Making the jump from "37" to "24" is a bit more difficult; even if it's commented, I have to go further out of my way to find what the new ID for that object is. References by name are just easier to read. Look ma, no comments!
• References by name are just as easy with as without helping custom functions.
Man, I thought I would never disagree with My Hero Matt, but I'm going to have to side with Jeremy on this one. This is too fragile to rely on. What if YOUR CUSTOMER cloned a file? What a support nightmare!
jonathan at fletcher data dot com
I'm wondering if you guys have used this custom function before and tested against a clone. FileMaker does not randomly change its internal id values associated to elements when making a clone - at least to my knowledge. That would be chaos.
The issue you would come across is if two different versions were coded against and you tried to merge the results. It's only when you delete an item (such as a script) and then create another item with the same name that the internal ID will not match against code.
FileMaker does not reset internal IDs when you clone a file. It keeps moving the number forward.
I'm using the ObjectID function on my Theme Studio and my standard deployment is to create a clone and then import data for each release. I've not had one single issue with using this custom function for the 200+ beta testers currently using it.
In other words, please show me a pair of files where you've broken the functionality of how ObjectID works. I've not been able to do it. - Then again, I'm not working against all possible situations.
I've been trying, but so far, I can't get a clone to change ObjectIDs. You're right.
Looks like you've right on this one Matt - major relief that it is not broken on clones.
Maybe FileMaker will give us the objectID in the Inspector palette in some future version.
What about this -- in your Startup file for your solution, have a section which "declares" correspondences between global variable names such as $$L_CustomerFormLayout and "CUST_FORM" (the layout is assumed to be titled CUST_FORM at this moment) and then tie all scripts, buttons, etc to the global variable rather than the explicit layout name. If you later on decide to change the actual layout name to CST_FORM you simply change one line in your Startup file so the target of the global variable is the new layout name and your whole solution will then track with the change to the global variable's value. Doing it this way also allows you at any point to change your mind and decide a new or different layout makes more sense as a customer form layout, or even dynamically allow a user to override a default choice of form or list layout and select another one, with the whole solution tracking that change in preference for them alone without messing up anyone else.
Charles South (nobody special)
So your proposing a method of substituting for FileMaker's built-in layout IDs with our own mechanism. Very interesting. Security hawks will complain about storing names of potentially off-limits layouts in variables, so it might be better to base the method on custom functions instead. (I concede that my go-to solution for most problems is "just wrap it in a custom function".) One CF-based approach would be to have one function passed one argument with some name for the layout that you are never allowed to change, which returns the actual layout name, which needs to be updated only in the function; Ex: GetLayoutName ( "CustomerFormLayout" ) = "CUST_FORM". Usage is exactly the same as the ObjectID method, except that it's more self-documenting. Another CF-based approach would be to have a separate CF for every layout name you want to address; Ex: CustomerFormLayout = "CUST_FORM". Then you could change the name of the custom function at will, and all references to it will update automatically as if you made a direct reference to the layout rather than by its calculated name, which I think more closely matches the real goal. On the other hand, that could be a LOT of custom functions.
This method also requires developers to remember to update index of layout names, wherever it is, every time a layout name is changed. I like investing in patterns that are robust against forgetful developers — the less I have to remember how I solved one problem, the more attention I can devote to solving others. Perhaps a hybrid of the ObjectID and self-documenting references could work? The global variable or CF would be a wrapper for the ObjectID ( <layoutNumber> ; "Layout" ; null ; null ) function.
I like the direction here. The ultimate goal is to avoid (at all costs) any literal references within code. Primarily because FileMaker does not provide any type of find/replace for code or refactoring.
If we did create wrappers around ObjectID we would simply add something like LayoutIs[name] where LayoutIs becomes the reserved keyword (or class) of custom functions.
I think this would work well since there is a tightly bound knowledge required of a solution and its distinct areas. Using this class prefix gives me the immediate hint of what I'm dealing with. I don't have to know exactly which layout is LayoutIsCustomerWorkOrders if I know that it's pretty much the singular place in the solution where a user interacts with that type of layout. And, obviously, I'll be able to contrast that against LayoutIsCustomerInvoices.
To follow along with other ObjectID options, we would end up using TableIs[name], FieldIs[name], ScriptIs[name] and ValueListIs[name]. If we don't like the Is part because it might look like ls then we can use NameIs like LayoutNameIs[name].
Granted, there will be some who will complain that we're using abusing custom functions for the sake of the solution, but I'm all for this. I love the singular nature of custom functions. It's what makes working in FileMaker tolerable.
Actually, thinking about this makes me excited enough to want to start doing it to my solution right now. We would privatize ObjectID to ~ObjectID and gain the advantage of some really clear code!
What do you guys think about this? Nice idea spark Charles (always somebody special)
As I begin doing more FMGO development, this concept becomes more challenging because of the nature of the i(Tool)(Pod, Pad, Phone). Because of the smaller screen real estate, I find I need to create a lot more layouts. This would significantly increase the number of CFs and add more work. I'm seeing the benefit of never using a literal reference, but I believe the overhead to be too great.
jesse at swensens dot net
Hmnnnn, "more challenging" as in resistance because of the perceived challenge or actual literal complicated challenges. We would also need to define "overhead". In most solutions, the "overhead" is the difficulty in making heads or tails out of what does what. With strong conventions you solve a lot of these issues.
When it comes to the amount (or volume) of code, I'm not as concerned with the volume as much as I am the clarity of code - and the adherence to understood conventions. With a group of 20 custom functions all prefixed with LayoutIs it is quite easy to search within that list of 20. The same would apply to 50 or even 100.
Typically, as my understanding of voluminous amounts of code goes, it's the randomness within that volume that can be disconcerting. If the convention is clear and states how it works, then it becomes quite easy to follow and decipher.
Ultimately, any code within FileMaker will reside somewhere. It may be in the auto-enter calc, a script, within conditional formatting or in a custom function.
The biggest perceived issue is that "custom functions" are just for functions - similar to Int() and Floor() and general things you can use anywhere. For some developers, it becomes uncomfortable when they start comingling UI based functions with "general functions".
The best approach, in my opinion, to custom functions is not as custom functions, but rather part of the whole "code base" that runs your FileMaker solution. The Custom Functions area is the one place that centralized code can be ... well, centralized. And that's a very good thing when you're trying to follow DRY (Don't Repeat Yourself) principles.
Along similar lines to what Matt is saying, there's going to be overhead either way. Would you rather have the overhead now, when you can predict how long it will take and you still remember where everything is; or later, when you not only have to fix a problem, but also re-discover what you were thinking the first time. (When you update a custom function, all references to it also update.) Dealing with it now is the lesser of the two overheads. Don't think of it as creating a whole bunch of functions (most of the labor is basic copy-and-paste anyway), but as creating a set of named global constants.
This is a problem that bugs me to no end. We need a GetLayoutNumber(LayoutReference) function. But alas we don't have it. I am thinking about registering Layouts in either a table or a global on startup. There may be some interesting things you do if you had them in a table. You could add other bits of meta data. etc. With FM 12 a table becomes possible using SQL. I expect that SQL would be slower but not noticeably so.
You might be interested in Crumb, a navigation framework I developed that parses all the layout names in a file to (meta)data in global variables used to drive navigation. The re-write for FileMaker 12 is a work in progress, but I found the FileMaker 11 version pretty solid in the solutions I used it in. The FileMaker 12 version makes better use of layout IDs, has a simpler data structure, and is more thoroughly tested; I just have to make it pretty and write the documentation. (And make some progress on the roadmap, including support for better iOS behaviors in particular.)
I have problem with global vars. I hate them. Every language that has them actively discourages their use. They do so for a reason. I would even go so far to say that they are an anti-pattern.
I understand that there are cases where there are no other options. I try to stick to those cases. And if I can avoid their use I do.
I think ExecuteSQL provides a way to deal with a class of cases where the data being stored in globals is being updated infrequently, ie on start up for example. See my other comment on this below
Curious. What situations would you use Global vars? Because I know of many developers that use loads of them. Not that I'm disagreeing with you...but I always like to hear others views pro/con for anything.
I can definitely see some security issues with certain uses of them. Since users can change them on the fly.
I will use them as part of a scripted process that cleans up and removes them when the process is done. I don't mind them as much in that case since they are only transient. As long as the setup and cleanup of such a process is well managed they can be OK. Not great but OK.
Its the ones that hang around as configuration options, user interface, or some other kind of application state that I really don't like. They just add clutter and noise and makes the system harder to understand. Once those $$Vars get out there in your code they are hard to maintain. Too hard to find and correct if you need to.
Sometimes you have no choice, like when you need the variable name to be dynamic. When I have no choice, I try to bundle them together into a single $$variable, stored as name value pairs for example. This removes some of the evil, by giving you a small amount of encapsulation.
Not if they don't have Advanced??
I use them for path related functions - find it much easier to use $$docsPath, $$tempPath & $$osPath having set them up when the file first opens. Also $$SEP for the os specific file seperator character, avoids lots of escaping slashes - and getting it wrong
The problem with Global variables is not really about end users. Its about developers. They are hard to maintain, especially when a solution is being developed by more than one person.
Yes it is easier to do as you suggest. But over time this becomes difficult to deal with. This is pretty much the definition of an Anti Pattern. Its so easy you don't notice your screwed, until you are.
Again, its not that I don't use them. But I try not to. In your case, the stuff you are referencing is all environment related, that is, it can only be determined at runtime. So you couldn't use a Table. But you could store all of those strings in a single $$ENV variable. This would be my preference.
ExecuteSQL brings us some new options here. I am experimenting with what I call a Layout Registry Table. Layouts are registered by storing the Layout ID and and a "reference" that doesn't change. For example the Layout named "Customer Details", would be registered as "CustomerDetails", and it would have it's ID stored along with it. Like so.
ExecuteSQL allows me to query this table from anywhere and get either an ID, which I can turn into a LayoutNumber with Design Functions, or get the reference of the current layout.
The Layout Registry Table is populated by a Register Layouts Script. That script goes to each layout you need to register using Go To Layout ( Layout ), gathers the ID and reference and stores it in the table. This script only runs when changes need to be made to the Layout Registry. It is self maintaining, so you can run it at any time.
Finally there is a Custom Function LayoutGetNumberByReference( reference ) that returns the LayoutNumber for the Reference. That function uses ExecuteSQL to get the ID from the Layout Registry and Transform it into LayoutNumber by using the Design Functions. I use the LayoutID to protect agains changes in Layout Order.
Although I am currently using this only as a way to get some indirection with LayoutNames, I can see the potential of storing other Meta Information in this table as well; perhaps things like Navigation groups.