YUI 3.4.0 Preview Release 3 Now Available on CDN

July 28, 2011 at 12:39 pm by George Puckett | In Development | 4 Comments

The YUI team has just completed the final development sprint for the 3.4.0 release. At this time we consider the code functionally complete. We are planning to spend our next sprint focusing on our final round of testing and creating more examples and documentation. We have posted an FC (functional complete) build to the CDN for community exploration and feedback. You can access this release at http://yui.yahooapis.com/3.4.0pr3/build/yui/yui-min.js.

There are some particular areas of the library where we’d love to have community feedback:

  • Loader has had a significant update for 3.4.0. If you are doing manual load specifications via use("*") or make use of submodule configurations, we’d greatly appreciate you trying your code with the new Loader to be sure we are correctly handling all use cases. For more detailed information on the Loader changes in this release, refer to the blog post describing 3.4.0 Loader changes.
  • Calendar and Panel are fully functional and ready for developer use.
  • Graphics: There have been a few API changes that will affect any experimental code written on the Graphics API distributed in the PR2 release. getShape() has been renamed addShape(). There have also been several attribute replacements.
  • Transition: Native transitions are now supported in FireFox.
  • WidgetButtons has been released as a new Widget extension that allows you to place css-styled buttons in the header and footer of any widget that implements standard module support.
  • Widget-Modality and Widget-AutoHide plugins have been converted to extensions.
  • Widget: Added support for destroy(true) which will remove and destroy all child nodes (not just the boundingBox and contentBox) contained within the Widget’s boundingBox. destroy() will maintain its current behavior due to the potentially high run-time cost of destroying all child nodes. If you destroy Widgets in your application or are a custom widget developer, your help in testing this change would be appreciated.
  • ScrollView now supports vertical paging, includes a scrollview-list plugin to add CSS classnames to immediate list elements, as well several bug fixes and refactoring
  • App Framework: We want to extend a sincere thank you to all of the developers in the community who have taken the time to test drive the new App Framework. We have received excellent feedback following the PR2 release. Please continue to explore these components and send us your observations and suggestions.

You can get additional information on the content of this release by reviewing the History Rollup and the full list of tickets addressed in PR3. Please file any enhancement requests, bugs and regressions in the ticket database on YUILibrary.com.

Share and extend: Bookmark with del.icio.us | digg it! | reddit!

YUI: Open Hours Thurs July 28th

July 25, 2011 at 10:56 pm by Luke Smith | In Development, Open Hours | 2 Comments

Y.Calendar is coming to 3.4.0

Calendar is one of our more popular widgets in the YUI 2 family, and it’s making its debut on the YUI 3 architecture in 3.4.0. Allen Rabinovich is the component owner and author and will be on the call reintroducing us to this old favorite, showing some new approaches to problems faced by 2.x Calendar. I’m particularly jazzed about the support for internationalization, but the new rendering rules are also pretty fascinating.

Come on in, and bring your date-picker, event-calendar, import-from-iCal-and-make-pancakes questions and feature requests with you as we flesh out the now and future Y.Calendar. (No, it won’t import iCal, but if somebody wants to create a gallery module to tame that beast, there’s sure to be a YUIConf ticket in it for you ;) )

We’re back to our usual time this week, so we’ll see you in Connect at 10am PDT.

Time & Details

We’ll be online from 10am to 11am PDT Thursday. The connection details are the same as usual.

  1. Dial in to 1-888-371-8922 (Skype works great for non-US participants*)
  2. Enter the attendee code 47188953#
  3. Join the screen sharing session (this will prompt you to install the Adobe Connect plugin if this is your first time using it)

Note: Because it’s an open conference line, we ask that callers mute their lines unless they are participating in active discussion.

* – If Skype is not an option, email me or catch me (ls_n) in the #yui IRC channel on freenode for a local number.

Recording

Thanks to everyone for calling in! The online recording of the session is now available.

The high quality, iPhone/iPad compatible, downloadable recording is here.

Share and extend: Bookmark with del.icio.us | digg it! | reddit!

YUI: Open Hours Thurs July 21st

July 19, 2011 at 2:16 am by Luke Smith | In Development, Open Hours | 12 Comments

A DataTable update and gallery showcase

The 3.4.0 release cycle is coming to a close and will be packed with all sorts of great features, but speaking plainly, DataTable hasn’t gotten as much development focus as it should have. There have been some bug fixes, though, and a fair amount of planning for changes that are targeted in 3.5.0, and a great start to community involvement with its development.

We know that DataTable is an incredibly important widget for a lot of customers, so we understand the cost of delaying focused development. This Open Hours will be an update on what work is getting done for 3.4.0, what’s planned for 3.5.0, and an introduction to the great work that’s begun to spring up in the Gallery to add features and fix bugs for DataTable (and its family of supporting classes).

We’ll be online an hour earlier this week for the benefit of Eamon Brosnan (aka, mosen from #yui), who’s provided a number of the Gallery patches we’ll be looking over. Otherwise, we’ll have other #yui denizens and Gallery contributors showing their wares. If you have a DataTable solution or work in progress you’d like to share, please let me know so I can block out the schedule to fit everything (ls_n in #yui or twitter).

Time & Details

We’ll be online from 9am to 10am PDT Thursday. The connection details are the same as usual.

  1. Dial in to 1-888-371-8922 (Skype works great for non-US participants*)
  2. Enter the attendee code 47188953#
  3. Join the screen sharing session (this will prompt you to install the Adobe Connect plugin if this is your first time using it)

Note: Because it’s an open conference line, we ask that callers mute their lines unless they are participating in active discussion.

* – If Skype is not an option, email me or catch me (ls_n) in the #yui IRC channel on freenode for a local number.

Share and extend: Bookmark with del.icio.us | digg it! | reddit!

Next-Gen YSlow powered by YUI

July 18, 2011 at 9:17 pm by Marcel Duran | In Development, Performance | 4 Comments

A couple of weeks ago, Yahoo! announced YSlow for Mobile at Velocity 2011, bringing the power of YSlow performance analysis to the mobile world.

YSlow for Mobile works as a bookmarklet, making it possible to run on browsers other than Firefox (as an add-on) or Chrome (as an extension).

The YSlow architecture was partially redesigned to work cross-platform and YUI was the essential factor in make sandboxing, cross-browser abstraction and simple YQL access possible.

Sandboxing

In order to be embedded on a page without interfering with performance analysis and without messing with the page itself, YSlow is a bookmarklet that injects JavaScript and CSS into any page by leveraging the power of YUI sandboxing:

Bookmarklet URL:

javascript:(function (y, p, o) {
    p = y.body.appendChild(y.createElement('iframe'));
    p.id = 'YSLOW-bookmarklet';
    p.style.cssText = 'display:none';
    o = p.contentWindow.document;
    o.open().write('
        <head>
        <body onload = "
            YUI_config = {
                win: window.parent,
                doc: window.parent.document
            };
            var d = document;
            d.getElementsByTagName(\'head\')[0]
                .appendChild(
                    d.createElement(\'script\')
                ).src = \'http://d.yimg.com/jc/yslow-bookmarklet.js\'"
        >
    ');
    o.close()
}(document))

The code above:

  • creates an empty iframe;
  • appends it to the page body;
  • hides the iframe*;
  • gets its window handler;
  • writes into its content the body of the iframe;
  • this body is empty but has an onload event
  • the onload event defines how to inject YSlow JS:
    • sets YUI_config, so win and doc points to the page being analyzed window and document respectively
    • dynamically injects YSlow URL by creating a script element into iframe’s head

* the iframe is displayed by the time all YSlow presentation assets are loaded

This will place an iframe into the page being analyzed. This iframe will act as a sandboxed environment and YSlow will reside within it. Since the iframe is dynamically created without the src attribute, it will have access to its parent (the page being analyzed) because there’s no same origin policy violation happening there.

The YUI_config object is handy because it sets win and doc to the iframe’s parent (the page being analyzed), thus any new YUI instance will be bound to the parent document by default, wiring any call to Y.all and Y.one through Y.config.win or Y.config.doc from the YUI use callback.

YSlow’s presentation is handled by the iframe window and document references, allowing the YSlow main script to render the markup as well as fetch the external CSS within this iframe without conflicting with the parent page’s styles. YSlow scans the parent page in order to get all the components (images, scripts, links, background-images, flash, etc.) required for later performance analysis. This is done by accessing Y.config.win and Y.config.doc, since they refer to the parent page.

Cross-browser abstraction

Being a bookmarklet, YSlow for Mobile is supposed to work on any browser*. YUI abstracts cross-browser issues by normalizing browser differences, resulting in a clean, easy-to-read and maintainable codebase.

YSlow was not fully ported to YUI 3 — only the controller layer (from the upcoming App component) for now — but still, all DOM manipulation and event handling are done by the node and event modules. In future releases we plan to port more YSlow features to YUI 3.

* not all browsers are currently supported

YQL

YSlow analyzes pages by checking the HTTP headers for the components found on the page. HTTP response headers are not available in the page, hence those components need to be requested again in order for YSlow get the response header information. This could be achieved by requesting the list of component URLs through XmlHttpRequest (AJAX) but unfortunately due to same origin policy restriction, this is not possible unless all components are in the same domain as the page which is very unlikely.

A common workaround for same origin policy restriction is using JSONP, where an external server works as a proxy requesting the list of components URLs and retrieving their HTTP response headers on behalf of YSlow. Due to YSlow’s popularity and recent mobile performance analysis efforts, we’re expecting quite heavy traffic for the YSlow for Mobile bookmarklet. In order to support such traffic, YQL was the scalable solution adopted by YSlow through an open data table named data.headers, which retrieves the response headers and content for a given list of URLs while impersonating the user-agent to ensure the expected content is retrieved.

The YQL Query component does all the work of managing YQL queries while managing JSONP requests under the hood, making the YSlow controller code much simpler and easy-to-maintain.

Future enhancements: New YSlow for Mobile friendly interface

Currently the YSlow for Mobile user experience is the same as the desktop experience. Dealing with a long list of performance analysis data is not the best experience on small smart-phone screens. Since YUI also abstracts cross-device gestures, YSlow for Mobile will get a brand new mobile-friendly interface in future releases.

Performance of performance tool

YSlow for Mobile deployment was made carefully considering its performance impact on the load time of the page being analyzed. The YUI 3 modules used on YSlow were scrutinized to include only the modules needed to load YSlow as fast as possible. The YUI seed file and Loader were not included since all necessary modules and submodules were combined together following Ryan Grove’s Performance Zen tips, which made it possible to load everything together into a single small single request: yslow-bookmarklet.js: 204KB, 66KB (gzip) where:

  • YUI: 75KB, 27KB (gzip)
  • YSlow: 129KB, 39KB (gzip)

More about YSlow

Keep up-to-date with the latest YSlow announcements by:

Marcel DuranAbout the author: Marcel Duran is the Front End Lead for Yahoo!’s Exceptional Performance Team. He has been into web performance optimization on high traffic sites in Yahoo! Front Page and Search teams where he applied and researched web performance best practices making pages even faster. He is now dedicated to YSlow and other performance tools development, researches and evangelism. His goal is to make the web even faster than it can be and believes there is no such thing as “just a few milliseconds won’t hurt”.

Share and extend: Bookmark with del.icio.us | digg it! | reddit!

Graded Browser Support Update

July 12, 2011 at 8:55 pm by Jenny Donnelly and Matt Sweeney | In Development, Graded Browser Support | 21 Comments

GBS Changes

Specific changes for this update include:

Browser Test Baseline

Internet Explorer 6.0 7.0 8.0 9.0
Firefox 3.† 4.† 5.†
Chrome † Latest stable
Safari 5.† iOS 3.† iOS 4.†
Webkit Android 2.†

Notes:

  • The dagger symbol (as in “Firefox 4.†”) indicates that the most-current non-beta version at that branch level receives support.
  • No guidance is given on iOS or Android OS device usage. The recommendation is that you choose the devices that are most representative of your user base for each OS.

Removing Grades from the Browser Test Baseline

This edition of the GBS update represents a departure from our previous updates in that we are moving away from mapping browsers directly to experience grades (e.g. “A-grade” and “C-grade”). Rather than prescribe what user experience is appropriate for which browsers, we’ll focus on defining an efficient baseline test strategy that maximizes test coverage and minimizes the testing surface. For example, IE6′s still-significant global marketshare warrants continued testing; however today’s GBS allows for the IE6 user experience to be different from the IE9 experience.

Removing Operating Systems from the Browser Test Baseline

In order to streamline testing and minimize resource requirements, we no longer specify which operating system should be tested on. The only exception is when the browser is tightly coupled with the OS version, in which case we refer to the OS version rather than the browser version (e.g. “Safari iOS 4″). This allows us to focus test coverage on browser versions, and minimize redudant testing across platforms. Issues with the same browser across versions are negligible, and generally related to higher-level OS differences, such as key handling and available fonts. Code that is known to touch upon cross-platform issues should be tested on as many platforms as possible, but this testing generally can be isolated to the specific issues rather than running a full regression test of all features. We recommend aligning operating system testing priority with your user base.

Why is IE6 Still on the List?

IE6 still has a significant enough global market share to warrant a verified acceptable user experience. One common misconception with the Progressive Enhancement strategy has been that once a browser enters “C-grade” that it becomes “unsupported”, when in fact it really means that it should be delivered the HTML-only experience. Now that we no longer prescribe which browsers receive what experience, this is left for projects to decide based on their users and resources. The GBS focuses on specifying which browsers need a verified usable experience based on factors such as market share and influence. Defining what is “usable” and specifiying acceptable levels of degradation are left for teams to decide. We still promote a simple Progressive Enhancement model, and discourage projects from creating new tiers without accounting for the additional costs in development, testing, and maintenance resources.

GBS Forecast

We expect to make the following changes in the next update:

  • Discontinue coverage for Safari on iOS 3.
  • Add coverage for Webkit on Android 3.
  • Add coverage for Firefox 6.
  • Add coverage for Safari iOS 5.

The GBS Archive

Share and extend: Bookmark with del.icio.us | digg it! | reddit!

The "MakeNode" Widget Extension

July 8, 2011 at 2:11 pm by Satyam | In Development | 6 Comments

Editor’s Note: Since this article was originally published, the MakeNode module has been published to the YUI Gallery and received some enhancements. Please refer to the updated article, Updated: The “MakeNode” Widget Extension.

In my previous article, A Recipe for a YUI 3 Application, I showed a way to use Y.substitute as a very basic template processor. The idea took life from there, with suggestions from the folks in the #yui IRC channel, and I made it a Widget extension that is available on my site, called MakeNode. MakeNode is not a generic template processor and it is not meant as one. On the other hand, it is tightly integrated with the YUI Widget foundation class, including className and event helpers and internationalization. In this article, I will take the Spinner example and modify it to follow the guidelines from my previous article and to use MakeNode. The modified Spinner component (JS, CSS, sprites) as well as an example are available from my site. Links to further resources can be found at the end of this article.

Extending your component

Once MakeNode is loaded, you need to include the module in your YUI().use() statement using the name ‘makenode’. Then, to extend your widget, you list it in the third argument to Y.Base.create(), like this:

Y.Spinner = Y.Base.create(
     ‘Spinner’,
     Y.Widget,
     [Y.MakeNode],
     {
        // instance members …
     },
     {
         // static members
     }
); 

You can add MakeNode along any number of suitable extensions for Widget, such as WidgetParent, WidgetChild, WidgetStdMode, etc. MakeNode adds two protected methods, _makeNode and _locateNodes, and it will read from several static properties, if found.

All members of this extension are either protected or private since they are meant to be used by the component developer and not by the implementer using those components, who should not be bothered with them.

Defining the Template

The first thing you will normally do is to define the template for your component. For the Spinner, our template will be:

_TEMPLATE: [
    '<input type="text" title="{s input}" class="{c input}">',
    '<button type="button" title="{s up}" class="{c up}"></button>',
    '<button type="button" title="{s down}" class="{c down}"></button>'
].join(‘\n’),

The default template will usually be named _TEMPLATE and declared along the other static properties of the class, such as ATTRS. MakeNode will use this template if none other is explicitly provided. The template is made of plain HTML plus a series of placeholders enclosed in curly brackets, each made of a single character (the processing code) and followed by one or more arguments. The placeholders and what they produce are:

  • {@ attributeName} configuration attribute value

  • {p propertyName} instance property value

  • {m methodName arg1 arg2 ….} return value of the given method. The processing code is followed by the method name and any number of arguments separated by whitespace. Strings must be enclosed in double quotation marks. Numbers, Booleans and null will be converted from string to their proper data types

  • {c classNameKey} CSS className generated from the _CLASS_NAMES static property

  • {s key} string from the strings attribute, using key as the sub-attribute.

  • {? other placeholder} Produces the string checked when the result of processing the rest of the placeholder is true.

  • {} any other value will be handled just like Y.substitute does.

For example, {@ value} will translate to this.get(‘value’) while {p value} translates to this['value'].

The {m} placeholder is a little more complex. The first argument after the m processing code is the name of the method and the rest of the arguments, all separated by whitespace, that will be passed to the given method. Those arguments can be numbers, null, true, false or strings enclosed in double quotes. MakeNode will parse them and convert them to their proper types, thus {m myMethod 123.45 true “this is a string”} will result in calling this.myMethod(123.45, true, “this is a string”) so that the first two arguments are converted to their correct data types and the string can contain spaces. To include a double quote, use \\", the double backslash being required because JavaScript will interpret a single one and discards it before it gets to MakeNode. Only double quotes are allowed, MakeNode does not use eval() so the parser is limited but safe. Anything but numbers, null, Booleans and double quoted strings will be ignored.

The {?} placeholder is handy to use with checkboxes and radio buttons. It will produce the string “checked” depending on the truth value of the processing instruction code that follows. Thus, <input type=”checkbox” {? m getLength}/> will produce a marked checkbox if the getLength method returns anything but zero. {?} will accept any of the other placeholders, though it only makes sense with the first three.

For the {c} placeholder, we need to have a _CLASS_NAMES property defined.

Further placeholders can be added to MakeNode by adding them into the _templateHandlers hash.

The _CLASS_NAMES property

Along with the ATTRS and _TEMPLATE static attributes, we can define a _CLASS_NAMES property which points to an array of strings. Each of those strings will be used to generate a className. Thus _CLASS_NAMES: ['input'] will produce the className “yui3-spinner-input”. Those classNames are stored in an instance property this._classNames. The {c input} placeholder in the template above will be replaced by “yui3-spinner-input”.

You can use the _CLASS_NAMES property to generate any number of classNames, whether you use them in the template or not. You can still reach those extra classNames from within this._classNames. The className is generated using the yui3 prefix followed by the value of the NAME static property turned lowercase, and then the string given in _CLASS_NAMES (this last one will not be turned lowercase), all separated by hyphens. The _classNames hash will also contain the classNames for the boundingBox and the contentBox, the first under the "." key and the second under the “content” key. Widget assigns to the boundingBox the classNames derived from the values of the NAME property of each of the classes in the inheritance chain, starting with yui3-widget. MakeNode stores into this._classNames only the top-most className for the boundingBox.

If a component is several levels away from Widget, like SuperSpecialSpinner inheriting from SuperSpinner which inherits from Spinner which inherits from Widget, and if any or all of them have _CLASS_NAMES properties defined, MakeNode will produce classNames for all of them and store them in this._classNames. You don’t need to include at each level the names already declared in the previous levels. In fact, it is better that you don’t since the classNames produced at each level will use the value of the NAME property of that level. Thus, in SuperSpecialSpinner, {c input} will still result in “yui3-spinner-input” and not “yui3-superspecialspinner-input” and so it will keep your CSS file still valid.

The {s} placeholder

Widget has a strings configuration attribute defined, though it is not initialized with any value. This attribute is meant to hold strings that are visible to (or, via screen readers, read to) the user. It is important that you never include visible strings directly in the template. This is not a requirement of MakeNode — it has never been a good idea at all. All strings that are to be viewed by or read to the user should always be placed in the strings attribute. The strings attribute contains a hash where each individual text is located by its key. The Spinner component has the following strings, which you can see used in the template above.

strings: {
    value: {
        input: "Press the arrow up/down keys for minor increments, page up/down for major increments.",
        up: "Increment",
        down: "Decrement"
    }
},

The best part of doing this is that your component can be localized to other languages very easily by developers using your component. When creating an instance of Spinner, you might do:

var mySpinner = new Spinner({strings: Y.Intl.get(‘spinner’)});

Setting the configuration attribute strings in this way replaces the default strings values with those from the language resource file using the language previously defined. The {s} placeholder accesses the strings stored in the strings attribute, either the default ones or the translated ones, if set. The {s xxxx} placeholder is, in fact, nothing more than a shortcut to the {@ strings.xxxx} placeholder. However, the first can only access strings at the top level while, for example, {@ strings.xxxx.yyyy.zzzz} would allow you to access a string deeper down.

Using _makeNode in renderUI

We use the template to create the markup for our component. To do so, we can call MakeNode’s _makeNode method, like this:

renderUI : function() {
    this.get(‘contentBox’).append(this._makeNode());
},

This will fill in the contentBox of our widget with the markup from processing the template. The _makeNode method returns an instance of Y.Node which can be appended or inserted anywhere or just held for later use. It does not return a string, it produces a Node instance.

The _makeNode method takes two optional arguments: a reference to a template and an object to fill in placeholders, as Y.substitute does. In our simple Spinner example there is a single template for the whole widget but other widgets might require bits and pieces made out of several templates. In that case, you would usually call _makeNode with no arguments for the main part and call it once again with different templates to fill in the extra parts. The example contains this renderUI method:

renderUI: function () {
    var fieldset = this._makeNode();
    this.each(function (item) {
        fieldset.appendChild(this._makeNode(MultipleTemplates.RADIO_TEMPLATE, item));
    }, this);
    this.get(‘contentBox’).append(fieldset);
}

The first call to _makeNode returns a Node instance stored in the variable fieldset. The sample component is also extended with Y.ArrayList so the RADIO_TEMPLATE will be filled with values taken from the items stored in the array list and the resulting Nodes appended to the fieldset before it is finally appended to the contentBox. The special placeholders such as {@} or {p} will still refer to attributes or properties in the main object. The nested items will be processed just as Y.substitute would.

The _locateNodes method

MakeNode further provides a _locateNodes method which will try to locate all the elements with the classNames declared in _CLASS_NAMES. To locate specific elements you can pass any number of className keys, otherwise, _locateNodes tries them all. For each element found of each className, _locateNodes will produce a private instance property using the underscore prefix followed by the key name and the “Node” suffix. Thus, in our Spinner example, _locateNodes will generate the properties _inputNode, _upNode and _downNode. If several elements have the same className, _locateNodes will return a reference to the first of them. If an element is not found, no variable will be created.

In the Spinner component we use _locateNodes after creating the markup:

renderUI : function() {
    this.get(CBX).append(this._makeNode());
    this._locateNodes();
},

The _EVENTS static property

One further property can be defined along the lines of _TEMPLATE and _CLASS_NAMES and that is _EVENTS. _EVENTS will contain a hash made up of class name keys, each containing a hash of event types and methods to handle them. It is better explained with an example:

_EVENTS: {
    ‘.’: {
        key:{
            fn:’_onDirectionKey’,
            args:((!Y.UA.opera) ? "down:" : "press:") + "38, 40, 33, 34"
        },
        mousedown: ‘_onMouseDown’
    },
    ‘..’: {
        mouseup: ‘_onDocMouseUp’
    },
    input: {
        change:’_onInputChange’
    }
},

_EVENTS is an object (a hash) with any number of properties. The names of the properties, that is, the keys of the hash, identify the elements whose events we will listen to. They are the same identifiers used in _CLASS_NAMES. There are two extra special keys "." and "..". While the className keys refer to elements nested within the contentBox, the "." key refers to the boundingBox itself while ".." refers to the document containing this widget. Think of them as doing a chdir command when located at the boundingBox level. The _EVENTS property is processed after the renderUI, bindUI and syncUI methods have been called so the widget is expected to be already inserted within the document body, otherwise the ".." will fail.

Each of the entries in _EVENTS is a further object that uses the type of event as its key and the name of an instance method to handle it. _EVENTS, being a static variable, has no access to this so it cannot take actual function references, only the name of the event listener method. Some event types need extra arguments, such as the key event. In that case, instead of providing the name of the event handler you can provide an object with properties fn and args to hold the function name and the extra arguments, when required.

MakeNode will use Node.delegate to listen to the events of the nested elements, while it will use Y.on to listen to events from the boundingBox and the document body. (Note: listening to the key event on any nested element works only with version 3.4.0pr1 and above, since delegation of the key event was not available before. All the other features work with previous versions as well.)

The _EVENTS declarations are cumulative when components inherit from one another. Each class in the inheritance chain will have its own _EVENTS declaration processed separately.

The _ATTRS_2_UI static property

Events go both ways, from the UI to the component and from the component to the UI. The first are handled by the _EVENTS property. Then there are the events fired by attribute value changes that need to be reflected in the user interface. As I mentioned in the previous article, when there are any secondary effects from changing a configuration attribute, they should be handled by change event listeners, not by the optional setter method of the attribute, which should only deal with normalizing the value being set. The UI should reflect the state of the configuration attributes, first in syncUI, when being initialized and then on every attribute change event. For the latter, we need to attach an event listener, which we do in bindUI. Widget already provides a mechanism to make that simple, which I described in the comments to the previous article.

Widget uses the instance property _UI_ATTRS that contains an object with two further properties, SYNC and BIND. Each of these is an array listing the names of the configuration attributes to be initially synched and then listened to in order to keep the UI reflecting current values. Widget expects each of those entries to have a method associated with it, named after the attribute name prefixed by _uiSet with the first character of the attribute name converted to uppercase to have the method name in proper camel case. Thus, if "value" was listed in any of the _UI_ATTRS arrays (in either SYNC or BIND), Widget would expect to find a _uiSetValue method. This method will receive two arguments, the value being set and the src of the change. This is the code for our Spinner _uiSetValue method:

_uiSetValue : function(value, src) {
    if (src === UI) {
        return;
    }
    this._inputNode.set(VALUE, this.get(FORMATTER)(value));
},

All the uppercase identifiers you see in this piece of code correspond to string constants declared elsewhere, to allow the YUI compressor to do its job better. The method, basically, sets the value HTML attribute in the <input> box to the new value set, after being formatted. The reference to the textbox was provided by _locateNodes. The src argument is initially checked to see if set to the string value ‘ui’. If this is so, no action will be taken. This is to avoid endless loops. If the user enters something in the input box, its value would go into the value configuration attribute which then would fire a valueChange event, which would get _uiSetValue called which, if unchecked, would then go and change the value of the input box, which would trigger the whole process again. Thus, in _uiSetValue, if we know the change comes from the UI, we do nothing and so break the loop. However, this requires another piece of code elsewhere. In the listener for the DOM event, when we set the configuration attribute, we use the third optional argument to set, like this:

_afterValueChange : function(ev) {
    this.set(VALUE, ev.newVal, {src: UI});
}

It is up to us to ensure that changes coming from the UI are flagged thus and then check that same flag to avoid loops.

With all this said, I haven’t yet mentioned the static property _ATTRS_2_UI mentioned in the heading of this section. As the comments in my previous article shows (through the blunders I made in them), making sure that all attributes affecting the UI are properly listed is somewhat messy. You should never initialize _UI_ATTRS from scratch since Widget already lists a whole lot of attributes and those would be lost. You have to concatenate new attribute names over the existing ones, which is somewhat hard to remember how to do it right. To make it simple, MakeNode will read from the static property _ATTRS_2_UI and do that concatenation for you. It will concatenate all such lists from each and every class in the inheritance chain so at each level each class can handle its own attributes. In Spinner, we have:

_ATTRS_2_UI: {
    BIND: VALUE,
    SYNC: VALUE
},

MakeNode will accept both an array of names or a single attribute name, as in this case.

The question naturally arises, why two lists, one for binding the other for syncing? Quite often the SYNC array has fewer entries than the BIND list and this is because the template for the component might already have the very same default value as the configuration attribute and there is no need to do an initial syncing. So, if the default value for the value configuration attribute is an empty string and the <input> element in the template has no value attribute, then there is no need to sync them on initialization.

MakeNode will check for duplicate entries in any of these arrays. If any appear, it means that a class our component inherits from already handles this attribute and any new declaration would most likely overstep the _uiSetXxxx handler for it. Incidentally, MakeNode also checks for duplicate entries in _CLASS_NAMES, which can also cause conflict in some, though not all, circumstances. MakeNode will write a message to the log for any such error.

Conclusion

MakeNode provides a very simple template processor, with simple functionality that is highly integrated with the Widget foundation class. It also provides helper methods to create classNames to be used in the template and use those names to locate the nodes created. It also provides the means to hook into the events generated both by the UI and the component itself and associate each with a method. It does all these things, while taking care to respect the inheritance chain straight up to Widget and any level of classes you may define.

It does not provide for absolutely all possibilities, but covers a good range of them. Nevertheless, it does not preclude you from adding extra functionality. You might rarely have to write a bindUI or syncUI method if you use the glue provided by MakeNode, but you may do so, since MakeNode does not use them.

As a bonus to those who had the patience to read this far, I have also modified Anthony Pipkin’s Button set of gallery components:

The API docs can be found here.

Share and extend: Bookmark with del.icio.us | digg it! | reddit!

YUI and Loader changes for 3.4.0

July 1, 2011 at 6:34 am by Dav Glass | In Development, Performance | 10 Comments

In 3.4.0 we started the process of shifting some of Loader’s logic around, to not only make it more performant, but to make it more robust and easier to use in other places (like on the server). We will be rolling out more changes in future revisions, but I wanted to take some time and explain what was changed, why it was changed and how it may impact developers. For the majority of use-cases, developers will notice nothing different, except that things are a little faster and their requirement downloads are a little smaller.

Seed File

The first thing I want to address is the YUI seed file. In previous versions of YUI, our seed file was very tiny and did not contain Loader or any of its meta-data. We’ve found that in the 90% use-case this was not as performant as we had hoped. The normal user includes the seed file then requests their modules, which in turn means that the seed needs to first fetch Loader, then calculate all of its dependencies, then fetch them all. We now feel that this extra http request is the wrong thing to do, so the new standard seed file contains Loader and its meta-data. Yes, this will make the initial request a little larger, but it will make the loading of modules that much faster since all of its meta-data requirements are now already on the page.

If you wish to use it the old way, you can just include the yui-base seed file instead. It contains everything that is needed to make YUI run in stand-alone mode plus it contains the ability to fetch Loader on demand. If you require even finer-grained dependencies, we have created a yui-core seed file that is exactly what the old yui-base seed was.

    /build/yui/yui-min.js //YUI Seed + Loader
    /build/yui-base/yui-base-min.js //Old YUI Seed with Loader fetch support
    /build/yui-core/yui-core-min.js //Old yui-base without Loader fetch support

It should be noted that these URLs are different than the previous URLs. Anyone that was using the yui/yui-base.js files need to repoint them to yui-core/yui-core.js. If you want the older way of loading the seed and fetching Loader, you would use the yui-base/yui-base.js seed file.

The other reasoning for this change is our roadmap for making YUI run in as many places as possible. The old seed file plus Loader in a single combo server request is all well and good if you have a combo server available in your application. But what about on the server? Or in an offline app on a mobile device? These places need to minimize file access while still getting the information they need.

Rollups

The next thing that we changed was removing rollups from the system and defaulting allowRollup to false in the Loader config. What does this mean to you? Well, hopefully nothing at all. Before I explain the impact of the change, let me explain the reasoning behind it. The primary reason is, again, performance, along with payload delivery. Take this example:

     Module A: requires event-a, event-b
     Module B: requires event-c, event-d

When you request both, the rollup logic prior to 3.4.0 used to determine that you should get the event rollup. Which actually meant that you were getting:

     event.a, event.b, event.c, event.d, event.e, event.f, event.g, event.h

You ended up with more on your page than you actually needed. By turning off the rollup support, YUI will now ask for only what you actually requested and nothing more. In most cases, you will not notice this. Module developers, may run into a situation where things that worked in the past may not work now. The reason for this is that they actually worked by accident before. Let me use a real world example: Dial.

In 3.3.0, Dial required this:

    requires: [
        'widget',
        'dd-drag',
        'substitute',
        'event-mouseenter', 
        'transition',
        'intl'
    ]

For the most part, Dial worked in 3.4.0, however keyboard support did not work. After doing some simple investigating, it turned out that the rollup support was actually requesting the entire Event rollup (which includes event-move and event-key). Without the rollup logic pulling in all of event, 3.4.0 Dial no longer had all of its requirements. Making Dial’s requirements more specific and defining all of its actual dependencies properly makes it work as expected.

    requires: [
        'widget',
        'dd-drag',
        'substitute',
        'event-mouseenter',
        'event-move',
        'event-key',
        'transition',
        'intl'
    ]

For module developers, it is a best practice to make sure that your module requires exactly what it needs to function. Do not assume that an upstream module requirement is there. It’s always better to make sure that you ask for what you need.

This also means that module requirements are more well defined. For example, datatype-date has Intl support built in. In previous versions you would access the Intl like this:

    Y.Intl.getAvailableLangs('datatype-date');

But since this module doesn’t actually have a language (the datatype-date-format module does), this will fail. It needs to be more specific and actually ask for languages for the correct module:

    Y.Intl.getAvailableLangs('datatype-date-format');

Build File Explosion and Submodule Removal

After making this change, the next change we made was exploding the build directory and removing submodules from the core system. Submodule logic was not removed, only our meta-data structure was changed. This will provide backward compatibility for current installations.

Submodules in the core system caused a couple of issues that we needed to resolve. The first reason was performance. Each time Loader needed to calculate dependencies, it needed to walk the submodule/plugin structure of each module. Doing this thousands of times was hurting our performance during the Loader calculate routine. By removing support for submodules in the core system we saved tens of thousands of function calls and iterations.

Loader was changed so that if a use property in a module’s meta-data defined more modules, it will use those modules instead of trying to load the original module. So, if you requested “dd” Loader would inspect “dd“‘s meta-data and see a use property that looks something like this:

    "dd-ddm-base,dd-ddm,dd-ddm-drop,dd-drag,dd-proxy,dd-constrain,dd-drop,dd-scroll,dd-drop-plugin"

In the core YUI seed file, we are also including what we are calling virtual rollups or aliases. These module definitions are exactly the same as the meta-data in Loader. This way you can include all the files exported from our Dependency Configurator and still use these rollups without having Loader present on the page. In future releases, we will be refining this approach even more.

After making this change, we then preceeded to explode our build files. In previous releases, the submodules determined the modules file path. For example:

    "dd": {
        "submodules": {
            "dd-drag": 
            // Module data
        }
    }

In 3.3.0 when you built “dd”, the file structure looked something like this:

    /build/dd/dd-drag.js
    /build/dd/dd-ddm.js
    /build/dd/dd-drop.js

With the build system exploded in 3.4.0, “dd”‘s build files now look like this:

    /build/dd-drag/dd-drag.js
    /build/dd-ddm/dd-ddm.js
    /build/dd-drop/dd-drop.js

This allowed us to remove the “path” property from all of our module meta-data as well, saving file size and reducing the logic required to assemble the modules url paths.

If you are including a pre-configured combo URL, you must recalculate your URL when you upgrade.

The downside to this change is that if you are including a combo URL of modules to “prep” your page you can not just change the version number and upgrade. You will need to revisit the Dependency Configurator and generate a new URL with new module structure.

The Future

I will be continuing to refine, refactor and maximize every aspect of our Loader and Seed strategy. These first steps were needed to aid in future changes that need to be made for not only our client-side strategy but also our server, command-line and mobile device strategies as well.

Share and extend: Bookmark with del.icio.us | digg it! | reddit!

Hosted by Yahoo!

Copyright © 2006-2012 Yahoo! Inc. All rights reserved. Privacy Policy - Terms of Service

Powered by WordPress on Yahoo! Web Hosting.