Vuejs emit after ajax, gagal render

jadi saya mau buat image uploader component dengan vuejs. nah masalah sy, pingin uploadernya mendeteksi kalau ada nama file yg sama maka diganti dengan kode uniq. setelah itu kode uniq tersebut mengupdate nilai value di vuejs, maksud sy variable yg ada di v-model terupdate juga nilainya

ini kode yg berjalan dengan baik sementara ini;

JS (VueJS Component)


<template id="upload-template">
    <div class="form-group">
        <label ref="label">
            <slot></slot>
        </label>

        <figure>
            <i v-if="loading" class="fa fa-spin fa-refresh" style="margin-bottom: 16px"></i>
            <img v-if="!loading && url" :src="url" style="width: 100%;margin-bottom: 8px">
        </figure>

        <input ref="fileinput" :value="value" @change="inputEvent" type="file">
        <p v-if="placeholder" class="help-block">{{ placeholder }}</p>
    </div>
</template>

<script>
    var UploadComponent = Vue.extend({
        template: '#upload-template',

        props: {

            value: {
                type: String,
                default: '',
            },

            placeholder: {
                type: String,
                default: '',
            },

        },

        data: function () {
            return {
                loading: false,
                url: '',
            };
        },

        methods: {

            inputEvent: function (event) {
                this.$emit('input', event.target.value);
                this.uploadFile(event);
            },

            uploadFile: function (event) {

                this.loading = true;

                var data = new FormData();

                data.append('apiKey', API_KEY);
                data.append('fileActive', $(event.currentTarget)[0].files[0]);

                $.ajax({
                    url: base_url('api/system/upload'),
                    data: data,
                    cache: false,
                    contentType: false,
                    processData: false,
                    type: 'POST',
                    success: function(result){

                        this.url = result.data.url;
                        this.loading = false;

                    }.bind(this),
                });
            },
        },

    });

    Vue.component('ui-upload', UploadComponent);
</script>

Script Upload PHP (Codeigniter)

<?php
defined('BASEPATH') OR exit('No direct script access allowed');

class SystemController extends MY_Controller {

    public function upload() {
        $this->authen(AT_FREE);

        $fileActive = $_FILES["fileActive"];

        $targetDir = 'writable/tmp';
        $targetFile = $fileActive['name'];
        $targetPath = $targetDir.'/'.$targetFile;

        if (file_exists($targetPath)) {
            $xTargetFile = explode('.', $targetFile);
            $targetExtension = $xTargetFile[count($xTargetFile) - 1];
            $targetFile = uniqid().'.'.$targetExtension;

            $targetPath = $targetDir.'/'.$targetFile;
        }

        $targetUrl = base_url($targetPath);

        if (move_uploaded_file($fileActive["tmp_name"], $targetPath))
            $this->success('File uploaded', [
                'file' => $targetFile,
                'path' => $targetPath,
                'url' => $targetUrl,
            ]);
        else
            $this->failed('Upload failed');
    }
}

sy pakai component ini seperti ini;

<ui-upload v-model="foto"></ui-upload>
...
...
data: {
   foto: '',
},
...

masalahnya kalau ada file dengan nama yg sama di upload, ya file tersebut bakal ketimpa. maka dari itu sesuai coding php diatas, sy mau mengubah nama file jadi kode uniq kalau ada nama file yg sama. nah problemnya si vuejs bermasalah ketika sy emit event setelah ajax selesai eksekusi (sudah kasih result back dari api). bgini sy ubah js-nya agar v-model menangkap perubahan nama file

...
...
             $.ajax({
                    url: base_url('api/system/upload'),
                    data: data,
                    cache: false,
                    contentType: false,
                    processData: false,
                    type: 'POST',
                    success: function(result){

                        this.url = result.data.url;
                        this.loading = false;

                        this.$emit('input', result.data.file);
                    }.bind(this),
                });
...
...

hasilnya perubahan nama file di variable terkait v-model terdeteksi dengan baik. namun yg aneh, hanya image yg pertama di upload yg kerender, jika sy coba upload image lagi imagenya ga berubah ga kerender. terus ada pesan dari vue di console -> SecurityError: The operation is insecure. Padahal untuk tau atau ngga ada file dengan nama yg sama di server, sy harus mengambil datanya dari server side PHP. tapi kalau vuejs ga bisa emit eventnya, setelah ajax, gmana caranya sy bisa tau dan mengabari ada perubahan di v-model kalau nama filenya sama.

gmana ini gan caranya?

avatar QaiserLab
@QaiserLab

366 Kontribusi 390 Poin

Diperbarui 7 tahun yang lalu

11 Jawaban:

saya kurang nangkep masalahnya gan. Kan kita nampilin text atau gambar hasil dari backend, jadi harusnya frontend ngga peduli dia namanya kembar atau ngga, dia otomatis bakal ngambil result nama yang udah diproses di backend. Artinya kalo dari backend ngelempar 1.jpg maka di frontend juga 1.jpg, jadi harusnya frontend ngga ada yang berubah, cukup di backend aja

avatar hilmanski
@hilmanski

2670 Kontribusi 2132 Poin

Dipost 7 tahun yang lalu

jadi gini gan. 1. sy punya form, di antara beberapa textbox sy punya upload image. 2. waktu kita milih si gambar dengan upload image, proses upload langsung berjalan si image lalu disimpan di folder tmp, setelah sukses, gambar langsung ditampilkan (dengan url yg sudah terupload = url/nama file di dapet dari backend). note: ini belum submit form 3. setelah ngisi foto, user ngisi textbox yg lain. terus tekan submit. note: ini baru submit beneran - disini nih, waktu submit sy perlu nama dari file yg real yg ada di server. karena filenya ada di folder tmp saya perlu nama file tersebut, utk moving si file dari tmp ke archive. note: submit artinya deal bahwa file yg di tmp tersebut yg disimpan, kalo dah deal ya ditarok ke archive

nah masalahnya sy pengen ngambil nama file tersebut (setelah proses point 2). yaitu setelah ajax mengupload file, php kasih umpan balik yaitu nama file yg tersimpan di folder tmp. nah nama file ini sudah sampai ke javascript, tapi ga mau di binding ke v-model, bandel bener. intinya perintah emit nya bikin kacau preview gambarnya kalo ditarok setelah ajax, sy juga heran, kalau emit nya ditarok sebelum ajax, ga da masalah gan.

avatar QaiserLab
@QaiserLab

366 Kontribusi 390 Poin

Dipost 7 tahun yang lalu

oh kayanya rada paham sekarang. Kalau saya ngga salah paham, berarti : -Ada dua folder /tmp sama /archive, /tmp cuman bentar doang pas submit foto (bukan submit form), -Setelah tombol form submit di klik kita mau mindah dari tmp ke archive?

Asumsi saya: - kita punya text gambar awal saat di /tmp - kita juga punya text gambar baru kalau ada yang kembar (hasil submit)

Ide yang kepikiran: ketika udah upload pertama kali, simpan nama temporary nya ini disatu variable berbeda $name_temp, nah nanti setiap butuh nama asli atau pas mau upload ambil $name_temp ini untuk jadi nama file yang mau diupload. Jadi kayanya ngga perlu emit-emitan

*maaf kalo salah paham *Saya rada janggal, bukannya $emit pasangan sama $on ya di vue, tapi saya ngga ngelihat ada methode $on - nya disini

avatar hilmanski
@hilmanski

2670 Kontribusi 2132 Poin

Dipost 7 tahun yang lalu

Jawaban asumsi - text gambar yg sudah kesimpan di /tmp itu yg nama aslinya. data nama text ini yg perlu sy emmit ke object yg udah di enkapsul. - text gambar kembar udah dapet dari script api php diatas. sudah berhasil pula dibalikin ke client side/javascript. tapi ga bisa dikirim datanya ke object yg udah di enkapsul, jadi text gambar ini intinya cuma sampe di abstraction komponennya aja dan ga mau dikirim ke object yg udah di enkapsul.

jawaban ide yg kepikiran ga bisa disimpen di variable biasa gan. kan gini api submit gambar sama api submit form berbeda. alias si variable tersebut hilang setelah dieksekusi masing2 api. ide agan bisa sy sempurnain sebenernya, yaitu dengan nyimpan nama filenya di $_SESSION['name_temp']. tapi walaupun benar bukan ini yg sy inginkan. sy benar2 mau bikin reusable komponen yg sumber data setiap UInya benar-benar seragam, yaitu ada di v-model. berikut ini kira2 komponen2 yg sy buat;

nah sekarang yg mau sy bikin

jadi kalau next time sy ada kerjaan, semuanya tinggal pake gitu, tanpa banyak konfigurasi karena rata-rata sudah seragam. kalau sy main variable atau session, nanti setiap ada kasus upload, repot lagi repot lagi sy bikinnya gan. sebenernya sy udah bikin upload ini, dan sukses, tapi sy pake standard jquery plugin bikinnya. nah ini sy mau terapin vue

\* jawaban kejanggalan iya gan $emit dan $on, emang sepasang, ini untuk event based programming kan nah yg punya sy itu, sy ikutin rekomendasi dari forum dan dokumentasi official, yaitu utk menerapkan v-model ke komponen yg kita buat, kita harus emmit event dari ui nativenya ke ui custom kita. misal kita lagi bikin ui custom dengan komponen dasar textbox, trus kita pengen custom komponen kita ada event @keydown, simpelnya kita emit aja event keydown dari textbox native ke parent nya (ui custom). nah dalam kasus ini yg di emit itu perubahan even dan perubahan value yg terjadi di native, biar two way data binding gan, alias v-model jalan, kan v-model konsepnya two way data binding -> perubahan di dalam abstraction berpengaruh ke object yg dienkapsul begitupun sebaliknya.

disitu kan sy $emmit event input gan. jadi kalau di object yg dienkapsule sy bisa . nah ini ketemu kan pasangan $on-nya

kalau tau cara supaya v-model di ui-upload saya jalan, kasi tau ya gan, atau ada agan2 lain yg tau?

avatar QaiserLab
@QaiserLab

366 Kontribusi 390 Poin

Dipost 7 tahun yang lalu

oh.. maap kruang merhatiin gan. nanti malam saya cobain kodenya, siapa tahu ketemu. Btw saya masih ngga nangkep masalah yang ini "hanya image yg pertama di upload yg kerender, jika sy coba upload image lagi imagenya ga berubah ga kerende" Ini maksudnya kalau upload 1 image lebih? atau kalau udah klik tombol upload gambar, terus di klik lagi ? (edit gambar) ?

avatar hilmanski
@hilmanski

2670 Kontribusi 2132 Poin

Dipost 7 tahun yang lalu

kepengennya: pengen bikin si komponen ui-upload tsb, bisa nerima v-model masalahnya: kalau sy $emmit event-nya (agar v-model terupdate datanya) malah error efek errornya: gambarnya (url yg sy binding ke :src) jadi ga keupdate/kerender/ga tampil gambarnya flow ux nya: - pertama kali sy klik tombol browse, lalu sy pilih gambarnya -> proses upload berjalan lalu gambar tampil, dan v-model terupdate. - ketika sy klik tombol browse lagi (yg kedua kali) -> proses upload berjalan, GAMBAR TIDAK MAU TAMPIL, tapi v-model terupdate. penyebabnya: - jika sy $emmit setelah ajax (ini yg sy mau) -> v-model terupdate tapi render gambar bermasalah

ini sy kasi source code lengkapnya gan kalau mw test:

di application/core/MY_Controller.php

<?php
defined('BASEPATH') OR exit('No direct script access allowed');

class MY_Controller extends CI_Controller {

    public function __construct() {
        parent::__construct();
        session_start();
    }

//    protected function _php() {
//        $this->load->view('errors/cli/error_php');
//    }

    protected function authen($authLevel) {
        $postApiKey = $this->input->post('apiKey');
        $postAuthKey = $this->input->post('authKey');

        $apiKey = $this->config->item('apiKey');

        if (empty($postApiKey))
            $this->denied();

        if ($postApiKey != $apiKey)
            $this->denied();

        if ($authLevel == AT_MEMBER) {
            if (empty($postAuthKey))
                $this->denied();

            session_start();

            if ($postAuthKey != $_SESSION['authKey'])
                $this->denied();
        }
        elseif ($authLevel == AT_USER) {
            if (empty($postAuthKey))
                $this->denied();

            if ($_SESSION['authType'] != AT_USER)
                $this->denied();

            if ($postAuthKey != $_SESSION['authKey'])
                $this->denied();
        }

    }

    protected function result($state, $message, $data = array(), $extra = array()) {
        header("Access-Control-Allow-Origin: *");
        header('Content-Type: application/json');

        $result = array(
            'state' => $state,
            'message' => $message,
            'data' => $data,
            'extra' => $extra,
        );

        echo json_encode($result);
        exit;
    }

    protected function success($message, $data = array(), $extra = array()) {
        $this->result('success', $message, $data, $extra);
    }

    protected function failed($message, $data = array(), $extra = array()) {
        $this->result('failed', $message, $data, $extra);
    }

    protected function invalid($data = array(), $extra = array()) {
        $this->result('invalid', 'Please correct following errors;', $data, $extra);
    }

    protected function denied() {
        $this->result('denied', 'Access denied');
    }

}

di application/controllers/SystemController.php

<?php
defined('BASEPATH') OR exit('No direct script access allowed');

class SystemController extends MY_Controller {

    public function upload() {
        // $this->authen(AT_FREE);

        $fileActive = $_FILES["fileActive"];

        $targetDir = 'writable/tmp';
        $targetFile = $fileActive['name'];
        $targetPath = $targetDir.'/'.$targetFile;

        if (file_exists($targetPath)) {
            $xTargetFile = explode('.', $targetFile);
            $targetExtension = $xTargetFile[count($xTargetFile) - 1];
            $targetFile = uniqid().'.'.$targetExtension;

            $targetPath = $targetDir.'/'.$targetFile;
        }

        $targetUrl = base_url($targetPath);

        if (move_uploaded_file($fileActive["tmp_name"], $targetPath))
            $this->success('File uploaded', [
                'file' => $targetFile,
                'path' => $targetPath,
                'url' => $targetUrl,
            ]);
        else
            $this->failed('Upload failed');
    }
}

ini komponen sy;

<template id="upload-template">
    <div class="form-group">
        <label ref="label">
            <slot></slot>
        </label>

        <figure>
            <i v-if="loading" class="fa fa-spin fa-refresh" style="margin-bottom: 16px"></i>
            <img v-if="!loading && url" :src="url" style="width: 100%;margin-bottom: 8px">
        </figure>

        <input ref="fileinput" :value="value" @change="inputEvent" type="file">
        <p v-if="placeholder" class="help-block">{{ placeholder }}</p>
    </div>
</template>

<script>
    var UploadComponent = Vue.extend({
        template: '#upload-template',

        props: {

            value: {
                type: String,
                default: '',
            },

            placeholder: {
                type: String,
                default: '',
            },

        },

        data: function () {
            return {
                loading: false,
                url: '',
            };
        },

        methods: {

            inputEvent: function (event) {
                // this.$emit('input', event.target.value);
                this.uploadFile(event);
            },

            uploadFile: function (event) {

                this.loading = true;

                var data = new FormData();

                // data.append('apiKey', API_KEY);
                data.append('fileActive', $(event.currentTarget)[0].files[0]);

                $.ajax({
                    url: base_url('api/system/upload'),
                    data: data,
                    cache: false,
                    contentType: false,
                    processData: false,
                    type: 'POST',
                    success: function(result){

                        this.url = result.data.url;
                        this.loading = false;

                        this.$emit('input', result.data.file);
                    }.bind(this),
                });
            },
        },

    });

    Vue.component('ui-upload', UploadComponent);
</script>

itu controllernya sy routes makanya alamat url ajaxnya beda. makasi banyak gan

avatar QaiserLab
@QaiserLab

366 Kontribusi 390 Poin

Dipost 7 tahun yang lalu

belum ketemu gan udah nyoba-nyoba, kapan-kapan saya nyoba lagi. btw agan make ui-upload ini dibungkus di dalam form seperti ini ?

 <form action="index.html" method="post">
                <upload-ui></upload-ui>
                <br>
                <input type="text" name="name" value="">
                <input type="submit" name="" value="submit">
            </form>
avatar hilmanski
@hilmanski

2670 Kontribusi 2132 Poin

Dipost 7 tahun yang lalu

ini yang saya kepikiran gan, pake $parent dari childnya sebelum emit, tapi saya nyediain $on juga buat nangkep di parent, cobain aja setelah ajacnya pake $parent atau $root sebelum emit

<!-- main -->
        <div id="app">
            <form v-on:submit.prevent="onSubmit">
                <upload-ui></upload-ui>
                <br>
                <input type="text" name="name" value="">
                <input type="submit" name="" value="submit">
            </form>
        </div>

        <!-- template -->
        <template id="upload-template">
            <div>
                <input type="file" @change="uploadFile($event)">
                <img :src="imageSrc" alt="">
            </div>
        </template>

        <script src="https://cdn.jsdelivr.net/vue.resource/1.2.0/vue-resource.min.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.10/vue.min.js"></script>
        <script>
            var uploadComponent = Vue.extend({
                 template: '#upload-template',
                 data: function() {
                     return {
                         imageSrc: '',
                     }
                 },
                 methods: {
                     uploadFile: function(e) {
                         var f = e.target.files[0]
                         this.imageSrc = URL.createObjectURL(f)
                         this.$parent.$emit('myEvent', this.imageSrc)
                     }
                 }
             })

             Vue.component('upload-ui', uploadComponent)

             var app = new Vue({
               el: "#app",
               data: {

               },
               methods: {
                   onSubmit: function() {
                       console.log('yeah')
                   },
               }
            })

            app.$on('myEvent', function(url){
                console.log(url)
            })
        </script>

avatar hilmanski
@hilmanski

2670 Kontribusi 2132 Poin

Dipost 7 tahun yang lalu

Oh. Maksud agan, valuenya diumpan keluar komponen sebagai parameter ya. Jadi si parent ngelisten event ketika upload telah dilakukan. Bisa juga ni gan kalo udah ga da jalan keluar utk ngebalikin nilai value ke parentnya. Tapi sy masi ngotot gan, supaya valuenya ngebinding ke v-model

avatar QaiserLab
@QaiserLab

366 Kontribusi 390 Poin

Dipost 7 tahun yang lalu

Jawaban Terpilih

ketemu gan. sederhana sekali binding :value di input file ny sy hilangin. ternyata itu yg bikin jadi error. ini source komponennya;

<template id="upload-template">
    <div class="form-group">
        <label ref="label">
            <slot></slot>
        </label>

        <figure>
            <i v-if="loading" class="fa fa-spin fa-refresh" style="margin-bottom: 16px"></i>
            <img v-if="!loading && url" :src="url" style="width: 100%;margin-bottom: 8px">
        </figure>

        <input ref="fileinput" @change="uploadFile" type="file">
        <p v-if="placeholder" class="help-block">{{ placeholder }}</p>
    </div>
</template>

<script>
    var UploadComponent = Vue.extend({
        template: '#upload-template',

        props: {

            value: {
                type: String,
                default: '',
            },

            placeholder: {
                type: String,
                default: '',
            },

        },

        data: function () {
            return {
                loading: false,
                url: '',
            };
        },

        created: function () {
            this.url = base_url('writable/archive/' + this.value);
        },

        methods: {

            uploadFile: function (event) {

                this.loading = true;

                var data = new FormData();

                data.append('apiKey', API_KEY);
                data.append('fileActive', $(event.currentTarget)[0].files[0]);

                $.ajax({
                    url: base_url('api/system/upload'),
                    data: data,
                    cache: false,
                    contentType: false,
                    processData: false,
                    type: 'POST',
                    success: function(result){

                        this.url = result.data.url;
                        this.loading = false;

                        this.$emit('input', result.data.file);
                    }.bind(this),
                });
            },
        },

    });

    Vue.component('ui-upload', UploadComponent);
</script>

saya pakai di form ini;

<div id="page-active" class="content-wrapper">

    <!-- Headbar -->
    <ui-header title="<?= $title ?>" caption="<?= $caption ?>">
        <ol class="breadcrumb">
            <li><a href="<?= base_url('admin') ?>"><i class="fa fa-dashboard"></i> Dashboard</a></li>
            <li><a href="<?= base_url('admin/users/all-data') ?>"><?= $title ?></a></li>
            <li class="active"><?= $caption ?></li>
        </ol>
    </ui-header>
    <!-- End Headbar -->

    <!-- Content -->
    <form @submit.prevent="saveDataAction" role="form" autocomplete="off">
        <section class="content">
            <div class="row">

                <!-- Left Column -->
                <div class="col-md-9">

                    <!-- Widget -->
                    <ui-widget caption="Editor" :loading="loading">
                        <div class="box-body">

                            <ui-error :data-source="result"></ui-error>

                            <ui-textbox v-model="form.firstName">First Name</ui-textbox>
                            <ui-textbox v-model="form.lastName">Last Name</ui-textbox>
                            <ui-textbox v-model="form.username">Username</ui-textbox>
                            <ui-textbox v-model="form.email">Email</ui-textbox>
                            <ui-textbox v-model="form.password" type="password" autocomplete="off">Password</ui-textbox>
                            <ui-textbox v-model="form.retypePassword" type="password" autocomplete="off">Retype Password</ui-textbox>

                        </div>
                    </ui-widget>
                    <!-- End Widget -->

                </div>
                <!-- End Left Column -->

                <!-- Right Column -->
                <div class="col-md-3">

                    <ui-widget caption="Action">

                        <div class="box-body">

                                <ui-upload v-model="form.photo" placeholder="Size 3x4">Photo</ui-upload>
                                <ui-select v-model="form.activationId" :data-source="rsActivation" field="activation">
                                    Activation
                                </ui-select>

                        </div>

                        <div class="box-footer">
                            <ui-button :loading="loading" icon="save">
                                Save
                            </ui-button>
                        </div>

                    </ui-widget>

                </div>
                <!-- End Right Column -->
            </div>

        </section>
    </form>
    <!-- End Content -->

</div>

<script>
    var vm = new Vue({
        el: '#page-active',
        data: {
            loading: false,
            result: $api.result(),

            rsActivation: [],

            form: {
                firstName: '',
                lastName: '',
                username: '',
                email: '',
                password: '',
                retypePassword: '',
                photo: '',
                activationId: 1,
            },
        },

        created: function () {
            this.refreshDataAction();
        },

        methods: {

            refreshDataAction: function () {
                this.loading = true;

                var url = 'crud/activation/read';
                var data = {
                    orderBy: 'id',
                };

                $api.post(url, data, function (result) {

                    this.rsActivation = result.data;
                    this.loading = false;

                }.bind(this));

            },

            saveDataAction: function () {
                this.loading = true;

                var url = 'crud/user/create';
                var data = this.form;

                $api.post(url, data, function (result) {

                    if (result.state == 'success') {
                        $splash.show(result.message, function () {
                            window.location = admin_url('users/edit-data/' + result.data.id);
                        });
                    }
                    else {
                        this.result = result;
                        this.loading = false;
                    }

                }.bind(this));
            },
        },
    });
</script>

tengkyu

avatar QaiserLab
@QaiserLab

366 Kontribusi 390 Poin

Dipost 7 tahun yang lalu

mantap gan, thanks for sharing, emang ngoding gitu, suka di luar dugaan solusinya haha

avatar hilmanski
@hilmanski

2670 Kontribusi 2132 Poin

Dipost 7 tahun yang lalu

Login untuk ikut Jawaban