Software > Keyboard Maestro Browser Control (Advanced)

Keyboard Maestro Browser Control (Advanced)

Keyboard Maestro Browser Control (Advanced)

Introduction

In the first part of this tutorial, we explored Keyboard Maestro's browser control actions and learned how to fill out and submit basic forms. Now it's time to try putting together something more advanced. We'll see how to interact with webpages and how to improve and control a web application.

I won't just show you what works, though. Instead we'll build macros like we would in real life, so we'll also run into problems and errors, and we'll see how to solve or work around them. So I recommend you not to just read, but to build the macros and test them along with the tutorial.

Is this tutorial for you?

Again, if you're a web developer, you probably learned everything you need to know about controlling web pages in the first part of the tutorial, but you might still learn here a few things about KM itself. If you're not a developer but have some understanding of how HTML and JavaScript work, then this second part was definitely made for you. Finally, if you have no prior experience with HTML, don't expect to understand everything, but you might still learn some things about KM.

In any case, this second part of the tutorial was written with the assumption that you read the first part. If you haven't, you should do so now and come back here later.

The goal

Our goal, here, is to control a web application like we'd control a desktop application. We'll add hot keys and other customizations to it, and we'll automate some tasks.

For this we need a web application where we can experiment, so I put a simple one together. To be precise, it's more like a prototype than a real web application, because it lacks many features and has no backend at all, but that will do. And hopefully it will show you how you would approach controlling a real web application.

Our test application is a kind of online note taking app (except that your notes are not stored on a server, but on your Mac). Here's its main (and only) screen:

You can create (and delete) Notepads and add Notes to them. And then you can read the notes in each Notepad.

The application can be found here, so open it and spend a minute getting familiar with it. Again everything is done on the client side, including saving the notes (using LocalStorage), so except for loading the page the first time, it makes no other server access at all. And when you want to delete everything it stored to your local storage, click the Clear LocalStorage button.

Let's get ready

This time we'll make several macros, so if you haven't from the first part of the tutorial, you'd better start by creating a new macro group for this app. It will make your life easier. Name it Safari Tutorial and make sure it's enabled only in Safari.

OK, so our first macro will open the app in a new window and set it up according to our preferences. We'll change its window size, its title, a bit of its contents, and its background color.

Launching the app

Create a new macro named "Open" in your Safari Tutorial group, and give it a trigger (I usually start by giving my macros a Status Menu Trigger, which makes them easy to test, and defer deciding on a hot key or another trigger to later).

The first thing we want is to open the app in Safari. So insert a "New Safari Window" action into the macro, and paste the url http://media.flipmartin.net/files/KMTestAdvanced.html into its "With URL" field.

Next we want to resize the window. Add to the macro a "Manipulate a Window" action (from the Interface Control category) and set it to resize the front window in Safari.

Here's my method for finding out the values I want to use for width (→) and height (↓): I open a window (in any application), resize it manually to the size I want, and use this macro to measure it.

Right. From now on, we'll alter the contents of the page. This implies that we must make sure the page has finished loading before we do anything else. So we add a "Wait For Safari to Finish Loading" action. At this point we don't need to wait for extra contents to be downloaded after the page has finished loading, but to make absolutely sure the macro won't resume too soon, we set the value to "For at least .5 seconds" (or to 0.5 seconds, as KM understands both).

Next we'll customize the window's title to make it easier to find in Safari's Window menu and tabs. KM has an action that makes it dead easy: "Set Safari Title". Add it to the macro, and set the title to ••• My Notes •••.

Now that we changed its title, how about changing the "KM Browser Control - Advanced Test" line that appears at the top of the page? KM doesn't have a specific action for that, of course, but we can do it using JavaScript. Let's look at its source code (right-click on that line and select Inspect Element):

We see that it's a h1 tag, enclosed in a div whose class is "title_box". A quick Google search reveals that we can reference h1 tags with document.getElementsByTagName('h1'). But we have to remember that a tag name is not necessarily unique, so we have to also use an index (it's simple to determine which one in this case, because there's only one h1 tag in the page; so it's gonna be 0).

An easy way to determine if the index has to be used or not is to look at the first part of the command. If it's in the plural form (get​Elements​By​Tag​Name) we'll have to use the index. If it's not (get​Element​By​Id) we won't.

Another quick search tells us that we can change the contents of the referenced tag with [object-reference].innerHTML = 'something'. So let's try that:

Add an "Execute a JavaScript in Safari" action to the macro, and set it to document​.​get​Elements​By​Tag​Name('h1')[0].​innerHTML = 'My Notes'. Make sure our app is in front in Safari, and click the Try button at the top right of the action. The h1 will change to "My Notes". Good!

But what about removing that h1 completely, so there's more room to display the notes without scrolling? If we use the same method to set the h1 to '' (empty), the h1 itself will still be there, and it will still take room in the page (a bit less than before, but still...). So it'd be better to remove it completely.

Looking at the source, we saw that the h1 tag is in a div whose class is "title_box". Another quick search tells us that we can reference a tag by its class using document.​get​Elements​By​Class​Name. Again, since the command is in the plural form (getElements) we know we'll also have to use its index. So let's try replacing the value of the "Execute a JavaScript" action with document.getElementsByClassName('title_box')[0].innerHTML = '' and try the action again. Now the h1 disappears completely. Perfect!

What next? I don't know about you, but I feel like changing the background color of the page. Webpages always have a body tag that encloses their whole contents, so it should be easy to assign a background color to it (or to change it if it already has one). Another quick search tells us that we can do it from JavaScript with [object-reference].style.backgroundColor. So let's try that. Add a new "Execute a JavaScript in Safari" action to the macro and set it to document.getElementsByTagName('body')[0].style.backgroundColor = 'cyan'; and try it. OK, it's ugly (almost painful, even), but it works!

How do you find the code of a color you like, then? Well, you can use an online color picker, like the one at w3schools. You can also use the color picker in most image editing apps, as long as you set it to display the value in hexadecimal form. Or you can use a dedicated app like my HTMLColorPicker.

For now, I'll set the background to '#e6eefe' (which is a pale blue).

What if the background color came from another tag, or if you wanted to change the background color of the three grey boxes? Well, you'd take the same approach: study the source code to find which tags define the backgrounds, and apply the same technique to change them.

OK, we're almost there. Since we don't want to customize anything else at this point, all we have left to do is adding an action to take that window to the front (in case we decide later to make that macro globally accessible). For this we'll just use the Activate a Specific Application from the Application Control category and set it to activate Safari.

Time to try it for real, right? Take Safari to the front, trigger the macro, and here's what you get:

Pretty cool... But hang on. What happens if we call that macro a second time? That's right, a new window opens. This is not good. We'd rather take the existing one to the front, in this case, wouldn't we?

Taking it to the front

KM's Safari Control category has no action to take a given Safari window to the front if it's already open, so how can we do it?

Well, the first thing that comes to mind is to use an "If Then Else" action to see if Safari's Window menu contains an item named ••• My Notes •••. If it does, we select that item, and if it doesn't, we open a new window, set its background, its title, and so on... But wait. If we already opened that window, it will also appear in the History menu, wouldn't it? And then the condition would find it there even if it was not in the Window menu. So that method wouldn't work.

How about trying to select the item from the Window menu anyway, and see if the front window's title is ••• My Notes •••? If it is, we're done, and if it isn't, we open a new window and all. That should do it!

Right. So we add a "Select or Show a Menu Item" action at the top of the macro, and set it to select ••• My Notes ••• in Safari's Window menu (use the Menu popup to select it). We also make sure that "Stop macro if menu cannot be selected" is NOT checked.

Then we'd better insert a short "Pause" action, to make sure the window has time to come to the front. Half a second should be enough.

And finally, we insert an "If Then Else" action, whose condition is set to "the front window of Safari title is ••• My Notes •••", and insert a "Cancel Just This Macro" action into its "execute the following actions" branch.

Why didn't we just use a "Cancel This Macro" action? Well, that would work, but there is a good reason to avoid it, in this case: if we want to call this macro from another (and I can tell you that we will), the "Cancel This Macro" action would not only kill the Open macro, but also the one that called it. Definitely not what we'll want!

Anyway, let's go back to Safari and try out both cases. Yes, it works! Cool, so we're done.

Just one more little point: we want to make things easy for ourselves when we'll look at that macro again in a few months, so we'll just add a couple of comments to describe the less readable actions.

And here are the actions of our finished macro:

Adding hot keys

Since our goal is to be able to control that web app as if it was a desktop app, we want hot keys! So let's start by creating a hot key to add a new Notepad.

New Notepad

To do it by hand, we'd click into the Add New Notepad field, type its name, and press the Add button. So let's make a macro that will:

  • Give focus to the Add New Notepad field;
  • let us type the name and press Return when we're done (or Escape if we want to cancel);
  • submit that form.

This app runs in Safari, and our hot keys will only make sense when it's in front, not when another webpage is. So first we need a way to specifically target the ••• My Notes ••• window and do nothing if another page is in front. Sounds like another job for the "If Then Else" action with a Front Window condition.

Create a new macro in the same group, named "New Notepad", give it a trigger, add a "If Then Else" action to it, and set it to "The front window of: Safari title is not ••• My Notes •••.

Then, in its "execute the following actions:", insert a "Cancel This Macro" action. This way, when the front window is not our app, the macro will cancel itself immediately.

OK, so let's get to the core of the macro. First, we want to give focus to the Add New Notepad field. The "Focus Safari Field" action does just that, so add one to the macro, then click its Safari popup and you'll see this:

We could check the source of the page but it's pretty obvious that we want the third item, here: its type is text (as indicated by "(text)"), and it's in a form whose identifier is "notepads_form". Let's select that, then.

Next, we want the macro to wait for us to be done typing the Notepad's name (which we'll tell the macro by pressing Return) or until we cancel (which we'd do by pressing Escape). That's easy: add a "Pause Until" action at the bottom of the macro, set its "Pause until" to "any of the following are true", add a Key Condition set to "this key Return is down", and another Key Condition set to "this key Escape is down".

But what if you're interrupted and move away from the keyboard at this point? By default, the Pause action would keep waiting for 99 hours. So let's replace that by a timeout of one minute. Press the clock button at the bottom of the window to do it (the pause action has to be selected).

OK. Now that pressing either Return or Escape resumes the macro, we want an action to kill the macro when Escape was pressed. Add an "If Then Else" action, and set it to "If all of the following are true: This key: Escape is down". Then add a "Cancel This Macro" action into its "execute the following actions:" branch.

Finally, since we want to submit the form, we add a "Submit Safari Form" after the "Pause Until action", and select forms["notepads_form"]["add_notepad"] from its Safari popup.

Let's try it, then. Take Safari to the front, trigger the macro, type the name of the new Notepad, and press Return.

Oops. Did you see what happened? The window was reloaded, so our Open macro wasn't involved, and therefore the title, h1 and background didn't change. And if that wasn't enough, our new Notepad hasn't been created. WTF?!

Well, the thing is, this is a web application, not a regular webpage! So it's very likely to work differently. Let's have a look at the source of the Add button:

We can see that its type is "button", not "submit". So it's pretty clear that it triggers a JavaScript function, somehow, rather than a form submission. But if you know a bit of JavaScript you probably noticed that it has no "onclick" attribute. So how does it trigger that function? And how can we do it? Well, no "onclick" attribute is a clear indication that it uses jQuery or some other JavaScript library. And indeed, if we look in the head tag of the page, we find:

<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>

So what do we do, then? Don't worry, we have a couple of options:

1. We can click a button directly from JavaScript, so in this case that would give document.forms["notepads_form"]["add_notepad"].click().

2. Or, now that we know jQuery is loaded into the page, we could use the jQuery selector, which makes the command much shorter: $('#add_notepad').click().

For now, though, let's stick to the pure JavaScript method (you have to know JavaScript to use jQuery, but you don't have to know jQuery to use JavaScript). So replace the "Submit Safari Form" action with an "Execute a JavaScript in Safari" action, set to document.forms["notepads_form"]["add_notepad"].click().

Try the macro again, and this time the new Notepad is created without the page reloading. Yay!

What? Yes, the Add New Notepad field keeps the focus, so what? Ah, you want the focus removed? Fine, we can do that. Another quick search shows us that we can remove the focus from JavaScript by using the blur command. So let's do it.

In the previous macro we added a new "Execute JavaScript action" for each command, but we can also run several commands from a same action. So add this statement to the last action: document.forms["notepads_form"]["new_notepad"].blur(); (and a couple of comments to remind you later of what they do).

When using several statements in an "Execute a JavaScript" action, make sure each one ends with a semicolon ( ; ).

So now we're done, and here are the actions of our macro:

New note

Next we'll create a macro to add a new note. Since we prefer to use only the keyboard for that kind of things (it's faster), that macro will be similar to the New Notepad macro, with a few differences.

To add a new note by hand, we'd:

  1. select a Notepad;
  2. select a label;
  3. type or paste a title;
  4. type or paste an URL (or not);
  5. type or paste the text of the body;
  6. press Add.

So based on the method we used to add a Notepad, our macro will:

  1. open the Notepad popup and pause until we made a selection (using the arrow keys) and pressed Return;
  2. give focus to the Label radio buttons group and pause until we made a selection (using the arrow keys) and pressed Return;
  3. give focus to the Note Title field and pause until we entered the title and pressed Return;
  4. give focus to the Article URL field and pause until we entered the URL and pressed Return;
  5. give focus to the Note Body field and stop. We'll press the Add button by hand when we're done (so we can use Return to insert new lines into the body).

This time, instead of starting from scratch, we'll duplicate our New Notepad macro and edit the copy. This way, we'll already have the window checking at the top, along with the "Pause Until" and the "If Then Else Escape was pressed" actions.

Select the New Notepad macro in the Macros column, and press ⌘D (for Duplicate). KM will select the new copy and highlight its title, so we just have to type the new title, "New Note". Give it a new hot key, and now we can start editing the actions.

1. The first action we have is "Focus Safari Field". This is what we want, so we just have to change its target. Click the Safari popup and select (you guessed it) forms["note_form"]["note_notepad"]. Make sure our app is frontmost in Safari and click the action's Try button to see what happens.

The popup menu gets the focus, but it doesn't open. Hmm... What do you usually do when a menu has the focus and you want to select a given item? One way is to start typing its name, and another is to press the Down-Arrow key. Let's see if that one works: press the ↓ key, and... the menu opens. Cool! So we just have to insert a "Type a Keystroke" action after the focus action and set it to press Down Arrow.

Next we have the pause action that waits for us to press Return or Escape, so we can try the macro to make sure our little trick works. Take Safari to the front and trigger the New Note macro. The popup menu opens and now we can use the arrow keys to make our selection. Excellent! Press Escape to cancel the macro.

2. OK, next we want to give the focus to the radio buttons group. But we also want to keep the next two actions ("Pause Until" and "If Then Else"), so select the next action after them ("Execute JavaScript in Safari"), delete it, and insert a new "Focus Safari Field" action. Click its Safari popup and look for the radio buttons group. This time too, the one we want is pretty obvious, so select forms["note_form"]["note_label"] ("radio"). And again, we'll try it before going any further.

Press its Try button and take Safari to the front to see what happened... Errrr... Well, nothing happened. Why did it fail? Time to have another look at the source code:

Let's see... Ah, there's one input tag for each button but there is no tag for the whole group. And we didn't specify a given button in our selector; we targeted "note_label", but there are three elements with that name. So maybe we need to use the index to narrow down the selector to one button only? Let's try it.

Update the Focus Field in the action so it reads document.forms["note_form"]["note_label"][0] and press Try again. Go back in Safari and... ah, something changed: now the first radio button has a blue ring around it. So it has the focus. Try pressing the right-arrow key... Cool, that selected the next button. So it looks like we have the solution, here.

But hold on. What happens if another radio button is selected when we run that action? Let's try it.

Make sure "Important" is checked in Safari and press the Try button in the action again. Go back to Safari and see what we have. Hmm, the first button has the focus but the second one is still checked. Looks weird... What if we wanted to check the first button at that point? Try pressing Return... Nope, the second one stays checked. Try Left-Arrow... Nope, again nothing happens. Try Right-Arrow... Ah, now the second button has the focus. So to check the first button if the second was previously checked, we'd have to press Right-Arrow, then Left-Arrow.

This is neither intuitive nor handy. It would be much better if the checked button had the focus in the first place, so we'd have only one arrow key to press, and which one would be obvious. But how can we do that?

One way would be to have an "Execute a JavaScript in Safari" to get the index of the checked button, store it into a KM variable, and use a token to insert that variable into the Focus action (like this: document.​forms​["note_form"]["note_label"][%Variable%index%]). But if we have to use an "Execute a JavaScript" action to get the checked button, we could as well give it the focus from that same JavaScript.

A quick Google search shows that JavaScript doesn't have a command to get the checked radio button. Instead, we find suggestions that involve looping on the radio buttons. We also find a method using jQuery that would make it pretty easy: all we'd have to run is $('input[name=note_label]:checked').focus(). But since we decided earlier to use pure JavaScript, we'll do it with a loop.

Remove the focus action and replace it with a "Execute a JavaScript" action that goes:

var e = document.forms["note_form"]["note_label"];
for (var i = 0; i < e.length; i++) {
	if (e[i].checked) {
		e[i].focus();
		break;
	}
}

It's easy enough to understand: we loop on the radio buttons, incrementing the variable i on each loop, and using it to test the checked attribute of the button whose index is i. When we find it, we give it the focus, and the break statement just exits the loop immediately. And if you wonder why there's a pair of parenthesis after focus but not after checked, it's because focus is a function while checked is an attribute.

When you use variables in an "Execute a JavaScript" action, make sure to precede their name with the keyword "var". This way, the scope of these variables will be limited to that bit of script and they won't conflict with identically named variables that could exist in the page's JavaScript.

Let's try the action again, and indeed, this time it does the sensible thing. Cool!

OK, next we want another occurrence of the combination "Pause Until Return or Escape is pressed", and "Cancel This Macro when Escape was pressed". So select and copy those two actions, select the last "Execute JavaScript" action we added, and paste.

3. Next we want to give the Title field the focus. We already know how to do that with a focus action, so that's easy and you don't need me to tell you how to do it anymore.

4. Then we want another occurrence of the Pause and If Then Else actions, and another focus action. So we can copy the last three actions, paste them at the bottom, and update the target of the focus action to target the URL field. Again, piece of cake.

5. And finally, we want the same again, this time targeting the Body field. Since we already have those three actions on the clipboard, we just select the last action and paste. We update the target of the last action, and we're done.

Take Safari to the front and try the action a few times, to make sure everything works as expected.

Ah. I don't know about you, but I found a problem, here: if I press the Return key for a bit too long, the macro jumps over several fields without pausing. Hmm, let's think about it for a sec.

Apparently, what happens is this: KM runs the JavaScript to focus a field, then it notices that the Return key is pressed, so the Pause Until action resumes the macro immediately, the macro focuses the next field, finds the Return key pressed, and so on. What we need, then, is to delay the Pause Until actions a bit, so we have a chance to release the Return key before they are reached. Even better, we can pause until the Return key is no longer pressed.

Find the first "Pause Until" action, and insert a "Pause Until" action above it. Set its condition to "This key Return is up". Copy that "Pause" action, find the next "Pause Until" action, and paste it above it. Repeat this until each "Pause until Return or Escape is down" action is preceded by a "Pause until Return is up" action.

OK, all done? Let's try the macro again, then... Ahhhh, that's much better, isn't it?

That's it, then. We just add a few comments to the macro, and here it is:

New Note (automatic)

So far we've seen how to help adding Notebooks and Notes manually. Now it's time to completely automate the addition of a note.

Here's what we want: let's say we're reading a webpage in Safari and decide to create a note based on it. We want this new note to have the title and the URL of the webpage, and if we selected some text in the webpage, we want it inserted into the body of the note. We also want to be able to select a Notepad and a Label, and to be able to edit the Title, URL and Body before creating the note (in case you didn't notice, my stupid app doesn't let us edit an existing note).

In short, when we trigger the macro, we want to get a dialog such as this one, where pressing OK would create the new note:

So the macro will have to:

  1. extract the URL of the current webpage;
  2. extract its title;
  3. extract the selected text if any;
  4. open ••• My Notes ••• or bring it to the front;
  5. extract from it the names of the existing Notepads;
  6. display a dialog where we can select a Notepad, a Label, and possibly edit the Title, URL and Body;
  7. submit a new Note with the data we chose.

Let's get to it, then. Create a new macro named "New Note (Automatic)", and give it a trigger.

Getting the data

1. OK. First, we want to extract the URL of the current page (no need to make sure Safari is in front, here, we know it will be). KM has no action to get that URL, but it's easily done from JavaScript: you could insert an "Execute a JavaScript" action and set it to execute window.location.href. However, the fact that KM has no action to extract the URL doesn't mean it has no way to get it. And if you look in the menu Edit > Insert Token, you'll see that indeed it has: "Safari Document URL".

Don't forget about this menu and the Insert Function menu! They're really handy, not only to remind you of the syntax of a given token or function, but also to discover tokens and functions you didn't even know existed.

We're going to use a Set Variable To Text" action, then. Insert it into the macro, put the insertion point in its "to" field, and select Edit > Insert Token > Safari Document URL (which will insert the %SafariURL% token). Name the variable tuto__URL (with two underscores).

Time for a note about variable names: we'll use this variable in the "Prompt For User Input" action later in the macro, so its name will determine the label displayed in front of the corresponding field. Our first impulse would then be to name it "URL". However URL is a pretty common name for a variable, so another of your macros may already be using it. And there's another reason:

Since variables are persistent (they survive across sessions and even reboots), it's often a good idea to set the Default Value of a field, in the "Prompt For User Input" action, to the same variable it represents (using %Variable%​some_name% or its short form %some_name%). By doing this, your last choice becomes the default.

This is the case in the screenshot above where the default values of Notepad and Label were inherited that way. So it's obvious that sharing a variable between unrelated macros could lead a macro to display the wrong default value. Thankfully, KM offers a method to make variable names uniques without affecting the way they're displayed in the "Prompt For User Input" action:

If you prefix variable names with something that ends with two under­scores, that prefix will not be displayed in the dialog. So my_macro__URL would be displayed in the dialog as "URL".

2. Next we want to extract the page's title. KM has no specific action for that either and we could easily get it from an "Execute a JavaScript" action by executing document.title. But again, there is a token that extracts the title, so we can just use a "Set Variable To Text" action, with the %SafariTitle% token. Name the variable tuto__Title.

3. Then we want to grab the selection (if any), so insert a new "Execute a JavaScript" action and set it to execute window.getSelection().toString(). Make it save the results into a variable named tuto__Body.

4. Now we need to open or take our app to the front. We already have an action that does just that: Open. So we just have to call it with an "Execute a Macro" action (told you we were going to do it).

5. Next we want to extract from our app the existing Notepad names (we don't want to edit the macro each time we add or delete a Notepad, do we?). We know (because we read the documentation) that to display a popup in the prompt, we have to separate its items by a pipe ( | ). So we need some JavaScript to extract the values from the option elements of the "notepads" select, and separate them with a pipe. If you know a bit of JavaScript (or how to use Google) you can easily enough come up with something like that:

var e = document.forms["notepads_form"]["notepads"];
var vals = [];
for (var i = 0; i < e.length; i++) {
	vals.push(e[i].value);
}
vals.join('|') 

Basically that script loops on the elements of the "notepads" select, adding the value of each to the "vals" array (a kind of list), and the last statement converts that array into a string, separating each items with a pipe, and returns the result.

So add an "Execute JavaScript" action to the macro, set it to execute the script above and to save its results into the variable tuto__notepads (note that "notepads" has no capital n, here; we'll see why in a moment).

6. Now we're at the point where we want to display the dialog. So insert a "Prompt For User Input" action into the macro, and set it like this:

Let's detail what we did, here.

- We set Title to New Note from "%tuto__Title%": so the title of the dialog will contain the title of the webpage.

- We don't need a prompt, so we emptied out this field.

- Then there's a few important things to notice in the first variable definition:

a) We named the variable tuto__Notepad (with a capital N) because we want to store the user's choice, in there, not the contents of the Notepads menu.

b) In the Default Value field, we used %tuto__notepads% (lowercase n and trailing s) to insert the string we extracted from the previous action (i.e. "To Do|To Read|To Buy"), which will be rendered as a popup menu.

c) We preceded it with %tuto__Notepad%| (with a capital N and a trailing pipe) to tell KM to use the value of tuto__Notepad as default and pre-select the corresponding menu item (don't forget the trailing pipe, here!).

d) We preceded %tuto__Notepad%| with another pipe to tell KM to evaluate the tokens before evaluating the pipes. The documentation states: "If the field starts with a bar ( | ), then token expansion happens first, followed by separating by bar ( | ). Otherwise, the entry is separated by bar first, and then token expansion is applied to each field.". This means that if we didn't prefix the whole thing with a pipe, the tuto__notepads variable would be evaluated after the pipes were detected, so the contents of that variable would appear as one string ("To Do|To Read|To Buy") instead of the three menu items we expect.

- The second variable definition is more straightforward: since the values of the radio buttons will never change, we don't have to extract them from the page. So we can hardcode them and therefore we don't need a leading pipe. We still use the %tuto__Label% variable to store and restore the user's selection, though.

- The next three variable definitions don't require any explanation, as we simply set their default values to those of the corresponding variables.

Let's test

OK, so now we have the first part of our macro. What's missing is the creation of the new note, but we'll get to that when we're sure the first part works as expected. Time to test it, then:

Select some text in the present page and trigger the macro. If all goes well, the ••• My Notes ••• window should come to the front, and KM's dialog should open...

Wait! It did open, but the popups display "%Delete%". Why? Don't worry; it's simply because the tuto__Notepad and tuto__Label variables aren't defined yet. Select another value in those popups and press OK to initialize the variables. Then trigger the macro again and those popups should then look as expected.

So, is it working? Excellent! Now let's try it without any text selected in the webpage... Ah, we have another problem, here: the last field contains %tuto__Body%. Why? We know it's not undefined; we used it a minute ago! Well, here's why:

When you use the long form of the variable token (like %Variable%tuto__Body% ), KM knows that it's a variable, even if it's empty. So an undefined variable can be considered as empty, and an empty variable can be displayed. But when it evaluates a variable token in the short form (like %tuto__Body% ) that doesn't exist or is empty, KM has no way to know if it's a variable or a misspelled token. And if it displayed misspelled tokens as empty values, that would be a nightmare to debug.

So the fix is very easy: just replace %tuto__Body% with %Variable%tuto__Body%.

Try the macro again, and this time the dialog will look right in both cases.

Creating the note

All we have left to do is to create and submit the new note. This should be easy, because we saw how to do most of it in the first part of this tutorial and when we made the New Note macro.

Let's summarize what the macro must do:

  1. set the Notepads popup to the chosen value;
  2. check the Label radio button that corresponds to the chosen value;
  3. insert the title;
  4. insert the URL;
  5. insert the body;
  6. click the Add button.

a) So let's start by inserting a "Set Safari Field to Text" action that targets document.forms["note_form"]["note_notepad"], and set its "to" field to the variable that contains the Notepad selection, %tuto__Notepad%.

b) Then we need a "Set Safari Radio Button", so insert one that targets document.forms["note_form"]["note_label"]. Ah, but we have another problem, here. Do you see it? The "to" popup contains "0", "1" and "2", but our tuto__Label variable will contain either "Normal", "Important", or "Vital". So we must convert what we have in the variable to the corresponding valid value.

We could do it with embedded "If Then Else" actions, like this:

But personally I'd rather to do it in in a single action, using AppleScript :

Anyway, we just have to insert one of these above the "Set Safari Radio Button" action and that will do.

c)/d)/e) The next three actions are pieces of cake, using the "Set Safari Field To Text" action.

f) And then we have to click the Add button. We already know how to do that because we did it in the New Notepad macro.

And there we are, here's the finished macro:

Try it and it should do exactly what we expect.

Conclusion

This concludes this tutorial. It was a bit longer than I intended, so well done for reading that far! Hopefully it will have given you the knowledge you'll need when you start making your own macros, and shown you how to analyze and work around the error you'll will probably run into. It showed you how to extract data from a webpage, how to use it in KM, how to insert new data into a form, how to submit it, and even how to name KM variables to avoid conflicts and how to use AppleScript to work with these variables. I hope you find it useful.

And finally, as always, thanks to Peter N Lewis for Keyboard Maestro (the most indispensable utility on all my Macs) and for his kind attention to questions and feature requests.

Appendix

You can download all the macros we created in this tutorial from here (4 Ko). Unzip the archive and open the kmmacros file in KM to import them.

 Previous Comments

1. Xerxes commented :

Sweet motherlode! I've been looking for some serious Keyboard Maestro repository for a while, and you sir are a one man repository. KM's ability to automate web apps is so wonderful. At work I am cursed with navigating some of the most poorly designed pieces of shite that have ever been written. But with KM I've been superimposing my own order.

I've been pushing the built in actions to their limits, It seems I'll have to learn some javascript to get KM's full potential.

Thanks for everything you've written, I hope more people find this.

Please Log in if you wish to comment.