Crear aplicación web con Hapi.js, Vue y MongoDB

Estoy aprendiendo Hapi.js y Vue.js y qué mejor que ir anotando aquí lo que voy aprendiendo, y si a alguien le sirve, mejor que mejor.

Pretendo crear una aplicación web e ir explicando en diferentes posts, que iré añadiendo aquí, lo que estoy aprendiendo.

He estado viendo distintos boilerplates y el que más me ha gustado ha sido appy. Podría usarlo, pero prefiero aprender poco a poco que pegarme con algo ya hecho e indagar qué hace cada cosa.

Lo que más me gusta de appy es que diferencia entre el backend y el frontend de forma bastante clara, distintos directorios y aplicaciones para cada cosa. Es por ello que voy a seguir esta estructura. Aquí hay un artículo bastante majo que explica más o menos lo mismo.

Pues nada más, iré subiendo posts sobre lo que voy aprendiendo. Eso sí, porfa, si meto la pata en algo, no me lo tengas muy en cuenta, estoy aprendiendo.

  1. Hapi.js+Vue.js Crear servidor backend (código v0.1.0)
  2. Reorganización de la configuración (código v0.2.0)
  3. Empezar añadiendo routes (código v0.3.0)
  4. Conectando a mongodb (código v0.4.0)
  5. Modelos mejorados y controladores (código v0.5.0)
  6. Inicializando el frontend (código v0.6.0)
  7. Vue router y login form “tonto” (código v0.7.0)
  8. Comunicación entre frontend y el servidor usando JWT (login) (código v0.8.0)
  9. i18n (código v0.9.0)
  10. Activación de usuario, recordar contraseña, … (código v0.10.0)

Añadir números de línea en highlight.js

Aprovechando la versión 1.2.0 de Snippet Block, comparto cómo añadir números de línea al código formateado por highlight.js.

El desarrollador del plugin pasa de añadir esta característica a la librería, lo cual me parece muy bien, que cada uno haga lo que quiera con su código. Pero como tampoco hay eventos o algo que permita añadir características, es necesario “hackear” la función antes de su uso.

document.addEventListener( 'DOMContentLoaded', () => {
	const prevHighligth = window.hljs.highlightBlock;
	window.hljs.highlightBlock = block => {
		const newline = '<span class="sw_new_line"></span>';
		// Replace all without regex
		block.innerHTML = block.innerHTML
			.split( newline )
			.join( '' );
		prevHighligth( block );
		if ( block.classList.contains( 'sw_show_line_numbers' ) ) {
			block.innerHTML = newline +
				block
					.innerHTML
					.replace( /\n/g, `\n${ newline }` );
		}
	};
} );

¿Qué es lo que hace este código? Primero guarda el método original highlightBlock para usarlo posteriormente. Y luego se modifica el método para apadir una clase para mostrar los números de línea al principio del código y después de cada salto de línea.

Ahora toca explicar la parte CSS, para ello usaremos CSS counters.

pre code {
	counter-reset: linenumber;
}

pre code .sw_new_line::before {
	content: counter(linenumber, decimal-leading-zero);
	counter-increment: linenumber;
	padding-right: 15px;
	font-size: 0.8em;
	opacity: 0.6;
}

Snippets Block en WordPress.org

El otro día comentaba que estaba haciendo un plugin para WordPress que permitiera añadir snippets como bloques en Gutenber.

Por fin el otro día aprobaron el plugin en el directorio de WordPress.org, así que ya sabéis, si os viene bien, podéis instalarlo en vuestras webs.

Por cierto, echad un ojo al script que venía incluído dentro del boilerplate que usé. Está genial para poder subir al SVN de WordPress.org los cambios realizados en GitHub. Etiqueta versiones, sube a WP.org, …

Snippets Blocks en WordPress.org

Gutenberg Snippet block

Últimamente estoy trabajando bastante con Gutenberg, tiene sus cosas buenas y malas. Sea como sea, es el futuro de WordPress, así que toca aprender.

Lo más interesante de todo es poder usar lo que sabía de webpack, React, HMR, … Y para practicar he hecho un plugin que permite añadir snippets de código en los posts usando los bloques de Gutenberg

Para ello uso la librería highlight.js que permite destacar código de forma sencilla. Aquí un ejemplo

//  Import CSS.
import './scss/style.scss';
import './scss/editor.scss';
import icon from './icon';
import edit from './edit';
import save from './save';
import attributes from './attributes';

import { __ } from '@wordpress/i18n'; // Import __() from wp.i18n

export const name = 'sentidoweb/snippet';

export const settings = {
	// Block name. Block names must be string that contains a namespace prefix. Example: my-plugin/my-custom-block.
	title: __( 'Snippets editor', 'sw-snippet' ), // Block title.
	icon: icon,
	category: 'common', // Block category — Group blocks together based on common traits E.g. common, formatting, layout widgets, embed.
	keywords: [
		__( 'code', 'sw-snippet' ),
		__( 'format', 'sw-snippet' ),
		__( 'snippet', 'sw-snippet' ),
	],

	attributes,
	edit,
	save,
};

Snippet Block en Github

Testear páginas web con AVA y PhantomJS

Estoy colaborando junto a @jlantunez y @belelros en WebSlides, un proyecto open source que permite crear presentaciones usando un navegador web de forma increíble.

Para hacer las cosas bien, vamos a incluir pruebas de testing, y para ello he mirado cómo hacerlo con AVA y PhantomJS. El problema con el que me he encontrado ha sido que todo es asíncrono y a veces da un poco de problemas esperar a cargar la página para que AVA empiece a realizar las pruebas.

Además no es plan de cargar la página en cada una de las pruebas, por lo que cargo la página una vez y luego realizo las pruebas necesarias. Esto me obliga a que las pruebas sean secuenciales en vez de en paralelo, pero bueno, tampoco es mucho problema.

Como me he roto la cabeza intentado averiguar cómo hacer, ya que soy un tanto novato en esto, pongo el código para aquel que lo necesite, aunque bueno, en breve estará en GitHub:


// Cargo las librerías
let phantom = require("phantom");
import test from 'ava';

// Para almacenar lo que PhantomJS necesita
let ph_, page_, status_;

// Función que carga la página
const load = async () => {
  await phantom.create().then(async ph => {
    ph_ = ph;
    return await ph_.createPage();
  }).then(page => {
    page_ = page;
    return page_.open('http://webslides.tv/');
  }).then(status => {
    status_ = status;
    return true;
  }).catch(e => console.log(e));
}

// Tests
test.serial("Page loaded", async t => {
  await load();
  t.is(status_, 'success');
});

test.serial('#webslides exits', async t => {
  await page_
    .evaluate( () => document.querySelector('#webslides') != null )
    .then( ws => { t.truthy(ws); } );
});

test.serial('WebSlides object exits', async t => {
  await page_
    .evaluate( () => window.ws != null )
    .then( ws => { t.truthy(ws); } );
});

/**
 * Last test
 */
test.serial('Closing', async t => {
  await page_.close();
  ph_.exit();
  t.true(true);
});

Testear un plugin de WordPress con PHPUnit

Algo que casi nunca hago, pero que debería ser obligatorio, es realizar pruebas unitarias de los plugins que realizo en WordPress. Si normalmente casi no tengo tiempo de realizarlos con detalle, imagínate hacer pruebas unitarias.

Para realizar esas pruebas voy a hacer uso de PHPUnit, el cual no voy a explicar cómo usarlo, pero sí voy a poner el bootstrap.php que uso:

// Cargar WP, la ruta supuestamente está en el directorio wp-content/plugins
define('WP_PATH', dirname(__FILE__).'/../../..');
include(WP_PATH.'/wp-load.php');

/**
 * Loads/activates a plugin
 */
function run_activate_plugin( $plugin ) {
    $current = get_option( 'active_plugins' );
    $plugin = plugin_basename( trim( $plugin ) );

    if ( !in_array( $plugin, $current ) ) {
        $current[] = $plugin;
        sort( $current );
        do_action( 'activate_plugin', trim( $plugin ) );
        update_option( 'active_plugins', $current );
        do_action( 'activate_' . trim( $plugin ) );
        do_action( 'activated_plugin', trim( $plugin) );
    }

    return null;
}
// Activa el plugin si no lo está, que no debería al estar en testing...
run_activate_plugin( 'wordpress-nonce-object/class-wp-nonce.php' );

Chrome Debug – plugin para WordPress

Cuando estoy desarrollando para PHP siempre hago uso de la consola del navegador, antes de Firebug, pero ahora me he pasado a Firefox Web Console. Este plugin permite debuggear en la consola de Chrome o en la de Firefox (no Firebug).

Su uso es bastante sencillo:

// Normal debug
_debug($var, 'log');

// Info message
_debug($var, 'info');

// Warn message
_debug($var, 'warn');

// Error message
_debug($var, 'error');

// Message in table format
_debug(
  array(
    'idx_1' => 'val_1',
    'idx_2' => 'val_2',
    'idx_3' => 'val_3'
  ), 'table');

// Groups
_debug('New group', 'group');
_debug($var1, 'info');
_debug($var2, 'error');
// ...
_debug('', 'groupEnd');

Puedes bajarte el código directamente desde este ZIP o desde mi cuenta de GitHub

Búsquedas geolocalizadas en WordPress

Este es un ejemplo raro de uso de WordPress, pero el otro día una persona lo preguntaba en el foro de soporte de WordPress. El usuario tenía los posts geolocalizados (latitud/longitud), supongo con postmetas correspondientes.

Para poder realizar este tipo de búsquedas es necesario usar MySQL procedures, claro que no todos los hosting lo permiten:

CREATE PROCEDURE geodist (IN mylat decimal(18,12), IN mylon decimal(18,12), IN dist float)
BEGIN
declare lon1 float;
declare lon2 float;
declare lat1 float;
declare lat2 float;
set lon1 = mylon-dist/abs(cos(radians(mylat))*69);
set lon2 = mylon+dist/abs(cos(radians(mylat))*69);
set lat1 = mylat-(dist/69);
set lat2 = mylat+(dist/69);
SELECT p.*, 3956 * 2 * ASIN(SQRT( POWER(SIN((mylat -lat.meta_value) * pi()/180 / 2), 2) +COS(mylat * pi()/180) * COS(lat.meta_value * pi()/180) *POWER(SIN((mylon - lon.meta_value) * pi()/180 / 2), 2) )) as distance FROM wp_posts as p, wp_postmeta lat, wp_postmeta lon WHERE p.ID = lat.post_id and lat.meta_key = 'latitude' and p.ID = lon.post_id and lon.meta_key = 'longitude' and lon.meta_value between lon1 and lon2 and lat.meta_value between lat1 and lat2
and post_status = 'publish'
having distance < dist ORDER BY Distance;
END

Una vez creado el procedure deberemos llamarlo para obtener los resultados:

global $wpdb;
$posts = $wpdb->query($wpdb->prepare('call geodist(?, ?, ?)', $lat, $lon, $dis));

Acceder al código en Gist

Modificar el contenido de cualquier widget de WordPress

No entiendo por qué no hay un filter o un action cuando se trata de los contenidos de los widgets. A no ser que el autor los haya metido, no hay forma de modificar ni el form del widget ni su contenido.

Me he encontrado en la necesidad de tener que indicar en el footer el tamaño de columnas de Bootstrap para cada uno de los widgets de un sidebar, para lo cual es necesario modificar la salida de cada widget e incluir en el form un campo que indique el ancho y en el html del widget el código de Bootstrap para indicar el ancho de la columna:

En el function.php meteremos el siguiente código:

Primero modificaremos los datos del widget para que añada el nuevo campo que vamos a meter (widget_bootstrap_columns), milagrosamente sí hay un filtro para ello:

// Modificamos los datos enviados al guardar el widget
add_filter( 'widget_update_callback', 'sw_widget_update');
function sw_widget_update($instance, $new_instance, $old_instance, $obj ) {
  if (isset($_POST['widget_bootstrap_columns'])) $instance['widget_bootstrap_columns'] = $_POST['widget_bootstrap_columns'];
  return $instance;
}

Después vamos a modificar la variable global $wp_registered_widgets que contiene la llamada a la función widget del widget (la que escribe el HTML). Encapsularemos esta llamada dentro de una función anónima para realizar las tareas que sean necesarias, en mi caso dibujar un div con el class col-sm-N:

// Modifico el display del widget para meterle el código de bootstrap
add_action('dynamic_sidebar_before', 'sw_modify_widgets_display', 10, 2);
function sw_modify_widgets_display($index, $bool) {
  global $wp_registered_widgets;
  $sidebars_widgets = wp_get_sidebars_widgets();
  foreach ( (array) $sidebars_widgets[$index] as $id ) {
    if ( !isset($wp_registered_widgets[$id]) ) continue;
    $wp_registered_widgets[$id]['_callback'] = $wp_registered_widgets[$id]['callback'];
    $wp_registered_widgets[$id]['callback'] = function($args, $widget_args) use ($id) {
      global $wp_registered_widgets;
      // Recuperamos los datos del widget
      $instance = get_option($wp_registered_widgets[$id]['_callback'][0]->option_name);
      $bootstrap = isset($instance[$wp_registered_widgets[$id]['_callback'][0]->number]['widget_bootstrap_columns'])? $instance[$wp_registered_widgets[$id]['_callback'][0]->number]['widget_bootstrap_columns'] : false;
      // Pintamos el tamaño de la columna si así procede
      if ($bootstrap) echo '<div class="col-sm-'.$bootstrap.'">';
      call_user_func_array($wp_registered_widgets[$id]['_callback'], array($args, $widget_args));
      if ($bootstrap) echo '</div>';
    };
  }
}

Y por último haremos lo mismo para el formulario, modificar la variable global $wp_registered_widget_controls para encapsular la función form del widget y dibujar antes un input donde almacenemos el valor de widget_bootstrap_columns:

// Modifica el form de los widgets
add_action( 'load-widgets.php', 'sw_modificar_widgets_forms' );
function sw_modificar_widgets_forms() {
  global $wp_registered_widget_controls;
  foreach($wp_registered_widget_controls as $id=>$widget) {
    $wp_registered_widget_controls[$id]['_callback'] = $wp_registered_widget_controls[$id]['callback'];
    $wp_registered_widget_controls[$id]['callback'] = function($data) use ($id) {
      global $wp_registered_widget_controls;
      // Recuperamos los datos del widget para incluir el valor de widget_bootstrap_columns
      $instance = get_option($wp_registered_widget_controls[$id]['_callback'][0]->option_name);
      $widget_bootstrap_columns = isset($instance[$data['number']]) && isset($instance[$data['number']]['widget_bootstrap_columns'])? $instance[$data['number']]['widget_bootstrap_columns']:'';
      echo '<div  class="widget_added"><p>Número de columnas (col-sm-<input type="number" name="widget_bootstrap_columns" min="1" max="12" value="'.$widget_bootstrap_columns.'" />)</p><hr /></div>';
      call_user_func_array($wp_registered_widget_controls[$id]['_callback'], array($data));
    };
  }
}

Puedes bajarte el código en mi GitHub

Incrustar un SVG dentro del HTML directamente

Lo bueno de los SVG es que puedes modificarlos mediante CSS directamente en el HTML, el único problema es que el SVG debe estar incrustado en el HTML, no mediante una imagen <img>, ni mediante la propiedad content de CSS.

Encontré un script muy bueno que te modifica una etiqueta img que contiene un SVG por su contenido SVG real. El problema viene cuando yo añado el SVG como una propiedad content:

.class {
  content: url('imagen.svg');
}

Para ello he modificado el script anterior para que coja la propiedad content de los selectores CSS que se indiquen y añada el SVG directamente al elemento:

jQuery(document).ready(function() {
  var replaceWithSVG = function(selector, url) {
    jQuery('').appendTo('head');
    jQuery.get(url, function(data) {
      // Replace image with new SVG
      jQuery(selector).prepend(data.replace(/<\?xml[^>]+>/, ''));
    }, 'text');
  }
  var selectors = ['.site-title a', '.btn-video']
  for(var i in selectors) {
    var selector = selectors[i];
    var url = window.getComputedStyle(
    	document.querySelector(selectors[i]), ':before'
    ).getPropertyValue('content');
    url = url.replace(/url\(["']?([^'"\)]+)["']?\)/, '$1');
    replaceWithSVG(selectors[i], url);
  }
});