La forma más sencilla para exportar / importar la configuración y los paquetes entre instalaciones de Sublime Text 3 es usar la extensión PackageSync.
Author: koas
Acceder a una referencia dentro de un slot en Vue.js
Para facilitar las operaciones CRUD de elementos en mis aplicaciones, que son de lejos lo más aburrido que hay, me he creado un componente que tiene toda la funcionalidad y que se personaliza en cada caso con algunas variables de estado y dos slots, uno para la tabla de visualización de datos y otro para la ventana de creación / edición.
Al intentar que cuando se abra la ventana de creación / edición el foco se ponga en el campo del nombre me he encontrado un problema: no sabía cómo acceder a una referencia definida fuera de nuestro componente e inyectada como un slot. Para ello, si nuestro slot se llama por ejemplo editDialog y queremos localizar el elemento que tiene como referencia crudFocus deberemos hacerlo así:
this.$scopedSlots.editDialog()[0].context.$refs.crudFocus
Cómo refrescar un componente dinámico en Vue.js
Estoy haciendo un interfaz con Vue.js que utiliza pestañas para tener varias vistas abiertas a la vez. Esto se consigue metiendo un componente dinámico dentro de un keep-alive de forma que al ir cambiando el componente los datos se mantengan.
El problema es cómo mantener la coherencia entre las distintas pestañas. Si por ejemplo en una tengo abierto el editor de usuarios y en otra tengo el editor de permisos de los usuarios nos surgen los siguientes problemas:
- En el editor de usuarios creo un nuevo usuario, y al irme a la pestaña de permisos para asignarle permisos no me aparece en el desplegable, causando confusión y quebranto en quien esté usando la aplicación.
- Peor aún, en el editor de permisos puedo haber seleccionado un usuario y posteriormente haberme ido a la pestaña de usuarios y eliminarlo. En la pestaña de permisos puedo enviar datos de permisos para un usuario que ya no existe.
Para solucionar el primer problema lo que hago es guardar en el estado del componente principal una lista con los nombres de los componentes que deben ser recargados. Por recargado se entiende que no debemos crearlo de nuevo, debemos mantener su estado, y simplemente hay que llamar a su método de carga de datos, que incluirá el nuevo usuario.
Todos mis componentes implementan el método activated (que específicamente se llama cuando un componente inactivo en un keep-alive se activa) y en él se comprueba si el componente está en la lista de recarga, si es así recarga los datos.
Pero en el segundo problema no nos basta con recargar los datos, querremos reiniciar completamente el componente para evitar que se mantenga un estado con un usuario que ya no existe. Esto es lo que más quebraderos de cabeza me ha dado, y me he tirado toda la tarde probando cosas sin éxito hasta que he visto en el manual de Vue lo siguiente:
exclude
- string or RegExp or Array. Any component with a matching name will not be cached.
En el momento en el que metemos en ese atributo el nombre de un componente Vue deja de cachearlo, eliminándolo en ese mismo momento y haciendo por tanto que la próxima vez que lo carguemos en nuestro componente contenedor se cree desde cero, que es lo que queríamos.
Hay que acordarse de quitar el nombre del componente de exclude después de volverlo a cargar para que se mantenga en el caché hasta que queramos de nuevo invalidarlo.
Cargar phpMyAdmin en un iframe
Para desarrollo local ahora que tengo un monitor de 1440p quería mostrar en una misma pestaña de Chrome dos instancias de phpMyAdmin, ya que no hay ninguna extensión decente para dividir la ventana en dos pestañas.
El problema es que por seguridad phpMyAdmin no permite que lo carguemos en un iframe, pero como estoy en un servidor local sin acceso desde el exterior puedo saltarme esa medida de seguridad a cambio de trabajar más cómodamente. Para conseguirlo hay que editar el fichero /usr/share/phpmyadmin/js/cross_framing_protection.js y dejarlo así:
/**
* Conditionally included if framing is not allowed
*/
//if (self == top) {
var style_element = document.getElementById("cfs-style");
// check if style_element has already been removed
// to avoid frequently reported js error
if (typeof(style_element) != 'undefined' && style_element != null) {
style_element.parentNode.removeChild(style_element);
}
/*} else {
top.location = self.location;
}*/
Organizar pestañas en Google Chrome con Workona
Llevaba mucho tiempo buscando algo que me permitiera agrupar las pestañas de Chrome por grupos, de forma que pudiera tener un grupo para cada proyecto en el que estoy trabajando, y por fin lo he encontrado: Workona. 100% recomendable.
Explicación de cómo usar v-model en nuestros componentes de Vue.js
Después de mucho buscar por fin he encontrado una explicación decente de cómo usar v-model en nuestros componentes de Vue.js, especialmente cuando son componentes complejos que tienen como modelo un objeto:
Aquí hay otra buena explicación, que además aclara algo que me tuvo rascándome la cabeza un buen rato: los datos de un componente se actualizaban solos, sin necesidad de emitir yo el evento input desde el código. Como indican en el artículo:
Note: In certain situations you might notice that parent data is updated without any input events being emitted from the child. In these cases, the value prop is a deep object, and the changes are automatically reflected in the parent component. When passing a primitive value, however, we would need to emit the input event from the child to update data on the parent.
Muy recomendables los dos artículos, quizá en el segundo lo explican incluso mejor.
Desklet de Cinnamon para movimientos de empresa del Banco Sabadell
Como ya conté en la entrada API del Banco Sabadell quería ponerme en el escritorio un desklet que me mostrara los últimos movimientos de la cuenta de la empresa, pero se conoce que el Banco Sabadell piensa que no tiene por qué permitirme acceder a mis propios datos.
Así que al no poder usar la API hay que hacerlo a lo bruto, simulando un acceso web normal. Antes esto era un poco más latoso, pero desde que existe puppeteer está chupado. Adjunto el código del script que obtiene los datos y los dos ficheros del desklet.
El script de Node para obtener los datos:
const puppeteer = require("puppeteer");
const fs = require("fs");
const path = require('path');
(async () =>
{
const browser = await puppeteer.launch({/*headless: false, devtools: true, slowMo:500*/});
const page = await browser.newPage();
// Log in
await page.goto("https://www.bancsabadell.com/txempbs/default.bs");
await page.goto("https://www.bancsabadell.com/txempbs/default.bs");
await page.waitFor("input[name='userNIF']");
await page.$eval("input[name='userNIF']", el => el.value = 'DNI_DE_ACCESO');
await page.$eval("input[name='pinNIF']", el => el.value = 'PIN_DE_ACCESO');
await page.$eval("button[name='s1']", f => f.click());
await page.waitFor("a.enlaceIzquierda2Nv");
// Save cookies
const cookiesObject = await page.cookies();
for (let cookie of cookiesObject)
await page.setCookie(cookie);
await page.goto("https://www.bancsabadell.com/txempbs/CUExtractOperationsQueryNew.init.bs");
await page.waitFor("div.cuenta-item");
await page.$eval("div.cuenta-item", f => f.click());
await page.waitFor("#sm_modules_container2");
const data = await page.evaluate(() => {
const tds = Array.from(document.querySelectorAll('table.sorted tr'))
return tds.map(td => td.innerText);
});
const filename = path.join(__dirname, 'output.csv');
fs.writeFileSync(filename, data.join("\n"));
await page.waitFor(1000);
await browser.close();
})();
El fichero desklet.js:
const Desklet = imports.ui.desklet;
const St = imports.gi.St;
const Cinnamon = imports.gi.Cinnamon;
const GLib = imports.gi.GLib;
function HelloDesklet(metadata, desklet_id) {
this._init(metadata, desklet_id);
}
HelloDesklet.prototype = {
__proto__: Desklet.Desklet.prototype,
_init: function(metadata, desklet_id) {
Desklet.Desklet.prototype._init.call(this, metadata, desklet_id);
GLib.spawn_command_line_sync("node /home/koas/bin/bankScraper/sabadell.js");
this.setupUI();
},
setupUI: function() {
// main container for the desklet
this.window = new St.Bin();
this.text = new St.Label();
this.text.set_text(Cinnamon.get_file_contents_utf8_sync("/home/koas/bin/bankScraper/output.csv"));
this.window.add_actor(this.text);
this.setContent(this.window);
}
}
function main(metadata, desklet_id) {
return new HelloDesklet(metadata, desklet_id);
}
El fichero metadata.json:
{
"dangerous": false,
"description": "Muestra los \u00faltimos movimientos de una empresa del Banco Sabadell",
"prevent-decorations": false,
"uuid": "bancoSabadell@koas.dev",
"name": "\u00daltimos movimientos Banco Sabadell Empresas"
}
Palabra soñada: leprovérdura
Aunque yo sueño todas las noches y la inmensa mayoría de las veces recuerdo los sueños al despertar es la primera vez que escucho una palabra inventada en un sueño y la recuerdo al despertarme.
La palabra es leprovérdura.
La he buscado en Google y no aparece. Igual es una chorrada, pero por si acaso lo dejo registrado aquí, que nunca se sabe…
Montar Google Drive como unidad en Linux Mint 18
Al parecer en la versión 19 de Linux Mint está un poco más integrado, pero en la 18 lo más sencillo es instalar este programa: FUSE filesystem over Google Drive.
Ejecutar Vue.js para desarrollo desde un dominio distinto a localhost
Para cada proyecto que comienzo me creo un host virtual en mi servidor web local que apunte a la carpeta de desarrollo con un dominio tipo nombreProyecto_dev. Luego añado una entrada a /etc/hosts apuntando ese dominio inventado a 127.0.0.1. Esto permite detectar en el código cuándo estamos en un servidor de desarrollo y cuándo estamos en producción.
Pero el entorno de desarrollo de Vue.js sirve nuestra página desde localhost:8080, y esto es un problema. Supongamos que el código PHP del proyecto se sirve desde el dominio proyecto_dev: cuando hagamos una llamada desde JS (en localhost) a PHP (en proyecto_dev) las cookies no se enviarán, ya que se trata de dominios distintos.
Para solucionar esto editamos (o creamos, si no existe) el fichero vue.config.js en el directorio raíz de nuestra aplicación y añadimos estas líneas:
module.exports = { configureWebpack: { devServer: { host: 'proyecto_dev', port: '8080' } } }