|

CSS grid: filas con misma altura

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 simular contenido vacío que rellene la celda:

.grid {
  margin: 0 auto;
  width: 60vw;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 5px;
  grid-auto-rows: auto;
}

img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.grid--same-height .cell {
  position: relative;
}
.grid--same-height .cell::before {
  content: "";
  display: inline-block;
  padding-bottom: 100%;
}

.grid--same-height .content {
  height: 100%;
  position: absolute;
  margin-top: -100%;
}

Teniendo en cuenta que el HTML sería:

<div id="grid" class="grid">
  <div class="cell">
    <div class="content">
      <img src="https://picsum.photos/500/500"/>
    </div>
  </div>
  ...
</div>

Para el ejemplo uso picsum.photos, un lorem ipsum para fotos que permite indicar distintos tamaños de imágenes.

Podéis ver un ejemplo en este pen

Hapi.js + Vue.js inicializar el frontend
|

Hapi.js + Vue.js inicializar el frontend

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 a una, usando el siguiente package.json y ejecutando npm i, lo tendremos todo instalado.

{
  "name": "hapi-frontend",
  "version": "1.0.0",
  "description": "Hapi.js frontend",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "dev": "webpack-dev-server --mode development --config webpack.config.dev.js",
    "build": "webpack --mode production --config webpack.config.production.js"
  },
  "license": "ISC",
  "devDependencies": {
    "@babel/core": "^7.5.4",
    "babel-eslint": "^10.0.2",
    "babel-loader": "^8.0.6",
    "css-loader": "^3.0.0",
    "eslint": "^6.0.1",
    "eslint-plugin-html": "^6.0.0",
    "eslint-plugin-vue": "^5.2.3",
    "mini-css-extract-plugin": "^0.7.0",
    "node-sass": "^4.12.0",
    "sass-loader": "^7.1.0",
    "stylelint": "^10.1.0",
    "stylelint-config-standard": "^18.3.0",
    "stylelint-webpack-plugin": "^0.10.5",
    "vue-hot-reload-api": "^2.3.3",
    "vue-html-loader": "^1.2.4",
    "vue-loader": "^15.7.0",
    "vue-style-loader": "^4.1.2",
    "vue-template-compiler": "^2.6.10",
    "webpack": "^4.35.3",
    "webpack-cli": "^3.3.5",
    "webpack-dev-server": "^3.7.2",
    "webpack-merge": "^4.2.1"
  },
  "dependencies": {
    "buefy": "^0.7.10",
    "vue": "^2.6.10"
  }
}

Como se puede ver, existen dos scripts dentro de npm: build que compila el js y extrae los CSS, y dev, que arranca el servidor de webpack habilitando HMR (🎶 ¡ya no puedo vivir sin él! 🎶).

Ambas configuraciones de webpack usan un script en común (webpack.config.common.js):

const webpack = require( 'webpack' );
const path = require( 'path' );
// Carga los ficheros .vue
const VueLoaderPlugin = require( 'vue-loader/lib/plugin' );
// Configura stylelint
const StyleLintPlugin = require( 'stylelint-webpack-plugin' );

// Para obtener un path para los alias
function resolve( dir ) {
	return path.join( __dirname, '.', dir );
}

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

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

import Vue from 'vue';
import Buefy from 'buefy';
import 'buefy/dist/buefy.css';

import App from './components/App.vue';

import '@/assets/scss/main.scss';

Vue.use( Buefy );

new Vue( {
	el: '#app',
	components: {
		App,
	},
	render: ( c ) => c( 'app' ),
} );

// accept replacement modules
if ( module.hot ) {
	module.hot.accept();
}

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

<template>
	<header class="hero">
		<div class="hero-head">
			<h1>{{ title }}</h1>
		</div>
	</header>
</template>

<script>
export default {
	data() {
		return {
			title: "Demo site",
		};
	},
};
</script>

<style lang="scss" scoped>
	div {

		h1 {
			color: #fff;
		}
	}
</style>

Bueno, ha sido un resumen rápido, pero bajándote el código seguro que lo entiendes fácil

Crear un formulario de contacto para un theme de WordPress

¿Para que crear un formulario de contacto en un theme si ya existe algún plugin que te lo hace todo y muy bien?. Fácil, porque cuando subes un theme a wordpress.org, no te deja meter plugins, y si lo quieres vender, no puedes meter mil instrucciones para instalarlo, lo lógico es que copie el theme y poco más.

El tutorial explica paso a paso cómo crear la página, hacer que WordPress la entienda, crear el formulario de contacto, validación por jQuery haciendo uso de un plugin y envío del email.

How to create a built-in contact form for your WordPress theme

Vía / DZone

Laboratorio: Twitter trends

Mi amigo Christian me ha comentado que la entrada anterior sobre Twitter sería más interesante si mostrara los datos sobre un término.

Pues dicho y hecho, tan sólo se necesitan dos scripts, uno para recuperar los datos y otro para mostrarlos. El primero habrá que ponerlo en el cron para que recupere los datos cada cierto tiempo (en mi ejemplo busco “google” cada 2 minutos).

Hay que tener cuidado porque Twitter da un máximo de 2000 actualizaciones nuevas, por lo que tendremos que ajustar los tiempos de consulta en Twitter.

El script que lee los datos es el siguiente:

<? php

function insertar($consulta, $ult) {
  global $db;

  $data = json_decode(file_get_contents('http://search.twitter.com/search?q='.urlencode($consulta).'&refresh=true&since_id='.$ult));
  $n = isset($data->total) && $ult != $data->max_id? $data->total:0;
  $db->queryExec('INSERT INTO estadisticas (fecha, n) values ('.time().', '.$n.')');
  if (!$ult) $db->queryExec("INSERT INTO opciones (clave, valor) values ('ultimo', ".$data->max_id.")");
  else $db->queryExec("UPDATE opciones SET valor = ".$data->max_id." where clave='ultimo' ");
}

$consulta = $_GET['q'];
// Limpio para poder usarlo en el nombre para la BD
$_consulta = preg_replace('/[^A-Z0-9]/i', '_', $consulta);

if ($db = new SQLiteDatabase($_consulta.'.db')) {
  $q = @$db->query("SELECT valor FROM opciones Where clave='ultimo'");
  if (!$q) {
    $db->queryExec('CREATE TABLE estadisticas (fecha real, n real, PRIMARY KEY (fecha));');
    $db->queryExec('CREATE TABLE opciones (clave text, valor text, PRIMARY KEY (clave));');
    $q = $db->query("SELECT valor FROM opciones Where clave='ultimo'");
  }
  $r = $q->fetchAll(SQLITE_ASSOC);
  $ult = 0;
  if (!empty($r)) $ult = $r[0]['valor'];
  insertar($consulta, $ult);
}
?>

Y el script que dibuja la gráfica es:

<? php
$desde = strtotime($_GET['desde']);
$hasta = strtotime($_GET['hasta']);

$consulta = $_GET['q'];
// Limpio para poder usarlo en el nombre para la BD
$_consulta = preg_replace('/[^A-Z0-9]/i', '_', $consulta);

if ($db = new SQLiteDatabase($_consulta.'.db')) {
  $q = $db->query("SELECT fecha, n FROM estadisticas Where fecha>".$desde." and fecha<".$hasta);
  $r = $q->fetchAll(SQLITE_ASSOC);
  foreach($r as $item) {
    $x[] = $item['n'];
    $l[] = $item['fecha'];
  }
}

header('Location: http://chart.apis.google.com/chart?chtt=Line+Chart&chts=000000,12&chs=1000x600&chf=bg,s,ffffff|c,s,ffffff&chxt=x,y&chxl=0:|'.implode('|', $l).'|1:|'.implode('|', $x).'&cht=lc&chd=t:75.00,16.66,0.00,8.33,100.00&chdl=Label+1&chco=0000ff&chls=1,1,0');

Actualización: Google Charts no deja meter muchos valores por lo que la gráfica de arriba sólo saca las 20 últimas actualizaciones

Librería OAuth para Twitter

Twitter OAuth PHP Class es una librería que nos vendrá muy bien para poder utilizar el API de Twitter usando OAuth, método de autenticación que no necesita que el usuario registre su usuario/contraseña en otras aplicaciones.

Un ejemplo de uso sería el siguiente:

$to = new TwitterOAuth($consumer_key, $consumer_secret);
$tok = $to->getRequestToken();
$request_link = $to->getAuthorizeURL($token);
$tok = $to->getAccessToken();
$to = new TwitterOAuth($consumer_key, $consumer_secret, $user_access_key, $user_access_secret);
$content = $to->OAuthRequest('https://twitter.com/account/verify_credentials.xml', array(), 'GET');
$content = $to->OAuthRequest('https://twitter.com/statuses/update.xml', array('status' => 'Test OAuth update. #testoauth'), 'POST');

Twitter OAuth PHP Class

Vía / PHPDeveloper.org

Parseador HTML para PHP

PHP Simple HTML DOM Parser es una librería PHP que permite parsear de forma muy sencilla HTML.

Es necesario PHP5+, admite HTML inválido, permite buscar etiquetas usando selectores como jQuery y extraer el contenido.


// Create DOM from string
$html = str_get_html('<div id="hello">Hello</div><div id="world">World</div>');
$html->find('div', 1)->class = 'bar';
$html->find('div[id=hello]', 0)->innertext = 'foo';
echo $html; // Output: <div id="hello">foo</div><div id="world" class="bar">World</div>

PHP Simple HTML DOM Parser

Vía / DZone

Programación en paralelo en PHP

Alguna vez es necesario realizar acciones en paralelo en nuestras aplicaciones. En PHP no es tan sencillo y a veces implica algunas complicaciones. Para aquellos que necesitan realizar estas tareas puede venir muy bien esta librería que nos facilita enormemente estas funciones.

Permite ejecutar funciones síncronas y asíncronas, control de las salidas, pasarle argumentos a los scripts y obtener la salida en HTML y PHP.

PHP-Parallel-Programming

Librería Javascript para Facebook Connect

Mμ Connect es una librería Javascript muy ligera que permite utilizar Facebook Connect desde tu web y pudiendo usar tus frameworks favoritos: Dojo, jQuery, MooTools, Prototype o YUI.

Entre otras acciones nos permite loguearnos, desloguearnos, leer y escribir y añadir amigos.

Mμ Connect

Vía / WebAppers