Foro de elhacker.net

Programación => Desarrollo Web => Mensaje iniciado por: gAb1 en 16 Noviembre 2015, 06:11 am



Título: filtrar contenido con jquery
Publicado por: gAb1 en 16 Noviembre 2015, 06:11 am
Tengo una función con JQuery Ajax que recibe contenido de PHP enviado variables, depende de las variables y el contenido de estas se recibiran unas cosas u otras.

Con la misma función (un on('click')) tengo que declarar y asignar valores a las variables que se envian, dandoles el valor del elemento que hizo click, mayormente checkboxes.

Lo único que se me ocurre es hacer un if para cada checkbox:

Código
  1. if ($(this).is(':checkbox') {
  2.    //comprobar la clase del checkbox para saber que datos enviar
  3.    if ($(this).attr('class') == 'filter1') {
  4.        var filtro1 = $(this).val();
  5.    }
  6.    if ($(this).attr('class') == 'filter2') {
  7.        var filtro2 = $(this).val();
  8.    }
  9.    if ($(this).attr('class') == 'filter3') {
  10.        var filtro3 = $(this).val();
  11.    }
  12. }
Y asi sucesivamente... ¿Hay alguna manera más eficiente de hacer esto? Tambien tengo que comprobar que si se han checkeado dos checkboxes o mas de la misma clase se envien los valores como array o algo. Agradeceria cualquier ejemplo.

PHP se encarga del resto, de crear los condicionales "AND" y agregarlos al "SELECT" de la consulta mysqli.

Gracias!


Título: Re: filtrar contenido con jquery
Publicado por: MinusFour en 16 Noviembre 2015, 06:33 am
No es necesario que guardes los valores en variables, al final la información tiene que ser serializada y jQuery tiene un metodo (https://api.jquery.com/serialize/) para eso.


Título: Re: filtrar contenido con jquery
Publicado por: gAb1 en 16 Noviembre 2015, 12:30 pm
Pues entonces lo he estado haciendo mal, aunque funcionando:

Código
  1.    $('body').on('click', '.click, :checkbox, .pag_link', function() { // search, filters and change page buttons
  2.  
  3.        var self = this;
  4.  
  5.        if ($('#res_prop').is(':checked')) {
  6.            var use = $('#res_prop').val();
  7.        }
  8.        else if ($('#com_prop').is(':checked')) {
  9.            var use = $('#com_prop').val();
  10.        }
  11.        else {
  12.            $('p.error').show();
  13.            die();
  14.        }
  15.  
  16.        if ($(self).is(':checkbox')) {
  17.            $(self).on('change', function() {
  18.                if ($(self).attr('class') == 'filter1' || $('.filter1').is(':checked')) {
  19.                    if ($('.filter1').is(':checked'))
  20.                        var type = $(self).val(); // maybe should be an array
  21.                    else var type = null;
  22.                } else var type = null;
  23.                if ($(self).attr('class') == 'filter2' || $('.filter2').is(':checked')) {
  24.                    if ($('.filter2').is(':checked'))
  25.                        var status = $(self).val(); // maybe should be an array
  26.                    else var status = null;
  27.                } else var status = null;
  28.                if ($(self).attr('class') == 'filter3' || $('.filter3').is(':checked')) {
  29.                    if ($('.filter3').is(':checked'))
  30.                        var bhk = $(self).val(); // maybe should be an array
  31.                    else var bhk = null;
  32.                } else var bhk = null;
  33.            });
  34.        }
  35.        else {
  36.            var type = status = bhk = null;
  37.        }
  38.  
  39.        if ($(self).is('.pag_link')) {
  40.            if ($(self).text() == '«')
  41.                var page = (parseInt($('.active').text()) - 1);
  42.            else if ($(self).text() == '»')
  43.                var page = (parseInt($('.active').text()) + 1);
  44.            else
  45.                var page = parseInt($(self).text());
  46.        }
  47.        else {
  48.            var page = 1;
  49.        }
  50.  
  51.        $.ajax({
  52.            method: 'POST',
  53.            url: '/search',
  54.            data: {
  55.                'do': getUrlParameter('do'),
  56.                'use': use,
  57.                'type': type,
  58.                'status': status,
  59.                'bhk': bhk,
  60.                'city': $('select[name="city"]').val(),
  61.                'zone': $('select[name="zone"]').val(),
  62.                'page': page
  63.            }
  64.        }).done(function(data) {
  65.            if ($( '#search' ).is(':visible'))
  66.                $( '#search' ).hide();
  67.  
  68.            if ($(self).is(':checkbox')) {
  69.                var new_content = $(data).find( '#scroll-to-list' );
  70.                $( '#scroll-to-list' ).replaceWith( new_content );
  71.            }
  72.            else {
  73.                var new_content = $(data).find( '#search-filters, #scroll-to-list' );
  74.                $( '#results' ).html( new_content );
  75.                $( 'html, body' ).animate({
  76.                    scrollTop: $( '#scroll-to-list' ).offset().top
  77.                }, 1000);
  78.            }
  79.  
  80.        });
  81.    });

No entiendo bien para que es necesario codificar estos datos, ¿es necesario hacerlo?

Realmente necesito aprender a reducir el código...


Título: Re: filtrar contenido con jquery
Publicado por: MinusFour en 16 Noviembre 2015, 16:26 pm
Código
  1. var data = $('input[class^="filter"]')
  2. .add('select[name="city"]')
  3. .add('select[name="zone"]').serializeArray();
  4.  
  5. //para agregar nuevos pares:
  6. //data.push({ name : 'nombre', value : 'valor'});
  7. $.ajax({
  8.    method: 'POST',
  9.    url: '/search',
  10.    data: $.param(data)
  11. }).done(function (data) {
  12.    if ($('#search').is(':visible')) $('#search').hide();
  13.    if ($(self).is(':checkbox')) {
  14.        var new_content = $(data).find('#scroll-to-list');
  15.        $('#scroll-to-list').replaceWith(new_content);
  16.    } else {
  17.        var new_content = $(data).find('#search-filters, #scroll-to-list');
  18.        $('#results').html(new_content);
  19.        $('html, body').animate({
  20.            scrollTop: $('#scroll-to-list').offset().top
  21.        }, 1000);
  22.    }
  23. });

Si "use" puede ser "#res_prop" o "#com_prop" entonces el control debería ser un radio button.

Código
  1. <input type="radio" name="use">
  2. <input type="radio" name="use">

Los filtros tambien deberían tener los nombres(el atributo name) de tus variables (bhk, type, status). Es muy probable que necesites agregar page y do manualmente. (Lee el comentario que puse).


Título: Re: filtrar contenido con jquery
Publicado por: gAb1 en 16 Noviembre 2015, 16:45 pm
Si, estoy usando radio button para esos dos id.

¿Te refieres a cada checkbox ponerle el atributo name?

$('input[class^="filter"]') Esto añade solo los checkboxes que estén marcados no?

Entonces los ifs que tengo creados no sirven? Como sé que número de página pasar o que valor de los radio button pasar? Para todo en general, como hago eso??

Y luego en php como cojo los datos del post? Actualmente lo estaba haciendo asi:

Código
  1. $use            = isset( $_POST['use'] ) ? (int) $_POST['use'] : '';        // int AJAX
  2. $filter_type    = isset( $_POST['type'] ) ? (int) $_POST['type'] : '';      // int AJAX
  3. $filter_status  = isset( $_POST['status'] ) ? (int) $_POST['status'] : '';  // int AJAX
  4. $filter_bhk     = isset( $_POST['bhk'] ) ? (int) $_POST['bhk'] : '';        // int AJAX
  5. $filter_city    = isset( $_POST['city'] ) ? (int) $_POST['city'] : 0;       // int AJAX
  6. $filter_zone    = isset( $_POST['zone'] ) ? (int) $_POST['zone'] : 0;       // int AJAX
  7. $page_number    = isset( $_POST['page'] ) ? (int) $_POST['page'] : '';      // int AJAX

Lo siento, no me entero.


Título: Re: filtrar contenido con jquery
Publicado por: MinusFour en 16 Noviembre 2015, 19:00 pm
Código:
'input[class^="filter"]'

Selecciona todos los inputs con clase que empieze como 'filter' (filter1, filter2, filter3). No te tienes que preocupar si están vacios o no. "serializeArray" debería seleccionar solo los que no esten marcados. De igual forma, necesitas agregar los radios (se serializa el que esta marcado):

Código
  1. var data = $('input[class^="filter"]')
  2.                        .add('input[name="use"]')
  3. .add('select[name="city"]')
  4. .add('select[name="zone"]').serializeArray();

Mira como trabaja ".serializeArray" con "$.param":

https://jsfiddle.net/19avm4rb/

Para "do" y page puedes hacer lo que ya haces...


Título: Re: filtrar contenido con jquery
Publicado por: gAb1 en 16 Noviembre 2015, 22:11 pm
Ah vale ya lo entiendo mejor. Pero si tengo más de un checkbox con el mismo nombre luego en php como hago? Por ejemplo name type, un checkbox tiene el valor 1 y otro 2, ¿como manejo eso en php? Luego con IFs compruebo si se ha recibido el filtro y incluyo una condición AND al prepare, pero si son dos como hago?

Código
  1. $filter_type    = isset( $_POST['type'] ) ? (int) $_POST['type'] : NULL;
  2. if ($filter_type != NULL) {
  3.    $filter_type = 'AND t2.type = ' . $filter_type;
  4.    // si se marcaron dos checkboxes de nombre type, como hago la otra condición OR?
  5. }
  6. if ($stmt = $mysqli->prepare(' SELECT t1.id, t2.*
  7.                               FROM ' . $table . ' t1
  8.                               INNER JOIN property t2 ON t2.id = t1.id
  9.                               WHERE t2.use = ?
  10.                               ' . $filter_type
  11.                                 . $filter_status
  12.                                 . $filter_bhk
  13.                                 . $filter_city
  14.                                 . $filter_zone . '
  15.                               LIMIT ?, ?'))
  16.  

Para do y page usaré lo que me dijiste, el data.push.

Muchas gracias!

EDITO: Ya casi lo tengo:

Código
  1. $('body').on('click', '.click, :checkbox, .pag_link', function() {
  2.  
  3.    var self = this;
  4.  
  5.    if (!$(':radio').is(':checked')) {
  6.        $('p.error').show();
  7.        die();
  8.    }
  9.  
  10.    var data = $('input, select').serializeArray(),
  11.        mode = getUrlParameter('do'),
  12.        page = 1;
  13.  
  14.    if ($(self).is('.pag_link')) {
  15.        if ($(self).text() == '«')
  16.            page = (parseInt($('.active').text()) - 1);
  17.        else if ($(self).text() == '»')
  18.            page = (parseInt($('.active').text()) + 1);
  19.        else
  20.            page = parseInt($(self).text());
  21.    }
  22.  
  23.    data.push({ name : 'do',   value : mode});
  24.    data.push({ name : 'page', value : page});
  25.  
  26.    $.ajax({
  27.        method: 'POST',
  28.        url: '/search',
  29.        data: $.param(data)
  30.    }).done(function (data) {
  31.        if ($( '#search' ).is(':visible'))
  32.            $( '#search' ).hide();
  33.  
  34.        if ($(self).is(':checkbox')) {
  35.            var new_content = $(data).find( '#scroll-to-list' );
  36.            $( '#scroll-to-list' ).replaceWith( new_content );
  37.        }
  38.        else {
  39.            var new_content = $(data).find( '#search-filters, #scroll-to-list' );
  40.            $( '#results' ).html( new_content );
  41.            $( 'html, body' ).animate({
  42.                scrollTop: $( '#scroll-to-list' ).offset().top
  43.            }, 1000);
  44.        }
  45.  
  46.    });
  47. });
  48.  

¿Se podrá optimizar todavía más? : :)


Título: Re: filtrar contenido con jquery
Publicado por: MinusFour en 17 Noviembre 2015, 18:23 pm
Ah vale ya lo entiendo mejor. Pero si tengo más de un checkbox con el mismo nombre luego en php como hago? Por ejemplo name type, un checkbox tiene el valor 1 y otro 2, ¿como manejo eso en php? Luego con IFs compruebo si se ha recibido el filtro y incluyo una condición AND al prepare, pero si son dos como hago?

Suena a que debería ser otro filtro o si es una opción entre 1 y 2 entonces un radio. También puedes tratar los filtros como si fuera un arreglo:

Código
  1. <input type="checkbox" name="filtros[]">
  2. <input type="checkbox" name="filtros[]">

Y desde PHP:

Código
  1. $filtros = $_POST['filtros'];
  2. list($filtro1, $filtro2) = $filtros;
  3. //$filtro1 == $filtros[0];
  4. //$filtro2 == $filtros[1];

Aunque $.param transforma los "[]" en el query string, no se si PHP trabaje ese caso.


Título: Re: filtrar contenido con jquery
Publicado por: gAb1 en 17 Noviembre 2015, 21:44 pm
Pues podría ponerlos como radio, pero me gustaría dar la opción de que elijan más de un tipo a la vez.

EDITO: Si que funciona lo de meter arrays en el serializeArray() con el $.param: https://jsfiddle.net/19avm4rb/1/ En PHP se cogen como si fuera un post normal, pero tengo un problema para crear las condiciones OR. La primera (AND) funciona bien, pero ya las demás da error:

Código
  1. if ($filter_type != NULL) {
  2.    foreach ($filter_type as $type=>$value) {
  3.        if ($type === 0) {
  4.            $type_cond = 'AND t2.type = ' . $value;
  5.        } else {
  6.            $type_cond .= $type_cond . "\n OR t2.type = " . $value;
  7.        }
  8.    }
  9. }
  10.  

Citar
MySQLi failed: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 't2.type = 2
 OR t2.type = 3

                                           LIMIT ?,' at line 5<br><br />

Debe ser un error al crear las variables o al imprimirlas dentro del prepare(), ya que están bien:

Código
  1. SELECT t1.id, t2.*
  2. FROM for_sale t1
  3. INNER JOIN property t2 ON t2.id = t1.id
  4. WHERE t2.USE = 1
  5. AND t2.TYPE = 2
  6. OR t2.TYPE = 1
  7. OR t2.TYPE = 3
  8. AND t2.STATUS = 0
  9. OR t2.STATUS = 1
  10. OR t2.STATUS = 2
  11. AND t2.bhk = 3
  12. OR t2.bhk = 1
  13. OR t2.bhk = 2
Algo así es lo que quiero que haga. Lo he ejecutado y funciona perfectamente.

¿Abro mejor una pregunta en el subforo PHP?


Título: Re: filtrar contenido con jquery
Publicado por: eLank0 en 18 Noviembre 2015, 17:28 pm
Buenas,

Una consulta sobre tu código, porqué pones \n dentro de una consulta SQL? No será por ahí que te tira un error?

S2


Título: Re: filtrar contenido con jquery
Publicado por: gAb1 en 18 Noviembre 2015, 17:40 pm
Pues ahora que lo dices, al principio se imprimia el \n, pero porque se me olvido cambiar las comillas normales '\n' a dobles "\n". Ahora imprime el salto de linea, ¿eso no debe dar ningún error no? Si se ponen saltos de linea sin variables y no pasa nada. (Lo he quitado y nada, el mismo error sin saltos de linea)

Acaba de encontrar un supuesto error:

Código
  1. SELECT t1.id, t2.*
  2. FROM for_sale t1
  3. INNER JOIN property t2 ON t2.id = t1.id
  4. WHERE t2.USE = 1
  5. AND t2.TYPE = 2
  6. OR t2.TYPE = 3
  7. NULL
  8. NULL
  9. NULL
  10. NULL
  11. LIMIT ?, ?

Podria ser por las variables que se imprimen como NULL, sin embargo le acaba de poner else unset() a los IFs y sigue tirando el mismo error. Ya no sé que más probar...

Código
  1. if ($filter_type != NULL) {
  2.    foreach ($filter_type as $type=>$value) {
  3.        if ($type === 0) {
  4.            $type_cond = 'AND t2.type = ' . $value;
  5.        } else {
  6.            $type_cond .= $type_cond . "\n OR t2.type = " . $value;
  7.        }
  8.    }
  9. } else unset($filter_status); // Ya no imprime NULL dentro del prepare()
  10.  

EDITO: Que extraño, al marcar 3 checkboxes se repite el primero... Puede ser el foreach (que lo dudo) o el array...

Código
  1. MySQLi failed: You have an error IN your SQL syntax; CHECK the manual that corresponds TO your MySQL server version FOR the RIGHT syntax TO USE near 't2.type = 1
  2. OR t2.type = 2AND t2.type = 1AND t2.type = 1
  3. OR t2.type = 2
  4. OR t2' at line 5<br><br />

Esos dos AND despues del primer OR están de más... ¿Como es posible que se este imprimiendo asi?

Al parecer lo del NULL no tiene nada que ver, no se llega a imprimir, asi que esos unset() están de más.


Título: Re: filtrar contenido con jquery
Publicado por: MinusFour en 18 Noviembre 2015, 18:09 pm
Esto ya se vuelve un problema de PHP y MySQL:

Tienes que usar los paréntesis y USE y TYPE son palabras reservadas. Por eso la etiqueta code te resalta en rojo esas palabras:

Código
  1. SELECT t1.id, t2.*
  2. FROM for_sale t1
  3. INNER JOIN property t2 ON t2.id = t1.id
  4. WHERE t2.`USE` = 1
  5. AND (t2.`TYPE` = 2 OR t2.`TYPE` = 1 OR t2.`TYPE` = 3)
  6. AND (t2.`STATUS` = 0 OR t2.`STATUS` = 1 OR t2.`STATUS` = 2)
  7. AND (t2.bhk = 3 OR t2.bhk = 1 OR t2.bhk = 2)

Está es una forma de hacerlo con PHP:

Código
  1. <?php
  2. function agregaCondicionCompuesta($campo, $valores){
  3.    $red = function($acc, $valor) use ($campo){
  4.   $condicion = "$campo = $valor";
  5.   if($acc){
  6.   return $acc . ' OR ' . $condicion;
  7.   } else {
  8.     return $condicion;
  9.   }
  10. };
  11.    $reduced = array_reduce($valores, $red);
  12.    return count($valores) > 1 ? "($reduced)" : $reduced;
  13. }

O una manera más sencilla de verla sin el reduce:

Código
  1. function agregaCondicionCompuesta($campo, $valores){
  2.  $query = '';
  3.  foreach($valores as $index => $valor){
  4.    $condicion = "$campo = $valor";
  5.    if($index == 0){
  6.      $query .= $condicion;
  7.    } else {
  8.      $query .= ' OR ' . $condicion;
  9.    }
  10.  }
  11.  return count($valores) > 1 ? "($query)" : $query;
  12. }

Después simplemente agregas las condiciones compuestas:

Código
  1. $filtros = [0, 1];
  2. $use = [1];
  3. echo agregaCondicionCompuesta('t2.`USE`', $use) . ' AND ' . agregaCondicionCompuesta('t2.`TYPE`', $filtros);
  4. //t2.`USE` = 1 AND (t2.`TYPE` = 0 OR t2.`TYPE` = 1)
  5.  

Claro que esto deja mucho que desear porque te pueden hacer SQL Injection. Puedes usar '?' en lugar de $valor si vas a usar prepared statements:

Código
  1. $condicion = "$campo = ?";

Luego simplemente haces el bind de las variables con un for. Ojo que los parámetros van en orden, y si tienes otros parámetros antes estos tienen que ser puestos primero.

Ultimadamente creo que sería mejor que en lugar de usar múltiples ORs usaras la función IN de MySQL:

Código
  1.    SELECT t1.id, t2.*
  2.    FROM for_sale t1
  3.    INNER JOIN property t2 ON t2.id = t1.id
  4.    WHERE t2.`USE` = 1
  5.    AND t2.`TYPE` IN(1, 2, 3);
  6.    AND t2.`STATUS` IN(0, 1, 2)
  7.    AND t2.bhk IN(1, 2, 3)

Estoy seguro que te puedes imaginar formas de como agregar los valores tu solo a lo que hay dentro de el IN.


Título: Re: filtrar contenido con jquery
Publicado por: gAb1 en 18 Noviembre 2015, 18:33 pm
Tienes razón, debería escapar las palabras reservadas (aunque ese no fuera el problema). El problema estaba en la primera condición, que no dejaba un espacio o un salto de linea y se juntaba "use = 1AND t2.type".

Gracias por las funciones, tenia pensado hacer una cuando funcionara bien. Sobre lo del SQL Injection dudo mucho que se pueda hacer usando enteros  ;D Aunque sería mejor practica usar el bind_param es más trabajo.

Voy a adaptar la función que usa el reduce para usar el IN, yo tambien creo que mejor. Aunque aún estoy pensando en como hacerlo...  :rolleyes: Primero usar IN en lugar de ORs y lo del bind_param... En cuanto lo tenga edito.

Por mi se puede mover al subforo de PHP, donde podrá ser visto por todos.

Gracias!

EDITO: Al final no me hizo falta el array_reduce()

Código
  1. function addQueryCond($col, $values) {
  2.  
  3.    $red = function($acc, $values) use ($col) {
  4.  
  5.        $condition = '';
  6.        $count = count($values);
  7.  
  8.        foreach ($values as $key=>$value) {
  9.            if ($key === 0) {
  10.                $condition = "\nAND ". $col . ' IN(?';
  11.                if ($count === 1) {
  12.                    $condition .= . $condition . ')';
  13.                }
  14.            } else if ($key < $count) {
  15.                $condition .= $condition . ', ?';
  16.            } else {
  17.                $condition .= $condition . ', ?)';
  18.            }
  19.        }
  20.    };
  21.  
  22.    return array($condition, $count); // count para el bind_param
  23. }
  24.  

Luego para hacer el bind_param()... No lo tengo claro aun xD

EDITO 2: A ver no se me ocurre como reducir esto...

Código
  1. if ($filter_type != NULL) {
  2.    $data = addQueryCond('t2.`type`', $filter_type);
  3.    $type_cond = $data[0];
  4.    $type_count = $data[1];
  5. }
  6.  
  7. if ($filter_status != NULL) {
  8.    $data = addQueryCond('t2.`status`', $filter_status);
  9.    $status_cond = $data[0];
  10.    $status_count = $data[1];
  11. }
  12.  
  13. if ($filter_bhk != NULL) {
  14.    $data = addQueryCond('t2.bhk', $filter_bhk);
  15.    $bhk_cond = $data[0];
  16.    $bhk_count = $data[1];
  17. }
  18.  
  19. if (isset($type_count)) {
  20.    foreach($type_count as $count) {
  21.        $var = 'type' . $count;
  22.        foreach($filter_type as $filter) {
  23.            $$var = $filter;
  24.        }
  25.    }
  26. }
  27. if (isset($status_count)) {
  28.    foreach($status_count as $count) {
  29.        $var = 'status' . $count;
  30.        foreach($filter_status as $filter) {
  31.            $$var = $filter;
  32.        }
  33.    }
  34. }
  35. if (isset($bhk_count)) {
  36.    foreach($bhk_count as $count) {
  37.        $var = 'bhk' . $count;
  38.        foreach($filter_bhk as $filter) {
  39.            $$var = $filter;
  40.        }
  41.    }
  42. }

Al parecer con PDO se puede hacer mucho más facilmente lo que quiero, voy a mirar como funciona.