Yahoo! UI Library

gallery-inspector  0.1.0

Yahoo! UI Library > gallery-inspector > inspector.js (source view)
Search:
 
Filters
/**
 * Real-time attribute inspector widget.
 *
 * @module gallery-inspector
 * @class Inspector
 * @extends Widget
 * @uses WidgetStack
 * @constructor
 * @param {Object} config Configuration object.
 */

var Lang       = Y.Lang,
    Node       = Y.Node,
    Widget     = Y.Widget,
    YArray     = Y.Array,
    YObject    = Y.Object,
    
    // Lots of shorthands.
    BOUNDING_BOX  = 'boundingBox',
    CHECKED       = 'checked',
    COLLAPSED     = 'collapsed',
    CONTENT_BOX   = 'contentBox',
    DISPLAY       = 'display',
    EMPTY_STRING  = '',
    EXCLUDE       = 'exclude',
    FILTER        = 'filter',
    HEIGHT        = 'height',
    INCLUDE       = 'include',
    HOST          = 'host',
    HOST_ATTRS    = 'hostAttrs',
    NAME          = 'name',
    NONE          = 'none',
    OFFSET_HEIGHT = 'offsetHeight',
    PAUSED        = 'paused',
    STRINGS       = 'strings',
    TEXT_CONTENT  = 'textContent',
    VALUE         = 'value',
    
    TYPE_ARRAY     = 'array',
    TYPE_BOOLEAN   = 'boolean',
    TYPE_DATE      = 'date',
    TYPE_ERROR     = 'error',
    TYPE_FUNCTION  = 'function',
    TYPE_NULL      = 'null',
    TYPE_NUMBER    = 'number',
    TYPE_OBJECT    = 'object',
    TYPE_REGEXP    = 'regexp',
    TYPE_STRING    = 'string',
    TYPE_UNDEFINED = 'undefined',
    
    TYPE_DEFAULT = 'default',
    TYPE_YNODE   = 'node',
    
    _CLASS_COLLAPSED     = '_CLASS_COLLAPSED',
    _CLASS_EVEN          = '_CLASS_EVEN',
    _CLASS_FILTER        = '_CLASS_FILTER',
    _CLASS_NAME_CELL     = '_CLASS_NAME_CELL',
    _CLASS_ODD           = '_CLASS_ODD',
    _CLASS_VALUE         = '_CLASS_VALUE',
    _CLASS_VALUE_CELL    = '_CLASS_VALUE_CELL',
    _SELECTOR_COLLAPSE   = '_SELECTOR_COLLAPSE',
    _SELECTOR_FILTER     = '_SELECTOR_FILTER',
    _SELECTOR_NAME_CELL  = '_SELECTOR_NAME_CELL',
    _SELECTOR_PAUSE      = '_SELECTOR_PAUSE',
    _SELECTOR_VALUE_CELL = '_SELECTOR_VALUE_CELL',
    
    // For stacking multiple Inspector instances upon intitial render.
    INSTANCE_COUNT = 0,
    
    UI_SRC = Widget.UI_SRC,
    
    substitute = Y.substitute,
    
Inspector = Y.Base.create('inspector', Widget, [Y.WidgetStack], {
    
    // -- Public Lifecycle Methods ---------------------------------------------
    
    initializer: function (config) {
        INSTANCE_COUNT += 1;
        
        this._events     = [];
        this._hostEvents = [];
        this._attrNodes  = {};
        
        this[_CLASS_COLLAPSED]     = this.getClassName(COLLAPSED);
        this[_CLASS_EVEN]          = this.getClassName('even');
        this[_CLASS_FILTER]        = this.getClassName(FILTER);
        this[_CLASS_NAME_CELL]     = this.getClassName('name-cell');
        this[_CLASS_ODD]           = this.getClassName('odd');
        this[_CLASS_VALUE]         = this.getClassName(VALUE);
        this[_CLASS_VALUE_CELL]    = this.getClassName('value-cell');
        this[_SELECTOR_COLLAPSE]   = '.' + this.getClassName('collapse');
        this[_SELECTOR_FILTER]     = '.' + this[_CLASS_FILTER];
        this[_SELECTOR_NAME_CELL]  = '.' + this[_CLASS_NAME_CELL];
        this[_SELECTOR_PAUSE]      = '.' + this.getClassName('pause');
        this[_SELECTOR_VALUE_CELL] = '.' + this[_CLASS_VALUE_CELL];
    },
    
    destructor: function () {
        INSTANCE_COUNT -= 1;
        
        this._purgeEvents(this._events.concat(this._hostEvents));
        
        if (this._dd) {
            this._dd.destroy();
        }
    },
    
    renderUI: function () {
        this._renderHead();
        this._renderBody();
        this._renderFoot();
    },
    
    bindUI: function () {
        var contentBox = this.get(CONTENT_BOX),
            filterNode = contentBox.one(this[_SELECTOR_FILTER]);
        
        this._events.concat([
            this.get(HOST).after('destroyedChange',
                    this._afterHostDestroyedChange, this),
            
            contentBox.one(this[_SELECTOR_COLLAPSE]).on('click',
                    this._onCollapseClick, this),
            contentBox.one(this[_SELECTOR_PAUSE]).on('click',
                    this._onPauseClick, this),
            
            filterNode.on('valueChange', this._onFilter, this),
            filterNode.on('key', this._onFilterEscapeKey, 'up:27', this),
            
            this.after({
                collapsedChange: this._afterCollapsedChange,
                draggableChange: this._afterDraggableChange,
                hostAttrsChange: this._afterHostAttrsChange,
                excludeChange  : this._afterExcludeChange,
                filterChange   : this._afterFilterChange,
                includeChange  : this._afterIncludeChange,
                pausedChange   : this._afterPausedChange
            })
        ]);
    },
    
    syncUI: function () {
        this._uiSetCollapsed(this.get(COLLAPSED));
        this._uiSetDraggable(this.get('draggable'));
        this._uiSetFilter(this.get(FILTER));
        this._uiSetPaused(this.get(PAUSED));
        
        this._uiSetPosition();
        
        this._syncAttrs();
    },
    
    // -- Protected Prototype Methods ------------------------------------------
    
    /**
     * Sets up event handlers for host attribute value change events.
     *
     * @method _bindHostValueChangeEvents
     * @protected
     */
    _bindHostValueChangeEvents: function () {
        var attrs  = this.get(HOST_ATTRS),
            host   = this.get(HOST),
            events = [];
        
        YArray.each(attrs, function (name) {
            events.push(host.after(name + 'Change',
                    Y.bind(this._afterHostAttrValueChange, this, name)));
        }, this);
        
        this._hostEvents = events;
    },
    
    /**
     * Formats a value for display based on its type.
     *
     * @method _formatValue
     * @param {Any} value The value to format.
     * @protected
     */
    _formatValue: function (value) {
        var escapeHtml = Y.Escape.html,
            type       = Lang.type(value),
            subs;
        
        switch (type) {
        case TYPE_OBJECT:
            if (value instanceof Node) {
                type  = TYPE_YNODE;
                value = escapeHtml(value.toString().split(' ')[0]);
            }
            else if (value instanceof Boolean) {
                type  = TYPE_BOOLEAN;
                value = value.toString();
            }
            else if (value instanceof Number) {
                type  = TYPE_NUMBER;
                value = value.toString();
            }
            else if (value instanceof String) {
                type  = TYPE_STRING;
                value = '"' + escapeHtml(value.toString()) + '"';
            }
            else {
                // TODO: Support more objects
                value = '[object]';
            }
            break;
            
        case TYPE_ARRAY:
            // TODO: Support arrays
            value = '[array]';
            break;
            
        case TYPE_FUNCTION:
            // TODO: Support functions
            value = '[function]';
            break;
            
        case TYPE_BOOLEAN:
            value = value.toString();
            break;
            
        case TYPE_STRING:
            value = '"' + escapeHtml(value.toString()) + '"';
            break;
            
        case TYPE_NUMBER:
            value = escapeHtml(value.toString());
            break;
            
        case TYPE_NULL:
        case TYPE_UNDEFINED:
            value = type;
            break;
            
        case TYPE_ERROR:
        case TYPE_REGEXP:
            value = escapeHtml(value.toString());
            break;
        
        case TYPE_DATE:
            type  = TYPE_DATE;
            value = escapeHtml(value.toString());
            break;
            
        default:
            type  = TYPE_DEFAULT;
            value = escapeHtml(value.toString());
        }
        
        subs = {
            inspector_value_class: this[_CLASS_VALUE],
            inspector_type_class : this.getClassName('type-' + type),
            str_value         : value
        };
        
        return substitute(Inspector.VALUE_TEMPLATE, subs);
        
        // These old ideas might be useful for better array/object support.
        
        // if (Lang.isArray(value)) {
        //     YArray.each(value, function (v, i) {
        //         var tmp = this._formatValue(v, depth);
        //         
        //         if (tmp) {
        //             formattedValue += '<li><b>' + escape(i.toString()) + '</b> ' + tmp + '</li>';
        //         }
        //     }, this);
        // }
        // else if (Lang.isObject(value)) {
        //     YObject.each(value, function (v, k) {
        //         var tmp = this._formatValue(v, depth);
        //         
        //         if (tmp) {
        //             formattedValue += '<li><b>' + escape(k) + '</b> ' + tmp + '</li>';
        //         }
        //     }, this);
        // }
    },
    
    /**
     * Detaches all event handles in the given array.
     *
     * @method _purgeEvents
     * @param {Array} events Array of event handles.
     * @protected
     */
    _purgeEvents: function (events) {
        while (events && events.length) {
            events.pop().detach();
        }
    },
    
    /**
     * Renders a single row (name column only) for the given attribute name.
     *
     * @method _renderAttr
     * @param {String} name Attribute name.
     * @param {Number} i Index.
     * @protected
     */
    _renderAttr: function(name, i) {
        var guid        = Y.guid(),
            parityClass = i % 2 === 0 ? this[_CLASS_EVEN] : this[_CLASS_ODD],
            subs        = {
                inspector_row_parity_class: parityClass,
                inspector_name_cell_class : this[_CLASS_NAME_CELL],
                inspector_value_cell_class: this[_CLASS_VALUE_CELL],
                str_name: name,
                id_guid : guid
            },
            node  = Node.create(substitute(Inspector.ATTR_TEMPLATE, subs)),
            tbody = this._tbody;
            
        tbody.append(node);
        
        this._attrNodes[name] = tbody.one('#' + guid);
    },
    
    /**
     * Renders all rows (name column only) for all attributes.
     *
     * @method _renderAttrs
     * @protected
     */
    _renderAttrs: function () {
        var attrs = this.get(HOST_ATTRS).sort();
        
        this._tbody.empty(true);
        
        YArray.each(attrs, this._renderAttr, this);
    },
    
    /**
     * Renders the body section of the UI. Intended for use only during the
     * render lifecycle.
     *
     * @method _renderBody
     * @protected
     */
    _renderBody: function () {
        var contentBox = this.get(CONTENT_BOX),
            strings    = this.get(STRINGS),
            subs       = {
                str_name          : strings.name,
                str_value         : strings.value,
                inspector_bd_class: this.getClassName('bd')
            },
            body = Node.create(substitute(Inspector.BODY_TEMPLATE, subs));
        
        this._body  = body;
        this._tbody = body.one('tbody');
        
        contentBox.append(body);
    },
    
    /**
     * Renders the footer section of the UI. Intended for use only during the
     * render lifecycle.
     *
     * @method _renderFoot
     * @protected
     */
    _renderFoot: function () {
        var contentBox = this.get(CONTENT_BOX),
            strings    = this.get(STRINGS),
            subs       = {
                str_filter                 : strings.filter_attrs,
                str_pause                  : strings.pause,
                inspector_checkbox_class   : this.getClassName('checkbox'),
                inspector_controls_class   : this.getClassName('controls'),
                inspector_ft_class         : this.getClassName('ft'),
                inspector_pause_class      : this.getClassName('pause'),
                inspector_pause_label_class: this.getClassName('pause-label'),
                inspector_searchbox_class  : this[_CLASS_FILTER]
            };
        
        this._foot = Node.create(substitute(Inspector.FOOTER_TEMPLATE, subs));
        
        contentBox.append(this._foot);
    },
    
    /**
     * Renders the header section of the UI. Intended for use only during the
     * render lifecycle.
     *
     * @method _renderHead
     * @protected
     */
    _renderHead: function () {
        var contentBox = this.get(CONTENT_BOX),
            strings    = this.get(STRINGS),
            subs       = {
                str_collapse            : strings.collapse,
                str_name                : this.get(NAME),
                str_title               : strings.title,
                inspector_button_class  : this.getClassName('button'),
                inspector_collapse_class: this.getClassName('collapse'),
                inspector_hd_class      : this.getClassName('hd'),
                inspector_name_class    : this.getClassName(NAME),
                inspector_title_class   : this.getClassName('title')
            };

        this._head = Node.create(substitute(Inspector.HEADER_TEMPLATE, subs));

        contentBox.append(this._head);
    },
    
    /**
     * Syncs the host's attributes with the Inspector instance and applies any
     * <code>include</code> or <code>exclude</code> filters.
     *
     * @method _syncAttrs
     * @protected
     */
    _syncAttrs: function () {
        var host    = this.get(HOST),
            attrs   = host.getAttrs(),
            exclude = this.get(EXCLUDE),
            include = this.get(INCLUDE);
        
        YObject.each(attrs, function (value, name) {
            if (include.length && include.indexOf(name) === -1) {
                delete attrs[name];
            }
            else if (exclude.length && exclude.indexOf(name) !== -1) {
                delete attrs[name];
            }
        });
        
        this._set(HOST_ATTRS, YObject.keys(attrs));
    },
    
    // -- Protected UI Methods -------------------------------------------------
    
    /**
     * Filters visible attributes by the given filter value.
     *
     * @method _uiFilterAttrs
     * @param {String} value Filter value.
     * @protected
     */
    _uiFilterAttrs: function (value) {
        var selector = this[_SELECTOR_NAME_CELL],
            hide     = [],
            show     = [],
            cell;
            
        YObject.each(this._attrNodes, function (node) {
            if (value) {
                cell = node.one(selector);
                
                if (cell.get(TEXT_CONTENT).indexOf(value) !== -1) {
                    show.push(node);
                }
                else {
                    hide.push(node);
                }
            }
            else {
                show.push(node);
            }
        });
        
        Y.all(hide).setStyle(DISPLAY, NONE);
        Y.all(show).setStyle(DISPLAY, EMPTY_STRING);
    },
    
    /**
     * Sets the host destroyed state in the UI.
     *
     * @method _uiSetHostDestroyed
     * @protected
     */
    _uiSetHostDestroyed: function () {
        var node = this._head.one('.' + this.getClassName(NAME));
        
        if (node) {
            node.addClass(this.getClassName('destroyed'));
        }
    },
    
    /**
     * Sets the value for the given attribute name.
     *
     * @method _uiSetAttrValue
     * @param {String} name Attribute name.
     * @param {Any} value Attribute value.
     * @protected
     */
    _uiSetAttrValue: function(name, value) {
        var node = this._attrNodes[name],
            cell = node.one(this[_SELECTOR_VALUE_CELL]);
        
        cell.setContent(this._formatValue(value));
    },
    
    /**
     * Sets the value for all attributes.
     *
     * @method _uiSetAttrValues
     * @protected
     */
    _uiSetAttrValues: function () {
        var host  = this.get(HOST),
            attrs = this.get(HOST_ATTRS);
        
        YArray.each(attrs, function (name) {
            this._uiSetAttrValue(name, host.get(name));
        }, this);
    },
    
    /**
     * Sets the height of the UI. Sets the body height to the specified height
     * minus the combined heights of the header and footer. Overrides
     * <code>Widget.prototype._uiSetHeight</code>.
     *
     * @method _uiSetHeight
     * @param {Number} height Height.
     * @protected
     */
    _uiSetHeight: function (height) {
        Inspector.superclass._uiSetHeight.apply(this, arguments);

        if (this._head && this._foot) {
            height = this.get(BOUNDING_BOX).get(OFFSET_HEIGHT) -
                    this._head.get(OFFSET_HEIGHT) -
                    this._foot.get(OFFSET_HEIGHT);
                    
            this._body.setStyle(HEIGHT, height - 2 + 'px');
        }
    },
    
    /**
     * Sets the UI collapsed state.
     *
     * @method _uiSetCollapsed
     * @param {Boolean} collapsed Whether the UI should be collapsed.
     * @protected
     */
    _uiSetCollapsed : function (collapsed) {
        var boundingBox = this.get(BOUNDING_BOX),
            button      = this._head.one(this[_SELECTOR_COLLAPSE]),
            buttonText, height, method;
            
        if (collapsed) {
            buttonText = this.get(STRINGS + '.expand');
            height     = this._head.get(OFFSET_HEIGHT);
            method     = 'addClass';
        }
        else {
            buttonText = this.get(STRINGS + '.collapse');
            height     = this.get(HEIGHT);
            method     = 'removeClass';
        }
        
        boundingBox[method](this[_CLASS_COLLAPSED]);
        button.setContent(buttonText);
        
        this._uiSetHeight(height);
    },
    
    /**
     * Sets the UI draggable state.
     *
     * @method _uiSetDraggable
     * @param {Boolean} draggable Whether the UI should be draggable.
     * @protected
     */
    _uiSetDraggable: function (draggable) {
        if (this._dd) {
            this._dd.set('lock', !draggable);
        }
        else if (draggable) {
            this._dd = new Y.DD.Drag({
                node   : this.get(BOUNDING_BOX),
                handles: ['.' + this.getClassName('hd')]
            }).plug(Y.Plugin.DDConstrained);
        }
    },
    
    /**
     * Sets the UI filter input witht the given filter value.
     *
     * @method _uiSetFilter
     * @param {String} value Filter value.
     * @protected
     */
    _uiSetFilter: function (value) {
        var node = this._foot.one(this[_SELECTOR_FILTER]);
        
        if (node) {
            node.set(VALUE, value);
        }
    },
    
    /**
     * Sets the UI paused state.
     *
     * @method _uiSetPaused
     * @param {Boolean} paused Whether the UI should be paused.
     * @protected
     */
    _uiSetPaused : function (paused) {
        var node = this._foot.one(this[_SELECTOR_PAUSE]);

        if (node) {
            node.set(CHECKED, paused);
        }
    },
    
    /**
     * Sets the UI position and zIndex, adjusting for the presence of other
     * Inspector instances, such that they appear to stack. Intended to be
     * called during the syncUI lifecycle event.
     *
     * @method _uiSetPosition
     * @protected
     */
    _uiSetPosition: function () {
        var boundingBox = this.get(BOUNDING_BOX),
            count       = INSTANCE_COUNT - 1,
            offset      = this._head.get(OFFSET_HEIGHT);
            
        boundingBox.setStyle('top', (count * offset) + 'px');
        
        this.set('zIndex', this.get('zIndex') + count);
    },
    
    /**
     * Mini lifecycle for updating all attributes. Purges existing host events,
     * re-renders all attributes, then binds new host events.
     *
     * @method _uiSyncAttrs
     * @protected
     */
    _uiSyncAttrs: function () {
        this._purgeEvents(this._hostEvents);
        
        this._renderAttrs();
        
        this._uiSetAttrValues();
        this._uiFilterAttrs(this.get(FILTER));
        
        this._bindHostValueChangeEvents();
    },
    
    // -- Protected Host Event Handlers ----------------------------------------
    
    /**
     * Handles <code>[attribute]Change</code> events on the host.
     *
     * @method _afterHostAttrValueChange
     * @param {String} name Attribute name.
     * @param {EventFacade} e
     * @protected
     */
    _afterHostAttrValueChange: function (name, e) {
        if (!this.get(PAUSED)) {
            this._uiSetAttrValue(name, e.newVal);
        }
    },
    
    /**
     * Handles <code>destroyedChange</code> events on the host.
     *
     * @method _afterHostDestroyedChange
     * @param {EventFacade} e
     * @protected
     */
    _afterHostDestroyedChange: function (e) {
        if (e.newVal) {
            this._uiSetHostDestroyed();
        }
    },
    
    // -- Protected Event Handlers ---------------------------------------------
    
    /**
     * Handles <code>collaspedChange</code> events.
     *
     * @method _afterCollapsedChange
     * @param {EventFacade} e
     * @protected
     */
    _afterCollapsedChange: function (e) {
        this._uiSetCollapsed(e.newVal);
    },
    
    /**
     * Handles <code>draggableChange</code> events.
     *
     * @method _afterDraggableChange
     * @param {EventFacade} e
     * @protected
     */
    _afterDraggableChange: function (e) {
        this._uiSetDraggable(e.newVal);
    },
    
    /**
     * Handles <code>excludeChange</code> events.
     *
     * @method _afterExcludeChange
     * @param {EventFacade} e
     * @protected
     */
    _afterExcludeChange: function (e) {
        this._syncAttrs();
    },
    
    /**
     * Handles <code>filterChange</code> events.
     *
     * @method _afterFilterChange
     * @param {EventFacade} e
     * @protected
     */
    _afterFilterChange: function (e) {
        var filter = e.newVal;
        
        if (e.src !== UI_SRC) {
            this._uiSetFilter(filter);
        }
        
        this._uiFilterAttrs(filter);
    },
    
    /**
     * Handles <code>hostAttrsChange</code> events.
     *
     * @method _afterHostAttrsChange
     * @param {EventFacade} e
     * @protected
     */
    _afterHostAttrsChange: function (e) {
        this._uiSyncAttrs();
    },
    
    /**
     * Handles <code>includeChange</code> events.
     *
     * @method _afterIncludeChange
     * @param {EventFacade} e
     * @protected
     */
    _afterIncludeChange: function (e) {
        this._syncAttrs();
    },
    
    /**
     * Handles <code>pausedChange</code> events.
     *
     * @method _afterPausedChange
     * @param {EventFacade} e
     * @protected
     */
    _afterPausedChange : function (e) {
        var paused = e.newVal;

        if (e.src !== UI_SRC) {
            this._uiSetPaused(paused);
        }
        
        if (!paused) {
            this._syncAttrs();
        }
    },
    
    /**
     * Handles <code>click</code> events on the collapse button.
     *
     * @method _onCollapseClick
     * @param {EventFacade} e
     * @protected
     */
    _onCollapseClick: function (e) {
        this.set(COLLAPSED, !this.get(COLLAPSED));
    },
    
    /**
     * Handles <code>click</code> events on the pause checkbox.
     *
     * @method _onPauseClick
     * @param {EventFacade} e
     * @protected
     */
    _onPauseClick: function (e) {
        this.set(PAUSED, e.target.get(CHECKED), {src: UI_SRC});
    },
    
    /**
     * Handles <code>valueChange</code> events on the filter input.
     *
     * @method _onFilter
     * @param {EventFacade} e
     * @protected
     */
    _onFilter: function (e) {
        this.set(FILTER, e.newVal);
    },
    
    /**
     * Handles <code>esc</code> <code>keyUp</code> events on the filter input.
     *
     * @method _onFilterEscapeKey
     * @param {EventFacade} e
     * @protected
     */
    _onFilterEscapeKey: function (e) {
        this.set(FILTER, null);
    }
},
{
    ATTRS: {
        /**
         * If <code>true</code>, Inspector will initially render collapsed.
         *
         * @attribute collapsed
         * @type Boolean
         * @default false
         */
        collapsed: {
            value    : false,
            validator: Lang.isBoolean
        },
        
        /**
         * If <code>true</code> Inspector will be draggable, with the header
         * node as the drag handle.
         *
         * @attribute draggable
         * @type Boolean
         * @default true
         */
        draggable: {
            value    : true,
            validator: Lang.isBoolean
        },
        
        /**
         * Array of attribute names to exclude from being displayed.
         *
         * @attribute exclude
         * @type Array
         */
        exclude: {
            value    : [],
            validator: Lang.isArray
        },
        
        /**
         * Filter text value.
         *
         * @attribute filter
         * @type String
         */
        filter: {
            value: ''
        },
        
        // The "height" attribute is documented in Widget.
        height: {
            value: '300px'
        },
        
        /**
         * The object to inspect.
         *
         * @attribute host
         * @type Object
         * @writeonce
         */
        host: {
            value    : null,
            writeOnce: 'initOnly',
            validator: function (v) {
                return v instanceof Y.Base;
            }
        },
        
        /**
         * Name/value pairs of the host object's attributes.
         *
         * @attribute hostAttrs
         * @type Object
         * @readonly
         */
        hostAttrs: {
            value   : {},
            readOnly: true
        },
        
        /**
         * Array of attribute names to include. Opposite of, and overrides
         * <code>exclude</code>.
         *
         * @attribute include
         * @type Array
         */
        include: {
            value    : [],
            validator: Lang.isArray
        },
        
        /**
         * Display name of the Inspector instance. If not specified, the host
         * object's <code>name</code> property will be used if present.
         *
         * @attribute name
         * @type String
         * @writeonce
         */
        name: {
            value    : null,
            writeOnce: 'initOnly',
            getter   : function (value) {
                var host, name;
                
                if (value) {
                    name = value;
                }
                else {
                    host = this.get(HOST);
                    name = host.name ? host.name : '[No name]';
                }
                
                return name;
            }
        },
        
        /**
         * If <code>true</code>, Inspector will intitally be paused.
         *
         * @attribute paused
         * @type Boolean
         * @default false
         */
        paused: {
            value: false,
            validator : Lang.isBoolean
        },
        
        /**
         * Translatable strings used by Inspector.
         *
         * @attribute strings
         * @type Object
         */
        strings: {
            valueFn: function () {
                return Y.Intl.get('gallery-inspector');
            }
        },
        
        // The "width" attribute is documented in Widget.
        width: {
            value: '275px'
        }
        
        // The "zIndex" attribute is documented in WidgetStack.
    },
    
    CSS_PREFIX: Y.ClassNameManager.getClassName('inspector'),
    
    BODY_TEMPLATE:
        '<div class="{inspector_bd_class}">' +
            '<table>' +
                '<thead>' +
                    '<tr>' +
                        '<th scope="col">{str_name}</th>' +
                        '<th scope="col">{str_value}</th>' +
                    '</tr>' +
                '</thead>' +
                '<tbody></tbody>' +
            '</table>' +
        '</div>',
    
    FOOTER_TEMPLATE:
        '<div class="{inspector_ft_class}">' +
            '<div class="{inspector_controls_class}">' +
                '<label class="{inspector_pause_label_class}">' +
                    '<input type="checkbox" class="{inspector_checkbox_class} ' +
                        '{inspector_pause_class}" value="1">{str_pause}' +
                '</label>' +
                '<input type="search" class="{inspector_searchbox_class}"' +
                    'placeholder="{str_filter}"/>' +
            '</div>'+
        '</div>',
    
    HEADER_TEMPLATE:
        '<div class="{inspector_hd_class}">' +
            '<h4 class="{inspector_title_class}">{str_title}: ' +
                '<span class="{inspector_name_class}">{str_name}</span></h4>' +
            '<button type="button" class="' +
               '{inspector_button_class} {inspector_collapse_class}">{str_collapse}' +
           '</button>' +
        '</div>',
    
    ATTR_TEMPLATE:
        '<tr id="{id_guid}" class="{inspector_row_parity_class}">' +
            '<th class="{inspector_name_cell_class}" scope="row">{str_name}</th>' +
            '<td class="{inspector_value_cell_class}"></td>' +
        '</tr>',
            
    VALUE_TEMPLATE:
        '<span class="{inspector_value_class} {inspector_type_class}">' +
            '{str_value}' +
        '</span>'
});

Y.Inspector = Inspector;

Copyright © 2011 Yahoo! Inc. All rights reserved.