Source: layout/base.js

/**
 *  @namespace ludo.layout
 */
/**
 * Base class for ludoJS layouts
 *
 * For tutorial on layouts, see <a href="http://www.ludojs.com/learn/layout.html">ludojs.com/learn/layout.html</a>
 *
 * @namespace ludo.layout
 * @class ludo.layout.Base
 * @property {object} viewport
 * @property {Number} viewport.width - Inner width of View's body
 * @property {Number} viewport.height - Inner height of View's body
 * @fires ludo.layout.Base#rendered Fired after all children has been rendered and resized. Arguments: 1) the layout, 2) Parent view
 * @fires ludo.layout.Base#addChild Fired after adding new child to parent view. Arguments: 1) Layout, 2) parent view, 3) child
 * @fires ludo.layout.Base#addChildRuntime Fired after adding new child to parent during runtime, i.e. after first rendering with
 * code <code>view.addChild()</code>. Arguments: 1) Layout, 2) parent view, 3) child
 *
 */
ludo.layout.Base = new Class({
    Extends: Events,
    view: null,
    tabStrip: null,
    resizables: [],
    benchmarkTime: false,
    dependency: {},
    viewport: undefined,
    resized: false,

    hasWrapWidth: undefined,
    hasWrapHeight: undefined,

    initialize: function (view) {
        this.id = String.uniqueID();
        this.view = view;
        this.viewport = {
            top: parseInt(this.view.getBody().css('padding-top')),
            left: parseInt(this.view.getBody().css('padding-left')),
            width: 0, height: 0,
            bottom: 0, right: 0
        };


        if (view.getBody())this.onCreate();

        this.hasWrapWidth = !view.layout.weight && view.layout.width == 'wrap';
        this.hasWrapHeight = !view.layout.weight && view.layout.height == 'wrap';


    },

    prepareForChildrenOnCreate: function () {

    },

    onCreate: function () {


        if (this.view.layout.collapseBar) {
            this.addCollapseBars();
        }
        if (this.view.layout.listeners != undefined) {
            this.addEvents(this.view.layout.listeners);
        }

        this.fireEvent('create', [this, this.view]);
    },
    /**f
     * Method executed when adding new child view to a layout
     * @function addChild
     * @param {ludo.View} child
     * @param {ludo.View} insertAt
     * @optional
     * @param {String} pos
     * @optional
     * @memberof ludo.layout.Base.prototype
     */
    addChild: function (child, insertAt, pos) {


        child = this.getValidChild(child);
        child = this.getNewComponent(child);
        var parentEl = this.getParentForNewChild(child);
        parentEl = $(parentEl);
        if (insertAt) {
            var children = [];
            for (var i = 0; i < this.view.children.length; i++) {
                if (pos == 'after') {
                    children.push(this.view.children[i]);
                    parentEl.append(this.view.children[i].getEl());
                }
                if (this.view.children[i].getId() == insertAt.getId()) {
                    children.push(child);
                    parentEl.append(child.getEl());
                }
                if (pos == 'before') {
                    children.push(this.view.children[i]);
                    parentEl.append(this.view.children[i].getEl());
                }
            }
            this.view.children = children;
        } else {
            this.view.children.push(child);
            var el = child.getEl();
            parentEl.append(el);
        }

        this.onNewChild(child);


        this.addChildEvents(child);

        this.fireEvent('addChild', [this, this.view, child]);

        if (this.firstResized) {
            this.fireEvent('addChildRuntime', [this, this.view, child])
        }
        return child;
    },
    /**
     * Return parent DOM element for new child
     * @function getParentForNewChild
     * @protected
     * @memberof ludo.layout.Base.prototype
     */
    getParentForNewChild: function () {
        return $(this.view.els.body);
    },

    layoutProperties: ['collapsed'],

    getValidChild: function (child) {
        return child;
    },

    /**
     * Implementation in sub classes
     * @function onNewChild
     * @private
     * @memberof ludo.layout.Base.prototype
     */
    onNewChild: function (child) {
        var keys = this.layoutProperties;
        for (var i = 0; i < keys.length; i++) {
            if (child.layout[keys[i]] === undefined && child[keys[i]] !== undefined) {
                child.layout[keys[i]] = child[keys[i]];
            }
        }
    },

    addChildEvents: function () {

    },
    firstResized: false,


    resizeChildren: function () {
        if (this.benchmarkTime) {
            var start = new Date().getTime();
        }
        if (this.view.isHidden()) {
            return;
        }
        if (this.idLastDynamic === undefined) {
            this.setIdOfLastChildWithDynamicWeight();
        }

        this.storeViewPortSize();

        if (!this.firstResized) {
            this.beforeFirstResize();

        }

        this.resize();


        if (!this.firstResized) {

            this.firstResized = true;

            if (this.hasWrapHeight) {
                this.view.layout.height = this.viewport.height = this.getWrappedHeight();
            }
            if (this.hasWrapWidth) {
                this.view.layout.width = this.viewport.width = this.getWrappedWidth();
            }

            if (this.hasWrapHeight || this.hasWrapWidth) {
                this.view.resize({
                    height: this.view.layout.height,
                    width: this.view.layout.width
                });
                this.storeViewPortSize();
                this.hasWrapWidth = false;
                this.hasWrapHeight = false;
            }

            this.fireEvent('rendered', [this, this.view]);
            this.afterRendered();
        }


        if (this.benchmarkTime) {
            ludo.util.log("Time for resize(" + this.view.layout.type + "): " + (new Date().getTime() - start));
        }


    },

    afterRendered: function () {

    },

    hasBeenRendered: function () {
        return this.firstResized;
    },

    beforeFirstResize: function () {

    },

    getWrappedHeight: function () {
        return this.view.getEl().outerHeight(true) - this.view.getBody().height();
    },

    getWrappedWidth: function () {
        return 0;
    },


    storeViewPortSize: function () {
        this.viewport.absWidth = this.getAvailWidth();
        this.viewport.absHeight = this.getAvailHeight();
        this.viewport.width = this.getAvailWidth();
        this.viewport.height = this.getAvailHeight();
    },

    previousContentWidth: undefined,

    idLastDynamic: undefined,

    setIdOfLastChildWithDynamicWeight: function () {
        for (var i = this.view.children.length - 1; i >= 0; i--) {
            if (this.hasLayoutWeight(this.view.children[i])) {
                this.idLastDynamic = this.view.children[i].id;
                return;
            }
        }
        this.idLastDynamic = 'NA';
    },

    hasLayoutWeight: function (child) {
        return child.layout !== undefined && child.layout.weight !== undefined;
    },

    getNewComponent: function (config) {
        config.renderTo = this.view.getBody();
        config.type = config.type || this.view.cType;
        config.parentComponent = this.view;
        return ludo.factory.create(config);
    },

    isLastSibling: function (child) {
        var children = this.view.initialItemsObject;
        if (children.length) {
            return children[children.length - 1].id == child.id;
        } else {
            return this.view.children[this.view.children.length - 1].id == child.id;
        }
    },

    prepareView: function () {

    },

    resize: function () {
        var config = {};
        config.width = this.view.getBody().width();
        if (config.width < 0) {
            config.width = undefined;
        }
        for (var i = 0; i < this.view.children.length; i++) {
            this.view.children[i].resize(config);
        }
    },

    getAvailWidth: function () {
        return this.view.getBody().width();
    },

    getAvailHeight: function () {
        return this.view.getBody().height();
    },

    addCollapseBars: function () {
        var pos = this.view.layout.collapseBar;
        if (!ludo.util.isArray(pos))pos = [pos];
        for (var i = 0; i < pos.length; i++) {
            this.addChild(this.getCollapseBar(pos[i]));
        }
    },

    collapseBars: {},
    getCollapseBar: function (position) {
        position = position || 'left';
        if (this.collapseBars[position] === undefined) {
            var bar = this.collapseBars[position] = new ludo.layout.CollapseBar({
                position: position,
                parentComponent: this.view,
                parentLayout: this.view.layout,
                listeners: {
                    'show': this.toggleCollapseBar.bind(this),
                    'hide': this.toggleCollapseBar.bind(this)
                }
            });
            this.updateViewport(bar.getChangedViewport());
        }
        return this.collapseBars[position];
    },

    toggleCollapseBar: function (bar) {
        this.updateViewport(bar.getChangedViewport());
        this.resize();
    },
    /**
     * Update viewport properties, coordinates of DHTML Container for child views, i.e. body of parent view
     * @function updateViewport
     * @param {Object} c
     * @memberof ludo.layout.Base.prototype
     */
    updateViewport: function (c) {

        if (c)this.viewport[c.key] = c.value;
    },

    createRenderer: function () {
        if (this.renderer === undefined) {
            this.renderer = this.dependency['renderer'] = new ludo.layout.Renderer({
                view: this.view
            });
        }
        return this.renderer;
    },

    getRenderer: function () {
        return this.renderer ? this.renderer : this.createRenderer();
    },

    /**
     * Executed when a child is hidden. It set's the internal layout properties width and height to 0(zero)
     * @function hideChild
     * @param {ludo.View} child
     * @private
     * @memberof ludo.layout.Base.prototype
     */
    hideChild: function (child) {
        this.setTemporarySize(child, {
            width: 0, height: 0
        });
    },


    /**
     * Executed when a child is minimized. It set's temporary width or properties
     * @function minimize
     * @param {ludo.View} child
     * @param {Object} newSize
     * @protected
     * @memberof ludo.layout.Base.prototype
     */
    minimize: function (child, newSize) {
        this.setTemporarySize(child, newSize);
        this.resize();
    },

    /**
     * Store temporary size when a child is minimized or hidden
     * @function setTemporarySize
     * @param {ludo.View} child
     * @param {Object} newSize
     * @protected
     * @memberof ludo.layout.Base.prototype
     */
    setTemporarySize: function (child, newSize) {
        if (newSize.width !== undefined) {
            child.layout.cached_width = child.layout.width;
            child.layout.width = newSize.width;
        } else {
            child.layout.cached_height = child.layout.height;
            child.layout.height = newSize.height;
        }
    },
    /*
     * Clear temporary width or height values. This method is executed when a child
     * is shown or maximized
     * @function clearTemporaryValues
     * @param {ludo.View} child
     * @protected
     * @memberof ludo.layout.Base.prototype
     */
    clearTemporaryValues: function (child) {
        if (child) {
        }
        if (child.layout.cached_width !== undefined)child.layout.width = child.layout.cached_width;
        if (child.layout.cached_height !== undefined)child.layout.height = child.layout.cached_height;
        child.layout.cached_width = undefined;
        child.layout.cached_height = undefined;
        this.resize();
    },

    getWidthOf: function (child) {
        return child.layout.width;
    },

    getHeightOf: function (child, size) {
        var h = child.wrappedHeight != undefined ? child.wrappedHeight(size) : undefined;
        if (h != undefined)return h;
        if (child.layout.height == 'wrap') {
            child.layout.height = child.getEl().outerHeight(true);
        }
        return isNaN(child.layout.height) ? child.getEl().outerHeight(true) : child.layout.height;
    }
});