<?xml version="1.0" encoding="UTF-8" standalone="no"?><rss xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:sy="http://purl.org/rss/1.0/modules/syndication/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" version="2.0">

<channel>
	<title>Sentido Web</title>
	<atom:link href="https://sentidoweb.com/feed" rel="self" type="application/rss+xml"/>
	<link>https://sentidoweb.com/</link>
	<description>Publicación dirigida a los desarrolladores web e internautas apasionados por la programación, diseño, negocios y todo lo que ocurre en Internet.</description>
	<lastBuildDate>Wed, 08 Apr 2020 10:22:06 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	

<image>
	<url>https://sentidoweb.com/wp-content/uploads/2024/03/icon-sentidoweb.svg</url>
	<title>Sentido Web</title>
	<link>https://sentidoweb.com/</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>CSS grid: filas con misma altura</title>
		<link>https://sentidoweb.com/2020/04/08/css-grid-filas-con-misma-altura.php</link>
		
		<dc:creator><![CDATA[Luis]]></dc:creator>
		<pubDate>Wed, 08 Apr 2020 10:20:06 +0000</pubDate>
				<category><![CDATA[css]]></category>
		<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[grid]]></category>
		<category><![CDATA[height]]></category>
		<category><![CDATA[rows]]></category>
		<guid isPermaLink="false">https://sentidoweb.com/?p=3008</guid>

					<description><![CDATA[<p>Tener un grid con todas las filas al mismo tamaño puede ser algo difícil de conseguir. El problema viene con que CSS grid toma el tamaño de las filas según el tamaño máximo de sus celdas. Para conseguir ello es necesario que el contenido de la celda tenga posicionamiento absoluto, y jugar con ::before para...</p>
<p>The post <a href="https://sentidoweb.com/2020/04/08/css-grid-filas-con-misma-altura.php">CSS grid: filas con misma altura</a> appeared first on <a href="https://sentidoweb.com">Sentido Web</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>Tener un grid con todas las filas al mismo tamaño puede ser algo difícil de conseguir. El problema viene con que CSS grid toma el tamaño de las filas según el tamaño máximo de sus celdas.</p>



<p>Para conseguir ello es necesario que el contenido de la celda tenga posicionamiento absoluto, y jugar con ::before para simular contenido vacío que rellene la celda:</p>



<pre class="wp-block-sentidoweb-snippet"><button class="sw-snippet-button" data-label-copy="Copy" data-label-copied="Copied">Copy</button><code class="css sw_show_line_numbers hljs"><span class="hljs-selector-class">.grid</span> {
  <span class="hljs-attribute">margin</span>: <span class="hljs-number">0</span> auto;
  <span class="hljs-attribute">width</span>: <span class="hljs-number">60vw</span>;
  <span class="hljs-attribute">display</span>: grid;
  <span class="hljs-attribute">grid-template-columns</span>: <span class="hljs-built_in">repeat</span>(3, 1fr);
  <span class="hljs-attribute">gap</span>: <span class="hljs-number">5px</span>;
  <span class="hljs-attribute">grid-auto-rows</span>: auto;
}

<span class="hljs-selector-tag">img</span> {
  <span class="hljs-attribute">width</span>: <span class="hljs-number">100%</span>;
  <span class="hljs-attribute">height</span>: <span class="hljs-number">100%</span>;
  <span class="hljs-attribute">object-fit</span>: cover;
}

<span class="hljs-selector-class">.grid--same-height</span> <span class="hljs-selector-class">.cell</span> {
  <span class="hljs-attribute">position</span>: relative;
}
<span class="hljs-selector-class">.grid--same-height</span> <span class="hljs-selector-class">.cell</span><span class="hljs-selector-pseudo">::before</span> {
  <span class="hljs-attribute">content</span>: <span class="hljs-string">""</span>;
  <span class="hljs-attribute">display</span>: inline-block;
  <span class="hljs-attribute">padding-bottom</span>: <span class="hljs-number">100%</span>;
}

<span class="hljs-selector-class">.grid--same-height</span> <span class="hljs-selector-class">.content</span> {
  <span class="hljs-attribute">height</span>: <span class="hljs-number">100%</span>;
  <span class="hljs-attribute">position</span>: absolute;
  <span class="hljs-attribute">margin-top</span>: -<span class="hljs-number">100%</span>;
}
</code></pre>



<p>Teniendo en cuenta que el HTML sería:</p>



<pre class="wp-block-sentidoweb-snippet"><button class="sw-snippet-button" data-label-copy="Copy" data-label-copied="Copied">Copy</button><code class="html sw_show_line_numbers hljs"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"grid"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"grid"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"cell"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"content"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">img</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"https://picsum.photos/500/500"</span>/&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
  ...
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></code></pre>



<p>Para el ejemplo uso <a href="https://picsum.photos/">picsum.photos</a>, un lorem ipsum para fotos que permite indicar distintos tamaños de imágenes.</p>



<p>Podéis ver un ejemplo en este pen</p>



<div class="wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper"><iframe id="cp_embed_abvoVeV" src="//codepen.io/anon/embed/preview/abvoVeV?height=500&amp;theme-id=1&amp;slug-hash=abvoVeV&amp;default-tab=result" height="500" scrolling="no" frameborder="0" allowfullscreen allowpaymentrequest name="CodePen Embed abvoVeV" title="CodePen Embed abvoVeV" class="cp_embed_iframe" style="width:100%;overflow:hidden">CodePen Embed Fallback</iframe></div>
<p>The post <a href="https://sentidoweb.com/2020/04/08/css-grid-filas-con-misma-altura.php">CSS grid: filas con misma altura</a> appeared first on <a href="https://sentidoweb.com">Sentido Web</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Hapi.js + Vue.js avatar</title>
		<link>https://sentidoweb.com/2019/12/22/hapi-js-vue-js-avatar.php</link>
		
		<dc:creator><![CDATA[Luis]]></dc:creator>
		<pubDate>Sun, 22 Dec 2019 02:26:00 +0000</pubDate>
				<category><![CDATA[Javascript]]></category>
		<category><![CDATA[cropper]]></category>
		<category><![CDATA[hapi.js]]></category>
		<category><![CDATA[vue.js]]></category>
		<category><![CDATA[vuex]]></category>
		<guid isPermaLink="false">https://sentidoweb.com/?p=3003</guid>

					<description><![CDATA[<p>En esta nueva versión se ha añadido el poder subir el avatar, recortando la imagen usando la librería Cropper.js y para ello también se usará Vuex para guardar el avatar y que se actualice en toda la app. Para subir la imagen se usará el siguiente método. Para manejar todo habrá que usar Vuex con...</p>
<p>The post <a href="https://sentidoweb.com/2019/12/22/hapi-js-vue-js-avatar.php">Hapi.js + Vue.js avatar</a> appeared first on <a href="https://sentidoweb.com">Sentido Web</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>En esta nueva versión se ha añadido el poder subir el avatar, recortando la imagen usando la librería <a href="https://fengyuanchen.github.io/cropperjs/">Cropper.js</a> y para ello también se usará Vuex para guardar el avatar y que se actualice en toda la app.</p>



<p>Para subir la imagen se usará el siguiente método.</p>



<pre class="wp-block-sentidoweb-snippet"><button class="sw-snippet-button" data-label-copy="Copiar" data-label-copied="Copiado">Copiar</button><code class="javascript sw_show_line_numbers">this.cropper
	.getCroppedCanvas( this.outputOptions )
	.toBlob( blob => {
		new ApiFectch()
			.updateAvatar( blob )
			.then( response => {
				if ( response.response ) {
					success( response.message );
					this.avatar = avatar + '?cache=' + new Date();
					this.$store.commit( `user/${ USER_SET_AVATAR }`, new User().getAvatarURL( userData.username ) + '?cache=' + ( new Date() ) );
				} else {
					error( response.message );
				}
			} );
	} );
</code></pre>



<p>Para manejar todo habrá que usar Vuex con módulos, por ejemplo, para el módulo user:</p>



<pre class="wp-block-sentidoweb-snippet"><code class="javascript sw_show_line_numbers">import { USER_SET_AVATAR, USER_SET_USERNAME } from '../mutation-types';

const state = {
	avatar: null,
	username: null,
};

const mutations = {
	/**
	 * Sets avatar
	 *
	 * @param {Object} mutationState Vuex state.
	 * @param {String} avatar Avatar string.
	 */
	[ USER_SET_AVATAR ]( mutationState, avatar ) {
		mutationState.avatar = avatar;
	},

	/**
	 * Sets username
	 *
	 * @param {Object} mutationState Vuex state.
	 * @param {String} username User name.
	 */
	[ USER_SET_USERNAME ]( mutationState, username ) {
		mutationState.username = username;
	},
};

export default {
	namespaced: true,
	state,
	mutations,
};
</code></pre>



<p>Aquí está el <a href="https://github.com/displaynone/hapi-vue-demo/releases/tag/0.12.0">código</a></p>
<p>The post <a href="https://sentidoweb.com/2019/12/22/hapi-js-vue-js-avatar.php">Hapi.js + Vue.js avatar</a> appeared first on <a href="https://sentidoweb.com">Sentido Web</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Hapi.js + Vue.js internacionalización (i18n)</title>
		<link>https://sentidoweb.com/2019/09/06/hapi-js-vue-js-internacionalizacion-i18n.php</link>
		
		<dc:creator><![CDATA[Luis]]></dc:creator>
		<pubDate>Fri, 06 Sep 2019 20:54:48 +0000</pubDate>
				<category><![CDATA[Javascript]]></category>
		<category><![CDATA[hapi.js]]></category>
		<category><![CDATA[i18n]]></category>
		<category><![CDATA[vue-i18n]]></category>
		<category><![CDATA[vue.js]]></category>
		<guid isPermaLink="false">https://sentidoweb.com/?p=2997</guid>

					<description><![CDATA[<p>A partir de ahora no voy a explicar lo que hago, sino que compartiré lo que considere algo especial, pondré algún enlace a algún otro tutorial y por supuesto el enlace al tag de GitHub. En esta ocasión he añadido internacionalización al proyecto. Parece una tontería, pero cuanto antes se meta, en mi opinión, mejor...</p>
<p>The post <a href="https://sentidoweb.com/2019/09/06/hapi-js-vue-js-internacionalizacion-i18n.php">Hapi.js + Vue.js internacionalización (i18n)</a> appeared first on <a href="https://sentidoweb.com">Sentido Web</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>A partir de ahora no voy a explicar lo que hago, sino que compartiré lo que considere algo especial, pondré algún enlace a algún otro tutorial y por supuesto el enlace al tag de GitHub.</p>



<p>En esta ocasión he añadido internacionalización al proyecto. Parece una tontería, pero cuanto antes se meta, en mi opinión, mejor que mejor. Luego cambiar todos los textos para que admitan i18n es un tostón enorme.</p>



<p>Para añadir internacionalización usaremos el paquete <em>vue-i18n</em>, que añade todo lo que necesitamos. Aquí hay <a href="https://medium.com/hypefactors/add-i18n-and-manage-translations-of-a-vue-js-powered-website-73b4511ca69c">un tutorial</a> bastante completo que explica cómo usarlo, y es el que yo he seguido.</p>



<p>Lo único que he añadido es que coja el idioma del navegador:</p>



<pre class="wp-block-sentidoweb-snippet"><code class="javascript hljs"><span class="hljs-keyword">import</span>( <span class="hljs-string">`@/lang/<span class="hljs-subst">${ browserLang }</span>.json`</span> ).then( <span class="hljs-function"><span class="hljs-params">messages</span> =&gt;</span> {
	i18n.setLocaleMessage( browserLang, messages.default );
} );</code></pre>



<p>Aquí está el <a href="https://github.com/displaynone/hapi-vue-demo/releases/tag/0.9.0">código</a></p>
<p>The post <a href="https://sentidoweb.com/2019/09/06/hapi-js-vue-js-internacionalizacion-i18n.php">Hapi.js + Vue.js internacionalización (i18n)</a> appeared first on <a href="https://sentidoweb.com">Sentido Web</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Hapi.js + Vue.js llamadas autenticadas al servidor con JWT (login)</title>
		<link>https://sentidoweb.com/2019/09/01/hapi-js-vue-js-llamadas-autenticadas-al-servidor-con-jwt-login.php</link>
		
		<dc:creator><![CDATA[Luis]]></dc:creator>
		<pubDate>Sun, 01 Sep 2019 21:38:44 +0000</pubDate>
				<category><![CDATA[Javascript]]></category>
		<category><![CDATA[autenticacion]]></category>
		<category><![CDATA[hapi.js]]></category>
		<category><![CDATA[jwt]]></category>
		<category><![CDATA[login]]></category>
		<category><![CDATA[vue.js]]></category>
		<guid isPermaLink="false">https://sentidoweb.com/?p=2992</guid>

					<description><![CDATA[<p>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...</p>
<p>The post <a href="https://sentidoweb.com/2019/09/01/hapi-js-vue-js-llamadas-autenticadas-al-servidor-con-jwt-login.php">Hapi.js + Vue.js llamadas autenticadas al servidor con JWT (login)</a> appeared first on <a href="https://sentidoweb.com">Sentido Web</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>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 (<em>header.payload.signature</em>). En el <em>payload</em> 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, <a href="https://enmilocalfunciona.io/construyendo-una-web-api-rest-segura-con-json-web-token-en-net-parte-i/">aquí</a> te lo explican mejor.</p>



<p>En el payload vamos a guardar varios <em>claims</em>, entre ellos el <em>username</em> y un <em>uuid</em> 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.</p>



<p>Ya tenemos la explicación teórica, ahora vamos con la parte de programación, primero la parte servidor y luego el frontend.</p>



<p>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 <em>strategy</em> a <em>server.auth</em> 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.</p>



<pre class="wp-block-sentidoweb-snippet"><button class="sw-snippet-button" data-label-copy="Copiar" data-label-copied="Copiado">Copiar</button><code class="javascript sw_show_line_numbers hljs"><span class="hljs-comment">/**
 * Auth controller
 *
 * It uses JWT
 */</span>
<span class="hljs-keyword">const</span> jwt2 = <span class="hljs-built_in">require</span>( <span class="hljs-string">'hapi-auth-jwt2'</span> );
<span class="hljs-keyword">const</span> User = <span class="hljs-built_in">require</span>( <span class="hljs-string">'../models/users'</span> );

<span class="hljs-keyword">const</span> validate = <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"> decoded </span>) </span>{
	<span class="hljs-keyword">const</span> user = <span class="hljs-keyword">await</span> User.findByUUID( decoded.id );
	<span class="hljs-keyword">return</span> { <span class="hljs-attr">isValid</span>: !! user &amp;&amp; user.username === decoded.username };
};

exports.plugin = {
	<span class="hljs-attr">name</span>: <span class="hljs-string">'auth'</span>,
	<span class="hljs-attr">register</span>: <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"> server, options </span>) </span>{
		<span class="hljs-keyword">await</span> server.register( jwt2 );

		server.auth.strategy( <span class="hljs-string">'jwt'</span>, <span class="hljs-string">'jwt'</span>,
			{
				<span class="hljs-attr">key</span>: options.jwt.secret,
				<span class="hljs-attr">validate</span>: validate,
				<span class="hljs-attr">verifyOptions</span>: { <span class="hljs-attr">algorithms</span>: [ <span class="hljs-string">'HS256'</span> ] }, <span class="hljs-comment">// pick a strong algorithm</span>
			}
		);

		server.auth.default( <span class="hljs-string">'jwt'</span> );
	},
};</code></pre>



<p>En el modelo user añadiremos 2 nuevos métodos estáticos, que permitirán encontrar usuarios por username/email y por uuid:</p>



<pre class="wp-block-sentidoweb-snippet"><button class="sw-snippet-button" data-label-copy="Copiar" data-label-copied="Copiado">Copiar</button><code class="javascript sw_show_line_numbers hljs">userSchema.static( <span class="hljs-string">'findByUserOrEmail'</span>, <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"> username, email </span>) </span>{
	<span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>( <span class="hljs-function">(<span class="hljs-params"> resolve, reject </span>) =&gt;</span> {
		<span class="hljs-keyword">this</span>.model( <span class="hljs-string">'User'</span> )
			.findOne( {
				<span class="hljs-attr">$or</span>: [
					{ <span class="hljs-attr">username</span>: username },
					{ <span class="hljs-attr">email</span>: email },
				],
			} )
			.exec( <span class="hljs-function">(<span class="hljs-params"> error, data </span>) =&gt;</span> {
				<span class="hljs-keyword">if</span> ( error ) {
					reject( error );
				}
				resolve( data );
			} );
	} );
	<span class="hljs-keyword">return</span> result;
} );

userSchema.static( <span class="hljs-string">'findByUUID'</span>, <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"> uuid </span>) </span>{
	<span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>( <span class="hljs-function">(<span class="hljs-params"> resolve, reject </span>) =&gt;</span> {
		<span class="hljs-keyword">this</span>.model( <span class="hljs-string">'User'</span> )
			.findOne( {
				uuid,
			} )
			.exec( <span class="hljs-function">(<span class="hljs-params"> error, data </span>) =&gt;</span> {
				<span class="hljs-keyword">if</span> ( error ) {
					reject( error );
				}
				resolve( data );
			} );
	} );
	<span class="hljs-keyword">return</span> result;
} );</code></pre>



<p>En el manifest de Glue hay que añadir la configuración para <em>CORS</em>, ya que el token viajará usando la cabecera <em>Authorization</em>:</p>



<pre class="wp-block-sentidoweb-snippet"><button class="sw-snippet-button" data-label-copy="Copiar" data-label-copied="Copiado">Copiar</button><code class="javascript hljs"><span class="hljs-keyword">const</span> manifest = {
	<span class="hljs-attr">server</span>: {
		<span class="hljs-attr">port</span>: Config.get( <span class="hljs-string">'/server/port'</span> ),
		<span class="hljs-attr">routes</span>: {
			<span class="hljs-attr">cors</span>: {
				<span class="hljs-attr">origin</span>: [ <span class="hljs-string">'*'</span> ],
				<span class="hljs-attr">credentials</span>: <span class="hljs-literal">true</span>,
				<span class="hljs-attr">exposedHeaders</span>: [ <span class="hljs-string">'Authorization'</span> ],
			},
		},
	},
};</code></pre>



<p>Y en la configuración general del servidor hay que añadir una cadena que se usará para encriptar el signature del token JWT.</p>



<pre class="wp-block-sentidoweb-snippet"><code class="javascript hljs"><span class="hljs-keyword">const</span> config = {
	<span class="hljs-attr">auth</span>: {
		<span class="hljs-attr">jwt</span>: {
			<span class="hljs-attr">secret</span>: process.env.JWT_SECRET,
		},
	},
};</code></pre>



<p>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 <em>Authorization</em>:</p>



<pre class="wp-block-sentidoweb-snippet"><code class="">/**
 * Authenticates an user
 */
server.route( {
	method: 'POST',
	path: '/user/auth',
	options: {
		tags: [ 'api', 'user', 'auth' ],
		description: 'Server authenticate user',
		notes: 'Server authenticate user',
		auth: false,
		validate: {
			payload: {
				username: Joi.string().alphanum().min( 3 ).max( 20 ).required(),
				password: Joi.string().min( 8 ).required(),
			},
		},
	},

	/**
	 * Route handler
	 *
	 * @param {object} request
	 * @param {object} h Hapi object
	 * @returns {object}
	 */
	handler: async( request, h ) => { // eslint-disable-line
		try {
			const user = await User.findByUserOrEmail( request.payload.username, request.payload.email );
			if ( ! user ) {
				return Boom.badData( 'User or password incorrect' );
			}
			const isValidPassword = await bcrypt.compare( request.payload.password, user.password );
			if ( ! isValidPassword ) {
				return Boom.badData( 'User or password incorrect' );
			}

			const claims = {
				id: uuid(),
				exp: new Date().getTime() + ( 180 * 24 * 60 * 60 * 1000 ), // 3 months
				username: user.username,
			};

			user.uuid = claims.id;
			user.save();

			const token = JWT.sign( claims, Config.get( '/auth' ).jwt.secret ); // synchronous

			return h.response( {
				response: true,
				message: 'Check Auth Header for your Token',
			} ).header( 'Authorization', token );
		} catch ( error ) {
			return Boom.badImplementation( 'Error', { error } );
		}
	},
} );</code></pre>



<p>Vale, ya tenemos la parte del servidor, ahora la parte del frontend. Primero es añadir las rutas para <em>/login</em>, <em>/account</em> y <em>/logout</em>. 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á:</p>



<pre class="wp-block-sentidoweb-snippet"><button class="sw-snippet-button" data-label-copy="Copiar" data-label-copied="Copiado">Copiar</button><code class="javascript sw_show_line_numbers hljs"><span class="hljs-keyword">import</span> Vue <span class="hljs-keyword">from</span> <span class="hljs-string">'vue'</span>;
<span class="hljs-keyword">import</span> Router <span class="hljs-keyword">from</span> <span class="hljs-string">'vue-router'</span>;
<span class="hljs-keyword">import</span> Home <span class="hljs-keyword">from</span> <span class="hljs-string">'@/js/components/Home'</span>;
<span class="hljs-keyword">import</span> Login <span class="hljs-keyword">from</span> <span class="hljs-string">'@/js/components/Login'</span>;
<span class="hljs-keyword">import</span> Account <span class="hljs-keyword">from</span> <span class="hljs-string">'@/js/components/Account'</span>;
<span class="hljs-keyword">import</span> Logout <span class="hljs-keyword">from</span> <span class="hljs-string">'@/js/components/Logout'</span>;
<span class="hljs-keyword">import</span> Storage <span class="hljs-keyword">from</span> <span class="hljs-string">'@/js/utils/storage'</span>;

Vue.use( Router );

<span class="hljs-keyword">const</span> router = <span class="hljs-keyword">new</span> Router( {
	<span class="hljs-attr">mode</span>: <span class="hljs-string">'history'</span>,
	<span class="hljs-attr">routes</span>: [
		{
			<span class="hljs-attr">path</span>: <span class="hljs-string">'/'</span>,
			<span class="hljs-attr">name</span>: <span class="hljs-string">'Home'</span>,
			<span class="hljs-attr">component</span>: Home,
		},
		{
			<span class="hljs-attr">path</span>: <span class="hljs-string">'/login'</span>,
			<span class="hljs-attr">name</span>: <span class="hljs-string">'Login'</span>,
			<span class="hljs-attr">component</span>: Login,
			<span class="hljs-attr">meta</span>: {
				<span class="hljs-attr">auth</span>: <span class="hljs-literal">false</span>,
			},
		},
		{
			<span class="hljs-attr">path</span>: <span class="hljs-string">'/account'</span>,
			<span class="hljs-attr">name</span>: <span class="hljs-string">'Account'</span>,
			<span class="hljs-attr">component</span>: Account,
			<span class="hljs-attr">meta</span>: {
				<span class="hljs-attr">auth</span>: <span class="hljs-literal">true</span>,
			},
		},
		{
			<span class="hljs-attr">path</span>: <span class="hljs-string">'/logout'</span>,
			<span class="hljs-attr">name</span>: <span class="hljs-string">'Logout'</span>,
			<span class="hljs-attr">component</span>: Logout,
			<span class="hljs-attr">meta</span>: {
				<span class="hljs-attr">auth</span>: <span class="hljs-literal">true</span>,
			},
		},
	],
} );

router.beforeEach( <span class="hljs-function">(<span class="hljs-params"> to, <span class="hljs-keyword">from</span>, next </span>) =&gt;</span> {
	<span class="hljs-keyword">if</span> ( !! to.meta ) {
		<span class="hljs-keyword">const</span> storage = <span class="hljs-keyword">new</span> Storage();
		<span class="hljs-keyword">const</span> jwtToken = storage.getJWTToken();
		<span class="hljs-comment">// Can't be logged</span>
		<span class="hljs-keyword">if</span> ( to.meta.auth === <span class="hljs-literal">false</span> ) {
			<span class="hljs-keyword">if</span> ( jwtToken ) {
				next( <span class="hljs-string">'/'</span> );
			}
		<span class="hljs-comment">// Must be logged</span>
		} <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> ( to.meta.auth === <span class="hljs-literal">true</span> ) {
			<span class="hljs-keyword">if</span> ( ! jwtToken ) {
				next( <span class="hljs-string">'/'</span> );
			}
		}
		next();
	} <span class="hljs-keyword">else</span> {
		next();
	}
} );

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> router;</code></pre>



<p>Para gestionar el almacenamiento en el navegador en vez de cookies vamos a usar <em>sessionStorage</em> y <em>localStorage</em> 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.</p>



<pre class="wp-block-sentidoweb-snippet"><button class="sw-snippet-button" data-label-copy="Copiar" data-label-copied="Copiado">Copiar</button><code class="javascript sw_show_line_numbers hljs"><span class="hljs-keyword">import</span> Config <span class="hljs-keyword">from</span> <span class="hljs-string">'@/js/config'</span>;

<span class="hljs-comment">/**
 * API methods for sesion/local storage.
 * Depending on `this.session` it saves only on `sessionStorage` or also in `localStorage`
 *
 * @since v0.8.0
 */</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">storage</span> </span>{
	<span class="hljs-comment">/**
	 * Constructor
	 *
	 * @param {boolean} session If stored only in session
	 */</span>
	<span class="hljs-keyword">constructor</span>( session = false ) {
		<span class="hljs-keyword">this</span>.session = session;
	}

	<span class="hljs-comment">/**
	 * It saves the token in the session (and local storage is this.session === false),
	 *
	 * @param {strig} token JWT token
	 */</span>
	setJWTToken( token ) {
		sessionStorage.setItem( Config.jwt.storageKey, token );
		<span class="hljs-keyword">if</span> ( ! <span class="hljs-keyword">this</span>.session ) {
			localStorage.setItem( Config.jwt.storageKey, token );
		}
	}

	<span class="hljs-comment">/**
	 * It gets a value from session storage or in local if session = false
	 *
	 * @returns {string}
	 */</span>
	getJWTToken() {
		<span class="hljs-keyword">const</span> sessionValue = sessionStorage.getItem( Config.jwt.storageKey );
		<span class="hljs-keyword">if</span> ( sessionValue ) {
			<span class="hljs-keyword">return</span> sessionValue;
		}
		<span class="hljs-keyword">if</span> ( ! <span class="hljs-keyword">this</span>.session ) {
			<span class="hljs-keyword">const</span> storedValue = localStorage.getItem( Config.jwt.storageKey );
			<span class="hljs-keyword">return</span> storedValue;
		}
		<span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;
	}

	<span class="hljs-comment">/**
	 * Removes JWT token from session and local storage
	 */</span>
	removeJWTToken() {
		sessionStorage.removeItem( Config.jwt.storageKey );
		localStorage.removeItem( Config.jwt.storageKey );
	}
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> storage;</code></pre>



<p>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:</p>



<pre class="wp-block-sentidoweb-snippet"><button class="sw-snippet-button" data-label-copy="Copiar" data-label-copied="Copiado">Copiar</button><code class="javascript sw_show_line_numbers hljs"><span class="hljs-keyword">import</span> Config <span class="hljs-keyword">from</span> <span class="hljs-string">'@/js/config'</span>;
<span class="hljs-keyword">import</span> Storage <span class="hljs-keyword">from</span> <span class="hljs-string">'@/js/utils/storage'</span>;

<span class="hljs-comment">/**
 * API backend methods
 *
 * @since 0.8.0
 */</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">apiFetch</span> </span>{
	<span class="hljs-comment">/**
	 * 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}
	 */</span>
	auth( username, password ) {
		<span class="hljs-keyword">return</span> fetch( Config.api.user.auth, {
			<span class="hljs-attr">method</span>: <span class="hljs-string">'POST'</span>,
			<span class="hljs-attr">body</span>: <span class="hljs-built_in">JSON</span>.stringify(
				{ username, password }
			),
			<span class="hljs-attr">mode</span>: <span class="hljs-string">'cors'</span>,
		} ).then( <span class="hljs-function"><span class="hljs-params">response</span> =&gt;</span> {
			<span class="hljs-keyword">const</span> auth = response.headers.get( <span class="hljs-string">'Authorization'</span> );
			<span class="hljs-keyword">if</span> ( auth ) {
				<span class="hljs-keyword">return</span> {
					<span class="hljs-attr">response</span>: <span class="hljs-literal">true</span>,
					<span class="hljs-attr">token</span>: auth,
				};
			}
			<span class="hljs-keyword">return</span> {
				<span class="hljs-attr">response</span>: <span class="hljs-literal">false</span>,
				<span class="hljs-attr">message</span>: <span class="hljs-string">'Username or password not valid'</span>,
			};
		} );
	}

	getUser( username ) {
		<span class="hljs-keyword">return</span> fetch( <span class="hljs-string">`<span class="hljs-subst">${ Config.api.user.get }</span><span class="hljs-subst">${ username }</span>`</span>, {
			<span class="hljs-attr">method</span>: <span class="hljs-string">'GET'</span>,
			<span class="hljs-attr">headers</span>: {
				<span class="hljs-attr">Authorization</span>: <span class="hljs-keyword">new</span> Storage().getJWTToken(),
			},
		} ).then( <span class="hljs-function"><span class="hljs-params">response</span> =&gt;</span> response.json() );
	}
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> apiFetch;</code></pre>



<p>Ahora solo faltan los controladores para login, account y logout. Login realizar la llamada al servidor y si se obtiene el JWT se guarda:</p>



<pre class="wp-block-sentidoweb-snippet"><button class="sw-snippet-button" data-label-copy="Copiar" data-label-copied="Copiado">Copiar</button><code class="html sw_show_line_numbers hljs"><span class="hljs-tag">&lt;<span class="hljs-name">template</span>&gt;</span>
	<span class="hljs-tag">&lt;<span class="hljs-name">section</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"section"</span>&gt;</span>
		<span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"container"</span>&gt;</span>
			<span class="hljs-tag">&lt;<span class="hljs-name">h1</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"title"</span>&gt;</span>
				Login
			<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
			<span class="hljs-tag">&lt;<span class="hljs-name">div</span>
				<span class="hljs-attr">v-if</span>=<span class="hljs-string">"error"</span>
				<span class="hljs-attr">class</span>=<span class="hljs-string">"columns is-centered has-margin-bottom-2"</span>
			&gt;</span>
				<span class="hljs-tag">&lt;<span class="hljs-name">b-notification</span>
					<span class="hljs-attr">class</span>=<span class="hljs-string">"column is-7-tablet is-6-desktop is-5-widescreen"</span>
					<span class="hljs-attr">type</span>=<span class="hljs-string">"is-danger"</span>
					<span class="hljs-attr">has-icon</span>
					<span class="hljs-attr">aria-close-label</span>=<span class="hljs-string">"Close notification"</span>
					<span class="hljs-attr">role</span>=<span class="hljs-string">"alert"</span>
					<span class="hljs-attr">size</span>=<span class="hljs-string">"is-small	"</span>
				&gt;</span>
					{{ error }}
				<span class="hljs-tag">&lt;/<span class="hljs-name">b-notification</span>&gt;</span>
			<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
		<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
		<span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"container"</span>&gt;</span>
			<span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"columns is-centered"</span>&gt;</span>
				<span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"column is-5-tablet is-4-desktop is-3-widescreen has-background-light login-form"</span>&gt;</span>
					<span class="hljs-tag">&lt;<span class="hljs-name">b-field</span> <span class="hljs-attr">label</span>=<span class="hljs-string">"Username"</span>&gt;</span>
						<span class="hljs-tag">&lt;<span class="hljs-name">b-input</span>
							<span class="hljs-attr">v-model</span>=<span class="hljs-string">"username"</span>
							<span class="hljs-attr">value</span>=<span class="hljs-string">""</span>
							<span class="hljs-attr">maxlength</span>=<span class="hljs-string">"30"</span>
							<span class="hljs-attr">icon</span>=<span class="hljs-string">"account-circle-outline"</span>
						/&gt;</span>
					<span class="hljs-tag">&lt;/<span class="hljs-name">b-field</span>&gt;</span>
					<span class="hljs-tag">&lt;<span class="hljs-name">b-field</span> <span class="hljs-attr">label</span>=<span class="hljs-string">"Password"</span>&gt;</span>
						<span class="hljs-tag">&lt;<span class="hljs-name">b-input</span>
							<span class="hljs-attr">v-model</span>=<span class="hljs-string">"password"</span>
							<span class="hljs-attr">value</span>=<span class="hljs-string">""</span>
							<span class="hljs-attr">type</span>=<span class="hljs-string">"password"</span>
							<span class="hljs-attr">icon</span>=<span class="hljs-string">"lock-outline"</span>
						/&gt;</span>
					<span class="hljs-tag">&lt;/<span class="hljs-name">b-field</span>&gt;</span>
					<span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"field"</span>&gt;</span>
						<span class="hljs-tag">&lt;<span class="hljs-name">b-checkbox</span> <span class="hljs-attr">v-model</span>=<span class="hljs-string">"remember"</span>&gt;</span>
							Remember me
						<span class="hljs-tag">&lt;/<span class="hljs-name">b-checkbox</span>&gt;</span>
					<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
					<span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"has-text-right"</span>&gt;</span>
						<span class="hljs-tag">&lt;<span class="hljs-name">b-button</span>
							<span class="hljs-attr">type</span>=<span class="hljs-string">"is-primary"</span>
							@<span class="hljs-attr">click</span>=<span class="hljs-string">"submit"</span>
						&gt;</span>
							Log in
						<span class="hljs-tag">&lt;/<span class="hljs-name">b-button</span>&gt;</span>
					<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
				<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
			<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
		<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
	<span class="hljs-tag">&lt;/<span class="hljs-name">section</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">template</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
<span class="hljs-keyword">import</span> ApiFectch <span class="hljs-keyword">from</span> <span class="hljs-string">'@/js/utils/api'</span>;
<span class="hljs-keyword">import</span> Storage <span class="hljs-keyword">from</span> <span class="hljs-string">'@/js/utils/storage'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> {
	<span class="hljs-attr">name</span>: <span class="hljs-string">'Login'</span>,
	<span class="hljs-comment">// Form data and error messages if login fails</span>
	data() {
		<span class="hljs-keyword">return</span> {
			<span class="hljs-attr">username</span>: <span class="hljs-string">''</span>,
			<span class="hljs-attr">password</span>: <span class="hljs-string">''</span>,
			<span class="hljs-attr">remember</span>: <span class="hljs-literal">false</span>,
			<span class="hljs-attr">error</span>: <span class="hljs-string">''</span>,
		};
	},
	<span class="hljs-attr">methods</span>: {
		<span class="hljs-comment">// It logs in, using the backend API for authenticate the user data.</span>
		<span class="hljs-comment">// If user logs in, it saves the JWT token in the browser. If not, shows error message.</span>
		submit: <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{
			<span class="hljs-keyword">const</span> api = <span class="hljs-keyword">new</span> ApiFectch();
			api.auth( <span class="hljs-keyword">this</span>.username, <span class="hljs-keyword">this</span>.password )
				.then( <span class="hljs-function"><span class="hljs-params">response</span> =&gt;</span> {
					<span class="hljs-keyword">const</span> storage = <span class="hljs-keyword">new</span> Storage( ! <span class="hljs-keyword">this</span>.remember );
					<span class="hljs-keyword">if</span> ( !! response.response &amp;&amp; !! response.token ) {
						storage.setJWTToken( response.token );
						<span class="hljs-keyword">this</span>.error = <span class="hljs-literal">false</span>;
						<span class="hljs-comment">// `go` refreshes the page, so user data is updated</span>
						<span class="hljs-keyword">this</span>.$router.go( <span class="hljs-string">'/'</span> );
					} <span class="hljs-keyword">else</span> {
						storage.removeJWTToken();
						<span class="hljs-keyword">this</span>.error = response.message;
					}
				} );
		},
	},
};
</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">style</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"scss"</span> <span class="hljs-attr">scoped</span>&gt;</span><span class="css">
	<span class="hljs-selector-class">.login-form</span> {
		<span class="hljs-attribute">border-radius</span>: <span class="hljs-number">4px</span>;
	}
</span><span class="hljs-tag">&lt;/<span class="hljs-name">style</span>&gt;</span></code></pre>



<p>Logout borra los datos JWT del navegador y redirige a home:</p>



<pre class="wp-block-sentidoweb-snippet"><button class="sw-snippet-button" data-label-copy="Copiar" data-label-copied="Copiado">Copiar</button><code class="html sw_show_line_numbers hljs"><span class="hljs-tag">&lt;<span class="hljs-name">template</span>&gt;</span>
	<span class="hljs-tag">&lt;<span class="hljs-name">section</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"hero is-medium is-primary is-bold"</span>&gt;</span>
		<span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"hero-body"</span>&gt;</span>
			<span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"container has-text-centered"</span>&gt;</span>
				<span class="hljs-tag">&lt;<span class="hljs-name">h1</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"title"</span>&gt;</span>
					{{ message }}
				<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
				<span class="hljs-tag">&lt;<span class="hljs-name">h2</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"subtitle"</span>&gt;</span>
					Miss you <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f49b.png" alt="💛" class="wp-smiley" style="height: 1em; max-height: 1em;" />
				<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
			<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
		<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
	<span class="hljs-tag">&lt;/<span class="hljs-name">section</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">template</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
<span class="hljs-keyword">import</span> User <span class="hljs-keyword">from</span> <span class="hljs-string">'@/js/utils/user'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> {
	<span class="hljs-attr">name</span>: <span class="hljs-string">'Logout'</span>,
	<span class="hljs-comment">// Dummy data</span>
	data() {
		<span class="hljs-keyword">return</span> {
			<span class="hljs-attr">message</span>: <span class="hljs-string">'Bye'</span>,
		};
	},
	<span class="hljs-comment">// After being created it logs out and go to home</span>
	created() {
		<span class="hljs-keyword">new</span> User().logout();
		<span class="hljs-comment">// `go` instead of `push` because refreshing the page updates the user data</span>
		<span class="hljs-comment">// Maybe using vuex is a better way to do it, or not...</span>
		<span class="hljs-keyword">this</span>.$router.go( <span class="hljs-string">'/'</span> );
	},
};
</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span></code></pre>



<p>Y Account recupera los datos del usuario una vez que ha creado el controlador:</p>



<pre class="wp-block-sentidoweb-snippet"><button class="sw-snippet-button" data-label-copy="Copiar" data-label-copied="Copiado">Copiar</button><code class="html sw_show_line_numbers hljs"><span class="hljs-tag">&lt;<span class="hljs-name">template</span>&gt;</span>
	<span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"account"</span>&gt;</span>
		<span class="hljs-tag">&lt;<span class="hljs-name">section</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"hero is-primary is-bold"</span>&gt;</span>
			<span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"hero-body"</span>&gt;</span>
				<span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"has-text-centered"</span>&gt;</span>
					<span class="hljs-tag">&lt;<span class="hljs-name">h1</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"title"</span>&gt;</span>
						{{ message }}
					<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
					<span class="hljs-tag">&lt;<span class="hljs-name">h2</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"subtitle"</span>&gt;</span>
						Your data
					<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
				<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
			<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
		<span class="hljs-tag">&lt;/<span class="hljs-name">section</span>&gt;</span>
		<span class="hljs-tag">&lt;<span class="hljs-name">section</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"section"</span>&gt;</span>
			<span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"container"</span>&gt;</span>
				<span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"tile is-ancestor"</span>&gt;</span>
					<span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"tile is-parent"</span>&gt;</span>
						<span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"tile is-child notification"</span>&gt;</span>
							Some content
						<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
					<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
					<span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"tile is-8 is-parent"</span>&gt;</span>
						<span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"tile is-child notification is-info"</span>&gt;</span>
							<span class="hljs-tag">&lt;<span class="hljs-name">ul</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"data"</span>&gt;</span>
								<span class="hljs-tag">&lt;<span class="hljs-name">li</span>
									<span class="hljs-attr">v-for</span>=<span class="hljs-string">"( value, key ) in user"</span>
									<span class="hljs-attr">:key</span>=<span class="hljs-string">"key"</span>
								&gt;</span>
									{{ key }} : {{ value }}
								<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
							<span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span>
						<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
					<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
				<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
			<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
		<span class="hljs-tag">&lt;/<span class="hljs-name">section</span>&gt;</span>
	<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">template</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
<span class="hljs-comment">// Dummy component</span>

<span class="hljs-keyword">import</span> ApiFectch <span class="hljs-keyword">from</span> <span class="hljs-string">'@/js/utils/api'</span>;
<span class="hljs-keyword">import</span> User <span class="hljs-keyword">from</span> <span class="hljs-string">'@/js/utils/user'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> {
	<span class="hljs-attr">name</span>: <span class="hljs-string">'Account'</span>,
	data() {
		<span class="hljs-keyword">return</span> {
			<span class="hljs-attr">message</span>: <span class="hljs-string">'Account'</span>,
			<span class="hljs-attr">user</span>: {},
		};
	},
	created() {
		<span class="hljs-keyword">const</span> user = <span class="hljs-keyword">new</span> User().getCurrentUser();
		<span class="hljs-keyword">new</span> ApiFectch().getUser( user.username )
			.then( <span class="hljs-function"><span class="hljs-params">response</span> =&gt;</span> <span class="hljs-keyword">this</span>.user = response );
	},
};
</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span></code></pre>



<p>Recuerda que tienes todo el código <a href="https://github.com/displaynone/hapi-vue-demo/releases/tag/0.8.0">aquí</a></p>
<p>The post <a href="https://sentidoweb.com/2019/09/01/hapi-js-vue-js-llamadas-autenticadas-al-servidor-con-jwt-login.php">Hapi.js + Vue.js llamadas autenticadas al servidor con JWT (login)</a> appeared first on <a href="https://sentidoweb.com">Sentido Web</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Hapi.js + Vue.js ejemplo mínimo de frontend: formulario login</title>
		<link>https://sentidoweb.com/2019/08/17/hapi-js-vue-js-ejemplo-minimo-de-frontend-formulario-login.php</link>
		
		<dc:creator><![CDATA[Luis]]></dc:creator>
		<pubDate>Sat, 17 Aug 2019 15:29:36 +0000</pubDate>
				<category><![CDATA[Javascript]]></category>
		<category><![CDATA[buefy]]></category>
		<category><![CDATA[bulma]]></category>
		<category><![CDATA[hapi.js]]></category>
		<category><![CDATA[icons]]></category>
		<category><![CDATA[material design]]></category>
		<category><![CDATA[vue-router]]></category>
		<category><![CDATA[vue.js]]></category>
		<guid isPermaLink="false">https://sentidoweb.com/?p=2986</guid>

					<description><![CDATA[<p>Esta parte es solo frontend, aún no está configurado para que interactúe con el servidor. Lo más destacado de este ejemplo es el uso de vue-router, paquete que permite la realización de SPA de forma sencilla. Como la aplicación será gestionada por un único fichero (index.html), es necesario configurar el servidor de webpack para que...</p>
<p>The post <a href="https://sentidoweb.com/2019/08/17/hapi-js-vue-js-ejemplo-minimo-de-frontend-formulario-login.php">Hapi.js + Vue.js ejemplo mínimo de frontend: formulario login</a> appeared first on <a href="https://sentidoweb.com">Sentido Web</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>Esta parte es solo frontend, aún no está configurado para que interactúe con el servidor.</p>



<p>Lo más destacado de este ejemplo es el uso de vue-router, paquete que permite la realización de SPA de forma sencilla. Como la aplicación será gestionada por un único fichero (<em>index.html</em>), es necesario configurar el servidor de webpack para que gestione las URLs que acceden a otras partes de la aplicación para que no devuelva un error 404.</p>



<p>Esto es fácil, tan solo hay que añadir historyApiFallback y ponerlo a true en la configuración del servidor.</p>



<pre class="wp-block-sentidoweb-snippet"><code class="javascript hljs">devServer: {
	<span class="hljs-attr">inline</span>: <span class="hljs-literal">true</span>,
	<span class="hljs-attr">hot</span>: <span class="hljs-literal">true</span>,
	<span class="hljs-attr">port</span>: <span class="hljs-number">9999</span>,
	<span class="hljs-attr">historyApiFallback</span>: <span class="hljs-literal">true</span>,
},</code></pre>



<p>Si usas Apache u otro servidor deberás usar una <a href="https://router.vuejs.org/guide/essentials/history-mode.html">configuración distinta</a>.</p>



<p>Lógicamente habrá que instalar el paquete <em>vue-router</em>.</p>



<p>Vale, ya está todo instalado, ahora solo hace falta configurar vue-router para que acepte distintas URLs y que muestre distintos controladores según sea el caso.</p>



<p>Para ello creamos un fichero <em>router.js</em> que posteriormente añadiremos a nuestra instancia de <em>Vue</em>:</p>





<p>Es fácil de entender, importamos los distintos controladores y configuramos las rutas (&#8216;<em>/</em>&#8216; y &#8216;<em>/login</em>&#8216;), a las que les asignaremos el controlador correspondiente.</p>



<p>Para indicar a Vue que vamos a usar vue-router, debemos importarlo en la instancia de la aplicación:</p>



<pre class="wp-block-sentidoweb-snippet"><code class="">import App from './components/App.vue';
import router from './router';

Vue.use( VueRouter );

new Vue( {
	el: '#app',
	router,
	components: {
		App,
	},
	render: ( c ) => c( 'app' ),
} );</code></pre>



<p>El siguiente paso es modificar el controlador principal de la aplicación <em>(App.vue</em>) para que muestre la cabecera (que tendrá su propio controlador) y la vista principal de vue-router (&lt;router-view>):</p>



<pre class="wp-block-sentidoweb-snippet"><button class="sw-snippet-button" data-label-copy="Copiar" data-label-copied="Copiado">Copiar</button><code class="html sw_show_line_numbers hljs"><span class="hljs-tag">&lt;<span class="hljs-name">template</span>&gt;</span>
	<span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
		<span class="hljs-tag">&lt;<span class="hljs-name">v-header</span> /&gt;</span>
		<span class="hljs-tag">&lt;<span class="hljs-name">router-view</span> /&gt;</span>
	<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">template</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
<span class="hljs-keyword">import</span> header <span class="hljs-keyword">from</span> <span class="hljs-string">'@/js/components/layout/Header'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> {
	<span class="hljs-attr">name</span>: <span class="hljs-string">'App'</span>,
	<span class="hljs-attr">components</span>: {
		<span class="hljs-string">'v-header'</span>: header,
	},
};
</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span></code></pre>



<p>Como no soy diseñador, pues usaré Buefy (basado en Bulma) y <a href="http://materialdesignicons.com/">Material Design icons</a> (no sé por qué le tengo algo de manía a FontAwesome).</p>



<p>Existe un paquete especial para usar Material Design en vue (<em>vue-material-design-icons</em>), que para funcionar con Buefy necesitará usar la fuente de letras de Material Design (<em>@mdi/fonts</em>). Instalamos todo y ya estará todo listo para empezar a diseñar nuestra página.</p>



<p>La cabecera (&lt;v-header>) mostrará el logo, el menú principal y otro secundario para loguearse. No explicaré ni las clases Bulma (que yo casi ni conozco) y cómo se muestra el menú al clickar en el burger icon, ya que estos tutoriales son para llevar yo un diario de cómo desarrollar una app web con Hapi.js y Vue.js.</p>



<p>El controlador de la cabecera nos quedará así:</p>



<pre class="wp-block-sentidoweb-snippet"><button class="sw-snippet-button" data-label-copy="Copiar" data-label-copied="Copiado">Copiar</button><code class="html sw_show_line_numbers hljs"><span class="hljs-tag">&lt;<span class="hljs-name">template</span>&gt;</span>
	<span class="hljs-tag">&lt;<span class="hljs-name">header</span>&gt;</span>
		<span class="hljs-tag">&lt;<span class="hljs-name">nav</span>
			<span class="hljs-attr">class</span>=<span class="hljs-string">"navbar"</span>
			<span class="hljs-attr">role</span>=<span class="hljs-string">"navigation"</span>
			<span class="hljs-attr">aria-label</span>=<span class="hljs-string">"main navigation"</span>
		&gt;</span>
			<span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"navbar-brand"</span>&gt;</span>
				<span class="hljs-tag">&lt;<span class="hljs-name">a</span>
					<span class="hljs-attr">class</span>=<span class="hljs-string">"navbar-item"</span>
					<span class="hljs-attr">href</span>=<span class="hljs-string">"https://sentidoweb.com"</span>
				&gt;</span>
					<span class="hljs-tag">&lt;<span class="hljs-name">img</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"/assets/images/logo.svg"</span>&gt;</span>
				<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>

				<span class="hljs-tag">&lt;<span class="hljs-name">a</span>
					<span class="hljs-attr">role</span>=<span class="hljs-string">"button"</span>
					<span class="hljs-attr">class</span>=<span class="hljs-string">"navbar-burger burger"</span>
					<span class="hljs-attr">aria-label</span>=<span class="hljs-string">"menu"</span>
					<span class="hljs-attr">aria-expanded</span>=<span class="hljs-string">"false"</span>
					<span class="hljs-attr">:class</span>=<span class="hljs-string">"{ 'is-active' : showNav }"</span>
					@<span class="hljs-attr">click</span>=<span class="hljs-string">"showNav = !showNav"</span>
				&gt;</span>
					<span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">aria-hidden</span>=<span class="hljs-string">"true"</span> /&gt;</span>
					<span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">aria-hidden</span>=<span class="hljs-string">"true"</span> /&gt;</span>
					<span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">aria-hidden</span>=<span class="hljs-string">"true"</span> /&gt;</span>
				<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
			<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

			<span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">:class</span>=<span class="hljs-string">"[ { 'is-active' : showNav }, 'navbar-menu' ]"</span>&gt;</span>
				<span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"navbar-start"</span>&gt;</span>
					<span class="hljs-tag">&lt;<span class="hljs-name">router-link</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"navbar-item"</span> <span class="hljs-attr">to</span>=<span class="hljs-string">'/'</span>&gt;</span>Home<span class="hljs-tag">&lt;/<span class="hljs-name">router-link</span>&gt;</span>
				<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

				<span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"navbar-end"</span>&gt;</span>
					<span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"navbar-item"</span>&gt;</span>
						<span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"buttons"</span>&gt;</span>
							<span class="hljs-tag">&lt;<span class="hljs-name">router-link</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"button is-light"</span> <span class="hljs-attr">to</span>=<span class="hljs-string">'/login'</span>&gt;</span>Log in<span class="hljs-tag">&lt;/<span class="hljs-name">router-link</span>&gt;</span>
						<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
					<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
				<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
			<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
		<span class="hljs-tag">&lt;/<span class="hljs-name">nav</span>&gt;</span>
	<span class="hljs-tag">&lt;/<span class="hljs-name">header</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">template</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> {
	data() {
		<span class="hljs-keyword">return</span> {
			<span class="hljs-attr">showNav</span>: <span class="hljs-literal">false</span>,
		};
	},
};
</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span></code></pre>



<p>Tan solo mencionar cómo vue-router gestiona la navegación, para ello hace uso de &lt;router-link>:</p>



<pre class="wp-block-sentidoweb-snippet"><code class="html">&lt;router-link class="navbar-item" to='/'>Home&lt;/router-link></code></pre>



<p>El resto del <a href="https://github.com/displaynone/hapi-vue-demo/releases/tag/0.7.0">código</a> es simplemente la página principal y el formulario &#8220;tonto&#8221;.</p>
<p>The post <a href="https://sentidoweb.com/2019/08/17/hapi-js-vue-js-ejemplo-minimo-de-frontend-formulario-login.php">Hapi.js + Vue.js ejemplo mínimo de frontend: formulario login</a> appeared first on <a href="https://sentidoweb.com">Sentido Web</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Hapi.js + Vue.js inicializar el frontend</title>
		<link>https://sentidoweb.com/2019/07/16/hapi-js-vue-js-inicializar-el-frontend.php</link>
		
		<dc:creator><![CDATA[Luis]]></dc:creator>
		<pubDate>Tue, 16 Jul 2019 21:44:40 +0000</pubDate>
				<category><![CDATA[Javascript]]></category>
		<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[buefy]]></category>
		<category><![CDATA[bulma]]></category>
		<category><![CDATA[eslint]]></category>
		<category><![CDATA[hapi.js]]></category>
		<category><![CDATA[sass]]></category>
		<category><![CDATA[vue.js]]></category>
		<category><![CDATA[webpack]]></category>
		<guid isPermaLink="false">https://sentidoweb.com/?p=2979</guid>

					<description><![CDATA[<p>El backend ya está algo configurado, por lo que voy a empezar a configurar el frontend. Instalaré varias librerías: Vue.js Webpack: configurado para que funcione con HMR Eslint: para que no haya errores Javascript Stylelint: lo mismo para CSS Buefy: una librería que combina Bulma y Vue Sass loader En vez de ir instalando una...</p>
<p>The post <a href="https://sentidoweb.com/2019/07/16/hapi-js-vue-js-inicializar-el-frontend.php">Hapi.js + Vue.js inicializar el frontend</a> appeared first on <a href="https://sentidoweb.com">Sentido Web</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>El backend ya está algo configurado, por lo que voy a empezar a configurar el frontend.</p>



<p>Instalaré varias librerías:</p>



<ul class="wp-block-list"><li>Vue.js</li><li>Webpack: configurado para que funcione con HMR</li><li>Eslint: para que no haya errores Javascript</li><li>Stylelint: lo mismo para CSS</li><li>Buefy: una librería que combina Bulma y Vue</li><li>Sass loader</li></ul>



<p>En vez de ir instalando una a una, usando el siguiente <em>package.json</em> y ejecutando <em>npm i</em>, lo tendremos todo instalado.</p>



<pre class="wp-block-sentidoweb-snippet"><button class="sw-snippet-button" data-label-copy="Copiar" data-label-copied="Copiado">Copiar</button><code class="json sw_show_line_numbers hljs">{
  <span class="hljs-attr">"name"</span>: <span class="hljs-string">"hapi-frontend"</span>,
  <span class="hljs-attr">"version"</span>: <span class="hljs-string">"1.0.0"</span>,
  <span class="hljs-attr">"description"</span>: <span class="hljs-string">"Hapi.js frontend"</span>,
  <span class="hljs-attr">"main"</span>: <span class="hljs-string">"index.js"</span>,
  <span class="hljs-attr">"scripts"</span>: {
    <span class="hljs-attr">"test"</span>: <span class="hljs-string">"echo \"Error: no test specified\" &amp;&amp; exit 1"</span>,
    <span class="hljs-attr">"dev"</span>: <span class="hljs-string">"webpack-dev-server --mode development --config webpack.config.dev.js"</span>,
    <span class="hljs-attr">"build"</span>: <span class="hljs-string">"webpack --mode production --config webpack.config.production.js"</span>
  },
  <span class="hljs-attr">"license"</span>: <span class="hljs-string">"ISC"</span>,
  <span class="hljs-attr">"devDependencies"</span>: {
    <span class="hljs-attr">"@babel/core"</span>: <span class="hljs-string">"^7.5.4"</span>,
    <span class="hljs-attr">"babel-eslint"</span>: <span class="hljs-string">"^10.0.2"</span>,
    <span class="hljs-attr">"babel-loader"</span>: <span class="hljs-string">"^8.0.6"</span>,
    <span class="hljs-attr">"css-loader"</span>: <span class="hljs-string">"^3.0.0"</span>,
    <span class="hljs-attr">"eslint"</span>: <span class="hljs-string">"^6.0.1"</span>,
    <span class="hljs-attr">"eslint-plugin-html"</span>: <span class="hljs-string">"^6.0.0"</span>,
    <span class="hljs-attr">"eslint-plugin-vue"</span>: <span class="hljs-string">"^5.2.3"</span>,
    <span class="hljs-attr">"mini-css-extract-plugin"</span>: <span class="hljs-string">"^0.7.0"</span>,
    <span class="hljs-attr">"node-sass"</span>: <span class="hljs-string">"^4.12.0"</span>,
    <span class="hljs-attr">"sass-loader"</span>: <span class="hljs-string">"^7.1.0"</span>,
    <span class="hljs-attr">"stylelint"</span>: <span class="hljs-string">"^10.1.0"</span>,
    <span class="hljs-attr">"stylelint-config-standard"</span>: <span class="hljs-string">"^18.3.0"</span>,
    <span class="hljs-attr">"stylelint-webpack-plugin"</span>: <span class="hljs-string">"^0.10.5"</span>,
    <span class="hljs-attr">"vue-hot-reload-api"</span>: <span class="hljs-string">"^2.3.3"</span>,
    <span class="hljs-attr">"vue-html-loader"</span>: <span class="hljs-string">"^1.2.4"</span>,
    <span class="hljs-attr">"vue-loader"</span>: <span class="hljs-string">"^15.7.0"</span>,
    <span class="hljs-attr">"vue-style-loader"</span>: <span class="hljs-string">"^4.1.2"</span>,
    <span class="hljs-attr">"vue-template-compiler"</span>: <span class="hljs-string">"^2.6.10"</span>,
    <span class="hljs-attr">"webpack"</span>: <span class="hljs-string">"^4.35.3"</span>,
    <span class="hljs-attr">"webpack-cli"</span>: <span class="hljs-string">"^3.3.5"</span>,
    <span class="hljs-attr">"webpack-dev-server"</span>: <span class="hljs-string">"^3.7.2"</span>,
    <span class="hljs-attr">"webpack-merge"</span>: <span class="hljs-string">"^4.2.1"</span>
  },
  <span class="hljs-attr">"dependencies"</span>: {
    <span class="hljs-attr">"buefy"</span>: <span class="hljs-string">"^0.7.10"</span>,
    <span class="hljs-attr">"vue"</span>: <span class="hljs-string">"^2.6.10"</span>
  }
}
</code></pre>



<p>Como se puede ver, existen dos scripts dentro de npm: <em>build</em> que compila el js y extrae los CSS, y <em>dev</em>, que arranca el servidor de webpack habilitando HMR (<img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f3b6.png" alt="🎶" class="wp-smiley" style="height: 1em; max-height: 1em;" /> ¡ya no puedo vivir sin él! <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f3b6.png" alt="🎶" class="wp-smiley" style="height: 1em; max-height: 1em;" />). </p>



<p>Ambas configuraciones de webpack usan un script en común (<em>webpack.config.common.js</em>):</p>



<pre class="wp-block-sentidoweb-snippet"><button class="sw-snippet-button" data-label-copy="Copiar" data-label-copied="Copiado">Copiar</button><code class="javascript sw_show_line_numbers hljs"><span class="hljs-keyword">const</span> webpack = <span class="hljs-built_in">require</span>( <span class="hljs-string">'webpack'</span> );
<span class="hljs-keyword">const</span> path = <span class="hljs-built_in">require</span>( <span class="hljs-string">'path'</span> );
<span class="hljs-comment">// Carga los ficheros .vue</span>
<span class="hljs-keyword">const</span> VueLoaderPlugin = <span class="hljs-built_in">require</span>( <span class="hljs-string">'vue-loader/lib/plugin'</span> );
<span class="hljs-comment">// Configura stylelint</span>
<span class="hljs-keyword">const</span> StyleLintPlugin = <span class="hljs-built_in">require</span>( <span class="hljs-string">'stylelint-webpack-plugin'</span> );

<span class="hljs-comment">// Para obtener un path para los alias</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">resolve</span>(<span class="hljs-params"> dir </span>) </span>{
	<span class="hljs-keyword">return</span> path.join( __dirname, <span class="hljs-string">'.'</span>, dir );
}

<span class="hljs-built_in">module</span>.exports = {
	<span class="hljs-attr">mode</span>: <span class="hljs-string">'production'</span>,
	<span class="hljs-comment">// Fichero inicial del proyecto</span>
	entry: <span class="hljs-string">'./js/main.js'</span>,
	<span class="hljs-comment">// Fichero final para incluir</span>
	output: {
		<span class="hljs-attr">filename</span>: <span class="hljs-string">'js/main.js'</span>,
		<span class="hljs-attr">publicPath</span>: <span class="hljs-string">'/dist/'</span>,
	},
	<span class="hljs-attr">module</span>: {
		<span class="hljs-comment">// Reglas para los ficheros</span>
		rules: [
			{
				<span class="hljs-attr">test</span>: <span class="hljs-regexp">/\.js$/</span>,
				<span class="hljs-attr">exclude</span>: <span class="hljs-regexp">/node_modules/</span>,
				<span class="hljs-attr">loader</span>: <span class="hljs-string">'babel-loader'</span>,
			},
			{
				<span class="hljs-attr">test</span>: <span class="hljs-regexp">/\.vue$/</span>,
				<span class="hljs-attr">loader</span>: <span class="hljs-string">'vue-loader'</span>,
			},
			{
				<span class="hljs-attr">test</span>: <span class="hljs-regexp">/\.css$/</span>,
				<span class="hljs-attr">use</span>: [
					<span class="hljs-string">'css-loader'</span>,
					<span class="hljs-string">'sass-loader'</span>,
				],
			},
		],
	},
	<span class="hljs-attr">plugins</span>: [
		<span class="hljs-keyword">new</span> webpack.HotModuleReplacementPlugin(),
		<span class="hljs-keyword">new</span> VueLoaderPlugin(),
		<span class="hljs-keyword">new</span> StyleLintPlugin( {
			<span class="hljs-attr">files</span>: [ <span class="hljs-string">'**/*.{vue,htm,html,css,sss,less,scss,sass}'</span> ],
		} ),
	],
	<span class="hljs-attr">resolve</span>: {
		<span class="hljs-attr">extensions</span>: [ <span class="hljs-string">'.js'</span>, <span class="hljs-string">'.vue'</span>, <span class="hljs-string">'.json'</span> ],
		<span class="hljs-attr">alias</span>: {
			<span class="hljs-string">'@'</span>: resolve( <span class="hljs-string">''</span> ),
		},
	},
};</code></pre>



<p>El frontend se gestiona desde el fichero main.js, que inicializará Vue y añadirá el componente principal:</p>



<pre class="wp-block-sentidoweb-snippet"><button class="sw-snippet-button" data-label-copy="Copiar" data-label-copied="Copiado">Copiar</button><code class="java sw_show_line_numbers hljs"><span class="hljs-keyword">import</span> Vue from <span class="hljs-string">'vue'</span>;
<span class="hljs-keyword">import</span> Buefy from <span class="hljs-string">'buefy'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'buefy/dist/buefy.css'</span>;

<span class="hljs-keyword">import</span> App from <span class="hljs-string">'./components/App.vue'</span>;

<span class="hljs-keyword">import</span> <span class="hljs-string">'@/assets/scss/main.scss'</span>;

Vue.use( Buefy );

<span class="hljs-keyword">new</span> Vue( {
	el: <span class="hljs-string">'#app'</span>,
	components: {
		App,
	},
	render: ( c ) =&gt; c( <span class="hljs-string">'app'</span> ),
} );

<span class="hljs-comment">// accept replacement modules</span>
<span class="hljs-keyword">if</span> ( <span class="hljs-keyword">module</span>.hot ) {
	<span class="hljs-keyword">module</span>.hot.accept();
}</code></pre>



<p>Y ya por último el componente App.vue, que muestra simplemente un poco de HTML</p>



<pre class="wp-block-sentidoweb-snippet"><button class="sw-snippet-button" data-label-copy="Copiar" data-label-copied="Copiado">Copiar</button><code class="javascript sw_show_line_numbers hljs">&lt;template&gt;
	&lt;header class="hero"&gt;
		&lt;div class="hero-head"&gt;
			&lt;h1&gt;{{ title }}&lt;/h1&gt;
		&lt;/div&gt;
	&lt;/header&gt;
&lt;/template&gt;

&lt;script&gt;
export default {
	data() {
		return {
			title: "Demo site",
		};
	},
};
&lt;/script&gt;

&lt;style lang="scss" scoped&gt;
	div {

		h1 {
			color: #fff;
		}
	}
&lt;/style&gt;
</code></pre>



<p>Bueno, ha sido un resumen rápido, pero bajándote el <a href="https://github.com/displaynone/hapi-vue-demo/releases/tag/0.6.0">código</a> seguro que lo entiendes fácil</p>
<p>The post <a href="https://sentidoweb.com/2019/07/16/hapi-js-vue-js-inicializar-el-frontend.php">Hapi.js + Vue.js inicializar el frontend</a> appeared first on <a href="https://sentidoweb.com">Sentido Web</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Hapi.js + Vue.js modelos mejorados</title>
		<link>https://sentidoweb.com/2019/06/25/hapi-js-vue-js-modelos-mejorados.php</link>
		
		<dc:creator><![CDATA[Luis]]></dc:creator>
		<pubDate>Mon, 24 Jun 2019 22:44:34 +0000</pubDate>
				<category><![CDATA[Javascript]]></category>
		<category><![CDATA[boom]]></category>
		<category><![CDATA[hapi.js]]></category>
		<category><![CDATA[joi]]></category>
		<category><![CDATA[vue.js]]></category>
		<guid isPermaLink="false">https://sentidoweb.com/?p=2973</guid>

					<description><![CDATA[<p>En este caso voy a explicar cómo añadir una ruta en el backend para gestionar usuarios. Antes de nada vamos a instalar tres paquetes: @hapi/joi para validar los datos de entrada @hapi/boom para mostrar errores HTTP de forma sencilla bcrypt para encriptar la contraseña Tendremos que añadir la ruta al manifest de glee Ahora solo...</p>
<p>The post <a href="https://sentidoweb.com/2019/06/25/hapi-js-vue-js-modelos-mejorados.php">Hapi.js + Vue.js modelos mejorados</a> appeared first on <a href="https://sentidoweb.com">Sentido Web</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>En este caso voy a explicar cómo añadir una ruta en el backend para gestionar usuarios.</p>



<p>Antes de nada vamos a instalar tres paquetes:</p>



<ul class="wp-block-list"><li><em>@hapi/joi</em> para validar los datos de entrada</li><li><em>@hapi/boom</em> para mostrar errores HTTP de forma sencilla</li><li><em>bcrypt</em> para encriptar la contraseña</li></ul>



<pre class="wp-block-sentidoweb-snippet"><button class="sw-snippet-button" data-label-copy="Copiar" data-label-copied="Copiado">Copiar</button><code class="bash hljs">npm i @hapi/joi
npm i @hapi/boom
npm i bcrypt</code></pre>



<p>Tendremos que añadir la ruta al <a href="https://github.com/displaynone/hapi-vue-demo/blob/0.4.0/backend/config/manifest.js#L8">manifest de glee</a></p>



<pre class="wp-block-sentidoweb-snippet"><button class="sw-snippet-button" data-label-copy="Copiar" data-label-copied="Copiado">Copiar</button><code class="javascript sw_show_line_numbers hljs"><span class="hljs-keyword">const</span> manifest = {
	<span class="hljs-attr">server</span>: {
		<span class="hljs-attr">port</span>: Config.get( <span class="hljs-string">'/server/port'</span> ),
	},
	<span class="hljs-attr">register</span>: {
		<span class="hljs-attr">plugins</span>: [
			{
				<span class="hljs-attr">plugin</span>: <span class="hljs-string">'./api/home'</span>,
			},
			{
				<span class="hljs-attr">plugin</span>: <span class="hljs-string">'./api/user'</span>,
			},
			{
				<span class="hljs-attr">plugin</span>: <span class="hljs-string">'./plugins/db'</span>,
				<span class="hljs-attr">options</span>: Config.get( <span class="hljs-string">'/db'</span> ),
			},
		],
	},
};
</code></pre>



<p>Ahora solo falta crear el controlador para las rutas de usuarios, dos en este caso:</p>



<ul class="wp-block-list"><li>GET <em>/user/[user]</em> para recuperar un usuario</li><li>PUT <em>/user</em> para crear un nuevo usuario</li></ul>



<p>Lógicamente aún no hay nada de autenticación, por lo que cualquiera puede crear un usuario realizando una llamada PUT a la URL indicando <em>userName</em>, <em>email</em> y <em>password</em>.</p>



<p>Para comprobar la validez de los datos introducidos, usaremos <em>joi</em>. Usando las opciones de la ruta, indicaremos las reglas que deberá cumplir cada parámetro introducido. Así, para recuperar un usuario, se comprobará que  user sea string, alfanumérico y que tenga una longitud de 3 a 20 caracteres:</p>



<pre class="wp-block-sentidoweb-snippet"><code class="javascript hljs">validate: {
	<span class="hljs-attr">params</span>: {
		<span class="hljs-attr">user</span>: Joi.string().alphanum().min( <span class="hljs-number">3</span> ).max( <span class="hljs-number">20</span> ),
	},
},</code></pre>



<p>Así de fácil.</p>



<p>En el caso de comprobar los datos de entrada de la llamada PUT, en vez de usar <em>params</em>, usaremos <em>payload</em>:</p>



<pre class="wp-block-sentidoweb-snippet"><code class="javascript hljs">validate: {
	<span class="hljs-attr">payload</span>: {
		<span class="hljs-attr">userName</span>: Joi.string().alphanum().min( <span class="hljs-number">3</span> ).max( <span class="hljs-number">20</span> ).required(),
		<span class="hljs-attr">email</span>: Joi.string().email().required(),
		<span class="hljs-attr">password</span>: Joi.string().min( <span class="hljs-number">8</span> ).required(),
	},
},</code></pre>



<p>Por último mostrar el código para crear un nuevo usuario. Primero se comprueba si existe un usuario con ese nickname o email. Si es así, se devuelve error usando boom, si no, se genera la contraseña encriptada (aquí no me he molestado mucho en ello, ya lo haré más adelante), y se crea el usuario usando el método <em>create</em> de <em>moongose</em>:</p>



<pre class="wp-block-sentidoweb-snippet"><button class="sw-snippet-button" data-label-copy="Copiar" data-label-copied="Copiado">Copiar</button><code class="javascript sw_show_line_numbers hljs"><span class="hljs-comment">/**
 * Route handler
 *
 * @param {object} request
 * @param {object} h Hapi object
 * @returns {object}
 */</span>
handler: <span class="hljs-keyword">async</span>( request, h ) =&gt; { <span class="hljs-comment">// eslint-disable-line</span>
	<span class="hljs-keyword">try</span> {
		<span class="hljs-comment">// <span class="hljs-doctag">TODO:</span> Add role</span>
		<span class="hljs-keyword">const</span> user = <span class="hljs-keyword">await</span> User
			.findOne( {
				<span class="hljs-attr">$or</span>: [
					{ <span class="hljs-attr">userName</span>: request.payload.username },
					{ <span class="hljs-attr">email</span>: request.payload.email },
				],
			} )
			.exec();
		<span class="hljs-keyword">if</span> ( user ) {
			<span class="hljs-keyword">return</span> Boom.badData( <span class="hljs-string">'User exists'</span> );
		}
		<span class="hljs-keyword">const</span> password = <span class="hljs-keyword">await</span> bcrypt.hash( request.payload.password, Config.get( <span class="hljs-string">'/hash/PASSWORD_HASH'</span> ) );
		<span class="hljs-keyword">const</span> userData = <span class="hljs-built_in">Object</span>.assign( {}, request.payload, { password } );
		<span class="hljs-keyword">const</span> newUser = <span class="hljs-keyword">await</span> User.create( userData );

		<span class="hljs-keyword">return</span> newUser ?
			{
				<span class="hljs-attr">response</span>: <span class="hljs-literal">true</span>,
				<span class="hljs-attr">message</span>: <span class="hljs-string">'User created'</span>,
				<span class="hljs-attr">userId</span>: newUser.id,
			} :
			Boom.boomify( {
				<span class="hljs-attr">response</span>: <span class="hljs-literal">false</span>,
				<span class="hljs-attr">message</span>: <span class="hljs-string">'There was an error during user creation'</span>,
			}, { <span class="hljs-attr">statusCode</span>: <span class="hljs-number">400</span> } );
	} <span class="hljs-keyword">catch</span> ( error ) {
		<span class="hljs-keyword">return</span> Boom.badImplementation( <span class="hljs-string">'Error'</span>, { error } );
	}
},</code></pre>



<p>Como último apunte, he modificado la configuración para que admita ficheros <em>.env</em> con los datos necesarios.</p>



<p>Como siempre te puedes bajar el código <a href="https://github.com/displaynone/hapi-vue-demo/releases/tag/0.5.0">aquí</a></p>
<p>The post <a href="https://sentidoweb.com/2019/06/25/hapi-js-vue-js-modelos-mejorados.php">Hapi.js + Vue.js modelos mejorados</a> appeared first on <a href="https://sentidoweb.com">Sentido Web</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Hapi.js + Vue.js accediendo a mongodb</title>
		<link>https://sentidoweb.com/2019/06/10/hapi-js-vue-js-accediendo-a-mongodb.php</link>
		
		<dc:creator><![CDATA[Luis]]></dc:creator>
		<pubDate>Sun, 09 Jun 2019 22:19:32 +0000</pubDate>
				<category><![CDATA[Javascript]]></category>
		<category><![CDATA[hapi.js]]></category>
		<category><![CDATA[mongodb]]></category>
		<category><![CDATA[mongoose]]></category>
		<category><![CDATA[vue.js]]></category>
		<guid isPermaLink="false">https://sentidoweb.com/?p=2967</guid>

					<description><![CDATA[<p>Como ya dije, esta aplicación estará basada en Mongodb, y usaremos mongoose como ODM. El primer paso es instalar mongoose: Una vez instalado crearemos un controlador que nos permita usar la BD en toda la aplicación Hapi.js. Para ello haremos uso de los decorate del servidor. Los decorations permite extender objectos ofrecidos por Hapi.js, en...</p>
<p>The post <a href="https://sentidoweb.com/2019/06/10/hapi-js-vue-js-accediendo-a-mongodb.php">Hapi.js + Vue.js accediendo a mongodb</a> appeared first on <a href="https://sentidoweb.com">Sentido Web</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>Como ya dije, esta aplicación estará basada en Mongodb, y usaremos <a href="https://mongoosejs.com/">mongoose</a> como ODM.</p>



<p>El primer paso es instalar mongoose:</p>



<pre class="wp-block-sentidoweb-snippet"><button class="sw-snippet-button" data-label-copy="Copy" data-label-copied="Copied">Copy</button><code class="bash hljs">npm i mongoose</code></pre>



<p>Una vez instalado crearemos un controlador que nos permita usar la BD en toda la aplicación Hapi.js. Para ello haremos uso de los <a href="https://hapijs.com/api#server.decorate()">decorate</a> del servidor. Los decorations permite extender objectos ofrecidos por Hapi.js, en nuestro caso server y request. Usando un plugin nos conectaremos a mongodb usando mongoose y añadiremos ese objecto con los decorate.</p>



<p>Creamos el fichero <em>/plugins/db.js</em> con el siguiente código:</p>



<pre class="wp-block-sentidoweb-snippet"><button class="sw-snippet-button" data-label-copy="Copiar" data-label-copied="Copiado">Copiar</button><code class="javascript sw_show_line_numbers hljs"><span class="hljs-comment">/**
 * DB controller
 *
 * It uses Mongoose and "stores" it in the server and the request using `decorate`
 */</span>
<span class="hljs-keyword">const</span> mongoose = <span class="hljs-built_in">require</span>( <span class="hljs-string">'mongoose'</span> );

exports.plugin = {
	<span class="hljs-attr">name</span>: <span class="hljs-string">'db'</span>,
	<span class="hljs-attr">register</span>: <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"> server, options </span>) </span>{
		mongoose.connect( options.url, { <span class="hljs-attr">useNewUrlParser</span>: <span class="hljs-literal">true</span> } );
		<span class="hljs-keyword">const</span> db = mongoose.connection;
		<span class="hljs-comment">// eslint-disable-next-line</span>
		db.on( <span class="hljs-string">'error'</span>, <span class="hljs-built_in">console</span>.error.bind( <span class="hljs-built_in">console</span>, <span class="hljs-string">'connection error:'</span> ) );
		db.once( <span class="hljs-string">'open'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{
			server.decorate( <span class="hljs-string">'server'</span>, <span class="hljs-string">'db'</span>, mongoose );
			server.decorate( <span class="hljs-string">'request'</span>, <span class="hljs-string">'db'</span>, mongoose );
			<span class="hljs-comment">// eslint-disable-next-line</span>
			<span class="hljs-built_in">console</span>.log( <span class="hljs-string">'DB connected'</span> );
		} );
	},
};</code></pre>



<p>Para configurar la conectividad a <a href="https://github.com/displaynone/hapi-vue-demo/releases/tag/0.4.0">mongodb</a> tendremos que añadir los datos a la <em>/config/index.js</em></p>



<pre class="wp-block-sentidoweb-snippet"><button class="sw-snippet-button" data-label-copy="Copiar" data-label-copied="Copiado">Copiar</button><code class="javascript hljs"><span class="hljs-keyword">const</span> config = {
	<span class="hljs-attr">server</span>: {
		<span class="hljs-attr">port</span>: <span class="hljs-number">3001</span>,
	},
	<span class="hljs-attr">website</span>: {
		<span class="hljs-attr">name</span>: <span class="hljs-string">`WP Desk`</span>,
	},
	<span class="hljs-attr">db</span>: {
		<span class="hljs-attr">url</span>: <span class="hljs-string">'mongodb://localhost/wpdesk'</span>,
	},
};</code></pre>



<p>Y en el manifiest usado por <em>glue</em>, tendremos que añadir el nuevo plugin y las nuevas opciones de conexión:</p>



<pre class="wp-block-sentidoweb-snippet"><code class="javascript hljs"><span class="hljs-keyword">const</span> manifest = {
	<span class="hljs-attr">server</span>: {
		<span class="hljs-attr">port</span>: Config.get( <span class="hljs-string">'/server/port'</span> ),
	},
	<span class="hljs-attr">register</span>: {
		<span class="hljs-attr">plugins</span>: [
			{
				<span class="hljs-attr">plugin</span>: <span class="hljs-string">'./api/home'</span>,
			},
			{
				<span class="hljs-attr">plugin</span>: <span class="hljs-string">'./plugins/db'</span>,
				<span class="hljs-attr">options</span>: Config.get( <span class="hljs-string">'/db'</span> ),
			},
		],
	},
};</code></pre>



<p>Ya tenemos casi todo configurado, ahora vamos a empezar con un ejemplo creando un esquema de moongose que nos permite acceder a colecciones de mongodb. </p>



<p>Lo más común es tener una colección de usuarios, que tendrá los siguientes campos:</p>



<ul class="wp-block-list"><li><em>userName</em>: de tipo String,</li><li><em>firstName</em>: de tipo String,</li><li><em>lastName</em>: de tipo String,</li><li><em>email</em>: de tipo String,</li><li><em>role</em>: que referencia a otro elemento de otra colección,</li><li><em>isEnabled</em>: de tipo Boolean,</li><li><em>password</em>: de tipo String,</li><li><em>resetPassword</em>: un objeto representado por:<ul><li><em>hash</em>: de tipo String,</li><li><em>active</em>: de tipo Boolean,</li></ul></li></ul>



<p>También crearemos un método estático que devuelva todos los elementos de la colección users para realizar pruebas:</p>



<pre class="wp-block-sentidoweb-snippet"><button class="sw-snippet-button" data-label-copy="Copiar" data-label-copied="Copiado">Copiar</button><code class="javascript sw_show_line_numbers hljs"><span class="hljs-comment">/**
 * User model based on Mongoose
 */</span>
<span class="hljs-keyword">const</span> mongoose = <span class="hljs-built_in">require</span>( <span class="hljs-string">'mongoose'</span> );
<span class="hljs-keyword">const</span> Schema = mongoose.Schema;

<span class="hljs-comment">// Mongoose schema</span>
<span class="hljs-keyword">const</span> userSchema = <span class="hljs-keyword">new</span> mongoose.Schema( {
	<span class="hljs-attr">userName</span>: <span class="hljs-built_in">String</span>,
	<span class="hljs-attr">firstName</span>: <span class="hljs-built_in">String</span>,
	<span class="hljs-attr">lastName</span>: <span class="hljs-built_in">String</span>,
	<span class="hljs-attr">email</span>: <span class="hljs-built_in">String</span>,
	<span class="hljs-attr">role</span>: Schema.Types.ObjectId,
	<span class="hljs-attr">isEnabled</span>: <span class="hljs-built_in">Boolean</span>,
	<span class="hljs-attr">password</span>: <span class="hljs-built_in">String</span>,
	<span class="hljs-attr">resetPassword</span>: {
		<span class="hljs-attr">hash</span>: <span class="hljs-built_in">String</span>,
		<span class="hljs-attr">active</span>: <span class="hljs-built_in">Boolean</span>,
	},
} );

<span class="hljs-comment">/**
 * User static model findAll
 *
 * @returns {array}
 */</span>
userSchema.static( <span class="hljs-string">'findAll'</span>, <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{
	<span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>( <span class="hljs-function">(<span class="hljs-params"> resolve, reject </span>) =&gt;</span> {
		<span class="hljs-keyword">this</span>.model( <span class="hljs-string">'User'</span> ).find( {} ).exec( <span class="hljs-function">(<span class="hljs-params"> error, data </span>) =&gt;</span> {
			<span class="hljs-keyword">if</span> ( error ) {
				reject( error );
			}
			resolve( data );
		} );
	} );
	<span class="hljs-keyword">return</span> result;
} );

<span class="hljs-keyword">const</span> User = mongoose.model( <span class="hljs-string">'User'</span>, userSchema );

<span class="hljs-built_in">module</span>.exports = User;</code></pre>



<p>Ya está todo, ahora solo modificamos el handler de la ruta <em>home.js</em> para mostrar los valores de <em>findAll</em>:</p>



<pre class="wp-block-sentidoweb-snippet"><button class="sw-snippet-button" data-label-copy="Copiar" data-label-copied="Copiado">Copiar</button><code class="javascript sw_show_line_numbers hljs"><span class="hljs-comment">/**
 * Route handler
 *
 * @param {object} request
 * @param {object} h Hapi object
 * @returns {object}
 */</span>
handler: <span class="hljs-keyword">async</span>( request, h ) =&gt; { <span class="hljs-comment">// eslint-disable-line</span>
	<span class="hljs-keyword">try</span> {
		<span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> User.findAll();
		<span class="hljs-keyword">return</span> result;
	} <span class="hljs-keyword">catch</span> ( error ) {
		<span class="hljs-keyword">return</span> { <span class="hljs-attr">error</span>: <span class="hljs-number">500</span> };
	}
},</code></pre>



<p>Puedes bajarte el código <a href="https://github.com/displaynone/hapi-vue-demo/releases/tag/0.4.0">aquí</a></p>
<p>The post <a href="https://sentidoweb.com/2019/06/10/hapi-js-vue-js-accediendo-a-mongodb.php">Hapi.js + Vue.js accediendo a mongodb</a> appeared first on <a href="https://sentidoweb.com">Sentido Web</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Hapi.js + Vue.js empezando con las routes</title>
		<link>https://sentidoweb.com/2019/05/07/hapi-js-vue-js-empezando-con-las-routes.php</link>
		
		<dc:creator><![CDATA[Luis]]></dc:creator>
		<pubDate>Tue, 07 May 2019 17:32:15 +0000</pubDate>
				<category><![CDATA[Javascript]]></category>
		<category><![CDATA[hapi.js]]></category>
		<category><![CDATA[routes]]></category>
		<category><![CDATA[vue.js]]></category>
		<guid isPermaLink="false">https://sentidoweb.com/?p=2961</guid>

					<description><![CDATA[<p>Empezamos a mostrar contenido, hasta ahora solo mostraba un error 404 cuando se accedía a la URL del servidor. Ahora vamos a añadir una route simple que muestre un objeto JSON cuando se acceda a la home. Recordad que la parte del servidor solo va a devolver respuestas JSON que tratará el frontend. Usaremos plugins...</p>
<p>The post <a href="https://sentidoweb.com/2019/05/07/hapi-js-vue-js-empezando-con-las-routes.php">Hapi.js + Vue.js empezando con las routes</a> appeared first on <a href="https://sentidoweb.com">Sentido Web</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>Empezamos a mostrar contenido, hasta ahora solo mostraba un error 404 cuando se accedía a la URL del servidor.</p>



<p>Ahora vamos a añadir una route simple que muestre un objeto JSON cuando se acceda a la home. Recordad que la parte del servidor solo va a devolver respuestas JSON que tratará el frontend.</p>



<p>Usaremos <a href="https://hapijs.com/tutorials/plugins">plugins</a> para tratar todas las rutas que incluiremos en <em>glue</em>. En este ejemplo tan solo añadiremos un route al server que devuelva un mensaje:</p>



<pre class="wp-block-sentidoweb-snippet"><button class="sw-snippet-button" data-label-copy="Copiar" data-label-copied="Copiado">Copiar</button><code class="javascript sw_show_line_numbers hljs"><span class="hljs-keyword">const</span> Config = <span class="hljs-built_in">require</span>( <span class="hljs-string">'../config'</span> );

<span class="hljs-keyword">const</span> websiteName = Config.get( <span class="hljs-string">'/website/name'</span> );

<span class="hljs-keyword">const</span> register = <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"> server, serverOptions </span>) </span>{ <span class="hljs-comment">// eslint-disable-line</span>
	server.route( {
		<span class="hljs-attr">method</span>: <span class="hljs-string">'GET'</span>,
		<span class="hljs-attr">path</span>: <span class="hljs-string">'/'</span>,
		<span class="hljs-attr">options</span>: {
			<span class="hljs-attr">tags</span>: [ <span class="hljs-string">'api'</span>, <span class="hljs-string">'home'</span> ],
			<span class="hljs-attr">description</span>: <span class="hljs-string">'Server home'</span>,
			<span class="hljs-attr">notes</span>: <span class="hljs-string">'Server home'</span>,
			<span class="hljs-attr">auth</span>: <span class="hljs-literal">false</span>,
		},
		<span class="hljs-attr">handler</span>: <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"> request, h </span>) </span>{ <span class="hljs-comment">// eslint-disable-line</span>
			<span class="hljs-keyword">return</span> {
				<span class="hljs-attr">message</span>: <span class="hljs-string">`Welcome to <span class="hljs-subst">${ websiteName }</span>`</span>,
			};
		},
	} );
};

<span class="hljs-built_in">module</span>.exports = {
	<span class="hljs-attr">name</span>: <span class="hljs-string">'api-home'</span>,
	<span class="hljs-attr">dependencies</span>: [],
	register,
};</code></pre>



<p>Ahora solo falta añadir el plugin en el <em>manifest.js</em></p>



<pre class="wp-block-sentidoweb-snippet"><button class="sw-snippet-button" data-label-copy="Copiar" data-label-copied="Copiado">Copiar</button><code class="javascript sw_show_line_numbers hljs"><span class="hljs-keyword">const</span> manifest = {
	<span class="hljs-attr">server</span>: {
		<span class="hljs-attr">port</span>: Config.get( <span class="hljs-string">'/server/port'</span> ),
	},
	<span class="hljs-attr">register</span>: {
		<span class="hljs-attr">plugins</span>: [
			{
				<span class="hljs-attr">plugin</span>: <span class="hljs-string">'./api/home'</span>,
			},
		],
	},
};</code></pre>



<p>Puedes bajarte el código <a href="https://github.com/displaynone/hapi-vue-demo/releases/tag/0.3.0">aquí</a></p>
<p>The post <a href="https://sentidoweb.com/2019/05/07/hapi-js-vue-js-empezando-con-las-routes.php">Hapi.js + Vue.js empezando con las routes</a> appeared first on <a href="https://sentidoweb.com">Sentido Web</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Traduce tu plugin para Gutenberg</title>
		<link>https://sentidoweb.com/2019/04/30/traduce-tu-plugin-para-gutenberg.php</link>
		
		<dc:creator><![CDATA[Luis]]></dc:creator>
		<pubDate>Mon, 29 Apr 2019 22:43:09 +0000</pubDate>
				<category><![CDATA[PHP]]></category>
		<category><![CDATA[Gutenberg]]></category>
		<category><![CDATA[i18n]]></category>
		<category><![CDATA[WordPress]]></category>
		<guid isPermaLink="false">https://sentidoweb.com/?p=2957</guid>

					<description><![CDATA[<p>Traducir los plugins desde WordPress es bastante fácil, tan solo tienes que ejecutar este comando para generar los ficheros .pot. Una vez generados los ficheros de traducción, yo uso PoEdit, el siguiente paso es generar los ficheros JSON, ya que Gutenberg es lo que usa. Para ello utilizaremos la herramienta po2json, que podrás instalar ejecutando:...</p>
<p>The post <a href="https://sentidoweb.com/2019/04/30/traduce-tu-plugin-para-gutenberg.php">Traduce tu plugin para Gutenberg</a> appeared first on <a href="https://sentidoweb.com">Sentido Web</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>Traducir los plugins desde WordPress es bastante fácil, tan solo tienes que ejecutar este comando para generar los ficheros <em>.pot</em>.</p>



<pre class="wp-block-sentidoweb-snippet"><button class="sw-snippet-button" data-label-copy="Copiar" data-label-copied="Copiado">Copiar</button><code class="bash hljs">wp i18n make-pot .</code></pre>



<p>Una vez generados los ficheros de traducción, yo uso PoEdit, el siguiente paso es generar los ficheros JSON, ya que Gutenberg es lo que usa.</p>



<p>Para ello utilizaremos la herramienta <strong>po2json</strong>, que podrás instalar ejecutando:</p>



<pre class="wp-block-sentidoweb-snippet"><button class="sw-snippet-button" data-label-copy="Copiar" data-label-copied="Copiado">Copiar</button><code class="bash hljs">npm i -g po2json</code></pre>



<p>Cuando se haya instalado tendremos que generar el fichero json ejecutando desde el directorio donde están los ficheros de traducción:</p>



<pre class="wp-block-sentidoweb-snippet"><button class="sw-snippet-button" data-label-copy="Copiar" data-label-copied="Copiado">Copiar</button><code class="bash hljs">po2json mi-plugin-es_ES.po mi-plugin-es_ES-mi-plugin-handler.json -f jed</code></pre>



<p>Ahora viene la parte más complicada, y digo complicada porque sinceramente he tenido que mirar el código porque algo me fallaba cuando seguía lo que decía la documentación. </p>



<p>El nombre del fichero JSON tiene el siguiente formato <em>[dominio de traducción]-[idioma]-[handler del fichero].json</em>, total nada.</p>



<ul class="wp-block-list"><li>Dominio será el que usemos para traducir, en el ejemplo sería <strong>mi-plugin</strong>:</li></ul>



<pre class="wp-block-sentidoweb-snippet"><code class="javascript hljs">__( <span class="hljs-string">'Hola que tal'</span>, <span class="hljs-string">'mi-plugin'</span> );</code></pre>



<ul class="wp-block-list"><li>Idioma es el código del idioma, en este caso <em>es_ES</em></li><li>Y por último el handler del fichero es el primer parámetro que usamos en <em>wp_enqueue_script</em></li></ul>



<p>Lo podemos ver todo en un ejemplo final:</p>



<pre class="wp-block-sentidoweb-snippet"><button class="sw-snippet-button" data-label-copy="Copiar" data-label-copied="Copiado">Copiar</button><code class="php sw_show_line_numbers hljs">wp_enqueue_script(
	<span class="hljs-string">'mi-plugin-handler'</span>, <span class="hljs-comment">// El handler mencionado anteriormente</span>
	$blocks_script, <span class="hljs-comment">// Nuestro path</span>
	[
		<span class="hljs-string">'wp-i18n'</span>, <span class="hljs-comment">// De referenciar al menos a wp-i18n</span>
	],
);

wp_set_script_translations( <span class="hljs-string">'mi-plugin-handler, '</span>mi-plugin<span class="hljs-string">', plugin_dir_path( __FILE__ ) . '</span>languages<span class="hljs-string">' );</span></code></pre>



<p></p>
<p>The post <a href="https://sentidoweb.com/2019/04/30/traduce-tu-plugin-para-gutenberg.php">Traduce tu plugin para Gutenberg</a> appeared first on <a href="https://sentidoweb.com">Sentido Web</a>.</p>
]]></content:encoded>
					
		
		
			</item>
	</channel>
</rss><!--
Performance optimized by W3 Total Cache. Learn more: https://www.boldgrid.com/w3-total-cache/?utm_source=w3tc&utm_medium=footer_comment&utm_campaign=free_plugin

Page Caching using Memcached (SSL caching disabled) 
Database Caching 2/40 queries in 0.053 seconds using Memcached

Served from: sentidoweb.com @ 2026-04-12 16:33:48 by W3 Total Cache
-->