2017年4月29日 星期六

(js)_.debounce_b

////////////////////////////////////////////////////////////////////////////////
/**
 * _.debounce_b
 *
 * 1.0.1
 *
 * 2017-4-14
 *
 * 減輕高頻執行事件的執行頻率
 *
 * for backbone.js event
 *
 */
////////////////////////////////////////////////////////////////////////////////
!(function (root) {
    // 被引用了嗎

    if (typeof _ === 'function') {
        _.mixin({
            debounce_b: debounce_
        });
    } else if (typeof module === 'object' && typeof module.exports === 'object') {
        module.exports = debounce_;
    } else {
        return;
    }
    /* ====================================================================== */
    /**
     * @param {int} wait: 要超過多少時間間隔才執行
     * @param {function} job: 要執行的事
     *
     */
    function debounce_(callback, wait) {
        var d = new Debounce({
            callback: callback,
            waitTime: wait
        });
        return d.proxy_fn();
    };
    ////////////////////////////////////////////////////////////////////////////
    // core
    function Debounce(option) {
        var self = this;

        this.waitTime = 5;
        this._waitTime;
        /* ---------------------------------- */
        this._callback;
        this.timeHandle;
        this.dataList = [];
        this.checkTime;
        /* ================================================================== */
        // 建構式
        (function () {
            (option !== Object(option)) && (option = {});

            (typeof option.waitTime === 'number') && (this.waitTime = option.waitTime);
            (typeof option.callback === 'function') && (this._callback = option.callback);

            this.check();
        }).call(this);
    }
    ////////////////////////////////////////////////////////////////////////////
    (function () {
        /**
         * 暴露在外的函式
         * 外部呼叫此
         */
        this.proxy_fn = function () {
            return function (data) {
                this._check(data);
            }.bind(this);
        };
        /* ================================================================== */
        this._check = function (data) {
            // 記錄每次呼叫傳來的資料
            this.dataList.push(data);

            // 更新時間
            this.checkTime = (new Date()).getTime();

            this._bindTimeCheck();
        };
        /* ================================================================== */
        this._bindTimeCheck = function () {
            if (!this.timeHandle) {
                console.log('no lock but will lock');
                this._waitTime = this._waitTime || this.waitTime;

                this.timeHandle = setTimeout(function () {
                    console.log('open lock');
                    this.timeHandle = undefined;
                    this._timeEnd();
                }.bind(this), this._waitTime);
            } else {
                console.log('is lock');

            }
        };
        /* ================================================================== */
        this._timeEnd = function () {
            var now = (new Date()).getTime();
            var diff = now - this.checkTime;

            console.log('timeCheck: 離上一次激發時間: ', diff);

            if (diff < this.waitTime) {

                // 未過時間檢測間隔
                this._waitTime = this.waitTime - diff;

                console.log('will lock agin: 上鎖時間: ', this._waitTime);

                this._bindTimeCheck();
            } else {
                // 已過時間檢測間隔

                console.log('finish');

                this._waitTime = undefined;

                // 執行任務
                this._doJob();
            }
        };
        /* ================================================================== */
        this._doJob = function () {
            var self = this;

            // 執行任務
            this._callback(this.dataList);

        };
        /* ================================================================== */
        this.check = function () {
            var error_array = [];

            if (!this._callback) {
                error_array.push('no setting function');
            }

            if (error_array.length > 0) {
                throw new Error(error_array.join(' | '));
            }
        };
    }).call(Debounce.prototype);

})(this || {});

2017年4月28日 星期五

(php)CodeIgniter_pageLink

<?php

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */

/**
 * Description of MyPagination_type1
 *
 * @author xman
 *
 *
 */
class MyPagination_type1 {

    // put your code here
    protected $config = array();
    protected $CI;

    /* ---------------------------------- */
    protected $totalBlocks; // 共有幾個區塊
    protected $totalPages; // 共有幾頁
    protected $block; // 從0開始
    /* ---------------------------------- */
    protected $page;
    /* ---------------------------------- */
    protected $linkData = array();
    protected $otherLinkData = array();

    /* ====================================================================== */

    public function __construct() {
        $this->CI = &get_instance();

        if ($this->CI->config->item('enable_query_strings')) {
            throw new Exception('cant use in (enable_query_strings)');
        }
        /* ---------------------------------- */
        $this->config['page'] = NULL; // 每個命令相對的資料區塊
        $this->config['start'] = NULL; // 每個命令相對開始的資料
        /* ---------------------------------- */
        $this->config['totalRows'] = NULL;
        $this->config['perPageRows'] = NULL;
        $this->config['perPageLinks'] = 10;
        /* ---------------------------------- */
        $this->config['path'] = NULL; // url.path
        $this->config['query'] = NULL; // url.query
        $this->config['segments'] = array(); // url.query_array
        /* ---------------------------------- */
        $this->config['segment'] = NULL; // 要修改的參數位置
        /* ---------------------------------- */
        $this->config['template'] = NULL; // 樣版
        $this->config['rwd'] = false; // 為了RWD
    }

    /* ====================================================================== */

    public function initialize($config) {
        $this->_setConfig($config);

        $this->_check_1();

        $this->_check_2();
     
        echo '<pre>';
        var_export($this->config);
        echo '</pre>';
    }

    /* ====================================================================== */

    public function create_links() {
     
    }

    /* ====================================================================== */

    public function create_LinkData() {
        $this->_model_1();

        if ($this->config['rwd']) {
            // for RWD 只印出(上一頁)(下一頁)          
         
            $this->_model_2b();
        } else {
            // for 一般正常頁面
            $this->_model_2a();
        }
        /* ---------------------------------- */
        $this->_model_3();

        $linkData = $this->linkData;
        /* ---------------------------------- */

        if ($this->config['rwd']) {
            list($f2, $f1, $b1, $b2) = $this->otherLinkData;
            array_unshift($linkData, $f1);
            array_unshift($linkData, $f2);
            array_push($linkData, $b1);
            array_push($linkData, $b2);
        } else {
            list($f1, $f2, $b1, $b2) = $this->otherLinkData;
            array_unshift($linkData, $f2);
            array_unshift($linkData, $f1);
            array_push($linkData, $b1);
            array_push($linkData, $b2);
        }
        /* ---------------------------------- */
        $start = ($this->page - 1) * $this->config['perPageRows'];
     
        $result = array(
            'page' => $this->page,
            'start' => $start,
            'linkData' => $linkData,
            'totalPages' => $this->totalPages,
            'totalRows' => $this->config['totalRows']
        );
     
        return $result;
    }

    /* ====================================================================== */

    protected function _model_1() {
        $config = $this->config;

        // totalPage
        $this->totalPages = floor(($config['totalRows'] - 1) / $config['perPageRows']) + 1;
        $this->totalPages = ($this->totalPages < 1 ? 1 : $this->totalPages);

        /* ---------------------------------- */
        // check page
        $this->page = ($this->page < 1 ? 1 : $this->page);
        $this->page = ($this->page > $this->totalPages ? $this->totalPages : $this->page);

        /* ---------------------------------- */
        // totalBlock
        $this->totalBlocks = floor(($this->totalPages - 1) / $config['perPageLinks']) + 1;
        $this->totalBlocks = ($this->totalBlocks < 1 ? 1 : $this->totalBlocks);

        /* ---------------------------------- */

        // block
        $this->block = floor(($this->page - 1) / $this->config['perPageLinks']);
    }

    /* ====================================================================== */

    /**
     * 一般狀況,印出(上一頁)(下一頁)
     */
    protected function _model_2a() {
        $config = $this->config;


        for ($i = 0; $i < 4; $i++) {
            $this->otherLinkData[] = array(
                'content' => '',
                'path' => $config['path'],
                'query' => '',
                'url' => 'javascript:;',
                'show' => TRUE,
                'selected' => FALSE,
                'number' => FALSE,
                'name' => ''
            );
        }
        /* ================================== */
        // (<<)
        if ($this->block < 1) {
            // 不顯示
            $this->otherLinkData[0]['path'] = '';
            $this->otherLinkData[0]['show'] = FALSE;
        } else {
            $page = $this->page - $config['perPageLinks'];
            $page = $this->_checkPage($page);
            $query = $this->_changeURLSegment($page);


            $this->otherLinkData[0]['query'] = $query;
            $this->otherLinkData[0]['url'] = sprintf('%s/%s', $this->otherLinkData[0]['path'], $query);
        }

        $this->otherLinkData[0]['content'] = '<<';
        $this->otherLinkData[0]['name'] = 'b2';
        /* ================================== */
        // (<)
        if ($this->page < 2) {
            // 不顯示
            $this->otherLinkData[1]['path'] = '';
            $this->otherLinkData[1]['show'] = FALSE;
        } else {
            $page = $this->page - 1;
            $page = $this->_checkPage($page);
            $query = $this->_changeURLSegment($page);


            $this->otherLinkData[1]['query'] = $query;
            $this->otherLinkData[1]['url'] = sprintf('%s/%s', $this->otherLinkData[1]['path'], $query);
        }

        $this->otherLinkData[1]['content'] = '<';
        $this->otherLinkData[1]['name'] = 'b1';
        /* ================================== */
        // (>)
        if ($this->page >= $this->totalPages) {
            // 不顯示
            $this->otherLinkData[2]['path'] = '';
            $this->otherLinkData[2]['show'] = FALSE;
        } else {
            $page = $this->page + 1;
            $page = $this->_checkPage($page);
            $query = $this->_changeURLSegment($page);


            $this->otherLinkData[2]['query'] = $query;
            $this->otherLinkData[2]['url'] = sprintf('%s/%s', $this->otherLinkData[2]['path'], $query);
        }

        $this->otherLinkData[2]['content'] = '>';
        $this->otherLinkData[2]['name'] = 'f1';
        /* ================================== */
        // (>>)
        if ($this->block >= ($this->totalBlocks - 1)) {
            // 不顯示
            $this->otherLinkData[3]['path'] = '';
            $this->otherLinkData[3]['show'] = FALSE;
        } else {
            $page = $this->page + $config['perPageLinks'];
            $page = $this->_checkPage($page);
            $query = $this->_changeURLSegment($page);


            $this->otherLinkData[3]['query'] = $query;
            $this->otherLinkData[3]['url'] = sprintf('%s/%s', $this->otherLinkData[3]['path'], $query);
        }

        $this->otherLinkData[3]['content'] = '>>';
        $this->otherLinkData[3]['name'] = 'f2';
    }

    /* ====================================================================== */

    /**
     * (for RWD)印出(上一頁)(下一頁)
     */
    protected function _model_2b() {
        $config = $this->config;


        for ($i = 0; $i < 4; $i++) {
            $this->otherLinkData[] = array(
                'content' => '',
                'path' => $config['path'],
                'query' => '',
                'url' => 'javascript:;',
                'show' => TRUE,
                'selected' => FALSE,
                'number' => FALSE,
                'name' => ''
            );
        }
        /* ================================== */
        // (<<)回到第一頁
        if ($this->page < 2) {
            // 不顯示
            $this->otherLinkData[0]['path'] = '';
            $this->otherLinkData[0]['show'] = FALSE;
        } else {
            $page = 1;
            $page = $this->_checkPage($page);
            $query = $this->_changeURLSegment($page);


            $this->otherLinkData[0]['query'] = $query;
            $this->otherLinkData[0]['url'] = sprintf('%s/%s', $this->otherLinkData[0]['path'], $query);
        }

        $this->otherLinkData[0]['content'] = '<<';
        $this->otherLinkData[0]['name'] = 'b2';
        /* ================================== */
     
     
        // (<)
        if ($this->page < 2) {
            // 不顯示
            $this->otherLinkData[1]['path'] = '';
            $this->otherLinkData[1]['show'] = FALSE;
        } else {
            $page = $this->page - 1;
            $page = $this->_checkPage($page);
            $query = $this->_changeURLSegment($page);


            $this->otherLinkData[1]['query'] = $query;
            $this->otherLinkData[1]['url'] = sprintf('%s/%s', $this->otherLinkData[1]['path'], $query);
        }

        $this->otherLinkData[1]['content'] = '<';
        $this->otherLinkData[1]['name'] = 'b1';
        /* ================================== */
        // (>)
        if ($this->page >= $this->totalPages) {
            // 不顯示
            $this->otherLinkData[2]['path'] = '';
            $this->otherLinkData[2]['show'] = FALSE;
        } else {
            $page = $this->page + 1;
            $page = $this->_checkPage($page);
            $query = $this->_changeURLSegment($page);


            $this->otherLinkData[2]['query'] = $query;
            $this->otherLinkData[2]['url'] = sprintf('%s/%s', $this->otherLinkData[2]['path'], $query);
        }

        $this->otherLinkData[2]['content'] = '>';
        $this->otherLinkData[2]['name'] = 'f1';
        /* ================================== */
        // (>>)最後一頁
        if ($this->page >= $this->totalPages) {
            // 不顯示
            $this->otherLinkData[3]['path'] = '';
            $this->otherLinkData[3]['show'] = FALSE;
        } else {
            $page = $this->totalPages;
            $page = $this->_checkPage($page);
            $query = $this->_changeURLSegment($page);


            $this->otherLinkData[3]['query'] = $query;
            $this->otherLinkData[3]['url'] = sprintf('%s/%s', $this->otherLinkData[3]['path'], $query);
        }

        $this->otherLinkData[3]['content'] = '>>';
        $this->otherLinkData[3]['name'] = 'f2';
    }

    /* ====================================================================== */

    /**
     * 創建(pageLink)
     */
    protected function _model_3() {
        $config = $this->config;

        for ($i = 0; $i < $config['perPageLinks']; $i++) {


            $data = array(
                'content' => '',
                'path' => $config['path'],
                'query' => '',
                'url' => 'javascript:;',
                'show' => TRUE,
                'selected' => FALSE,
                'number' => TRUE,
                'class' => 'number',
                'name' => ''
            );


            /* ================================== */
            $page = $this->block * $config['perPageLinks'] + $i + 1;

            if ($page <= $this->totalPages) {

                if ($page == $this->page) {
                    // 當前頁
                    $data['selected'] = TRUE;
                } else {
                    $data['query'] = $this->_changeURLSegment($this->_checkPage($page));
                    $data['url'] = sprintf('%s/%s', $data['path'], $data['query']);
                }

                $data['content'] = $page;
            } else {

                // 過頭了
                $data['show'] = FALSE;
            }
            $this->linkData[$i] = $data;
        }
    }

    /* ====================================================================== */

    protected function _checkPage($page) {

        $page = ($page < 1 ? 1 : $page);
        $page = ($page > $this->totalPages ? $this->totalPages : $page);

        return $page;
    }

    /* ====================================================================== */

    /**
     * 更改(page)的值
     *
     * @param type $page
     * @return type
     */
    function _changeURLSegment($page) {
        $config = $this->config;
        $value;

        $position = $config['segment'];

        /* ---------------------------------- */
        if (isset($config['page'])) {
            // 用(page)切換分頁
            $value = $page;
        } else {
            // 用(start)切換分頁
            $value = ($page - 1) * $config['perPageRows'];
        }

        /* ---------------------------------- */
        if ($position == -1 || count($config['segments']) == 0) {
            // 加在最後面
            $config['segments'][] = $value;
        } else {
            $config['segments'][$position] = $value;
        }

        return implode('/', $config['segments']);
    }

    /* ====================================================================== */

    protected function _setConfig($config) {
        foreach ($config as $k => $v) {

            if (array_key_exists($k, $this->config)) {
                if (isset($v)) {
                    $this->config[$k] = $v;
                }
            }
        }
    }

    /* ====================================================================== */

    /**
     * 做檢查
     */
    protected function _check_1() {
        $config = &$this->config;

        $error_array = array();

        // path
        if (empty($config['path'])) {
            $error_array[] = 'need set base_url';
        }

        // totoalRows
        if (!isset($config['totalRows'])) {
            $error_array[] = 'need set totalRows';
        }

        // perPageRows
        if (empty($config['perPageRows'])) {
            $error_array[] = 'need set perPageRows or != 0';
        }

        // perPageLinks
        if (empty($config['perPageLinks'])) {
            $error_array[] = 'need set perPageLinks or != 0';
        }

        // segment
        if (!isset($config['segment'])) {
            $error_array[] = 'need set segment';
        }
        /* ---------------------------------- */

        if (!isset($config['page']) && !isset($config['start'])) {
            $error_array[] = 'need set page or start';
        }

        if (count($error_array) > 0) {
            $result = implode($error_array, ' | ');
            throw new Exception($result);
        }
    }

    /* ====================================================================== */

    protected function _check_2() {
        $config = &$this->config;

        if (!isset($config['page'])) {
            $this->page = floor($config['start'] / $config['perPageRows']) + 1;
        } else {
            $this->page = $config['page'];
        }


        if (!empty($config['query']) && count($config['segments']) == 0) {
            $config['segments'] = explode('/', $config['query']);
        }

        if (empty($config['query']) && count($config['segments']) > 0) {
            $config['query'] = implode('/', $config['segments']);
        }
     
        if($config['rwd']){
            // 若是為了RWD
            $config['perPageLinks'] = 1;
        }
    }

}

?>




/////////////////////////////////////////////////////////////////////////////////////////
<?php
if (! defined('BASEPATH'))
    exit('No direct script access allowed');

class Test_2 extends CI_Controller
{
 
    public function __construct () {
        parent::__construct();
    }
 
    /* ====================================================================== */
    public function index () {
        printf('<p>%s</p>', 'test_2--index');
        var_export(func_get_args());
    }
 
    /* ====================================================================== */
    public function classTest_1 () {
        $this->load->library('MyURL');
     
        $urlData = $this->myurl->parse_url(func_get_args());
     
        $data = array(
        'urlData' => $urlData
        );
     
        // printf('<a href="%s/%s">link</a>', $urlData['path'],
        // $urlData['query']);
        $this->load->view('test_2/classTest_1', $data);
    }
 
    /* ====================================================================== */
    public function classTest_2 ($arg1,$page=1) {
        $this->load->library('parseurl');
        $this->load->library('MyPagination_type1', NULL, 'pagination');
     
        $urlData = $this->parseurl->parse_url(func_get_args());
     
        printf('<p>%s => %s</p>', $urlData['path'], $urlData['query']);
        /* ---------------------------------- */
     
        $config = array();
        $config['page'] = $page;
        $config['totalRows'] = 263;
        $config['perPageRows'] = 10;
        $config['perPageLinks'] = 5;
     
        $config['path'] = $urlData['path'];
        $config['query'] = $urlData['query'];
        $config['segment'] = 1;
        $config['rwd'] = true;
     
        $this->pagination->initialize($config);
        $pagination_data = $this->pagination->create_LinkData();
     
        $data = array(
            'pagination' => $pagination_data
        );
     
        $this->load->view('test_2/pagination_t1', $data);
    }
}
?>

2017年4月27日 星期四

打開C:\Users\{yourname}\AppData\Roaming\NetBeans\7.3.1\config\Editors\Preferences,用文本編輯器打開     org-netbeans-modules-editor-settings-CustomPreferences.xml

<entry javaType="java.lang.Float" name="line-height-correction" xml:space="preserve">
        <value><![CDATA[1.20]]></value>
    </entry>

http://netbeansthemes.com/

Atom-php

autocpmplete-php>>

設定 php.exe的路徑

php-cs-fixer>>

http://cs.sensiolabs.org/

把檔案跟 php.exe 放一起

2017年4月25日 星期二

一次引用多個 javascript 檔

<?php
header( 'Content‐type: application/javascript');

require_once './jquery-3.2.0.min.js';
require_once './underscore-1.8.3-min.js';
require_once './backbone-1.3.3_t3.js';
?>

2017年4月14日 星期五

visual studio code

// 將您的按鍵組合放入此檔案中以覆寫預設值
[{
        "key": "ctrl+\\",
        "command": "editor.action.triggerSuggest",
        "when": "editorTextFocus"
    },
    {
        "key": "ctrl+shift+\\",
        "command": "editor.action.triggerParameterHints",
        "when": "editorHasSignatureHelpProvider && editorTextFocus"
    },
    {
        "key": "alt+/",
        "command": "workbench.action.splitEditor"
    },
    {
        "key": "ctrl+`",
        "command": "workbench.action.toggleSidebarVisibility"
    }
]
///////////////////////////////////////////////////////////////////////////////////
Atom JavaScript Snippet

Auto Close Tag

Copy Relative Path

EaseServer

Guides

HTML Class Suggestions

IntelliSense for CSS class names

JavaScript Snippet Pack

JavaScript Patterns Snippets

Path Intellisense

PHP IntelliSense

QuickSnippet

php-symbols

PHP, Perl, ASM, Bash Code Format

HTML Boilerplate

HTML Snippets

/////////////////////////////////////////////////////////////////////////////////// Capo-lifgt

(js)underscore_throttle



_.throttle = function(func, wait, options) {
        var timeout; // timeHandle

        var context, args, result;
        var previous = 0;

        if (!options) {
            options = {};
        }
        /* ---------------------------------- */
        var later = function() {
            previous = options.leading === false ? 0 : _.now();

            timeout = null; // timeHandle 開鎖

            result = func.apply(context, args);

            if (!timeout) {
                context = args = null;
            }

        };
        /* ---------------------------------- */
        var throttled = function() {
            console.log('-----------------------');
            var now = _.now();

            // previous 要等開鎖後才會更新
            if (!previous && options.leading === false) {
                previous = now;
            }

            // 還有多久到
            var remaining = wait - (now - previous);

            context = this;
            args = arguments;
            console.log('remaining: ', remaining);

            if (remaining <= 0 || remaining > wait) {
                // 避免計時器超時未開鎖
                // 主要是這個 remaining <= 0
                // 若過了 wait.time 鎖尚未打開
                // 強制開鎖

                console.log('a');

                if (timeout) {
                    // 清除鎖
                    clearTimeout(timeout);
                    timeout = null;
                }
                previous = now;
                result = func.apply(context, args);
                if (!timeout) {
                    context = args = null;
                }
            } else if (!timeout && options.trailing !== false) {
                // 開鎖了
                console.log('b: ', remaining);

                // 設置一個時間,並上鎖
                timeout = setTimeout(later, remaining);
            }

            console.log('c: ', remaining);
            return result;
        };
        /* ---------------------------------- */
        throttled.cancel = function() {
            console.log('cancal');
            clearTimeout(timeout);
            previous = 0;
            timeout = context = args = null;
        };
        /* ---------------------------------- */
        return throttled;
    };

2017年4月11日 星期二

(js)transitionJS....簡易版,除去 step, timer

////////////////////////////////////////////////////////////////////////////////
//
// 處理 transition 動畫
//
// (簡化版)
// 把每次的動畫屬性都設為 all
// 就不用處理 css 與 transitionProperty 間的對應
//
// 2017-03-30
//
//
// 最大的問題是 >>
// 如何得知 transition 是否順利執行
// transitionend 能否正確啟動
//
////////////////////////////////////////////////////////////////////////////////
if (typeof Area88_ == 'undefined') {
    if (typeof window.jQuery != 'undefined') {
        Area88_ = window.jQuery;
    } else {
        Area88_ = {};
    }
}
////////////////////////////////////////////////////////////////////////////////
(function ($) {


    // class
    // 處理動畫的類別
    function SetTransition(dom) {
        this.fn = SetTransition;
        this.dom = dom;

        // 使用者對動畫的設定
        this.setting = {};

        this.next = (typeof next === 'function' ? next : undefined);

        this.timeHandle = {
            delay: undefined,
            before: undefined,
            end: undefined
        };
        // 防止 endEvent 太早激發
        // false 為解鎖
        this.timeLock = false;

        // 備份 this.setting.css
        this.cssSetting = {};
    }
    /* ====================================================================== */
    (function () {
        // this._API = function(){};

        // 只能 stop 自身下的動畫命令
        this.stop = function (jumpToEnd) {

            if (!this.dom.dataset.area88Animate) {
                alert('no animation');
                // 元素已經沒在運動
                return;
            }
            this._resetEndEvent();
            this._resetTimeHandle();
            /* ---------------------------------- */
            var style = this.dom.style;

            if (jumpToEnd) {
                // 下面順序不能變換
                style.transition = 'none';

                $(this.dom).redraw_();

            } else {
                // 從 style 取得非 transition 的屬性與質
                // var styleProperty = this._getCurrentStyleProperty();

                // 運動中的動畫屬性
                var styleProperty = this.cssSetting;
                var css = getComputedStyle(this.dom, null);

                // 將 style 換成 現在的 css 值
                for (var key in styleProperty) {
                    if (styleProperty.hasOwnProperty(key)) {
                        styleProperty[key] = css[key] || '';
                    }
                }
                $(this.dom).redraw_();

                // 最重要的步驟
                $(this.dom).css(styleProperty);
            }
            /* ---------------------------------- */

            // 呼叫結束程序,但不執行 complete
            this.setting.complete = undefined;
            this._whenTransitionEnd();
        };
        /* ------------------------------------------------------------------ */
        this.animate = function (setting, next) {
            // debugger;

            this.next = (typeof next === 'function' ? next : undefined);
            this.setting = ($.isPlainObject(setting) ? setting : {});

            // 過濾 this.setting.css 的設定
            // 不能留有 transition 相關的設定
            this._cssSetFilter();

            /* ---------------------------------- */
            if (!this._willAnimate()) {
                // 若不符合動畫條件

                console.log('cant animate');
                this._animateNext();
                return;
            }

            if(this.dom.dataset.area88Animate){
                console.dir(this.setting.css);
                console.dir(this.cssSetting);
                console.log('-----------------');
                // 若在移動中,又下命令
                // 需判斷與先前的 style 設定是否相符
                // 若完全相符,就不用往下
                if(!this._willContinueAnimate()){
                    console.log('the same before .... no new setting');
                    return;
                }
            }
            /* ================================== */
            // 以下為確定動畫

            // 複製 cssSetting
            this.cssSetting = $.extend({}, this.setting.css);

            // 前置動作
            // 清理之前檢查(transitionEnd)的計時器
            // 若不用 queue 在第一個動作尚未結束時,加入第2個動作
            // 第一個的檢查計時器的時間將不對
            this._resetTimeHandle();

            this._resetEndEvent();


            if (typeof this.setting.start === 'function') {
                this.setting.start.call(this.dom);
            }


            // dom 未隱藏,且有設置動畫時間
            // 執行運動
            // 沒有設置動畫時間,就不會有動畫

            if (this.setting.delay && !this.next) {
                // 非隊列,有設定 delay
                this.fn.delayTimeHandle = setTimeout(function () {
                    this._animate();
                }.bind(this), this.setting.delay);
            } else {
                this._animate();
            }

        };
    }).call(SetTransition.prototype);
    /* ====================================================================== */
    (function () {
        // 執行運動
        this._animate = function () {
            var self = this;
            // 符合動畫條件
            // 若有設置顯示時間
            // 若有顯示
            // 若 opacity > 0

            this._setTansition();

            this._bindEvent();
            /* ---------------------------------- */
            // 計時器

            var time_1 = this.setting.duration;
            var time_2 = this.setting.duration + 50;

            // 預防 endEvent 太早起動
            this.timeLock = true;

            this.timeHandle.before = setTimeout(function () {
                self.timeHandle.before = undefined;
                // 開鎖
                self.timeLock = false;
                // alert('unlock');
            }, time_1);

            // 預防 endEvent 過時沒啟動
            this.timeHandle.end = setTimeout(function () {
                // alert('check');
                self.timeHandle.end = undefined;

                // 觸發 endEvent
                self._trigger($.support.transitionEnd);
            }, time_2);

            /* ---------------------------------- */
            // 標注動畫中
            // 才會知道正在動畫中
            this.dom.dataset.area88Animate = (this.next ? 'queue' : 'noqueue');

            $(this.dom).redraw_();

            // 下面開始動畫
            $(this.dom).css(this.cssSetting);
        };
        /* ------------------------------------------------------------------ */
        // animate end
        this._animateNext = function () {

            this.setting = {};

            if (typeof this.next === 'function') {
                this.next();
            } else {
                return;
            }
        };
    }).call(SetTransition.prototype);
    /* ====================================================================== */
    (function () {
        // $.tsEndCheck_ 會設置計時器來檢查 transitionEnd 是否正常執行
        // 在每次執行前,要清除上次設定的計時器
        // 刪除之前(tsEndCheck_)的檢查
        this._resetTimeHandle = function () {

            clearTimeout(this.timeHandle.before);
            clearTimeout(this.timeHandle.end);
            clearTimeout(this.timeHandle.delay);

            this.timeHandle.before = undefined;
            this.timeHandle.end = undefined;
            this.timeHandle.delay = undefined;
        };
        /* ------------------------------------------------------------------ */

        // 移除之前設定的事件
        // 萬一之前的(transition)尚未完成
        // 又突然給一個命令
        // 會造成之前的一些註冊事件重複發生
        this._resetEndEvent = function () {
            // transitionEnd 會執行的任務
            $(this.dom).off($.support.transitionEnd + '.area88.complete');
        };
        /* ------------------------------------------------------------------ */
        // 設置 transition
        this._setTansition = function () {
            // debugger;
            var cssEase = $.transitionOption_.cssEase;


            if (typeof cssEase[this.setting.easing] === 'string') {
                this.setting.easing = cssEase[this.setting.easing];
            }

            var set = 'all';

            // duration
            set += ' ' + ((this.setting.duration / 1000) + 's');

            // easing
            set += ' ' + this.setting.easing;

            // delay
            set += ' 0s';

            this.dom.style[$.support.transition] = set;
        };
        /* ------------------------------------------------------------------ */
        this._whenTransitionEnd = function () {
            // 清除 endEvent check
            clearTimeout(this.timeHandle.end);
            this.timeHandle.end = undefined;

            $(this.dom).off($.support.transitionEnd + '.area88.complete');

            // 去除動畫標記
            // 表示動畫結束
            delete this.dom.dataset.area88Animate;

            // 還原原本的 transition 設置
            this._resetTransitionSetting();

            /* -----------------------------------*/
            if (typeof this.setting.complete === 'function') {
                // complete
                // alert(self.setting.complete.toString());
                this.setting.complete.call(self.dom);
            }
            /* -----------------------------------*/
            this._animateNext();
        };
        /* ------------------------------------------------------------------ */
        /**
         * 綁定 transitionEnd 事件,才能順利驅動 complete
         * @return [type] [description]
         */
        this._bindEvent = function () {
            var self = this;

            // 綁定(transitionend.area88.complete)事件
            $(this.dom).on($.support.transitionEnd + '.area88.complete', function (e) {
                // alert('fire: ' + self.fn.timeLock);
                if (self.timeLock) {
                    // 時間鎖未打開,太早起動
                    // e.stopPropagation();
                    return;
                }

                self._whenTransitionEnd();
            });
        };

        /* ------------------------------------------------------------------ */
        // 過濾 this.setting.css 的設定
        this._cssSetFilter = function () {
            // debugger;

            var css2TransitionMap = $.transitionOption_.css2TransitionMap;

            var keyList = Object.keys(this.setting.css);

            keyList.forEach(function (key) {
                // debugger;

                this.setting.css[key] = this.setting.css[key].trim();

                // 不能在css裡置動畫屬性
                if (/transition/gi.test(key)) {
                    delete this.setting.css[key];
                }
                /* ---------------------------------- */
                // 若是諸如 padding, margin 之類
                // 必須轉換,主要方便與 css 判別,與 stop
                // padding -> padding-top, padding-right.......
                // style.padding 可能在 css 找不到正確數質...無法 stop
                if (typeof css2TransitionMap[key] !== 'undefined') {
                    var tmpValue = this.setting.css[key];
                    delete this.setting.css[key];

                    css2TransitionMap[key].forEach(function (_key) {
                        this.setting.css[_key] = tmpValue;
                    });
                }
            }, this);
        };
    }).call(SetTransition.prototype);
    /* ====================================================================== */
    (function () {
        // 判斷是否合乎動畫規則
        // 若 cssSetting 的數質等於當前
        // 若隱藏起來
        this._willAnimate = function () {
            // debugger;

            var self = this,
                css = getComputedStyle(this.dom, null);

            var res = [];

            // 是否隱藏中
            if (/none/gi.test(css.display)) {
                res.push(false);
            }
            /* ---------------------------------- */
            // duration <= 0 不能動畫
            if (this.setting.duration <= 0) {
                res.push(false);
            }
            /* ---------------------------------- */
            // 設定的數質是否與當前不同 syle => css
            // 要不同才能動畫


            var judge_1 = (function (self) {
                // debugger;

                for (var key in self.setting.css) {
                    // 紀錄 cssSetting 對應當前的 css 數值

                    var styleValue = self.setting.css[key];
                    var cssValue = css.getPropertyValue(key).trim();

                    var reg = new RegExp('^' + cssValue, 'i');

                    if (reg.test(styleValue)) {
                        continue;
                    } else {
                        // 有數值不同
                        return true;
                    }
                } // for-loop

                return false;
            })(this);

            res.push(judge_1);
            /* ---------------------------------- */


            return (res.indexOf(false) >= 0 ? false : true);
        };
        /* ------------------------------------------------------------------ */
        // 若 dom 正在移動中
        // 而 style 設定與上次相同
        // 也沒有必要設定動畫
        this._willContinueAnimate = function(){
            debugger;
            var judge_2 = (function (self) {
                var settingNum_1 = Object.keys(self.cssSetting).length;
                 var settingNum_2 = Object.keys(self.setting.css).length;

                if (settingNum_1 !== settingNum_2) {
                    // 若與上次 style 設定的數量不同
                    // 會動畫
                    return true;
                } else {

                    for (var key in self.setting.css) {
                        var nowValue = self.setting.css[key];
                        var prevValue = self.cssSetting[key];

                        if (/\+=|-=/i.test(nowValue)) {
                            // style 的設定有與上次不同
                            return true;
                        } else {
                            // 用現在的 style 設定,比對上次的 style 設定
                            var reg = new RegExp(('^' + nowValue), 'i');
                            if (reg.test(prevValue)) {
                                continue;
                            } else {
                                return true;
                            }
                        }
                    }

                    return false;
                }
            })(this);

            return judge_2;
        };
        /* ------------------------------------------------------------------ */
        // 處理 trigger 功能
        this._trigger = function (evenName, data) {
            try {
                $(this.dom).trigger_(evenName, data);
            } catch (error) {
                $(this.dom).trigger(evenName, data);
            }
        };
        /* ------------------------------------------------------------------ */
        // 清除動畫設定
        this._resetTransitionSetting = function () {
            var style = this.dom.style;

            style.transitionProperty = '';
            style.transitionDuration = '';
            style.transitionDelay = '';
            style.transitionTimingFunction = '';
            style.transition = '';
        };

    }).call(SetTransition.prototype);
    ////////////////////////////////////////////////////////////////////////////
    // 處理 .transition_() 進來的參數
    // 方法的重載
    function _getArguments(cssSetting, duration, easing, delay, callback) {
        // debugger;
        var option;

        var arg = $.makeArray(arguments).filter(function (value) {
            if (value == null) {
                return false;
            }
            return true;
        });

        if (!$.isPlainObject(cssSetting)) {
            throw new TypeError('arguments[0] must be plainObject');
            return;
        }

        if (arg.length == 2 && $.isPlainObject(duration)) {
            // .transition({}, option)
            option = duration;
            duration = undefined;
        } else {
            option = undefined;

            if (typeof duration === 'function') {
                callback = duration;
                duration = undefined;
            } else if (typeof easing === 'function') {
                // .transition({}, duration, callback)
                callback = easing;
                easing = undefined;
            } else if (typeof delay === 'function') {
                // .transition({}, duration, easing, callback)
                callback = delay;
                delay = undefined;
            } else if (typeof option === 'function') {
                // .transition({}, duration, easing, delay, callback)
                option = undefined;
                callback = option;
            }
        }
        /* ================================================================== */
        var res = {
            css: cssSetting,
            queue: true,
            duration: $.fx.speeds._default,
            easing: $.transitionOption_.cssEase._default,
            delay: 0,
            start: function () { },
            complete: function () { }
        };

        if ($.isPlainObject(option)) {
            res = $.extend({}, res, option);
        } else {
            res.duration = (typeof duration === 'number' ? duration : res.duration);
            res.easing = (typeof easing === 'string' ? easing : res.easing);
            res.delay = (typeof delay === 'number' ? delay : res.delay);
            res.complete = (typeof callback === 'function' ? callback : res.complete);
        }
        /* ---------------------------------- */
        // 檢查 easing
        var easingKeyList = Object.getOwnPropertyNames($.transitionOption_.cssEase);

        if (easingKeyList.indexOf(res.easing) < 0 && !/^cubic-bezier\(.+\)&/gi.test(res.easing)) {
            throw new TypeError('easing setting no support');
        }
        /* ---------------------------------- */
        return res;
    }
    /* ====================================================================== */
    // here
    $.fn.transition_ = function (cssSetting, duration, easing, delay, option, callback) {
        var self = this;

        var setting = _getArguments(cssSetting, duration, easing, delay, option, callback);

        this.each(function (index, dom) {
            setting.dom = dom;
            $.transition_(setting);
        });

        return this;
    };
    /* ====================================================================== */
    // here
    $.transition_ = function (dom, cssSetting, duration, easing, delay, option, callback) {

        var setting;
        var _cssSetting, _queue, _duration, _easing, _delay, _complete;

        if ($.isPlainObject(dom)) {
            // 內部傳送
            setting = dom;
            dom = setting.dom;
        } else {
            setting = _getArguments(cssSetting, duration, easing, delay, option, callback);
            setting.dom = dom;
        }
        /* ---------------------------------- */
        // debugger;
        var xman;

        if (xman = $(dom).data('_setTransition_')) { } else {
            xman = new SetTransition(dom);
            $(dom).data('_setTransition_', xman);
        }

        if (setting.queue) {
            // 若要設置 queue
            if (setting.delay) {
                // 若有設置 delay
                $(dom).delay(setting.delay);
            }

            $(dom).queue(function (next) {
                xman.animate(setting, next);
            });
        } else {
            // 若不用 queue

            if (setting.delay) {
                setTimeout(function () {
                    xman.animate(setting);
                }, setting.delay);
            } else {
                xman.animate(setting);
            }
        }
    };
    ////////////////////////////////////////////////////////////////////////////
    ////////////////////////////////////////////////////////////////////////////

    /**
     * @param {string} queue: 隊列的名稱
     */
    $.fn.transition_stop_ = function (queue, clearQueue, jumpToEnd) {

        return this.each(function (i, dom) {
            $.transition_stop_(dom, queue, clearQueue, jumpToEnd);
        });
    };
    /* ---------------------------------------------------------------------- */
    /**
     * 必須清除計時器
     * 清除 transiotnEnd.area88.complete 事件
     * 清除 delay 計時器
     *
     * @param  {[type]} dom        [description]
     * @param  {[type]} queue      [queueName]
     * @param  {[type]} clearQueue [是否要清除 queue]
     * @param  {[type]} jumpToEnd  [是否直接跳到設定值]
     * @return [type]              [description]
     */
    $.transition_stop_ = function (dom, queue, clearQueue, jumpToEnd) {
        // 清空隊列


        if (typeof queue === 'string') {
            // ( [queue ] [, clearQueue ] [, jumpToEnd ] )
            clearQueue = clearQueue || false;
            jumpToEnd = jumpToEnd || false;
        } else {
            // ( [clearQueue ] [, jumpToEnd ] )
            queue = 'fx';
            jumpToEnd = clearQueue || false;
            clearQueue = queue || false;
        }
        /* ---------------------------------- */
        if (clearQueue) {
            $(dom).clearQueue(queue);
        }
        /* ---------------------------------- */
        var xman = $(dom).data('_setTransition_');
        // 暫停
        if (jumpToEnd) {
            xman && (xman.stop(true));
        } else {
            xman && (xman.stop());
        }
    }
})(Area88_);
////////////////////////////////////////////////////////////////////////////////


2017年4月5日 星期三

(js)yahoo promise

https://github.com/yahoo/ypromise



(function(global) {
    if (typeof window === 'object') {
        // browser
        if (typeof global.Promise === 'function') {
            // 若瀏覽器有內建 promise 就做罷
            return;
        }
    }
    /* ====================================================================== */

    var built = (function() {

        var status_pending = 1; // 事件仍在處理狀態
        var status_accepted = 2; // 已經 resolve 但尚未 fulfilled
        var status_fulfilled = 3; // 事件已處理完成
        var status_rejected = 4; // 事件已處理完,但拋出 reject

        /**
         A promise represents a value that may not yet be available. Promises allow
         you to chain asynchronous operations, write synchronous looking code and
         handle errors throughout the process.
       
         This constructor takes a function as a parameter where you can insert the logic
         that fulfills or rejects this promise. The fulfillment value and the rejection
         reason can be any JavaScript value. It's encouraged that rejection reasons be
         error objects
       
         <pre><code>
         var fulfilled = new Promise(function (resolve) {
         resolve('I am a fulfilled promise');
         });
       
         var rejected = new Promise(function (resolve, reject) {
         reject(new Error('I am a rejected promise'));
         });
         </code></pre>
       
         @class Promise
         @constructor
         @param {Function} fn A function where to insert the logic that resolves this
         promise. Receives `resolve` and `reject` functions as parameters.
         This function is called synchronously.
         **/
        function Promise(fn) {
            if (!(this instanceof Promise)) {
                throw new TypeError(this + 'is not a promise');
            }
            if (typeof fn !== 'function') {
                throw new TypeError('Promise resolver ' + fn + ' is not a function');
            }

            var resolver = new Resolver();

            /**
             A reference to the resolver object that handles this promise
           
             @property _resolver
             @type Object
             @private
             */
            this._resolver = resolver;

            try {
                fn(function(value) {
                    resolver.resolve(value);
                }, function(reason) {
                    resolver.reject(reason);
                });
            } catch (e) {
                resolver.reject(e);
            }
        }
        /* ================================================================== */

        (function() {
            /**
             Schedule execution of a callback to either or both of "fulfill" and
             "reject" resolutions for this promise. The callbacks are wrapped in a new
             promise and that promise is returned.  This allows operation chaining ala
             `functionA().then(functionB).then(functionC)` where `functionA` returns
             a promise, and `functionB` and `functionC` _may_ return promises.
           
             Asynchronicity of the callbacks is guaranteed.
           
             @method then
             @param {Function} [callback] function to execute if the promise
             resolves successfully
             @param {Function} [errback] function to execute if the promise
             resolves unsuccessfully
             @return {Promise} A promise wrapping the resolution of either "resolve" or
             "reject" callback
             **/
            this.then = function(callback, errback) {
                // using this.constructor allows for customized promises to be
                // returned instead of plain ones
                var resolve, reject,
                    promise = new this.constructor(function(res, rej) {
                        resolve = res;
                        reject = rej;
                    });
                /* ---------------------------------- */
                var _callback = (typeof callback === 'function' ?
                    Promise._makeCallback(promise, resolve, reject, callback) : resolve);

                var _errorback = (typeof errback === 'function' ?
                    Promise._makeCallback(promise, resolve, reject, errback) : reject);

                this._resolver._addCallbacks(_callback, _errorback);

                return promise;
            };
            /* ============================================================== */
            /*
             A shorthand for `promise.then(undefined, callback)`.
           
             Returns a new promise and the error callback gets the same treatment as in
             `then`: errors get caught and turned into rejections, and the return value
             of the callback becomes the fulfilled value of the returned promise.
           
             @method catch
             @param [Function] errback Callback to be called in case this promise is
             rejected
             @return {Promise} A new promise modified by the behavior of the error
             callback
             */
            this.catch = function(errback) {
                return this.then(undefined, errback);
            }
        }).call(Promise.prototype);
        ////////////////////////////////////////////////////////////////////////
        // 類別方法
        /**
         Wraps the callback in another function to catch exceptions and turn them
         into rejections.
       
         @method _makeCallback
         @param {Promise} promise Promise that will be affected by this callback
         @param {Function} fn Callback to wrap
         @return {Function}
         @static
         @private
         **/
        Promise._makeCallback = function(promise, resolve, reject, fn) {
            // callbacks and errbacks only get one argument
            return function(valueOrReason) {
                var result;

                // Promises model exception handling through callbacks
                // making both synchronous and asynchronous errors behave
                // the same way
                try {
                    // Use the argument coming in to the callback/errback from the
                    // resolution of the parent promise.
                    // The function must be called as a normal function, with no
                    // special value for |this|, as per Promises A+
                    // 執行 then() 註冊的函式
                    result = fn(valueOrReason);
                } catch (e) {
                    // calling return only to stop here
                    reject(e);
                    return;
                }

                if (result === promise) {
                    reject(new TypeError('Cannot resolve a promise with itself'));
                    return;
                }

                // 叫下一個 promise resolve()
                // resolve 包著下一個 promise 的 resolve()          
                resolve(result);
            };
        };
        /* ================================================================== */
        /*
         Ensures that a certain value is a promise. If it is not a promise, it wraps it
         in one.
       
         This method can be copied or inherited in subclasses. In that case it will
         check that the value passed to it is an instance of the correct class.
         This means that `PromiseSubclass.resolve()` will always return instances of
         `PromiseSubclass`.
       
         @method resolve
         @param {Any} Any object that may or may not be a promise
         @return {Promise}
         @static
         */
        Promise.resolve = function(value) {
            if (value && value.constructor === this) {
                return value;
            }
            /*jshint newcap: false */
            return new this(function(resolve) {
                /*jshint newcap: true */
                resolve(value);
            });
        };
        /* ================================================================== */
        /*
         A shorthand for creating a rejected promise.
       
         @method reject
         @param {Any} reason Reason for the rejection of this promise. Usually an Error
         Object
         @return {Promise} A rejected promise
         @static
         */
        Promise.reject = function(reason) {
            /*jshint newcap: false */
            var promise = new this(function() {});
            /*jshint newcap: true */

            // Do not go through resolver.reject() because an immediately rejected promise
            // always has no callbacks which would trigger an unnecessary warning
            promise._resolver._result = reason;
            promise._resolver._status = 'rejected';

            return promise;
        };
        /* =================================================================== */
        /*
         Returns a promise that is resolved or rejected when all values are resolved or
         any is rejected. This is useful for waiting for the resolution of multiple
         promises, such as reading multiple files in Node.js or making multiple XHR
         requests in the browser.
       
         @method all
         @param {Any[]} values An array of any kind of values, promises or not. If a value is not
         @return [Promise] A promise for an array of all the fulfillment values
         @static
         */
        Promise.all = function(values) {
            var Promise = this;
            return new Promise(function(resolve, reject) {
                if (!Array.isArray(values)) {
                    reject(new TypeError('Promise.all expects an array of values or promises'));
                    return;
                }

                var remaining = values.length,
                    i = 0,
                    length = values.length,
                    results = [];

                function oneDone(index) {
                    return function(value) {
                        results[index] = value;

                        remaining--;

                        if (!remaining) {
                            resolve(results);
                        }
                    };
                }

                if (length < 1) {
                    return resolve(results);
                }

                for (; i < length; i++) {
                    Promise.resolve(values[i]).then(oneDone(i), reject);
                }
            });
        };
        /* ================================================================== */
        /*
         Returns a promise that is resolved or rejected when any of values is either
         resolved or rejected. Can be used for providing early feedback in the UI
         while other operations are still pending.
       
         @method race
         @param {Any[]} values An array of values or promises
         @return {Promise}
         @static
         */
        Promise.race = function(values) {
            var Promise = this;
            return new Promise(function(resolve, reject) {
                if (!Array.isArray(values)) {
                    reject(new TypeError('Promise.race expects an array of values or promises'));
                    return;
                }

                // just go through the list and resolve and reject at the first change
                // This abuses the fact that calling resolve/reject multiple times
                // doesn't change the state of the returned promise
                for (var i = 0, count = values.length; i < count; i++) {
                    Promise.resolve(values[i]).then(resolve, reject);
                }
            });
        };
        /* ================================================================== */
        /**
         Forces a function to be run asynchronously, but as fast as possible. In Node.js
         this is achieved using `setImmediate` or `process.nextTick`. In YUI this is
         replaced with `Y.soon`.
       
         @method async
         @param {Function} callback The function to call asynchronously
         @static
         **/
        /* istanbul ignore next */
        Promise.async = typeof setImmediate !== 'undefined' ?
            function(fn) {
                setImmediate(fn);
            } :
            typeof process !== 'undefined' && process.nextTick ?
            process.nextTick :
            function(fn) {
                setTimeout(fn, 0);
            };

        ////////////////////////////////////////////////////////////////////////
        /**
         * 核心方法
         */

        /**
         Represents an asynchronous operation. Provides a
         standard API for subscribing to the moment that the operation completes either
         successfully (`fulfill()`) or unsuccessfully (`reject()`).
         @class Promise.Resolver
         @constructor
         **/
        function Resolver() {
            /**
             List of success callbacks
           
             @property _callbacks
             @type Array
             @private
             **/
            this._callbacks = [];

            /**
             List of failure callbacks
           
             @property _errbacks
             @type Array
             @private
             **/
            this._errbacks = [];

            /**
             The status of the operation. This property may take only one of the following
             values: 'pending', 'fulfilled' or 'rejected'.
           
             @property _status
             @type String
             @default 'pending'
             @private
             **/
            this._status = status_pending;

            /**
             This value that this promise represents.
           
             @property _result
             @type Any
             @private
             **/
            this._result = null;
        }
        /* ================================================================== */
        (function() {
            /**
             Resolves the promise, signaling successful completion of the
             represented operation. All "onFulfilled" subscriptions are executed and passed
             the value provided to this method. After calling `fulfill()`, `reject()` and
             `notify()` are disabled.
           
             @method fulfill
             @param {Any} value Value to pass along to the "onFulfilled" subscribers
             **/
            this.fulfill = function(value) {
                var status = this._status;

                if (status === status_pending || status === status_accepted) {
                    this._result = value;
                    this._status = status_fulfilled;
                }

                if (this._status === status_fulfilled) {
                    this._notify(this._callbacks, this._result);

                    // Reset the callback list so that future calls to fulfill()
                    // won't call the same callbacks again. Promises keep a list
                    // of callbacks, they're not the same as events. In practice,
                    // calls to fulfill() after the first one should not be made by
                    // the user but by then()
                    this._callbacks = [];

                    // Once a promise gets fulfilled it can't be rejected, so
                    // there is no point in keeping the list. Remove it to help
                    // garbage collection
                    this._errbacks = null;
                }
            };
            /* -------------------------------------------------------------- */

            /**
             Resolves the promise, signaling *un*successful completion of the
             represented operation. All "onRejected" subscriptions are executed with
             the value provided to this method. After calling `reject()`, `resolve()`
             and `notify()` are disabled.
           
             @method reject
             @param {Any} reason Value to pass along to the "reject" subscribers
             **/
            this.reject = function(reason) {
                var status = this._status;

                if (status === status_pending || status === status_accepted) {
                    this._result = reason;
                    this._status = status_rejected;
                }

                if (this._status === status_rejected) {
                    this._notify(this._errbacks, this._result);

                    // See fulfill()
                    this._callbacks = null;
                    this._errbacks = [];
                }
            };
            /* -------------------------------------------------------------- */

            /*
             Given a certain value A passed as a parameter, this method resolves the
             promise to the value A.
           
             If A is a promise, `resolve` will cause the resolver to adopt the state of A
             and once A is resolved, it will resolve the resolver's promise as well.
             This behavior "flattens" A by calling `then` recursively and essentially
             disallows promises-for-promises.
           
             This is the default algorithm used when using the function passed as the
             first argument to the promise initialization function. This means that
             the following code returns a promise for the value 'hello world':
           
             var promise1 = new Promise(function (resolve) {
             resolve('hello world');
             });
             var promise2 = new Promise(function (resolve) {
             resolve(promise1);
             });
             promise2.then(function (value) {
             assert(value === 'hello world'); // true
             });
           
             @method resolve
             @param [Any] value A regular JS value or a promise
             */
            this.resolve = function(value) {
                if (this._status === status_pending) {
                    this._status = status_accepted;
                    this._value = value;

                    if ((this._callbacks && this._callbacks.length) ||
                        (this._errbacks && this._errbacks.length)) {
                        this._unwrap(this._value);
                    }
                }
            };
            /* -------------------------------------------------------------- */
            /**
             If `value` is a promise or a thenable, it will be unwrapped by
             recursively calling its `then` method. If not, the resolver will be
             fulfilled with `value`.
           
             This method is called when the promise's `then` method is called and
             not in `resolve` to allow for lazy promises to be accepted and not
             resolved immediately.
           
             @method _unwrap
             @param {Any} value A promise, thenable or regular value
             @private
             **/
            this._unwrap = function(value) {
                var self = this,
                    unwrapped = false,
                    then;

                if (!value || (typeof value !== 'object' &&
                        typeof value !== 'function')) {
                    self.fulfill(value);
                    return;
                }

                try {
                    then = value.then;

                    if (typeof then === 'function') {
                        then.call(value, function(value) {
                            if (!unwrapped) {
                                unwrapped = true;
                                self._unwrap(value);
                            }
                        }, function(reason) {
                            if (!unwrapped) {
                                unwrapped = true;
                                self.reject(reason);
                            }
                        });
                    } else {
                        self.fulfill(value);
                    }
                } catch (e) {
                    if (!unwrapped) {
                        self.reject(e);
                    }
                }
            };
            /* -------------------------------------------------------------- */

            /**
             Schedule execution of a callback to either or both of "resolve" and
             "reject" resolutions of this resolver. If the resolver is not pending,
             the correct callback gets called automatically.
           
             @method _addCallbacks
             @param {Function} [callback] function to execute if the Resolver
             resolves successfully
             @param {Function} [errback] function to execute if the Resolver
             resolves unsuccessfully
             **/
            this._addCallbacks = function(callback, errback) {
                var callbackList = this._callbacks,
                    errbackList = this._errbacks;

                // Because the callback and errback are represented by a Resolver, it
                // must be fulfilled or rejected to propagate through the then() chain.
                // The same logic applies to resolve() and reject() for fulfillment.
                if (callbackList) {
                    callbackList.push(callback);
                }
                if (errbackList) {
                    errbackList.push(errback);
                }

                switch (this._status) {
                    case status_accepted:
                        this._unwrap(this._value);
                        break;
                    case status_fulfilled:
                        // 何時會進來??
                        // 迷樣的步驟
                        // 也許是應付 then()裡面發生錯誤
                        console.log('<<switch -> fulfilled>>');
                        this.fulfill(this._result);
                        break;
                    case status_rejected:
                        // 何時會進來??
                        // 迷樣的步驟
                        // 也許是應付 then()裡面發生錯誤
                        console.log('<<switch -> rejected>>');
                        this.reject(this._result);
                        break;
                }
            };
            /* -------------------------------------------------------------- */

            /**
             Executes an array of callbacks from a specified context, passing a set of
             arguments.
           
             @method _notify
             @param {Function[]} subs The array of subscriber callbacks
             @param {Any} result Value to pass the callbacks
             @protected
             **/
            this._notify = function(subs, result) {
                // Since callback lists are reset synchronously, the subs list never
                // changes after _notify() receives it. Avoid calling Y.soon() for
                // an empty list
                if (subs.length) {
                    // Calling all callbacks after Promise.async to guarantee
                    // asynchronicity. Because setTimeout can cause unnecessary
                    // delays that *can* become noticeable in some situations
                    // (especially in Node.js)
                    Promise.async(function() {
                        var i, len;

                        for (i = 0, len = subs.length; i < len; ++i) {
                            subs[i](result);
                        }
                    });
                }
            };
        }).call(Resolver.prototype);

        Promise.Resolver = Resolver;

        return Promise;

    })();
    /* ====================================================================== */

    if (typeof window === 'object') {
        window.Promise = built;
    } else if (typeof module === 'object' && typeof module.exports === 'object') {
        module.exports = built;
    }
})(this);