Showing posts with label Yahoo UI Library. Show all posts
Showing posts with label Yahoo UI Library. Show all posts

Sunday, August 29, 2010

YUI "website top nav" Menu from JavaScript only

I recently had the opportunity to observe someone running into some difficulties trying to implement a YUI 2 Menu. The menu items were to be retrieved from a database, and needed to be dynamically updatable through an AJAX call - so using JSON rather than HTML to build the menu made sense in this case. According to YUI's menu page, "Menus can be created from simple, semantic markup on the page or purely through JavaScript".

In this case, getting the menu built "purely through JavaScript" was looking to be a little bit of a challenge. The "Website Top Nav With Submenus" was the type of menu desired. YUI's available menu examples included this type of menu built both "From Markup" and "From JavaScript". However, the "From JavaScript" version still built the top-level menu with HTML rather than JavaScript. While certainly possible, dynamically generating both HTML and JavaScript from the server-side certainly didn't seem ideal.

Recognizing that YUI is a very robust JavaScript library, I knew that there had to be a way to build the menu with only JavaScript. After starting with a new test script, I quickly found the 2 main parts to the solution:

  1. The MenuBar constructor accepts an "itemData" property as part of its 2nd "config" argument. The menu structure can be configured here for the top level, in a fashion identical to the "aSubmenuData" array of objects visible in the original YUI example.
  2. All menu items - including the top-level menus, submenus, and the menu bar itself - must have a unique ID for the HTML DOM, otherwise the item will not activate. For example, if a top-level menu is missing an ID, it will display, but not activate to display the submenus.

    Ideally, it'd be nice to have the "id" property optional. However, the "YAHOO.util.Dom.generateId()" function works well for this - and at least for the purposes of this example, I aliased it as simply "id".

The complete updated example is shown below. For lack of better data, the same example data from the original YUI example is used:

JavaScript source code:

  YAHOO.util.Event.onDOMReady(function(){
    
    var id = YAHOO.util.Dom.generateId;
    
    var menuData = [
      {
        text: "Communication",
        submenu: {id: id(), itemdata: [ 
          {text: "360", url: "http://360.yahoo.com"},
          {text: "Alerts", url: "http://alerts.yahoo.com"},
          {text: "Avatars", url: "http://avatars.yahoo.com"},
          {text: "Groups", url: "http://groups.yahoo.com"},
          {text: "Internet Access", url: "http://promo.yahoo.com/broadband"},
          {
            text: "PIM", 
            submenu: { 
              id: id(), 
              itemdata: [
                {text: "Yahoo! Mail", url: "http://mail.yahoo.com"},
                {text: "Yahoo! Address Book", url: "http://addressbook.yahoo.com"},
                {text: "Yahoo! Calendar",  url: "http://calendar.yahoo.com"},
                {text: "Yahoo! Notepad", url: "http://notepad.yahoo.com"}
              ] 
            }
          }, 
          {text: "Member Directory", url: "http://members.yahoo.com"},
          {text: "Messenger", url: "http://messenger.yahoo.com"},
          {text: "Mobile", url: "http://mobile.yahoo.com"},
          {text: "Flickr Photo Sharing", url: "http://www.flickr.com"},
        ]}
      },
      {
        text: "Shopping",
        submenu: {id: id(), itemdata: [
          {text: "Auctions", url: "http://auctions.shopping.yahoo.com"},
          {text: "Autos", url: "http://autos.yahoo.com"},
          {text: "Classifieds", url: "http://classifieds.yahoo.com"},
          {text: "Flowers & Gifts", url: "http://shopping.yahoo.com/b:Flowers%20%26%20Gifts:20146735"},
          {text: "Real Estate", url: "http://realestate.yahoo.com"},
          {text: "Travel", url: "http://travel.yahoo.com"},
          {text: "Wallet", url: "http://wallet.yahoo.com"},
          {text: "Yellow Pages", url: "http://yp.yahoo.com"}
        ]}
      },
      {
        text: "Entertainment",
        submenu: {id: id(), itemdata: [
          {text: "Fantasy Sports", url: "http://fantasysports.yahoo.com"},
          {text: "Games", url: "http://games.yahoo.com"},
          {text: "Kids", url: "http://www.yahooligans.com"},
          {text: "Music", url: "http://music.yahoo.com"},
          {text: "Movies", url: "http://movies.yahoo.com"},
          {text: "Radio", url: "http://music.yahoo.com/launchcast"},
          {text: "Travel", url: "http://travel.yahoo.com"},
          {text: "TV", url: "http://tv.yahoo.com"}
        ]}
      },
      {
        text: "Information",
        submenu: {id: id(), itemdata: [
          {text: "Downloads", url: "http://downloads.yahoo.com"},
          {text: "Finance", url: "http://finance.yahoo.com"},
          {text: "Health", url: "http://health.yahoo.com"},
          {text: "Local", url: "http://local.yahoo.com"},
          {text: "Maps & Directions", url: "http://maps.yahoo.com"},
          {text: "My Yahoo!", url: "http://my.yahoo.com"},
          {text: "News", url: "http://news.yahoo.com"},
          {text: "Search", url: "http://search.yahoo.com"},
          {text: "Small Business", url: "http://smallbusiness.yahoo.com"},
          {text: "Weather", url: "http://weather.yahoo.com"}
        ]}
      }
    ];
    
    new YAHOO.util.YUILoader({require: ["menu"], onSuccess: function(){
      var oMenuBar = new YAHOO.widget.MenuBar(id(), {
        autosubmenudisplay: true, 
        hidedelay: 750, 
        itemdata: menuData});
      
      oMenuBar.render("com.ziesemer.demos.yuiMenuJsOnly.global_menu_parent");
    }}).insert();
  });

HTML source code:

  <div class="yui-skin-sam">
    <div id="com.ziesemer.demos.yuiMenuJsOnly.global_menu_parent" class="yuimenubarnav"></div>
  </div>

Output:

Sunday, July 11, 2010

Updated Blogger Tools

I've updated the "Blog Archive" and "Labels" gadgets on this site - as visible in the right-hand margin of this page. Please leave a comment with any issues or suggestions.

Over a year ago, I had already replaced Blogger's default Labels gadget with the Yahoo! UI Library (YUI)'s TreeView component, which provided:

  • A view that is collapsed by default, saving screen space for other features unless the Labels are clicked and expanded for use.
  • A complete and compact list of tagged posts under each label, without having to request a new label search page - while still providing these links.

This tree is populated with an AJAX request to Google's GData Blogger API, using the JSON Alt Type and YUI's Get Utility. Since the returned data is a list of posts, a limited amount of JavaScript is needed to group and sort the posts into the labels. As part of this last update, a label will now again automatically expand to show the related posts if the current page being viewed is a label search page. (This is similar to the default functionality, but is determined by the current URL.)

Also now just completed, I've also replaced Blogger's default Blog Archive gadget with another YUI TreeView, providing:

  • A consistent look-and-feel between the Blog Archive and Labels features.
  • A more compact display, while maintaining readability.
  • Post titles were previously truncated after 50 characters. Complete post titles are now displayed, along with the specific date posted.
  • As the same data retrieved to populate the Labels is re-used, the page loading performance should actually be improved as the HTML is now dynamically generated client-side - instead of downloading what was essentially duplicate information as additional HTML from the server.

As with the Labels, the archive will also automatically expand to show the related posts for the time period being viewed - similar to the default functionality, but again determined by the current URL.

Also just added are "loading bar" graphics for the short period of time between the initial page load and the population of the trees.

A few other quick things to note:

  • The source JavaScript code used for these features is available for reference in this page's HTML source (among the rest of Blogger's default and somewhat cluttered code), using your browser's View/Source feature. Even though it is only ~250 lines or ~7 KB of code, this is not the most efficient as this code is reloaded with every page load. I'd like to be able to properly move this code into a separate and cacheable *.js file - but can't currently find a usable solution that matches the reliability and pricing (free) of the rest of Blogger. I attempted using versions stored on Google Code and Google Sites, but each had various issues - including being able to properly make updates, as well as having download landing pages interfere - partially complicated by not being hosted on the blogspot.com domain. Amazon's S3 storage service looks like a promising option that I may still consider. However, while it would likely only cost pennies / month to use for this, it's another bill to take care of.

  • The JSON data feed received is a bit large - currently ~200 KB - but unlike the JavaScript code, this data is returned with proper HTTP headers by Google, and should be cacheable by the browser. Much of this data is information that is not necessary or used, such as post summaries and comments. Hopefully, these extra fields will soon be selectively disabled - as soon as the Blogger GData API supports partial responses.

  • As visible in the source code, I also needed to implement a custom "clickEvent" handler for YUI's TreeView - otherwise the HTML links contained within the tree nodes were not clickable, as any clicks would instead expand/collapse the node instead of activating the link.

A few ideas for future enhancements:

  • Options to alternatively sort the labels, including by post count.
  • Having the displayed labels under each post interact with the Labels tree.

Sunday, January 11, 2009

New version of MarkUtils-Web: ZipServlet and CompressFilter

If you're not already familiar with my ZipServlet and CompressFilter, see my previous posting where I introduced these Java web utilities.

As with the previous release, the update is available on ziesemer.java.net. The release folder is directly available here. "com.ziesemer.utils.web-2009.01.11.zip" contains the source code, a compiled .jar, and generated JavaDocs.

New in this release are a number of fixes and enhancements. To report any new bugs or enhancement requests, please use the issue tracker on ziesemer.dev.java.net.

ZipServlet combo mode

ZipServlet now provides a feature almost identical to YUI's Combo Handling. This allows for multiple files to be requested and sent joined together in one response, which can reduce the number of HTTP requests and improve performance, as detailed in this post on yuiblog.com.

This feature works particularly well for JavaScript and CSS files. However, only files of one type should be requested together, otherwise the returned MIME Content-Type HTTP header wouldn't make any sense. Unlike the current implementation provided by yahooapis.com, ZipServlet enforces this by returning a HTTP 400 error (Bad Request) if multiple files are requested that have different content types.

The combo mode is enabled by default in ZipServlet, but can be disabled by using the "comboEnabled" servlet parameter. The path on which combination requests are answered can also be configured through the "comboPath" servlet parameter. comboPath defaults to "combo", as used by Yahoo! / YUI. Additional details are listed in the JavaDocs for ZipServlet, included in the download.

A notable difference between ZipServlet and the Yahoo! / YUI implementation is that YUI files require the version to be prefixed to each requested file, e.g. "/combo?2.6.0/build/yuiloader/yuiloader-min.js&2.6.0/build/dom/dom-min.js&2.6.0/build/event/event-min.js". This presumably allows for multiple files to be requested across different versions. This doesn't make a lot of sense, and results in a longer URL as the version is prefixed before each requested file. As ZipServlet is designed to have each instance associated with one .zip file, as each version of a resource (YUI, etc.) belongs in its own file, and due to implementation details, it made sense to include the "combination" functionality directly into ZipServlet rather than as an additional filter, etc. This restricts combination requests to a given ZipServlet instance, while improving code reuse, performance, the URL length, and other aspects. The equivalent URL to the above when requested from ZipServlet, when using the example configuration in the previous post, including the "yui/" zipPrefix, would be "/combo?/build/yuiloader/yuiloader-min.js&/build/dom/dom-min.js&/build/event/event-min.js". Additionally, the leading slash before each file is optional in the ZipServlet implementation.

Unit Tests

New in this release is a complete suite of JUnit tests. I previously had not included any tests partially due to finding a unit testing solution for Java EE servlets and filters that met my requirements. I had looked at HttpUnit ServletUnit, but found a number of shortcomings. While I had previously been testing under Apache Tomcat, this required manual starting and stopping of the server, and registration of the projects. There are methods to automate this, but none that didn't seem overly complex and without their own shortcomings.

I finally decided upon using Jetty. Jetty is both free and open-source, and is 100% Java which makes it very portable. It is very representative of a production servlet engine as it is one, used by many projects including the JBoss and Apache Geronimo application servers. Jetty fully supports embedded within a Java application, which makes it very suitable for unit testing. It can be configured either declaratively or by using standard web.xml files, with details and several examples available at http://docs.codehaus.org/display/JETTY/Embedding+Jetty. As an added bonus, Jetty is readily available as an Apache Maven artifact, including the latest release and beta versions. This means that if opening the project with Maven support, such as with m2eclipse, Jetty and any other dependencies will automatically be downloaded and included on the testing classpath.

For this project, I wrote a ServletTester class that is reused by all the test classes. Each instance starts a Jetty instance on a dynamic port on the loopback address (127.0.0.1), rather than requiring that a specific be used or configured. This class then provides convenience methods for obtaining the server, connector, context, and base URL.

There are 2 types of tests that I provided for each component (ZipServlet and CompressFilter) - standard tests and configuration tests. The standard tests initializes and holds on to a Jetty instance within a ServletTester as a static field, which is reused by all the test methods. This is mainly for performance reasons, so that a new server isn't required for every test. The configuration tests deal specifically with testing the configuration options available for each component. While these configuration tests still reuse the same server instance, the server's context is restarted with a new ServletHandler for every test.

For the details of this methodology, please feel free to download the code and see for yourself. These tests also demonstrate the features and usage of the components.

Thursday, May 29, 2008

Yahoo! User Interface Library (YUI)

As I previously posted, I've switched to the Yahoo! User Interface Library (YUI).

Some key links:

I think YUI is a great library for many of the same reasons I had liked Ext JS:

YUI includes a number of useful and versatile UI components. The ones I use the most are DataTable and Calendar.

A non-UI component that I've been quite pleased with is the YUI Loader Utility. Instead of including the entire YUI library across an entire web site, it allows for safe, easy, and relatively efficient partial loading, including reliable, sorted loading of dependencies.

Unlike Ext JS, there is excellent, comprehensive documentation available by component. Not only the API docs, but "getting started" pages complete with numerous examples and notes as well. In addition to all this, there is also the forum and blog.

There are a few bugs and feature requests that I've submitted on YUI's SourceForge trackers that may be of interest.

I have some additional YUI-related notes to share that will follow as future posts. Just watch for the "Yahoo UI Library" label.

Monday, May 12, 2008

JavaScript namespace function

As a follow-up to my "Respecting the JavaScript global namespace" post, here's a function that can be used to quickly and easily create namespaces:

(Updated to use a global function instead of extending the String prototype.)

var namespace = function(name, separator, container){
  var ns = name.split(separator || '.'),
    o = container || window,
    i,
    len;
  for(i = 0, len = ns.length; i < len; i++){
    o = o[ns[i]] = o[ns[i]] || {};
  }
  return o;
};

Here's the same code, compressed using YUI Compressor:

var namespace=function(c,f,b){var e=c.split(f||"."),g=b||window,d,a;for(d=0,a=e.length;d<a;d++){g=g[e[d]]=g[e[d]]||{}}return g};

Ideally, this function would be assigned to some other object (to respect the global namespace), unless accepted as a standard function as mentioned above.

See also:

Unlike the "Namespacing made easy" post, this solution doesn't require Prototype. This solution also prevents overwriting existing objects and returns the created "namespace".

Example of use:

namespace("com.example.namespace");
com.example.namespace.test = function(){
  alert("In namespaced function.");
};

Or, as one statement:

namespace("com.example.namespace").test = function(){
  alert("In namespaced function.");
};

Either is then executed as:

com.example.namespace.test();

Update (2008-05-13):

The concern was raised that the "len" variable in my for-loop is being created in the global scope / "namespace", which would contradict the entire purpose of this post! :-)

Fortunately, according to developer.mozilla.org, all variables declared after "var" are created within the same scope - and if declared within a function, the scope is that current function.

Here's my "test case". Use Firebug. First check that neither the "i" nor "len" variables already exist. Then run the following code, and note that neither variable is created in the global scope.

(function(){
  for(var i=0, len=0;false;){};
})();

Compared to this version, which does populate "len" into the global scope:

(function(){
  len = 0;
  for(var i=0;false;){};
})();

Maybe surprisingly, this makes "len" local again, even though it is assigned to before being declared with "var". This is because JavaScript does not have block scope.

(function(){
  len = 0;
  for(var i=0, len=0;false;){};
})();

This general practice is documented at http://www.prototypejs.org/api/array, where it is also explained how caching the length property is an aid to performance. (Though for the stated purpose of creating namespaces, which will probably only be handling single-digit lengths for the majority of the cases, the savings will be practically zero.)

Farewell, Ext JS

Though I was previously impressed with the Ext JavaScript library, it's time to move on.

Everything I previously wrote about Ext JS is still true. It is a very capable, comprehensive, well-written JavaScript library. Unfortunately, the management of the project doesn't at all seem to live up to the same standards.

There were always a few minor ongoing issues, which still exist as of this posting. One is the lack of bug / feature request trackers, e.g. Bugzilla. Another is the restriction of read-only access to the Subversion source code repository to only premium (paying) members.

The main issue, however, is the license change that was announced with the release of Ext JS 2.1 on 2008-04-21. Ext JS was always dual licensed under both the LGPL license as well as a commercial license. This meant that anyone was free to do almost anything with the library. The only recognized restriction was using Ext JS in a "software development library or toolkit", when the commercial license would have been required.

Introducing the GPL

With the license change, the dual licensing model still exists, but uses GPL instead of LGPL. I and many other users didn't see this as an issue for our uses. Unfortunately, there was little more than confusion on Ext's "License Change?" forum, which as of this writing topped 650 posts and 61,000 views.

I generally support open source. Even corporate development commonly makes uses of open source software, licensed under either LGPL, GPL, or dozens of other "open source / free" licenses. The one restriction with using GPL-licensed software is that if the parent application is to be redistributed, the source code usually has to be redistributed as well. I don't have a problem with this for any of my personal applications.

While many businesses work with proprietary applications that simply are not meant to be shared, they can even utilize GPL-licensed software as long as their application is not "redistributed". For companies who only develop and host a web site rather than developing and selling software, it would seem that there would be not redistribution and no GPL requirements. Even Microsoft uses a GPL-licensed JavaScript library on their web site, and they don't appear to be under any requirement to release any source code.

The GPL license makes a distinction between propagate and convey:

To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well.

To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying.

From all definitions / explanations I've seen, simply hosting a web site (utilizing Ext or whatever) would be classified as propagation, not conveying. To convey, one would have to do something similar to zipping up the web site and redistributing it for others to host as well (regardless of terms).

Ext Indecisions

This is where everything gets muddy really fast with Ext.

On 2007-03-27, one of the lead Ext developers agreed with the statement that "For the purposes of Ext, hosting an application is not considered distribution, so the license does not apply".

Less than a month later, the Ext team then claims that by simply hosting an Ext-enabled web application, the application is being conveyed as well as propagated. They claim that all the source for all related JavaScript code in the application would then also be covered by the GPL, and must have the source code be made available. Since JavaScript is an interpreted rather than a compiled language, the argument could be made that this requirement is already fulfilled - by running the application, the source is already provided. However, the same lead developer further stretches a claim that not only must the sources be made available for the client-side code, but portions of any server-side code as well.

Please understand - the Ext authors probably have the right to distribute Ext JS under whatever license they choose. The issue is that they are claiming the GPL license, but requiring different terms. As I and others have suggested, it seems what the Ext team really wants is the AGPL license. From http://www.fsf.org/licensing/licenses/:

Its (the AGPL) terms effectively consist of the terms of GPLv3, with an additional paragraph in section 13 to allow users who interact with the licensed software over a network to receive the source for that program. We recommend that developers consider using the GNU AGPL for any software which will commonly be run over a network.

Again, if Ext, LLC does not wish to license Ext JS under either the LGPL, the GPL, or the AGPL, they are free to create their own license. Just don't claim to honor the GPL and then dispute the terms.

To clarify, I don't see any issues using Ext JS in an "open source", GPL'd application. However, there is so much confusion over the license terms that I don't see how any business could risk playing this license game, short of paying for the commercial license (which still leaves unanswered questions and concerns). Additionally, why choose the commercial license when other genuinely-free alternatives are readily available?

Related Posts

Here are a few other related posts on this subject:

Switching to YUI

As I posted to the Ext "License Change?" forum, I've since switched to the Yahoo! User Interface Library (YUI). Beyond being BSD licensed, YUI has:

  • equally capable event listeners/management,
  • official & structured bug / feature request / patch trackers on SourceForge,
  • no forum threads restricted to premium/paying members,
  • commitment to make the SVN repository publicly available,
  • and fixes / inclusions of many of the bugs / enhancement requests reported here against Ext JS without action.

I was using Ext JS only for 2-3 odd bits of functionality I found in it almost a year ago. I didn't see them in YUI at the time, either because they've since been added, or that I just missed them the first time around (more likely).

Over the past few weeks, I've found that working with YUI performs better, and is actually easier and faster to work with. (Yahoo! User Interface Library (YUI))

YUI isn't the only alternative out there. The Dojo Toolkit is one other that I've been considering.

Monday, April 21, 2008

MarkUtils-Web: ZipServlet and CompressFilter

"MarkUtils-Web" is an initial collection of web-related Java utilities that I wrote and am releasing on ziesemer.java.net for public use (GPL).

The source code, a compiled .jar, and generated JavaDocs are all included in "com.ziesemer.utils.web-*.zip". (Download)

At the moment, there are 2 components:

ZipServlet

My idea for ZipServlet came while working with YUI (and previously Ext JS) in a J2EE application. Typically, when including Java components, one or few JAR libraries are imported into the application. No real equivalent really exists for web content.

Full support of most JavaScript libraries require a sizable collection of files - not only JavaScript, but CSS, images, and other resources as well. For example, for full support of Ext JS 2.0.2, including the several required .js scripts along with .css and image resources, this amounts to 1,818 files across 174 folders, totaling 21.9 MB. While Ext JS provides some other options, including custom builds, each poses dependency and other challenges. Even including only the core 2 .js files, 1 ext-all.css, and possibly 196 image files still seems a bit excessive.

Including any large number of files in a project can quickly lead to difficulties, particularly with source control. While utilizing the extracted layout would easily allow developers to patch and modify the included project, such practice is typically not a good idea and would likely lead to additional upgrade complexities as previous changes would have to be found and migrated. Just the upgrade itself would require the replacement of potentially hundreds of files!

My more ideal solution was to simply include YUI's distribution .zip into my project's WebContent/WEB-INF folder. My ZipServlet then serves any YUI-related files requested by the client directly out of the .zip file. While this leads to additional uncompression that can require additional CPU time, I've not encountered any performance issues. Client-side caching is usually "on" by default. Additional server-side caching could additionally be used. Another option would be to re-package the .zip file without compression. (The size of the .zip file would be larger, but it would still be a single .zip file.)

Shown below are the relevant sections out of my web.xml file, allowing yui_2.6.0.zip to appear just like a folder stored on my web server. For example, with the configuration listed below, "<contextRoot>/build/yahoo-dom-event/yahoo-dom-event.js" will return oneof YUI's primary JavaScript files:

<servlet>
 <servlet-name>yui_2.6.0</servlet-name>
 <servlet-class>com.ziesemer.utils.web.ZipServlet</servlet-class>
 <init-param>
  <param-name>file</param-name>
  <param-value>/WEB-INF/yui_2.6.0.zip</param-value>
 </init-param>
 <init-param>
  <param-name>zipPrefix</param-name>
  <param-value>yui/</param-value>
 </init-param>
</servlet>

<servlet-mapping>
 <servlet-name>yui_2.6.0</servlet-name>
 <url-pattern>/yui_2.6.0/*</url-pattern>
</servlet-mapping>

See the included JavaDocs for further details on these options.

ZipServlet follows the standards described in RFC 2616 as applicable. In particular, this implementation supports last modified checking and partial content requests.

Similar to YUI, some other libraries I've included this way include Firebug Lite and The Dojo Toolkit.

CompressFilter

The idea behind CompressFilter is explained fairly well in "Two Servlet Filters Every Web Application Should Have" (Jayson Falkner, 2003-11-19, ONJava.com). The sample code included on ONJava.com is a bit lacking and requires some improvement, but to be fair, it is over 4 years old.

Notable features of my CompressFilter include:

  • Following the standards described in RFC 2616 as applicable, particularly sections 3 and 14.
  • Support for both gzip and deflate, while respecting the priorities both requested by the client and configured on the server.

Configuring a CompressFilter into an application requires only a <filter/> and one or more <filter-mapping/> elements in web.xml. It is recommended to include <dispatcher>REQUEST</dispatcher> into <filter-mapping/> to prevent output from being compressed multiple times (e.g. from <jsp:include/>), as defined in the Java Servlet 2.4 Specification (see SRV.6.2.5).

See the included JavaDocs for further details. Also see the update information.