La verdad es que Firefox4 está de lujo, y las demos que ofrece Mozilla son increíbles. De una de ellas he sacado cómo hacer clipping en vídeos usando HTML5 y la posibilidad de incrustar SVG (sólo funciona en Firefox4).
El método es sencillo, tengo un SVG que muestra el contorno y los botones de play y pausa, además tiene un clipPath que se usará para el estilo clip-path del vídeo:
SVG
Vídeo
Javascript
var play = document.getElementById('play');
var pause = document.getElementById('pause');
var video = document.getElementById('video');
play.addEventListener('click', function() {
play.style.display = 'none';
pause.style.display = 'block';
video.play();
}, true);
pause.addEventListener('click', function() {
play.style.display = 'block';
pause.style.display = 'none';
video.pause();
}, true);
video.addEventListener("ended", function() {
play.style.display = 'block';
pause.style.display = 'none';
video.pause();
}, true);
El vídeo es el mismo que el de la demo de Mozilla, he puesto el borde semi-transparente para que se vea el clipping como va.
Hoy toca realizar conexiones con el servidor usando llamadas autenticadas. Para eso usaremos JWT (JSON Web Token), que viene a ser el envío de un token en cada llamada que necesita autenticación. El token está formado por tres cadenas codificadas en base64 y separadas por un punto (header.payload.signature). En el payload se envía toda la info que queramos, pero sin pasarse porque el token no debería ser muy largo y sobre todo sin enviar información sensible, porque el token no está encriptado, es por ello que la comunicación navegador/servidor debe ser mediante HTTPS. Si quieres una explicación más detallada, aquí te lo explican mejor.
En el payload vamos a guardar varios claims, entre ellos el username y un uuid que usaremos para validar que el usuario es correcto. JWT asegura que el token es válido, pero no viene mal añadir cierta seguridad. Cuando el usuario se loguea un nuevo uuid es generado, por lo que se comprobará si coinciden para el username enviado.
Ya tenemos la explicación teórica, ahora vamos con la parte de programación, primero la parte servidor y luego el frontend.
Lo primero que tenemos que hacer es añadir un nuevo plugin a hapi.js que se encargue de la autenticación, registrando un nuevo strategy a server.auth que use JWT. El plugin hapi-auth-jwt2 se encargará de toda la autenticación JWT y añadiremos una capa extra de validación comprobando que el username y el uuid coinciden.
Y por último añadimos una nueva ruta para autenticar (login) el usuario, comprobamos que el usuario y la contraseña coinciden, y si es así, creamos un uuid que guardamos en la bd y generamos el JWT y lo enviamos en la cabecera Authorization:
Vale, ya tenemos la parte del servidor, ahora la parte del frontend. Primero es añadir las rutas para /login, /account y /logout. He añadido un meta que indica si la ruta tiene que ser obligatoriamente autenticada, obligatoriamente no autenticada o como sea. Para ello, para cada ruta comprobará si el JWT token está almacenado o no y según el meta redirigirá a home o continuará:
Para gestionar el almacenamiento en el navegador en vez de cookies vamos a usar sessionStorage y localStorage para guardar el token JWT. Como el formulario de login permite recordar la sesión, vamos a usar ambos storages. Si no se recuerda usaremos sessionStorage, que se borrará cuando se cierra el navegador, en caso contrario usaremos localStorage.
import Config from'@/js/config';
/**
* API methods for sesion/local storage.
* Depending on `this.session` it saves only on `sessionStorage` or also in `localStorage`
*
* @since v0.8.0
*/classstorage{
/**
* Constructor
*
* @param {boolean} session If stored only in session
*/constructor( session = false ) {
this.session = session;
}
/**
* It saves the token in the session (and local storage is this.session === false),
*
* @param {strig} token JWT token
*/
setJWTToken( token ) {
sessionStorage.setItem( Config.jwt.storageKey, token );
if ( ! this.session ) {
localStorage.setItem( Config.jwt.storageKey, token );
}
}
/**
* It gets a value from session storage or in local if session = false
*
* @returns {string}
*/
getJWTToken() {
const sessionValue = sessionStorage.getItem( Config.jwt.storageKey );
if ( sessionValue ) {
return sessionValue;
}
if ( ! this.session ) {
const storedValue = localStorage.getItem( Config.jwt.storageKey );
return storedValue;
}
returnnull;
}
/**
* Removes JWT token from session and local storage
*/
removeJWTToken() {
sessionStorage.removeItem( Config.jwt.storageKey );
localStorage.removeItem( Config.jwt.storageKey );
}
}
exportdefault storage;
También hemos creado una librería para tratar las llamadas a la API. Hay dos métodos, uno para autenticar el usuario (login) que mirará la cabecera Authorization, y otro método que obtiene los datos del usuario actual realizando una llamada autenticada enviando el JWT token en la cabecera Authorization:
import Config from'@/js/config';
import Storage from'@/js/utils/storage';
/**
* API backend methods
*
* @since 0.8.0
*/classapiFetch{
/**
* Authenticate an user, if ok, JWT token is sent by the server in Authorization header
*
* @param {string} username User name
* @param {string} password Password
*
* @returns {Promise}
*/
auth( username, password ) {
return fetch( Config.api.user.auth, {
method: 'POST',
body: JSON.stringify(
{ username, password }
),
mode: 'cors',
} ).then( response => {
const auth = response.headers.get( 'Authorization' );
if ( auth ) {
return {
response: true,
token: auth,
};
}
return {
response: false,
message: 'Username or password not valid',
};
} );
}
getUser( username ) {
return fetch( `${ Config.api.user.get }${ username }`, {
method: 'GET',
headers: {
Authorization: new Storage().getJWTToken(),
},
} ).then( response => response.json() );
}
}
exportdefault apiFetch;
Ahora solo faltan los controladores para login, account y logout. Login realizar la llamada al servidor y si se obtiene el JWT se guarda:
<template><sectionclass="section"><divclass="container"><h1class="title">
Login
</h1><divv-if="error"class="columns is-centered has-margin-bottom-2"
><b-notificationclass="column is-7-tablet is-6-desktop is-5-widescreen"type="is-danger"has-iconaria-close-label="Close notification"role="alert"size="is-small "
>
{{ error }}
</b-notification></div></div><divclass="container"><divclass="columns is-centered"><divclass="column is-5-tablet is-4-desktop is-3-widescreen has-background-light login-form"><b-fieldlabel="Username"><b-inputv-model="username"value=""maxlength="30"icon="account-circle-outline"
/></b-field><b-fieldlabel="Password"><b-inputv-model="password"value=""type="password"icon="lock-outline"
/></b-field><divclass="field"><b-checkboxv-model="remember">
Remember me
</b-checkbox></div><divclass="has-text-right"><b-buttontype="is-primary"
@click="submit"
>
Log in
</b-button></div></div></div></div></section></template><script>import ApiFectch from'@/js/utils/api';
import Storage from'@/js/utils/storage';
exportdefault {
name: 'Login',
// Form data and error messages if login fails
data() {
return {
username: '',
password: '',
remember: false,
error: '',
};
},
methods: {
// It logs in, using the backend API for authenticate the user data.// If user logs in, it saves the JWT token in the browser. If not, shows error message.
submit: function() {
const api = new ApiFectch();
api.auth( this.username, this.password )
.then( response => {
const storage = new Storage( ! this.remember );
if ( !! response.response && !! response.token ) {
storage.setJWTToken( response.token );
this.error = false;
// `go` refreshes the page, so user data is updatedthis.$router.go( '/' );
} else {
storage.removeJWTToken();
this.error = response.message;
}
} );
},
},
};
</script><stylelang="scss"scoped>.login-form {
border-radius: 4px;
}
</style>
Logout borra los datos JWT del navegador y redirige a home:
<template><sectionclass="hero is-medium is-primary is-bold"><divclass="hero-body"><divclass="container has-text-centered"><h1class="title">
{{ message }}
</h1><h2class="subtitle">
Miss you 💛
</h2></div></div></section></template><script>import User from'@/js/utils/user';
exportdefault {
name: 'Logout',
// Dummy data
data() {
return {
message: 'Bye',
};
},
// After being created it logs out and go to home
created() {
new User().logout();
// `go` instead of `push` because refreshing the page updates the user data// Maybe using vuex is a better way to do it, or not...this.$router.go( '/' );
},
};
</script>
Y Account recupera los datos del usuario una vez que ha creado el controlador:
<template><divid="account"><sectionclass="hero is-primary is-bold"><divclass="hero-body"><divclass="has-text-centered"><h1class="title">
{{ message }}
</h1><h2class="subtitle">
Your data
</h2></div></div></section><sectionclass="section"><divclass="container"><divclass="tile is-ancestor"><divclass="tile is-parent"><pclass="tile is-child notification">
Some content
</p></div><divclass="tile is-8 is-parent"><divclass="tile is-child notification is-info"><ulid="data"><liv-for="( value, key ) in user":key="key"
>
{{ key }} : {{ value }}
</li></ul></div></div></div></div></section></div></template><script>// Dummy componentimport ApiFectch from'@/js/utils/api';
import User from'@/js/utils/user';
exportdefault {
name: 'Account',
data() {
return {
message: 'Account',
user: {},
};
},
created() {
const user = new User().getCurrentUser();
new ApiFectch().getUser( user.username )
.then( response =>this.user = response );
},
};
</script>
VÃdeos de alta calidad: la misma tecnologÃa que se usa para VC-1, el codec de los BluRay y HD DVD, es la que se usa para el streaming de los vÃdeos.
Multiplataforma y multinavegador: funcionará para cada plataforma y cada navegador, incluso gracias al proyecto Mono funcionará para Linux.
Unión de diseño gráfico y desarrollo: los desarrolladores de Visual Studio podrán crear aplicaciones para Silverlight de forma rápida, pudiendo separar el core de la aplicación de la parte gráfica.
Depuración multiplataforma y multinavegador: gracias a las caracterÃsticas que ofrece Visual Studio.
Mejor entorno de desarrollo del planeta: bueno, esto va en gustos, y al autor le debe gustar mucho Visual Studio.
Soporte a mashups: muy buena utilidad, obtenida gracias al soporte a LINQ y LINQ-to-XML por parte de Silverlight y a que los datos son accesibles mediante RSS, POX, REST y JSON.
Protección anticopia: evitando asà que los contenidos como vÃdeos se puedan copiar como ocurre en los vÃdeos Flash como los que ofrece Youtube. Hasta que venga uno y consiga copiarlos…
Rapidez: habrá un gran incremento en la rapidez en las aplicaciones basadas en Ajax que usen Silverlight.
Otro punto que cuestiono es el alarde que hace el autor de que solo ocupa 2Mb., pero eso es solo el Silverlight, y según he entendido es necesario .NET, por lo que a esos 2Mb hay que añadirle los 22Mb. del framework de .NET. Demasiado si lo comparamos con el tamaño del Flash Player que es el que ejecuta las aplicaciones Flex.
Lo siento, me ha sido incapaz no dar mi opinión, que quizás parezca anti-microsoft, pero he leÃdo en varios sitios cosas parecidas al siguiente artÃculo, y aunque seguro que Silverlight aporta buenas cosas a la web, tampoco hay que pasarse.
Interesante javascript que nos permite crear PDFs sin necesidad de aplicaciones en el servidor, sino usando únicamente una librería PDF. Su uso es muy sencillo, devolviendo una URL con Content-type y codificada en Base64:
var doc = new jsPDF();
doc.setFontSize(22);
doc.text(20, 20, 'This is a title');
doc.setFontSize(16);
doc.text(20, 30, 'This is some normal sized text underneath.');
// Output as Data URI
doc.output('datauri');
A mí en Firefox no me ha funcionado, pero en Chrome sí.
There’s nothing like a subtle, slick website widget that effectively uses CSS and JavaScript to enhance the user experience. Of course widgets like that take many hours to perfect, but it doesn’t take long for that effort to be rewarded with above-average user retention and buzz. One of the widgets I love is Twitter’s “Follow” button. Let me sho …
Separa el contenido de la funcionalidad, por ejemplo, si vas a añadir un evento a un objeto (onclick) añádelo mediante el evento window.onload usando un archivo js.
Una cosa que ya hemos comentado anteriormente es ver cómo funciona tu aplicación web si no está activado Javascript.
Realiza código compatible con los distintos navegadores. Desgraciadamente, a veces es necesario detectar el navegador y resolver según el navegador que sea.
Declara las variables usando var, si no lo haces puedes tener problemas en las funciones recursivas, por ejemplo si en una función tienes un for con una variable i y dentro de ese for vuelves a llamar a la misma función, al llegar al mismo bucle puedes tener problemas.