2011/10/27

jQuery Canvas Minimap

Y para rematar la semana, la joya de la corona. Otro plugin hecho por mí que toma un contenedor y genera un minimapa de éste y que pese a basarse en otro ya existente llamado Fracs, le da un giro de tuerca y lo hace más flexible.

El principio de este plugin es tomar un contenedor del que se quiere hacer un minimapa e indicando un canvas de destino, pinta sobre él los elementos cuyos selectores hayamos definido usando el estilo que deseemos.

Usarlo es lo más sencillo del mundo. Dados un canvas #myCanvas y un contenedor
var minimapInstance = $('#myCanvas').minimap( $('myContainer') [, options] );

Las opciones son opcionales, pero recomendables pues es donde se definen qué elementos se representarán en el minimapa y cómo se hará. Concretamente, la encargada de ello es la propiedad "style" que  es un array de objetos con la siguiente estructura:
styleDef {
       // selector to use for that style
       selector: String / HTMLElement / [HTMLElement, ...] / jQuery

       // border stroke width
       strokeWidth: float / undefined / 'auto'

       // border color
       strokeStyle: String / undefined / 'auto'

       // fill color
       fillStyle: String / undefined / 'auto'
 }

Como una imagen vale más que mil palabras, como es usual, el repositorio del código está en mi jsFiddle. Solo hace falta el código javascript.

El código completo es éste
/**
 * jquery.minimap-0.1.js - Container Minimap Plugin
 * ==========================================================
 * (C) 2011 José Ramón Díaz - jrdiazweb@gmail.com
 *
 * http://3nibbles.blogspot.com/jquery-canvas-minimap.html
 * http://plugins.jquery.com/project/CanvasMinimap
 *
 * Container Minimap is a plugin that creates a minimap of the desired container
 * scaled down and styled so you can highlight the overall position of the
 * contents of the container element.
 *
 * Scale is calculated based on the minumum of width or height of the canvas used
 * to draw the minimap. So, using only CSS or attributes you can modify the scale
 * adjusting it to the desired size of the container.
 *
 * INSTANTIATION
 * Call the minimap method over the selector of the canvas where the minimap is
 * going to be drawn
 *
 *     minimapInstance = $('#myCanvas').minimap( $('myContainer') [, options] );
 *
 * The only HTML needed is the target canvas and the container to be drawn.
 *
 * OPTIONS
 *     - container : document.    Defaults to whole page. Otherwise is a
 *                                selector of the desired container to be mapped.
 *     - styles    : [ styleDef, ... ]    array with the styles to apply.
 *     - viewportStyle : styleDef. Style of the viewport.
 *     - viewportDragStyle: styleDef. Style of the viewport
 *
 * The OutlineStyle style must be in the following format (Same format than Fracs):
 *
 *     styleDef {
 *         // selector to use for that style
 *         selector: String / HTMLElement / [HTMLElement, ...] / jQuery
 *
 *         // border stroke width
 *         strokeWidth: float / undefined / 'auto'
 *
 *         // border color
 *         strokeStyle: String / undefined / 'auto'
 *
 *         // fill color
 *         fillStyle: String / undefined / 'auto'
 *     }
 *
 * strokeWidth, strokeColor, fillColor may have the special values
 * undefined to ignore and 'auto' to fetch values from css.
 *
 * PUBLIC API
 *     - $.minimap.redraw()    Redraws the minimap
 *     - drag                  Boolean that indicates that the viewport is being dragged
 *
 * Legal stuff
 *     Based on jQuery plugin named Fracs, but modified to allow minimapping of a
 *     desired container and some other enhancements.
 *
 *     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( $ ) {

 var $window   = $(window)
   , $document = $(document)
   ;

 ////////////////////////////////////////////////////////////////////////////////
    Rect = function (left, top, width, height) {

        if (!(this instanceof Rect)) {
            return new Rect(left, top, width, height);
        }

        this.left   = Math.round( left   );
        this.top    = Math.round( top    );
        this.width  = Math.round( width  );
        this.height = Math.round( height );
        this.right  = this.left + this.width;
        this.bottom = this.top  + this.height;
    };

    Rect.prototype = {
        equals: function( that ) {
            return this.left === that.left && this.top === that.top && this.width === that.width && this.height === that.height;
        },

        area: function() {
            return this.width * this.height;
        },

        intersection: function( rect ) {
            var left   = Math.max( this.left  , rect.left   ),
                right  = Math.min( this.right , rect.right  ),
                top    = Math.max( this.top   , rect.top    ),
                bottom = Math.min( this.bottom, rect.bottom ),
                width  = right  - left,
                height = bottom - top;

            return ( width >= 0 && height >= 0 ) ? Rect( left, top, width, height ) : undefined;
        },

        envelope: function( rect ) {
            var left   = Math.min( this.left  , rect.left   ),
                right  = Math.max( this.right , rect.right  ),
                top    = Math.min( this.top   , rect.top    ),
                bottom = Math.max( this.bottom, rect.bottom ),
                width  = right  - left,
                height = bottom - top;

            return Rect( left, top, width, height );
        }
    };


    /**
     * Special constructors
     */
    Rect.ofDocument = function() {
        return Rect( 0, 0, $document.width(), $document.height() );
    };

    Rect.ofViewport = function() {
        return Rect( $window.scrollLeft(), $window.scrollTop(), $window.width(), $window.height() );
    };

    Rect.ofElement = function( element ) {
        var $element = $(element)
          , offset;

        if( !$element.is(":visible") ) {
            return Rect( 0, 0, -1, 0 );
        }

        offset = $element.offset();
        return Rect( offset.left, offset.top, $element.outerWidth(), $element.outerHeight() );
    };

    Rect.ofElementOS = function( element, contOffset ) {
        var $element = $(element)
          , offset;

        if( typeof contOffset === 'undefined' || !contOffset ) contOffset = { left: 0, top: 0 };
        if( !$element.is(":visible") ) {
            return Rect( 0, 0, -1, 0 );
        }

        offset = $element.offset();
        return Rect( offset.left - contOffset.left, offset.top - contOffset.top, $element.outerWidth(), $element.outerHeight() );
    };


 ////////////////////////////////////////////////////////////////////////////////
    // Instantiation
    $.fn.minimap = function( container, options ) {

     var contOffset;

     // Private members
     function MiniMap( canvas, container, options ) {

         if ( !(this instanceof MiniMap) ) {
             return new MiniMap( canvas, container, options );
         }

         if ( !canvas.nodeName || canvas.nodeName.toLowerCase() !== "canvas" ) {
             return undefined;
         }

         var me = this
           , $canvas    = $(canvas)
           , $container = $(container) || document
           , width      = $canvas.width()  || $canvas.attr("width")
           , height     = $canvas.height() || $canvas.attr("height")
           , context    = canvas.getContext("2d")
           , drag       = false
           , vpRect
           , scale
           ;

         options = $.extend( {}, $.fn.minimap.defaults, options );

      // ========================================================================
      // Public Members
         this.redraw = function() {
          // Gets the container offset to transform contents position
          contOffset = $(container).offset();
          if( !contOffset ) contOffset = { left: 0, top: 0 };

          // Gets the Rect of the container
          docRec = null;
    if( $container === document )
     docRect = Rect.ofDocument();
    else
     docRect = Rect.ofElement( $container );
    vpRect = Rect.ofViewport();
    scale = Math.min(width / docRect.width, height / docRect.height);

    // Scales canvas on size and on inner transform
    $canvas.height( docRect.height * scale );
    $canvas.width(  docRect.width  * scale );

    context.setTransform( 1, 0, 0, 1, 0, 0 );
    context.clearRect( 0, 0, $canvas.width(), $canvas.height() );

    //context.scale( scale, scale ); // WRONG! deforms the projection
    context.scale( width / docRect.width, height / docRect.height );

    // Draws the minimap
    applyStyles();
    //drawViewport();
         },

         drawRect = function( rect, strokeWidth, strokeStyle, fillStyle, invert ) {
          // Draws a Rect on canvas
             if ( strokeStyle || fillStyle ) {
                 if (fillStyle) {
                     context.beginPath();
                     if (invert) {
                         context.rect( 0, 0, docRect.width, rect.top );
                         context.rect( 0, rect.top, rect.left, rect.height );
                         context.rect( rect.right, rect.top, docRect.right - rect.right, rect.height );
                         context.rect( 0, rect.bottom, docRect.width, docRect.bottom - rect.bottom );
                     } else {
                         context.rect( rect.left, rect.top, rect.width, rect.height );
                     }
                     context.fillStyle = fillStyle;
                     context.fill();
                 }
                 if (strokeStyle) {
                     context.beginPath();
                     context.rect( rect.left, rect.top, rect.width, rect.height );
                     context.lineWidth = scale ? Math.max( strokeWidth, 0.2 / scale ) : strokeWidth;
                     context.strokeStyle = strokeStyle;
                     context.stroke();
                 }
             }
         },

         drawElement = function( element, strokeWidth, strokeStyle, fillStyle ) {
          // Gets the Rect of an element and draws it on canvas
             var $element = $(element),
                 rect = Rect.ofElementOS( element, contOffset );
              //rect = Rect.ofElement( element );

             if ($element.css("visibility") === "hidden" || rect.width === 0 || rect.height === 0) {
                 return;
             }

             strokeWidth = strokeWidth === "auto" ? parseInt($element.css("border-top-width"), 10) : strokeWidth;
             strokeStyle = strokeStyle === "auto" ? $element.css("border-top-color") : strokeStyle;
             fillStyle   = fillStyle   === "auto" ? $element.css("background-color") : fillStyle;
             drawRect(rect, strokeWidth, strokeStyle, fillStyle);
         },

         applyStyles = function () {
          // Loops through the contents of the container
             $.each( options.styles, function (idx, style) {
                 $(style.selector).each(function () {
                     drawElement( this, style.strokeWidth, style.strokeStyle, style.fillStyle );
                 });
             });
         },

         drawViewport = function () {
             var style = drag && options.viewportDragStyle ? options.viewportDragStyle : options.viewportStyle;

             drawRect( vpRect, style.strokeWidth, style.strokeStyle, style.fillStyle, options.invertViewport );
         };

         me.redraw();
     }

        return new MiniMap( this.get(0), container, options );
    };

    // ========================================================================
    // Minimap options defaults
    $.fn.minimap.defaults = {
        container  : document,        // Defaults to whole page. Otherwise is a selector of the container
        styles     : [{               // Sample styles
                      selector: "header,footer,section,article",
                      fillStyle: "rgb(230,230,230)"
                     }, {
                      selector: "h1",
                      fillStyle: "rgb(240,140,060)"
                     }, {
                      selector: "h2",
                      fillStyle: "rgb(200,100,100)"
                     }, {
                      selector: "h3",
                      fillStyle: "rgb(100,200,100)"
                     }, {
                      selector: "h4",
                      fillStyle: "rgb(100,100,200)"
                     }]
    };

}(jQuery));

Y el código minimizado que llega a ocupar menos de 1.5Kb es
/** !jquery.minimap-0.1.js - Container Minimap Plugin
 * (C) 2011 José Ramón Díaz - jrdiazweb@gmail.com
 * http://3nibbles.blogspot.com/jquery-canvas-minimap.html | http://plugins.jquery.com/project/CanvasMinimap
 *
 * Based on jQuery plugin named Fracs, but modified to allow minimapping of a desired container and some other enhancements.
 * You are free to use this code, but you must give credit and/or keep header intact.
 */
(function(f){var h=f(window),j=f(document);Rect=function(a,b,c,e){if(!(this instanceof Rect))return new Rect(a,b,c,e);this.left=Math.round(a);this.top=Math.round(b);this.width=Math.round(c);this.height=Math.round(e);this.right=this.left+this.width;this.bottom=this.top+this.height};Rect.prototype={equals:function(a){return this.left===a.left&&this.top===a.top&&this.width===a.width&&this.height===a.height},area:function(){return this.width*this.height},intersection:function(a){var b=Math.max(this.left,
a.left),c=Math.min(this.right,a.right),e=Math.max(this.top,a.top),a=Math.min(this.bottom,a.bottom);c-=b;a-=e;return c>=0&&a>=0?Rect(b,e,c,a):void 0},envelope:function(a){var b=Math.min(this.left,a.left),c=Math.max(this.right,a.right),e=Math.min(this.top,a.top),a=Math.max(this.bottom,a.bottom);return Rect(b,e,c-b,a-e)}};Rect.ofDocument=function(){return Rect(0,0,j.width(),j.height())};Rect.ofViewport=function(){return Rect(h.scrollLeft(),h.scrollTop(),h.width(),h.height())};Rect.ofElement=function(a){var a=
f(a),b;if(!a.is(":visible"))return Rect(0,0,-1,0);b=a.offset();return Rect(b.left,b.top,a.outerWidth(),a.outerHeight())};Rect.ofElementOS=function(a,b){var c=f(a),e;if(typeof b==="undefined"||!b)b={left:0,top:0};if(!c.is(":visible"))return Rect(0,0,-1,0);e=c.offset();return Rect(e.left-b.left,e.top-b.top,c.outerWidth(),c.outerHeight())};f.fn.minimap=function(a,b){function c(a,b,i){if(!(this instanceof c))return new c(a,b,i);if(a.nodeName&&a.nodeName.toLowerCase()==="canvas"){var g=f(a),h=f(b)||document,
j=g.width()||g.attr("width"),l=g.height()||g.attr("height"),d=a.getContext("2d"),m,k,i=f.extend({},f.fn.minimap.defaults,i);this.redraw=function(){(e=f(b).offset())||(e={left:0,top:0});docRec=null;docRect=h===document?Rect.ofDocument():Rect.ofElement(h);m=Rect.ofViewport();k=Math.min(j/docRect.width,l/docRect.height);g.height(docRect.height*k);g.width(docRect.width*k);d.setTransform(1,0,0,1,0,0);d.clearRect(0,0,g.width(),g.height());d.scale(j/docRect.width,l/docRect.height);applyStyles()};drawRect=
function(a,b,c,e,f){if(c||e){if(e)d.beginPath(),f?(d.rect(0,0,docRect.width,a.top),d.rect(0,a.top,a.left,a.height),d.rect(a.right,a.top,docRect.right-a.right,a.height),d.rect(0,a.bottom,docRect.width,docRect.bottom-a.bottom)):d.rect(a.left,a.top,a.width,a.height),d.fillStyle=e,d.fill();if(c)d.beginPath(),d.rect(a.left,a.top,a.width,a.height),d.lineWidth=k?Math.max(b,0.2/k):b,d.strokeStyle=c,d.stroke()}};drawElement=function(a,b,c,d){var g=f(a),a=Rect.ofElementOS(a,e);g.css("visibility")==="hidden"||
a.width===0||a.height===0||(b=b==="auto"?parseInt(g.css("border-top-width"),10):b,c=c==="auto"?g.css("border-top-color"):c,d=d==="auto"?g.css("background-color"):d,drawRect(a,b,c,d))};applyStyles=function(){f.each(i.styles,function(a,b){f(b.selector).each(function(){drawElement(this,b.strokeWidth,b.strokeStyle,b.fillStyle)})})};drawViewport=function(){var a=i.viewportStyle;drawRect(m,a.strokeWidth,a.strokeStyle,a.fillStyle,i.invertViewport)};this.redraw()}}var e;return new c(this.get(0),a,b)};f.fn.minimap.defaults=
{container:document,styles:[{selector:"header,footer,section,article",fillStyle:"rgb(230,230,230)"},{selector:"h1",fillStyle:"rgb(240,140,060)"},{selector:"h2",fillStyle:"rgb(200,100,100)"},{selector:"h3",fillStyle:"rgb(100,200,100)"},{selector:"h4",fillStyle:"rgb(100,100,200)"}]}})(jQuery);

Éste ha quedado chulo, ein?

No significa nada, pero me hace ilu!

Mi plugin de WizardSteps está el número 3 de la lista de los 10 plugins de navegación más creativos según script-tutorials.com

http://www.script-tutorials.com/10-most-creative-jquery-navigation-plugins/

Y también se me nombra en otros sitios en posts con el apelativo de "superb" :D

http://www.multyshades.com/2011/08/30-superb-jquery-plugins-for-dropdown-navigation-menus/

Ya se que no es un ranking mundial ni nada de eso, pero siempre es agradable algo de reconocimiento, no?

Detectando si un contenedor tiene barras de scroll

Hola, hoy toca algo un poco más ligero que la entrada de ayer y os comparto otro mini-plugin de jQuery para detectar si un elemento tiene barras de scroll o no. No es mío, pero lo importante es que es diminuto y funciona a la perfección.

/* Plugin to test if the element has vertical scrollbar or not */
(function($) {
 $.fn.hasScrollBar = function() { return this.get(0).scrollHeight > this.height(); }
})(jQuery);

Para invocarlo, nada más sencillo que llamarlo sobre el selector del elemento a comprobar y devuelve tru o false.

Por ejemplo si tenemos un div llamado #miDiv, sería:  $('#miDiv').hasScrollBar();

Sencillo, no?, pues preparaos, que en breve viene el próximo macro-plugin jQuery by me (o al menos, mayormente jejejejeje).

2011/10/26

Plugin jQuery Sliding Panel

Hola, hoy os presento uno de los plugins de jQuery que he hecho y que os he prometido para esta semana. Hablamos de la continuación del Slide Button ya que comparte gran parte de la apariencia y funcionalidad, pero aplicado a paneles en vez de a botones.


Este plugin está basado en uno denominado jqEasy (un saludo!), pero con cuyas tripas no estoy de acuerdo, ya que arrastraba varios fallos, no seguía las recomendaciones de desarrollo de plugins, definiciones CSS incompletas y memory leaks graves, así que opté por reescribirlo todo desde cero, tanto el CSS como javascript adaptándolo a la apariencia de los slide buttons (lo cual no quiere decir que mi código sea infalible, solo que los errores que detecté han sido corregidos).

Usarlo es lo más sencillo del mundo, ya que solo hay que llamar a la función slidePanel() sobre el contenedor del widget, que tiene que tener una mínima estructura que os detallaré más adelante. La única particularidad es que si se quiere a la izquierda no hay que hacer nada, pero si se quiere a la derecha hay que anadir la clase ".right" al contendor (o usar las propiedades del plugin).

Por defecto también considera que está "fixed" a la página, pero cambiando la propiedad position del CSS (o del objeto jQuery) a "relative" o "absolute", se cambia el comportamiento del widget radicalmete. Para más detalles podéis comprobar el ejemplo del jsFiddle, donde hay ejemplos con las 6 combinaciones posibles.

También es fácilmente integrable con jQueryUI (los estilos aún no, lo siento), pudiéndose usar dentro de una pila de llamadas para añadirle comportamiento con draggable() o modificando las funciones de despliegue del panel mediante las propiedades del objeto jQuery.

Debe funcionar en todos los navegadores, incluido el IE6 (aunque no soporta la propiedad CSS "position: fixed" sustituyéndose por "position: absolute" de forma automática). Evidentemente, mientras más "moderno" sea el navegador, más bonito se verá (transparencias, esquinas redondeadas, etc), pero eso es cosa de CSS y no de código.

Sin más demoras, os dejo el jsF


iddle, donde está todo el código, css y ejemplos necesarios. El código está comentado así como documentado así que no deberíais tener mucho problema. Si encontrarais un bug, no dudéis en comunicármelo. Alternativamente, podéis visitar la página del plugin en la página oficial de jQuery.


Y para los más impacientes, el código completo:

Estructura básica HTML
<div class="slidePanelWidget" id="widget1">
    <a class="slidePanelButton" href="">&nbsp;Widget 1</a>
    <div class="slidePanel panel">

        These are the contents of the widget!
    </div>
</div>

Código javascript minimizado. Llega a ocupar un mínimo de unos 900 bytes. Quién pide más?
/*! jquery.slidePanel-0.1.js - Slide Panel plugin for jQuery
* ==========================================================
* (C) 2011 José Ramón Díaz - jrdiazweb@gmail.com - v.0.1.0 (25/10/2010)
* Requires: jQuery v1.4.3+
* http://3nibbles.blogspot.com/2011/10/plugin-jquery-sliding-panel.html
* 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(b){function f(c,d){var g=b.browser.msie&&b.browser.version=="6.0",a=this;a.opts=d;a.widget=c;a.button=a.widget.find(".slidePanelButton");a.panel=a.widget.find(".slidePanel");a.left=a.widget.hasClass("right")?false:true;if(g)a.opts.position=a.opts.position=="fixed"?"absolute":a.opts.position;a.opts.position&&a.button.css("position",a.opts.position);a.opts.buttonTop&&a.button.css("top",a.opts.buttonTop);a.opts.panelTop&&a.panel.css("top",a.opts.panelTop);a.button.attr("href","javascript:void(0)").click(function(){a.opts.ajax?
a.panel.is(":visible")?a.toggle():a.panel.load(a.opts.ajax,function(b,c){c!=="success"&&a.panel.html("Sorry, but there was an error loading the document.

");a.toggle()}):a.toggle();return false});a.deployed=false;a.toggle=function(b){if(typeof open==="boolean")a.deployed=b;a.deployed?a.close():a.open()};var e=0;a.open=function(){a.deployed=true;var b=a.button.width();b&&(oldPaddingR=a.button.css("padding-right"),oldPaddingL=a.button.css("padding-left"),e=b);a.button.addClass("deployed");a.left?
a.button.animate({width:"0px","padding-right":"0px"},a.opts.speed,a.opts.animFunction):a.button.animate({width:"0px","padding-right":"0px","padding-left":oldPaddingR},a.opts.speed,a.opts.animFunction);a.panel.toggle(a.opts.speed,a.opts.animFunction,function(){});a.opts.clickToClose&&a.enableCloseFn()};a.close=function(){a.deployed=false;a.panel.toggle(a.opts.speed,a.opts.animFunction,function(){});a.left?a.button.animate({width:e+"px","padding-right":oldPaddingR},a.opts.speed,a.opts.animFunction):
a.button.animate({width:e+"px","padding-right":oldPaddingR,"padding-left":oldPaddingL},a.opts.speed,a.opts.animFunction);a.button.removeClass("deployed");a.opts.clickToClose&&a.disableCloseFn()};a.stopCloseFn=function(a){a.stopPropagation()};a.enableCloseFn=function(){b(document).bind("click",a.close);a.panel.bind("click",a.stopCloseFn)};a.disableCloseFn=function(){b(document).unbind("click",a.close);a.panel.unbind("click",a.stopCloseFn)}}b.fn.slidePanel=function(c){var d=b.extend({},{position:null,
buttonTop:null,panelTop:null,animFunction:"swing",speed:"fast",ajax:null,clickToClose:true},c);return!this.data("slidePanel")||typeof c=="object"?this.each(function(){var c=b(this);c.data("slidePanel",new f(c,d))}):this.data("slidePanel")}})(jQuery);

Código javascript completo
/*! jquery.slidePanel-0.1.js - Slide Panel plugin for jQuery
 * ==========================================================
 * (C) 2011 José Ramón Díaz - jrdiazweb@gmail.com
 * Version: 0.1.0 (25/10/2010)
 * Requires: jQuery v1.4.3+
 *
 * http://3nibbles.blogspot.com/2011/10/plugin-jquery-sliding-panel.html
 *
 * INSTANTIATION
 * The basic structure is a div indicating on his class if it must be at
 * left (default, no extra class needed) or right (".right" class needed).
 * Inside the div there must be an <a class="slidePanelButton"> and a
 * <div class="slidePanel">. That's all.
 *
 * Manual instantiation
 *     $('.slidePanelWidget').slidePanel( [ options_object ] );
 *
 * Optionally if you use jQueryUI, you can add draggable to it with
 *     $('.slidePanelWidget').slidePanel( [ options_object ] ).draggable( { axis: 'y' } );
 *
 * OPTIONS
 *     - position     : null null (CSS position) / 'fixed' / 'absolute'
 *     - buttonTop    : null top position of the button inside slidePanelWidget. Null = CSS specified.
 *     - panelTop     : null top position of the panel inside slidePanelWidget. Null = CSS specified.
 *     - animFunction : 'swing' Animation function used to deploy panel
 *     - speed        : 'fast' Animation speed
 *     - ajax         : null URL of th AJAX call to perform to load panel contents. It is loaded before deploying the panel.
 *     - clickToClose : true True = click anywhere to close the panel
 *
 * API
 *     - toggle()  Toggles open/close panel state
 *     - open()    Forces open panel
 *     - close()   Forces close panel
 *     - deployed  Indicates that the control is deployed (opened)
 *
 * CLASSES USED
 *     - Container: .slidePanelWidget, .right
 *     - Button:    .slidePanelButton, .deployed
 *     - Content:   .slidePanel
 *
 * HTML format
 *     <div id="widget1" class="slidePanelWidget">
 *        <a class="slidePanelButton"> Name</a>
 *        <div class="slidePanel panel">
 *
 *            <!-- Widget contents -->
 *            Paste here the contents of the panel
 *            <!-- End of widget contents -->
 *
 *        </div>
 *      </div>
 *
 * 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.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 *
 * Modifications:
 *     - 2011-10-25  JRDiaz  Creation
 */
(function($){

    // Instantiation
 $.fn.slidePanel = function( options ) {
  var defaults = {
     position     : null
   , buttonTop    : null
   , panelTop     : null
   , animFunction : 'swing'
   , speed        : 'fast'
   , ajax         : null
   , clickToClose : true
  };

  var opts = $.extend( {}, defaults, options );

  if( !this.data('slidePanel') || typeof options == 'object' )
  {
   // Not instantiated or part of a selector call
   return this.each(function() {
    var me = $(this);
    me.data('slidePanel', new SlidePanel( me, opts ));
   });
  }
  else
  {
   return this.data('slidePanel');
  }
    };

 // SlidePanel Object
    function SlidePanel( el, opts ) {
  var isIE6  = $.browser.msie && $.browser.version == "6.0";

  var me    = this;
  me.opts   = opts;
  me.widget = el;
  me.button = me.widget.find('.slidePanelButton');
  me.panel  = me.widget.find('.slidePanel');
  me.left   = me.widget.hasClass('right') ? false : true;

  // ie6 doesn't like fixed position
  if( isIE6 ) { me.opts.position = me.opts.position == 'fixed' ? 'absolute' : me.opts.position; }

  // set css properties for button and panel
  if( me.opts.position  ) me.button.css( 'position', me.opts.position );
  if( me.opts.buttonTop ) me.button.css( 'top', me.opts.buttonTop );
  if( me.opts.panelTop  ) me.panel.css ( 'top', me.opts.panelTop  );

  // Button click event
  //me.button.attr( "href", "javascript:void(0)" ).mousedown(function() {
  me.button.attr( "href", "javascript:void(0)" ).click( function() {
   // load default content if ajax is false
   if (!me.opts.ajax) {
    //me.panel.toggle( me.opts.speed, me.opts.animFunction, function () {} );
    me.toggle();
   }
   else
   {
    // Loads the panel contents via AJAX if opts.ajax is defined
    if ( !me.panel.is(':visible') ) { // fetch data ONLY when panel is hidden...
     me.panel.load(me.opts.ajax, function( response, status, xhr ) {
      if (status !== "success") { // the ajax source wasn't loaded properly
       var msg = "<p>Sorry, but there was an error loading the document.</p>";
       me.panel.html(msg);
      };
      // Sets the HTML of the panel BEFORE opening it
      //me.panel.toggle( me.opts.speed, me.opts.animFunction, function () {} );
      me.toggle();
     });
    } else {
     //me.panel.toggle( me.opts.speed, me.opts.animFunction, function () {} );
     me.toggle();
    }
   }
   //me.button.toggleClass("deployed");
   return false;
  });

  // Control functions
  me.deployed = false;
  me.toggle = function( deployPanel ) {
   if( typeof open === 'boolean' ) me.deployed = deployPanel; // Forces open or close
   if( !me.deployed ) me.open();
   else               me.close();
  };

  var oldPadding = '0px';
  var oldWidth   = 0;
  me.open = function() {
   me.deployed = true;

   // Stores old values
   var w = me.button.width();
   if( w ) {
    oldPaddingR = me.button.css('padding-right');
    oldPaddingL = me.button.css('padding-left');
    oldWidth   = w;
   }
   me.button.addClass( 'deployed' );
   // Animates button
   if( me.left )
    me.button.animate( { 'width': '0px', 'padding-right': '0px' }, me.opts.speed, me.opts.animFunction );
   else
    me.button.animate( { 'width': '0px', 'padding-right': '0px', 'padding-left': oldPaddingR }, me.opts.speed, me.opts.animFunction );
   me.panel.toggle( me.opts.speed, me.opts.animFunction, function () {} );

   if ( me.opts.clickToClose ) me.enableCloseFn(); // Enables autoclose function if clickToClose is enabled
  };

  me.close = function() {
   me.deployed = false;

   me.panel.toggle( me.opts.speed, me.opts.animFunction, function () {} );
   // Animates button
   if( me.left )
    me.button.animate( { 'width': oldWidth + 'px', 'padding-right': oldPaddingR }, me.opts.speed, me.opts.animFunction );
   else
    me.button.animate( { 'width': oldWidth + 'px', 'padding-right' : oldPaddingR, 'padding-left': oldPaddingL }, me.opts.speed, me.opts.animFunction );
   me.button.removeClass( 'deployed' );

   if ( me.opts.clickToClose ) me.disableCloseFn(); // Disables autoclose function if clickToClose is enabled
  };

  // Autoclose panel when clicking outside
  me.stopCloseFn    = function(e) { e.stopPropagation(); }; // don't close panel when clicking inside it
        me.enableCloseFn  = function( ) { $(document).bind  ( 'click', me.close ); me.panel.bind  ('click', me.stopCloseFn ); };
        me.disableCloseFn = function( ) { $(document).unbind( 'click', me.close ); me.panel.unbind('click', me.stopCloseFn ); }; // Removes binds
 };
})(jQuery);

Código CSS
/*
 * jQuery slidePanel plugin basic styles
 * Version: 0.1.0 (2011/10/25)
 */

/* Widget styles */
.slidePanelWidget { position: fixed; .position: absolute; left: 0; }
.slidePanelWidget.right { left: auto; right: 0; }

  /* Button styles */
  .slidePanelWidget a.slidePanelButton:focus { outline: none; }

  .slidePanelWidget .slidePanelButton {
    white-space: nowrap;
    overflow: hidden;
    position: absolute;
    top: 4px;
    height: 19px;
    background-color: #BAC8DC;

    text-decoration: none;
    color: #fff;
    font-weight: bold;

    z-index: 2;
  }

  /* Left/right button styles */
  .slidePanelWidget .slidePanelButton {
   left: 0;
    padding: 1px 10px 1px 20px;
    background-position: 0 0;

           -moz-border-radius-topright: 10px;
        -khtml-border-top-right-radius: 10px;
       -webkit-border-top-right-radius: 10px;
               border-top-right-radius: 10px;

        -moz-border-radius-bottomright: 10px;
     -khtml-border-bottom-right-radius: 10px;
    -webkit-border-bottom-right-radius: 10px;
            border-bottom-right-radius: 10px;
  }
  .slidePanelWidget.right .slidePanelButton {
   left: auto; right: 0;
    padding: 1px 20px 1px 10px;
    background-position: 100% 0;

                   -moz-border-radius: 0;
                 -khtml-border-radius: 0;
                -webkit-border-radius: 0;
                        border-radius: 0;

           -moz-border-radius-topleft: 10px;
        -khtml-border-top-left-radius: 10px;
       -webkit-border-top-left-radius: 10px;
               border-top-left-radius: 10px;

        -moz-border-radius-bottomleft: 10px;
    -khtml-border-bottom-right-radius: 10px;
    -webkit-border-bottom-left-radius: 10px;
            border-bottom-left-radius: 10px;
  }

  /* Dynamic change of sprites. Sprites are 19px tall */
  .slidePanelWidget .slidePanelButton         { /* Open sprite */ background: #BAC8DC url(data:image/png;base64,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) no-repeat 0 0; }
    .slidePanelWidget .slidePanelButton:hover { background-color: #59B; background-position: 0 -19px; }
    .slidePanelWidget.right .slidePanelButton:hover { background-position: 100% -19px; }

  .slidePanelWidget .slidePanelButton.deployed         { /* Closed sprite */ background: #666 url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAoCAYAAAD+MdrbAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAABZ0RVh0Q3JlYXRpb24gVGltZQAwMy8yMy8wOT6cudcAAAAfdEVYdFNvZnR3YXJlAE1hY3JvbWVkaWEgRmlyZXdvcmtzIDi1aNJ4AAAHu0lEQVRIiX2Xe3BUVx3HP+fc3ezdbAJ5bIkkZAshoU3S0AQQgxQkPBywZUCHqYUqMJYxFmi1jtUW6IxIHaWD08q0kzpCdMYGis6A2nZGRiIOQyXGPhRoQssrMdsSwmbZbDb33n3ce/zj3mTSx3hmzl/33M/9Pb+/c4VSivElhJBAHjAFCHt7KqB7RyzgNjAMxIBRIKOUciYY40AhhAYUAhVADTAPaAZme1AFJIH3gS7gXeAq8BEwqpSyJ4AerBioBlYA32hubp6zYsUKWVZWhs/nQ0pJNpslGo3S2dlpv/XWW+8DHcApD5xQStkCkJ6LdwFfq6io2L5p06aCSCRCLBYjkUhgmiYAwWCQoqIiwuEwfX19dHR0jAwODr4E/MmzfFQDAkAEuD8SiTyxZcuWgvz8fHp6erh16xaZTAYhBEopxsbGiMViDA0NUVZWRmNjo97b29tgGMaA53py3NW5wBOtra0VhYWFXL58mfnz5xOJRAiFQvT395PJZKirq6Ompobp06dz4cIFysrKmDVrlt7d3T0N+DdwSwIlwLwFCxbcVVlZSTQaBSCXy7Fjxw52795NMBiktLSUPXv2sHPnTtLpNI7jMDAwQHV1NU1NTfWeUSUaUAts2rx585xMJkMymUQIQW9vL+FwmKqqKnRdZ+HChVRWVnL8+HFOnDhBKBRCSonP52PGjBny3Llzo8C7vvHsVlRUcOXKFbysM2XKFNrb25k7dy5r1qwBYGhoiPb2dgoKCpBSApBIJJg5cybAHKBIekkp9vl8DA8PY5omlmWRy+WIx+McPnx4ovAPHjyIZVnYtk06ncayLOLxOLqu44UuKCd1CZZlTWzDMABYv379BPDhhx9GKYVhGBNAy7IQQkyckYAJJC3Lwu/3Y5omhmEQj8fZtm0btbW1vPfeewwODlJbW0trayvxeBzDMDBNE7/fj2VZAHHAlEACuDQwMEA4HMY0TUZGRqirq2PDhg0A7N27l127dgHw4IMP0tDQwMjICIZhUFpaSl9fH8Al4LbEbfI3Ozs77dmzZ2PbNpqmsW/fPgAOHTrExYsX6erq4tixYwA8++yzaJpGLpejqqqKU6dOZYF/AjGBqyjNwE8ee+yxJk3T6OzsJJPJTMB9Ph9CCLLZLLlcDikleXl5tLS0YNs2bW1tbwPPAN0akPOUJDUwMLB44cKFgfLycqLRKLZtA+A4Do7jTCRP13WWLFmCruscOXLklmmabUA3cFMDHCALGKlUKtHb23tPY2NjcOnSpUgpGRsbI5lMksvlKCkpoaGhgVWrVjEyMsKrr756OxaLPQ90Av2ANVm+inC1bzmwcd68eXXLli3zVVZWEggEUEphmibXr1/n9OnT2Z6enneAI8CbwDUgqZSyP0tgP+eB7wU+D9ztdZPCVepeLwEXPdBNIPUxgZ1U3OMjoMCr/LBn+fgIMLwyi3l1lwKyahLEx6TlzQZLCOHgtmQRoE065/NinvKsyvCJ9VkWhoG6vbP47towK8vzCOkSoQkwHdQ1k9HXYpz8aT8veu4P/78hVbE2zPYnIzxen08wYUtGbYHluL2qS0WhpijSHP6TwvjZf3nhr3HagI/GoeNZFkD5ujBPPxXhO9MCUrtqSUwbAhLyJQgBYzakHdAlzNIdYlnH/uFVDpxN8DwwpJRS48AC4KG/NfFydVBqvYZEW7sNI1BI5tYN0qeOIoC8lQ/hv6Oc/PQo6T8foi7f4XzKya6/wLeAE0qpMZ9nXeSbZey+N4R2LS1RgOnA2t3PAbB/dQ+Bwil874VXAPjjrkcRCm5kJYumOv6N03jm6BD/EkJ8oHll8uWDc9iqa1KkbIEE+s+/TaZqLjNq7iYzdRrVX1pNxZ0z6Tz6W661/Zg7/AK/cENSqavijpv8A7jkA/KBlroQIpoROICUUKELuvY/SfX8Zpav/SoAsZuDnPv5D7g7AD4JSkDCFtSHkMBi4A0JhICGgIR4VmDYYNogAG3wOide+sVEWb2yfy9+IwEIMg5YNtzOCgo1AJqA0PhMKZOAaStMW2HZimRWkQwW0fL1zRPA+x/ZTlIvIpVTpG1F2nHPSreqpuNGwF2jOfAJheHAqA19acUXfnSA6voGLl08z83BG9TUN7Dk6QP0WYqkDSkbNKEYzbmNBu5MyQBD58egxK8wbMVgWqEtup+VG7cC0L7rcV76fisAqzdtRV/8AIMZxZitKPEr3hkF4EMgI72+PPtiFO7UFTkH0qFiHjnwa7dEXv4l5jtniJ95nb90/AaAbx/4FVZ+ERkbKgOKg1EUcAZICcAPLAVeO34PwQpdcHJYEMuC5SiCmht0AZ6bCr+AEp9gdaniQ0uxsYdR4CtAl+b5bgL3vJui5oESRE2+YjjrRjhPuDAh3JYrlFDsg5ZixZgNj1/GSdqcBH6nlEqOt14A98b6h0iA8hfnIBYUwtsp+MAQDGVd6LQ8qA4q6kPQnYSnrqL6Ld4HHgXOKaXSKKXwFKcQWAdcBnLrwqg35qKiX0QllqCG70Ndb0b9vh61vBgF2LiKvRJXkF3lmgQUHnQl8DrufdrxQjJ5296zI8CiyTCl1KcEVniFXox7cW8B7gNmePA+4Czwd8+6BO5fwATkY8BPgDXcPg95H1FAGhgDTKVU7lMvAv8D7eSa3J0E5V4AAAAASUVORK5CYII=) no-repeat 0 0; }
    .slidePanelWidget .slidePanelButton.deployed:hover { background-color: #59B; background-position: 0 -19px; }
    .slidePanelWidget.right .slidePanelButton.deployed:hover { background-position: 0 -19px; }


  /* Panel styles */
  .slidePanelWidget .slidePanel {
    display: none;
    position: absolute;
    width: auto;
    height: auto;
    z-index: 1;

    padding: 8px;
    color:#CCC;
    background: #111;

      -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=90)";
          filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=90);
    -moz-opacity: .9;
         opacity: .9;
  }

  /* Left/right panel styles */
  .slidePanelWidget .slidePanel {
    left: 0;
           -moz-border-radius-topright: 4px;
        -khtml-border-top-right-radius: 4px;
       -webkit-border-top-right-radius: 4px;
               border-top-right-radius: 4px;

        -moz-border-radius-bottomright: 4px;
     -khtml-border-bottom-right-radius: 4px;
    -webkit-border-bottom-right-radius: 4px;
            border-bottom-right-radius: 4px;
  }
  .slidePanelWidget.right .slidePanel {
    left: auto;
    right: 0;

                   -moz-border-radius: 0;
                 -khtml-border-radius: 0;
                -webkit-border-radius: 0;
                        border-radius: 0;

           -moz-border-radius-topleft: 4px;
        -khtml-border-top-left-radius: 4px;
       -webkit-border-top-left-radius: 4px;
               border-top-left-radius: 4px;

        -moz-border-radius-bottomleft: 4px;
     -khtml-border-bottom-left-radius: 4px;
    -webkit-border-bottom-left-radius: 4px;
            border-bottom-left-radius: 4px;
  }


2011/10/25

Colocación de elementos mediante CSS

La entrada de hoy está dedicada a problemas del día a día para la colocación de elementos mediante CSS con recetas sencillas para resolverlos.

En la prehistoria, si se quería un contenido centrado nada era más fácil que poner un <center></center>, pero eso, aparte de viejuno, ahora mismo como te pillen haciéndolo, te cortan las manos.
The <center> tag is supported in all major browsers. However, it is deprecated and should be avoided!
Así que ahora, los más listos habrán dicho que poniendo (con perdón del estyle inline) un <div style="text-align: center;"></div> ya se arregla todo y se quedan tan panchos. Pues sí y no. Si se quiere centrar un texto, va perfecto, pero... y si lo que se quiere centrar es otro contenedor como por ejemplo un div o un span... centraríamos el texto del segundo contenedor, pero no el contenedor en sí.

Pues para este tipo de disyuntivas, vamos a ver algunas recetas copy-paste.

Centrado de texto
Tal y como hemos visto, la propiedad CSS text-align:center hace el trabajo necesario.

Centrado de bloques de texto
Más que centrado, se le puede llamar márgenes equivalentes, que por ende, produce un efecto de centrado. Ni qué decir tiene, que sin especificar un ancho, no sirve de mucho.
P.bloqueCentradoDeTexto { margin-left: auto; margin-right: auto; width: 300px;}

Centrado de imágenes
Equivalente al centrado de texto. La única diferencia es que para que funcione, hay que convertir la imagen en un bloque con display:block.
img.imagenCentrada { display: block; margin-left: auto; margin-right: auto; width: 300px;}

Centrado vertical
Subamos la dificultad un nivel. En las tablas es muy sencillo... existe la propiedad CSS de vertical-align: middle, bueno, pues con otros elementos de bloque es también muy sencillo. El secreto es hacer que el contenedor de dichos elementos se comporte como una celda de tabla para aplicar la propiedad CSS de vertical-align dando además un alto al contendor.
div.containerDeBloqueCentradoVerticalmente { height: 500px; display: table-cell; vertical-align: middle;}

Final Boss. Colocando elementos en la esquina inferior derecha.
Hay dos maneras, o con un "float: right" que sirve en los casos en los que el contenedor crece con los contenidos, pero si tenemos un contenedor que no solo no crece si no que a lo mejor tiene un alto fijo, no funciona. Para ello, hay una receta sencilla para conseguirlo sin tener que recurrir a código javascript mediante position:absolute. La pega es para variar IE6, que tiene un bug calculando el alto de elementos, pero que se evitar mediante hacks. Para la receta, defino dos clases, una para el contenedor ".contenedor" y otra para el contenido ".alineadoEnLaEsquina" (en este caso un hipervínculo).
div.contenedor { 
    position: relative; 
    height: 1%; /* Hack para el IE6 */
}
a.alineadoEnLaEsquina { 
    position: absolute; 
    bottom: 0; /* en vez de 0 y 0 se puede poner otra cantidad */
    right: 0;  /* para simular el margen respecto a la esquina */
}

A disfrutarlo!.

2011/10/24

Quién me lee?

La verdad es que para ser éste un blog que nació únicamente como memoria digital de las cosillas que voy descubriendo por la web, viendo las estadíasticas me he dado cuenta de tres cosas muy curiosas...

La primera, es que mientras menos escribo más gente me lee... Cuando he estado publicando una o más entradas diarias no llegaba a 100 visitas al mes (y he contado a mi madre... dos veces). Sin embargo, ahora que me he tomado el verano de relax, en casi dos-tres meses sin escribir una línea, tengo unas 1500 visitas mensuales y creciendo exponencialmente. Curioso, no?

La segunda, es que los que me lean (si es que hay alguien) son personas muy tímidas. 0 comentarios en total. Al menos tengo que administrar pocos comentarios soeces y descalificaciones personales. Eso es de agradecer.

Lo tercero es la tecnología de los navegadores de la gente que me visita. Cuanto menos sorprendente. Os pego un extracto de las estadísticas:
Chrome29%
Google Desktop28%
Firefox24%
Internet Explorer10%
Safari5%
Opera1%
Mobile Safari<1%Hola Curro!
chromeframe<1%Todavía hay alguien (5) que lo usa! bien!
ThunderBrowse<1%Y éso qué es?
Mobile<1%1 visita. Ese soy yo :D

Es decir, que el navegador más usado por gente dedicada a aprender cosas sobre la web es el Chrome, que si lo juntamos con el Google Desktop, que supongo que es el Reader de Google, suma la friolera del 57% de las visitas provenientes de productos de Google!!!.

Luego viene el firefox y solamente un 10% de IE (no voy a hacer sangre desglosando por versiones :P)

En fin... si trabajara en los informativos de la tele, diría que los que usan Chrome son más listos, audaces y ávidos de conocimiento, pero como no es así, simplemente diré que son más y punto, por ahí no me pilláis, pillines!

Bueno, y esta cutre-entrada servirá para advertiros que la semana que viene postearé varios (>1) plugins de jQuery que he hecho. Os van a gustar. Estad atentos, y va por vosotros, esos casi 1500 ;)

2011/10/21

Copiando a Google, sí, ¿y?. Flexbox y overlay/dialog.

Retomemos viejas costumbres. Llevaba algún tiempo sin publicar nada básicamente porque llegaron las vacaciones y por pereza, sinceridad ante todo. Pero hay que luchar contra la apatía, así que para abrir boca, os voy a poner un cachito de código copiado vilmente desde Chrome aplicando las últimas tecnologías y ese toque minimalista de Google, pero al alcance de todos los navegadores (modernos). Concretamente, os hablo del combo overlay/caja de diálogo que se usa en la configuración de Chrome pero fácilmente aplicable a cualquier web. A continuación, una captura.
Las tecnologías principales que se han usado son el juego de propiedades CSS3 denominado flexbox que permite definir cajas de elementos distribuyendolas tanto a ellas como a sus contenidos dentro de la página y  varias propiedades visuales de CSS3 junto con sus variantes según el navegador, puestas en el orden correcto. De regalo, un clon de los botones de Chrome también, que se ven muy bonitos.

Para más información sobre las propiedades de flexbox podéis ver un magnífico artículo en HTMLRocks. Es tontería explicarlo de nuevo aquí porque es una página muy trabajada con muchos ejemplos y que hasta detecta si el navegador usado para verla es capaz de mostrar correctamente todos los ejemplos, así que no os pongáis a verla con el IE6, que os veo y luego me venís llorando!.

Para ver el código completo, en mi jsFiddle como es habitual.


El ejemplo se compone de dos partes. Por un lado una estructura muy sencilla en HTML
<!-- Overlay/dialog -->
<div id="overlay" class="overlay">
    <div id="myDialog" class="dialog">

        <!-- Título del diálogo -->
        <h1>Título del Diálogo</h1>

        <!-- Cuerpo del diálogo -->
        <div class="dialog-area">
            <p>CUERPO</p>
        </div

        <!-- Pie del diálogo -->
        <div class="dialog-footer">
            <div class="extensible">PIE con botones a la derecha sin float</div>
            <input type="button" value="Botón1"/>
            <input type="button" value="Botón2"/>
        </div>

    </div>
</div>

Y por el otro, un CSS algo más complejo (CSS3, por supuesto). Los comentarios además de ayudar a identificar los estilos, dan información sobre para qué y cómo se usan.

/* ================================================================================ */
/* Sample styles */
html {
    font-family: arial;
}

/* Sample dialog styles */
#myDialog {
    background-color: white;
    margin-bottom: 6px;
    margin-top: 6px;
    width: 500px;
}


/* ================================================================================ */
/* Overlay/Dialog styles */
.overlay {
    -webkit-transition: 0.25s opacity;
       -moz-transition: 0.25s opacity;
        -ms-transition: 0.25s opacity;
         -o-transition: 0.25s opacity;
            transition: 0.25s opacity;  

    background: -webkit-radial-gradient(rgba(127, 127, 127, 0.5), rgba(127, 127, 127, 0.5) 35%, rgba(0, 0, 0, 0.7));
    background:    -moz-radial-gradient(rgba(127, 127, 127, 0.5), rgba(127, 127, 127, 0.5) 35%, rgba(0, 0, 0, 0.7));
    background:      -o-radial-gradient(rgba(127, 127, 127, 0.5), rgba(127, 127, 127, 0.5) 35%, rgba(0, 0, 0, 0.7));
    background:     -ms-radial-gradient(rgba(127, 127, 127, 0.5), rgba(127, 127, 127, 0.5) 35%, rgba(0, 0, 0, 0.7));
    background:         radial-gradient(rgba(127, 127, 127, 0.5), rgba(127, 127, 127, 0.5) 35%, rgba(0, 0, 0, 0.7));
    
    position: fixed;
    left: 0; top: 0; right: 0; bottom: 0;
    padding: 20px; padding-bottom: 130px;
    z-index: 10;
}


.overlay .dialog {
    -webkit-user-select: none;
       -moz-user-select: none;

    -webkit-box-shadow: 0px 5px 80px #505050;
       -moz-box-shadow: 0px 5px 80px #505050;
         -o-box-shadow: 0px 5px 80px #505050;
        -ms-box-shadow: 0px 5px 80px #505050;
            box-shadow: 0px 5px 80px #505050;

    background: white;
    border: 1px solid #BCC1D0;

    -webkit-border-radius: 2px;
       -moz-border-radius: 2px;
     -khtml-border-radius: 2px;
            border-radius: 2px;

    position: relative;
    min-width: 400px;
    padding: 0;
}


/* Dialog heading */
.overlay .dialog h1 {
    -webkit-padding-end: 24px; /* Specifies the space between an element's left or right border and its contents, depending on the writing direction */
       -moz-padding-end: 24px;

    -webkit-user-select: none;
       -moz-user-select: none;

    background: -webkit-linear-gradient(white, #F8F8F8);
    background:    -moz-linear-gradient(white, #F8F8F8);
    background:      -o-linear-gradient(white, #F8F8F8);
    background:         linear-gradient(white, #F8F8F8);

    border-bottom: 1px solid rgba(188, 193, 208, .5);
    padding: 10px 15px 8px 15px;
    margin: 0;

    font-size: 105%;
    font-weight: bold;
    color: #53637D;
    text-shadow: white 0 1px 2px;
}


/* Dialog area */
.overlay .dialog .dialog-area {
    -webkit-user-select: none;
       -moz-user-select: none;

    padding: 10px 15px;
}


/* Dialog footer */
.overlay .dialog .dialog-footer {

    border-top: 1px solid rgba(188, 193, 208, .5);
    padding: 12px;
}


/* ================================================================================ */
/* Flexible box model */
/* http://www.html5rocks.com/en/tutorials/flexbox/quick/ */
.overlay {
    display: -webkit-box;            /* Enables flexible box model */
    -webkit-box-orient: horizontal; /* How should the box's children be aligned */
      -webkit-box-pack: center;    /* Sets the alignment of the box along the box-orient axis. . So if box-orient is horizontal it will chose how the box's children are aligned horizontally, and vice-versa. */
     -webkit-box-align: center;   /* Basically box-pack's brother property. Sets how the box's children are aligned in the box. If the orientation is horizontal it will decide the alignment vertically and vice-versa */

    display: -moz-box;
    -moz-box-orient: horizontal;
      -moz-box-pack: center;
     -moz-box-align: center;

    display: box;
    box-orient: horizontal;
      box-pack: center;
     box-align: center;
}

.overlay .dialog .dialog-footer {
    display: -webkit-box;
    -webkit-box-orient: horizontal;
      -webkit-box-pack: end;
     -webkit-box-align: center;

    display: -moz-box;
    -moz-box-orient: horizontal;
      -moz-box-pack: end;
     -moz-box-align: center;

    display: box;
    box-orient: horizontal;
      box-pack: end;
     box-align: center;
}

.extensible {
    -webkit-box-flex: 1;  /* Float value. Extensible up to 1 (or value) times its width (or height depending on parent's orientation) */
       -moz-box-flex: 1;
            box-flex: 1;
}


/* ================================================================================ */
/* Chrome buttons */
button, input[type="button"], input[type="submit"] {
    -webkit-border-radius: 2px;
       -moz-border-radius: 2px;
     -khtml-border-radius: 2px;
            border-radius: 2px;

    -webkit-box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.1);

    -webkit-user-select: none;
       -moz-user-select: none;

    background: -webkit-linear-gradient(#FAFAFA, #F4F4F4 40%, #E5E5E5);
    background:    -moz-linear-gradient(#FAFAFA, #F4F4F4 40%, #E5E5E5);
    background:      -o-linear-gradient(#FAFAFA, #F4F4F4 40%, #E5E5E5);
    background:         linear-gradient(#FAFAFA, #F4F4F4 40%, #E5E5E5);

    border: 1px solid #AAA;
    color: #444;
    font-size: inherit;
    margin-bottom: 0px;
    min-width: 4em;
    padding: 3px 12px 3px 12px;
}