/**
 * @docauthor Robert Dougan <[email protected]>
 *
 * 単一のチェックボックスフィールドです。従来のチェックボックスフィールドを直接代替することができます。また、{@link Ext.form.field.Radio ラジオボタン}の親クラスとしても機能します。
 *
 * #ラベリング
 *
 * {@link Ext.form.Labelable 標準的なフィールドのラベルオプション}に加えて、チェックボックスには、チェックボックスの後でただちに表示されるオプションの{@link #boxLabel}が備わります。関連するチェックボックスをグループ化する便利な方法については、{@link Ext.form.CheckboxGroup}を参照してください。
 *
 * #値
 *
 * チェックボックスの主な値はBooleanであり、チェックボックスがチェックされているかどうかを示します。次の値の場合、チェックボックスがチェックされます。
 *
 * - `true`
 * - `'true'`
 * - `'1'`
 * - `'on'`
 *
 * 他の全ての値ではチェックが外れます。
 *
 * メインのboolean値のほかに、別途{@link #inputValue}を指定することもできます。これは、フォームの{@link Ext.form.Basic#submit 送信}時にパラメータ値として渡されます。同じ{@link #name}を持つチェックボックスが複数ある場合、この値を設定します。指定されていない場合、`on`の値が使用されます。
 *
 * #使用例
 *
 *     @example
 *     Ext.create('Ext.form.Panel', {
 *         bodyPadding: 10,
 *         width: 300,
 *         title: 'Pizza Order',
 *         items: [
 *             {
 *                 xtype: 'fieldcontainer',
 *                 fieldLabel: 'Toppings',
 *                 defaultType: 'checkboxfield',
 *                 items: [
 *                     {
 *                         boxLabel  : 'Anchovies',
 *                         name      : 'topping',
 *                         inputValue: '1',
 *                         id        : 'checkbox1'
 *                     }, {
 *                         boxLabel  : 'Artichoke Hearts',
 *                         name      : 'topping',
 *                         inputValue: '2',
 *                         checked   : true,
 *                         id        : 'checkbox2'
 *                     }, {
 *                         boxLabel  : 'Bacon',
 *                         name      : 'topping',
 *                         inputValue: '3',
 *                         id        : 'checkbox3'
 *                     }
 *                 ]
 *             }
 *         ],
 *         bbar: [
 *             {
 *                 text: 'Select Bacon',
 *                 handler: function() {
 *                     Ext.getCmp('checkbox3').setValue(true);
 *                 }
 *             },
 *             '-',
 *             {
 *                 text: 'Select All',
 *                 handler: function() {
 *                     Ext.getCmp('checkbox1').setValue(true);
 *                     Ext.getCmp('checkbox2').setValue(true);
 *                     Ext.getCmp('checkbox3').setValue(true);
 *                 }
 *             },
 *             {
 *                 text: 'Deselect All',
 *                 handler: function() {
 *                     Ext.getCmp('checkbox1').setValue(false);
 *                     Ext.getCmp('checkbox2').setValue(false);
 *                     Ext.getCmp('checkbox3').setValue(false);
 *                 }
 *             }
 *         ],
 *         renderTo: Ext.getBody()
 *     });
 */
Ext.define('Ext.form.field.Checkbox', {
    extend: 'Ext.form.field.Base',
    alias: ['widget.checkboxfield', 'widget.checkbox'],
    alternateClassName: 'Ext.form.Checkbox',
    requires: ['Ext.XTemplate', 'Ext.form.CheckboxManager' ],

    // inputEl should always retain the same size, never stretch
    stretchInputElFixed: false,

    childEls: [
        /**
         * @property {Ext.dom.Element} boxLabelEl
         * {@link #boxLabel}用に作成されたラベル要素への参照です。コンポーネントにboxLabelコンフィグが設定されていてレンダリングされている場合のみ存在します。
         */
        'boxLabelEl'
    ],

    // note: {id} here is really {inputId}, but {cmpId} is available
    fieldSubTpl: [
        '<div class="{wrapInnerCls} {noBoxLabelCls}" role="presentation">',
            '<tpl if="labelAlignedBefore">',
                '{beforeBoxLabelTpl}',
                '<label id="{cmpId}-boxLabelEl" data-ref="boxLabelEl" {boxLabelAttrTpl} class="{boxLabelCls} ',
                        '{boxLabelCls}-{ui} {boxLabelCls}-{boxLabelAlign} {childElCls}" for="{id}">',
                    '{beforeBoxLabelTextTpl}',
                    '{boxLabel}',
                    '{afterBoxLabelTextTpl}',
                '</label>',
                '{afterBoxLabelTpl}',
            '</tpl>',
            '<input type="button" id="{id}" data-ref="inputEl" role="{role}" {inputAttrTpl}',
                '<tpl if="tabIdx != null"> tabindex="{tabIdx}"</tpl>',
                '<tpl if="disabled"> disabled="disabled"</tpl>',
                '<tpl if="fieldStyle"> style="{fieldStyle}"</tpl>',
                ' class="{fieldCls} {typeCls} {typeCls}-{ui} {inputCls} {inputCls}-{ui} {childElCls} {afterLabelCls}" autocomplete="off" hidefocus="true" />',
            '<tpl if="boxLabel && !labelAlignedBefore">',
                '{beforeBoxLabelTpl}',
                '<label id="{cmpId}-boxLabelEl" data-ref="boxLabelEl" {boxLabelAttrTpl} class="{boxLabelCls} ',
                        '{boxLabelCls}-{ui} {boxLabelCls}-{boxLabelAlign} {childElCls}" for="{id}">',
                    '{beforeBoxLabelTextTpl}',
                    '{boxLabel}',
                    '{afterBoxLabelTextTpl}',
                '</label>',
                '{afterBoxLabelTpl}',
            '</tpl>',
        '</div>',
        {
            disableFormats: true,
            compiled: true
        }
    ],

    publishes: {
        checked: 1
    },

    subTplInsertions: [
        /**
         * @cfg {String/Array/Ext.XTemplate} beforeBoxLabelTpl
         * ボックスラベル要素の前のフィールドマークアップに挿入するオプションの文字列または`XTemplate`設定です。`XTemplate`を使用している場合、コンポーネントの{@link Ext.form.field.Base#getSubTplData subTplデータ}が使用されます。
         */
        'beforeBoxLabelTpl',

        /**
         * @cfg {String/Array/Ext.XTemplate} afterBoxLabelTpl
         * ボックスラベル要素の後のフィールドマークアップに挿入されるオプションの文字列または`XTemplate`設定です。`XTemplate`を使用している場合、コンポーネントの{@link Ext.form.field.Base#getSubTplData subTplデータ}が使用されます。
         */
        'afterBoxLabelTpl',

        /**
         * @cfg {String/Array/Ext.XTemplate} beforeBoxLabelTextTpl
         * ボックスラベルテキストの前のフィールドマークアップに挿入するオプションの文字列または`XTemplate`設定です。`XTemplate`を使用している場合、コンポーネントの{@link Ext.form.field.Base#getSubTplData subTplデータ}が使用されます。
         */
        'beforeBoxLabelTextTpl',

        /**
         * @cfg {String/Array/Ext.XTemplate} afterBoxLabelTextTpl
         * ボックスラベルテキストの後のフィールドマークアップに挿入するオプションの文字列または`XTemplate`設定です。`XTemplate`を使用している場合、コンポーネントの{@link Ext.form.field.Base#getSubTplData subTplデータ}が使用されます。
         */
        'afterBoxLabelTextTpl',

        /**
         * @cfg {String/Array/Ext.XTemplate} boxLabelAttrTpl
         * ボックスラベル要素内のフィールドマークアップに(属性として)挿入するオプションの文字列または`XTemplate`設定です。`XTemplate`を使用している場合、コンポーネントの{@link Ext.form.field.Base#getSubTplData subTplデータ}が使用されます。
         */
        'boxLabelAttrTpl',

        // inherited
        'inputAttrTpl'
    ],

    /*
     * @property {Boolean} isCheckbox
     * このクラスでオブジェクトをインスタンス化したチェックボックスまたはそのサブクラスとして識別するには、`true`に設定します。
     */
    isCheckbox: true,

    /**
     * @cfg {String} [focusCls='x-form-checkbox-focus']
     * チェックボックスにフォーカスが当たったときのCSSクラス。
     */
    focusCls: 'form-checkbox-focus',

    /**
     * @cfg {String} [fieldCls='x-form-field']
     * チェックボックスのデフォルトのCSSクラス。
     */
    
    // private
    fieldBodyCls: Ext.baseCSSPrefix + 'form-cb-wrap',

    /**
     * @cfg {Boolean} checked
     * true にすると初期状態でチェックボックスが初期状態でチェックされます
     */
    checked: false,

    /**
     * @cfg {String} [checkedCls='x-form-cb-checked']
     * チェックされた状態の時にコンポーネントの主要な要素に追加されるCSSクラス。ユーザー自身のクラス(checkedCls='myClass x-form-cb-checked')の追加またはデフォルトクラス全体(checkedCls='myClass')の置き換えができます。
     */
    checkedCls: Ext.baseCSSPrefix + 'form-cb-checked',

    /**
     * @cfg {String} boxLabel
     * チェックボックスに続いて表示するオプションのテキストラベルです。チェックボックスの前後どちらに表示されるかは、{@link #boxLabelAlign}コンフィグで指定します。
     */

    /**
     * @cfg {String} [boxLabelCls='x-form-cb-label']
     * {@link #boxLabel}要素に適用されるCSSクラスです。
     */
    boxLabelCls: Ext.baseCSSPrefix + 'form-cb-label',

    /**
     * @cfg {String} boxLabelAlign
     * チェックボックスの位置から見て{@link #boxLabel}が表示される位置です。設定できる値は、'before' (チェックボックスの前)と 'after' (チェックボックスの後)です。
     */
    boxLabelAlign: 'after',

    afterLabelCls: Ext.baseCSSPrefix + 'form-cb-after',

    wrapInnerCls: Ext.baseCSSPrefix + 'form-cb-wrap-inner',

    // This is to work around lack of min-width support in older IE browsers.
    // If it's determined that there is no box label, apply the following class to the
    // wrapper around the inputEl and all browsers will get width from its theme's CSS rule.
    // See EXTJSIV-10302 and EXTJSIV-10977.
    noBoxLabelCls: Ext.baseCSSPrefix + 'form-cb-wrap-inner-no-box-label',

    /**
     * @cfg {String} inputValue
     * 生成された入力エレメントのvalue属性になる値、またフォームの一部として送信されるときに、パラメータ値として使われる値です。
     */
    inputValue: 'on',

    /**
     * @cfg {String} uncheckedValue
     * 設定されている場合、チェックボックスがチェックされていなければ、これはフォームの送信中にチェックボックスの値として送信されます。デフォルトでは、これは定義されず、フォームの送信時、チェックボックスフィールドに何も送信されません(HTMLチェックボックスのデフォルトの動作)。
     */

    /**
     * @cfg {Function} handler
     * {@link #checked}値が変更される際に呼び出される関数です({@link #change イベント変更}を処理する代わりに使用できます)。
     * @cfg {Ext.form.field.Checkbox} handler.checkbox 切り替えられるチェックボックス。
     * @cfg {Boolean} handler.checked チェックボックスの新しいチェック状態。
     */

    /**
     * @cfg {Object} scope
     * {@link #handler}関数のスコープ('this'参照)として使用するオブジェクト。
     *
     * デフォルトはこのチェックボックスです。
     */

    // private overrides
    checkChangeEvents: [],
    inputType: 'checkbox',
    isTextInput: false,
    ariaRole: 'checkbox',
    
    // private
    onRe: /^on$/i,

    // the form-cb css class is for styling shared between checkbox and subclasses (radio)
    inputCls: Ext.baseCSSPrefix + 'form-cb',

    initComponent: function() {
        var me = this,
            value = me.value;
            
        if (value !== undefined) {
            me.checked = me.isChecked(value, me.inputValue);
        }
        
        me.callParent(arguments);
        me.getManager().add(me);
    },

    initValue: function() {
        var me = this,
            checked = !!me.checked;

        /**
         * @property {Object} originalValue
         * {@link #checked}で設定されたか、フォームの{@link Ext.form.Basic#trackResetOnLoad trackResetOnLoad}設定が`true`である場合に直前のフォームのロード処理でロードされた、フィールドの元の値です。
         */
        me.originalValue = me.lastValue = checked;

        // Set the initial checked state
        me.setValue(checked);
    },

    getElConfig: function() {
        var me = this;

        // Add the checked class if this begins checked
        if (me.isChecked(me.rawValue, me.inputValue)) {
            me.addCls(me.checkedCls);
        }

        return me.callParent();
    },

    getSubTplData: function(fieldData) {
        var me = this,
            boxLabel = me.boxLabel,
            boxLabelAlign = me.boxLabelAlign,
            labelAlignedBefore = boxLabel && boxLabelAlign === 'before';

        return Ext.apply(me.callParent(arguments), {
            disabled: me.readOnly || me.disabled,
            wrapInnerCls: me.wrapInnerCls,
            boxLabel: boxLabel,
            boxLabelCls: me.boxLabelCls,
            boxLabelAlign: boxLabelAlign,
            labelAlignedBefore: labelAlignedBefore,
            afterLabelCls: labelAlignedBefore ? me.afterLabelCls : '',
            noBoxLabelCls: !boxLabel ? me.noBoxLabelCls : '',
            role: me.ariaRole
        });
    },

    initEvents: function() {
        var me = this;
        me.callParent();
        // We rely on the labelEl to also trigger a click on the DOM element, so force
        // a click here and never have it translate to a tap
        me.mon(me.inputEl, 'click', me.onBoxClick, me, {
            translate: false
        });
    },
    
    /**
     * このチェックボックスの{@link #boxLabel}を設定します。
     * @param {String} boxLabel 新しいラベル
     */
    setBoxLabel: function(boxLabel){
        var me = this;
        
        me.boxLabel = boxLabel;
        if (me.rendered) {
            me.boxLabelEl.setHtml(boxLabel);
        }
    },

    /**
     * @private チェックボックスボタン上でのクリックを処理します
     */
    onBoxClick: function() {
        var me = this;
        if (!me.disabled && !me.readOnly) {
            this.setValue(!this.checked);
        }
    },

    /**
     * チェックボックスのチェック状態を返します。
     * @return {Boolean} チェックされていたらtrue、そうでなければfalse
     */
    getRawValue: function() {
        return this.checked;
    },

    /**
     * チェックボックスのチェック状態を返します。
     * @return {Boolean} チェックされていたらtrue、そうでなければfalse
     */
    getValue: function() {
        return this.checked;
    },

    /**
     * フォームを送信するときに使用できるチェックボックのsubmit値を返します。
     * @return {String} チェックされている場合、{@link #inputValue}が返されます。それでない場合、{@link #uncheckedValue}が返されます(設定されていなければ、nullが返されます)。
     */
    getSubmitValue: function() {
        var unchecked = this.uncheckedValue,
            uncheckedVal = Ext.isDefined(unchecked) ? unchecked : null;
        return this.checked ? this.inputValue : uncheckedVal;
    },

    isChecked: function(rawValue, inputValue) {
        return (rawValue === true || rawValue === 'true' || rawValue === '1' || rawValue === 1 ||
                      (((Ext.isString(rawValue) || Ext.isNumber(rawValue)) && inputValue) ? rawValue == inputValue : this.onRe.test(rawValue)));
    },

    /**
     * チェックボックスのチェックの状態をセットします。
     *
     * @param {Boolean/String/Number} value 次の値の場合、チェックボックスがチェックされます。`true, 'true', '1', 1, or 'on'`、および{@link #inputValue}と一致する文字列。他の全ての値ではチェックが外れます。
     * @return {Boolean} チェックボックスの新しいチェック状態
     */
    setRawValue: function(value) {
        var me = this,
            inputEl = me.inputEl,
            checked = me.isChecked(value, me.inputValue);

        if (inputEl) {
            me[checked ? 'addCls' : 'removeCls'](me.checkedCls);
        }

        me.checked = me.rawValue = checked;
        if (!me.duringSetValue) {
            me.lastValue = checked;
        }
        return checked;
    },

    /**
     * チェックボックスをチェック状態に設定し、変更検知を実行します。
     * @param {Boolean/String} checked 次の値の場合、チェックボックスがチェックされます。`true, 'true', '1', or 'on'`、および{@link #inputValue}と一致する文字列。他の全ての値ではチェックが外れます。
     * @return {Ext.form.field.Checkbox} this
     */
    setValue: function(checked) {
        var me = this,
            boxes, i, len, box;

        // If an array of strings is passed, find all checkboxes in the group with the same name as this
        // one and check all those whose inputValue is in the array, unchecking all the others. This is to
        // facilitate setting values from Ext.form.Basic#setValues, but is not publicly documented as we
        // don't want users depending on this behavior.
        if (Ext.isArray(checked)) {
            boxes = me.getManager().getByName(me.name, me.getFormId()).items;
            len = boxes.length;

            for (i = 0; i < len; ++i) {
                box = boxes[i];
                box.setValue(Ext.Array.contains(checked, box.inputValue));
            }
        } else {
            // The callParent() call ends up trigger setRawValue, we only want to modify
            // the lastValue when setRawValue being called independently.
            me.duringSetValue = true;
            me.callParent(arguments);
            delete me.duringSetValue;
        }

        return me;
    },

    // private
    valueToRaw: Ext.identityFn,

    /**
     * @private
     * チェックボックスのチェック状態が変化したときに呼び出されます。指定されている場合、{@link #handler}コールバック関数を呼び出します。
     */
    onChange: function(newVal, oldVal) {
        var me = this,
            handler = me.handler;

        Ext.callback(me.handler, me.scope, [me, newVal], 0, me);

        me.callParent(arguments);

        if (me.reference && me.publishState) {
            me.publishState('checked', newVal);
        }
    },
    
    resetOriginalValue: function(/* private */ fromBoxInGroup){
        var me = this,
            boxes,
            box,
            len,
            i;
            
        // If we're resetting the value of a field in a group, also reset the others.
        if (!fromBoxInGroup) {
            boxes = me.getManager().getByName(me.name, me.getFormId()).items;
            len  = boxes.length;
            
            for (i = 0; i < len; ++i) {
                box = boxes[i];
                if (box !== me) {
                    boxes[i].resetOriginalValue(true);
                }
            }
        }
        me.callParent();
    },

    // inherit docs
    beforeDestroy: function(){
        this.callParent();
        this.getManager().removeAtKey(this.id);
    },

    // inherit docs
    getManager: function() {
        return Ext.form.CheckboxManager;
    },

    onEnable: function() {
        var me = this,
            inputEl = me.inputEl;
        me.callParent();
        if (inputEl) {
            // Can still be disabled if the field is readOnly
            inputEl.dom.disabled = me.readOnly;
        }
    },

    setReadOnly: function(readOnly) {
        var me = this,
            inputEl = me.inputEl;
        if (inputEl) {
            // Set the button to disabled when readonly
            inputEl.dom.disabled = !!readOnly || me.disabled;
        }
        me.callParent(arguments);
    },

    getFormId: function(){
        var me = this,
            form;

        if (!me.formId) {
            form = me.up('form');
            if (form) {
                me.formId = form.id;
            }
        }
        return me.formId;
    }
});