Kirk Brooks: Thank you for your kind words. My son (Adam Bevan) and I have multiple motivation for writing our shell the way we are. ‘Bleed Through/Butterfly effect’ were one of them. It is likely that our very large application we previously had showed us that it can bite one too easily without strict discipline in coding.
Once we got comfortable with v12 we started creating forms with dynamic variables. With the multiple ongoing jurisdictional requirement changes and certifications we just never got enough time to implement them much. Therefore when rewriting the shell which I usually did ever ~ 5 years, we are doing the most ambitious rewrite. Every line of code is being rewritten in a clean room concept. We want to encourage ourselves to write all the code with the latest tools 4D provides us. Another reason for not using process Variables is to reduce the memory foot print. As every process variable no matter how little it is used gets memory reserved for it in every process, no matter how small the process is or little the process is. It is always easy to slip in to old tried and proven paths (ruts) when coding. There are times when we have slipped into putting in some process variables. I just looked at our COMPILER_Prov_Var method. We currently have 7 Process Variables. I know we had more but as the shell reaches its feature set we are removing them. I understand what you are saying about flag variables. It is so easy and convenient to use a process variable for this. On our journey of writing the shell we have done that. At times though we have reassessed our use of them. It made us take some time and say how else can we do this. Our Object module is evolving very nicely. We are getting very used to putting flags, and many other things into these objects and then looking for them there. It is becoming natural to do so now. One of the first items that Adam built was dot notation object handlers (Set/Get/Clear). I am sure he could have used Cannon’s highly rated module, but he wanted to do for himself. The next was to create an Object Viewer. The Object Viewer supports viewing a whole tree in the dot notation, and diving deeper into the tree to focus on specifics. It also permits us to modify the object values which is a huge time saver when initially developing a new feature. A side benefit of the Object Viewer is that it is available in a compiled application. Therefore all of these flags, etc are available to be viewed at runtime in the compiled application. So great to getting a view of all this management information that we have. We are building our shell to handle all of the things that we encountered in our previous regulatory world out of the box. As we encountered back then there are jurisdictions that have little regulation so users would opt for little controls (typically a small 2 user site where trust was implicit). Therefore the shell is designed to have items turned on or off. All of this control data is stored in a few objects so that they are quick to access and modify. We something strange happens it is easy for us to at least see what the settings are. We are also including many of the wonderful interface features that we accumulated over the years in our previous shell. These are things that I would hate not to have if I was working with the system. This includes things like the size and position of a window that the user likes for that specific window. How many items in a popup list they want as a maximum, after which it will be a scrollable selection dialog. There are literally hundreds of these options / features that we are building in. As a user logs out and another use in, the objects are changed out and the application behaves differently. The Object feature of 4D makes this all very possible and efficient (we used ObjectTools in the past). There are several forms. The first three are the most heavily used: - Single Output Form. - Included Button Tool Bar form - Alert/Request/Confirm Dialog Single Output Form There is only one Output form for the whole application. We use selection Listbox. Of course every table will have different possible fields to be loaded into the output form. Not only that each person will have a different requirement for which fields they are permitted to see, or would like to see. In addition to this there may be calculated values (non-field data) that they would like displayed. Therefore like all our listboxes in the shell they are built dynamically via code. This ability lets the users feel like they have control over the application rather than the other way around. The system administrator sets up default output forms for each role of users. Therefore if they are not so included to set them up they are done for them. We also have a ‘factor setting’ so that even the System Administrator does not have to do this work. With all the controls available for controlling listboxes we can provide many user controllable features (min / max width), default width, alternate colours, font, font style, font size, sort orders, column positions, and many more. Of course underneath all of this is the security of records. Who can view who cannot view, are there fields of data they cannot view etc. This is all handled for all the tables with the one Output Form. We also have many standard features that are available on the form. Security underpins it all and removes the items that the user does not have access to. Therefore a user with little access will always have a very simple form which means much less training for them, less clutter for them to filter out. Many of these features will automatically appear once the user adds an item (saved search, saves sets, saved reports, etc). The user / system Admin can determine which users have access to these items as well. The beauty of this is that all tables have exactly the same appearance as all others - simply because there is only one form. If there is a bug we fix it once - done. If we add a feature we only need to add it in one place - it is everywhere. This is the shell we plan on using to develop other applications for. It is only good as a shell if it saves time. The Single Output Form is a huge time saver in so many ways. The form itself has zero process variables or interprocess variables. All flags etc are stored in our standard objects. Therefore once you get to know where this is you know exactly where to look. I am writing the programmers manual for the shell as we write the shell. Inherited Button Tool Bar Form This really should be considered as much more than just the button tool bar, but that is the biggest part. We could have gone the way of having a single form for the whole application (like the Output), but for now at least, we decided not to go that far with it. Input forms can have such dramatically different features. Having said that we know from training and supporting thousands of users on our last system that consistency is critical to faster learning, more complete usage, and fewer support calls. To this end having a specific button bar is key, (with custom designed buttons). Again the same data in the objects is used to control the visibility of the various buttons, popups, administrative tools in the input. As you might be getting, security is a foundational concern. Therefore which fields show up for a specific user or user’s role is controlled by the information in the objects. This is where we have a strict object naming convention for our fields, and labels. By doing this when the security code is gone through the appropriate fields, labels, groups, etc are made visible, non-visible, enterable, non-enterable. We recognized that the same form may be opened in different processes, and in some cases within the same process. Therefore our object structure needed to support this. One of the key ideas was that we have a process object. This object contains process specific items. Which forms open, sets, named selections, form open, window Ids (important for Call Worker / Call Form), Process Ids, Table number, table name, Status of the form, status of specific form objects, and so much more. Having all this information in one easy to access location makes coding issues so much easier to handle. Trying to write this for each input form would be a huge job. By writing it from scratch with this in mind from the beginning makes this a joy when creating a new input form. Our ‘Inherited input Form’ makes so much of the work easier. It even has built in vertical and horizontal guides for helping us place objects on each form in a consistent manner. Really all we need to deal with on an Input form is the table / form specific items. This saves us lots of time - a key requirement of a shell. Alert/Request/Confirm Dialog We have one form for these three types of dialogs. Each part of the form is ‘controlled’ by the data in our Parameter Object. - Window Type, Window Title, Which of the 4 buttons to show (with their title), Message to User, User Response, Button Access to Help, Which button clicked, Response by the user. We have a DEV Macro that sets this all up so setting up the code for a dialog and for responding to the dialog takes 2 seconds to place the code. Then just set up the specifics in the code by highlighting and changing as appropriate. The form has no process variables. This ensures there is no butterfly effect ever. The code is easy to write as we just get a pointer to each object that we know what the name of the object is. The form itself will change it’s size up to a defined limit if the message to or from the user is longer text. The code that sets up the control for the dialog creates a sub object within the process object. Once the response from the user is obtained by the calling method that specific object is deleted from memory. This way there is not a problem with multiple dialogs open within the whole application or within a process. These are just some of the forms we do this with. We have several others that we have developed within the shell. Of course when an application is created with the shell the same approach will be taken. re: I caught a lot of flak last year when I suggested process variables had > become passe in favor of a single process object for managing the random > bits of information they are usually used for. I am not certain if I understand you or you understand me. I do not think that for other than some forms we have gone to not having field information on it. We have though removed all process variables by placing form variables on screen that do not get an object name instead of a variable name. With some of our forms it is true that the data on the form is contained in an object and the form is really just a temporary container for that object information. An example where we do this is for all the Preferences we have in the system. There are hundreds of them. Some are boolean, some are ‘pictures’, others colours, numbers, and text. All of the preferences are stored in an object. Therefore the form when displaying them is just displaying what is in the object. The Listbox with the 4D View Pro feature set of being able to have an object column that displays different controls based on the type of data in the object is a great enhancement to the user interface. Way back in v12 we had 50 pages of preferences (System Admin, Organization, Site, Computer, and User) all totalled. The feature set of Listboxes in 16R2 is critical to being able to do all of this - as it is all dynamic within code. Now we have one preference page for each of the types. On each page is a single Listbox with a Group selector popup. We may move this onto a single input form just changing the titles etc in the future. For now though we see great advantage in time saving, and simplicity for the user over what we had previously. Now we can add a new preference and it will typically just show up in the appropriate place. No extra space on a form, no coding on the form to handle it. We wanted this as our vision for the shell is that it will be used to create many different application - simple to very sophisticated, from one user to thousands of users at once. Our goal is that we can use the shell to jump start a project with all of these features while not burdening them down with things they do not want. I cannot see any reason to have a process variable on a form any more. Now that we have our Object Viewer and the underlying Object ‘Dynamic Library’ there is no need. Too many advantages in doing it this way compared to a variable on a form. re: Are you willing to talk about how you manage the fields and data in the > process object? I suppose I have my head too deep into our project to understand what you are asking for. It now just all seems so obvious. Having said that if you talk to my son a year ago he would likely have expressed frustration by my lack of getting it. It is a paradigm shift working with objects for storing the data rather than variables. re: This opens up a lot of options for things like inherited forms too as I > > think of it. Instead of having different inherited forms in different sizes > there can be one and the objects simply sized as appropriate. Absolutely! This way of coding reduces the number of forms greatly. I expect that the memory footprint used by applications developed by the shell will be significantly reduced from what it would be. re: What are some examples of the generic form methods you are using? There are many that we have. So many that we created a DEV Macro palette that we use while coding. When we need a new Input form we just select the macro for that type of a form. The code is in. If we need a listbox on a page we just use the DEV Macro Pallet to select the code for building a listbox. We currently have many generic methods. As this is the shell most of our code is generic. Some examples are: - STD_inputMethod: This method calls a whole set of other generic methods. Based on object data, and object names it will do things like: - Set up the form help caller, add to the count of that user using the form - Handle security of access to the form (view/modify/delete), same for field access, Audit trail of access, window title, colours field background colours, Setup Menu, button access (and more). All of these tasks I mention are controlled by generic methods. Therefore the code is done, do not need to be touched for this code to be written for the form. LOVE THIS! - Logging: This code is very standardized. This is a lesson we learned hard over the last 30 years. Logging of our code progress is critical for finding crashing, or problems. Therefore all our Dev Macros have calls to our logging. We have three types of logging that we do (beyond what is built into 4D itself). The logging can be turned on or off as required. As well we can set the level of the logging. Of course the logs can be sent back to us automatically. The importance of logs cannot be understated. LOVE THIS - even now while still programming! - Listbox Setup: We have a few lines of code that we paste in from the DEV_Macros to setup a listbox. Need more columns, just copy a line, and adjust a few strings in the line. - Listbox Configuration by User: This is all built into the Listbox Setup. The method for this is called automatically from within the Listbox. The user’s configuration for the listbox is all stored for each listbox for that user. They set it once and they have it just the way they like it. So jurisdiction requirements, user preferences, business type, business needs etc. All of this is handled within the generic code of our listbox. LOVE THIS! - Clairvoyance for fields. This is a single set of code that is generic (of course). Pasting in from the Dev Macro and change a few strings and it is set up. It handles which lists, show description, or code, and much more. Very sophisticated. The whole feature set of the Clairvoyance fields was one of the favourite among our user base. Though not really clairvoyance, our date handler for fields is also very good. No more improperly entered dates / times, yet entering them is very very quick. As I say there are many more. I just looked quickly at an input form. We have not written any application specific code as of yet so most of the code we have written are generic. This is all done without any process variables (other than the few I mentioned). We will delete these as we needed them initially to build and test our Object code, and other generic code. Now that we have the objects feature sophisticated enough, we do not need these process variables. The only Interprocess variables are the objects. Kirk, I hope this is what you were looking for. Sincerely Jody > On Jun 18, 2017, at 3:13 PM, Kirk Brooks via 4D_Tech <4d_tech@lists.4d.com> > wrote: > > Jody, > > Nice post. I've also implemented several of these ideas but you're really > going more deeply than I have. My motivation didn't have anything to do > with bleed through. They came about as I was working more and more with > objects and dynamic variables. > > On Sat, Jun 17, 2017 at 2:35 PM, Jody Bevan via 4D_Tech < > 4d_tech@lists.4d.com> wrote: > >> 1. On all forms we never use variables. >> > Assuming you're talking about process variables. I agree mostly but there > are some times when they are still useful. Most recently as a dataChanged > flag. But that could be stored in the process object you touch on later. > >> >> 2. We have a generic form that has several objects on it. At runtime we >> will make objects visible or not, and change their titles, positions, etc. >> This permits us to have one form that handles Alerts, Requests, and >> Confirms. We provide more features than that with these dialogs (like help >> buttons, trace, etc). This means this dialog gets used extensively. >> > I've played with this idea a bit too. Could you talk more about what kinds > of forms or records you're using this for? They are easy enough to > implement in concept but to make them look nice and such has been a > challenge for me. > > 4. Dialogs that a process call up though will need to get the input from >> the user. We accomplish this through an object we call our Process Object. >> Inside the process object a object is created for every running process. >> When a form is opened within the process an object is created for that >> form. When a dialog is opened an object is created within that form object. >> There we set what all the dialog objects are to be, and we also return the >> results from the user input. The form that called the dialog gets the >> information it is looking for from this object. Once the information is >> obtained that dialog object is deleted from memory. >> > I caught a lot of flak last year when I suggested process variables had > become passe in favor of a single process object for managing the random > bits of information they are usually used for. I must admit I hadn't > thought about this approach. I like it. The form becomes more like a > reflection of the data (stored in the object) instead of the data actually > residing on the form. > > Are you willing to talk about how you manage the fields and data in the > process object? > > This opens up a lot of options for things like inherited forms too as I > think of it. Instead of having different inherited forms in different sizes > there can be one and the objects simply sized as appropriate. > >> >> It seems complex and it would be if we had to write the code each time. We >> have written nice generic methods for its use. We also created macros as we >> do for all this stuff that makes it very quick to build a request dialog >> (for example) in our code. >> > What are some examples of the generic form methods you are using? > > You know, this also allows for you to create a hell of an error log - you > could write the process object to the error record in addition to the > other stuff. > > -- > Kirk Brooks > San Francisco, CA ********************************************************************** 4D Internet Users Group (4D iNUG) FAQ: http://lists.4d.com/faqnug.html Archive: http://lists.4d.com/archives.html Options: http://lists.4d.com/mailman/options/4d_tech Unsub: mailto:4d_tech-unsubscr...@lists.4d.com **********************************************************************