0
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Vue.jsでラジオボタン付きの開閉するコンポーネント

Posted at

今回作成するのはタイトルをクリックでラジオボタンにチェックし、コンテンツを開くコンポーネントです。
支払い方法の選択等で1つは選択してほしい、かつ説明が必要な時に使用します。

ElementUIのアコーディオンでは満たせなかったのですが、ElementUIベースで作成しました。
http://element.eleme.io/#/en-US/component/collapse

Point

  • $refs
  • $nextTick
  • コンポーネントにv-model

$refsを使うことでDOMへ直接アクセスできます。
$nextTickで更新後のDOMへアクセスできます。
$refs->$nextTickというパターンがよく使われます。
Pay.vueのcollapseのv-modelは親子間のprops、emitをシンプルに定義することができます。

v-modelは一見双方向バインディングしているようで、実際はVueルールに則ってpropsで親から値を渡しeventで子から値を更新しているわけですね。

Pay.vue


<collapse v-model="activeNames">
    <collapse-item type="radio" name="pay_type" number="1">
        <template slot="title">クレジットカード</template>
        <credit></credit>
    </collapse-item>
    <collapse-item type="radio" name="pay_type" number="2">
        <template slot="title">銀行振込</template>
        <div>購入後に登録いただいてるメールアドレスへ振込先をお送りします。振込後2,3日でお届けします。</div>
    </collapse-item>
    <collapse-item type="radio" name="pay_type" number="3">
        <template slot="title">仮想通貨</template>
        <div>現在仮想通貨での支払いはできません。</div>
    </collapse-item>
</collapse>

Collapse.vue

<template>
    <div class="radio-collapse">
        <slot></slot>
        {{value}}
    </div>
</template>
<script>
    export default {
        props: {
            accordion: Boolean,
            value: {
                type: [Array, String],
                default() {
                    return [];
                }
            }
        },
        data() {
            return {
                activeNames: [].concat(this.value)
            };
        },
        watch: {
            value(value) {
                this.activeNames = [].concat(value);
            }
        },
        methods: {
            setActiveNames(activeNames) {
                this.activeNames = [].concat(activeNames);
                this.$emit('input', activeNames);
            },
            handleItemClick(item) {
                this.setActiveNames(item.number);
            }
        },
        created() {
            this.$on('item-click', this.handleItemClick);
        }
    };
</script>

CollapseItem.vue


<template>
    <div class="radio-collapse" :class="{'is-active': isActive}">
        <div class="radio-collapse__header" @click="headerClick">
            <input type="radio" :name="name" :number="number" :checked="isActive">
            <span><slot name="title"></slot></span>
        </div>
        <div class="radio-collapse__content" ref="content" :style="contentStyle">
            <div><slot></slot></div>
        </div>
    </div>
</template>

<script>
    export default {
        props: {
            name: String,
            type: String,
            number: String,
        },
        data() {
            return {
                contentStyle: {},
                contentHeight: 0
            };
        },
        computed: {
            isActive() {
                return this.$parent.activeNames.indexOf(this.number) > -1;
            }
        },
        watch: {
            isActive: function(value) {
                value ? this.open() : this.close();
            }
        },
        methods: {
            open() {
                const contentElm = this.$refs.content;
                const contentStyle = this.contentStyle;
                contentStyle.display = 'block';
                this.$nextTick(_ => {
                    contentStyle.height = this.contentHeight + 'px';
                    contentStyle.height = 'auto';
                });
            },
            close() {
                const contentElm = this.$refs.content;
                const contentHeight = contentElm.clientHeight;
                const contentStyle = this.contentStyle;
                this.contentHeight = contentHeight;
                this.$set(this.contentStyle, 'height', contentHeight + 'px');
                this.$nextTick(_ => {
                    contentStyle.height = '0';
                    this.$set(this.contentStyle, 'display', 'none');
                });
            },
            init() {
                this.contentHeight = this.$refs.content.clientHeight;
                if (!this.isActive) {
                    this.$set(this.contentStyle, 'height', '0');
                    this.$set(this.contentStyle, 'display', 'none');
                }
            },
            headerClick() {
                this.$parent.$emit('item-click', this);
            }
        },
        mounted() {
            this.init();
        }
    }
</script>
0
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?