2011/11/23

jQuery cssHook rotate3D

Los cssHooks de jQuery me parecen el no va más en simplificación y hacen un gran trabajo para hacer uniforme el comportamiento de los CSS a través de los distintos navegadores.

Para el que no sepa qué es un cssHook, le remito a la documentación de jQuery (http://api.jquery.com/jQuery.cssHooks/), pero en pocas palabras es la posibilidad de definir y usar propiedades CSS inventadas (o no) de modo que se puedan leer y ficjar sus valores, ejecutando un código concreto en este caso, claro.

Sirven para o crear nuevas propiedades CSS que hagan cosas maravillosas (este caso) o hacer compatibles distintos navegadores usando una misma propiedad CSS en todos ellos. P.e. con ellos se puede usar la propiedad border radius en IE6 y funcionará igual que en cualquier otro navegador.

Este cssHook, realiza una rotación isométrica de un elemento HTML. Se puede usar directamente mediante el método .css() de jQuery (tanto para hacer el set como para el get), así como parte de una llamada al método .animate() en todas sus variantes. El ángulo es en grados y se permiten tanto valores positivos como negativos así como expresiones de jQuery tipo '+=180deg', que lo que hará es sumarle 180 grados a la rotación actual.

Como un ejemplo es mejor que mil palabras, os pongo el jsfiddle del cssHook para que podáis probarlo en directo. Haced click en la carta para darle la vuelta. podía haber usado la imagen de una moneda, pero he preferido la de una carta.

Importante!
Dado que éste cssHook usa la propiedad CSS3 transform, para simplificar su uso sin tener que recurrir a transformaciones de matrices, usa a su vez otro cssHook que se encarga de hacer más sencilla la definición de la propiedad transform. En este caso, se ha usado el cssHook de brandonaaron/Louis Rémi pero hay otras alternativas aunque posiblemente, menos elegantes.

Extra!
Además del rotate3D, se define también otro cssHook llamado "flipped" para el control de cuándo se pasa de una cara a la otra al realizar la rotación.

Este cssHook, se encarga de mantener el estado de qué cara del elemento se está mostrando. Si la propiedad css 'flipped' es false, es la parte de adelante, si es false, la de atrás. El valor de la propiedad se cambia automáticamente cuando se gira el elemento al llegar a los 90 y a los 270 grados (módulo 360, como es lógico).

Asimismo, definiendo el método $.fn.flippedEvent, se consigue un callback en el momento que se produce el cambio de cara por ejemplo, para cambiar la imagen que se muestra.
$.fn.flippedEvent = function( elem, isFlipped ) { <yourCode> };

Y ahora, la versión minimizada de todo el código, que llega a unos ridículos 500bytes!!!
(function(b){if(!b.cssHooks)throw"jQuery 1.4.3 or above is required for this plugin to work";b.fn.flippedEvent=null;b.cssNumber.flipped=!0;b.cssHooks.flipped={get:function(a){return a.style.flipped||!1},set:function(a,d){var c=/true/i.test(d);"function"===typeof b.fn.flippedEvent&&b.fn.flippedEvent.flipEvent(a,c);a.style.flipped=c}};b.cssNumber.rotate3D=!0;b.cssHooks.rotate3D={get:function(a){return a.style.rotate3D},set:function(a,d){d=parseFloat(d);var c=d;if(c!=parseFloat(a.style.rotate3D)){var i=
0<=c?1:-1,f=Math.floor(Math.abs(c)),j=(180<=f%360?-1:1)*(1-f%180/90),g=b(a),e=parseInt(a.style.rotate3D),h=e<c?!0:!1,e=Math.abs(e)%180,c=Math.abs(parseInt(c))%180;(h&&90>e&&90<=c||!h&&90<e&&90>=c)&&g.css("flipped",!g.css("flipped"));g.css("transform","skew(0deg, "+i*f+"deg) scale("+j+", 1)")}a.style.rotate3D=d}};b.fx.step.rotate3D=function(a){b.cssHooks.rotate3D.set(a.elem,a.now,a.unit)}})(jQuery);

Código completo del cssHook. Como podréis ver, hace falta muy poco código para hacer que todo funcione.
/**
 * rotate3D/flipped jQuery cssHook v0.1
 * ==========================================================
 * (C) 2011 José Ramón Díaz - jrdiazweb@gmail.com
 *
 * Elegant and simple cssHooks to perform 3D-like css rotations and control 
 * visible side. 
 * 
 * REQUIRES.
 * A cssHook/code make transform CSS3 property work converting human readable 
 * values into its transformation matrix. In my case i've used the transform 
 * cssHooks from Louis-Rémi Babé to make it compatible, but you are free to use 
 * whatever you want meanwhile makes transform work.  
 *     https://github.com/brandonaaron/jquery-cssHooks
 *     https://github.com/louisremi/jquery.transform.js
 * 
 * CSS properties use the grammar:
 *     - "rotate3D: <n>[deg]"
 *     - "flipped: true|false"
 * 
 * When an element is rotated, flipped CSS property is set according the 
 * element orientation. Side changes occurs on 90/270 degrees.
 * 
 * Side change event callback can be used defining the function $.fn.flippedEvent. 
 *     $.fn.flippedEvent = function( elem, isFlipped ) { <yourcode> };
 * 
 * Examples:
 *     - elem.css('rotate3D')      - Gets current rotation
 *     - elem.css('rotate3D', 180) - Rotates element 180 degrees
 *     - elem.animate({ rotate3D, -180 }, 1000);    - Animates element from current rotation to -180 degrees
 *     - elem.animate({ rotate3D, "+=360" }, 1000); - Animates element adding 360 degrees to its current rotation
 *     - $('.c').animate({'rotate3D': '+=180'}, 2000, 'easeOutBounce') - Animates using easing function 'easeInOutBounce' defined in jQueryUI
 *
 * Browser copatibility.
 * Any modern CSS3 compatible browser.
 * 
 * NOTE: cssHooks needs jQuery v1.4.3 or greater.
 * 
 * Inspired by zachstronaut's rotate3Di
 *     http://www.zachstronaut.com/projects/rotate3di/
 * 
 * Legal stuff
 *     You are free to use this code, but you must give credit and/or keep header intact.
 *     Please, tell me if you find it useful. An email will be enough.
 *     If you enhance this code or correct a bug, please, tell me.
 */
(function ($) {
 // Check if cssHooks are supported
 if(!$.cssHooks) { throw( "jQuery 1.4.3 or above is required for this plugin to work" ); return; }

 ///////////////////////////////////////////////////////////////////////////
   // 'flipped' cssHook. Property has the format: "flipped: true|false"
   $.fn.flippedEvent = null; // Event function called when changing the side: "function( elem, isFlipped? ) {}"
 $.cssNumber['flipped'] = true; // Special units. None in this case
 $.cssHooks['flipped']  = {
  
  get      : function( elem, computed, extra ) { return elem.style['flipped'] || false; },
  set      : function( elem, value ) { 
      var val = /true/i.test(value); // Boolean conversion
      if( typeof $.fn.flippedEvent === "function" )
       $.fn.flippedEvent.flipEvent( elem, val );
      elem.style['flipped'] = val;  
     }
  
 };
        
 ///////////////////////////////////////////////////////////////////////////
 // 'rotate3D' csshook. Property has the format: "rotate3D: <n>deg|rad"
 $.cssNumber['rotate3D'] = true; // Special units support 'deg', 'rad', etc
 $.cssHooks['rotate3D']  = {
  
  get: function( elem, computed, extra ) { return elem.style['rotate3D']; },
  set: function( elem, value, unit ) {
      value = parseFloat( value );
      apply3DRotation( elem, value, unit );
      elem.style['rotate3D'] = value;
     }
        
 };

 // Animation step function for 'rotate3D' custom CSS property
 $.fx.step['rotate3D'] = function ( fx ) { $.cssHooks['rotate3D'].set( fx.elem, fx.now, fx.unit ); };

 // Performs 3D rotation on element
 var apply3DRotation = function( elem, value, unit ) {

  if( value == parseFloat( elem.style['rotate3D'] ) ) return; // No changes

  var dir   = value >= 0 ? 1 : -1;
  var deg   = Math.floor( Math.abs( value ) );
  var scale = ( ( deg % 360 ) >= 180 ? -1 : 1 ) * ( 1 - ( deg % 180 ) / 90 );

  var el  = $(elem); // Caches jQuery element to speed up execution

  var old = parseInt( elem.style['rotate3D'] );
  var inc = old < value ? true : false; 

  var before = Math.abs( old ) % 180;
  var after  = Math.abs( parseInt( value ) ) % 180;

  // Side change control
  if(  inc && before < 90  && after >= 90 || !inc && before > 90  && after <= 90 )
   el.css('flipped', !el.css('flipped') );

  // Performs CSS transform
  el.css( 'transform', 'skew(0deg, ' + dir * deg + 'deg) ' +
                 'scale(' + scale + ', 1)'
  );
 };

})(jQuery);

2011/11/15

Sesiones PHP a lo campestre III. No se puede hacer más lento.

Parafraseando al famoso mago (por si no lo conocéis es manco y hace trucos de cartas con una única mano), voy a darle la vuelta de tuerca definitiva a este método de obtener la información de sesión sin abrir la sesión.

Una vez que ya sabemos dónde están los ficheros de sesión tal y como se ha visto en las entregas anteriores, podemos simular de forma idéntica a como hace PHP la carga de la información de la sesión en una matriz asociativa.

El primer paso es cargar los contenidos del fichero de la sesión.
$fname    = session_save_path() . "/sess_" . $sid;
$_session = unserializeSession( file_get_contents( $fname ) );

Y para ello, se usa la siguiente función. Si es que es demasiado fácil...
///////////////////////////////////////////////////////////////////////////////
/**
 * unserializeSession - Unserialize para ficheros de sesión
 *
 * Convierte una cadena que se corresponde con los contenidos de un fichero de sesión en un array 
 * asociativo con las claves y valores de la sesión correspondiente.
 *
 * @access    public
 * @link      http://www.php.net/manual/en/function.session-decode.php#101687
 *
 * @param     string  $data  Cadena de texto del fichero de la sesión
 * @return    array          Array asociativa con las claves y valores
 */
function unserializeSession( &$data )
{
       if(  strlen( $data) == 0)
       {
              return array();
       }

       // match all the session keys and offsets
       preg_match_all('/(^|;|\})([a-zA-Z0-9_]+)\|/i', $data, $matchesarray, PREG_OFFSET_CAPTURE);

       $returnArray = array();

       $lastOffset = null;
       $currentKey = '';
       foreach ( $matchesarray[2] as $value )
       {
              $offset = $value[1];
              if(!is_null( $lastOffset))
              {
                     $valueText = substr($data, $lastOffset, $offset - $lastOffset );
                     $returnArray[$currentKey] = unserialize($valueText);
              }
              $currentKey = $value[0];

              $lastOffset = $offset + strlen( $currentKey )+1;
       }

       $valueText = substr($data, $lastOffset );
       $returnArray[$currentKey] = unserialize($valueText);

       return $returnArray;
}

2011/11/14

Colocando el cursor con javascript

Seguro que alguna vez hemos querido colocar el cursor de un cuadro de texto o un textarea para cualquier cosa que se nos haya ocurrido, la pega es que no hay métodos nativos para ello, pero jugando un poco con los rangos de selección, es fácil de hacer.

A continuación os pongo dos pequeñas funciones para conseguir el setter/getter de la posición del cursor. Es una "tontá", pero es efectiva. Además si lo minimizas ocupa una miseria.

function getCursorPosition( elem ) {

    var CaretPos = 0;
    // IE Support
    if( document.selection ) {

        elem.focus();
        var Sel = document.selection.createRange();

        Sel.moveStart( 'character', -elem.value.length );

        CaretPos = Sel.text.length;
    }
    // Firefox support
    else if ( elem.selectionStart || elem.selectionStart == '0' )
        CaretPos = elem.selectionStart;

    return( CaretPos );
}


function setCursorPosition( elem, pos )
{
    if( elem.setSelectionRange )
    {
        elem.focus();
        elem.setSelectionRange( pos,pos );
    }
    else if( elem.createTextRange ) {
        var range = elem.createTextRange();
        range.collapse( true );
        range.moveEnd( 'character', pos );
        range.moveStart( 'character', pos );
        range.select();
    }
}