Latest Posts

Archives [+]

Sling gems: a blog in 46 lines of code

This is our first post in the Sling Gems series, that we'll use to demonstrate our favorite Sling features.

In this episode, the SlingPostServlet and the sling.js library are brought together using 46 (no kidding: fourty-six) lines of code to create a simple blog (or let's say bloggish) application.

I used this example in my Rapid JCR applications development with Sling presentation at the last ApacheCon (slides are available), and I think it's a good testimony to the power and simplicity of Sling.

Step 1: Creating content

The easiest way to create content in Sling is to use an HTTP POST request, let's use a simple HTML form to do that:

<html>
  <body>
    <h1>Sling microblog</h1>
  
    <div>
      <form method="POST">
        Title:<br/>
        <input type="text" name="title" 
          style="width:100%"/>
        
        <br/>Text:<br/>
        <textarea style="width:100%" name="text">
        </textarea>
        
        <br/>
        <input type="submit" value="save"/>
        <input type="hidden" name=":redirect" 
          value="*.html"/>
      </form>
    </div>
  
    <!-- code of step 2 comes here -->
  </body>
</html>

That's two input fields, a submit button and a hidden field that tells Sling what to do after the POST (in this case: redirect to the html view of the node that was just created).

To test the form, start Sling and save the above script as /apps/blog/blog.esp in the Sling repository - a WebDAV mount is the easiest way to do that. Browsing to http://localhost:8888/content/blog/*.html2 should display the above form 4.

Input some data (using "foo" for the title, for the sake of our examples below), save the form, and Sling should display the form again, using the URL of the node that was just created.

At this point you're probably looking at an empty form with an URL ending in foo, if you used that for the title. Or foo_0 or foo_1 if other foos already existed. Don't worry about not seeing your content, we'll fix that right away.

Step 2: Where's my content?

To verify that our content has been created, we can have a look at the JSON data at http://localhost:8888/content/blog/foo.tidy.json, which should display our new node's values:

{
  "jcr:primaryType": "nt:unstructured",
  "text": "This is the foo text",
  "title": "foo"
}

That's reassuring, but what we really want is for these values to be displayed on the editing form for our post.

Thanks to the sling.js client library, we just need to add a Sling.wizard() call to our form to display those values. Let's first add a <head> element to our form to load the sling.js library, before the existing <body> of course:

<head>
  <script src="/system/sling.js"></script>
</head>

And add the Sling.wizard() after the form, where we had the code of step 2 comes here comment:

<!-- code of step 2 comes here -->
<script>Sling.wizard();</script>

Reloading the form at http://localhost:8888/content/blog/*.html and creating a new post should now redirect to an editable version of the post, with the form fields correctly initialized.

We can now create and edit posts; let's add some navigation, using more of the sling.js functionality.

Step 3: Navigation

The sling.js library provides utilities to access and manipulate content. Four our blog, we'll use the getContent(path) method to list the siblings of the current node.

Add the following code to your script, after the Sling.wizard() call that was added in step 2:

<h3>Navigation</h3>
<ul>
    <li><em><a href="/content/blog/*.html">
      [Create new post]</a></em></li>
    <script>
      var posts = 
        Sling.getContent("/content/blog", 2);
      for(var i in posts) {
        document.write("<li>"
          + "<a href='/content/blog/" + i + ".html'>" 
          + posts[i].title 
          + "</a></li>");
      }
    </script>
</ul>

The first link to /content/blog/* brings us back to our content creating form, which is nothing else than the editing form reading empty values and posting to the "magic star" URL.

The rest of the javascript runs client-side, as it is not embedded in <% %> code markers, calls the sling.getContent method to get two levels of node data below /content/blog, and displays links to nodes that it finds.

That's a basic navigation, of course, in a real blog we'd need some paging and contextualization to cope with large numbers of posts.

Nevertheless, with this addition our ESP script allows us to create, edit and navigate blog posts - not bad for 46 lines of code, including comments, whitespace and output formatting.

Step 4: Data first, structure later

You might have heard this mantra, which we apply in many areas of Sling.

In this case, adding a new field to our blog posts could not be easier: just add an input field to the form, and Sling will do the rest.

Adding this inside our script's <form> element, for example:

<br/>Author:<br/>
<input type="author" name="author" 
  style="width:100%"/>

Allows us to add an author name to our blog posts. No need to define anything at the repository level, as Sling is using it in unstructured mode in this case, and no need to migrate existing data, the author field of existing posts will simply be empty.

I want my ESP!

Now wait...we said we were going to create an ESP script, but our "application" is just static HTML and some client javascript at this point.

That's correct - as we are using only Sling client-facing features at this point (HTTP POST and sling.js), we do not necessarily need to use ESP code.

To keep things simple, we'll refrain from adding ESP-based features at this point, and keep that for a future Sling Gem.

That's the power of Sling

The 46-line blog is a good example of the power of Sling. It leverages the SlingPostServlet3, which handles POST requests in a form-friendly way, and the sling.js client library, which provides high-level functionality on the client side.

A slightly fancier version of the blog.esp script is attached to this post, the code is the same but some CSS makes it look a bit nicer.

Let us know what you think, and stay tuned for more Sling Gems episodes!

1. ESP is Sling's server-side javascript language.
2. This assumes your instance of Sling is running on port 8888. If that's not the case, adjust the example URLs accordingly.
3. See Manipulating Content: The SlingPostServlet for a more complete description of the Sling POST servlet.
4. Note that this requires disabling anonymous access to Sling pages: uncheck the Allow Anonymous Access setting on the Sling Request Authenticator configuration page at http://localhost:8888/system/console/config, and save that configuration.
 

COMMENTS

  • By Renaud - 4:02 PM on Dec 15, 2008   Reply
    nice and slick!
  • By Anonymous - 4:18 PM on Dec 15, 2008   Reply
    Nice read.
  • By Cris - 12:50 AM on Jan 29, 2009   Reply
    <br/><br/>I tried with a localhost version of sling but I couldn't get it to work. Looks like an authorization issue. I just grabbed the attachment and dropped it into the /apps/blog directory via WebDAV (using MacOSX 1.5). The form displays correctly but the form post appears to fail. Any idea what might be going on here and how I get around it?<br/><br/><br/>http://localhost:8888/content/blog/*.json<br/><br/>Error while processing /content/blog/dog<br/>Status <br/>500<br/>Message <br/>javax.jcr.AccessDeniedException: /: not allowed to modify item<br/>Location /blog/dog<br/>Parent Location /blog<br/>Path <br/>/content/blog/dog<br/>Referer http://localhost:8888/content/blog/*.html<br/>ChangeLog <br/><br/>created("/content");<br/>created("/content/blog");<br/>created("/content/blog/dog");<br/>modified("/content/blog/dog/text");<br/>modified("/content/blog/dog/title");<br/><br/>Go Back<br/><br/>Modified Resource<br/><br/>Parent of Modified Resource
  • By Cris - 1:01 AM on Jan 29, 2009   Reply
    OK....saw the footnote and found my problem.<br/>But the location was http://localhost:8888/system/console/configMgr, not config (sorry...I don't yet know my way around the OSGI console.<br/><br/>- Cris
  • By Anonymous - 1:10 AM on Jan 29, 2009   Reply
    One other question/problem though...I can now create content but the [Create New Post] link takes me to http://localhost:8888/blog/*/ rather than to http://localhost:8888/blog/*.html/. The actual link when clicked, produces a 403:<br/><br/>The requested URL /blog/*/ resulted in an error in org.apache.sling.servlets.get.DefaultGetServlet.<br/>ApacheSling/2.0 (Java HotSpot(TM) Client VM 1.5.0_16; Mac OS X 10.5.6 i386)<br/><br/>Is this just a typo in the attachment or is there some other setting I should be altering?<br/><br/>- Cris
  • By Bertrand Delacretaz - 3:15 PM on Feb 04, 2009   Reply
    @Cris: you are right, the /content/blog/* link in the original blog.esp does not work with the latest Sling code. I have updated the blog.esp script to use the full /content/blog/*.html path, can you try again?
  • By Glenn Silverman - 3:36 AM on Mar 12, 2009   Reply
    I can't get the original blog.esp to display when I go to http://myserver:8888/content/blog/*.html. Instead I get the following page:<br/><br/>Resource dumped by HtmlRendererServlet<br/><br/>Resource path: /content/*<br/><br/>Resource metadata: {sling.resolutionPath=/content/*, sling.resolutionPathInfo=.html}<br/>Resource Value: <br/><br/>Any idea why?
  • By Bertrand Delacretaz - 8:17 AM on Mar 12, 2009   Reply
    @glenn, here's a small debugging guide:<br/><br/>1) Double-check that the blog script is indeed stored under /apps/blog/blog.esp in the repository (case-sensitive), for example by requesting http://localhost:8888/apps/blog/blog.esp which should display it.<br/><br/>2) The org.apache.sling.samples.path-based.rtp bundle must be active, the easiest way to check is via the http://localhost:8888/system/console/config page, search for "org.apache.sling.samples.path-based.rtp" there. If not, you can build it from http://svn.apache.org/repos/asf/incubator/sling/trunk/samples/path-based-rtp. I just noticed that it seems to be missing in the latest Sling trunk launchpad builds, I'll investigate. This bundle is what maps /content/blog/*.html to the "blog" resource type.<br/><br/>3) The ESP scripting engine must be active, look at the http://localhost:8888/system/console/scriptengines console page, which should include the "Apache Sling Scripting JavaScript Support" engine.
  • By Michael Marth - 8:12 AM on Jun 02, 2009   Reply
    Glenn,<br/><br/>in the Sling trunk the file Sling.js is in bundles/servlets/post/src/main/resources/system
  • By Laurence - 12:46 AM on Dec 25, 2009   Reply
    4. Note that this requires disabling anonymous access to Sling pages: uncheck the Allow Anonymous Access setting on the Sling Request Authenticator configuration page at http://localhost:8888/system/console/config, and save that configuration.<br/><br/>After I do footnote 4, all I get is HTTP ERROR 200 for every page with Reason: OK<br/><br/>Which means now I can't use the config page to undo this.<br/><br/>The title of : a blog in 46 lines of code lured me in, but I've had to do so many steps already (downloading, compiling, googling, more downloading ...) and still can't get this to work.<br/><br/>What exactly is the advantage of Apache Swing over using something like Caucho Resin with webdav enabled (I can add other languages like Groovy etc.)?<br/><br/>
  • By Laurence - 1:07 AM on Dec 25, 2009   Reply
    Oops, in above question, I obviously meant Apache Sling instead of Apache Swing.<br/>
  • By Laurence - 2:25 PM on Dec 25, 2009   Reply
    Solution to problem (http error on every page) after executing footnote 4:<br/><br/>Add this to your query string:<br/>sling:authRequestLogin=1
  • By Greg Klebus - 10:37 PM on May 12, 2010   Reply
    FYI, I have packaged the example file into a CRX content package, and uploaded it to Day Package Share. Start your CRX, navigate to "Share", log in with your day.com account, and find the package under Packages » Public » Day » CRX 2.1.0 » example. It's called crx-microblog-example-application.zip.

    I had to adapt the code in rendering post title for new posts, as currentNode was not defined (1 extra line), otherwise it works nicely.
  • By Claude - 10:24 PM on Jul 07, 2011   Reply
    Great post!

    The new look with the CSS magic is awesome for such a small code!

    Thank
  • By Avi - 7:50 AM on Nov 18, 2011   Reply
    i want to upload an image in dam through form field input type=file. I dont want the image as to be the child node of newly created node on form submit. Instead wants that image should be uploaded in dam folder of cq5.
    How can i do that