Bookmarklets for everyone

Ah, those nice javascript snippets that let some external site interact with our applications.

Being a bridge for complex features or some simple functions, i was surprised how easy it is to make one.

I am almost always looking for new ways to give admins and writers easier and faster way to do their job, and give common users a better experience and some way to contribute.
In this multi-site cms i was looking for shortcuts to address common needs. After a simplified content management and automagic sync from rss or ical webservices, i noticed it was a common task to just recommend some blog post or story from other sites. Just the title, a little excerpt or intro (often the original subtitle or first few paragraphs), and the link back to the complete content on the original source.
Isn’t that a task for a bookmarklet?

Well, it was, at least for me.


To get started with bookmarklets, here is a nice primer on net tuts.

My need was: get the relevant info from the page (title, url, optionally some text) and submit them to a simple “add content” action in my cms, after a little tweaking. The javascript / bookmarklet side can be very simple,  just grab the data – then on our side with cakephp in my case) we can do anything we want with that data: something simple like saving the link in a social bookmarking site, or a more complex scraping like the one of facebook, to let the user select an image from the source page and chang text or title of the submitted link..

So, here is my example:

Step 1 – The javascript snippet

I just put a “button” like this* on the user’s dashboard, with instruction to drag / save it in the bookmarks bar.

  1. <a href="javascript:var intro_text = window.getSelection ? window.getSelection().toString() : (document.selection ? document.selection.createRange().text : '');location.href=''+encodeURIComponent(location.href)+'&title='+encodeURIComponent(document.title)+'&intro='+encodeURIComponent(intro_text);" class="button">
  2. >> Save to your website
  3. </a>


The only caveat is, the user has to be logged in to let it work properly.
(I could implement some authentication method / one time password, but it would be overkill in my case)

As you see, the javascript is really short and simple. Just get the user selected text on the page, and submit it and the page url and title to the cakephp action that will process the data.

It’s important to note that the command url is in the form of a normal url with a querystring. I’m not using cakephp pretty url format because it can break when one of the arguments contains slashes (like the link does). Escaping and encoding can become complicated, as different layers (external javascript, php/cakephp processing) are implied.
I found it safer and easier to use a common querystring (after all, we don’t need pretty urls for this), and retrieve the data in cakephp using $this->params[‘url’] (in cake $this->params[‘url’][‘title’] is $_GET[”title’], and so on).

Also, be careful with the variable’s names.. Don’t use a “url” argument, it conflicts with cakephp’s $this->params[‘url’][‘url’]. (learned this the hard way 🙂 )

  1. function bookmarklet () {
  3. // insert here your auth checks and so on..
  5. extract($this->params['url']); // get querystring arguments: $title, $link, $intro
  6. // process the data..
  7.   $data['Content']['title'] = $title;
  8. App::import('Helper', 'Text');
  9. $truncated = TextHelper::truncate($intro, 350, array( 'exact' => false));
  10. $content = str_replace(substr($truncated,0,-3), '', $intro);
  11. $data['Content']['intro'] = '<p><strong>[Original article: <em><a href="'.$url.'">'.$title.'</a></em>]</strong></p>';
  12. $data['Content']['intro'] .= $truncated; //this is shown in indexes
  13. $data['Content']['content'] = $content; // the whole content
  15. if($this->Session->read('actual_site') != 'home') {
  16. //if the user was not in the main portal, but in some specific site, prepare the data to be saved
  17.  //else, pass the data to the add form, where the user can edit it, select the site he want to suggest the content to, etc.
  18. $site_id = $this->Site->field('id', array( 'slug' => $this->Session->read('actual_site') ) );
  19. $data['Content']['site_id'] = $site_id;
  20. $this->data = $data;
  21. }
  22. // cut
  23. //pass our processed data to the actual insert action
  24.   $this->add($data);
  25. }

add($data = null) is a common add action, that just can use the $data array to pre-compile the form. If $this->data is set from our bookmarklet actions, add() sets automatically other needed fields and saves immediatly a new record.
(just remember to explicitly call the view to be rendered -$this->render( ‘add’ );-, as the actual action may vary and the autorender would complain that a bookmarklet.ctp view does’nt exist)
So, it’s very easy to process the bookmarklet input from cake, and even set a “polymorphic” behaviour based on previous user actions in our application. In my case, open a precompiled add form or save immediatly base on the actual “site” selected by the user.

Security-wise it’s not very different from the standard add (compile form) action, the same sanitize and security checks are made on save. getting only simple text simplifies the task, and this is only a shortcut for something the user could do the usual way.

No cut’n’paste’n’edit of excerpt, title, and link back to the original article (something less tech-savvy users are often not familiar with). Just a click, and it’s automatically saved (or ready to be saved), with the correct link.
And a busy copywriter will thank you for saving him a lot of time.

*note: of course the needs may vary – the example snippet is simple because i didn’t want to retrieve html, but just simple text; i didn’t have to do a lot of processing client side, but a front end developer could add layers of complexity, features, and options to the user before submitting to php.