1

En un proyecto de Laravel + Vue JS, dentro del administrador de contenidos, en la sección de mensajes de contacto, saco el típico listado de todos los mensajes que hay disponibles en la base de datos que fueron enviados por los usuarios, no por el administrador (es decir, cuyo campo "correo" de la tabla "contactos" no sea el correo del administrador que es usado como email para enviar las respuestas a los correos de los usuarios).

Vale dispongo del listado. Pero como añadido, por cada registro del listado, quería sacar el total de mensajes de respuesta que tiene vinculados cada mensaje original. Para saber qué registro de la tabla "contactos" es una respuesta, tengo un campo "msg_origen" dónde se guarda el ID del mensaje al que conteste dicho mensaje de respuesta.

Espero, por ahora, se entienda.

Expongo el código de las diferentes partes que entran en juego:

En la parte del controlador de Laravel:

public function index()
{
    $elems_no_papelera = Contacto::where('correo', '!=', $this->app_email)
                    ->orderBy('created_at', 'desc')->get();

    $_arr_detalle = [];
    $_arr_detalle['elems_no_papelera'] = $elems_no_papelera;

    return $_arr_detalle;
}

/**
 * Tot de respuestas enviadas al mensaje original.
 *
 * @return int
 */
public function getTotResponses($id)
{
    return Contacto::where('msg_origen', $id)
                    ->where('correo', $this->app_email)
                    ->count();
}

En la parte del componente tengo esto

<script>
    export default {
        created() {
            //para cargar el listado de registros al llegar al componente
            this.getElems();
        },

        data() {
            return {
                urlBase: '/api/contacts',
                elems: {},  //variable contenedora de los registros a listar
                //tot de respuestas vinculadas al mensaje recorrido
                elems_resps_tot: 0,
            }
        },

        methods: {

            /**
             * Obteniendo listado de registros
            */
            getElems() {
                //URL hacia la ruta del listado de registros
                let url = this.urlBase;
                axios.get(url).then( response => {
                    ////console.log(response.data)
                    this.elems = response.data.elems_no_papelera
                });
            },

            /**
             * Obteniendo listado de registros respuesta
             * referidos al registro consultado
            */
            getElemsRespsTot(id) {
                //URL hacia la ruta del listado de registros
                //  >> SIN paginación
                let url = this.urlBase + '/get-responses/' + id + '/tot';
                axios.get(url).then( response => {
                    console.log('TOT Respuestas para [' + id + ']: ' + response.data)
                    //this.elems_resps_tot = response.data
                    return response.data
                    //return this.elems_resps_tot
                });
            },

            //...

        },
    }
</script>

En la parte HTML del componente, tengo esto para el bucle FOR

<table class="table table-hover table-striped">
    <tbody>
        <tr v-for="(elem, index) in elems" :key="index">
            <td>
                <a v-if="elem.leido" href="javascript: void(0);" @click="updateField(elem.id, 'leido', 0)" class="text-primary" title="Leido - Marcar como NO LEIDO">
                    <i class="fas fa-circle i_mens_contacto_leidoOK"></i>
                </a>
                <a v-else href="javascript: void(0);" @click="updateField(elem.id, 'leido', 1)" class="text-primary" title="Sin leer - Marcar como LEIDO">
                    <i class="fas fa-circle i_mens_contacto_leidoNOK"></i>
                </a>
            </td>
            <td class="mailbox-name">
                <router-link :to="{ name: 'contact_msg', params: {id: elem.id} }" :title="'Ver mensaje de ' + elem.correo">
                    {{ elem.nombre }}
                </router-link>
            </td>
            <td class="mailbox-subject"><strong>{{ elem.asunto }}</strong> <span v-if="getElemsRespsTot(elem.id) > 0">{{ getElemsRespsTot(elem.id) }}</span> <br>{{ elem.mensaje | resumenTxt }}</td>
            <td class="mailbox-date">{{ elem.created_at | formatFHHaceTanto }}</td>
            <td class="mailbox-attachment">
                <a href="javascript: void(0);" @click.prevent="trashElem(elem.id)" :title="'A papelera / Borrar registro [' + elem.id + ']'">
                    <i class="fas fa-trash-alt"></i>
                </a>
            </td>
        </tr>
    </tbody>
</table>

En este trozo de código, <span v-if="getElemsRespsTot(elem.id) > 0">{{ getElemsRespsTot(elem.id) }}</span>, mientras recorro el bucle, por cada registro lo que quiero plasmar es el total de respuestas que pudiera tener ese mensaje.
Como se puede ver, llamo al método getElemesRespsTot() pasándole el ID del mensaje original del que se quiere saber si tiene respuestas.

El caso es que, aunque haya algún mensaje con un total de respuestas mayor de 0, no muestra nada por pantalla. Tan solo, el console.log('TOT Respuestas para [' + id + ']: ' + response.data) si que me muestra los totales de respuestas correctos.

TOT Resp para [13]: 0
TOT Resp para [6]: 0
TOT Resp para [9]: 0
TOT Resp para [4]: 2
TOT Resp para [1]: 0
TOT Resp para [10]: 0
TOT Resp para [7]: 0
TOT Resp para [11]: 0

En estos momentos, el mensaje original de ID=4 tiene 2 mensajes de respuesta. A pesar de ello, en su correspondiente línea del listado, no aparece ese total reflejado sino que como con los demás, se queda vacío.

¿Por qué, entonces, no se imprime adecuadamente el valor devuelto por el método getElemesRespsTot()?

zacktagnan
  • 583
  • 7
  • 27
  • Recomendación para leer y entender el porque. Axios trabaja con [Promesas](https://es.stackoverflow.com/questions/64265/qu%C3%A9-es-una-promesa-en-javascript/64403#64403) , pero creo que la solución podría darse desde el backend ;) – Dev. Joel Feb 09 '19 at 04:26

1 Answers1

1

El error es de concepto, axios trabaja con Promesas, para entender el tema ya existe una pregunta relacionada donde se expande y se explica a detalle este concepto.

Simplemente acotar que axios.get es asíncrono, por lo que no puede devolver un valor cuando se le llama.

En su lugar propongo una solución desde el backend y obtener el count deseado.

En su modelo Contacto, cree la relación uno a muchos con la clave msg_origen

public function respuestas()
{
    return $this->hasMany('App\Contacto', 'msg_origen');
}

Y así, desde el controlador utilizando withCount, bastaría, si desea añadir where al conteo, en la documentación hay un ejemplo ;)

 return  App\Contacto::where('correo', '!=', $this->app_email)
    ->orderBy('created_at', 'desc')
    ->withCount('respuestas')
    ->get();

Así desde su Componente Vue, solo deberá acceder a la propiedad respuestas_count de cada registro iterado en el v-for, El método getElemsRespsTot sería innecesario.

{{ elem.respuestas_count}}
Dev. Joel
  • 23,229
  • 3
  • 25
  • 44
  • Muy bien Joel, así me funciona perfecto. Y me ahorro la implementación del método getElemsRespsTot(elem.id) y de su correspondiente en el controlador del backend, al obtener todo a través de la misma llamada de consulta general del listado de registros. No conocía el helper "withCount" que devuelve, como otro campo de resultado de la consulta, el total de elementos de la relación con el nombre "nombreRelación_count". Gracias. Saludos. – zacktagnan Feb 09 '19 at 11:39