Source: views/lifeCycle.js

/*global define:false */
define(['jquery', 'underscore'],
    function ($, _) {
        'use strict';

        // TODO: create constants module
        var BEFORE_RENDER_DONE = 'beforeRenderDone',
            RENDER_DONE = 'renderDone',
            AFTER_RENDER_DONE = 'afterRenderDone';

        /**
         * @module views/lifeCycle
         */
        return {
            runAllMethods : runAllMethods
        };

        function runAllMethods ($deferred, $parentRenderPromise) {
            var notifyBeforeRenderDone = $deferred.notify.bind(this, BEFORE_RENDER_DONE),
                waitForParentPromiseToBeResolved = _waitForParentPromiseToBeResolved.bind(this,$parentRenderPromise),
                afterRender = _afterRender.bind(this,$deferred),
                renderAndAfterRender = _renderAndAfterRender.bind(this, $deferred, afterRender),
                rejectStart = $deferred.reject.bind(this);

            $deferred.progress(this.trigger.bind(this));

            $
                .when(_runLifeCycleMethod.call(this, this.beforeRender))
                .then(notifyBeforeRenderDone)
                .then(waitForParentPromiseToBeResolved)
                .then(
                    renderAndAfterRender,
                    rejectStart);
        }

        /**
         * Life cycle methods have an event triggered before the run.
         * If a life cycle method has one or more arguments, then the first argument passed in is its deferred.
         * The life cycle method will automatically return this deferred, otherwise it will pass through whatever
         * the method itself returns.
         *
         * @function
         * @param lifeCycleMethod
         * @returns {*}
         * @private
         */
        function _runLifeCycleMethod (lifeCycleMethod, $startDeferred) {
            var $deferred,
                args = Array.prototype.slice.call(arguments),
                returned;

            if (!lifeCycleMethod) {
                return undefined;
            }

            if (lifeCycleMethod.length) {
                $deferred = new $.Deferred();
                args.unshift($deferred);
            }

            if ($startDeferred) {
                args.push($startDeferred);
            }

            returned = lifeCycleMethod.apply(this, args);

            return lifeCycleMethod.length ? $deferred.promise() : returned;
        }

        function _waitForParentPromiseToBeResolved ($parentRenderPromise) {
            if ($parentRenderPromise) {
                return $parentRenderPromise;
            }
            return undefined;
        }

        function _renderAndAfterRender ($deferred, afterRender) {
            var rejectStart = $deferred.reject.bind(this);
            $
                .when(_runLifeCycleMethod.call(this, this.render, $deferred))
                .then($deferred.notify.bind(this,RENDER_DONE))
                .then(
                    afterRender,
                    rejectStart);
        }

        function _afterRender ($deferred) {
            var $afterRenderDeferred = _runLifeCycleMethod.call(this, this.afterRender),
                resolveStart = $deferred.resolve.bind(this),
                rejectStart = $deferred.reject.bind(this);
            $
                .when(
                    $afterRenderDeferred,
                    _startChildren.call(this, $deferred)
                )
                .then($deferred.notify.bind(this,AFTER_RENDER_DONE))
                .then(
                    resolveStart,
                    rejectStart);
        }

        function _startChildren ($parentDeferred) {
            var childPromiseArray = [],
                $afterRenderDeferred = new $.Deferred();

            $parentDeferred.progress(function (step) {
                if (step === RENDER_DONE) {
                    $afterRenderDeferred.resolve();
                }
            });

            _(this.children).each(function (child) {
                var $childDeferred = child.start($afterRenderDeferred.promise());

                $childDeferred.done(function () {
                    child.hasStarted = true;
                });

                childPromiseArray.push($childDeferred);
            });

            return $.when.apply($, childPromiseArray);
        }
    });