Menú

Mostrar Mensajes

Esta sección te permite ver todos los mensajes escritos por este usuario. Ten en cuenta que sólo puedes ver los mensajes escritos en zonas a las que tienes acceso en este momento.

Mostrar Mensajes Menú

Temas - Gus Garsaky

#1
Java / Timer & TimerTask
Julio 29, 2015, 05:24:47 PM

La clase Timer es una clase muy útil para situaciones particulares. Estas situaciones pueden ser por ejemplo, cuando necesitamos hacer algo cada X tiempo. Quizás hayas visto en algunas webs que aparecen ofertas o anuncios cada X tiempo, pues bien, esto se hace gracias a los Timers.

Vamos a realizar un pequeño timer que hace un conteo regresivo imprimiendo en pantalla la hora actual. Primero hagamos una clase que muestre la hora actual.

Código: java

public class Display {

    public void printTime(int hour, int minute, int second) {
        String fullHour = "";

        fullHour += (hour > 9) ? ":" + hour : "0" + hour;
        fullHour += (minute > 9) ? ":" + minute : ":0" + minute;
        fullHour += (second > 9) ? ":" + second : ":0" + second;

        System.out.println(fullHour);
    }
}


Nada del otro mundo, solo le da formato a una hora recibida como 3 parámetros: hora, minuto y segundo. Esta clase la usaremos en el Timer para mostrar la hora actual.

Código: java

import java.util.Timer;
import java.util.TimerTask;

public class Timeout {

    private int hour = 0;
    private int minute = 0;
    private int second = 11;
    private Timer timer;
    private boolean isTimerRunning;
    private Display display;

    public Timeout() {
        timer = new Timer();
        display = new Display();
    }

    TimerTask task = new TimerTask() {
        @Override
        public void run() {
            isTimerRunning = true;
            if(second > 0) {
                second--;
            } else {
                second = 59;
                if(minute > 0) minute--;
                else {
                    minute = 59;
                    if(hour > 0) hour--;
                    // si segundo = 0, minuto = 0 y hora = 0,
                    // cancelamos el timer
                    else {
                        isTimerRunning = false;
                        timer.cancel();
                        timer.purge();
                    }
                }
            }
            if(isTimerRunning)
                display.printTime(hour, minute, second);
        }
    }; // fin timertask

    public void start(int timeout, int interval) {
        timer.schedule(task, timeout, interval);
    }

} // fin clase


El código es autoexplicado. En lo que debemos de fijarnos es el TimerTask. La TimerTask nos permite realizar tareas en un thread separado. Esto es así, porque por lo general, cuando usamos un Timer es para que se esté ejecutando cada cierto tiempo un código de forma paralela, esto eso, multithreading programming (programación multi hilo). El código del TimerTask solo hace una serie de comprobaciones para disminuir la hora, minuto y segundo. Luego, llama al método printTime del objeto Display, pasándole la hora, minuto y segundo y que este método dará forma e imprimirá.

Para empezar un timer, debemos de llamar al método schedule el cual recibe 3 parámetros:

  • Un objeto TimerTask
  • Un timeout (tiempo de espera para que empiece a ejecutarse)
  • Un intervalo (la tarea se ejecutará cada X tiempo)

    Los dos últimos parámetros se debe especificar en milisegundos, así, 1000 es equivalente a 1 segundo. Por último, hagamos nuestra clase principal.

    Código: java

    public class TimerTest {

        public static void main(String[] args) {
            Timeout timeout = new Timeout();
            timeout.start(0, 1000);
        }
    }


    En la clase principal decimos que el timer tendrá un delay de 0s y un intervalo de 1s. Si compilamos y corremos:

    Código: dos
    javac Display.java
    javac Timeout.java
    javac TimerTest.java

    java TimerTest


    Obtendremos lo siguiente:



    ¿Posibilidades? La que puedas imaginar, así que piensa como sacarle provecho en tu próximo proyecto.
#2
Java / CDI 1.1 - Context and Dependecy Injection
Julio 14, 2015, 10:49:24 PM
CDI

Context and Dependency Injection o Contexto e inyección de depencias en español, es un poderoso elemento de la plataforma Java EE el cual nos provee un poderoso mecanismo de inyección de depencias. Además, nos provee de muchas utilidades, como interceptores, calificadores, eventos, decoradores, productores y trituradores, entre otros.

CDI es un concepto incomprendido por gran parte de los programadores Java EE. En el presente documento, intentaremos despejarnos las dudas. Empecemos.


Dependency Injection

Dependency Injection (DI) es un patrón de diseño que desacopla componentes depenientes. Es parte de inversion of control (IoC) donde el objetivo que es invertido es el proceso de obtención de dependencias necesarias. El término fue originalmente dicho por Martin Fowler. Una manera de pensar en DI en un entorno manejado como Java EE es pensar en JNDI al revés. En lugar que un objeto busque por otros, el contenedor inyecta su dependencia por ti. Esto es llamado Principio Hollywood, "Don't call us (buscar objetos), we'll call you (inyectar objetos)".


Gestión del ciclo de vida

El ciclo de vid de un POJO es muy simple, creas una instancia de una clase usando la palabra reservada new y esperas a la recolección de basura para que la elimine y libere algo de memoria. En su lugar, necesitas inyectar el bean y el contenedor hace el resto, esto quiere decir que el contenedor es el único responsable de gestionar el ciclo de vida de un bean: este lo crea, este lo destruye. Así que, ¿como inicializas un bean si tu no llamas a un constructor? El contenedor te brinda un manejo luego de construir la instancia y antes de destruirla. Estas anotaciones son:


  • @PostConstruct: un método anotado con esta anotación es llamado inmediatamente después que el contenedor haya inyectado el bean (instanciado).
  • @PreDestroy: un método anotado con esta anotación es llamado justo antes que el recolector de basura elimine el bean.


CDI Bean

Un bean CDI puede ser cualquier tipo de clase que contenga lógica de negocio. Este puede ser llamado directamente por código Java vía inyección o puede ser invocado vía El desde una página JSF. Un bean es un POJO que no extiende de nada, puede inyectar referencias en otros beans (@Inject), tiene su ciclo de vida manejado por el contenedor y puede tener sus propios metodos incerptores:

Código: java
public class BankLoan {
    @Inject
    private AccountChecker accountChecker;
    private Date dateOfLoan;

    @PostConstruct
    private void initializeDate() {
        dateOfLoan = new Date();
    }
}


En el ejemplo anterior, se está inyectando la dependencia del bean accountChecker, esto lo hace el contenedor. El método initializeDate anotado con @PostConstruct será llamado automáticamente luego de que se instancie la clase BankLoan. Los CDI beans tienen ciertas características que deben cumplirse de manera obligatoria. Estas son:

  • No debe ser una interna no estática.
  • Tiene un constructor por defecto sin parámetros o debe declarar un constructor anotando @Inject.
  • Es una clase concreta (no debe extender) o  es anotada con @Decorator

    Para mayor comprensión, veamos el siguiente diagrama de clases:


    ¿Cómo conectamos CDStore y CreditCard? La forma que todos conocemos es mediante el operador new:

    Código: java
    public class CDStore {
        private CreditCard creditCard;
       
        public CDStore() {
            creditCard = new Visa();
        }
        public void buyItems() {
            // pagar con la tarjeta
      creditCard.pay();
        }
    }


    El código anterior es bien simple  y es lo que nosotros hacemos y conocemos. Pero, ¿que pasaría si quisieras pagar con MasterCard?, ¿Necesitarías crear una (otra) instancia de MasterCard? Una solución pasar su dependencia por medio del constructor de una clase externa que haga uso de la clase CDStore:

    Código: java
    public class CDStore {
        private CreditCard creditCard;

        public CDStore(CreditCard creditCard) {
            this.creditCard = creditCard;
        }
        ...
    }


    En otra clase:

    Código: java
    CDStore cdStore = new CDStore(new MasterCard());


    Ésto es lo que se conoce como inversion of control (IoC) o inversión de control (en español). La dependencia se inyecta desde una tercera clase, de ésta manera no creamos un vínculo entre CDStore y CreditCard.

    Inyección (@Inject)

    Java EE es un entorno gestionado, así que quizás no necesitas construir las dependencias tú mismo, en su lugar, puedes dejar que el contenedor inyecte las dependencias de los beans por tí. CDI tiene la habilidad de inyectar dependencias de prácticamente lo que sea. Veamos un ejemplo:

    Código: java
    public class CDStore {
        @Inject
        private CreditCard creditCard;
       
        public void buyItems() {
            creditCard.pay();
        }
        ...
    }


    Como se puede observar, no necesitamos construir la dependencia de creditCard, se lo dejamos al contenedor. La implementación se vería así:

    Código: java
    public class Visa implements CreditCard {
        @Override
        public void pay() {
             // logica de negocio
        }
    }


    Nota que solo hemos especificado una implementación de la interface CreditCard. Si hubiéramos creado otra implementación, la inyección no se podría realizar porque el contenedor no sabría qué dependencia inyectar. Esto, lo resolveremos más adelante con calificadores (@Qualifier).




    ANEXO

    Puntos de inyección

    Una inyección puede llevarse a cabo en tres puntos:

  • Por propiedad:

       
    Código: java
    @Inject
    private CreditCard creditCard;


       Nota que no es necesario especificar setter para la propiedad.
  • Por constructor:

       
    Código: java
    @Inject
             private CDStore (CreditCard creditCard) {
                 this.creditCard = creditCard;
            }


       La regla aquí es que la inyección por constructor se puede realizar solo una vez (por    clase).

  • Por setter:
       
    Código: java
    @Inject
            public void setCreditCard(CreditCard creditCard) {
                this.creditCard = creditCard;
            }



    Ésto es un anexo, porque en realidad no importa en qué punto hagas la inyección. Por propiedad, constructor o setter, para el contenedor le da igual. Por lo tanto, escoger uno u otro, es simplemente opción personal.




    Injección por defecto (@Default)

    Tu puedes tener una dependencia para inyectar por defecto. Por ejemplo, nosotros tenemos solo una implementación, aquella, tendrá implícitamente el modificador @Default. Así, ésto:

    Código: java
    @Inject
    private CreditCard creditCard;


    Es igual que:

    Código: java
    @Inject @Default
    private CreditCard creditCard;


    Podemos apreciarlo en una imagen:


    Si tienes una implementación, el contenedor inyectará la única dependencia existente. Si por otro lado, tienes muchas implementaciones de una interface, el contenedor no sabrá cuál implementación debe inyectar, y aquí es cuando toman tanta importancia los calificadores.


    Calificadores (@Qualifier)

    Cuando el sistema inicia, el contenedor CDI debe validar que todos los beans satisfasgan cada punto de inyección existente. Si alguna dependencia de un bean no puede ser satisfecha por el contenedor CDI, entonces la aplicación no se podrá desplegar informando de una insatisfacción de dependencia. Si hay una sola implementación disponible, la inyección funcionará aplicando la dependencia única, que es marcada implícitamente con @Default.

    Si hay más de usa implementación y tratamos de desplegar, la aplicación no desplegará porque dectectará ambiguación en las dependencias, es decir, si hay 2 o más implementaciones, ¿cómo sabe el contenedor qué dependencia inyectar? Aquí es donde toman importancia los calificadores. Veamos el siguiente diagrama de clases que ilustra la situación actual:


    Podemos ver que hemos creado una nueva implementación, llamada MasterCard, la misma está anotada bajo el calificador MasterCardPay. Además, vemos como una nueva clase, CDStoreWithQualifier inyecta la nueva dependencia. Esto lo hará mediante el calificador. Veamos el ejemplo en código (solo las nuevas clases):

    Calificador MasterCardPay:

    Código: java
    @Qualifier
    @Retention(RUNTIME)
    @Target({FIELD, TYPE, PARAMETER, METHOD})
    public @interface MasterCardPay {}


    Implementación MasterCard:

    Código: java
    @MasterCardPay
    public class MasterCard implements CreditCard {
        private Logger logger = Logger.getLogger(MasterCard.class.getName());
       
        @Override
        public void pay() {
            logger.log(Level.INFO, "Pagando con MasterCard");
        }
    }


    La anotación @Retention, especifica el nivel de retención de la anotación. Existen 3 tipos:

  • SOURCE: la anotación será visible en el código fuente, una vez que se compila la aplicación, es descartada. La anotación no será escrita en bytecode. Por ejemplo, @Override.
  • CLASS: La anotación se descarta durante la carga de la clase por parte del ClassLoader. Sí se escribe en bytecode.
  • RUTNIME: No se descarta. Ésta anotación estará siempre disponible por reflection en tiempo de ejecución.

    Las primeras dos anotaciones no podrán ser inspeccionadas en tiempo de ejecución, mientras que la última sí. Esta es la diferencia entre las 3.
    La anotación @Target, especifica que elementos podrán ser afectados por la anotación. Hay varias opciones:

  • ElementType.ANNOTATION_TYPE
  • ElementType.CONSTRUCTOR
  • ElementType.FIELD
  • No tienes permitido ver los links. Registrarse o Entrar a mi cuenta_VARIABLE
  • ElementType.METHOD
  • ElementType.PACKAGE
  • ElementType.PARAMETER
  • ElementType.TYPE

    Creo que la mayoría se explican solas. Las dos anotaciones que son un poco confusas son ANNOTATION_TYPE y TYPE. ANNOTATION_TYPE significa que la anotación solo podrá ser usada para anotar otras anotaciones, como @Retention. TYPE significa cualquier tipo de elemento (clases, interfaces, enums, anotaciones, etc).

    Comprendidos ya estos conceptos, vamos a ver la clase que inyecta la nueva implementación:

    Código: java
    public class CDStoreWithQualifier {
        @Inject @MasterCardPay
        private CreditCard masterCard;
       
        public void buyItems() {
            masterCard.pay();
        }
    }


    Observa lo sencillo que es inyectar múltiples dependencias, solo basta con indicar el calificador de la dependencia que debe ser inyectada. Es posible también anotar un elemento con múltiples anotaciones. Java no nos limita en este aspecto. Por ejemplo:

    Código: java
    @Inject @Visa @Gold
    private CreditCard creditCard;



    Productores (@Produces)

    Como ya hemos visto, podemos inyectar CDI beans dentro de otros CDI beans. Pero, si quisiéramos inyectar POJOs, o tipo de datos primitivos, ¿sería eso posible? La respuesta corta es no y la razón es la siguiente.

    Por defecto, no puedes inyectar clases como String o Date y la razón, es porque ellas están empaquetadas en el archivo rt.jar (Java runtime enviroment classes) y este no contiene un descriptor de beans beans.xml. Esto es importante tenerlo presente, si un archivo no tiene un descriptor beans.xml bajo el directorio META-INF, CDI no activará el descrubimiento de beans (bean discovery) y los POJOs no podrán ser tratados como beans y por ende, no podrán ser inyectados. Afortunadamente, podemos revertir esta situación con ayuda de los productores. Veamos un ejemplo:


    Veamos que la clase CDSerialProducer tiene 3 anotaciones, las cuales pasamos a describir:

  • @Produces: utilizada para producir un bean listo para inyección.
  • @CDSerialPrefix: anotación propia.
  • @RandomNumber: anotación propia.

    Primer veamos nuestras anotaciones, serán igual que los calificadores que ya hemos visto:

    Código: java
    @Retention(RUNTIME)
    @Target({FIELD, TYPE, PARAMETER, METHOD})
    public @interface CDSerialPrefix { }


    Código: java
    @Retention(RUNTIME)
    @Target({FIELD, TYPE, PARAMETER, METHOD})
    public @interface RandomNumber { }


    Ahora, veamos como producimos beans para inyectar de tipo String e int:

    Código: java
    public class CDSerialProducer {
        @Produces @CDSerialPrefix
        private final String prefix = "T9-SK534";
       
        @Produces @RandomNumber
        public int generateNumber() {
            return new Random().nextInt(10000);
        }
    }


    La anotación @Produces, producirá una propiedad y un método para inyectar. Seguido de @Produces, debemos de especificar el calificador con el cual se va a inyectar la propiedad y el método. En este caso son @CDSerialPrefix y @RandomNumber. Ahora veamos como los inyectamos:

    Código: java
    public class CDService {
        @Inject @CDSerialPrefix
        private String prefix;
        @Inject @RandomNumber
        private int number;
        private Logger logger = Logger.getLogger(getClass().getName());
       
        public void createCD() {
            logger.log(Level.INFO, "Creando CD con serial: {0}", (prefix + number));
        }
    }


    Si ejecutamos el proyecto, podemos ver que se inyectan satisfactoriamente los valores de la propiedad y el método dentro de las propiedades de CDService. Gracias a los productores, podemos inyectar prácticamente lo que sea.




    ANEXO

    API de InjectionPoint

    Los atributos y propidades producidad por productores (@Produces) no necesitan saber ningún tipo de información acerca de donde ellos son inyectados. Sin embargo, hay ciertos casos  donde podría ser de utilidad que el bean a inyectar sepa en qué punto de inyección se ha llevado a cabo la inyección. Un ejemplo común de ésto, es generar una dependencia dinámica de Logger (para crear logs). Generalmente, siempre creamos un Logger así:

    Código: java
    Logger logger = Logger.getLogger(TuClase.class.getName());


    Lo anterior funciona correctamente, pero también podríamos generar depenencias dinámicamente. Veamos primero la API de InyectionPoint:


    Podemos usar esta API para generar una dependencia dinámica para Logger. Veamos un ejemplo:

    Código: java
    public class LoggerProducer {
        @Produces
        private Logger createLogger(InyectionPoint ip) {
            return Logger.getLogger(ip.getMember().getDeclaringClass().getName());
        }
    }


    Perfecto. Ahora tan solo debemos inyectar al logger de ésta manera:

    Código: java
    @Inject Logger log;


    ¿Cómo ocurre la magia? Bien, el contenedor CDI buscará cualquier bean de tipo Logger listo para inyectar, y lo encuentra gracias a que hemos anotado el método con @Produces, lo que deja ese método listo para su inyección (como con @CDSerialPrefix y @RandomNumber del ejemplo anterior).





    Trituradores (@Disposes)

    Ahora sabemos como producir beans o valores para inyectar, pero algunos beans podrían necesitar ser cerrados, como es el caso de conexiones JDBC o un EntityManager. Por lo tanto, algunos beans producidos deberían asegurarse de ser cerrados. Aquí es donde toman importancia los Disposers. Los Disposers son lo  contrario a Producers, éste último crea beans o valores para inyectar, mientras que el primero se encarga de destruirlo.
    Veamos un ejemplo con una clásica conexión JDBC:

    Código: java
    public class DerbyJdbcProducer {
        private final static String DRIVER = "org.apache.derby.jdbc.EmbeddedDriver";
        private final static String URL = "jdbc:derby://localhost:1527/CDI";
        private final static String USER = "duke";
        private final static String PASS = "1234";
        @Inject
        private Logger logger;
       
        @Produces
        public Connection createConnection() {
            Connection con = null;
            try {
                Class.forName(DRIVER).newInstance();
                con = DriverManager.getConnection(URL, USER, PASS);
                logger.log(Level.INFO, "Creando conexión JDBC con Derby");
            } catch(InstantiationException | IllegalAccessException |
                    ClassNotFoundException | SQLException e) {
                e.printStackTrace();
            }
            return con;
        }
        public void closeConnection(@Disposes Connection con) {
            try {
                con.close();
                logger.log(Level.INFO, "Cerrando conexión JDBC con Derby");
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }


    Un método disposer debe obligatoriamente recibir un parámetro @Disposes y el tipo del parámetro debe ser el mismo que el tipo devuelto con el método productor (@Produces) y su calificador (@Qualifier) en caso haya más métodos productores con el mismo tipo de retorno. Dado que el ciclo de vida de un bean inyectado depende del contexto en donde se inyecta, cuando se elimina el bean que contiene el bean inyectado, éste se destruirá también. Pero si no hubiésemos escrito el método disposer, la conexión nunca se hubiese cerrado por ser ésta independiente del contexto. Cuando el bean que contiene el bean inyectado es eliminado, antes que eso ocurra, se ejecutará el método disposer del bean inyectado, en este caso, cerrando la conexión JDBC.




    ANEXO

    Alcances (scopes)

    Todo objeto cuyo ciclo de vida es manejado por CDI tiene un alcance definido y un ciclo de vida que está ligado a un contexto específico. En Java, el alcance de un POJO es muy simple: tu creas una instancia de una clase usando la palabra reservada new y tu confías en el recolección de basura para deshacerse  de él y liberar alguna memoria. Con CDI, un bean es ligado al contexto y este lo mantiene en ese contexto hasta que el bean es destruido por el contenedor. No hay manera para remover manualmente un bean desde un contexto.

    CDI define los siguientes alcances y permite puntos de extesión para crear tu propio alcance:

  • Application scope(@ApplicationScoped): Se extiende por la duración entera de una aplicación. El bean es creado solo una vez por duración de la aplicación y es descartado cuando la aplicación se apaga. Este alcance es util para cases utilitarias o helpers, u objetos que guardan datos compartidos por la aplicación entera (pero deberías ser cuidadoso con los problemas de concurrencia cuando los datos tienen que ser accesados por muchos threads).

  • Session scope(@SessionScoped): Se extiene a través de muchas peticiones HTTP o muchas invocaciones de métodos por una sola sesión de usuario. El bean es creado por la duración de una sesión HTTP y es descartado cuando la sesión termina. Este ámbito es para objectos que son necesarios a lo largo de la sesión como preferencias de usuario o credenciales de login.

  • Request scope(@RequestScoped): Corresponde a una simple petición HTTP o a una invocación de un método. El bean es creado para la duración de una invocación a un métod y es descartado cuando el método termina. Esto es usado para clases de servicio o beans JSF que son solo necesitados durante la duración de una petición HTTP.

  • Conversation scope(@ConversationScoped): Se extiende entre múltiples invocaciones dentro de los límites de una sesión con puntos de inicio y fin determinados por la aplicación. Este alcance es usado a través de múltiples páginas como parte de un flujo de trabajo de múltiplos pasos.

  • Dependent pseudo-scope(@Dependent): El ciclo de vida es el mismo que del cliente. Un bean @Dependent es creado cada vez que este es injectado y la referencia es eliminada cuando el objetivo de la inyección es eliminado. Éste es el alcance por defecto para CDI.

    Como puedes ver, todos los alcances tienen una anotación que tu puedes usar en tus beans CDI (todas ellas están en el paquete javax.enterprise.context). Los primeros tres alcances son bien conocidos. Por ejemplo, si tu tienes un bean de alcance de sesión como de un carro de compras, el bean será automáticamente creado cuando la sesión inicia (primera vez que un usuario se loguea) y es  destruido automáticamente cuando la sesión termina.

    CitarNotas: El ciclo de vida de un bean @Dependent está ligado al ciclo de vida del bean que lo contiene. Se crea cuando el bean que lo contiene es instanciado y es eliminado cuando el mismo es eliminado. Es el scope por defecto.


    Conversation (@ConversationScoped)

    El alcande conversation es un poco diferente al resto. Este alcance mantiene estados asociados en un intérvalo de tiempo, a través de múltiples peticiones y es eliminado programáticamente (explícitamente) por la aplicación. Este alcance es usado mayormente en procesos  donde se requiere mantener valores durante múltiples peticiones HTTP pero sin extenderse demasiado, como un alcance @SessionScoped.

    API de Conversation:


    La principal diferencia con otros alcances, es que ellos son eliminados automáticamente por el contenedor al cabo de un tiempo cuando termina la petición, sesión, aplicación, etc. En Conversation, nosotros debemos encargarnos terminar su ciclo de vida. Por ejemplo, un bean @ConversationScoped puede ser usado para un asistence de registro de usuario. Deseamos mantener los datos que se van registrando del usuario a través de distintas páginas y que elimine el bean al terminar el registro:

    Código: java
    @Named
    @ConversationScoped
    public class UserRegistrationWizard implements Serializable {
        @Inject
        private Conversation conversation;
        @Inject
        private User user;
        @Inject
        private transient Logger logger;
       
        @PostConstruct
        public void init() {
            conversation.begin();
        }
        public User getUser() {
            return user;
        }
        public void register() {
            conversation.end();
            logger.log(Level.INFO, "Usuario registrado:");
            logger.log(Level.INFO, "Email: {0}", user.getEmail());
            logger.log(Level.INFO, "Password: {0}", user.getPassword());
            logger.log(Level.INFO, "Fecha Nacimiento: {0}", user.getBirthDate());
        }
    }


    Para probarlo, crea distintas páginas JSF y en cada una pon un formulario con un input text para el email, un input secret para el password y un input text para la fecha de cumpleaños:

    start.xhtml

    Código: html5
    <h3>Inicio de registro</h3>
            <h:form>
                <h:outputLabel for="email" value="Email:"/>
                <h:inputText id="email" value="#{userRegistrationWizard.user.email}"/>
                <h:commandButton value="Siguiente" action="step2"/>
            </h:form>


    step2.xhtml

    Código: html5
    <h3>Paso 2</h3>
            <h:form>
                <h:outputLabel for="password" value="Contraseña"/>
                <h:inputSecret id="password" value="#{userRegistrationWizard.user.password}"/>
                <h:commandButton value="Siguiente" action="end"/>
            </h:form>


    end.xhtml

    Código: html5
    <h3>Paso 3</h3>
            <h:form>
                <h:outputLabel for="birthDate" value="F. Nacimiento:"/>
                <h:inputText id="birthDate" value="#{userRegistrationWizard.user.birthDate}">
                    <f:convertDateTime pattern="dd-MM-yyyy"/>
                </h:inputText>
                <h:commandButton value="Registrar" action="#{userRegistrationWizard.register()}">
                    <f:ajax execute="@form" />
                </h:commandButton>
            </h:form>


    Al llegar a la últma página y pulsar el botón registrar, se ejecuta el método registrar(), donde termina la conversación y se imprime toda la información del usuario recolectada durante la misma.


    Interceptores (@Interceptor)

    Los interceptores te permiten agregar conceptos a través de tus beans. Cuando un cliente invoca a un método en un Managed Bean (y por lo tanto a CDI beans, EJB, RESTful, ...), el contenedor puede interceptar la invocación y procesar lógica de negocio antes que el método del bean sea invocado. Los interceptores vienen en cuatro tipos:

  • Interceptores a nivel de constructor: el interceptor es asociado con un constructor de la clase objetivo (@AroundConstruct)
  • Interceptores a nivel  de método: el interceptor es asociado con un método de negocio específico (@AroundInovke)
  • Interceptores por tiempo límite: el interceptor se interpone en el tiempo límite de los métodos (@AroundTimeout (usado solo con EJB timer)
  • Interceptores de ciclo de vida: el interceptor se interpone en las llamadas de eventos del ciclo de vida del objetivo (@PostConstruct y @PreDestroy).

    Un método anotado con @AroundInvoke, debe tener el siguiete patrón de firma:

    Código: java
    @AroundInvoke
    Object <Método> (InvocationContext ic) throws Exception;


    Las siguientes reglas aplican a un método @AroundInvoke:

  • El método puede tener cualquier modificador de visibilidad pero no debe ser static o final.
  • El método debe tener un parámeto InvocationContext y debe retornar Object, el cual es el resultado del método objetivo invocado.
  • El método debe lanzar una excepción comprobada.
  • El objeto InvocationContext permite a los interceptores controlar el comportamiento de la cadena de invocaciones. Si muchos interceptores son encadenados, la misma instancia de InvocationContext es pasada a cada interceptor, quien puede agregar datos contextuales para ser procesador por otros interceptores.

    Echememosle un ojo a la API de InvocationContext:


    Otra forma de trabajar con interceptores, es que estos pueden estar en una clase y luego ser referenciada por la clase que los necesite:

    Código: java
    @Interceptors(MisInterceptores.class)
    public class MiClase { ... }


    O en algún método:

    Código: java
    @Interceptors(MisInterceptores.class)
    public void doSomething() { ... }


    Si queremos que un método no sea interceptado, lo anotamos con @ExcludeClassInterceptors.

    Veamos un ejemplo:


    Vamos con nuestro interceptor:

    Código: java
    public class LoginInterceptor {
        private @Inject Logger logger;

        @AroundConstruct
        private Object init(InvocationContext ic) throws Exception {
            logger.log(Level.INFO, "--- ConstructInterceptor ---");
            logger.log(Level.INFO, "Entrando al constructor");
            try {
                return ic.proceed();
            } finally {
                logger.log(Level.INFO, "Saliendo del constructor");
            }
        }
        @AroundInvoke
        private Object interceptMethod(InvocationContext ic) throws Exception {
            logger.entering(ic.getTarget().toString(), ic.getMethod().getName());
            try {
                logger.log(Level.INFO, "método interceptado");
                return ic.proceed();
            } finally {
                logger.exiting(ic.getTarget().toString(), ic.getMethod().getName());
            }
        }   
    }


    Y nuestra clase que se verá afectada por nuestro inteceptor:

    Código: java
    @Interceptors(LoginInterceptor.class)
    public class LoginService {
        private @Inject Logger logger;
       
        public void login() {
            logger.log(Level.INFO, "Haciendo login");
        }
    }


    Cuando se instancie ésta clase y cuando llamemos al método login(), se ejecutarán los interceptores que hemos definido en LoginInterceptor, que son uno a nivel de método y otro a nivel de constructor.

    CitarTambién se pueden proporcionar más de un interceptor, deben separarse por comas:

    Código: java
    @Interceptors(LoginInterceptor.class, AccountcheckerInterceptor.class)
    public class LoginService { ... }


    Los interceptores serán ejecutados en el orden que son escritos.


    Vinculación de interceptores (@InterceptorBinding)

    Una manera más práctica y que es la más aconsejada por su bajo acoplamiento, es Interceptor Binding. Es muy fácil de hacer, solo basta crear un Interceptor Binding que es muy parecido a un calificador:

    Código: java
    @InterceptorBinding
    @Retention(RUNTIME)
    @Target({METHOD, TYPE})
    public @interface Loggable { }


    El interceptor cambia muy poco, solo debemos anotar con @Interceptor y el interceptor binding que hemos creado, en nuestro caso, @Loggable:

    Código: java
    @Interceptor
    @Loggable
    public class LoginInterceptorBinding { ... }


    Ahora, en nuestra clase objetivo (donde se interceptarán los métodos), solo debemos anotar la clase o los métodos/constructor que queremos interceptar. Por ejemplo:

    Código: java
    public class LoginServiceBinding {
        @Loggable
        public void Login() {
            ...
        }
    }


    Como se puede observar, es mucho más práctico de esta forma y además, más agradable a la vista. Para poder utilizar ésta manera, debemos de agregar nuestro interceptor binding en el descriptor de beans (beans.xml):

    Código: xml
    <beans xmlns="http://xmlns.jcp.org/xml/ns/javaee"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
           http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd"
           bean-discovery-mode="all">
        <interceptors>
            <class>com.acme.interceptors.binding.LoginInterceptorBinding</class>
        </interceptors>
    </beans>


    Prioridad de interceptores

    Los interceptores sigue el orden en el que los hemos escrito en la clase interceptada, pero podemos crear nuestro propio orden. Por ejemplo, tenemos dos interceptores con prioridad diferente:

    Código: java
    @Interceptor
    @CdDisponibility
    @Priority(200)
    public class CdDisponibilityInterceptor {
        @Inject
        private Logger logger;
       
        @AroundInvoke
        public Object checkCdDisponibility(InvocationContext ic) throws Exception {
            try {
                logger.log(Level.INFO, "Ejecuando método interceptor checkCdDisponibility");
                return ic.proceed();
            } finally {
                logger.log(Level.INFO, "Terminando ejecución método interceptor checkCdDisponibility");
            }
        }
    }


    Código: java
    @Interceptor
    @DvdDisponibility
    @Priority(300)
    public class DvdDisponibilityInterceptor {
        @Inject
        private Logger logger;
       
        @AroundInvoke
        public Object checkDvdDisponibility(InvocationContext ic) throws Exception {
            try {
                logger.log(Level.INFO, "Ejecutando método interceptor checkDvdDisponibility");
                return ic.proceed();
            } finally {
                logger.log(Level.INFO, "Terminando ejecución del método interceptor checkDvdDisponibility");
            }
        }
    }


    Ahora, nuestra clase interceptada:

    Código: java
    @CdDisponibility @DvdDisponibility
    public class SellService {
        public void checkDisponibility() {
            ...
        }
    }


    Si ejecutamos, veremos la siguiente salida:

    Código: php
    Información:   Ejecuando método interceptor checkCdDisponibility
    Información:   Ejecutando método interceptor checkDvdDisponibility
    Información:   Terminando ejecución del método interceptor checkDvdDisponibility
    Información:   Terminando ejecución método interceptor checkCdDisponibility


    Como vemos, el primer interceptor (menor prioridad) se ejecuta primero y el segundo (mayor prioridad), se ejecuta después de éste, pero el segundo termina primero. Luego que termina el interceptor de mayor prioridad, el flujo vuelve hacia el de menor prioridad.


    Decoradores (@Decorator)

    Decorador es un patrón de diseño muy común, originalmente acuñado por Gang of Four (la banda de los cuatro). La idea detrás de este patrón es tomar una clase y envolverla con otra (se dice que esta la decora). De esta manera, cuando tu llamas a una clase decorada, debes pasar a través del decorador antes que alcances la clase objetivo. Los decoradores son usados para añadir lógica a un método.

    Tomemos como ejemplo un registro para una tienda de CDs online. Hay la membresía básica, que los permite acceder gratis a 15 CDs. Luego, cuando hay ofertas, viene el decorador que lo que hace es incrementar la cantidad de CDs a 40 (+25 del decorador).

    interface Membership

    Código: java
    public interface Membership {
        public int freeCDs();
    }


    Implementación BasicMembership

    Código: java
    @Basic
    public class BasicMembership implements Membership {
        @Inject
        private Logger logger;
       
        @Override
        public int freeCDs() {
            logger.log(Level.INFO, "[+] Clase BasicMembership");
            logger.log(Level.INFO, "Gratis 15 CDs");
            return 15;
        }
    }


    @Basic es un calificador, que como ya sabemos sigue ésta anatomía:

    Código: java
    @Qualifier
    @Retention(RUNTIME)
    @Target({FIELD, TYPE, METHOD, PARAMETER})
    public @interface Basic { }


    Ahora, veamos nuestro decorador:

    Decorador MembershipGiftDecorator

    Código: java
    @Decorator
    public class MembershipGiftDecorator implements Membership {
        @Inject @Delegate @Basic
        private Membership membership;
        @Inject
        private Logger logger;
       
        public int orderGift() {
            logger.log(Level.INFO, "[+] Clase decoradora para Membership");
            logger.log(Level.INFO, "Regalo momentáneo de 25 CDs");
            return membership.freeCDs() + 25;
        }
        @Override
        public int freeCDs() {
            return orderGift();
        }
    }


    Lo primero que vemos, es que hemos anotado el decorador con @Decorator. Esta anotación le dirá al contenedor que es decorador. Un decorador debe implementar a la interface de la implementación que quiere decorar, es por eso que el decorador implementa también a Membership. Dentro del decorador, vemos que inyectamos la implementación BasicMempership (@Basic) y la anotamos con @Delegate para indicar que ese bean será decorado. A continuación, sobreescribimos el método de la interface y añadimos la lógica de negocio que deseamos. En nuestro caso, agregar 25 CDs gratis.

    Por último hacemos uso del decorador:

    Código: java
    @Named
    public class MembershipBean {
        @Inject @Basic
        private Membership gift;
       
        public void orderGift() {
            gift.freeCDs();
        }
    }


    Vemos que NO inyectamos al decorador, si no al bean normal (@Basic). El contenedor se encarga de añadir la lógica del decorador al método del bean. Si ejecutamos, vemos ésta salida:

    Código: php
    Información:   [+] Clase decoradora para Membership
    Información:   Regalo momentáneo de 25 CDs
    Información:   [+] Clase BasicMembership
    Información:   Gratis 15 CDs


    El orden es porque yo he ejecutado primero la lógica del decorador y al último del bean. Si hubiera sido al revés, el resultado hubiera sido invertido.
    Los decoradores no están activados por defecto en CDI, así que debemos listarlos en el descriptor beans.xml:

    Código: xml
    <decorators>
            <class>com.acme.decorator.MembershipGiftDecorator</class>
    </decorators>


    Eventos

    Los eventos permiten a los beans interactuar con sus dependencias en tiempo de no-compilación. Cuando un bean define un evento, otro bean puede disparar el evento e incluso otro bean puede manejar el evento. Los beans pueden estar en paquetes y capas distintos dentro de la aplicación. El esquema que siguen éstos eventos es el observer/observable, de Gand of four.

    Los productores de los eventos disparan los eventos usando la interface Event (javax.enterprise.event.Event). Un productor eleva eventos llamando el método fire(), pasando el objeto asociado al evento.

    Veamos un ejemplo. En nuestra tienda online de CDs, cada vez que se quiera agregar un ítem al carro, deseamos disparar un evento CDI que envíe el ítem que se desee agregar al método observador addItem y reciba el ítem elegido por el usuario para que se agrege al carro.

    Vamos con nuestro bean CDShopCart:

    Código: java
    @Named("cdShopCart")
    @RequestScoped
    public class CDShopCart {
        @Inject
        private Event<CD> cdAddedEvent;
       
        public void addItem2cart() {
            List<String> songs = new ArrayList<>();
            Collections.addAll(songs,
                    "The day that never comes",
                    "Enter Sandman",
                    "The fourth horseman",
                    "Whiskey in the jar");
            CD cd = new CD("Metallica", songs);
            cdAddedEvent.fire(cd);
        }
    }


    Este bean tendrá un alcande de petición (@RequestScoped), es decir que su ciclo de vida será igual al de una petición HTTP. Esto es así porque solo vamos a utilizar este bean para agregar un ítem (CD) al carro. Vemos que hemos definido una propiedad tipo Event que acepta datos tipo CD. Cuando se ejecute el método addItem2cart, se ejecutará el evento CDI y se enviará un bean CD para que sea procesado por el método observador, que lo define la siguiente clase:

    Código: java
    @SessionScoped
    public class Cart implements Serializable {
        @Inject
        private transient Logger logger;
        private List<CD> items;
       
        @PostConstruct
        public void init() {
            items = new ArrayList<>();
        }
       
        public void addItem(@Observes CD cd) {
            logger.log(Level.INFO, "Nuevo CD añadido al carro");
            logger.log(Level.INFO, "Autor: {0}", cd.getAuthor());
            logger.log(Level.INFO, "Canciones:");
            for(String song : cd.getSongs()) {
                logger.log(Level.INFO, song);
            }
        }
    }


    El método addItem() es el método observador. Este método escucha el evento y cuando se lanza lo captura y recibe el objeto asociado con el evento lanzado, en este caso CD. Es conveniente que la entidad CD sea pasivada (implemente Serializable). Además, vemos que el carro de compras está marcado como @SessionScoped ya que el carro de compras estará ligado siempre a la sesión de un usuario.

    Para disparar el evento, podemos usar JSF, por ejemplo:

    Código: html5
    <h:form>
                <h:commandButton value="Agregar CD al carro" actionListener="#{cdShopCart.addItem2cart()}">
                    <f:ajax execute="@form"/>
                </h:commandButton>
            </h:form>


    Cuando se haga click en el botón, se ejecutará el método y se lanzará el evento. Si hacemos click, veremos lo siguiente en el log:

    Código: php
    Información:   Nuevo CD añadido al carro
    Información:   Autor: Metallica
    Información:   Canciones:
    Información:   The day that never comes
    Información:   Enter Sandman
    Información:   The fourth horseman
    Información:   Whiskey in the jar


    Ahora, para hacer más interesante nuestro ejemplo, podemos crear calificadores llamados @Added y @Removed para identificar a cada evento: Agregar y Eliminar. Para esto, debemos hacer unos pequeños cambios:

    Debemos cambiar el alcance de CDShopCart de @RequestScoped a @SessionScoped:

    Código: java
    @Named("cdShopCart")
    @SessionScoped
    public class CDShopCart implements Serializable {
        @Inject @Added
        private Event<CD> cdAddedEvent;
        @Inject @Removed
        private Event<CD> cdRemovedEvent;
        @Inject
        private CD cd;
       
        public void addItem2cart() {
            List<String> songs = new ArrayList<>();
            Collections.addAll(songs,
                    "The day that never comes",
                    "Enter Sandman",
                    "The fourth horseman",
                    "Whiskey in the jar");
            cd.setAuthor("Metallica");
            cd.setSongs(songs);
            cdAddedEvent.fire(cd);
        }
        public void removeItemFromCart() {
            cdRemovedEvent.fire(cd);
        }
    }


    Los métodos observadores del carro, se verán así:

    Código: java
    public void addItem(@Observes @Added CD cd) {
            logger.log(Level.INFO, "Nuevo CD añadido al carro");
            logger.log(Level.INFO, "Autor: {0}", cd.getAuthor());
            logger.log(Level.INFO, "Canciones:");
            for(String song : cd.getSongs()) {
                logger.log(Level.INFO, song);
            }
        }
        public void removeItem(@Observes @Removed CD cd) {
            logger.log(Level.INFO, "CD Removido");
            logger.log(Level.INFO, "Autor: {0}", cd.getAuthor());
        }


    Prestar atención al calificador después de @Observes. Esto identifica al evento disparado, @Added representa un nuevo CD agreado y @Removed eliminará un CD del carro.

    Nuestra web, se verá así:

    Código: html5
    <h:form>
                <h:commandButton value="Agregar CD al carro" action="#{cdShopCart.addItem2cart()}">
                    <f:ajax execute="@form"/>
                </h:commandButton>
                <h:commandButton value="Eliminar CD del carro" action="#{cdShopCart.removeItemFromCart()}">
                    <f:ajax execute="@form"/>
                </h:commandButton>
            </h:form>






    Eso es todo lo que quería compatirles acerca de CDI. Es un tema muy importante y que pocos entienden y comprenden cómo funciona. Espero que el presente documento es ayuda a entender un poquito mejor cómo funciona CDI y sus nuevas características.

    Nos vemos en otra oportunidad, hasta la próxima.
#3
Java / Subir archivos con Servlet
Julio 05, 2015, 06:15:56 PM
Subir archivos en Java es realmente muy sencillo. Ya Java nos provee interfaces como Part para facilitarnos el trabajo. Veamos un ejemplo:

Vamos con nuestro sencillo formulario:


Código: html5

<h1>Subir archivos</h1>
    <form method="post" action="upload" enctype="multipart/form-data">
    <fieldset>
    <legend>Formulario de subida</legend>
    <input type="file" name="files" id="files" multiple/>
        <button type="submit">Subir</button>
    </fieldset>
</form>


Como podemos observar no hay nada del otro mundo. Simplemente un input tipo file e indicamos que se podrán elegir múltiples archivos. Por últmo, el botón submit.


Servlet

Nuestro servlet será como cualquier otro, pero añadiremos la anotación @MultipartConfig, que básicamente, identificará el servlet como un servlet multipart/form-data.

Código: java
@WebServlet(name = "UploadServlet", urlPatterns = {"/upload"})
@MultipartConfig(location="D:/uploads")
public class UploadServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
   
    protected void processRequest(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        response.setContentType("text/html;charset=UTF-8");
        Collection<Part> parts = request.getParts();
        for(Part part : parts) {
        part.write(getFileName(part));
        }
    }

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        processRequest(request, response);
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        processRequest(request, response);
    }

    public String getFileName(Part part) {
        String contentHeader = part.getHeader("content-disposition");
        String[] subHeaders = contentHeader.split(";");
        for(String current : subHeaders) {
            if(current.trim().startsWith("filename")) {
            int pos = current.indexOf('=');
                String fileName = current.substring(pos+1);
                return fileName.replace("\"", "");
            }
        }
        return null;
    }
}


El atributo location sirve para especificar la ruta donde se guardarán los archivos recibidos. Ésto porsupuesto puede hacerse desde código también. En mi caso, he decidido guardarlas en D:/uploads.

Primero, recibimos todos los part mediante el método getParts() del objeto HttpServletRequest. Cada part viene a ser una parte que representa a cada elemento enviado. El payload de la petición se ve así:

Código: text
------WebKitFormBoundaryKZUc66kZeBth3nDc
Content-Disposition: form-data; name="files"; filename="jsf-scopes.png"
Content-Type: image/png


------WebKitFormBoundaryKZUc66kZeBth3nDc
Content-Disposition: form-data; name="files"; filename="navbar.png"
Content-Type: image/png


Donde Content-Disposition y Content-Type son las cabeceras de las peticiones por cada part (elemento) enviado.  Es importante saber la anatomía de éstas peticiones, porque luego las usaremos para obtener ciertos datos,  como el nombre de los archivos.

Luego, recorremos los parts y por cada uno de ellos, obtenemos su nombre llamando al método getFileName el cual recibe un part y devuelve el nombre del archivo/elemento al que corresponde:

Código: java
public String getFileName(Part part) {
        String contentHeader = part.getHeader("content-disposition");
        String[] subHeaders = contentHeader.split(";");
        for(String current : subHeaders) {
            if(current.trim().startsWith("filename")) {
            int pos = current.indexOf('=');
                String fileName = current.substring(pos+1);
                return fileName.replace("\"", "");
            }
        }
        return null;
    }


Primero obtenemos la cabecera content-disposition la cual como vimos, almacena los datos del elemento enviado. Ahora, obtenemos los datos de esa cabecera y les llamaremos subHeaders. Los obtenemos diviendo el contenido de la cabecera por puntos y comas (;) porque así está formada la petición.

En éste punto ya tenemos los siguientes datos:

  • form-data
  • name
  • filename

    El que nos interesa es filename ya que éste almacena el nombre del archivo enviado. Comprobamos si la subHeader actual comienza con 'filename', si es así,  obtenemos la posición del caracter '=' para obtener todo lo que hay hacia la derecha, que es lo que nos interesa: el nombre del archivo. Por último, reemplazamos las comillas por nada (eliminar) y eliminamos los espacios en blanco al inicio/final del nombre del archivo. El resultado, el nombre del archivo solamente.

    Una vez que obtenemos el nombre del archivo asociado a cada part, los escribimos en el directorio especificado por el atributo location, que será D:/uploads:

    Código: java

    part.write(getFileName(part));


    Por último, verificamos si se han subido  los archivos:



    Eso es todo. ¿Quién dijo que Java es difícil? :P
#4
Siguiendo con estos pequeños artículos de front end development, hoy vamos a realizar un slider utilizando JavaScript y un poco de CSS.

No tienes permitido ver los links. Registrarse o Entrar a mi cuenta
(Click en la imagen para ver el demo)

CitarTodo el código fuente está en Github: No tienes permitido ver los links. Registrarse o Entrar a mi cuenta
QUÉ NECESITAMOS

  • CDN de fontawesome
  • normalize
  • prefixfree (opcional)

    ESTRUCTURA

    La estructura del proyecto será la siguiente:


    MARCADO HTML

    El marcado HTML es realmente muy sencillo. Solo necesitamos crear un wrapper con dos botones, los cuales serán anterior y siguiente:

    Código: html5

    <section class="slider" data-img-folder="img/">
        <button class="prevBtn"><i class="fa fa-2x fa-chevron-left"></i></button>
        <button class="nextBtn"><i class="fa fa-2x fa-chevron-right"></i></button>
        <img src="img/img1.jpg" alt="Image one" class="img" style="right: 0%"/>
    </section>


    Como vemos, el slider será el container y tendrá 2 botones que tienen un ícono (font awesome), que es una flecha en cada dirección. Además, por defecto  añadimos una imagen. El atributo data-img-folder contiene la ruta del folder que contiene las imágenes, en mi caso, img. Ahora pasemos a darle look and feel.

    CSS

    Código: css

    html {
      box-sizing: border-box;
    }
    *, *::before, *::after {
      box-sizing: inherit;
    }
    .slider {
      border-radius: 4px;
      box-shadow: 0 15px 25px rgba(0,0,0,.15),
        0 25px 60px rgba(0,0,0,.25);
      display: block;
      height: 360px;
      margin: 30px auto;
      overflow: hidden;
      position: relative;
      width: 650px;
    }


    El slider tendrá unas dimensiones de 650px x 360px. La posición será relativa para que los botones se puedan ubicar bien. Tiene una sombra y un overflow: hidden. El overflow: hidden lo que hará es ocultar cuando su contenido se desborde. Esto lo hacemos así, porque las imágenes estarán a la derecha del slider, y con una suave transición, se mueven hacia la izquierda ocupando el tamaño del slider o viceversa (izquierda a derecha y derecha a izquierda).

    Las imágenes tendrán unos estilos básicos:

    Código: css

    .slider .img {
      display: block;
      height: 100%;
      position: absolute;
      transition: all .6s ease-in-out;
      width: 100%;
    }


    Como ya habíamos dicho, las imágenes ocuparan todo el ancho y alto del slider. También tiene una posición absoluta para que se puedan desplaza hacia la derecha o izquierda fuera del slider, Por último, tiene un transition, para que el deslizamiento de derecha a izquierda o viceversa sea suave y con una duración de 0.6 segundos.

    Las imágenes podrán estar a la izquierda o derecha, de acuerdo al botón que se presione:

    Código: css

    .slider .img.left {
      left: -100%;
    }
    .slider .img.right {
      right: -100%;
    }


    Por último, hay que darle estilizar los botones de control:

    Código: css

    .slider > .prevBtn, .slider > .nextBtn {
      background-color: transparent;
      border: none;
      color: rgba(250,250,250,.8);
      outline: none;
      position: absolute;
      top: calc(50% - 25px);
      z-index: 1;
    }
    .slider > .prevBtn {
      left: 10px;
    }
    .slider > .nextBtn {
      right: 10px;
    }


    Los botones no tendrán borde ni fondo (transparente). Tienen una posición absoluta lo que nos permitirá centrarla con la propiedad top, que tiene un cálculo de 50% - 25px que es el tamaño estimado de los botones. También, estarán a 10px de los lados izquierdos y derechos respectivamente.

    JAVASCRIPT

    Para éste propósito, y aprovechando las bondades de EcmaScript 6, vamos a crear una clase llamada Slider, que represente al objeto, como en la POO que todos conocemos.

    Vayamos primero por el constructor:

    Código: javascript

    'use strict'
    class Slider {
      constructor(el) {
        this.slider = el
        this.prevBtn = el.querySelector('.prevBtn')
        this.nextBtn = el.querySelector('.nextBtn')
        this.images = []
        this.current = 0
        this.url = this.slider.getAttribute('data-img-folder')
        this.init()
      }


    El constructor recibe un parámetro, éste será el slider que hemos definido en el HTML. A partir de él, obtenemos los botones anterior y siguiente que controlarán el flujo del slider. También inicializamos la variable images como array y current con 0, que indicará la posición de la imagen actual dentro del array; también obtenemos la ruta del folder de las imágenes por medio del atributo especificado en el marcado HTML. Por último, llamamos al método init que vamos a describir a continuación:

    Código: javascript

    init() {
        this.prevBtn.addEventListener('click', function() {
          this.loadPrevImg()
        }.bind(this))
        this.nextBtn.addEventListener('click', function() {
          this.loadNextImg()
        }.bind(this))
        this.loadImages()
    }


    Lo único que hace el método init es escuchar por eventos click a cada botón y llamar a los métodos, loadPrevImg() y loadNextImg(). Por último, llamamos a loadImages(). Veamos que hace éste último método:

    Código: javascript

    loadImages() {
        let self = this;
        let imgUrls = [];
        let http = new XMLHttpRequest()
        http.open('GET', this.url, false)

        http.onload = function() {
          if(http.status === 200) {
            let html = document.createElement('html')
            html.innerHTML = this.response
            let links = html.querySelectorAll('a')
            for(let i=0; i<links.length; i++) {
              let content = (links[i].innerText).trim()
              if(self.isValidImg(content)) {
                imgUrls.push(self.url+content)
              }
            }
          } else {
            console.log('error: '+http.response)
          }
        }
        http.send()
        this.createImagesBlob(imgUrls)
    }


    Lo primero que hacemos es crear un objeto XMLHttpRequest para hacer una llamada AJAX. Abrimos la llamada con la ruta que especificamos en el atributo data-img-folder.

    Si la petición ha sido exitosa, creamos todo un documento html, ésto es, porque la petición a un folder, trae un documento html con cada elemento en un link (lo hace el browser). Por eso 'parseamos' el texto devuelto en un elemento tipo html para poder obtener los links y la ruta de la imagen allí embebida.

    Primero, obtenemos los links y los iteramos. Por cada link obtenemos el texto que tiene y le borramos los espacios con trim(). Luego, verificamos si contiene la extensión jpg, jpeg, png, o gif y si es cierto la añadimos al array de rutas de imágenes para luego precargarlas en blobs.

    El método para determinar su el objeto actual es válido es isValidImg:

    Código: javascript

    isValidImg(file) {
        let suffixes = ['.jpg', '.jpeg', '.png', '.gif']
        for(var i=0; i<suffixes.length; i++) {
            if(endsWith(file, suffixes[i])) {
              return true
            }
        }
        function endsWith(str, suffix) {
          return str.indexOf(suffix, str.length - suffix.length) !== -1;
        }
        return false
    }


    Aquí simplemente itera un array de sufijos de imágenes y verifica si el elemento pasado finaliza con algunas de esas extensiones. Si es así, se trata de una imagen y devolvemos una bandera de luz verde.

    Para crear blobs que representen a las imágenes, tenemos que hacer una llamada ajax por cada url de imagen y convertirla a blob. También es necesario, especificar que se devolverá un blob con la instrucción: http.responseType = 'blob':

    Código: javascript

    createImagesBlob(imgUrls) {
        let self = this;
        for(let i=0; i<imgUrls.length; i++) {
          let http = new XMLHttpRequest()
          http.responseType = 'blob'
          http.open('GET', imgUrls[i], true)
          http.onload = function() {
            if(this.status === 200) {
              self.images.push(window.URL.createObjectURL(this.response))
            }
          }
          http.send();
        } // end for
      }


    Lo único que hacemos en el punto anterior es, hacer una llamada AJAX por cada ruta de imagen y especificar que se deve devolver un blob. Luego, creamos el blob con createObjectURL de la constante URL de window.

    Sigamos con los métodos loadPrevImg y loadNextImg:

    Código: javascript

    loadPrevImg() {
        let self = this
        this.current = (this.current > 0) ? this.current - 1 : this.images.length - 1
        let img = this.createImage('left')
        this.slider.appendChild(img)

        setTimeout(function() {
          img.style.left = '0%'
          // 600 = .6s of slide animation
          setTimeout(function() {
              self.removeBefore(img)
          }, 600)
        },30)
      }
      loadNextImg() {
        let self = this
        this.current = (this.current < this.images.length - 1) ? this.current + 1 : 0
        let img = this.createImage('right')
        this.slider.appendChild(img)

        setTimeout(function() {
          img.style.right = '0%'
          // 600 = .6s of slide animation
          setTimeout(function() {
            self.removeBefore(img)
          }, 600)
        }, 30)
      }


    Los métodos son muy parecidos (tarea, si quieres modulariza). En primera instancia, comprobamos si el índice está dentro del rango, si no, reseteamos (caso siguiente) o volvemos a la última imagen (caso anterior). Luego llamamos al método llamado createImage y le pasamos dos tipos de parámetros: left y right (ya lo veremos a continuación). Éste método crea un elemento IMG, le asigna la ruta de la imagen actual y retorna el elemento.

    Una vez que ha retornado la imagen, le asignamos la propiedad left o right a 0%. Ésto lo que hará es mover la imagen de afuera hacia dentro del slider con la duración y transición que le hemos dado via CSS (¿recuerdas el transition: all .6s ease-in-out?). Por último, una vez que la imagen se ha mostrado, eliminamos la anterior creada. Importante, si no, agregarías la misma imagen una y otra vez y el HTML se haría enorme.

    Veamos el método createImage:

    Código: javascript

    createImage(side) {
        let img = document.createElement('img')
        img.src = this.images[this.current]
        img.classList.add('img')
        if (side === 'right') {
          img.classList.add('right')
        } else {
          img.classList.add('left')
        }
        return img
      }


    Simplemente creamos un elemento IMG y le asignamos la ruta de la imagen a mostrar. Aquí viene algo importante:

  • El parámetro es left: la imagen se deslizará de izquierda a derecha.
  • El parámetro es right: la imagen se deslizará de derecha a izquierda.

    Ahora veamos el método removeBefore:

    Código: javascript

    removeBefore() {
        let child = this.slider.querySelector('img');
        this.slider.removeChild(child);
    }


    Este método es sencillo. Obtenemos el primer elemento tipo img del slider (que será el elemento img creado antes del nuevo) y lo removemos. De ésta manera siempre habrá solo un elemento img. Problemas de rendimiento, creo que ninguno, porque el manejo del DOM será de 2 cambios para 5 segundos.

    Por último, una función para ponerlo en modo automático en caso quiera el usuario:

    Código: javascript

    setModeAuto() {
        setInterval(function() {
          this.loadNextImg()
        }.bind(this), 5000)
    }





    No tienes permitido ver los links. Registrarse o Entrar a mi cuenta

    PD: Disculpen el delay al cargar las imágenes al principio. Lo que pasa es que Github no permite CORS y no pude obtener las imágenes como blob. En local, works like a charm.
#5
Front-end / [CSS3|JS] Menú dropdown
Junio 25, 2015, 04:36:31 PM
En éste post, vamos a crear un menú dropdown, lo que en HTML se llama select, y aplicarle los estilos que deseemos, lo que no podemos hacer con select (puedes customizarlo, pero de forma bastante limitada).


Marcado HTML

Código: html5

<section class="dropdown" tabindex="1">
<span>Choose one skill</span>
<ul>
<li><a href="#">Fullstack developer</a></li>
<li><a href="#">Frontend developer</a></li>
<li><a href="#">Backend developer</a></li>
</ul>
</section>


CSS

El dropdown será un section, y las opciones una lista. El primer hijo, el elemento span, será el texto que se le muestre al usuario antes de que éste escoja un valor. Ahora, veamos que estilos le damos.

Código: css

.dropdown {
    background-color: #fff;
    align-items: center;
    border: 1px solid #c5c5c5;
    border-radius: 3px;
    box-shadow: 0 2px 4px rgba(0,0,0,.15);
    display: flex;
    height: 35px;
    margin: 0;
    outline: none;
    position: relative;
}


El fondo será blanco, tendrá un borde color gris y un redondeado de 3 px. También tiene un alto de 35 px y una sobra ligera inclinada un poco hacia abajo. Tiene una posición relative que nos permitirá ubicar la lista de opciones de acuerdo a él; tiene además un display: inline-flex que hace que los elementos se muestren en forma horizontal y un align-items, que hará que los hijos se centren de acuerdo a su eje vertical.

Por el momento tenemos un rectángulo nada más, no hay indicios que ésto sea un dropdown, así que será necesario ponerle una flecha para que el usuario lo identifique. Para esto usaremos la pseudo clase :after.

Código: css

.dropdown:after {
    align-items: center;
    border-left: 1px solid #d8d8d8;
    color: #555;
    content: "▼";
    display: flex;
    font-family: 'segoe ui';
    font-size: .8rem;
    height: 100%;
    justify-content: center;
    width: 30px;
}


Crearemos un elemento en :after, de tipo flex con un alto de 100% y un ancho de 35px, además un borde izquierdo gris. Como contenido, tendrá una flecha mirando hacia abajo. Ahora, para centrar esa flecha, tendremos que usar align-items y justify-content con el valor center, logrando así un centrado perfecto.

Citar
El mismo centrado lo podemos lograr con las propiedades: line-height, que debe tener el mismo valor que el height y con text-align para centrar el texto en el eje horizontal.

Para el texto por defecto, será muy simple la cosa:

Código: css

.dropdown > span {
color: #777;
       display: block;
font-family: 'segoe ui';
margin: 0;
       padding: 0 .7rem;
}


Un color gris oscuro y un padding horizontal de .7rem.

LA LISTA

La lista es también sencilla. Veamos:

Código: css

.dropdown > ul {
        background-color: #fff;
border: 1px solid #c5c5c5;
border-radius: 3px;
box-shadow: 0 2px 4px rgba(0,0,0,.15);
        display: block;
left: 0;
list-style: none;
margin: 0;
        opacity: 0;
        padding: .45rem 0;
position: absolute;
top: 140%;
        transition: all .2s linear;
        visibility: hidden;
        z-index: 1;
}


Solo algunas pequeñas cosas que rescatar. Tiene un position absolute y un top de 140%, lo que hace que la lista se ubique un poco hacia bajo. También tiene una sombra y una opacidad 0 y visibility hidden, lo que hace que no esté visible la lista. Por último, tiene un z-index de 1, lo que hace que la lista esté sobre el resto de elementos en caso 'detrás' de la lista haya algún otro elemento.

Por cuestiones de estética, le creamos una flecha que indique que el pertenece al dropdown:

Código: css

.dropdown > ul:after {
    background-color: #fff;
    border: 1px solid #c5c5c5;
    border-right-width: 0;
    border-bottom-width: 0;
    content: "";
    display: block;
    height: 10px;
    position: absolute;
    top: -6px;
    right: 10px;
    transform: rotate(45deg) skew(5deg, 5deg);
    width: 10px;
}


Lo hacemos gracias a transform. Simplemente lo rotamos 45 grados y nos da la forma de un rombo, le borramos el borde derecho e inferior y ya tenemos nuestro triángulo. ¿fácil no?

Ahora simplemente vamos a estilizar los list items y los elementos a:

Código: css

.dropdown > ul > li, .dropdown > ul > li > a {
    display: block;
}
.dropdown > ul > li:not(:last-of-type) {
    border-bottom: 1px dashed #ddd;
}
.dropdown > ul > li:hover {
    background-color: #f5f5f5;
}
.dropdown > ul > li > a {
color: #777;
font-family: 'segoe ui';
padding: .5rem .8rem;
        text-align: center;
text-decoration: none;
}


La instrucción .dropdown > ul > li:not(:last-of-type) se traduce a: afecta a todos los elementos li hijos de ul menos el último hijo. Es decir, el borde inferior se lo daremos al primero y al rango entre él y el último (excluido).

Por último, haremos la lista visible cuando se haga click en el menú (y se añadirá la clase active mediante JavaScript):

Código: css

.dropdown.active > ul {
    opacity: 1;
    visibility: visible;
}


JAVASCRIPT

Ya tenemos la parte visual, ahora nos queda darle funcionalidad. Veamos el código JavaScript:

Código: javascript

'use strict';
class Dropdown {
constructor(el) {
this.dropdown = el;
this.display = el.querySelector('span');
this.list = el.querySelector('ul');
this.opts = this.list.querySelectorAll('li');
this.index = -1;
this.value = '';
this.initEvents();
}
initEvents() {
let self = this;
this.dropdown.addEventListener('click', function() {
self.dropdown.classList.toggle('active');
});
[].forEach.call(self.opts, function(cur) {
cur.addEventListener('click', function(e) {
e.preventDefault();
self.value = cur.textContent;
self.display.innerText = self.value;
self.index = Array.prototype.indexOf.call(self.list, cur);
});
});
},
       getValue() {
           return this.value;
       }
       getIndex() {
          return this.index;
       }
}


Primero le pasamos por el constructor nuestro dropdown. En el constructor, guarda el span, la lista y las opciones (li) como variables. También crea dos variables: value e index, las cuales contendrán el texto del valor escogido y el índice del mismo respectivamente. Cuando se escoja una opción, se guarda el texto de la opción escogida y el índice del mismo y se actualiza el texto del span con el texto de la opción que se ha escogido.

Tan solo basta con:

Código: javascript

document.addEventListener('DOMContentLoaded', function() {
let dropdown = new Dropdown(document.querySelector('.dropdown'));
});


Y ya tendremos nuestro dropdown funcional. Si quieremos saber en algún momento en valor o el índice actual, basa con:

Código: javascript
var value = dropdown.getValue();
var index = dropdown.getIndex();



No tienes permitido ver los links. Registrarse o Entrar a mi cuenta


Saludos.
#6
Front-end / [CSS3] Login + Page preload
Junio 24, 2015, 05:37:41 PM
Con el avance de las tecnologías de la web, se pueden hacer cada vez cosas mas bonitas, cosas que sin duda pueden dar una buena UX al usuario de nuestra web o aplicación web.

En el presente texto, haremos un login con algunas animaciones y un preloader que seguramente habrás visto en algunas webs cuando te logueas y aparece algo que demuestra que la web está cargando (animaciones por lo general).

No tienes permitido ver los links. Registrarse o Entrar a mi cuenta
(Click en la imagen para ir al demo)

CitarUsuario: No tienes permitido ver los links. Registrarse o Entrar a mi cuenta
Contraseña: abc123



1. INDEX



  • Necesitaremos importar fontawesome y nornalize.

    La estructura HTML para nuestro login es sencilla:

    Código: html5

    <!DOCTYPE html>
    <html lang="es">
    <head>
    <meta charset="UTF-8">
    <link rel="stylesheet" href="css/vendor/normalize.css"/>
    <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css"/>
    <link rel="stylesheet" href="css/main.css"/>
    <title>Login</title>
    </head>
    <body>
    <section class="main">
    <form id="loginFrm" class="form">
    <figure class="tooltip"></figure>
    <section class="form-head">
    <span>Login</span>
    <figure class="logo"></figure>
    </section>
    <section class="form-body">
    <div class="group">
    <label for="user">Email:</label>
    <input type="text" id="user" class="txt">
    </div>
    <div class="group">
    <label for="pass">Password:</label>
    <input type="password" id="pass" class="txt">
    </div>
    <div class="group flex-end">
    <button type="submit" id="login" class="btn btn-radical">login</button>
    </div>
    </section>
    </form>
    </section>
    <script>
    'use strict';
    window.addEventListener('load', function() {
    let main = document.querySelector('.main');
    main.classList.add('visible');
    });
    </script>
    <script src="js/auth.js"></script>
    </body>
    </html>


    La estructura es muy simple. Consta de un formulario con dos partes:


    • form-head: Aquí tendremos el título del form y el pin (el círculo con el ícono de un usuario).
    • form-body: Aquí tendremos todos los grupos (label/inputs) y botones.

    Vemos que hay un elemento con la clase 'tooltip'. Bien, éste elemento es como su nombre lo dice, un tooltip en cual nos servirá para mostrar los errores de autenticación.

    Ahora veamos el CSS. Lo primero que haremos será aplicar la propiedad box-sizing: border-box a HTML y hacer que todos los elementos hereden de él esa propiedad. La razón de ésto es porque así obligamos a mantener las mismas dimensiones; el padding forma parte de las dimensiones. Con el modelo no, puedes especificar 300px de ancho y con el padding se haría más grande.

    Código: css

    html {
    box-sizing: border-box;
    }
    *, *:before, *:after {
    box-sizing: inherit;
    }


    Luego, aplicamos estilos al container general, es decir, main:

    Código: css

    .main {
    align-items: center;
    background: rgba(136,211,124,1);
    background: linear-gradient(to bottom, rgba(136,211,124,1) 0%,
    rgba(136,211,124,1) 50%,
    rgba(190,144,212,1) 51%,
    rgba(190,144,212,1) 71%,
    rgba(190,144,212,1) 100%);
    background-repeat: repeat;
    display: flex;
    height: 100vh;
    justify-content: center;
    opacity: 0;
    transition: all .2s linear;
    visibility: hidden;
    }
    .main.visible {
    opacity: 1;
    visibility: visible;
    }


    Aplicamos display: flex, align-items: center y justify-content: center para centrar el formulario, un centrado perfecto. Para ésto, .main debe de tener un alto, el cual se lo especificamos en 100vh (100 view height).

    También, le damos una opacidad de 0 y una visibilidad oculta (hidden), además un transition: all .2s ease-in para que al cambiar opacity a 1 y visibility a visible, el efecto sea suave y bonito. Aquí es donde toma importancia .visible, que solamente cambia la opacidad y la visibilidad de .main.

    Ahora vayamos con el formulario:

    Código: css

    .form {
    background-color: #f7f7f7;
    border: none;
    border-radius: 2px;
    box-shadow: 0 2px 5px rgba(0,0,0,.25),
    0 -1px 5px rgba(0,0,0,.1);
    position: relative;
    width: 350px;
    }


    Nada relevante. Solo tiene un color blanco humo, sin bordes, un redondeado de 2px, una sombra, un ancho y lo importante, una posición relative. Esta propiedad nos permitirá situar el pin y el tooltip con relación al formulario.

    Código: css

    .form-head {
    align-items: center;
    border-radius: 2px 2px 0 0;
    border-bottom: 2px solid #9B59B6;
    display: flex;
    height: 45px;
    }
    .form-head > span {
    color: #555;
    font-family: 'segoe ui';
    font-size: 17pt;
    font-weight: lighter;
    margin-left: 12px;
    }


    La cabecera del formulario no tiene nada que explicar. Vayamos con el pin:

    Código: css

    .form-head .logo {
    background-position: center;
    background-repeat: no-repeat;
    background-image: url('http://urbita.com/img/default/default_user_256.png');
    background-size: cover;
    border-radius: 100%;
    box-shadow: 0px 2px 5px rgba(0,0,0,.25);
    height: 85px;
    left: calc(50% - 38.5px);
    margin: 0px;
    position: absolute;
    top: calc(0% - 38.5px);
    width: 85px;
    }


    El pin tiene un ancho y alto de 85px, además un border-radius de 100%, lo que le dará la forma de un círculo. Tiene una pequeña sombra con un offset Y de 2px, lo que hará que la sombra se despligue hacia abajo. Las propiedades de background permiten que la imagen se centre y que cubra el tamaño del círculo.

    La parte más importante es la propiedad position: absolute. Esta instrucción nos permite posicionar el pin de acuerdo al padre. Ahora, podemos centrarlo horizontalmente con solo left: calc(50% - 38.5px) y verticalmente con top: calc(0% - 38.5px) (por cuestión de entendimiento, si preguntas porqué no hice -38px xD). Ahora tenemos en pin centrado en la cabecera. ¿Fácil no?

    Ahora, hagamos algo de magia. Hagamos al pin animado, para ésto, usaremos keyframes:

    Código: css

    @keyframes spin {
    from { transform: rotate(0deg); }
    to { transform: rotate(1800deg); }
    }


    La animación se llamará spin, y el elemento que la use empezará con una rotación de 0 grados y terminará con una rotación de 1800 grados (5 vueltas completas). Ahora, el pin la usará cuando tenga la clase 'auth', que informa que se ha hecho una autenticación en el formulario:

    Código: css

    .form-head .logo.auth {
    animation-name: spin;
    animation-duration: 3000ms;
    animation-iteration-count: 1;
    animation-timing-function: linear;
    animation-play-state: running;
    animation-fill-mode: forwards;
    animation-delay: 0s;
    }


    La duración será de 3 segundos. Solo iterará una vez, será a una velocidad plana (linear) y sin tardanza antes de empezar la animación.

    Ahora vayamos con el cuerpo del formulario:

    Código: css

    .form-body {
    padding: 40px 25px 10px 25px;
    }
    .group {
    align-items: center;
    display: flex;
    margin-bottom: 10px;
    }
    .group > label {
    color: #777;
    display: block;
    flex-grow: 1;
    font-family: 'segoe ui';
    }


    El cuerpo de formulario tendrá un padding de 40px hacia arriba, 25px hacia los lados y 10px hacia abajo (lo último para contrarrestrar el margin-bottom de los grupos, que contiene los label y los input. Los labels serán en forma de bloque y con un flex-grow: 1 que hará que el label tenga un tamaño definido.

    Pondremos éstas 2 clases como apoyo, la primera para alinear los elementos hacia el final del bloque, y el otro para un centrado absoluto:

    Código: css

    .flex-end {
    justify-content: flex-end;
    }
    .flex-abs-center {
    align-items: center;
    justify-content: center;
    }


    Ahora vayamos con los textboxes:

    Código: css

    .txt {
    border: 1px solid #ddd;
    border-radius: 2px;
    color: #777;
    font-family: 'segoe ui';
    font-size: .9rem;
    outline: none;
    padding: .35rem .5rem;
    transition: all .2s ease;
    }
    .txt.invalid {
    border-color: rgba(231,76,60,.7);
    }
    .txt:hover {
    border-color: #ccc;
    }
    .txt:focus {
    border-color: #bbb;
    }


    Nada del otro mundo. La clase invalid solo pintará el borde del textbox de rojo. Ahora los botones:

    Código: css

    .btn {
    border: none;
    border-radius: 2px;
    box-shadow: 0 2px 5px rgba(0,0,0,.2);
    font-family: 'segoe ui';
    font-size: .9rem;
    outline: none;
    padding: .45rem 1.25rem;
    text-transform: uppercase;
    transition: all .3s ease;
    }
    .btn-radical {
    background-color: #F62459;
    color: rgba(255,255,255,.9);
    margin-top: 15px;
    }
    .btn-radical:hover {
    background-color: #DC1F4E;
    }


    Tampoco nada que destacar. Un redondeado de 2px, fuente segoe ui, una ligera sombra y un padding. btn-radical solo le da color (radical) y un margin-top de 15px.

    Por último, finalizaremos con el tooltip:

    Código: css

    .form .tooltip {
    background-color: rgba(0,0,0,.5);
    border-radius: 5px;
    box-shadow: 0px 2px 5px rgba(0,0,0,.25);
    color: rgba(255,255,255,.8);
    font-family: 'segoe ui';
    font-size: .9rem;
    height: 100px;
    left: calc(100% - 35px);
    margin: 0;
    padding: .7rem .6rem;
    opacity: 0;
    position: absolute;
    text-align: center;
    top: -140%;
    transition: all .4s ease-in;
    visibility: hidden;
    width: 200px;
    }


    El tooltip tiene un alto de 200px y un ancho de 100px. Tiene una posición absoluta, lo cual nos hace ubicarlo fácilmente de acuerdo a su padre (formulario). Le damos un left para que quede tirado más a la derecha y un top negativo muy grande para desplazarlo muy arriba, además una opacidad de 1.  Le damos un top muy alto porque en la transición, lo bajaremos y así da la impresión que viene aparece de la nada (por la opacidad también).

    Para hacer el efecto de la flecha, jugaremos un poco con las pseudoclases :before:

    Código: css

    .form .tooltip:before {
    border-color: rgba(0,0,0,.5) transparent;
    border-style: solid;
    border-width: 10px 10px 0px 10px;
    content: "";
    left: 10px;
    position: absolute;
    bottom: -10px;
    }


    Por último, para hacerlo visible, aplicaremos una clase:

    Código: css

    .form .tooltip.visible {
    opacity: 1;
    top: -50%;
    visibility: visible;
    }


    La clase visible, cambia la opacidad del tooltip, visiblidad y le da un top de -50%. Ésto lo que genera, es un efecto que el tooltip viene de arriba y en el camino se hace visible.

    Código JS

    Código: javascript

    'use strict';
    document.addEventListener('DOMContentLoaded', function() {
    let loginFrm = document.querySelector('#loginFrm');

    loginFrm.addEventListener('submit', function(e) {
    e.preventDefault();

    const ANIMATION_TIMEOUT = 3000; // duration of logo animation
    let logo = document.querySelector('.logo');
    let user = document.querySelector('#user').value.toLowerCase();
    let pass = document.querySelector('#pass').value;

    // if logo already is animated, the info is already processing
    if(logo.classList.contains('auth')) {
    return false;
    }

    logo.classList.add('auth'); // start animation

    // check credentials
    if(user == '[email protected]' && pass == 'abc123') {
    window.setTimeout(function() {
    sessionStorage.setItem('full_access', false);
    window.location.href = 'views/home.html';
    }, ANIMATION_TIMEOUT);
    } else {
    window.setTimeout(function() {
    logo.classList.remove('auth'); // remove animation of logo
    showTooltip();
    }, ANIMATION_TIMEOUT);
    }
    });

    // show and hide tooltip
    function showTooltip() {
    let tooltip = loginFrm.querySelector('.tooltip');
    tooltip.textContent = 'El email o contraseña ingresados no son correctos. Verifique e inténtelo nuevamente.';
    tooltip.classList.add('visible');
    // after 4 seconds, the tooltip disappear
    window.setTimeout(function() {
    tooltip.classList.remove('visible');
    }, 4000);
    }
    }); // end script


    El script es muy simple. Escuchamos por evento submit del formulario y obtenemos lo que se ha ingresado en las cajas de texto; también añadimos una constante llamada ANIMATION_TIMEOUT que especifica el tiempo que durará la animación del pin.

    Añadimos la clase 'auth' al pin para empezar la animación. Comprueba las credenciales y si coinciden crear una sessión para el usuario (HTML5 WebStorage) y redirige hacia home, todo ésto con un delay del mismo tiempo que dura la animación, esto es, que una vez que acaba la animación, se redigirá hacia home.

    Si las credenciales no coindicen, quitamos la clase 'auth' del pin para poder volver a iniciar la animación las veces que se requiera. Además, mostramos el tooltip informando de los errores de credenciales. El mismo tooltip desaparecerá al cabo de 4 segundos.

    El script embebido lo único que hace es hacer visible el index para darle el efecto de 'aparición':

    Código: javascript

    'use strict';
    window.addEventListener('load', function() {
    let main = document.querySelector('.main');
    main.classList.add('visible');
    });




    2. HOME



    HTML

  • Normalize.css requerido
  • Prefixfree requerido

    Código: html5

    <!DOCTYPE html>
    <html lang="es">
    <head>
    <meta charset="UTF-8">
    <link rel="stylesheet" href="../css/vendor/normalize.css"/>
    <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css"/>
    <link rel="stylesheet" href="../css/home.css"/>
    <script src="js/vendor/prefixfree.min.js"></script>
    <title>Home page</title>
    </head>
    <body>
    <section class="preload"><i class="fa fa-4x fa-circle-o-notch fa-spin"></i></section>
    <section class="main">
    <header>
    <nav class="navbar">
    <!-- brand -->
    <a href="#" class="brand">Oh yeah!<i class="fa fa-2x fa-hand-o-right"></i></a>
    <!-- menu -->
    <ul class="menu">
    <li><a href="#">Messages<i class="fa fa-envelope"></i></a></li>
    <li>
    <a href="#">John doe<i class="fa fa-caret-down"></i></a>
    <ul>
    <li><a href="#" id="profile-img">
    <img src="http://i.imgur.com/IxWfQy2.jpg"/></a>
    </li>
    <li><a href="#">Profile<i class="fa fa-user"></i></a></li>
    <li><a href="#">Settings<i class="fa fa-cog"></i></a></li>
    <li><a href="#" id="logout">Logout<i class="fa fa-sign-out"></i></a></li>
    </ul>
    </li>
    </ul>
    </nav>
    </header>
    </section>
    <script src="js/homeui.js"></script>
    </body>
    </html>


    El marcado es simple. Consta de un header y un nav, con una lista incluída con dos elementos: Messages y un dropdown. El dropdown contiene una lista que son las opciones del usuario logueado.

    CSS

    Primero hacemos todos los elementos border-box:

    Código: css

    html { box-sizing: border-box; }
    *, *:before, *:after { box-sizing: inherit; }


    Preload:

    El preload es un simple etiqueta section con un elemento i dentro, el cual será la animación. Veamos que elementos tiene:

    Código: css

    .preload {
    align-items: center;
    background-color: #F22613;
    display: flex;
    height: 100vh;
    justify-content: center;
    left: 0;
    opacity: 0;
    position: absolute;
    top: 0;
    transition: all .5s ease;
    visibility: hidden;
    width: 100%;
    z-index: 1;
    }
    .preload.visible {
    opacity: 1;
    visibility: visible;
    }
    .preload i {
    color: rgba(255,255,255,.9);
    }


    Como vemos, preload ocupa el 100%(100vh) de alto y ancho, además es de posición absoluta y se posiciona en el vértice izquierdo, lo que junto con sus medidas hace que cubra toda la pantalla. Tiene una opcidad de 0 y una visibilidad oculta (hidden) por defecto, además se comporta como flexbox y tiene un centrado absoluto (flex-align: center y justify-content: center).

    El color del ícono animado es de color blanco con una opacidad de .9 (hace que se note apenas el fondo). La clase que activa la animación, se llama visible y lo único que hace es cambiar la opacidad a 1 y la visibilidad a visible.

    El elemento i es animado con la ayuda de font awesome, pero se puede hacer manualmente, quizás lo veamos en otro tutorial.

    Main

    El main es donde están todos nuestros elementos. Es el container general. Tiene las siguientes propiedades:

    Código: css

    .main {
    opacity: 0;
    transition: all .6s ease;
    visibility: hidden;
    }
    .main.visible {
    opacity: 1;
    visibility: visible;
    }


    Creo que en este punto ya sabemos que significa... Oculto por defecto y cuando se aplica la clase visible, se hace visible con animación. Ahora veamos el header y el nav:

    Código: css

    header {
    background-color: #22A7F0;
    color: rgba(255,255,255,.9);
    display: block;
    }
    .navbar {
    align-items: center;
    color: inherit;
    display: flex;
    justify-content: space-between;
    margin: 0px;
    padding: 0 .5rem;
    width: 100%;
    }


    Ambos tienen un ancho de 100%. El header es block y nav es flex. El nav tiene la propiedad justify-content: space-between, que hace que los hijos de alineen hacia los lados dejando todo el espacio restante del padre entre ellos. ¿Cuáles son los hijos? Son el logo de la empresa y la lista.

    El logo de la empresa tiene la clase brand y tiene los siguientes estilos:

    Código: css

    .brand {
    align-self: stretch;
    align-items: center;
    color: inherit;
    display: flex;
    font-family: 'segoe ui';
    margin: 0px;
    padding: 0 .25rem;
    }
    .brand > i {
    margin-left: .8rem;
    }


    De entrada, podemos ver que el brand tiene la propiedd align-self: stretch. Ésta propiedad sobreescribe el comportamiento por defecto de un hijo flex en fila (por defecto display: flex) en el eje Y. Es decir, si el padre especifica un align-items: center y el hijo especifica align-self, stretch, predomina el comportamiento del hijo. El valor stretch hace que el hijo se estire ocupando todo el alto del padre.

    Veamos la lista y sus elementos:

    Código: css

    .navbar ul {
    display: block;
    list-style: none;
    margin: 0px;
    padding: 0px;
    }
    .navbar > .menu {
    display: block;
    }
    .menu > li {
    display: inline-block;
    transition: all .1s ease-in;
    }
    .menu > li:hover {
    background-color: #1C99DD;
    }


    La lista es un bloque y sus elementos son bloque en línea, lo que hace que se muestre en forma horizontal dentro de la lista. Además, tienen una transición que anima el cambio de fondo cuando se hace hover sobre ellos. Además, ellos tendrán un margen a la derecha de 55px siempre y cuando no sea el último elemento de la lista y sus hijos (elementos a) serán elementos de bloque y tendrán cierto padding:

    Código: css

    .menu > li > a {
    display: block;
    padding: 1rem .5rem;
    }
    .menu > li > a > i {
    margin-left: 10px;
    }
    .menu > li:not(:last-of-type) {
    margin: 0 55px 0 0;
    }


    Veamos ahora, el dropdwon que serán las opciones del usuario.

    Código: css

    /* last li (profile dropdown) */
    .menu > li:last-of-type {
    position: relative;
    }
    /* dropdown */
    .menu > li:last-of-type > ul {
    background-color: #22A7F0;
    border-radius: .1725rem;
    left: -50%;
    opcity: 0;
    padding: .5rem 0;
    position: absolute;
    top: 170%;
    visibility: hidden;
    transition: all .14s ease-in;
    }
    /* arrow of menu */
    .menu > li:last-of-type > ul:after {
    border-color: #22A7F0 transparent;
    border-style: solid;
    border-width: 0 8px 8px 8px;
    content: "";
    position: absolute;
    right: 8px;
    top: -7px;
    }
    .menu > li:last-of-type:hover > ul {
    opacity: 1;
    top: 130%;
    visibility: visible;
    }


    El menú de usuario (dropdown), tendrá una posición relative, lo que permitirá aplicar a su lista interna, una posición absoluta y posicionarla debajo. La lista como ya dijimos tiene posición absoluta, una opacidad de 0, visibilidad oculta (hidden), un left de -50% lo que hará que la lista se desplace a la izquierda y un top de 170% que posiciona la lista muy abajo.

    Cuando se haga hover en el menú de usuario, la lista interna se hace visible (opacidad, visibility) y el top se reduce a 130%, lo que da el efecto de desplazamiento hacia arriba mientras se hace visible.

    Los li y a de la lista interna, ambos son block lo que hace que se muestre en forma de columna. Además, los li tienen se animan en cuanto se haga hover sobre ellos y se cambie el color.

    Código: css

    /* dropdown list items*/
    .menu > li:last-of-type > ul > li, .menu > li:last-of-type > ul a {
    display: block;
    font-size: .9rem;
    transition: transform .1s ease-in;
    }
    /* dropdown options */
    .menu > li:last-of-type > ul a {
    padding: .5rem .9rem;
    width: 8rem;
    }
    /* a elements of dropdown options */
    .menu > li:last-of-type > ul li:hover {
    background-color: #1998DC;
    }
    /* margin left to dropdown icon */
    .menu > li:last-of-type > a > i {
    margin-left: .8rem;
    }
    /* dropdown options icons */
    .menu li:last-of-type > ul a > i {
    float: right;
    margin-top: .3125rem;
    }


    Por último, la sección donde irá el avatar del usuario:

    Código: css

    /* profile image */
    #profile-img {
    display: flex;
    justify-content: center;
    margin-bottom: 5px;
    pointer-events: none;
    width: 100%;
    }
    #profile-img > img {
    background-position: center;
    background-size: cover;
    border-radius: 50%;
    display: block;
    height: 5rem;
    width: 5rem;
    }


    La sección que contiene la imagen del usuario es un bloque tipo flex que ocupa el 100% y que alinea la imagen al centro horizontal (justify-content: center). La imagen tiene un ancho y alto de 5rem (80px) y la imagen estará centrada y ocupara todos los 80px.

    El resultado:


    Código JS:

    Código: javascript

    'use strict';
    window.addEventListener('load', function() {
    let full_access = sessionStorage.getItem('full_access');
    let preload = document.querySelector('.preload');
    let main = document.querySelector('.main');
    // full_access is gived when user enter for first time
    if(full_access == "false") {
    // show preload animation
    preload.classList.add('visible');
    // animation delay 3s. After that, the main content appear
    window.setTimeout(function() {
    preload.classList.remove('visible');
    main.classList.add('visible');
    },3000);
    // change the flag. Now, the user has full access
    sessionStorage.setItem('full_access', true);
    }
    // if user has full_access, just show the page
    else {
    main.classList.add('visible');
    }
    });
    document.addEventListener('DOMContentLoaded', function() {
    document.querySelector('#logout').addEventListener('click', function(e) {
    e.preventDefault();
    // remove user's session
    sessionStorage.removeItem('full_access');
    // redirect to login
    window.location.href = '/PreloadCSS';
    });
    });


    Primero obtenemos el item 'full_access' de la sesión. Como sabemos, en el login, lo inicializamos en false cuando se ha logueado el usuario por primera vez.Seleccionamos el preload y el main y los guardamos en variables locales.

    Verificamos, si full_access es false (lo será la primera vez que se loguee) se le aplica la clase 'visible' al preload, que como recordamos, al aplicarle visible se muestra el preload y su animación.

    Creamos un delay de 3 segundos (el tiempo que tarda la animación) y cuando termine removemos la clase active del preload, haciendo que se oculte y agregamos la clase visible al main, haciendo que éste se haga visible. Al final, cambiamos el valor full_access por true.

    Si ya se tiene acceso total, simplemente se agrega la clase visible al main, haciendolo visible (sin mostrar el preload). Lo anterior lo aplicamos en el evento load de window.

    Por último, cuando el dom esté cargado, escuchamos por evento click en la opción logout del menú de usuario, removiendo la sesión y redirigiendo al login (fíjense que éste tbn tiene un efecto).


    No tienes permitido ver los links. Registrarse o Entrar a mi cuenta
#7
Back-end / Tuneando el doc root de Apache
Mayo 10, 2015, 07:14:32 PM
Se me ocurrió ver mis proyectos "con estilo", así que, hice un pequeño script PHP y JS para que mis proyectos PHP se vean como una colección.

Los únicos requisitos son:


  • jQuery 2.x
  • Poner una imagen .png, .jpg, .jpeg o .gif en el root de nuestros proyectos.

Primero escribamos una función que lea los folders (proyectos) que hay en nuestro document root:

Código: php
function getAll() {
    $files = scandir(".");
    $folders = array(); // solo carpetas (nombres)
    $images = array(); // imagenes de las carpetas
    $pack = array(); // carpetas e imagenes

    foreach($files as $current) {
        if($current == "." || $current == "..") {
            continue;
        } else {
            if(is_dir($current)) {
                array_push($folders, $current);
            }
        }
    }
}


Ya tenemos solo las carpetas que hay en nuestro document root. Ahora, necesitamos checkear cada carpeta para buscar la imagen que usarmos como portada del proyecto:

Código: php
function getAll() {
    $files = scandir(".");
    $folders = array(); // solo carpetas (nombres)
    $images = array(); // imagenes de las carpetas
    $pack = array(); // carpetas e imagenes

    foreach($files as $current) {
        if($current == "." || $current == "..") {
            continue;
        } else {
            if(is_dir($current)) {
                array_push($folders, $current);
            }
        }
    }
    foreach($folders as $folder) {
        $files = scandir($folder);
        foreach($files as $file) {
            // si la carpeta está vacía (. y ..)
            if(count($files) < 3) {
                array_push($images, NULL);
                break;
            }
            // si no está vacía, ignora el . y .. leídos
            if($file == "." || $file == "..") {
                continue;
            }
            // checkea si el fichero es una imagen y la agrega al array
            if(strpos($file, ".jpg") || strpos($file, ".jpeg")
               || strpos($file, ".png") || strpos($file, ".gif")) {
               array_push($images, $file);
            }

        }
    }
    $pack["folders"] = $folders;
    $pack["images"] = $images;

    return json_encode($pack);
}


Ya tenemos los nombres de las carpetas e imágenes, pero necesitamos obtenerlos desde JS. Lo podemos hacer fácilmente:

Código: javascript
$(document).on("ready", init);

function init() {
    var all = <?php echo getAll(); ?>;
}


Ya las tenemos en el cliente, solo nos basta crear los elementos HTML que representarán nuestros proyectos y que utilizarán las imágenes de cada proyecto como portadas. En caso no el proyecto no tenga imagen, se colocará una que está encodeada en base64. Veamos como lo realizamos:

Código: javascript
$(document).on("ready", init);

function init() {
    var all = <?php echo getAll(); ?>;

    createDivs(all);

    function createDivs(data) {
        var folderNames = data["folders"];
        var folderImages = data["images"];
        var html = "<div class='project'>"+
            "<section class='back'>"+
            "<a class='link' href='#'>IR</a>"+
            "</section>"+
            "<section class='content'>"+
            "<section class='project-header'></section>"+
            "<section class='project-body'><img /></section>"+
            "</section>"+
            "</div>";
        for(var i=0; i<folderNames.length; i++) {
            $(".wrapper").append(html);
            var current = $(".project")[i];
            $(current).find(".project-header").html(folderNames[i]);
            // si la imagen existe (no es null)
            if(folderImages[i] !== undefined && folderImages[i] !== null) {
                $(current).find(".project-body > img").attr("src", folderNames[i]+"/"+folderImages[i]);
            } else { // de lo contrario asigna una por defecto en base64
                $(current).find(".project-body > img").attr("src", "data:image/jpg;base64,"+defaultImage);
            }
            $(current).find(".link").attr("href", folderNames[i]+"/"); // establece el link del proyecto
         }

var defaultImage = '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AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/9k=';
    }
}


Ya tenemos toda nuestra lógica. Ahora solo nos queda hacer el HTML darle un poco de "estilo":

HTML

Código: html5
<!DOCTYPE html>
<html lang="es">
<head>
<link rel="icon" type="img/image/png" href="http://i.imgur.com/Fy36fAm.png"/>
<meta charset="UTF-8">
<title>Projects</title>
<!-- aquí irán nuestros estilos -->
</head>
<body>
<header>
<nav class="navbar">
<span>ÉSTOS SON TUS PROYECTOS</span>
</nav>
</header>
<section class="wrapper"></section>
<script src="http://code.jquery.com/jquery-latest.js"></script>
        <!-- aquí irá nuestro código JS -->
</body>
</html>


CSS

Código: css

* {
margin: 0px;
padding: 0px;
}
body {
background: #fff;
padding: 25px 50px;
}
.wrapper {
display: flex;
flex-flow: row wrap;
}
header {
display: block;
margin-bottom: 32px;
width: 100%;
}
.navbar {
display: block;
padding: .5rem;
text-align: center;
width: 100%;
}
.navbar > span {
font-family: 'dosis', 'segoe ui';
font-size: 24pt;
font-weight: normal;
}
.project {
background-color: #f7f7f7;
border-radius: 2px;
border-top: 2px solid #1E90FF;
box-shadow: 0px 2px 2px rgba(0,0,0,0.3);
display: flex;
height: 250px;
margin: 0px 30px 30px 0px;
overf low: hidden;
width: 200px;
}
.project:hover {
cursor: pointer;
}
.project:hover > .back {
width: 100%;
}
.project:hover > .content {
width: 0px;
}
.project > .content {
display: block;
height: 100%;
transition: width .3s ease-out;
width: 100%;
}
.project > .back {
align-items: center;
background-color: #eee;
display: flex;
height: 100%;
justify-content: center;
overflow: hidden;
transition: width .3s ease-out;
width: 0px;
}
.back > .link {
align-items: center;
color: #1e90ff;
display: flex;
font-family: 'segoe ui';
font-size: 20pt;
height: 100%;
justify-content: center;
padding: .8rem 1.5rem;
text-decoration: none;
text-transform: uppercase;
width: 100%;
}
.project-header {
align-items: center;
color: #555;
display: flex;
font-family: "segoe ui";
height: 40px;
justify-content: center;
text-align: center;
text-transform: uppercase;
}
.project-body {
display: block;
height: calc(100% - 40px);
}
.project-body > img {
border: 0px !important;
height: 100%;
width: 100%;
}


Ya está, probemos cómo se ve.




ANTES


DESPUÉS


Para ir hacia el proyecto, solo hay que colocar el cursor en el proyecto y una capa se deslizará, mostrándonos el link hacia el proyecto:





Simple y bonito. El código completo lo pueden ver en éste gist: No tienes permitido ver los links. Registrarse o Entrar a mi cuenta
#8
Java / Mini curso Java [En progreso]
Mayo 03, 2015, 10:27:43 PM

Hola, mi nombre es Gustavo y soy parte del STAFF de Underc0de. A partir de mañana, escribiré un post diario dedicado a JAVA, los cuales serán parte de un minicurso, que se irá completando con el tiempo. Asi mismo, efspero éstos tutoriales sirvan como formación a postulantes al equipo de desarrollo de Underc0de que desean ser parte del equipo Java y no cuentan con los conocimentos necesarios o que quieran profundizar conocimientos.

CitarCualquier sugerencia, por favor, háganmelo saber por MP

CitarEn caso tengan problemas con los ejercicios planteados, abrir un tema de la manera normal




ANTES DE EMPEZAR

Herramientas:



ÍNDICE


Java básico

1. ¿Qué es java? ¿Para qué sirve? (✔)
2. Método main (✔)
3. Tipos de datos (✔)
4. Operadores (✔)
5. Variables y consantes (✔)
6. Clase System (✔)
7. Entrada, proceso y salida de datos (✔)
8. Modificadores de acceso/visibilidad (✔)
9. El concepto de static (✔)
10. Estructuras de control (if, else, else if) (✔)
11. Estructuras repetitivas (while, do while, for) (✔)
12. Switch (✔)
13. Clase Math (✔)

POO

¿Qué es POO? Introducción teórica (✔)
Clases, métodos y objetos (✔)
POJO
this
Encapsulamiento
Herencia
Polimorfismo
Abstracción
¿Por qué es importante el concepto de acoplamiento?
Interfaces
Callbacks

Java intermedio

1. Clase String
2. Wrappers
3. Generics
4. Collections
5. ¿Qué es el ClassLoader?
6. Inicialización est\u00e1tica (static block)
7. Archivos (API java.nio)
  7.1 Lectura
  7.2 Escritura
  7.3 Trabajando con objetos y ficheros
7. Threads
8. Multithreading
9. Timer y TaskTimer
9. Sockets

JDBC

1. Introducción a JDBC
2. Conexión a BBDD
3. Consultas
4. Statement vs PreparedStatement
5. Metadata
6. Consultas complejas (inner join, subconsultas, union, etc)

GUI

Introducción a las GUI
Patrón MVC
Introducción a JavaFX
JavaFX Scene Builder
Vistas y controladores
Modelos
Aplicación de ejemplo

Java Avanzado

1. Diferencias entre JavaSE  y JavaEE
2. Introducción a JavaEE
3. JSP
4. Servlets y ciclo de vida
5. Paso de par\u00e1metros entre JSP's
6. Sesiones
7. Cookies
8. AJAX

Persistencia

1. JPA - introducción
2. PersistenceUnit
3. EntityManager y EntityManagerFactory
2. Mapeo de tablas
3. Relaciones 1:1
4. Relaciones 1:N y N:1
5. Relaciones N:M
6. Ejemplo

EJB

1. ¿Qué es un EJB?
2. Tipos de EJB
3. EJB de sesión
4. Beans locales y remotos
4. EJB dirigidos por mensajes

CDI

1. ¿Qué es CDI?
2. Contextos de inyección
3. @Named y @Model

Webservices

1. Introducción
2. SOAP y RESTful
3. RESTful
4. MIME types
5. Ejemplo

JSF

¿Qué es JSF?
2. Ciclo de vida
3. Facelets
4. Controladores
5. Controles comunes
6. Estructuras de control
7. Estructuras repetitivas
8. JSF AJAX
9. Bean validation
10. Passthrought: HTML5 y JSF

WebSockets

1. ¿Qué es un websocket? ¿Cómo funciona?
2. Decoders
3. Encoders
4. Aplicación de ejemplo



Cada día se desarrollará un tema. Si algún día no publico, será porque estaré verdaderamente ocupado, en exámenes o cosas por el estilo. De todas formas trataré de no romper el flujo. Cualquier comentario, crítica o sugerencia, me la hacen saber. Si desean colaborar con algún tema, me avisan para colocar su aporte.
#9
Después de 1 año y 1/2, JAVA vuelve a posicionarse como top 1 en el índice de lenguajes más populares, estadísticas generadas por TIOBE. Cabe aclarar que éstas estadísticas son generadas por:


  • Habilidades de desarrolladores calificados (Lenguaje primario) a nivel mundial
  • Cursos por lenguaj
  • Frameworks/librerías

Y son usados la mayoría de buscadores (Google, Bing, Yahoo, Wikipedia, Baidu, Amazon y Youtube) para los rankings.


Veamos algunas gráficas de popularidad de algunos lenguajes a través del tiempo:

C#


PHP


JAVA


JavaScript


Alternativamente, podemos comparar el ranking TIOBE con PyPL. PyPL realiza su ranking en base a búsquedas de tutoriales de cada lenguaje en Google, obteniendo la info de Google Trends.





¿Ya decidiste qué lenguaje aprender?
#10

CÓDIGO LIMPIO - R. C. MARTIN




DESCRIPCIÓN

Cada año, se invierten innumerables horas y se pierden numerosos recursos debido a código mal escrito, ralentizando el desarrollo, disminuyendo la productividad, generando graves fallos e incluso pudiendo acabar con la organización o empresa. El reconocido experto de software Robert C. Martin, junto con sus colegas de Object Mentor, nos presentan sus óptimas técnicas y metodologías ágiles para limpiar el código sobre la marcha y crearlo de forma correcta, de este modo mejorará como programador.

Esta obra se divide en tres partes. La primera describe los principios, patrones y prácticas para crear código limpio. La segunda incluye varios casos de estudio cuya complejidad va aumentando. Cada ejemplo es un ejercicio de limpieza y transformación de código con problemas. La tercera parte del libro contiene una lista de heurística y síntomas de código erróneo (smells) confeccionada al crear los casos prácticos. El resultado es una base de conocimientos que describe cómo pensamos cuando creamos, leemos y limpiamos código. Imprescindible para cualquier desarrollador, ingeniero de software, director de proyectos, jefe de equipo o analista de sistemas interesado en crear código de mejor calidad. ¡El libro que todo programador debe leer!

DESCARGA

No tienes permitido ver los links. Registrarse o Entrar a mi cuenta
#11
Java / Patrones de diseño
Abril 26, 2015, 08:31:32 PM

PATRONES DE DISEÑO



CAPÍTULO I

Introducción

Los patrones de diseño son la base para la búsqueda de soluciones a problemas comunes en el desarrollo de software y otros ámbitos referentes al diseño de interacción o interfaces.
Un patrón de diseño resulta ser una solución a un problema de diseño. Para que una solución sea considerada un patrón debe poseer ciertas características. Una de ellas es que debe haber comprobado su efectividad resolviendo problemas similares en ocasiones anteriores. Otra es que debe ser reutilizable, lo que significa que es aplicable a diferentes problemas de diseño en distintas circunstancias.

Los patrones de diseño pretenden:

  • Proporcionar catálogos de elementos reusables en el diseño de sistemas software.
  • Evitar la reiteración en la búsqueda de soluciones a problemas ya conocidos y solucionados anteriormente.
  • Formalizar un vocabulario común entre diseñadores.
  • Estandarizar el modo en que se realiza el diseño.
  • Facilitar el aprendizaje de las nuevas generaciones de diseñadores condensando conocimiento ya existente.

    Asimismo, no pretenden:

  • Imponer ciertas alternativas de diseño frente a otras.
  • Eliminar la creatividad inherente al proceso de diseño.

    No es obligatorio utilizar los patrones, solo es aconsejable en el caso de tener el mismo problema o similar que soluciona el patrón, siempre teniendo en cuenta que en un caso particular puede no ser aplicable. "Abusar o forzar el uso de los patrones puede ser un error".


    PATRÓN ADAPTER



    El patrón adapter nos permite ampliar la funcionalidad de una clase o interfaz adaptando objetos que no coinciden con una determinada jerarquía de clases.

    Convierte la interfaz de una clase en otra interfaz que el cliente espera. Adapter permite a las clases trabajar juntas, lo que de otra manera no podría hacerlo debido a sus interfaces incompatibles.

    Cuándo usarlo:

  • Se desea usar una clase existente, y su interfaz no se iguala con la necesitada.
  • Cuando se desea crear una clase reusable que coopera con clases no relacionadas, es decir, las clases no tienen necesariamente interfaces compatibles.


    Problema: Se nos pide adaptar un auto eléctrico a un sistema de abastecimiento de combustible para autos. Siendo que el auto eléctrico, utiliza como combustible la energía eléctrica.

    Solución: Utilizar el patrón Adapter para extender la funcionalidad de la interfaz adaptando el auto eléctrico.

    Creamos una interfaz llamada Car que representará de forma genérica a un Auto:

    No tienes permitido ver los links. Registrarse o Entrar a mi cuenta
    Código: java
    package org.underc0de.adapter;

    public interface Car {

    void fillTank();
    default void start() {
    System.out.println("Encendiendo auto...");
    }
    }


    NOTA: el método start() lo predefinimos porque será el mismo para todos las implementaciones.

    Ahora sus implementaciones: GasolineCar y GasCar. Representan un auto a gasolina y otro a gas.

    No tienes permitido ver los links. Registrarse o Entrar a mi cuenta
    Código: java
    package org.underc0de.adapter;

    public class GasolineCar implements Car {

    public GasolineCar() {
    super(); // llamada al constructor padre
    System.out.println("Creando un auto a gasolina...");
    }

    @Override
    public void fillTank() {
    System.out.println("Colocando gasolina...");
    }
    }


    No tienes permitido ver los links. Registrarse o Entrar a mi cuenta
    Código: java
    package org.underc0de.adapter;

    public class GasCar implements Car {

    public GasCar() {
    super(); // llamada al constructor padre
    System.out.println("Creando un auto a gas...");
    }

    @Override
    public void fillTank() {
    System.out.println("Colocando gas...");
    }

    }


    Ambos autos, GasolineCar y GasCar necesitan combustible para funcionar. Por eso, el método fillTank llena el combustible de ellos, dependiendo si es gasolina o gas.

    Ahora queremos añadir un auto eléctrico a nuestra jerarquía de clases.

    No tienes permitido ver los links. Registrarse o Entrar a mi cuenta
    Código: java
    package org.underc0de.adapter;

    public class ElectricCar {

    public ElectricCar() {
    super(); // llamada al constructor padre
    System.out.println("Creando un auto eléctrico...");
    }

    public void connect() {
    System.out.println("Conectando motor a generador de electricidad...");
    }
    }


    Nos damos con la sorpresa que éste auto no coincide con nuestra jerarquía. La interfaz Car dice que todos los autos que la implementen deben tener el método fillTank. Pero, ¿Como un auto eléctrico puede llenar el tanque? ¿Cómo hacemos para adaptar éste auto a nuestra jerarquía?

    Un error es común es modificar la interfaz o clase abstracta. Ésto viola el principio OCP, el cual nos dice:
    Citar
    Las entidades de software deben ser abiertas para ser extendidas y cerradas para no ser modificadas.

    Aquí toma importancia en patrón Adapter. Éste patrón nos permite ampliar la funcionalidad de una interfaz si tener que cambiar código en ella. Veamos como funciona.

    Código: java
    package org.underc0de.adapter;

    public class ElectricCarAdapter implements Car {

    ElectricCar electricCar;

    public ElectricCarAdapter() {
    electricCar = new ElectricCar();
    }

    @Override
    public void fillTank() {
    electricCar.connect();

    }

    }


    Como vemos hemos podido adaptar nuestro auto eléctrico a nuestra interfaz Car. ¡Ahora, podemos crear tanto autos a gasolina, gas o eléctricos aplicando polimorfismo!

    Veamos que salida nos arroja:

    No tienes permitido ver los links. Registrarse o Entrar a mi cuenta
    Código: java
    package org.underc0de.adapter;

    public class AdapterTest {

    public static void main(String[] args) {
    Car gasolineCar = new GasolineCar();
    gasolineCar.fillTank();
    gasolineCar.start();

    System.out.println();

    Car gasCar = new GasCar();
    gasCar.fillTank();
    gasCar.start();

    System.out.println();

    Car electricCar = new ElectricCarAdapter();
    electricCar.fillTank();
    electricCar.start();
    }

    }


    Salida:

    Código: php
    Creando un auto a gasolina...
    Colocando gasolina...
    Encendiendo auto...

    Creando un auto a gas...
    Colocando gas...
    Encendiendo auto...

    Creando un auto eléctrico...
    Conectando motor a generador de electricidad...
    Encendiendo auto...


    Y así podemos extender la funcionalidad de nuestra aplicación de forma sencilla y eficiente.


    PATRÓN FACADE



    Descripción: El patrón fachada viene motivado por la necesidad de estructurar un entorno de programación y reducir su complejidad con la división en subsistemas, minimizando las comunicaciones y dependencias entre éstos.

    Cuándo usarlo:

  • Se usa para proporcionar una interfaz sencilla para un sistema complejo.
  • Se quiere desacoplar un subsistema de sus clientes u otros subsistemas, haciéndolo mas independiente y portable.
  • Se quiera dividir los sistemas en niveles: las fachadas serian el punto de entrada a cada nivel.

    Pros/contras:

  • + La principal ventaja del patrón fachada consiste en que para modificar las clases de los subsistemas, sólo hay que realizar cambios en la interfaz/fachada, y los clientes pueden permanecer ajenos a ello. Además, y como se mencionó anteriormente, los clientes no necesitan conocer las clases que hay tras dicha interfaz.
  • - Como inconveniente, si se considera el caso de que varios clientes necesiten acceder a subconjuntos diferentes de la funcionalidad que provee el sistema, podrían acabar usando sólo una pequeña parte de la fachada, por lo que sería conveniente utilizar varias fachadas más específicas en lugar de una única global.

    Problema: Crear una aplicación que haga tres operaciones bancarias: Crear una cuenta, depositar dinero y retirar dinero. Las operaciones deben hacerse dentro de una sola entidad que las maneje.

    Solución: Aplicar el patrón Facade para encapsular todos los objetos que hacen las 3 operaciones.

    No tienes permitido ver los links. Registrarse o Entrar a mi cuenta
    Código: java
    package org.underc0de.facade;

    public class Bank {

    public Bank() {

    }

    public void createAccount(String account) {
    System.out.println("Creando cuenta N° "+account);
    }
    }


    No tienes permitido ver los links. Registrarse o Entrar a mi cuenta
    Código: java
    package org.underc0de.facade;

    public class Deposit {

    public Deposit() {

    }

    public void makeDeposit(double amount, String account) {
    System.out.println("Se ha depositado $"+amount+" a la cuenta "+account);
    }

    }


    No tienes permitido ver los links. Registrarse o Entrar a mi cuenta
    Código: java
    package org.underc0de.facade;

    public class Withdrawal {

    public Withdrawal() {

    }

    public void makeWidthdrawal(double amount, String account) {
    System.out.println("Se ha retirado $"+amount+" de la cuenta "+account);
    }

    }


    Ahora, creamos nuestra clase principal:

    No tienes permitido ver los links. Registrarse o Entrar a mi cuenta
    Código: java
    package org.underc0de.facade;

    public class Facade {

    public static void main(String[] args) {

    Bank bank = new Bank();
    Deposit deposit = new Deposit();
    Withdrawal withdrawal = new Withdrawal();

    bank.createAccount("9343435093");
    deposit.makeDeposit(2599.90, "9343435093");
    withdrawal.makeWidthdrawal(699.90, "9343435093");

    }

    }


    Como vemos creamos 3 objetos los cuales se encargan de efectuar las acciones. Pero, ¿es necesario crear éstos tres objetos en éste ambito? ¿Los vamos a necesitar siempre?

    Una mejor idea sería encapsular éstos objetos dentro de uno solo que se encargue de realizar todas las operaciones. Éste es el propósito del patrón Facade, actuar como intermediario entre la interfaz y las funcionalidades de un sistema. Veamos como se representa:

    No tienes permitido ver los links. Registrarse o Entrar a mi cuenta
    Código: java
    package org.underc0de.facade;

    public class OperationsFacade {


    public OperationsFacade() {
    }

    public void createAccount(String account) {
    new Bank().createAccount(account);
    }

    public void makeDeposit(double amount, String account) {
    new Deposit().makeDeposit(amount, account);
    }

    public void makeWithdrawal(double amount, String account) {
    new Withdrawal().makeWidthdrawal(amount, account);
    }

    }


    Como podemos observar, ésta clase encapsula el comportamiento de las clases Bank, Deposit y Withdrawal. Ya no tenemos que declarar los objetos porque éstos se crean en los métodos de OperationsFacade y se destruyen al finalizar el mismo. ¡Además ahorramos memoria!

    Ahora, veamos como queda la clase principal:

    No tienes permitido ver los links. Registrarse o Entrar a mi cuenta
    Código: java
    package org.underc0de.facade;

    public class FacadeTest {

    public static void main(String[] args) {
    OperationsFacade facade = new OperationsFacade();
    facade.createAccount("9343435093");
    facade.makeDeposit(2599.90, "9343435093");
    facade.makeWithdrawal(699.90, "9343435093");
    }

    }


    Como podemos ver, solo necesitamos de un objeto OperationsFacade para realizar todas las operaciones. Así aplicamos también el principio KISS (Keep it simple stupid!).

    Salida:

    Código: php
    Creando cuenta N° 9343435093
    Se ha depositado $2599.9 a la cuenta 9343435093
    Se ha retirado $699.9 de la cuenta 9343435093



    PATRÓN ABSTRACT FACTORY



    Descripción: El patrón Abstract Factory nos permite crear, mediante una interfaz, conjuntos o familias de objetos (denominados productos) que dependen mutuamuente y todo esto sin especificar cual es el objeto concreto.

    Cuándo usarlo:

  • Un sistema debe ser independiente de como sus objetos son creados.
  • Un sistema debe ser 'configurado' con una cierta familia de productos.
  • Se necesita reforzar la noción de dependencia mutua entre ciertos objetos.


    Pros/contras:

  • + Brinda flexibilidad al aislar a las clases concretas.
  • + Facilita cambiar las familias de productos.
  • - Para agregar nuevos productos se deben modificar tanto las fabricas abstractas como las concretas.

    Problema: Se nos pide crear animales de X tipo sin especificar cuál es el objeto en concreto.

    Solución: Utilizar el patrón Abstract Factory para crear los objetos solicitados.

    Primero, creamos una clase abstracta llamada Animal que representará de forma genérica a un Animal:

    No tienes permitido ver los links. Registrarse o Entrar a mi cuenta
    Código: =java
    package org.underc0de.factory;

    public abstract class Animal {

    protected String type;
    protected String family;
    protected String habitat;

    public Animal(String type, String family, String habitat) {
    this.type = type;
    this.family = family;
    this.habitat = habitat;
    }

    public String getType() {
    return type;
    }

    public void setType(String type) {
    this.type = type;
    }

    public String getFamily() {
    return family;
    }

    public void setFamily(String family) {
    this.family = family;
    }

    public String getHabitat() {
    return habitat;
    }

    public void setHabitat(String habitat) {
    this.habitat = habitat;
    }

    @Override
    public String toString() {
    return "Tipo de animal: "+getType()+"\nFamilia: "+getFamily()+
    "\nHábitat: "+getHabitat();
    }
    }


    Y tres animales que extienden de Animal:

    No tienes permitido ver los links. Registrarse o Entrar a mi cuenta
    Código: java
    package org.underc0de.factory;

    public class Dog extends Animal {

    public Dog(String type, String family, String habitat) {
    super(type, family, habitat);
    }

    }


    No tienes permitido ver los links. Registrarse o Entrar a mi cuenta
    Código: java
    package org.underc0de.factory;

    public class Shark extends Animal {

    public Shark(String type, String family, String habitat) {
    super(type, family, habitat);
    }


    }


    No tienes permitido ver los links. Registrarse o Entrar a mi cuenta
    Código: java
    package org.underc0de.factory;

    public class Lion extends Animal {

    public Lion(String type, String family, String habitat) {
    super(type, family, habitat);
    }


    }


    Bien, ya tenemos nuestros 3 tipos de animales que heredan de Animal: Dog, Shark y Lion. Como la tarea es construir objetos sin especificar de qué tipo son, creamos una fábrica abstracta que especifica la creación de un Animal genérico sin especificar de qué tipo:

    No tienes permitido ver los links. Registrarse o Entrar a mi cuenta
    Código: java
    package org.underc0de.factory;

    public interface AbstractAnimalFactory {

    Animal createAnimal();
    }


    Ahora, el siguiente paso es hacer las implementaciones de ésta fábrica abstracta, es decir las fábricas concretas que crearán los objetos concretos:

    No tienes permitido ver los links. Registrarse o Entrar a mi cuenta
    Código: java
    package org.underc0de.factory;

    public class DogFactory implements AbstractAnimalFactory {

    protected String type;
    protected String family;
    protected String habitat;

    public DogFactory(String type, String family, String habitat) {
    this.type = type;
    this.family = family;
    this.habitat = habitat;
    }

    @Override
    public Animal createAnimal() {
    return new Dog(type, family, habitat);
    }

    }


    No tienes permitido ver los links. Registrarse o Entrar a mi cuenta
    Código: java
    package org.underc0de.factory;

    public class SharkFactory implements AbstractAnimalFactory {

    protected String type;
    protected String family;
    protected String habitat;

    public SharkFactory(String type, String family, String habitat) {
    this.type = type;
    this.family = family;
    this.habitat = habitat;
    }

    @Override
    public Animal createAnimal() {
    return new Shark(type, family, habitat);
    }

    }


    No tienes permitido ver los links. Registrarse o Entrar a mi cuenta
    Código: java
    package org.underc0de.factory;

    public class LionFactory implements AbstractAnimalFactory {

    protected String type;
    protected String family;
    protected String habitat;

    public LionFactory(String type, String family, String habitat) {
    this.type = type;
    this.family = family;
    this.habitat = habitat;
    }

    @Override
    public Animal createAnimal() {
    return new Lion(type, family, habitat);
    }

    }


    Analicemos un poco el código. Cada una de las implementaciones de AbstractAnimalFactory especifican la implementación que tendrá para crear un tipo de animal.

    Cada factoría concreta, tiene las 3 propiedades que necesita cada objeto para poder crearse. Así mismo, dichos datos se pasan como parámetros a su constructor para establecer los valores en las propiedades que se utilizarán para crear una instancia del objeto.

    Establece las propiedades:

    Código: java
    public LionFactory(String type, String family, String habitat)


    Y usa esas mismas propiedades para crear el objeto:

    Código: java
    @Override
    public Animal createAnimal() {
    return new Lion(type, family, habitat);
    }


    Finlamente, se sobre-escribe el método de la factoría abstracta createAnimal() para retornar un nuevo objeto de acuerdo al tipo de factoría. Así, la factoría de Perros, creará objetos tipo Perro, la factoría de Tiburones creará objetos tipo Tiburón y la factoría de Leones, crearán objetos tipo León.

    Por último, especificamos una Fábrica global que utilizará las 3 fábricas para crear los objetos (Aquí se aplica también el patrón Facade):

    No tienes permitido ver los links. Registrarse o Entrar a mi cuenta
    Código: java
    package org.underc0de.factory;

    public abstract class AnimalFactory {

    public static Animal create(AbstractAnimalFactory factory) {
    return factory.createAnimal();
    }
    }


    Ésta clase recibe un objeto que implemente la interfaz AbstractAnimalFactory, por lo tanto podrá recibir objetos tipo: DogFactory, SharkFactory y LionFactory. Luego simplemente usa la factoría especificada para crear un animal mediante polimorfismo.

    Ahora construyamos nuestra clase principal para probar el funcionamiento del patrón:

    No tienes permitido ver los links. Registrarse o Entrar a mi cuenta
    Código: java
    package org.underc0de.factory;

    public class FactoryTest {

    public static void main(String[] args) {
    Animal dog = AnimalFactory.create(new DogFactory("Perro","Caninos","Doméstico"));
    Animal shark = AnimalFactory.create(new SharkFactory("Tiburón", "Lámnidos", "Mar"));
    Animal lion = AnimalFactory.create(new LionFactory("León", "Felinos", "Selva"));

    System.out.println(dog.toString());
    System.out.println();
    System.out.println(shark.toString());
    System.out.println();
    System.out.println(lion.toString());
    }
    }


    Utilizamos el método estático create() de AnimalFactory que recibe una implementación de la interfaz AbstractAnimalFactory, en éste caso un objeto DogFactory al que se le asignan los valores "Perro", "Caninos" y "Doméstico" y finalmente AnimalFactory llama al método createAnimal() de DogFactory para crear un animal tipo Dog y devolverlo hacia AnimalFactory que lo devuelve y lo guarda en el objeto 'dog'. El mismo procedimiento es para todas las factorías.

    Salida:

    Código: php
    Tipo de animal: Perro
    Familia: Caninos
    Hábitat: Doméstico

    Tipo de animal: Tiburón
    Familia: Lámnidos
    Hábitat: Mar

    Tipo de animal: León
    Familia: Felinos
    Hábitat: Selva


    Y de ésta manera sencilla, podemos usar tantas fábricas como queramos para poder crear objetos si generar dependencias ;)


    PATRÓN SINGLETON



    Cuándo usarlo:

  • Cuando la aplicación requiere que solo exista una instancia de un determinado objeto.

    Problema: Encapsular la configuración de una aplicación en un objeto y compartirlo con los demás objetos de la aplicación que lo requiera.

    Solución: Utilizar el patrón Singleton para encapsular la configuración de la aplicación.

    Para utilizar éste patrón se deben seguir dos reglas:

  • El primer paso es hacer el constructor privado para que no se pueda llamar y por lo tanto, no se puedan crear instancias.
  • El segundo paso es crear una instancia de la clase y devolverla mediante un método estático.

    Teniendo en cuenta éstas reglas, creamos una clase que representa a la configuración de la aplicación:

    No tienes permitido ver los links. Registrarse o Entrar a mi cuenta
    Código: java
    package org.underc0de.singleton;

    import java.util.HashMap;
    import java.util.Map;

    public class Configuration {

    private Map<String,Object> appOptions = null;
    private static Configuration config;

    private Configuration() {

    }

    public static Configuration getConfiguration() {
    if(config == null) {
    config = new Configuration();
    }
    return config;
    }

    public Map<String,Object> getAppOptions() {
    if(appOptions == null) {
    appOptions = new HashMap<>();
    appOptions.put("theme", "dark");
    appOptions.put("show_hidde_files", true);
    }
    return appOptions;
    }

    public void setAppOptions(Map<String,Object> appOptions) {
    this.appOptions = appOptions;
    }

    }


    Y simplemente, cuando la necesitemos, obtenemos su única instancia:

    No tienes permitido ver los links. Registrarse o Entrar a mi cuenta
    Código: java
    package org.underc0de.singleton;

    import java.util.Map;

    public class SingletonTest {

    public static void main(String[] args) {
    Configuration cfg = Configuration.getConfiguration();

                    // recorre el hashmap para leer las claves y valores
    for(Map.Entry<String, Object> entry: cfg.getAppOptions().entrySet()) {
    System.out.println(entry.getKey()+": "+entry.getValue());
    }

    }

    }


    Salida:

    Código: php
    show_hidde_files: true
    theme: dark



    NOTA IMPORTANTE:

    Generalmente, cuando se usa éste patrón, se garantiza que solamente existirá una instancia de dicha clase. Pero, si se trata de una aplicación web y se va a integrar una aplicación web con otra, es probable que ya no exista una sola instancia.

    Ésto se debe a los ClassLoaders. Cada contenedor de cada aplicación web (WAR) tiene su propio ClassLoader, por lo que en el supuesto caso de una integración de WARs, las instancias serán dos y no una como se había previsto.

    Una mejor forma de de implementar el patrón Singleton es mediante un enum, como recomienda Joshua Bloch. Al ser un enum, todas sus propiedades serán constantes únicas, no se pueden crear nuevas, solo utilizar las ya descritas.

    Código: java
    public enum Configuration {
    INSTANCE;

    private Map<String, Object> options;

    public Map<String, Object> getOptions() {
    if(options == null)
    fillOptions();
    return options;
    }

    private void fillOptions() {
    options = new HashMap<>();
    options.put("theme", "Dark");
    options.put("show_hidden_files", true);
    }

    }



    En el capítulo II, veremos más patrones de diseño. Nos vemos.




    By Gus.
#12
Back-end / [JEE] Servicios REST - primer contacto
Abril 25, 2015, 11:20:18 AM
Un servicio web nos provee mucha utilidad y flexibilidad. Al estar basado en la arquitectura Cliente Servidor, nos permite mucha escalabilidad horizontal. Para empezar, crearemos una POJO:

Código: java

public class Song implements Serializable {
private Integer id;
private String name;
private Long duration;

public Song() {

}
public Song(Integer id, String name, Long duration) {
this.id = id;
this.name = name;
this.duration = duration;
}
// getters y setters
}


Ésta clase es un Java Bean, no un POJO. Un POJO debe cumplir los siguientes requisitos:

  • Variables privadas
  • Un constructor vacío
  • Getters y setters
  • No implementar nada del exterior

    Un POJO no debe implementar ni heredar nada. Por otro lado, un JavaBean, tiene como requisito implementar la interface Serializable. La interface Serializable hace que el objeto sea descompuesto/recompuesto en una secuencia de Bytes, permitiendo así que éste objeto pueda ser transportado en diferentes ámbitos como pueden ser: ficheros, sockets, peticiones HTTP, etc.

    Un JavaBean debe de implementar Serializable porque éste va a ser transportado o persistido, por lo que, si no implementara la interface Serializable, no podría ser posible su transporte.

    Ahora, hagamos una clase que sirva como servicio o comúnmente llamado DAO:

    Código: java

    public class SongService {
    private static final Map<Integer, Song> songs;
    static {
    songs = new HashMap<>();
    songs.put(1, new Song(1, "Enter Sandman", 3500L));
    songs.put(2, new Song(2, "The day that never comes", 5000L));
    songs.put(3, new Song(3, "Whiskey in the jar", 4000L));
    }
    public static Song find(Integer id) {
    return songs.get(id);
    }
    // otros metodos que simulan el crud (create, findAll, update, remove)
    }


    Ésta clase simula un servicio o DAO. Dispone de un método find, que recibe un entero que representa a un ID. Éste ID es buscado es el Map y es retornado hacia el contexto en que se llamó.

    Ahora, resta crear nuestro REST:

    Código: java

    @Path("/music")
    @Stateless
    public class MusicServiceREST {
    @Path("/find/{id}")
    @Produces(MediaType.APPLICATION_JSON)
    public Song findMetallica(@PathParam("id") Integer id) {
    return SongService.find(id);
    }
    // otros servicios (findAll, update, remove, etc)
    }


    Aquí es necesario explicar algunas cosillas:


    • @Path indica una ruta y ésta ruta será mapeada; es decir, todas las peticiones que se hagan a dicha ruta las interceptará el REST.

    • @Stateless indica que dicha clase es un EJB (Enterprise Java Bean). Un Enterprise Java Bean es una especificación de Java EE, el cual trae una serie de novedades como: JMS, control de concurrencia, transacciones (flujos), servicios de nombre y directorios. Éste concepto lo dejaremos para después. Al estar la clase anotada con @Stateless estamos diciendo que esa clase es un EJB y que soporta las características antes mencionadas. Por ejemplo, al soportar transacciones, aquí se puede inyectar un EntityManager y que el EJB sea el encargado de manejar las transacciones (cuando se abren y cierran). En éste caso, al no haber entidades no es necesario.

    • @Produces indica qué MIME retornará la petición a dicha ruta (@Path). Aquí podemos especificar que se devolverá el MIME que se crea necesario: text/plain, text/html, application/json, application/xml, etc. En nuestro caso hemos dicho que se devolverá un objeto JSON, que es la representación de nuestro objeto Song.

    • @PathParam indica el parámetro que se va a formatear en el tipo de dato deseado. En nuestro caso, el parámetro enviado lo especificamos entre llaves, y se llamará id ({id}), así que, formateamos éste parámetro a entero para poder buscar al objeto Song con dicho ID.

    Es necesario registrar nuestro REST. Para ello, solo basta hacer:

    Código: java

    @ApplicationPath("api")
    public class RestApplication extends Application {
        @Override
        public Set<Class<?>> getClasses() {
        Set<Class<?>> rests = new HashSet<>();
        rests.add(MusicServiceREST.class);
        return rests;
        }
    }


    Solo debemos de agregar el class de los REST que tengamos creados para registrarlos. Una vez hecho lo anterior, ya tenemos todo hecho. Ahora, para probarlo, solo tipea la url:

    Código: html5
    http://localhost:8080/tu_proyecto/api/music/find/3


    La URL anterior, buscará la canción con el ID 3 y lo retornará como JSON:

    Código: javascript
    {
        "id": 3,
        "name": "Whiskey in the jar",
        "duration": 4000L
    }


    En caso se quiera consumir algo, por ejemplo, un XML, debemos de utilizar la anotación @Consumes:

    Código: java
    @Path("/upload")
    @Consumes(MediaType.APPLICATION_XML)
    @Produces(MediaType.TEXT_HTML)
    public String upload(final InputStream IS) {
        /* proceso */
    }


    El caso anterior es sin utilizar JAXB. Si utilizáramos JAXB no necesitaríamos el InputStream. Como la petición puede venir desde todo tipo de clientes, es necesario capturarla con un InputStream para poder procesarla.

    Ésta es la forma básica en que se crea un servicio REST en Java. Generalmente, se usa un REST como capa intermedia entre el acceso a datos y el cliente.



    Librerías necesarias:

  • jersey-server
  • jersey-json
#13
Front-end / [HTML5] Pizarra virtual
Abril 19, 2015, 05:24:23 PM
Descripción: Simple pizarra virtual con soporte para toda la paleta de colores, grosor el pincel y borrador.

Características:

  • Diversidad de colores.
  • Grosor del pincel entre 1 y 30.
  • Borrador.
  • Limpiador de lienzo (full clean).
  • Responsivo.





    C{0}digo fuente



    El source lo pueden encontrar en mi github: No tienes permitido ver los links. Registrarse o Entrar a mi cuenta

    Espero que le sirva a alguien. Nos vemos.




    By Gus.
#14
Estaba viendo el foro, y cuando veo los temas de una sección, lo siento muy fuerte a la vista. Pensé en algunos retoques para ésta area:

Reemplazar:

Código: css

.table_grid tr {
    border: 1px solid #111;
}


Por:

Código: css

.table_grid tr {
  border: 1px solid rgba(0,0,0,.1); /* Borde menos sólido */
}





Reemplazar:

Código: css

.table_grid .catbg {
   background: rgba(0, 0, 0, 0.5)
}



Por:

Código: css

.table_grid .catbg {
background: #f2f2f2; /* Color al header */
}





Reemplazar:

Código: css

table.table_grid td {
    padding: 3px;
    /*border: 1px solid #CACACA;*/
    color: #848484;
}


Por:

Código: css

table.table_grid td {
    padding: 6px; /* un poco más de padding */
    /*border: 1px solid #CACACA;*/
    color: #848484;
}





Reemplazar:

Código: css

.pagesection {
    background-color: /* No recuerdo el color xD*/;
    font-size: 0.9em;
    padding: 0.2em;
    overflow: hidden;
    position: relative;
}


Por:

Código: css

.pagesection {
    background-color: #f2f2f2;
    font-size: 0.9em;
    padding: 0.4em; /* mas padding */
    overflow: hidden;
    position: relative;
    width: calc(100% - 2*.45em); /* Para que quede alineado */
}





Reemplazar:

Código: css

table.table_grid {
background: #00080D;
border-collapse: collapse;
margin-top: 0.1em;
box-shadow: 0 0 10px #848484;
}


Por:

Código: css

table.table_grid {
background: #00080D;
border-collapse: collapse;
         border-top: 2px solid #a1a1a1; /* Un borde para separar */
/*margin-top: 0em; No es necesario */
box-shadow: 0 0 10px #848484;
}





Remplazar:

Código: css

/*Mod ANTRAX*/
table.table_grid tr:hover > td {
background-color: #777;
color:  #ccc;
}
table.table_grid tr:hover > td a {
color: #f8f8f8;
}
table.table_grid tr:hover > td p {
color: #ccc;
}





Reemplazar:

Código: css

.description_board {
    overflow: auto;
    margin: 1em 1px 0 1px;
}


Por:

Código: css

.description_board {
    color: #ccc;
    overflow: auto;
    margin: 1em 1px 0 1px;
}



Resultado



Antes:


Después:



Así lo veo más limpio e incomoda menos la vista. Los bordes de cada tr son demasiado fuertes en mi opinión. Saludos.
#15
Java / [JavaEE | HTML5] Minichat con WebSockets
Abril 10, 2015, 05:50:11 PM

JwsChat - Minichat con WebSockets y HTML5






Descripción: Éste sencillo chat demuestra lo fácil que es la comunicación bidireccional con la nueva API para WebSockets de Java EE 7 combinado con el poder de HTML5.

Funcionalidades:

  • Elegir un nombre de usuario (único por sesión).
  • Personalizar el chat eligiendo un color proveído.
  • Desconectar/Reconectar manteniendo las preferencias.
  • Ver la lista de usuarios conectados.


    Imágenes







    Código fuente



    El código fuente lo pueden encontrar en mi Github: No tienes permitido ver los links. Registrarse o Entrar a mi cuenta



    By Gus.
#16
Java / [Tutorial] Peticiones AJAX a Servlets
Abril 10, 2015, 10:17:23 AM


SERVLETS Y AJAX



El presente pequeño tutorial, tiene como finalidad mostrar cómo interactuar entre Servlets y javascript, mediante AJAX, tomando como ejemplo un formulario de ingreso.


Se asume que el lector tiene noción de las siguientes tecnologías:

  • Lenguaje de programación Java
  • HTTP
  • Servlets
  • javascript
  • AJAX

    Si no se tiene nociones de las mencionadas tecnologías, se puede seguir el tutorial, ya que se explicarán algunos puntos. De todas maneras, el lector debe de investigar cada punto, término, trozo de código o afines que no entienda.

    Herramientas necesarias

    Para seguir perfectamente éste tutorial, se necesitarán las siguientes herramientas:

  • Eclipse Luna EE: IDE para programación en Java Enterprise Edition. No solo soporta el API estándar de Java, si no que también nos brinda soporte para la API empresarial.
  • NetBeans Java EE. Gran IDE con muy buen soporte para Java Enterprise Edition. Excelente editor de código y muy buen auto completado.
  • GlassFish: Servidor para aplicaciones Java web.
  • org.json.jar: Librería que nos permite transformar un Map o String a JSONObject y viceversa.



    Zona de descargas




    Notas de aclaración

    Dada la simpleza que requiere éste tutorial, NO se empleará un gestor de base de datos. El objetivo del presente documento es mostrar de forma práctica y sencilla, la comunicación entre servlets y AJAX.


    CONFIGURACIÓN DE ECLIPSE E INSTALACIÓN DE GLASSFISH



    Instalación de GlassFish Tools en Eclipse

    Abrimos nuestro Eclipse Luna EE. Si es primer uso, se preguntará dónde desea que sea el espacio de trabajo, por defecto es en la carpeta de usuario pero se puede especificar donde se desea (siempre y cuando tengamos permisos).

    Una vez abierto Eclipse, tenemos que instalar GlassFish Tools para poder instalar nuestro servidor GlassFish. Para ésto vamos a la opción Eclipse Marketplace del menú Help y buscamos "glassfish":


    Hacemos click en Install. Nos preguntará si aceptamos la licencia, elejimos el radio button "Yes. I accept..." y empezará a instalar GlassFish Tools (Si pregunta algo durante la instalación, dale "Yes"). Al finalizar la instalación nos pedirá reiniciar Eclipse. Aceptamos.

    Instalación de GlassFish 4.1

    Descomprimimos el zip que nos hemos bajado de la web de GlassFish y lo colocamos en:

    CitarNo tienes permitido ver los links. Registrarse o Entrar a mi cuenta Files/

    De tal modo que la instalación de GlassFish quede así:

    CitarNo tienes permitido ver los links. Registrarse o Entrar a mi cuenta Files/glassfish/

    Abrimos Eclipse y nos dirigimos hacia la opción Preferences del menú Window. Aquí desplegamos el menú Server -> Runtime Environments:


    Hacemos click en Add y nos mostrará una ventana con los adaptadores de servidores disponibles. Escogemos GlassFish 4 y hacemos click en Next:


    En la siguiente cara, tendremos que especificar la dirección de la instalación de GlassFish y el JDK:


    NOTA: Es bien importante que el elemento en "Java development kit" sea el JDK y no el JRE. Si les aparece JRE deben de agregar un nuevo "installed JRE" dentro de Java -> Installed JREs. Para ésto solo basta hacer click en Add y agregar la ruta del JDK de Java.

    Por último hacemos click en Next, dejamos todo por defecto y hacemos click en Finish para finalizar. Ya tenemos nuestro IDE listo para empezar a programar.


    INSTALAR NETBEANS Y GLASSFISH



    Al momento de instalar NetBeans Java EE, GlassFish viene incorporado. También Tomcat, pero pueden elegir no instalarlo sacando el checkbox de Tomcat 8:

#17
Java / [Tutorial] JPA 2.1 - Parte I
Abril 10, 2015, 09:48:35 AM
TUTORIAL JPA 2.1



El presente tutorial tiene como finalidad mostrar los aspectos básicos de la especificación de Java JPA (API de Persistencia de Java por sus siglas en inglés). Se cubrirán los aspectos más básicos de ésta API para que el lector se dé cuenta del potencial que nos ofrece y que se puede aplicar en nuestros proyectos. Así mismo,




¿QUÉ ES JPA?


Como en todo proceso de aprendizaje, primero debes saber qué es exactamente el objeto de nuestro estudio. JPA como ya dijimos es el API para la persistencia en Java, pero, ¿qué es en concreto? Bien, JPA es un ORM, aunque no propiamente. Explicaremos esto en detalle a continuación.

La Java Community Process (JCP), es la encargada de las especificaciones en Java. ¿Qué quiero decir con especificaciones? Pues, una especificación no es más que un estándar. La JCP propone un estándar y si por mayoría de votos se acepta la proposición, se designa un equipo experto para que trabaje en ella. Aquí el equipo se encarga de definir la estructura de la especificación, sus características y forma de trabajar. Pero no podemos empezar a trabajar con una especificación si no tiene una implementación. Una implementación es una representación real de dicha especificación. Es como en el mismo lenguaje, una interface vendría a ser la especificación y una clase que implemente dicha interface vendría a ser la implementación o representación. Así mismo, la JCP puede o no realizar la implementación de una especificación, como lo hizo con Servlet, JAXB, JMS, JAAS, y algunos otros.

Comprendido lo anteriormente explicado, se procede a listar las mejores implementaciones de JPA:

• Hibernate
• EclipseLink
• MyBatis

Hibernate y MyBatis se pueden usar nativamente, es decir, sin usar a JPA como interfaz o también con JPA. En éste tutorial se usará Hibernate por ser el más adoptado por los desarrolladores.




PREPARANDO NUESTRO ENTORNO DE TRABAJO


Primero que todo, vamos a disponer del siguiente material:

• No tienes permitido ver los links. Registrarse o Entrar a mi cuenta
• No tienes permitido ver los links. Registrarse o Entrar a mi cuenta
• No tienes permitido ver los links. Registrarse o Entrar a mi cuenta (opcional, porque usaremos Maven).
• No tienes permitido ver los links. Registrarse o Entrar a mi cuenta
• No tienes permitido ver los links. Registrarse o Entrar a mi cuenta (opcional, usaremos Maven).
• No tienes permitido ver los links. Registrarse o Entrar a mi cuenta

Instalación de MySQL como servicio

Nos dirigimos a "C:\Program Files\MySQL\MySQL Server 5.6" y renombramos el archivo "my-default.ini" a "my.ini". Lo editamos y al final agregamos la línea:

Código: php
PERFORMANCE_SCHEMA=0


Ahora, abrimos la terminal como Administrador y nos dirigimos hacia "bin". Aquí ejecutaremos el comando:

Código: php
mysqld –install


Y ya tenemos MySQL instalado como servicio. Si no está corriendo lo iniciamos.



CREACIÓN DE LA BASE DE DATOS



Creación de la base de datos y la tabla Employees

Abrimos MySQL Workbench e iniciamos sesión. En el editor SQL escribimos el siguiente código para crear nuestra base de datos "jpa2_tuto" y nuestra primera tabla "employees":

#18

Consumir RESTful WebService CRUD en aplicación JavaFX



Éste es un demo de cómo interactuar con una BBDD remota mediante un WebService, en concreto un servicio RESTful. Las tecnologías usadas son:

  • JAXB
  • JAX-RS (implementación corre por servidor)
  • Jersey client (para el cliente REST)
  • JPA 2.1 (persistencia de datos)
  • Hibernate 4.x (implementación de JPA 2.1)
  • PostgreSQL 9.4.1 como SGBD
  • WildFly 8.2.0

    NOTA: Si van a utilizar GlassFish 4.x, necesitan usar Hibernate 4.3.5 en lugar de 4.3.8, ya que se conocen conflictos entre ambos.

    En el pom.xml, reemplazar:

    Código: xml

    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-core</artifactId>
        <version>4.3.5.Final</version>
    </dependency>

    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-entitymanager</artifactId>
        <version>4.3.5.Final</version>
    </dependency>





    La aplicación consta de 2 partes: La aplicación web, donde esté levantado el servicio REST y el cliente que lo consume, construido con JavaFX.

    La aplicación consta básicamente de un CRUD de clientes, que será llevada a cabo por el REST utilizando para ésto EJB's como servicios de acceso a la BBDD mediante JPA.

    De ésta manera, varios clientes pueden consumir el REST y hacer un CRUD sin necesidad de tener instalada una BBDD (como ocurre generalmente con los sistemas de escritorio normales).


    Imágenes











    Código fuente



    Todo el código así como los WAR y JAR están en mi cuenta de Github: No tienes permitido ver los links. Registrarse o Entrar a mi cuenta. Saludos.




    By Gus.