Best Practices for Shared Customizable Macros?

Most of the macros I share are "use as is" macros. But a couple, and one in particular, allow a fair amount of user customization. The one in particular is the web search via abbreviations macro, where pretty much every user has their own list of preferred shortcuts.

I have the macro set up with all the user customizations in a separate sub-macro. Still, this means that to upgrade, a user must:

  1. Install the new macro.
  2. Open two KM editor windows (ideally), one with the old macro selected, one with the new.
  3. Copy the contents of four separate variable definition boxes (that hold their modified content) from the old macro, and paste into the new macro.

This is a bit of a pain, so I've been thinking about how to make it easier. Unfortunately, I can only come up with one solution, and it's still not ideal. I could store their settings in a few global variables, instead of in the macro itself. When they launch the macro, it would check to see if there are any new entries in the user area, and if so, make the proper changes (add or remove) to the saved variables.

The upside of this method is that the variables would survive a macro upgrade, so there wouldn't be any work to do when they upgrade. The downside is that they can't easily see the list of things they've changed, as they can now by browsing the set variable actions.

The only way I see around that is to write an entire editor that would show their customized items, and allow them to do all adds, deletes, and changes from that screen. And the only way I can see to do that is via a whole bunch of JavaScript running in a Custom HTML Prompt action ...and that sounds like a ton of work.

Is there some other way to make the upgrade process easier for existing users with customized content?

thanks;
-rob.

3 Likes

This is way beyond my expertise but sounds like something @DanThomas might have some input on since he shares a lot of highly customizable macros.

Yep, it's hard to decide what to do in these cases.

Something I'm working on right now has the user execute a macro with KM 10 subroutine parameters. Here's an example, and I'll comment more below:

2022-01-14_12-40-31

The names aren't ideal, but I was trying to include a little "help" at the same time. And ignore the macro name with the prefix number.

When I make changes, they still keep their options. But it's only one way to do it.


You can take a look at my Spotlight Search Prompt, where I have the user pass options like this:

If I were to write that now, I'd store all their options in one JSON variable, and provide some way to make it easy for them to set them up, probably with an Execute Subroutine like before.

That's a start. If I remember any other ways I've done it, I'll let you know.

1 Like

@griffman, this is a good question and I'll certainly be tracking this thread.

But on the topic of Global Variables...

As I'm sure you know (but some KM beginners, might not) one should avoid large quantities of data (e.g., long lists) in global variables.

For global variables, I use the following naming convention:

jsXYZ__some description

where:

  • js : my initials
  • XYZ : two or three letters indicating the macro (or group of macros)
  • Since these variables are often used in dialogs, I use two underscores before the description.

For example, I have a macro named Log It and one of the global variables is:

jsLI__Add Timestamp Prefix

This groups the variables nicely so I can see (and maybe delete) them in the KM Preferences.

In general, you always want to separate the code from the data. For Keyboard Maestro macros that might mean using a global variable or a text file for persistent settings.

For A Few Favorites, the Keyboard Maestro launcher I wrote, I used separate text files they user can edit to include their own favorites. That can be a lot of data and therefore better kept out of a set of global variables.

But for Brevis, which does keyboard expansions, one global formatted as JSON strings, saves all the unique text expansions any user sets up.

So there are two approaches I've used that isolate my code from a user's data that have worked well.

1 Like

Yea, the length is one of my concerns, though I'm not sure how long is too long. The macro includes a list of 44 shortcuts (which are an abbreviation, description, and URL), totaling about 4300 characters. I don't know if most users will add any more than 10 to 20; it seems if you have so many shortcuts you need a tool to remember them, then you're using too many :).

I thought about saving to a file, too, but that opens a can of worms for sync and write permissions and who knows what else.

-rob.

1 Like

Where did you store the files, and how does that work if they have more than one Mac? That's one of my concerns with file-based storage.

-rob.

1 Like

I KMFAM, I have a "resources" folder. People who want to "sync" their settings with other computers store it on a shared drive, like DropBox or iCloud.

@peternlewis I've been thinking about this recently - the idea of having a place for storing KM data. Some sort of file/folder structure that you'd sanction, with UUIDs or something like that to keep people from overwriting each other's data. And it would be something that could sync along with KM settings. I don't know if you've had any thoughts on something like this.

1 Like

I'd love it if I could write their responses to (some form of input gathering) directly into the text area of a Set Variable to Text action. Then they could interact with my (input gathering method), yet still see what they did directly in the user portion of the macro.

Maybe I'll offer it in two forms—check to see if files exist, and use them if they do, but otherwise, stick to my temporary variables.

It's a bit of a quandary, trying to find the best way to do this for the users.

-rob.

1 Like

I'm not sure how that would work, but it's probably doable if you really thought it was a good idea.

I was also thinking that rather than storing these types of things in a variable, a dictionary might be the better format. It's a key/value structure which is kind of ideal for this. And it translates well to/from JSON.

For example, this:

image

Is simply this:

{
  "Key1": "If this isn't showing in a grid, make sure to turn off JSON view mode.",
  "Key2": "Double-click to edit. Enter to save, or Esc to cancel.",
  "Key3": "Click the Help button (the \"question mark\") for more information.",
  "Key4": "test"
}
2 Likes

I prompt for the folder they would like to store their files and save that as a global. Once the global is created, the prompt is skipped.

3 Likes

I'm sure you already know this, but there's a Prompt for File / Prompt for Folder action in KM now. If you're anything like me, you've got code like that Applescript code you haven't touched in years, but if you care, there's a native way to do it now. :slightly_smiling_face:

5 Likes

Good point. This macro predates that addition but I wanted to illustrate the concept with working code. Namely, you can let users store data anywhere they like as long as they tell your macro about it.

3 Likes

I looked at dictionaires, albeit briefly, but I don't think it'd work well—I'd have to iterate through all the keys to find a match, at least as I understand how the dictionaries work. Right now, it's just one large text block that can be searched—incredibly quickly—with a single regex.

-rob.

1 Like

Keyboard Maestro settings do not sync. Only the Keyboard Maestro Macros.plist file syncs.

Therefore you can save the setting anywhere you want. I’d suggest the Documents folder, but if you are brave, you could create a custom folder inside the Keyboard Maestro preferences folder, just make sure it is not something I'll ever use. Or you could store stuff in the Application Support folder itself.

But again, I’d suggest the Documents folder.

2 Likes

The reason I suggested dictionaries, and I was actually just thinking out loud, is that I thought you were storing user-defined settings or values. So with a dictionary, you give the setting a name, and store its value.

Something like this dictionary, which I've named MyParameters:

image

 
Then if you wanted the value of the parameter called "htmlFilePath", you access it with a token, like this:

image

 
Again, just thinking out loud.

2 Likes

I'm storing user-defined shortcuts, which are a string like this:

shortcut•description•URL with {search} placeholder

When the user enters a shortcut and search string in the input box, I use regex to extract the match and replace the {search} with their entered search string. If I were to try to do this using dictionaries, I guess the shortcut would be the key, but I'd still have to parse the description and URL, and I can't imagine that'd be any faster than how I'm doing it now.

The main challenge is just figuring out where to store it for the easiest upgrades. I'm leaning to using the filesystem, and will experiment with that this weekend.

-rob.

1 Like

That makes total sense.

If you actually want to change the content of a specific "set variable to text" action, I've got an idea of how to do it. I'm not saying that changing the action is a good or bad idea, but if it ends up being something you want to try, let me know and I can write some code.

I figure you could identify the action to be changed by putting a specific UUID in the "Set Note" for the action, and I could search for it.

Anyway, something to think about.

1 Like

It would require more editing on my part before releasing an update to use UUIDs, because they change with every release. But it wouldn't be awful, there are only two fields that they use. So yea, I'd be interested in something that would take a user's input and append it directly in a variable assignment box—I could see that being useful in a number of situations where it'd be easier for the user to type in an onscreen input box instead of mucking about in the editor.

Edit: The other advantage would be error trapping—I could validate the addition to the variable before it's stored, instead of having to notify them of an error that they then have to find and fix.

-rob.

1 Like