Skip to content

JQuery Content Utility

Jonathan Rochkind edited this page Mar 17, 2015 · 29 revisions

The "JQuery Content Utility" is a javascript object that can be used to take a page on a non-Umlaut website representing a particular item, and very easily place HTML sections rendered by Umlaut on that page.

This javascript code uses the Umlaut HTML Snippet API Response under the hood, but takes care of a lot of boilerplate for you, using JQuery.

The helper will update your page at DOM locations you specify (via JQuery selectors), and keep polling Umlaut for new results, continuing to re-update your page until Umlaut is finished.

Requirements

  • You need to have the ability to add html <script> tags referencing external js files to the page you'd like to Embed Umlaut content in.

  • You also need to be able to somehow make an OpenURL Context Object representing the thing you'd like to load Umlaut content for, and pass this to the Javascript. If an OpenURL link is already on your HTML somewhere, Javascript can easily pull the 'context object' out of this (see example).

  • You need to be willing to load the JQuery library on your page, although it can be loaded in "no conflict" mode if you also need Prototype or another JS library. jQuery 1.4 or higher; JQuery version requirement may go up in the future, you ought to be able to use the latest version already.

Note Well: This utility was written assuming one call to Umlaut per host page -- for instance on a single 'item detail' page, not on a search results page with multiple items. Performance of the underlying SFX server is generally so slow that the author didn't think it would be feasible to call multiple times on a search result page. This utility may have some baked in assumptions that prevent calling multiple times on a host page, it may need additional development to work on a multi-item page.

Step by Step

In the following examples, $UMLAUT_BASE stands for the base URL you have installed Umlaut at, for instance http://findit.library.jhu.edu for JHU.

Load JQuery

If JQuery isn't already loaded on the page, you need to load it. The Google CDN Hosting JQuery is one easy place to get it from.

  <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
  <script type="text/javascript">
      jQuery.noConflict();
  </script>

Load the Umlaut.HtmlUpdater object

  <script type="text/javascript" src="$UMLAUT_BASE/assets/umlaut/update_html.js"></script>

Optionally, load Umlaut Javascript UI Behaviors

Note: No longer recommended/supported. Jan 2014. It was too hard to make this work reliably regardless of context. The only behavior really included here was expand/contract. For the moment you're on your own figuring out how to enable that for embedded umlaut content; the umlaut content already does have data-X attributes for Bootstrap collapsible (2 or 3), but you may want to consult [Umlaut's js code (https://github.com/team-umlaut/umlaut/blob/master/app/assets/javascripts/umlaut/expand_contract_toggle.js) for a model of preventing href from being followed and switching labels/icon.


HTML loaded in by the HtmlUpdater may include javascript behaviors when displayed in Umlaut, such as expand/contract toggles. You can choose to load JS files providing those behaviors into your local app. If you don't, certain links will degrade to linking out to Umlaut, but everything should still work fine.

Currently, expand/contract toggles are actually the only js behavior supported on external pages; dialog boxes will necessarily degrade to links out to your Umlaut app.

  <script type="text/javascript" src="$UMLAUT_BASE/assets/umlaut_ui.js"></script>

WARNING This has turned out to be very painful to support reliably. It may not be tenable long-term. This feature should probably be considered experimental. You may want to simply provide your own expand/contract functionality locally, perhaps copying source from Umlaut, instead of trying to use umlaut_ui.js like this.

Instantiate an HtmlUpdater

Now we start with the code you actually write to specify how to place Umlaut content on the page.

This code can be directly on the page in a <script> tag, or somewhere else referenced by a <script> tag, just ordinary javascript here.

You need to instantiate an Umlaut.HtmlUpdater, and tell it your Umlaut base url, and a context object key-encoded-value query parameter string. If you already have an OpenURL link on the page, it may be convenient to pull the context object out of that, see Complete Example below.

  //inside a jQuery ready()
  var ctx = "auinit=N&aulast=Chomsky&title=Aspects+of+the+Theory+of+Syntax&genre=book&isbn=0262530074&date=1965"
  var updater = new Umlaut.HtmlUpdater($UMLAUT_BASE,  ctx  );

OpenURL can be a somewhat tricky standard, if you haven't worked with it before you may want to do some research so you understand what the various options are for query parameters in your OpenURL and what they mean. Unfortunately, I don't have good documentations or a good guide available to recommend, the docs for OpenURL are not very good.

Umlaut 4.1+: In addition to the two-argument HtmlUpdater constructor, there is a version that takes a single object as named arguments, that can be used to pass some additional options. See below at More than one citation on a page for more information on the 'container' argument.

var updater = new Umlaut.HtmlUpdater({
   umlaut_base: "http://umlaut.example.org/",
   context_object: "auinit=N&aulast=Chomsky&title=Aspects+of...",
   // Optional:
   locale: "en",
   container: "#foo" # or DOM or JQuery object
});

Configure HtmlUpdater

You use the add_section_target() method on your updater to tell it which blocks of Umlaut content you'd like to put where on your page. The argument to add_section_target() is a JS object containing options.

In the simplest case, you can simply supply an umlaut section in the "umlaut_section_id" option, and the ID of a div you'd like to place the content in on your page in "selector":

  updater.add_section_target({ 
     umlaut_section_id: "fulltext", 
     selector:"#my_full_text_div"
  });

The exact umlaut sections available may depend on your Umlaut configuration (resolve_sections config key, see SectionRenderer and UmlautConfigurable).

The selector argument can actually be any JQuery selector. By default, the first item on the page that matches this selector will have it's content entirely replaced by the specified umlaut section. However, you can also supply a position argument of before, after, append, or prepend, and the umlaut section will instead be inserted before or after the element matching your selector, or prepended or appended to the element's children.

  updater.add_section_target({ 
    umlaut_section_id: "highlighted_links", 
    selector: ".sidebar", 
    position: "append"
  });

Call the updater

  //in a JQuery ready block
  updater.update();

The updater will now make a call to Umlaut, grab the content from Umlaut, and do what you've told it to do with it. Additionally, it will keep polling Umlaut until all content is available. How often it polls is configured by application config poll_wait_seconds, which defaults to 4 seconds.

Callbacks

When configuring the HtmlUpdate object, there are several callbacks you can supply functions to, for your own code to be called.

There are several callbacks for an individual sections, provided with add_section_target. These can be useful for modifying the HTML returned by Umlaut before it's placed on your page, or modifying other parts of your page upon receiving content.

There is currently also one callback on the HtmlUpdater as a whole, for when the Umlaut update is complete (all content has been fetched).

SectionTarget.before_update(html, count, target_obj)

  updater.add_section_target({ 
    umlaut_section_id: "highlighted_links", 
    selector: ".sidebar", 
    position: "append", 
    before_update: function(html, count) {
     //hide the section heading on the snippet, we don't want it
     $(html).find(".section_heading h3").hide();

     //add the snippet to page only if there are more than zero
     // SearchResponses included
     return (count > 0);
    }
  });

Called before the Umlaut html is actually on the page, you can use it to modify the HTML before it gets added to the page. count is a JS integer count of how many items are included in the section; sometimes you may wish to hide the whole section from the page if there are 0 items; returning false from before_update will cause the HtmlUpdater to not place the block on the page.

The target_obj is an HtmlUpdate internal object that's probably not too useful unless you want to look at the source, but can be used for some complex things.

SectionTarget.after_update(html, count, target_obj)

Similar to before_update, but called after the html has been placed in the DOM on the page. In after_update (unlike before_update) you can call JQuery closest() on the html to look up in the DOM if you want. This could be used for instance to show a previously hidden parent element only if there are umlaut items available:

   updater.add_section_target({umlaut_section_id: "fulltext", selector:"#my_fulltext", 
            after_update: function(html, count) {
               // assume we're inserting this on the page in a div.something that
               // is hidden. Show that div if we have non-zero responses included
               // in the snippet. 
               if (count !=0 ) {
                 $(html).closest("div.something").show();
               }
            }
   });

SectionTarget.complete(target_obj)

Complete on a given section is called after there are no more updates for that section. Currently, the HtmlUpdate isn't smart enough to know that until the entire Umlaut update is done however, so it'll be called at the same time as complete() on the updater as a whole. This may change in the future.

   updater.add_section_target({umlaut_section_id: "fulltext", selector:"#my_fulltext", 
                               complete: function() { alert("fulltext done!") }});

HtmlUpdate.complete(updater_obj)

  updater.complete = function() {
    alert("all content has been fetched!");
  }

More than one citation on a page?

This utility was originally designed for an 'item detail' type page, where the page is about a single particular citation, and you want Umlaut services about that single citation on the page. One citation on the page.

It will probably never be a good idea to embed multiple Umlaut citation responses on an item results page immediately, 10 or 20 or whatever. The API's Umlaut uses are just too slow. If you do want to try this, you should definitely use an app server for Umlaut that supports multi-threaded concurrent request dispatch and/or JRuby. But we do not currently recommend it.

However, it may work out better to embed Umlaut responses for multiple citations on a single page, when each Umlaut respones is only loaded on demand, for instance in response to a user click explicitly requesting to see Umlaut responses. There is experimental support for making this easier, in the form of the container argument to Umlaut.HTMLUpdater.

The container argument specifies a DOM element on the page, whereby the particular HtmlUpdater will be restricted to replacing content within that container. This lets you add section targets with selectors that may match multiple areas of the page, but the HtmlUpdater will restrict itself to selector matches within contents of the container when placing Umlaut content.

Container can be specified as a JQuery selector, or a JQuery object, or a raw DOM object -- basically anything you can pass to the jQuery() function to identify a container on the page.

var updater = new Umlaut.HtmlUpdater({
   umlaut_base: "http://umlaut.example.org/",
   context_object: "auinit=N&aulast=Chomsky&title=Aspects+of...",
   container: "#foo" # or instead:
   # container: dom_or_jquery_object
});

Additionally, in your section target callbacks, you can receive a third argument with an Umlaut.SectionTarget object, from which you can retrieve the original container set. This can be useful for targetting selectors only within the container, in your own callback code.

updater.add_section_target({
   umlaut_section_id: "fulltext",
   selector: ".my_fulltext_area",
   before_update: function(html, count, section_target) {
     # remove a 'waiting' message, but only within this updater's container
     section_target.container.find(".umlaut-waiting").hide();
   }
});

Complete Example

For clarity, this example has some javascript source inline in a <script> tag, the code that specifies how content is to be loaded. That javascript code could of course instead be in an external js file referenced by a <script src="url">.

You should be able to save this entire example in a file, replace $UMLAUT_BASE with the base url for an Umlaut installation, and have a working example.

<html>

<head>
  
  <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
  <script type="text/javascript">
    jQuery.noConflict();
  </script>
  
  <!-- load the Umlaut.HtmlUpdater object-->
  <script type="text/javascript" src="$UMLAUT_BASE/assets/umlaut/update_html.js"></script>
  
  <!--load Umlaut JS behaviors for HTML snippets we might place in page -->
  <script type="text/javascript" src="$UMLAUT_BASE/assets/umlaut_ui.js"></script>

<script type="text/javascript">
  
jQuery(function($) {
  var loader = new Umlaut.Loader();
  loader.load();
  
  /* pull openurl link out of the DOM, take umlaut base and context object
     out of it */ 
  var openurl_link = $("a.openurl_link").attr("href");
  var ctx_object_kev = openurl_link.substring( openurl_link.indexOf("?") + 1);
  var umlaut_base = openurl_link.substring(0, openurl_link.indexOf("/resolve"));
  var updater = new Umlaut.HtmlUpdater( umlaut_base  ,  ctx_object_kev );
  
  updater.add_section_target({ 
    umlaut_section_id: "fulltext", 
    selector:".my_full_text" ,
    // only show ourself if we have umlaut hits, false return from
    // before_update stops update.  
    before_update: function(html, count) { return (count != 0); }
  });   
  
  
  updater.add_section_target({ 
    umlaut_section_id: "search_inside", 
    selector:".stuff", 
    position: "append"  
  });  
  
  updater.add_section_target({ 
    umlaut_section_id: "excerpts", 
    selector:".stuff", 
    position: "append"
  });   
  
  updater.add_section_target({ 
    umlaut_section_id: "holding", 
    selector:".stuff", 
    position: "append",
    before_update: function(container_element) {
      //insert some content into the umlaut-delivered html
      //before it is placed on the page
      container_element.find("h3").after("<h4>We love these holdings.</h4>");
    }
  });   
  
  updater.add_section_target({
      umlaut_section_id: "abstract", 
      selector:".stuff", 
      position: "append",  
      before_update: function(html, count) {
        //only show if there are hits
        return ( count != 0);
      }
   });     
  
  updater.add_section_target({ 
    umlaut_section_id: "related_items", 
    selector:".stuff", 
    position: "append"
  });   
  
  updater.add_section_target({ 
    umlaut_section_id: "highlighted_link", 
    selector:".only_if_content",
    after_update: function(updated_content, count) {
      //show the ancestor DOM element with certain class, only
      //if there are umlaut hits
      if ( count != 0 ) {
        updated_content.closest(".only_if_content").show();
      }
    }
    });   
  
    updater.add_section_target({
      umlaut_section_id: "export_citation",
      selector:".stuff",
      position:"append",
      before_update: function(content, count) {
        $(content).find(".section_heading h3").text("My own crazy export options");
      }
    });
    
    updater.complete = function() { alert("Umlaut fully loaded") };
  
    updater.update();   

  });
</script>


</head>

<body>

<a class="openurl_link" href="http://$UMLAUT_BASE/resolve?sid=google&auinit=N&aulast=Chomsky&title=Aspects+of+the+Theory+of+Syntax&genre=book&isbn=0262530074&date=1965">Find It @ JH</a>

<h2>Full Text</h2>
<div class="my_full_text">
Replace me.
</div>


<h2>More Stuff</h2>
<div class="stuff">
</div>

<div class="only_if_content" style="display:none">
This will be shown only if it actually contains content. 
</div>


</body>

</html>