Archivo del Autor: Capy

Acerca de Capy

Hola me llamo Marcelo Tosco. Hace mucho tiempo que me llaman Capy y como mi apodo es muy corto y a veces ya está en uso, suelo tener que darme de alta con el apodo "capynet", aunque prefiero Capy.

En mi blog escribo de lo que yo quiera, es mi pequeño rincón de la rebeldía, aunque lo cierto es que casi siempre quiero escribir sobre informática, Drupal, Maquetacion, performance, Javascript.... Conclusión: mi blog es una mezcla de blog informático + personal, así que no se asusten si un día aparece un post que no tenga nada que ver a lo que estés acostumbrado.

Mi curriculum es mi blog, mi cuenta de Linkedin, la de Twitter y por supuesto, GitHub.

Si no tenes ganas de andar escudriñando por ahí sobre mis capacidades te las resumo:
Soy un semidios en XD:
* Drupal
* PHP
* Javascript y nodejs
* CSS, SCSS, LESS
* Performance a nivel de código y de servidores.
* Transmitir conocimientos.

Escribí un capitulo sobre Panels de Drupal para Forcontu.

En la vida personal soy un tipo al que le gusta andar en bici y que valora la amistad muchísimo.

Eso es todo. Un abrazo.

Drupal APC: configuración rápida

Aquí dejo una configuración rápida de APC.

extension=apc.so
apc.shm_segments=1
apc.shm_size=256M

Si están usando Debian o derivados pueden poner la configuración en /etc/php5/conf.d/20-apc.ini (la parte del nombre “20-” puede variar, verifiquenlo antes de editar).

Para mas opciones ver http://www.php.net/manual/es/apc.configuration.php

No olvides reiniciar apache: sudo service apache2 restart

Chau!

Drupal 7 + Views: Temear exposed filters como Dios manda

Lo primero que tenes que saber es que podes tomar el control del template que imprime los filtros expuestos de un view (views-exposed-form.tpl.php) simplemente copiandolo a tu theme y poniéndole un nombre con este formato:

views-exposed-form--VIEW_NAME.tpl.php
views-exposed-form--VIEW_NAME--DISPLAY_ID.tpl.php

Ejemplos:
Tengo un view llamado listado_usuarios y dos display: una página “administracion_usuarios” y un bloque “usuarios_por_fecha

(De mas especifico a mas genérico)

Solo para el formulario expuesto del display administracion_usuarios.

views-exposed-form--listado_usuarios--administracion_usuarios.tpl.php

Solo para el formulario expuesto del display usuarios_por_fecha.

views-exposed-form--listado_usuarios--usuarios_por_fecha.tpl.php

Para todos los display que tengan formulario expuesto en este view.

views-exposed-form--listado_usuarios.tpl.php

Cualquier view (CUALQUIERA) que tenga un display llamado administracion_usuarios.

views-exposed-form--administracion_usuarios.tpl.php

Cualquier view con cualquier display.

views-exposed-form.tpl.php

Chau!

Drupal: throbber para input gordos

La ruedita de autocomplete de drupal tiene un fallo, y es que cuando un input es un poquito “gordo” ya se ve parte de la ruiedita activa (porque es un sprite).
Aca les dejo el css y las imagenes que reemplazan al throbber de Drupal por el mismo pero separados, así deja de ser un incordio.

html.js input.form-autocomplete {
    background: url("throbber-inactive.png") no-repeat scroll 98% 50% transparent;
}
html.js input.form-autocomplete.throbbing {
    background: url("throbber-active.gif") no-repeat scroll 98% 50% transparent;
}

throbber-inactive <—- (click derecho y “guardar imagen como”)

throbber-active <—-(click derecho y “guardar imagen como”)

Aca pongo una versión con las imágenes embebidas. no hace falta que descargues las imágenes porque ya vienen incrustadas en el CSS.

html.js input.form-autocomplete {
  background: url('data:"image/png";base64,iVBORw0KGgoAAAANSUhEUgAAAA8AAAARCAMAAAA11AaTAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA2ZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYxIDY0LjE0MDk0OSwgMjAxMC8xMi8wNy0xMDo1NzowMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDpGRkY0M0MxQ0ZDOUFFMjExOUYwNTkyQzM4NDM3MTEyQiIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDpDNzEyMkVCMjlBRkUxMUUyQTQxNTk4RTJBMDQ5RjZDQiIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpDNzEyMkVCMTlBRkUxMUUyQTQxNTk4RTJBMDQ5RjZDQiIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1LjEgV2luZG93cyI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjA1RjUzQzFDRkM5QUUyMTE5RjA1OTJDMzg0MzcxMTJCIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOkZGRjQzQzFDRkM5QUUyMTE5RjA1OTJDMzg0MzcxMTJCIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+8+0pVAAAAFFQTFRFxsbGzs7O3t7e5+fnCHO9////a6/Yi7/h3u/3RpzODHu91uf3HITG7/f/oM7nNZDKyuLztdbv5+/3GHvG9/f/7+/vvb291tbW9/f3tbW1////V+AtUQAAABt0Uk5T//////////////////////////////////8AJzQLNQAAAFxJREFUeNp8j1kOgDAIRIHGI7CM9z+oharRuvBBeJBhgNZ70BureIuTg5HBMTgMxk6ALcUMyokaPFlhQ6pAL0iqXVK0nhyyc1WP+ayf96c/rv7HfR4f9//+uwkwABszFtyhYnczAAAAAElFTkSuQmCC') no-repeat scroll 98% 50% transparent !important;
}
html.js input.form-autocomplete.throbbing {
  background: url('data:"image/gif";base64,R0lGODlhDwARAMQTAMri8xyExgx7ve/3/73e7/f3/9bn9xh7xrXW797v91qi0zWQyou/4aDO52uv2Ofv90aczghzvf///////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh/wtYTVAgRGF0YVhNUDw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYxIDY0LjE0MDk0OSwgMjAxMC8xMi8wNy0xMDo1NzowMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDpGRkY0M0MxQ0ZDOUFFMjExOUYwNTkyQzM4NDM3MTEyQiIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDo3RkE0MTI2MTlBRkUxMUUyQUNCRDlCOEFDNDQ5MzZGQyIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo3RkE0MTI2MDlBRkUxMUUyQUNCRDlCOEFDNDQ5MzZGQyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1LjEgV2luZG93cyI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjAyRjUzQzFDRkM5QUUyMTE5RjA1OTJDMzg0MzcxMTJCIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOkZGRjQzQzFDRkM5QUUyMTE5RjA1OTJDMzg0MzcxMTJCIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+Af/+/fz7+vn49/b19PPy8fDv7u3s6+rp6Ofm5eTj4uHg397d3Nva2djX1tXU09LR0M/OzczLysnIx8bFxMPCwcC/vr28u7q5uLe2tbSzsrGwr66trKuqqainpqWko6KhoJ+enZybmpmYl5aVlJOSkZCPjo2Mi4qJiIeGhYSDgoGAf359fHt6eXh3dnV0c3JxcG9ubWxramloZ2ZlZGNiYWBfXl1cW1pZWFdWVVRTUlFQT05NTEtKSUhHRkVEQ0JBQD8+PTw7Ojk4NzY1NDMyMTAvLi0sKyopKCcmJSQjIiEgHx4dHBsaGRgXFhUUExIREA8ODQwLCgkIBwYFBAMCAQAAIfkEBQYAEwAsAAAAAA8AEQAABWjgJI5kaZ6mwThNgiZQJMvKUCZCJAQR3xek2CLBiDAAPMfIoHtIigwJQGabNCIOydOojSFEDu42KgkzRMWsBKFAdCPfCVPg1GqnkepEmLADDlgkODIQDjE/JQ+HMw56JQYNLC4olJUoIQAh+QQFBgATACwAAAIADQANAAAFR+AkTkDDTMOormIxPsEULcoiLukqFUF0vCoJIwIhiB6rhExpI5wcIoVK2mhMpJOHwzUiAGQskUAyinVZ5qttsg5HkSvkaRUCACH5BAUGABMALAAAAgANAA0AAAU/4CROz5iMY4mK6nSK0FqsoiQBwghM0SgRi55AIlKwjJEADSVg0Bwo1YI1ya0IvNZiJ+KmRtOYaGBazlZe8igEACH5BAUGABMALAAAAgANAA0AAAVI4CROBuOM6JSk4oAKE+xAYsCmtmjEqCQBk4hrcpoQGEiGDXFiiJyRaGSCRCEUWMVydwNOhqLVTQWbQIqTQGH0YDnAo91JLAoBACH5BAUGABMALAAAAgANAA0AAAVJ4CROQMNMw6iuYjE+wRQtyrikbP7kEyHuqofjIZEseieHqCZgSGqNxqQ22UUigSMBIFMRFpGJQDKKjQiClXl6nLR5U2BwclqFAAAh+QQFBgATACwAAAIADQANAAAFQOAkTs+YjGOJiup0itC0jMW6HiMwRbYuSCIFqjaJ2UQM4MohSvASStJEMGJEIIRda6ErBHio7WpgOhJROhF5FAIAIfkEBQYAEwAsAAACAA0ADQAABUXgJE7G5IzolKTigAoT7EBiwKa2WMJ35JoiAoOAQpwYIuToqExOGJIJDVEaMSJQQAuVuDIABy4vEMkFCqMHy/FDlU6rUQgAIfkEBQYAEwAsAAACAA0ADQAABUbgJE7AxEzDqK5iMT7BFC3KuKRs/uQTIe6qhwO36J0cohpyUms0mL2IACcilCKTRaQhGQm6osNMIvGtYllFMcsbKYBBEysEACH5BAUGABMALAAAAgANAA0AAAVB4CROz5iMY4mK6nSK0LSMxboeIzBFti5IIoXoVJvEUAUGzwYL8ByjGUkUgRiAE8KuFQBgdagWbDQwMYso8KQ8CgEAIfkEBQYAEwAsAAACAA0ADQAABUngJE4G44zolKTigAoT7EBiwKa2aMT3FA2MSUBBVCBQiFNkKQpOTs4FY8ogiGiIneAhkYwArdFi1UvAJpBTrTB6sByu1O5EFoUAACH5BAUGABMALAAAAgANAA0AAAVI4CROQMNMw6iuYjE+wRQtyrikY8yKjxxBhhGB15hAApGIA7eYEE6OwmniENWKE4VEkoiqCADZbiKQ5L4sXbY5YY+zvVVvqgoBACH5BAUGABMALAAAAgANAA0AAAVB4CROz5iMY4mK6nSiy1isUSweI8AuUSPqAolIISFMBCPISiJRrFAO1mQgspWQK2OktdD9VtyJUkQVvVazlXeKCgEAOw==') no-repeat scroll 98% 50% transparent !important;
}

Chau!

Drupal 7: Quitar la descripción “E.g., 19/11/2013″ del field date_popup

El FAPI date_popup (del módulo Date) tiene un poco de mala leche y no se deja quitar la descripción por la vía tradicional. Dicho lo anterior, acá está el hook que nos salva el día:

<?php
/**
 * Implements hook_date_popup_process_alter().
 */
function MODULO_O_THEME_date_popup_process_alter(&$element, &$form_state, $context) {
  unset($element['date']['#description']);
  unset($element['time']['#description']);
}

Chau!

tomcat-logo

Hacer proxy con Apache para aplicaciones Tomcat 7

La idea es simple, las aplicaciones Tomcat corren por lo general en el puerto 8080, y seria mas elegante si pudiéramos servir esta app desde apache por el puerto 80. Vamos a ello.

Crea un nuevo virtualhost:

<virtualhost *:80>
  ServerName youtrack.ecapy.com
  ProxyPreserveHost On
  ProxyRequests Off
  <proxy *>
      Order deny,allow
      Allow from all
  </proxy>
  ProxyPass        /youtrack ajp://localhost:8080/youtrack
  ProxyPassReverse /youtrack ajp://localhost:8080/youtrack
  RewriteEngine On
  RewriteRule ^/$ "http://youtrack.ecapy.com/youtrack" [L]
  ErrorLog /var/log/apache2/yt-error.log
  CustomLog /var/log/apache2/yt-access.log common
</virtualhost>

En este ejemplo, lo he configurado para que youtrack sea accedido mediante youtrack.ecapy.com.

Los últimos dos pasos son habilitar el modulo ajp que es el modulo que usa apache para hablar con tomcat:

sudo service apache2 restart

Y reiniciamos apache:

sudo service apache2 restart

Resultado:
Tomcat app running through Apache

Chau!

Node.js

Tutorial: implementar un servicio RESTful public API de modelos Mongoose en Node.js

Hola!, He terminado de hacer un ejemplo de como implementar una API pública tipo RESTful en una APP creada con express.
La idea detrás de esta app de ejemplo es la de crear automagicamente todas las URL necesarias para poder gestionar un modelo de Mongoose (aka recurso).

Su uso es realmente simple. Dentro de la carpeta “models” creas un nuevo modelo/entidad, tocas un par de detalles mas, y tienes una API publica completa de ese nuevo recurso :)

Ya no doy mas vueltas, acá la tienen:

https://github.com/capy/Simple-API-style

Chau!

Node.js

Node.js/express: Gestionar las URL de tu proyecto mas eficientemente.

Normalmente cuando haces una aplicación usando express, por comodidad tiendes a poner todas las definiciones de las url’s en la raíz del proyecto:

var express = require('express');
var app = express();
//...
//Configuraciones varias
//...
app.get("/users", function (req, res) {
    return res.render("list users");
});
app.get("/user/:uid", function (req, res) {
    return res.render("show an user");
});
app.get("/articles", function (req, res) {
    return res.render("list articles");
});
app.get("/article/:id", function (req, res) {
    return res.render("show an article");
});
app.get("/article/new", function (req, res) {
    return res.render("new article");
});
http.createServer(app).listen(app.get('port'), function () {
    console.log("http://localhost:" + app.get('port'));
});

Pero esto a la corta se vuelve algo inmanejable. La solución es adoptar un patrón que nos permita extraer las URL a archivos en los que poder juntar las url que se refieran a algo en común (en este ejemplo usuarios por un lado y articles por otro.).

Manos a la obra

crea un directorio llamado routes, crea los archivos “users.js” y “articles.js” y llévate allí las urls.
En el lugar que estaban las URL (dentro de app.js o server.js, como sea que lo hayas llamado.) pon:

var routePath = __dirname + '/routes/';
fs.readdirSync(routePath).forEach(function (file) {
    require(routePath + file)(app);
});

Este snippet se va a encargar de cargar todos los archivos que haya en la carpeta routes. Si te fijas bien vas a ver que está haciendo un require pero le pasa “(app)” al final de este. Eso es porque dentro de nuestros archivos de rutas vamos a devolver funciones que van a ejecutarse ni bien sean cargadas.

Lo ultimo que nos queda es definir la estructura que debe tener cada archivo que esté alojado en routes. Como ya no están en la raíz, vamos a encapsular estas definiciones de rutas dentro de una función, que es la que se va a encargar de hacernos llegar a app (que es la instancia de express que va a recibir nuestras rutas):

users.js:

module.exports = function (app) {
    app.get("/users", function (req, res) {
        return res.render("list users");
    });
    app.get("/user/:uid", function (req, res) {
        return res.render("show an user");
    });
};

articles.js:

module.exports = function (app) {
    app.get("/articles", function (req, res) {
        return res.render("list articles");
    });
    app.get("/article/:id", function (req, res) {
        return res.render("show an article");
    });
    app.get("/article/new", function (req, res) {
        return res.render("new article");
    });
};

Como podemos ver, en cada uno de los archivos que contienen las rutas, hemos metido las rutas dentro de module.exports = function (app){}. Esto sumado a que a cada archivo cargado se le estaba pasando la app por medio del require (require(routePath + file)(app);) todo cobra sentido.

Ya hemos acabado, ya puedes crear tantos archivos .js como tu corazón y lógica dicten, que se van a cargar sin problemas.

Chau!

Druplicon

Drupal 7: Integrar Inline entity form perfectamente en el formulario en el que está anidado.

Cuando Inline entity form renderiza la entidad dentro de otro formulario tiende a meterle fieldsets y títulos extra que no son estrictamente parte del formulario de la entidad referida. Como resultado, el formulario anidado queda como tal, como si estuviera anidado, y eso no queda bonito. Estaria mejor que el formulario visualmente se integrase mejor al formulario del nodo al que está referido.

Para lograr esto agarra tu módulo o theme (template.php) y pon esto:

<?php
function TUMODULO_o_THEME_field_widget_inline_entity_form_single_form_alter(&$entity_form, &$form_state) {
  $entity_form['#type'] = 'container';
  $entity_form['form']['product_details']['#type'] = 'container';
  unset(
  $entity_form['#title'],
  $entity_form['#tree'],
  $entity_form['#description'],
  $entity_form['#prefix'],
  $entity_form['#suffix']
  );
}
?>

Básicamente se deshace de los fieldset que aíslan al formulario de la entidad referida del formulario que lo contiene.

El antes y el después:
Entity inline clean
Chau!

1263055255_application-javascript

Generar propiedades de un objeto (JSON) dinámicamente.

El caso es el siguiente: Tenemos un objeto al que queremos crearle propiedades anidadas pero nos gustaría no tener que preocuparnos de si sus parent existen. por ejemplo, si tuviera un objeto “miObjeto” y quisiera crearle una función en miObjeto.modulos.ctools.watcher, primero tendría que crear ese path. eso o podemos usar esta función:

/**
 * Genera propiedades de un objeto (JSON) dinamicamente.
 * @returns objeto extendido.
 */
function extend(base, ns_string) {
    var parts = ns_string.split('.'),
        pl, i;
    pl = parts.length;
    for (i = 0; i < pl; i++) {
        //create a property if it doesnt exist
        if (typeof base[parts[i]] == 'undefined') {
            base[parts[i]] = {};
        }
        base = base[parts[i]];
    }
    return base;
}

Y listo, ya podemos extender nuestro objeto de forma dinámica:

var miObjeto = {};
//console.log("Esto va a dar error porque 'modulos.ctools.watcher' no existe");
//console.log(miObjeto.modulos.ctools);
extend(miObjeto, 'modulos.ctools.watcher');
extend(miObjeto, 'settings.general');
miObjeto.modulos.ctools.watcher = function () {
    console.log("Soy una función!");
};
console.log(miObjeto);

Resultado:
resultado-objeto-propiedades-dinamicas
Chau!