tag:blogger.com,1999:blog-40278857696983679192024-02-07T00:59:36.357-05:00NovatoZsney2002http://www.blogger.com/profile/11603966854374488214noreply@blogger.comBlogger38125tag:blogger.com,1999:blog-4027885769698367919.post-76086758816832726912014-02-14T15:24:00.001-05:002014-02-14T15:41:20.287-05:00Tablet nueva, juegos rotos.Hace pocos días llego por fin mi tableta (una gallaxy tab 2) y como podrán imaginar lo primero que hice fue probar los juegos que he desarrollado y para mi sorpresa varios de ellos no funcionaban correctamente; en algunos se veía un segundo canvas con la misma imagen y en otros el contenido previo no se borraba. Al parecer es un <a href="https://code.google.com/p/android/issues/detail?id=39247" title="canvas clearRect a veces no funciona"><span id="goog_713664843"></span>problema ya viejo<span id="goog_713664844"></span></a> con clearRect que <a href="https://code.google.com/p/android/issues/detail?id=41312" title="canvas duplicado en android 4.1">afecta a android 4.1</a>.<br />
<br />
Lo primero que se me ocurrió para solucionar el problema fue el viejo truco de re-definir el tamaño del canvas:<br />
<pre class="js" name="code">canvas.width = canvas.width;</pre>
Ahora si que funcionaban, pero algunas acciones como el arrastrar eran realmente lentas (especialmente al arrastrar y soltar); el problema con este método es que ademas de borrar el contenido del canvas, <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#concept-canvas-set-bitmap-dimensions">también restablece su estado</a> (fillStyle, clearStyle, scale, translate, shadowColor....) lo que al parecer influye en el rendimiento (al menos en el gallaxy tab - único dispositivo que tengo para hacer pruebas).<br />
<br />
Otra de las soluciones sugeridas para eliminar el problema del canvas duplicado que encontré era el quitar el estilo overflow: hidden de todos los elementos padres del canvas, aunque el problema desapareció al igual que en el método anterior el rendimiento era pésimo.<br />
<h3>
Solución</h3>
Hasta el momento la solución con los mejores resultados es la de usar un color solido como fondo para el canvas y al momento de borrar el contenido usar fillRect en lugar de clearRect<br />
<pre class="html" name="code"><canvas style="background: #fff;"></canvas></pre>
<pre class="js" name="code">function clear(ctx) {
ctx.fillStyle = "#fff";
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
}
</pre>
El aumento en rendimiento es considerable, sobre todo en juegos que requieren arrastrar y soltar (como el solitario). Aun así esta no es una solución definitiva ya que al usar un color solido no es posible <a href="http://www.ibm.com/developerworks/library/wa-canvashtml5layering/">apilar canvas para crear capas</a>.<br />
<div>
<br /></div>
<div>
¿Conoces alguna mejor solución? déjala en los comentarios.</div>
sney2002http://www.blogger.com/profile/11603966854374488214noreply@blogger.com0tag:blogger.com,1999:blog-4027885769698367919.post-63088469825489220172013-12-05T23:10:00.000-05:002013-12-05T23:10:02.058-05:00Easy QR Code: Mi primera extensión para chrome<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhxR0G98kYLysKtJ8hw1Y1Hyh_ZuotKI1g4WbO5rSZELcZLWElyFCL_TBKvoYfWEPb6Swo-_UAiYBjoqDReOl3HdEK0uTv0a01QKTAZY8kza5rEoJi3sYglLuIffYQ0hU5k78aOIacWiw8n/s1600/easy-qr-code.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhxR0G98kYLysKtJ8hw1Y1Hyh_ZuotKI1g4WbO5rSZELcZLWElyFCL_TBKvoYfWEPb6Swo-_UAiYBjoqDReOl3HdEK0uTv0a01QKTAZY8kza5rEoJi3sYglLuIffYQ0hU5k78aOIacWiw8n/s1600/easy-qr-code.png" /></a></div>
<div>
<b><br /></b></div>
<div>
<b>tltr</b>: <i>Hice una extensión para chrome que genera códigos QR con tan solo dos clics (<a href="https://chrome.google.com/webstore/detail/easy-qr-code/okcelelcbendjdeilpendpjophdppgjk">descarguen aquí</a>). Si les gusta compartan/comenten en twitter/facebook (quiero ser famoso *¬*).</i></div>
<div>
<br /></div>
Los códigos QR son una forma de almacenar información en un código de barras en dos dimensiones, y son una muy buena forma de copiar enlaces y texto a nuestros dispositivos móviles en lugar de tener que copiarlos a mano.<div>
<br /><div>
En la tienda de extensiones de chrome no encontré ninguna que cumpliera mis necesidades (rápido y simple de usar), mucha de las "extensiones" son simples enlaces a paginas y requieren muchos pasos:</div>
<div>
<ol>
<li>Copiar texto/enlace</li>
<li>Clic para abrir la "Extensión"</li>
<li>Pegar texto en un formulario</li>
<li>Clic para generar el código</li>
</ol>
<div>
Demasiado extenuante para un simple código, en mi extensión ideal solo se necesita:</div>
<div>
<ol>
<li>Clic derecho sobre texto/enlace</li>
<li>Seleccionar generar código</li>
</ol>
<div>
Listo. adicionalmente el código debe generarse rápidamente. con estas especificaciones y armado con una <a href="https://github.com/neocotic/qr.js">librería para generar códigos QR</a> y la <a href="http://developer.chrome.com/extensions/index.html">documentación del API de extensiones de chrome</a> <strike>y dos cojones</strike> nació mi primera extensión para google chrome: <a href="https://chrome.google.com/webstore/detail/easy-qr-code/okcelelcbendjdeilpendpjophdppgjk">Easy QR Code</a>.</div>
</div>
</div>
<div>
<br /></div>
<div>
A continuación el enlace a la extensión y un corto vídeo de su funcionamiento.</div>
</div>
<div>
<a href="https://chrome.google.com/webstore/detail/easy-qr-code/okcelelcbendjdeilpendpjophdppgjk">https://chrome.google.com/webstore/detail/easy-qr-code/okcelelcbendjdeilpendpjophdppgjk</a></div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<iframe allowfullscreen='allowfullscreen' webkitallowfullscreen='webkitallowfullscreen' mozallowfullscreen='mozallowfullscreen' width='320' height='266' src='https://www.youtube.com/embed/KnxswyzQrVM?feature=player_embedded' frameborder='0'></iframe></div>
<div>
<br /></div>
sney2002http://www.blogger.com/profile/11603966854374488214noreply@blogger.com0tag:blogger.com,1999:blog-4027885769698367919.post-27151158269948256102013-10-17T20:32:00.001-05:002013-10-17T20:32:18.622-05:00Mi obsesión con el anime<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgWA6c6RXVwaxIy3gPfycokBKewZYnIE0LURz2_lCq2G_FIwYVTv_x0Cxy_NIODC-OVvI0PtSDcNisS73ZNCg3dk0mlQMjStt82uK8Z3Lx79LTi6DD7b_LspoVKSLTSmRh87OtdjfFnBBv3/s1600/anime.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="225" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgWA6c6RXVwaxIy3gPfycokBKewZYnIE0LURz2_lCq2G_FIwYVTv_x0Cxy_NIODC-OVvI0PtSDcNisS73ZNCg3dk0mlQMjStt82uK8Z3Lx79LTi6DD7b_LspoVKSLTSmRh87OtdjfFnBBv3/s400/anime.png" width="400" /></a></div>
<br />
Los que me conocen saben que estoy interesado en una variedad de cosas, pero suelo distraerme con facilidad, y lo que mas me distrae (o ¿en lo que mas me concentro?) es el anime.<br />
<br />
De tiempo en tiempo me engancho tanto con alguna serie que termino buscando de todo sobre ésta: Fondos de pantalla, openings/endings, AMV, posters motivacionales (tengo alrededor de 20.000 en mi pc) y noticias entre otros. El problema es que no se de ningún sitio que contenga todo este material, todo lo recolecto de diferentes lugares como foros, youtube, google images... y esto me quita mucho tiempo.
<br />
<br />
Alguien dirá que la solución es muy simple: <i>que deje de buscar pendejadas y me ponga a trabajar</i>. Pero el anime es una forma de distraerme del día a día, ademas el contenido de estos (historia/humor/personajes...) es mucho mas rico que el de muchas películas y programas de televisión, incluso hay algunos que dejan enseñanzas sobre la vida y esas cosas u_u.
<br />
<br />
Si saben de algún un sitio donde se pueda encontrar este contenido concentrado, con animes/manga clasificados por géneros (gore, echi, shounen...) Dejen el link en los comentarios.sney2002http://www.blogger.com/profile/11603966854374488214noreply@blogger.com0tag:blogger.com,1999:blog-4027885769698367919.post-1185221332952330522013-10-16T19:00:00.001-05:002013-10-16T19:00:39.878-05:00Dividir correctamente un string en caracteres. No con string.split("")<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhvmDBnDNXLwfeBRWFxB3G_keU0OGEZy_VI9qz0ttghe_hmvSi3rgXDdxBI7CjtI3qETjvZhP5ghkwVo6RxGa0Lv_MutDYV3qUjvcLKSdtpwoDZadc-12eyPRnE63BlGmMSn3LACr0t0L1U/s1600/chars.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="215" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhvmDBnDNXLwfeBRWFxB3G_keU0OGEZy_VI9qz0ttghe_hmvSi3rgXDdxBI7CjtI3qETjvZhP5ghkwVo6RxGa0Lv_MutDYV3qUjvcLKSdtpwoDZadc-12eyPRnE63BlGmMSn3LACr0t0L1U/s320/chars.jpg" width="320" /></a></div>
<br />
Dividir un string en caracteres parece una tarea sencilla, y lo es. mientras un carácter sea solo un carácter.<br />
<br />
<h3>
Como así?</h3>
En <a href="http://es.wikipedia.org/wiki/Unicode" rel="nofollow">unicode</a> existen <a href="http://es.wikipedia.org/wiki/Unicode#Composici.C3.B3n_de_caracteres_y_secuencias">caracteres que se combinan con otros</a>, ejemplo de estos son los caracteres de acento. De esta forma a simple vista un carácter puede parecer un solo símbolo pero estar formado por dos o mas.<br />
<br />
Por ejemplo la letra <b>"a" </b>con tilde puede ser un solo carácter (u+00e1), pero puede darse el caso que sea una letra <b>a</b> + una tilde (a+') que son dos caracteres (u+0061 y u+0301 respectivamente).<br />
<br />
Esto es un problema, en especial para una aplicación que necesite acceder a cada uno de los caracteres de una palabra como en los crucigramas, ahorcado, sopas de letras, etc.<br />
<br />
Por ejemplo en <a href="http://codecanyon.net/item/word-search-game/2708856">mi juego de sopa de letras</a> a veces las palabras en la cuadricula quedaban en la posición incorrecta. El problema, algunas palabras usaban mas espacio debido a los caracteres de combinación (eje. "mamá" ocupando 5 cuadros = m | a | m | a | ' |).
<br />
<br />
<h3>
Que puede salir mal</h3>
Para conocer el largo de una cadena de texto normalmente usamos la propiedad <b>length</b>... error, ésta solo da resultados correctos si cada carácter en el texto esta formado por un solo <a href="http://en.wikipedia.org/wiki/Code_point">codepoint</a>.
<br />
<pre class="js" name="code">alert("mamá".length)
</pre>
<input onclick="alert('mam\u0061\u0301'.length)" type="button" value="resultado" /><br />
Gracias a un enlace en <a href="http://developer.teradata.com/blog/jasonstrimpel/2011/11/javascript-string-length-and-internationalizing-web-applications">MDN</a> (Mozilla Developer Network) encontre esta <a href="http://developer.teradata.com/blog/jasonstrimpel/2011/11/javascript-string-length-and-internationalizing-web-applications">solución</a>. para obtener el largo correcto del string contamos los caracteres que no son marcas de combinación. a continuación la función (con algunas modificaciones).
<br />
<pre class="js" name="code">// regexp = rangos de caracteres correspondientes a marcas de combinación
rCombiningMarks = /[\u0300-\u036F\u1DC0-\u1DFF\u20D0-\u20FF\uFE20-\uFE2F\u0483-\u0489\u0591-\u05BD]/g;
function stringLength(string) {
// iniciar un contador de caracteres en 0
var length = 0;
// por cada caracter en string
for (var i = 0, l = string.length; i < l; i++) {
// si no es una marca de combinación
if (! rCombiningMarks.test(string.charAt(i)) )
// incrementar el contador de caracteres
length += 1
}
return length;
}
</pre>
Probamos nuestra función.
<br />
<pre class="js" name="code">alert(stringLength("mamá"));
</pre>
<input id="alertMamaLength" type="button" value="Resultado" />
<script>
(function(){
var rMarks = /[\u0300-\u036F\u1DC0-\u1DFF\u20D0-\u20FF\uFE20-\uFE2F\u0483-\u0489\u0591-\u05BD]/g;
function stringLength(string) {
var length = 0;
for (var i = 0, l = string.length; i < l; i++) {
if (! rMarks.test(string.charAt(i)) )
length += 1
}
return length;
}
function alertStringLength() { alert(stringLength('mam\u0061\u0301')); }
document.getElementById("alertMamaLength").onclick=alertStringLength;
}())
</script>
<br />
<br />
<h3>
Como dividir un string?</h3>
<div>
Podemos usar un método similar al anterior para dividir un string:</div>
<pre class="js" name="code">// regexp = rangos de caracteres correspondientes a marcas de combinación
rCombiningMarks = /[\u0300-\u036F\u1DC0-\u1DFF\u20D0-\u20FF\uFE20-\uFE2F\u0483-\u0489\u0591-\u05BD]/g;
function splitString(string) {
// iniciar un array de caracteres
var chars = [],
lastChar;
// por cada caracter en string
for (var i = 0, l = string.length; i < l; i++) {
// si no es una marca de cominación
if (! rCombiningMarks.test(string.charAt(i)) ) {
// Agregar al array de caracteres
chars.push(string.charAt(i));
}
// de lo contrario
else {
// Concatenar con ultimo caracter en el array
var lastChar = chars[chars.length - 1];
chars[chars.length - 1] = lastChar + string.charAt(i);
}
}
return chars;
}
// TEST
alert("con string.split: "+("mamá".split("").join(" | ")));
alert("con splitString: " + splitString("mama").join(" | "));
</pre>
<input id="splitStringExample" type="button" value="Resultado" /><br />
<script>
(function() {
// regexp = codepoints correspondientes a marcas de combinación
rCombiningMarks = /[\u0300-\u036F\u1DC0-\u1DFF\u20D0-\u20FF\uFE20-\uFE2F\u0483-\u0489\u0591-\u05BD]/g;
function splitString(string) {
// iniciar un array de caracteres
var chars = [],
lastChar;
// por cada caracter en string
for (var i = 0, l = string.length; i < l; i++) {
// si no es una marca de cominación
if (! rCombiningMarks.test(string.charAt(i)) ) {
// Agregar al array de caracteres
chars.push(string.charAt(i));
}
// de lo contrario
else {
// Concatenar con ultimo caracter en el array
var lastChar = chars[chars.length - 1];
chars[chars.length - 1] = lastChar + string.charAt(i);
}
}
return chars;
}
document.getElementById("splitStringExample").onclick = function() {
// probamos
alert("con string.split: "+("mam\u0061\u0301".split("").join(" | ")));
alert("con splitString: " + splitString("mam\u0061\u0301").join(" | "));
}
}())
</script>
<br />
<h3>
Conclusión</h3>
Aun cuando javascript nos provee varios métodos útiles para el manejo de strings, números, arrays y objetos, estos no siempre nos darán los resultados deseados. debemos probar nuestro código para estar seguros que se comporta de la forma correcta.
<br />
<br />
Algún otro método/función nativa de javascript de la que no hay que confiar. Déjalo en los comentarios.
<!-- Begin MailChimp Signup Form --><br />
<link href="//cdn-images.mailchimp.com/embedcode/slim-081711.css" rel="stylesheet" type="text/css"/><style type="text/css">
#mc_embed_signup{background:#fff; clear:left; font:14px Helvetica,Arial,sans-serif; }
/* Add your own MailChimp form style overrides in your site stylesheet or in this style block.
We recommend moving this block and the preceding CSS link to the HEAD of your HTML file. */
</style><br />
<div id="mc_embed_signup">
<form action="http://jsaw5.us7.list-manage1.com/subscribe/post?u=805064bc7014740083bbf3868&id=2c26ca8909" class="validate" id="mc-embedded-subscribe-form" method="post" name="mc-embedded-subscribe-form" novalidate="" target="_blank">
<label for="mce-EMAIL">Suscríbete y mantente informado de los últimos artículos</label><br />
<input class="email" id="mce-EMAIL" name="EMAIL" placeholder="email address" required="" type="email" value="" /><br />
<div class="clear">
<input class="button" id="mc-embedded-subscribe" name="subscribe" type="submit" value="Subscribe" /></div>
</form>
</div>
<br />
<!--End mc_embed_signup-->sney2002http://www.blogger.com/profile/11603966854374488214noreply@blogger.com0tag:blogger.com,1999:blog-4027885769698367919.post-60341216756200649922013-09-03T02:46:00.000-05:002013-10-16T18:53:45.774-05:00Canvas desde cero: primeros pasosEl elemento canvas es una gran herramienta para crear juego, animaciones y manipular imágenes, aun así no todos los navegadores soportan esta etiqueta, por tanto, es necesario contar con alternativas para no dejar de lado a los usuarios de dichos navegadores.
<br />
<br />
En esta primera parte veremos dos de estas alternativas así como algunas particularidades de la etiqueta canvas.
<br />
<h3>
Compatibilidad</h3>
En la actualidad los únicos navegador que no cuentan con <a href="http://caniuse.com/canvas">soporte para el elemento canvas</a> son Internet Explorer 6, 7 y 8; no obstante existen soluciones (<a href="http://nosolohtml5.blogspot.com/2011/07/que-es-un-polyfill.html">polyfills</a>) que emulan su funcionalidad, entre los que se destacan:
<br />
<ul>
<li><b><a href="http://excanvas.sourceforge.net/">excanvas</a></b>: utiliza VML, una tecnología propietaria de Microsoft similar al svg y no necesita de plugins, pero carece de
algunos métodos (getImageData, toDataURL...).</li>
<li><b><a href="http://flashcanvas.net/">flashcanvas</a></b>: utiliza flash (duh), es mas rápido que excanvas (a mi parecer) y <a href="http://flashcanvas.net/docs/canvas-api">soporta mas características</a>, pero hay que comprar una licencia para poder desarrollar aplicaciones de comerciales.</li>
</ul>
<h3 id="etiqueta">
La etiqueta canvas</h3>
El elemento canvas cuenta con una etiqueta de apertura y una de cierre, pero a diferencia de otras etiquetas, su contenido solo es visible en navegadores antiguos (ya que estos ignoran las etiquetas desconocidas) lo cual nos permite mostrar un contenido alternativo a los usuarios de dichos navegadores.
<br />
<pre class="js" name="code"><canvas height="150" id="canvas" width="300">
<!-- contenido a mostrar si el navegador no soporta canvas -->
<img src="/images/imagen-estatica.jpg" />
</canvas>
</pre>
Otro hecho a destacar es que sus medidas se declaran mediante los atributos width/height y no a través css. al usar css se escala el elemento, así, si el canvas mide 300x150 y cambiamos sus medidas por 600x300 los pixeles "medirán el doble" y la imagen se verá borrosa.
<br />
<br />
Por ejemplo, los siguientes cuadros se dibujaron en la mismas posición (10, 10) y tienen las mismas medidas (30x30) pero el canvas de la derecha fue escalado 6x usando css.
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgtgktBFNo5zDIGB2GXkUwQNiC2dlQo20jU4EitmBebcWg9LZ7cOKLVu2v-BhbSM175eF73Bdv3rxROh9hn51M4xtQ5CXShHTQznkg6KRozb7_8EFMyR9v8DifLxPlKZQk467iseahAsUq5/s1600/a.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="105" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgtgktBFNo5zDIGB2GXkUwQNiC2dlQo20jU4EitmBebcWg9LZ7cOKLVu2v-BhbSM175eF73Bdv3rxROh9hn51M4xtQ5CXShHTQznkg6KRozb7_8EFMyR9v8DifLxPlKZQk467iseahAsUq5/s400/a.png" width="400" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Note el borde borroso el la segunda imagen </td></tr>
</tbody></table>
<h3 id="soporte">
Detectar soporte</h3>
Antes de empezar a usar el canvas necesitamos saber si el navegador siquiera soporta
dicho elemento, para esto:
<br />
<ol>
<li>Creamos un elemento canvas y comprobamos si cuenta con el método getContext</li>
<li>De lo contrario verificamos que exista un Polyfill</li>
</ol>
<pre class="js" name="code">if (! document.createElement("canvas").getContext || ! window.G_vmlCanvasManager )
alert("Actualize su navegador");
</pre>
<h3>
Accediendo desde javascript</h3>
Para poder acceder al elemento desde javascript hay esperar que el DOM este listo, para esto puedes utilizar
la función ready de jQuery o colocar el script justo antes de </body>.<br />
<br />
A continuación obtenemos una referencia al contexto de dibujo del canvas utilizando el método getContext:
<br />
<pre class="js" name="code">var canvas = document.getElementById("canvas"),
ctx = canvas.getContext("2d");
</pre>
Ahora si podemos empezar a dibujar:
<br />
<pre class="js" name="code">ctx.fillStyle = "red";
ctx.fillRect(50, 50, 30, 20);
</pre>
<canvas id="my-canvas" style="border: 1px solid black;"><b>Utiliza un navegador moderno para poder ver el ejemplo</b></canvas><br />
<script>
(function() {
var canvas = document.getElementById("my-canvas");
if (canvas.getContext) {
var ctx = canvas.getContext("2d");
ctx.fillStyle = "red";
ctx.fillRect(50, 50, 30, 20);
}
}())
</script>
<br />
<h3 id="tips">
TIPS</h3>
Antes de poder acceder al contexto del canvas en IE<9 (usando excanvas/FlashCanvas) <a href="http://flashcanvas.net/docs/usage">debemos inicializarlo</a>, para esto, en lugar de usar el método getContext podemos usar la siguiente función:
<br />
<pre class="js" name="code">function getContext(canvas) {
if (! canvas.getContext && window.GV_ContextCanvas) {
G_vmlCanvasManager.init(canvas);
} else {
throw new Error("El navegador no soporta el elemento canvas");
}
return canvas.getContext("2d");
}
</pre>
<h3>
Conclusión</h3>
Finalizando esta primera parte tenemos todo listo para empezar a trabajar con el canvas. con suerte ahora podrás mostrar contenido alternativo para usuarios con navegadores antiguos, detectar el soporte del elemento canvas ya sea de forma nativa o mediante un polyfill y obtener el contexto de dibujo.
<br />
<br />
No te pierdas la siguiente parte en la que veremos el sistema de coordenadas y como dibujar formas básicas.
<br />
<br />
Si te gusto el tutorias compartelo en twitter o subscribete y te avisare cuando esté disponible la siguiente parte.
<!-- Begin MailChimp Signup Form --><br />
<link href="//cdn-images.mailchimp.com/embedcode/slim-081711.css" rel="stylesheet" type="text/css"></link><style type="text/css">
#mc_embed_signup{background:#fff; clear:left; font:14px Helvetica,Arial,sans-serif; }
/* Add your own MailChimp form style overrides in your site stylesheet or in this style block.
We recommend moving this block and the preceding CSS link to the HEAD of your HTML file. */
</style><br />
<div id="mc_embed_signup">
<form action="http://jsaw5.us7.list-manage1.com/subscribe/post?u=805064bc7014740083bbf3868&id=2c26ca8909" class="validate" id="mc-embedded-subscribe-form" method="post" name="mc-embedded-subscribe-form" novalidate="" target="_blank">
<label for="mce-EMAIL">Suscríbete y mantente informado</label><br />
<input class="email" id="mce-EMAIL" name="EMAIL" placeholder="email address" required="" type="email" value="" /><br />
<div class="clear">
<input class="button" id="mc-embedded-subscribe" name="subscribe" type="submit" value="Subscribe" /></div>
</form>
</div>
<br />
<!--End mc_embed_signup-->sney2002http://www.blogger.com/profile/11603966854374488214noreply@blogger.com0tag:blogger.com,1999:blog-4027885769698367919.post-12083458531545047532013-08-23T23:09:00.002-05:002013-09-03T02:16:43.492-05:00Canvas desde cero<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjtj8HDGMlzWJbS32zybFlgxWgVotfrqAC6y84batRozlrxepLexODMYoEUXPEPG7MuxfL52-wktaWzUxgYZsOBnUGguqp-G0DjFy0lzyAIgNgAHOYcO0daqYVR1qN73URT9hi1P0UIwNE7/s1600/canvas.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="211" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjtj8HDGMlzWJbS32zybFlgxWgVotfrqAC6y84batRozlrxepLexODMYoEUXPEPG7MuxfL52-wktaWzUxgYZsOBnUGguqp-G0DjFy0lzyAIgNgAHOYcO0daqYVR1qN73URT9hi1P0UIwNE7/s400/canvas.png" width="400" /></a></div>
<br />
Si llevas tiempo con ganas de aprender a usar el canvas o quieres refrescar tus conocimientos,<br />
no puedes perderte esta serie de tutoriales que arranca el día de hoy, donde no solo se explicara<br />
como usar x función sino que desarrollaremos programas reales donde podrás aplicar lo aprendido.<br />
<h3>
y quien es usted para decirme que hacer</h3>
Soy un tipo al que le gusta la programación, en especial todo lo relacionado con la web (javascript *¬*)<br />
y desde que conocí el elemento canvas quede fascinado con la idea de poder dibujar en navegador.<br />
si bien no soy un experto y aun sigo aprendiendo, ya llevo tiempo trabajando con este elemento<br />
y he aprendido mucho gracias a la comunidad y esta es mi forma de contribuir en algo.<br />
algunos de los trabajos que he hecho:<br />
<ul>
<li><a href="http://www.blogger.com/sney2002.github.io/canvas-event-js" title="canvas-event">canvas-event</a>: un framework para interactuar con el canvas.</li>
<li><a href="http://www.blogger.com/conceptmapp.com" rel="" title="Conceptmapp herramienta para crear mapas conceptuales">conceptmapp</a>: una herramienta para crear mapas conceptuales</li>
<li><a href="https://dl.dropboxusercontent.com/u/5308045/unshredder/index.html" rel="nofollow">unshredder</a>: mi solución en javascript a un reto planteado por <a href="http://instagram-engineering.tumblr.com/post/12651721845/instagram-engineering-challenge-the-unshredder" rel="nofollow">instagram</a></li>
<li><a href="http://codecanyon.net/user/sney2002/portfolio">pasatiempos</a>: Algunos juegos a la venta en codecanyon.</li>
</ul>
<br />
P.D.: Si ven que me estoy colgando en la actualización del blog no duden en jalarme las orejas en <a href="http://www.twitter.com/sney2002" rel="nofollow">twitter </a>XD.<!-- Begin MailChimp Signup Form --><br />
<link href="//cdn-images.mailchimp.com/embedcode/slim-081711.css" rel="stylesheet" type="text/css"></link><style type="text/css">
#mc_embed_signup{background:#fff; clear:left; font:14px Helvetica,Arial,sans-serif; }
/* Add your own MailChimp form style overrides in your site stylesheet or in this style block.
We recommend moving this block and the preceding CSS link to the HEAD of your HTML file. */
</style><br />
<div id="mc_embed_signup">
<form action="http://jsaw5.us7.list-manage1.com/subscribe/post?u=805064bc7014740083bbf3868&id=2c26ca8909" class="validate" id="mc-embedded-subscribe-form" method="post" name="mc-embedded-subscribe-form" novalidate="" target="_blank">
<label for="mce-EMAIL">Suscríbete y mantente informado</label><br />
<input class="email" id="mce-EMAIL" name="EMAIL" placeholder="email address" required="" type="email" value="" /><br />
<div class="clear">
<input class="button" id="mc-embedded-subscribe" name="subscribe" type="submit" value="Subscribe" /></div>
</form>
</div>
<br />
<!--End mc_embed_signup-->sney2002http://www.blogger.com/profile/11603966854374488214noreply@blogger.com0tag:blogger.com,1999:blog-4027885769698367919.post-87708927009489172482012-04-26T10:53:00.000-05:002012-04-26T10:53:23.993-05:00Creando un blog con django - parte 4<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiCpC_XIgdCsgNYJGnrMcj_Pfg322B83ZRrPBQRvZ6_5abelnUnc322LqFf4sAUUGvYs42z7Sby7SIlTiTPfoe3Ae-_8OfGpmMVMsWjsZtYLsEb1t7QjLjmtCaOmNWlIdYb15Lka4vZTaaI/s1600/Comentario_O_El_Pato_Muere.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiCpC_XIgdCsgNYJGnrMcj_Pfg322B83ZRrPBQRvZ6_5abelnUnc322LqFf4sAUUGvYs42z7Sby7SIlTiTPfoe3Ae-_8OfGpmMVMsWjsZtYLsEb1t7QjLjmtCaOmNWlIdYb15Lka4vZTaaI/s200/Comentario_O_El_Pato_Muere.jpg" width="198" /></a></div>
Después de una eternidad sin escribir y casi abandonar el tema de django, hoy por fin retomo el asunto. Hasta el momento tenemos los post clasificado por tags, pero un blog sin comentarios no es nada, así que hoy veremos como crear el formulario de comentarios.<br />
<br />
En django existen dos formas de crear un formulario, una es <a href="https://docs.djangoproject.com/en/1.4/topics/forms/#form-objects">crear cada campo manualmente</a> y la otra es crearlos automáticamente <a href="https://docs.djangoproject.com/en/1.4/topics/forms/modelforms/">basados en el modelo de datos</a>.<br />
<br />
Ya que casi siempre los formulario se usan para agregar/editar una tabla en una BD, y los campos del formulario reflejan los campos de la tabla, seria un desperdicio de tiempo crearlos manualmente.<br />
<br />
Dejemos de hablar y empecemos a escribir código, editemos el archivo <b>forms.py</b> en el directorio de nuestra aplicación:<br />
<pre class="python" name="code">from django.forms import ModelForm #1
from blog.models import Comment #2
class CommentForm(ModelForm): #3
"""Formulario de comentarios"""
class Meta: #4
model = Comment #5
exclude = ("post","deleted") #6
</pre>
<br />
Hora de explicar lo que hemos hecho:<br />
<ol>
<li>Importamos <a href="https://docs.djangoproject.com/en/1.4/topics/forms/modelforms/#modelform">ModelForm</a>, la base para los formularios basados en modelos de datos.</li>
<li>Importamos el modelo para el que se creará el formulario</li>
<li>Creamos una subclase de ModelForm.</li>
<li>En la clase Meta especificamos como crear el formulario</li>
<li><b>model</b>: modelo en el que se basa el formulario.</li>
<li><b>exclude</b>: campos a excluir, en este ejemplo post lo asignamos según el articulo desde el que se envió el comentario y deleted lo decidimos en la pagina de administración si es un comentario insultante. Si son muchos los campos a excluir podemos hacer lo contrario y <a href="https://docs.djangoproject.com/en/1.4/topics/forms/modelforms/#using-a-subset-of-fields-on-the-form">especificar los que se deben incluir</a>.</li>
<ol></ol>
</ol>
Ahora podemos incluir el formulario en la vista <b>single</b>:<br />
<pre class="python" name="code"># Archivo views.py
from blog.models import Post, Tag, Comment
from blog.forms import CommentForm
...
# la función recibe el numero capturado
def single(request, id=0):
# recuperar post según id
post = Post.objects.get(id=int(id))
# Crear formulario
form = CommentForm()
return render_to_response("single.html", locals())
</pre>
Para crear el formulario se instancia el objeto adecuado y en la vista se usa esta instancia para <a href="https://docs.djangoproject.com/en/1.4/topics/forms/#displaying-a-form-using-a-template">mostrar el formulario</a>:<br />
<pre class="python" name="code">{# plantilla single.html #}
{% extends 'base.html' %}
{% block main_content %}
{% include 'include/article.html' %}
{# necesitamos crear el tag form #}
&lt;form action="" method="form"&gt;
{{ form }}
&lt;/form&gt;
{% endblock %}
</pre>
Con esto veremos el formulario en nuestro blog, lo llenamos y enviamos...<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiZN6b8qF20WRnQ3VwMbRO6Lu0dwgPz32wZ9AjZMsSYyPMBfrSy6jCYUqob9sVafoDBMXyrDUkagxkxzqSKu2sqn9xb8O9jU8mRcNewqKBXToZ1Wt-eVXiSrdft2_p8-9ypyvDxshto0hj4/s1600/Pantallazo.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="163" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiZN6b8qF20WRnQ3VwMbRO6Lu0dwgPz32wZ9AjZMsSYyPMBfrSy6jCYUqob9sVafoDBMXyrDUkagxkxzqSKu2sqn9xb8O9jU8mRcNewqKBXToZ1Wt-eVXiSrdft2_p8-9ypyvDxshto0hj4/s320/Pantallazo.png" width="320" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
</div>
Wow un error. Django requiere que incluyamos protección contra <a href="http://www.squarefree.com/securitytips/web-developers.html#CSRF">ataques csrf</a>, este tipo de ataque permite que alguien engañe a un usuario registrado para enviar datos a nuestro sitio usando un formulario en otra pagina web. Afortunadamente django tiene <a href="https://docs.djangoproject.com/en/1.4/ref/contrib/csrf/">todo lo necesario para protegernos</a> con unas cuantas lineas de código.<br />
<pre class="python" name="code"># Archivo views.py
from blog.models import Post, Tag, Comment
from blog.forms import CommentForm
# importamos el decorador
from django.views.decorators.csrf import csrf_protect
# importamos render
from django.shortcuts import render
...
# aplicamos el decorador función que recibe datos de formulario
@csrf_protect
def single(request, id=0):
# recuperar post según id
post = Post.objects.get(id=int(id))
form = CommentForm()
# usamos render en lugar de render_to_response
return render(request, "single.html", locals())
</pre>
Ahora incluimos el tag <code>{% csrf_token %}</code> dentro del formulario en la plantilla single.html. Esto crea un elemento input oculto con un token único que se verifica al enviar datos al servidor, si este no coincide, se produce un <a href="http://es.wikipedia.org/wiki/Anexo:C%C3%B3digos_de_estado_HTTP#4xx_Errores_del_cliente">error 403</a> (prohibido) y tenemos una app algo mas segura.<br />
<br />
Ok, ya podemos enviar datos del formulario al servidor, hora debemos validarlos y guardarlos en la BD.<br />
<pre class="python" name="code"># Archivo views.py
@csrf_protect
def single(request, id=0):
# recuperar post según id
post = Post.objects.get(id=int(id))
# recuperar comentarios de este post
comments = Comment.objects.filter(post=post)
# Si la petición es POST es porque enviaron el formulario
if request.method == "POST":
# Creamos una instancia del modelo Comment asignado el post actual
comment = Comment(post=post)
# Creamos una instancia del formulario con los datos recibidos
form = CommentForm(request.POST, instance=comment)
# Validamos los datos
if form.is_valid():
# Guardamos el comentario en la BD
form.save()
# Enviamos al usuario de nuevo al post
return HttpResponseRedirect("/post/{0}".format(slug))
# De lo contrario creamos un formulario vacío
else:
form = CommentForm()
return render(request, "single.html", locals())
</pre>
Podemos notar algunas cosas:<br />
<ol>
<li>Validamos el formulario con el método <b>is_valid</b> que no definimos. ¿como es esto posible?; Al <a href="http://www.novatoz.com/2011/11/creando-un-blog-con-django-parte-2.html">crear el modelo</a> Comment especificamos el tipo de datos admitidos, el formulario "sabe" que si esto datos no corresponden, el formulario contiene errores.</li>
<li>Para guardar los datos usamos <b>form.save</b> y no <b>comment.save</b>. Esto se debe a que hay datos que no están incluidos en el formulario (post) pero si en la instancia comment y viceversa, por esto pasamos comment al formulario en el momento de crearlo.</li>
</ol>
Solo resta mostrar los comentarios, editamos la plantilla <b>single.html</b>:<br />
<pre class="python" name="code">{# plantilla single.html #}
{% extends 'base.html' %}
{% block main_content %}
{% include 'include/article.html' %}
{% for comment in comments %}
&lt;div class="comment"&gt;
&lt;div class="user"&gt;{{ comment.name }} dijo:&lt;/div&gt;
&lt;div class="body"&gt;{{ comment.body }}&lt;/div&gt;
&lt;small&gt;{{ comment.date|date:"d-m-Y" }}&lt;/small&gt;
&lt;/div&gt;
{% endfor %}
{# necesitamos crear el tag form #}
&lt;form action="" method="form"&gt;
{{ form }}
&lt;/form&gt;
{% endblock %}
</pre>
Con esto solo falta crear el sitio de administración para agregar post y moderar los comentarios y tendremos un blog terminado. Estén pendientes por la ultima parte de esta serie que seguro saldrá antes que se acabe el mundo este 21 de diciembre XD.sney2002http://www.blogger.com/profile/11603966854374488214noreply@blogger.com7tag:blogger.com,1999:blog-4027885769698367919.post-39345767507625407672012-03-28T10:08:00.000-05:002012-03-28T10:08:11.698-05:00while (fracaso) { try{do_something} catch(e){ continue; } }<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhfLlClzXcdMJmHNA9IxPo-K9YRFCQf8vbEJotrJv0avqrsHT03mj_PyLlE-OyH8icLHstPkNojK3v7rTZTv4tyzR4mutd3MT39xwWczT7w75be-ksh3g-N5hplANrXXmmYDhHOXL8PJ7XS/s1600/assaltoenet4.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="266" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhfLlClzXcdMJmHNA9IxPo-K9YRFCQf8vbEJotrJv0avqrsHT03mj_PyLlE-OyH8icLHstPkNojK3v7rTZTv4tyzR4mutd3MT39xwWczT7w75be-ksh3g-N5hplANrXXmmYDhHOXL8PJ7XS/s320/assaltoenet4.png" width="320" /></a></div>
<br />
Después de varios "fracasos" puedo decir con seguridad que la parte difícil de programar no es tener una idea y desarrollarla, no porque sea fácil, sino porque entre entre mas dificultad presente el problema mas nos entusiasmamos y a pesar de todo lo disfrutamos (al menos yo).<br />
<br />
Pero a la hora de "vender" el producto y lo pongo entre comillas ya que no lo digo en el sentido de intercambiarlo por dinero sino hacer que las personas lo usen, recomienden... todo va mal. Por poner ejemplos:<br />
<br />
<a href="http://conceptmapp.com/">ConceptMap</a>: una aplicación para crear mapas conceptuales que desde su lanzamiento ha tenido la increíble estadística de 2 personas afiliadas y 1 mapa conceptual creado.<br />
<br />
<a href="http://jsaw5.com/">jsaw5</a>: un juego que permite crear rompecabezas de hasta 100 fichas usando cualquier imagen y compartirlos con amigos usando una url corta, que empezó muy bien pero a medida que pasan los días veo como las estadísticas en analytics van cayendo.<br />
<br />
Y eso que estas aplicaciones son GRATIS, ahora no me imagino lo difícil que debe de ser atraer usuarios que paguen por usar tu programa.<br />
<br />
No es solo la falta de experiencia en marketing y esas cosas, sino que la tarea en sí resulta molesta a diferencia de programar que disfruto aun cuando me saca canas verdes.<br />
<br />
Tal vez no tengo talento para esto, tal vez nunca pueda llegar a crear una aplicación "popular", pero por el momento seguiré intentando.<br />
<br />
Alguien mas se siente de esta forma, algún consejo.sney2002http://www.blogger.com/profile/11603966854374488214noreply@blogger.com0tag:blogger.com,1999:blog-4027885769698367919.post-27241068333093059082012-03-08T18:21:00.000-05:002013-07-31T19:08:15.290-05:00jsaw5: Crea rompecabezas personalizados<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgF4ffmpFiguGpqikrFFMaeTi0oSIe0DYPSPyWliLZKqR5_w0yD6VMh72bbFQwTBthEUd2p2a0e1fKU29kh9aGXUi5jClFRdG4LHMIFXvC-JfTTJwraftWbDExID6uoG_4AS_aN8OTs2TGj/s1600/draf-logo-500.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="158" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgF4ffmpFiguGpqikrFFMaeTi0oSIe0DYPSPyWliLZKqR5_w0yD6VMh72bbFQwTBthEUd2p2a0e1fKU29kh9aGXUi5jClFRdG4LHMIFXvC-JfTTJwraftWbDExID6uoG_4AS_aN8OTs2TGj/s320/draf-logo-500.png" width="320" /></a></div><div><br />
</div>Ya llevo tiempo sin escribir, y es que en estos días han pasado muchas. Ahora que tengo tiempo libre (ya que estoy desempleado) me puse en la tarea de crear un juego en javascript y HTML5: <a href="http://jsaw5.com/">jsaw5</a>.<div><br />
<b>Actualización</b>: el demo fue movido a <a href="http://dl.dropboxusercontent.com/u/5308045/canvas-puzzle/index.html">dropbox</a><br />
<br />
</div><div>En jsaw5 podrán crear, resolver y compartir rompecabezas personalizados con amigos en twitter y facebook. Espero que lo visiten y den sus opiniones, aun debe tener varios errores pero si esperaba a que fuera "perfecto" </div><div><br />
</div><div>En el proceso de creación agregue algunas mejoras a <a href="http://github.com/sney2002/canvas-event-js">canvas-event-js</a> las cuales espero hacer disponibles en cuanto pueda.</div>sney2002http://www.blogger.com/profile/11603966854374488214noreply@blogger.com6tag:blogger.com,1999:blog-4027885769698367919.post-52830518086389265052011-12-28T09:53:00.000-05:002011-12-28T09:53:39.391-05:00Visor de imágenes para xkcd<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhkqe-JiOqwGRp3eCT8kW4PdAuvce6gyW_qdoQ8qZPIs1reoBqfDnGqhUhKe15hbM77Z817nNvK3S7pt3x7eFkfoDXJ8AKV4PSUoAKdatBneDHqP8p6CZJvud_Icg6RHgEffpK026bDLjGS/s1600/python.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhkqe-JiOqwGRp3eCT8kW4PdAuvce6gyW_qdoQ8qZPIs1reoBqfDnGqhUhKe15hbM77Z817nNvK3S7pt3x7eFkfoDXJ8AKV4PSUoAKdatBneDHqP8p6CZJvud_Icg6RHgEffpK026bDLjGS/s1600/python.png" title="Ayer escribí 20 programas cortos en Python. Fue increíble. Perl, te dejo." /></a></div>
Una de mis tiras cómicas favoritas es <a href="http://xkcd.com/">xkcd</a>, y desde ya hace tiempo quería descargarme todas las tiras para tenerlas en el disco duro. Si han leido xkcd seguro sabran de los textos adicionales que <a href="http://en.wikipedia.org/wiki/Randall_Munroe">Randall Munroe</a> pone en el atributo title de las imágenes, este texto es complementario a la tira y en muchas ocasiones mas gracioso que la tira misma.<br />
<br />
Así que si descargaba las tiras tenia que hacerlo con todo y titulo. Tuve un tiempo libre esta navidad y me puse a recolectar información de como hacerlo, lo primero era descargar las imágenes y guardar el texto del tooltip en los metadatos de la imagen, para esto me fue de mucha ayuda este <a href="http://blog.client9.com/2007/08/python-pil-and-png-metadata-take-2.html">articulo</a> donde se explica como hacerlo usando la libreria PIL.<br />
<br />
Lo siguiente era crear un visor de imágenes que mostrara los tooltip, algo complicado y mas teniendo en cuenta que esta es la segunda interfaz gráfica "seria" que hago, pero encontré muy buenos recursos como este <a href="http://effbot.org/tkinterbook/">libro online</a> sobre <a href="http://es.wikipedia.org/wiki/Tkinter">tkinter</a> con muy buenos ejemplos, ademas de un <a href="http://tkinter.unpythonic.net/wiki/ToolTip">script que implementa un tooltip</a> que me cayo de perlas.<br />
<br />
Luego de varias horas de pelearme acomodando los widget (con <a href="http://effbot.org/tkinterbook/pack.htm">pack</a> y <a href="http://effbot.org/tkinterbook/grid.htm">grid</a>), he aquí el visor de imágenes junto con todas las tiras hasta la fecha (1-995).<br />
<ul>
<li><a href="http://goo.gl/PMO70">Visor de imágenes para Windows</a>.</li>
<li><a href="http://goo.gl/CaKoN">Código fuente</a> (puede causar dolor de cabeza XD).</li>
<li><a href="http://goo.gl/oTJ1G">Tiras cómicas de 1-995</a>.</li>
</ul>
<div>
Si bajan el código fuente necesitaran instalar las siguientes librerías:</div>
<div>
<ul>
<li><a href="http://www.pythonware.com/products/pil/">PIL</a> (en Windows y Linux)</li>
<li><a href="http://www.pythonware.com/library/pil/handbook/imagetk.htm">ImageTk</a> (solo en Linux: python-imaging-tk)</li>
<li><a href="http://tkinter.unpythonic.net/wiki/How_to_install_Tkinter">Tkinter</a> (solo en Linux)</li>
</ul>
<div>
Si tienen alguna sugerencia no duden en dejarla en los comentarios, siempre es bueno saber en que mejorar.</div>
</div>sney2002http://www.blogger.com/profile/11603966854374488214noreply@blogger.com0tag:blogger.com,1999:blog-4027885769698367919.post-39936962689718606032011-12-26T11:07:00.001-05:002011-12-26T11:14:06.346-05:00Actualización: canvas-event-js 0.2Después de meses sin actualizar, he aquí algunas cosas nuevas que pueden encontrar en <a href="https://github.com/sney2002/canvas-event-js">canvas-event</a>:<br />
<ul><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEishxtplTCRnyydCLinrKq-z0rtxs7mD_XquAnv6ZpeBj11PsXpO58QoB2EoJasUnQeGZi5oACp8ZXrJpjVodyNsa-OmMkdl7c0CSSRu-f8n-yvnuBls4qkJuyd0CZm9jjYj9CH-s6hWWCw/s1600/HTML5-canvas.jpg" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEishxtplTCRnyydCLinrKq-z0rtxs7mD_XquAnv6ZpeBj11PsXpO58QoB2EoJasUnQeGZi5oACp8ZXrJpjVodyNsa-OmMkdl7c0CSSRu-f8n-yvnuBls4qkJuyd0CZm9jjYj9CH-s6hWWCw/s200/HTML5-canvas.jpg" width="200" /></a>
<li><a href="http://sney2002.github.com/canvas-event-js/">Documentación</a> terminada.</li>
<li>Soporte para <a href="http://sney2002.github.com/canvas-event-js/example-zoom.html">zoom</a>.</li>
<li>Nuevo <a href="http://sney2002.github.com/canvas-event-js/example-text.html">objeto text</a>.</li>
<li>Animaciones usando <a href="http://paulirish.com/2011/requestanimationframe-for-smart-animating/">RequestAnimationFrame</a>.</li>
<li>Corrección de errores.</li>
</ul>
Ideas que tengo pero no he implementado:<br />
<ul>
<li><b>Drag-n-Drop live</b> para evitar llamar <a href="http://sney2002.github.com/canvas-event-js/drag-n-drop.html">drag</a> en cada objeto creado, ya que demasiados callback pueden poner lenta las aplicaciones.</li>
<li><b>moveUp, moveDown, toFront, toBack</b>: para mover objetos entre capas (como en PowerPoint). </li>
</ul>
Todo esto lo pueden encontrar en el repositorio de <a href="https://github.com/sney2002/canvas-event-js">github</a> a donde he movido el proyecto, donde ademas pueden dejar sus <a href="https://github.com/sney2002/canvas-event-js/issues">sugerencias y reportes de errores</a>, su ayuda es muy importante y siempre bien recibida.<br />
<br />
<b>P.D.</b>: Has creado alguna aplicación usando canvas-event, deja el link en los comentarios (simple curiosidad *¬*)sney2002http://www.blogger.com/profile/11603966854374488214noreply@blogger.com0tag:blogger.com,1999:blog-4027885769698367919.post-22992117180128387352011-12-23T15:36:00.000-05:002011-12-23T15:36:07.744-05:00Creando un blog con django - parte 3<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgYj_E8SqiNEK9hjlrCTeJte7Kpbv5sdcMcx8E-Cj9Tb19qnfdDSHwJzgdhgTqvduzBDd8Iyv1DQqrJFIphfX5ZogYqTeGb_jOW_lpPDPLsEZPdDHiSBA1mEShe1QaKOA90XuIBH05crM0u/s1600/Tienda+Vichada.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgYj_E8SqiNEK9hjlrCTeJte7Kpbv5sdcMcx8E-Cj9Tb19qnfdDSHwJzgdhgTqvduzBDd8Iyv1DQqrJFIphfX5ZogYqTeGb_jOW_lpPDPLsEZPdDHiSBA1mEShe1QaKOA90XuIBH05crM0u/s1600/Tienda+Vichada.png" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
</div>
En la parte anterior dejamos la base de datos lista, es momento de crear las plantillas para visualizar estos datos.<br />
Lo primero que haremos es configurar el directorio donde estaran guardas las plantillas. Creamos el directorio templates en la raíz del proyecto y editamos el archivo settings.py.<br />
<pre class="python" name="code">.....
import os
TEMPLATE_DIRS = (
os.path.join(os.path.dirname(__file__) ,"templates"),
)
....
</pre>
Así el script puede encontrar el directorio templates aun cuando cambiemos de ubicación la carpeta del proyecto. Ahora editamos el archivo urls.py para incluir la url a la pagina.<br />
<pre class="python" name="code">....
urlpatterns = patterns('',
(r'^$', 'blog.views.index'),
....
)
</pre>
Con esto le decimos a django que toda petición hecha a la raíz del blog sera manejada por la función index ubicada en el archivo <a href="https://docs.djangoproject.com/en/1.3/topics/http/views/">views</a>, la cual luce así:<br />
<pre class="python" name="code">from django.shortcuts import render_to_response
def index(request):
return render_to_response("index.html")
</pre>
Al visitar la raíz del sitio, django llamara la función index pasando como argumento un objeto <a href="https://docs.djangoproject.com/en/1.3/ref/request-response/">request</a> que contiene información sobre la solicitud (datos POST y GET entre otros). La función index realizar algún proceso y retornar un objeto <a href="https://docs.djangoproject.com/en/1.3/ref/request-response/#httpresponse-objects">response</a>.<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjonPc_NPE7AFkRZMOMK6G2L_o5vyliaqf0gziGSse-UUdc6c-POBlsYQ04U7ov_dhGOXk-4bwTFuacilRydz8lhvsp4TsMH5kanZQdXH721faPsNu7v1ffIkWiEB64A7zdHkswEyzmqlsc/s1600/TemplateDoesNotExist.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjonPc_NPE7AFkRZMOMK6G2L_o5vyliaqf0gziGSse-UUdc6c-POBlsYQ04U7ov_dhGOXk-4bwTFuacilRydz8lhvsp4TsMH5kanZQdXH721faPsNu7v1ffIkWiEB64A7zdHkswEyzmqlsc/s1600/TemplateDoesNotExist.png" /></a></div>
<br />
Si iniciamos el servidor de desarrollo y entramos a la dirección localhost:8000 veremos un mensaje de error como el de la imagen ya que la plantilla <b>index.html</b> aun no existe, así que manos a la obra.<br />
<pre class="html" name="code"><!DOCTYPE HTML>
<html lang="es-ES">
<head>
<title>Mi Blog</title>
</head>
<body>
<!-- hasta aquí siempre es igual -->
<h1>Hola Django</h1>
</body>
</pre>
No muy útil, pero por el momento es todo lo que necesitamos. Imagine que creamos otras plantillas para post individuales, contacto, faq, quejas... debemos repetir la cabecera en cada una de ellas.<br />
<br />
El <a href="https://docs.djangoproject.com/en/1.3/topics/templates/">sistema de plantillas</a> de django permite crear <a href="https://docs.djangoproject.com/en/1.3/topics/templates/#id1">plantillas base</a> con bloques que luego pueden ser reemplazados por las plantillas que heredan de ésta; para tal efecto creamos la plantilla base.html:<br />
<pre class="html" name="code"><!DOCTYPE HTML>
<html lang="es-ES">
<head>
<title>Mi Blog</title>
</head>
<body>
{% block content %}
{% endblock %}
</body>
</pre>
Y editamos la plantilla index.html para que extienda base.html:<br />
<pre class="html" name="code">{% extends "base.html" %}
{% block content %}
<h1>Hola Django</h1>
{% endblock %}
</pre>
Ok, teniendo una idea general del sistema de plantillas, seguimos con la pagina inicial del blog empezando por editar el archivo views.py.<br />
<pre class="python" name="code">from django.shorcuts import render_to_response
from blog.models import Post
def index(request):
# recuperamos 5 primeras entradas
posts = Post.objects.filter()[:5]
return render_to_response("index.html", locals())
</pre>
Cada vez que visitemos a la raíz del blog se consultaran los primeros 5 post y se pasaran a la plantilla index.<br />
<pre class="python" name="code">{# plantilla index.html #}
{% extends "base.html" %}
{% block content %}
{% for post in posts %}
<h1><a href="/{{ post.id }}">{{ post.title }}</a></h1>
<p>{{ post.pub_date}}</p>
{{ post.body }}
{# esto no es muy eficiente* #}
{% for tag in post.tags.filter %}
<a href="/tags/{{ tag.tag }}">{{ tag.tag }}</a>
{% endfor %}
{% endfor %}
{% endblock %}
</pre>
<b>*</b> Extraer las etiquetas de esta forma no es eficiente ya que al llamar a filter (5 veces) se hace una nueva consulta a la base de datos, algunas soluciones se pueden <a href="http://stackoverflow.com/questions/5636482/django-optimizing-many-to-many-query">encontrar aquí</a>.<br />
<br />
Con esto la pagina principal esta lista. Sigamos con la pagina individual, veremos que al trabajar con django se sigue mas o menos el mismo patrón:<br />
<pre class="python" name="code"># archivo urls.py
urlpatterns = patterns('',
(r'^$', 'blog.views.index'),
# expresión regular que captura el numero después de post/
(r'^post/(?P<id>\d+)$', 'blog.views.single'),
)
</pre>
<pre class="python" name="code"># Archivo views.py
...
# la funcion recibe el numero capturado
def single(request, id=0):
# recuperar post según id
post = Post.objects.get(id=int(id))
return render_to_response("single.html", locals())
</pre>
<pre class="python" name="code">{# plantilla single.html #}
{% block content %}
<h1><a href="/{{ post.id }}">{{ post.title }}</a></h1>
<p>{{ post.pub_date}}</p>
{{ post.body }}
{# esto no es muy eficiente* #}
{% for tag in post.tags.filter %}
<a href="/tags/{{ tag.tag }}">{{ tag.tag }}</a>
{% endfor %}
{% endblock %}
</pre>
Notan algo raro? el código de <i>single.html</i> e <i>index.html</i> luce muy similar; podemos eliminar el código repetido moviéndolo a otra plantilla (article.html) que luego <a href="https://docs.djangoproject.com/en/1.3/ref/templates/builtins/#include">incluimos</a> en estas.<br />
<pre class="python" name="code">{# plantilla article.html #}
</div>
<h1><a href="/{{ post.id }}">{{ post.title }}</a></h1>
<p>{{ post.pub_date}}</p>
{{ post.body }}
{# esto no es muy eficiente* #}
{% for tag in post.tags.filter %}
<a href="/tags/{{ tag.tag }}">{{ tag.tag }}</a>
{% endfor %}
</div>
</pre>
Ahora editamos las plantillas index.html y single.html para que utilicen article.html.<br />
<pre class="python" name="code">{# plantilla inde.html #}
{% extends 'base.html' %}
{% block main_content %}
{% for post in posts %}
{# pasamos la variable post como argumento #}
{% include 'include/article.html' with post=post %}
{% endfor %}
{% endblock %}
</pre>
<pre class="python" name="code">{# plantilla single.html #}
{% extends 'base.html' %}
{% block main_content %}
{% include 'include/article.html' %}
{% endblock %}
</pre>
Con esto damos por terminado esta parte. No se olviden de comentar.<br />
<br />
<b>P.D.</b>: Procurare tener la próxima parte mas rápido.<br />
<br />sney2002http://www.blogger.com/profile/11603966854374488214noreply@blogger.com2tag:blogger.com,1999:blog-4027885769698367919.post-28362885844828565562011-12-09T07:30:00.001-05:002011-12-09T08:13:47.292-05:00Actualización mget<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiCTNPq1JNutDZTRZ4MY1sZKh8kj-pNCV8vim9K2dxKsRG-N1UBp-Lw2RF43QvUmmvCkPcADCQhNcDpu83UIru6fTQ4hAeKqIO5CYP5sgVoJb_FLGaE3juBmb3CPjv22S-i9Og-_Tk6jbP4/s1600/mget.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="177" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiCTNPq1JNutDZTRZ4MY1sZKh8kj-pNCV8vim9K2dxKsRG-N1UBp-Lw2RF43QvUmmvCkPcADCQhNcDpu83UIru6fTQ4hAeKqIO5CYP5sgVoJb_FLGaE3juBmb3CPjv22S-i9Og-_Tk6jbP4/s200/mget.png" width="200" /></a></div>
Debido al rediseño de megaupload <a href="http://www.novatoz.com/2011/01/script-descarga-automatica-de_24.html">mget</a> ha dejado de funcionar, el problema se debe específicamente a que el link de descarga ya no se identifica con el id <i>downloadlink </i>sino con la clase <i>download_regular_usual, </i>ademas el tiempo de espera ahora es de 60 segundos.<br />
<br />
Por el momento no tengo como arreglar la versión para windows pero la de linux <a href="https://gist.github.com/959958">ya esta lista</a>, solo hay que reemplazar el archivo antiguo y dar permisos de ejecución.sney2002http://www.blogger.com/profile/11603966854374488214noreply@blogger.com0tag:blogger.com,1999:blog-4027885769698367919.post-65639994428495991682011-11-26T16:15:00.001-05:002011-11-30T11:11:02.285-05:00Creando un blog con django - parte 2<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjjfUg0RZ8U-hqZK2fS0oZyaIlmNhCIED2Hft3hZyyHIWzjmWQseI2nbPLG6A9GO-VNTRrhVv340cVl-zf8z7fQElPF_ByL2mkhPUWqvrEzi5IDP4oDYEb7jsD47lw6h0rTE_de1QQHucrE/s1600/django-logo.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="145" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjjfUg0RZ8U-hqZK2fS0oZyaIlmNhCIED2Hft3hZyyHIWzjmWQseI2nbPLG6A9GO-VNTRrhVv340cVl-zf8z7fQElPF_ByL2mkhPUWqvrEzi5IDP4oDYEb7jsD47lw6h0rTE_de1QQHucrE/s320/django-logo.png" width="320" /></a></div>
<br />
Llego el momento de crear los modelos, pero antes analicemos los datos que necesitamos guardar. Nuestro blog tendrá post, comentarios y tags, los que a su vez se componen de:<br />
<ul>
<li><b>Post</b>: fecha, titulo, contenido y etiquetas.</li>
<li><b>Etiqueta</b>: nombre.</li>
<li><b>Comentario</b>: autor, fecha, contenido.</li>
</ul>
Ahora bien, una etiqueta puede estar relacionada con varios post, así como un post tener varias etiqueta, por lo que tienen una relación many to many. Los comentarios por otro lado solo pertenecen a un post por lo que necesitan una <a href="http://es.wikipedia.org/wiki/Clave_for%C3%A1nea">clave foránea</a> que lo relacione con el respectivo post.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiPlSAbzkwopAStmpHBQT0EKScPoJNP9_wbDaTL1kUzq5cpTHRwj9w43FcKds02N1_n_Lm6yOCX9JbjMn4MZCBUc3Hhxs64dpNZ7bde5dCa531Y1ps3EZULBvsLMhS89DECsYt5Ew3FWABW/s1600/tags.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="153" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiPlSAbzkwopAStmpHBQT0EKScPoJNP9_wbDaTL1kUzq5cpTHRwj9w43FcKds02N1_n_Lm6yOCX9JbjMn4MZCBUc3Hhxs64dpNZ7bde5dCa531Y1ps3EZULBvsLMhS89DECsYt5Ew3FWABW/s320/tags.png" width="320" /></a></div>
Teniendo claro el modelo de la base de datos, es hora de escribir el código necesario para crear las tablas, afortunadamente django nos facilita la tarea. Editamos el archivo models.py ubicado en la carpeta de la aplicación:<br />
<pre class="python" name="code"># 1
from django.db import models
# 2
class Tag(models.Model):
# 3
tag = models.CharField(max_length=100, unique=True, db_index=True)
# 4
def __unicode__(self):
return self.tag
</pre>
Veamos que hicimos:<br />
<ol>
<li>Importamos el modulo models.</li>
<li>Creamos una subclase de models.Model, </li>
<li>Cada columna de la bd es representada por un atributo, en este caso<tag 0x39294="" in="" memory=""> tag es de tipo caracter (<a href="https://docs.djangoproject.com/en/1.3/ref/models/fields/#charfield">CharField</a>), existen <a href="https://docs.djangoproject.com/en/1.3/ref/models/fields/">otros</a> tipos de campo según el valor almacenar. Los argumentos describen las <a href="https://docs.djangoproject.com/en/1.3/ref/models/fields/#field-options">propiedades del campo</a> como la cantidad de caracteres que puede contener (<b>max_length</b>), que no se permiten valores repetidos (<b>unique</b>), ademas de hacer que se indexe (<b>db_index</b>) ya que es comun hacer búsquedas como: buscar todos los post con tag django.</tag></li>
<li><tag 0x39294="" in="" memory="">el método __unicode__ permite dar un nombre mas significativo a las instancias, este es utilizarlo en el <a href="https://docs.djangoproject.com/en/1.3/ref/contrib/admin/">sitio administrativo</a>.</tag></li>
</ol>
Sabiendo esto es fácil crear las demás tablas:<br />
<pre class="python" name="code"># post
class Post(models.Model):
# la relación many to many entre Post y Tag
tags = models.ManyToManyField(Tag, blank=True)
title = models.CharField(max_length=200)
body = models.TextField()
# fecha de publicación, se agrega fecha automáticamente
pub_date = models.DateField(auto_now_add=True)
def __unicode__(self):
return self.title
class Comment(models.Model):
# Cada comentario pertenece a un post
post = models.ForeignKey(Post)
# nombre es opcional (blank) y por defecto es Anonimus (default)
name = models.CharField(max_length=100, blank=True, default="Anonimus")
# el cuerpo del comentario
body = models.TextField(verbose_name="comment")
# fecha de publicación, se agrega fecha automáticamente
date = models.DateField(auto_now_add=True)
def __unicode__(self):
return "Por " + self.name + " en " + self.post.title
</pre>
La <a href="https://docs.djangoproject.com/en/1.3/ref/models/fields/#django.db.models.ForeignKey">clave foránea</a> y relación <a href="https://docs.djangoproject.com/en/1.3/ref/models/fields/#django.db.models.ManyToManyField">many to many</a> se crea en una simple linea de código. Ahora sincronizamos nuevamente y entramos al interprete de python para introducir información en la base de datos:<br />
<pre class="bash" name="code">$ python manage.py syncdb
$ python manage.py shell
</pre>
<pre class="python" name="code"># importamos los modelos
from blog.models import Tag, Post
# aun no hay tags así que creamos una
django = Tag(tag="django")
django.save()
# creamos un post
new_post = Post(title="mi primer post", body="El primer post que realizo")
new_post.save()
# agregamos un tag y volvemos a guardar
new_post.tags.add(django)
new_post.save()
</pre>
Como vemos, para <a href="https://docs.djangoproject.com/en/1.3/topics/db/queries/#creating-objects">introducir un registro</a> en la base de datos creamos una instancia de la clase adecuada y seguidamente llamamos el método save, ¿pero como <a href="https://docs.djangoproject.com/en/1.3/topics/db/queries/">recuperamos un registro</a>? existen tres métodos para consultar la base de datos:<br />
<ul>
<li><b>get</b>: retorna un solo registro, lanza una excepción si la consulta retorna cero o mas de un registro.</li>
<li><b>filter</b>: retorna un array con los registros que coinciden la consulta o un array vacío.</li>
<li><b>exclude: </b>similar a filter pero retorna los registros que no cumplen con la consulta.</li>
</ul>
<div>
Estos métodos se acceden a través del <a href="https://docs.djangoproject.com/en/1.3/ref/models/querysets/">atributo objects</a> que poseen las subclases de models.Model.</div>
<pre class="python" name="code"># recuperar todos los post
Post.objects.filter()
# recuperar los primeros 10
Post.objects.filter()[:10]
</pre>
Para <a href="https://docs.djangoproject.com/en/1.3/topics/db/queries/#field-lookups">refinar la búsqueda</a> usamos argumentos que corresponden con los campos de la tabla:<br />
<pre class="python" name="code"># post segun titulo
Post.objects.filter(title="mi primer post")</pre>
Los campos que son claves foraneas (Post.tags, Comment.post) son algo especial: podemos seguir la relación escribiendo el nombre de la columna seguido de dos guiones bajos (_), pudiendo acceder el modelo al que apunta dicha relación. veamos un ejemplo:<br />
<pre class="python" name="code"># post con tag django
Post.objects.filter(tags__tag="django")
</pre>
Al usar <i>tags__</i> ya no estamos mas en Post sino en Tag, ahora podemos consultar los campos de esta tabla (tag). otro ejemplo:<br />
<pre class="python" name="code"># comentarios de posts con tag django
Comment.objects.filter(post__tags__tag="django")
</pre>
Seguimos la relación a Post (<i>post__</i>) y consultamos la columna <i>tags__</i> lo que nos lleva a Tag donde finalmente consultamos el atributo tag o_O.<br />
<br />
Con esto tenemos lista la base de datos, en la próxima parte veremos como crear plantillas para visualizar estos datos.<br />
<br />
Muchas gracias por su atención, no olviden dejar sus sugerencias, inquietudes y amenazas en los comentarios, hasta la próxima.sney2002http://www.blogger.com/profile/11603966854374488214noreply@blogger.com0tag:blogger.com,1999:blog-4027885769698367919.post-83372567282717181472011-11-25T10:47:00.001-05:002011-11-25T12:34:48.294-05:00Extrae imágenes de archivos PowerPoint<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjm6wbuN5_gaQcGWRZtXb14uW2zuZj2KO9caEMj8vyL79F0pQWVGr1vwuif3EK9L_St_cRZfF5WWW9CbO3OGR1EVsIstW0Aq2c4KDiUgcYcI9BWPkW0wxM_2wk7q3ZPe-dEhizEvMXtj6Rn/s1600/2-demo-mac.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="248" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjm6wbuN5_gaQcGWRZtXb14uW2zuZj2KO9caEMj8vyL79F0pQWVGr1vwuif3EK9L_St_cRZfF5WWW9CbO3OGR1EVsIstW0Aq2c4KDiUgcYcI9BWPkW0wxM_2wk7q3ZPe-dEhizEvMXtj6Rn/s320/2-demo-mac.png" width="320" /></a></div>
<br />
<b>Versión corta</b>: pues que escribí una aplicación para extraer imágenes de archivos PowerPoint y la pueden descargar de <a href="http://goo.gl/3eH3L">aquí</a>.<br />
<br />
<span class="Apple-style-span" style="font-size: large;"><b>Versión larga</b>:</span><br />
<br />
Nuca he entendido esa manía de algunas personas de estar enviando PowerPoints a diestra y siniestra por lo que todo el que llega termina en la papelera sin leerlo XD.<br />
<br />
Un conocido al que si le encantan me pidió un programa para extraerles las imágenes. <a href="http://www.google.com.co/search?sourceid=chrome&ie=UTF-8&q=powerpoint+image+extractor">Encontré dos</a> pero ninguno funciono, así que escribí un script en python que pueden encontrar <a href="http://gist.github.com/1371551">en github</a> (<b>advertencia</b>: visualizar el código puede causar sangrado de ojos). Ya estando en ello le cree interfaz gráfica (<i>la primera que hago</i>). Eso sí, es algo.... feeeeaaaa, pero en el proceso puede <strike>recuperar imágenes porno</strike> aprender algo sobre threads, instaladores y otras cosas de las que escribiré mas adelante, por el momento he aquí el cuerpo del delito: <a href="http://goo.gl/3eH3L">PowerPoint Extractor</a>.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="http://goo.gl/3eH3L" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh-F40HFc8wSim0bB0uBPtgtbwNbDRospqJuhwnw7qwFvsKSvZl9aXqmpxcnAxFj9l2JD9dmfH3dwKbgJmy8J0m_3IZgaRgwnxzgG43N6vRSabEEgTwMGlMr4LxP5c2cb5wc9xj9jsmB0CN/s1600/Screenshot_2.png" /></a></div>
<br />
Eso es todo. no olviden seguirme en <a href="http://www.twitter.com/sney2002">twitter</a> y/o suscribirse al feed del blog.<br />
<br />
<b>Actualización</b>: En github pueden encontrar un <a href="https://github.com/sney2002/PPTExtractor">modulo para dicho fin</a> que funciona tanto en Windows como en Linux.sney2002http://www.blogger.com/profile/11603966854374488214noreply@blogger.com0tag:blogger.com,1999:blog-4027885769698367919.post-42029942784060070462011-11-24T17:25:00.000-05:002012-04-26T10:57:39.205-05:00Creando un blog con django - parte 1<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhlzv2BdzEmb8QjpYppI_ERew1Tnzosx8lh5lK1tSPqfF3fdlvVXjHnCynO2WP_l6gG1Y_J3070gHwshiLTM4yUPjWrMS87p1dFlJEFUrgo43XqaqUUdiW0-F4APkxmhAW7ziDvDkRnYHfk/s1600/if_django_a_1.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="224" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhlzv2BdzEmb8QjpYppI_ERew1Tnzosx8lh5lK1tSPqfF3fdlvVXjHnCynO2WP_l6gG1Y_J3070gHwshiLTM4yUPjWrMS87p1dFlJEFUrgo43XqaqUUdiW0-F4APkxmhAW7ziDvDkRnYHfk/s320/if_django_a_1.jpg" width="320" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">the killer framework</td></tr>
</tbody></table>
Primero quiero dejar claro que no soy un experto en <a href="http://www.djangoproject.com/">django</a>, así que si cometo algún error no duden en hacérmelo saber.<br />
<br />
Este tutorial se divide en varias partes:<br />
<ol>
<li>instalación y configuración</li>
<li><a href="http://www.novatoz.com/2011/11/creando-un-blog-con-django-parte-2.html">creación de modelos</a></li>
<li><a href="http://www.novatoz.com/2011/12/creando-un-blog-con-django-parte-3.html">creación de vistas</a></li>
<li><a href="http://www.novatoz.com/2012/04/creando-un-blog-con-django-parte-4.html">formularios</a></li>
</ol>
<div>
<span class="Apple-style-span" style="font-size: large;">Instalación y configuración</span></div>
<br />
Procedemos a descargar la ultima versión de django e instalarla como dice <a href="https://www.djangoproject.com/download/">la pagina oficial</a>:<br />
<pre class="bash" name="code">$ wget http://www.djangoproject.com/download/1.3.1/tarball/
$ tar xzvf Django-1.3.1.tar.gz
$ cd Django-1.3.1
$ sudo python setup.py install
</pre>
Ahora haciendo uso del <a href="https://docs.djangoproject.com/en/1.3/ref/django-admin/">script de administración</a> creamos nuestro proyecto:<br />
<pre class="bash" name="code">$ django-admin startproject tutorial
</pre>
Esto crea el directorio tutorial, con los siguientes archivos:<br />
<ul>
<li><b>manage.py</b>: script para administrar el proyecto (sincronizar bd, crear aplicaciones, iniciar servidor...)</li>
<li><b>urls.py</b>: las urls admitidas.</li>
<li><b>settings.py</b>: configuración del proyecto (aplicaciones instaladas, directorios de plantillas, bd...)</li>
</ul>
Lo primero que haremos es crear nuestra aplicación (blog) usando el script manage.py<br />
<pre class="python" name="code">$ python manage.py startapp blog</pre>
Este comando crea el directorio blog con varios archivos en su interior, los cuales analizaremos en otra ocasión. Para terminar editamos el archivo settings.py e incluir la configuración de la base de datos y agregamos nuestra aplicación a la lista de aplicaciones instaladas:<br />
<pre class="python" name="code">DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': 'myblog',
# lo demás no es necesario para una bd sqlite
'USER': '',
'PASSWORD': '',
'HOST': '',
'PORT': '',
}
}
...
...
INSTALLED_APPS = (
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.messages',
'django.contrib.staticfiles',
'blog', # Nuestra aplicación
)</pre>
Sincronizamos la base de datos para crear las tablas necesarias:<br />
<pre class="bash" name="code">$ python manage.py syncdb</pre>
Se pedirán algunos datos para crear la cuenta de administrador. Luego iniciamos el servidor de desarrollo con:<br />
<pre class="bash" name="code">$ python manage.py runserver
</pre>
Y listo, si entramos a la dirección localhost:8000 veremos una flamante pagina web confirmando que django esta correctamente configurado y listo para iniciar a desarrollar nuestra aplicación.<br />
<br />
Eso fue todo por ahora, muchas gracias por su atención y no olviden estar pasando para la segunda parte.sney2002http://www.blogger.com/profile/11603966854374488214noreply@blogger.com5tag:blogger.com,1999:blog-4027885769698367919.post-87008728870604035912011-11-16T18:44:00.001-05:002011-11-17T07:14:11.262-05:00Ejemplo método find de canvas-event<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhpUN4HFWzwZV2mSdO1Y1wHKWC4hh0iZW26HffX0lmVfvoFByjO0Dqv2IfMsLH3YGENjRAGYQYf6j2NIdv66KjPCAlew43DwiMrEnxIQJLKMn_fRjCbqP_Hncz7bWrvHudn4R1iVnPlgUM_/s1600/find.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="164" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhpUN4HFWzwZV2mSdO1Y1wHKWC4hh0iZW26HffX0lmVfvoFByjO0Dqv2IfMsLH3YGENjRAGYQYf6j2NIdv66KjPCAlew43DwiMrEnxIQJLKMn_fRjCbqP_Hncz7bWrvHudn4R1iVnPlgUM_/s320/find.png" width="320" /></a></div>
<br />
Varias personas han estado buscando como usar el método find de <a href="http://github.com/sney2002/canvas-event-js">canvas-event</a>, por lo que dejo un ejemplo de su uso mientras termino la <a href="http://sney2002.github.com/canvas-event-js/">documentación</a>.<br />
<br />
<table cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: right; text-align: right;"><tbody>
<tr><td style="text-align: center;"><span class="Apple-style-span" style="clear: right; margin-bottom: 1em; margin-left: auto; margin-right: auto;"><a href="http://www.blogger.com/goog_502546919"><img border="0" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhXWrOvW8vxXvmeDMH80YnVOSZpiyxz2v7-nlc9b6cv6dI59Y34RT2KDa1OplkxmNvUYNnuUB5DAxtqtMdC2D0B7ufwoYCU2ZM762XTufPKeewt0l8zvz4jtfBsjCN9aD1mwS7r1h4i5Qjf/s200/Spanish-Kmines.png" width="143" /></a></span></td></tr>
<tr><td class="tr-caption" style="text-align: center;"><a href="http://es.wikipedia.org/wiki/Archivo:Spanish-Kmines.png">Interfaz del programa Kmines</a></td></tr>
</tbody></table>
Usando el método <b>find</b>, es posible seleccionar un grupo de objetos y aplicarles un método o manejador de evento. para esto debemos usar un selector como:<br />
<ul>
<li><b>tipo</b>: Corresponde con el nombre del método usado para crear el objeto (<i>circle, rect, line</i>...).</li>
<li><b>id</b>: Un nombre que podemos dar a un objeto con el fin de diferenciarlo de otros del mismo tipo.</li>
</ul>
Imagine estar creando un <a href="http://es.wikipedia.org/wiki/Buscaminas">buscaminas</a>, el campo esta lleno con varios rectángulos: unos son minas y otros no. Todos los rectángulos tienen el mismo color, pero solo los rectángulos <b>mina</b> explotan y terminan el juego, mientras los demás aumentan el puntaje del jugador.<br />
<br />
<b>Nota</b> Al igual que en css, el símbolo <b>#</b> indica que mina y safe son id.<br />
<pre class="js" name="code">for (var i = 0; i < 5; i++) {
ce.rect(i*10, 0, 10).setId("mina");
}
for (var i = 0; i < 5; i++) {
ce.rect(i*10; 10; 10).setId("safe");
}
// selector tipo: todos los rectángulos, tanto #mina como #safe
ce.find("rect").attr("fill", "gray");
// selector id: solo rectángulos #mina
ce.find("#mina").click(function(c, e) {
alert("Boom!");
alert("Game Over");
});
// selector id: solo rectángulos #safe
ce.find("#safe").click(function(c, e) {
puntos += 1;
});
</pre>
Por otro lado el método getAll (<a href="http://github.com/sney2002/canvas-event-js">solo en repo de github</a>) retorna un array con los objetos que coinciden con el selector:<br />
<pre class="js" name="code">// encontrar y retornar los objetos rect dibujados hasta el momento
var rects = ce.findAll("rect");</pre>
<pre class="js" name="code">// rects ahora es igual a [Rect, Rect, Rect...]
// obtener el primer circulo dibujado
var first_circle = ce.getAll("circle")[0];
</pre>
Si tienen dudas o sugerencia pueden dejarlas en los comentarios, prometo tenerlas en cuenta.sney2002http://www.blogger.com/profile/11603966854374488214noreply@blogger.com0tag:blogger.com,1999:blog-4027885769698367919.post-43006523985471505112011-11-15T09:43:00.001-05:002011-11-15T16:02:15.696-05:00Lecciones aprendidas<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiu5rf8uyCBwOz7wh2Y59RzggDPbVuRuCMmqbNB01zPj-k9IZUiFTuJX1veKfkZoqp0tmP-W_ZU65MHLwzwsZU5C5yehwxqXMpakzigt7oPUGghDPRgSr0H3IeFtjcy5KU2JNTvpSVOJqUw/s1600/idea_bulb.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiu5rf8uyCBwOz7wh2Y59RzggDPbVuRuCMmqbNB01zPj-k9IZUiFTuJX1veKfkZoqp0tmP-W_ZU65MHLwzwsZU5C5yehwxqXMpakzigt7oPUGghDPRgSr0H3IeFtjcy5KU2JNTvpSVOJqUw/s320/idea_bulb.jpg" width="320" /></a></div>
Andando por <a href="http://news.ycombinator.com/">hacker news</a> (hago muy frecuente), encontré un <a href="http://instagram-engineering.tumblr.com/post/12651721845/instagram-engineering-challenge-the-unshredder">interesante desafió</a> de <a href="http://instagr.am/">instagram</a>, que consiste en crear un programa que tome una imagen cortada en tiras y la "reconstruya". <a href="http://news.ycombinator.com/item?id=3225911">leyendo los comentarios</a> parece que para otros era algo trivial, pero para mi era todo un reto y una oportunidad de poner en practica conceptos aprendidos en <a href="http://courses.csail.mit.edu/6.006/spring11/notes.shtml">alguna</a> <a href="http://courses.csail.mit.edu/6.006/spring11/lectures/lec01.pdf">lecturas</a>.<br />
<br />
Durante el proceso de <a href="http://dl.dropbox.com/u/5308045/unshredder/index.html">resolver este problema</a>, aprendí varias cosas:<br />
<br />
<b>Estudiar </b>siempre nuevos algoritmos, herramienta, librerías..., no intentando memorizar o convertirse en un "experto". Basta con entender los conceptos aplicados y hacer algunas practicas. <i>lo esencial es tener la capacidad de asociar estos conocimientos con nuevos problemas</i>, ya luego podremos retornar y re-estudiarlos en el momento que los necesitemos.<br />
<br />
<b>Nunca compararnos con otros</b>, al compararnos con otra persona pueden ocurrir dos cosas:<br />
<ul>
<li><b>Nos frustramos</b> pensando que no podremos llegar a ser tan buenos como EL(LA).</li>
<li><b>Dejemos de avanzar </b>al creernos superior a otros y pensar que con lo que sabemos es suficiente, recordemos que somos novatoz.</li>
</ul>
Por otro lado al <b>comparamos con nosotros mismos</b>, veremos lo que realmente hemos avanzado: hace unos dos años no entendía ni el funcionamiento de un loop while, y hoy puede resolver un puzzle que aunque trivial para otros, para mi era como escalar el Everest.<br />
<br />
Esto nos lleva a otro punto: <b>Asumir retos</b>, si nos quedamos auto contemplando lo bien que hacemos una tarea, no avanzamos, debemos desafiarnos. puede que fracasemos, pero esto no quiere decir que no aprendimos. Siempre aprendemos y lo hacemos mas de los errores que de los triunfos, si fracasamos, <i>creemos una lista con las causas y usémosla como temas a estudiar</i>.<br />
<br />
Bueno eso es todo (creo), has tenido una "revelación" como esta. compártela los comentarios.sney2002http://www.blogger.com/profile/11603966854374488214noreply@blogger.com0tag:blogger.com,1999:blog-4027885769698367919.post-46023919454481588242011-11-01T17:16:00.001-05:002011-11-01T18:13:00.214-05:00ConceptMapp - webapp para crear mapas conceptuales<div class="separator" style="clear: both; text-align: center;">
<a href="http://conceptmapp.com/"><img border="0" height="208" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjbboErLU3R1AjtFCbafnhmu8w3hk_atH1lDjHAewaZjyCpV5nUYGzb0ZtybcWk2_SD9C593izwxEjtIudsEf0mc_OqtC0ZI5DX69HHlIHhF90e-OgzV0AlP_W8EncCID8agDH3u4tGdewD/s320/screen-shot.png" width="320" /></a></div>
Después de varios meses de posponerlo, por fin esta disponible <i>mi primera</i> aplicación web: <a href="http://conceptmapp.com/">ConceptMapp</a>, una herramienta para la creación de mapas conceptuales completamente gratis.<br />
<br />
La idea nació luego de estar experimentando con el elemento canvas, entonces me acorde de <a href="http://cmap.ihmc.us/">Cmaptools</a>, uno de los mejores programas que he utilizado para crear mapas conceptuales, pero que a mi parecer tiene los siguientes inconvenientes:<br />
<ul>
<li>Consume muchos recursos.</li>
<li>No es muy común: no se puede esperar encontrarlo instalado en otros computadores diferentes al personal, lo que impide realizar cambios cuando mas se necesitan.</li>
</ul>
Con ConceptMapp intento resolver estos problema, permitiendo acceder y editar mapas conceptuales desde cualquier computador con un navegador web moderno (a.k.a no ie <= 8) y una conexión a Internet, ademas de ser liviano<b>*</b>.<br />
<br />
Si les parece útil, encuentran algún error o tienen sugerencias pueden dejarlas en los comentarios o usando el <a href="http://conceptmapp.com/contact">formulario de contacto</a>.<br />
<br />
<b>*</b> Probado en un PC de: 1 GB de RAM con procesador de 1.8 GHz.sney2002http://www.blogger.com/profile/11603966854374488214noreply@blogger.com0Puerto Carreño, Departamento del Vichada, Colombia6.184765 -67.4885035.1745054999999995 -68.7519305 7.1950245 -66.225075499999988tag:blogger.com,1999:blog-4027885769698367919.post-48832057529738080142011-09-23T18:40:00.000-05:002011-09-26T08:55:16.041-05:00mget ahora para windows<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiCTNPq1JNutDZTRZ4MY1sZKh8kj-pNCV8vim9K2dxKsRG-N1UBp-Lw2RF43QvUmmvCkPcADCQhNcDpu83UIru6fTQ4hAeKqIO5CYP5sgVoJb_FLGaE3juBmb3CPjv22S-i9Og-_Tk6jbP4/s1600/mget.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="177" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiCTNPq1JNutDZTRZ4MY1sZKh8kj-pNCV8vim9K2dxKsRG-N1UBp-Lw2RF43QvUmmvCkPcADCQhNcDpu83UIru6fTQ4hAeKqIO5CYP5sgVoJb_FLGaE3juBmb3CPjv22S-i9Og-_Tk6jbP4/s200/mget.png" width="200" /></a></div>
<b>Actualización</b>: se incluyeron las dependencias del wget que faltaban, que pena u_u.<br />
<br />
Un amigo me pidió que le pasara el programa con el que <a href="http://gist.github.com/1150336">descargo de megaupload</a>, pero en cuanto le dije que tenia que:<br />
<ol>
<li>Descargar e instalar python 2.6.</li>
<li>Descargar e instalar wget para Windows.</li>
<li>Poner los directorios de wget y python en el PATH de Windows.</li>
</ol>
<div>
En seguida se desanimo (no se por que XD), así que me pase toda una tarde mejorando un poco el programa e intentando convertirlo en un ejecutable usando <a href="http://www.pyinstaller.org/">pyinstaller</a> y he aquí el resultado:</div>
<div>
<ul>
<li><a href="http://dl.dropbox.com/u/5308045/scripts/mget-win32.zip">mget para windows</a></li>
</ul>
<div>
Basta con descomprimir los archivos en la carpeta windows y a descargar de megaupload. Lo probé en varias maquinas con Windows XP y funciono correctamente, pero si tienen algún problema no duden en dejarlo en los comentarios.</div>
</div>
<div>
<br />
Su uso es muy sencillo, abren la consola de comandos y:</div>
<pre class="bash" name="code">mget -c url-con-link-de-megaupload
mget -i archivo-con-links
mget link1 link2 link3 ...
</pre>
Para una próxima explico como fue lo de convertir el script en ejecutable, algo muy util para compartir los programas que hagamos con amigos.<br />
<br />
Pueden seguirme en <a href="http://www.twitter.com/sney2002">twitter</a> o agregarme en <a href="http://plus.google.com/104412879063989278329">Google+</a>sney2002http://www.blogger.com/profile/11603966854374488214noreply@blogger.com2Puerto Carreño, Departamento del Vichada, Colombia6.184765 -67.4885035.1745054999999995 -68.7519305 7.1950245 -66.225075499999988tag:blogger.com,1999:blog-4027885769698367919.post-40162593974885430712011-09-09T16:04:00.000-05:002011-09-09T16:04:09.294-05:00Me decepciona el estudio<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhH-txxkmVWGofmz79VWabgL7uyqU2ZU-WWhQI36w1YT2aeOn7196FYOA1zXh9N23hAAmceMozNlBpHKmOCSPzpS3fP-jl-KR5djRyMVBZzJfYZQg1fbmPeA7HPblnQESEwdXi0_r6Ue4rI/s1600/leon-decepcionado.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhH-txxkmVWGofmz79VWabgL7uyqU2ZU-WWhQI36w1YT2aeOn7196FYOA1zXh9N23hAAmceMozNlBpHKmOCSPzpS3fP-jl-KR5djRyMVBZzJfYZQg1fbmPeA7HPblnQESEwdXi0_r6Ue4rI/s320/leon-decepcionado.jpg" width="320" /></a></div>
Viendo a mi hermano desesperado intentando grabarse un montón de formulas porque el día siguiente tenia una evaluación, me dí de cuenta que el sistema de "enseñanza" es una perdida de tiempo, los profesores no se preocupan por estimular a los estudiantes a aprender, sino por <i>embutirles</i> información. La única "motivación" que se tiene es la de pasar al siguiente curso.<br />
<br />
Recuerdo cuando estaba estudiando que aunque era muy bueno para el álgebra y la geometría, realmente no le veía mucha utilidad practica y luego de pasar la nota dejaba de estudiar, ya que no me interesaba profundizar el tema.<br />
<br />
Creo que la educación mejoraría si en lugar de hacer que los alumnos memoricen una gran cantidad de información, se hace que éstos ganen interés en el tema a estudiar.<br />
<br />
Por ejemplo: matemáticas - promedios. En la escuela vi promedios alrededor del 6 grado. fuera de hacer muchos ejercicios como el promedio de edades, precios... cosas que a alguien de esa edad no le hacen mucha gracia. pero que tal si se muestra algo interesante (para un niño). por ejemplo como gracias a los promedios se puede convertir una fotografía a color en una a blanco y negro.<br />
<ul>
<li><a href="http://dl.dropbox.com/u/5308045/wb/index.html">Promedios e imágenes a blanco y negro</a></li>
</ul>
tal vez alguien diga que es algo muy complicado para explicar a un estudiante de 6 grado, pero el objetivo es hacer que éste diga: "WOW las matemáticas son una bacaneria" y entonces se interese realmente en aprender y no solo en <i>ganar el año</i>.sney2002http://www.blogger.com/profile/11603966854374488214noreply@blogger.com1Puerto Carreño, Departamento del Vichada, Colombia6.184765 -67.4885035.1745054999999995 -68.7519305 7.1950245 -66.225075499999988tag:blogger.com,1999:blog-4027885769698367919.post-66760862528849994892011-08-24T19:14:00.001-05:002011-08-30T09:41:47.528-05:00Rehacer - Deshacer en javascript<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi5swo0vlcgdDofJya1x3JJKSfcP95CrX1mK4NtuP54tWNaRkQC7FLRrc4PHEE1bJ5yWgfHx-BE6Sf8RXvhZDgLpRjgIIwKekPJxEGXCGHaJtjmo1NTnfxgu-wLUorUPQ0aaWzPQbhf4IHF/s1600/CtrlV-CtrlX-Ctrl-Z.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi5swo0vlcgdDofJya1x3JJKSfcP95CrX1mK4NtuP54tWNaRkQC7FLRrc4PHEE1bJ5yWgfHx-BE6Sf8RXvhZDgLpRjgIIwKekPJxEGXCGHaJtjmo1NTnfxgu-wLUorUPQ0aaWzPQbhf4IHF/s200/CtrlV-CtrlX-Ctrl-Z.jpg" width="200" /></a></div>En este tutorial intentaré explicar la forma de implementar la funcionalidad de deshacer y rehacer, una característica muy usada en los programas de escritorio y que muchas aplicaciones web no tienen (y hace mucha falta ya que errar es de humanos).<br />
<a href="http://dl.dropbox.com/u/5308045/scripts/undo-redo.zip" style="background: green; color: white; display: block; font-size: 15px; margin: 10px auto 0; padding: 3px 10px; text-align: center; width: 150px;">Descargar código</a><br />
La idea consiste en tener objetos que representen las acciones (mover, eliminar, resaltar...), los cuales deben guardar el estado anterior del objeto afectado.<br />
<pre class="js" name="code">ChangeColorCommand = function(obj, color) {
this.obj = obj;
this.new_color = color;
// guardar estado anterior
this.prev_color = obj.style.backgroundColor;
}
ChangeColorCommand.prototype.execute = function() {
this.obj.style.backgroundColor = this.new_color;
}
ChangeColorCommand.prototype.undo = function() {
this.obj.style.backgroundColor = this.prev_color;
}
</pre>Cada objeto cuenta con dos métodos<br />
<ul><li><b>execute:</b> ejecuta la acción (dah)</li>
<li><b>undo:</b> deshace la acción valiéndose de los datos guardados previamente en el constructor.</li>
</ul>Las acciones son administradas por un objeto que mantiene dos <a href="http://es.wikipedia.org/wiki/Pila_(inform%C3%A1tica)">pilas</a> de acciones: las que se pueden deshacer y las que se pueden rehacer:<br />
<ul><li>cuando ejecuta una acción, la guarda en undo_stack y borra toda acción que se podía rehacer</li>
<li>cuando se deshace algo, saca el ultimo objeto de la pila undo_stack, ejecuta el método undo y guarda la acción en la pila redo_stack.</li>
</ul><pre class="js" name="code">CommandManager = function(max_undo) {
// máxima cantidad de acciones guardadas
max_undo = max_undo || 30;
// pilas de acciones
this.undo_stack = [];
this.redo_stack = [];
// ejecutar comando cmd
this.executeCommand = function(cmd){
cmd.execute();
// si se sobrepasa cantidad de acciones
// eliminar primer elemento
if (this.undo_stack.length &amp;gt;= max_undo) {
this.undo_stack.shift();
}
this.undo_stack.push(cmd);
this.redo_stack = [];
}
// deshacer acción
this.undoCommand = function() {
var cmd = this.undo_stack.pop();
// si existe acción
if ( cmd ) {
cmd.undo();
this.redo_stack.push(cmd);
}
}
}
</pre>Para realizar una acción creamos una instancia del objeto apropiado y la pasamos al CommandManager (previamente creado).<br />
<pre class="js" name="code">var UndoRedo = new CommandManager(),
box = document.getElementById("box");
// cambiar color a rojo
UndoRedo.executeCommand(new ChangeColorCommand(box, "red"));
// cambiar color a verde
UndoRedo.executeCommand(new ChangeColorCommand(box, "green"));
// cambiar de verde a rojo
UndoRedo.undoCommand();
// cambiar de rojo a blanco
UndoRedo.undoCommand();
</pre>Ahora solo resta crear mas acciones (mover, negrita, itálica...) dependiendo de la aplicación que estemos desarrollando.<br />
<br />
Eso es todo, espero les haya servido; si tienen alguna duda o critica no duden en dejarla en los comentarios.sney2002http://www.blogger.com/profile/11603966854374488214noreply@blogger.com4tag:blogger.com,1999:blog-4027885769698367919.post-22594500256879784952011-07-13T10:58:00.000-05:002011-07-13T10:58:38.796-05:00Objetivos<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhkGmiELP5-x1f3qOeAGCdrmwof5j4XCbRdB6OlPLSMrcMpEdvZr9O3YdmV5N5uYrbhQ2e6JaLtR3e_t0msyY7pd6iZNM3oAOdycTESqtrBXyJodJOeP7bn_McgGL9ph28xnUKsguvgBdrb/s1600/rambo.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhkGmiELP5-x1f3qOeAGCdrmwof5j4XCbRdB6OlPLSMrcMpEdvZr9O3YdmV5N5uYrbhQ2e6JaLtR3e_t0msyY7pd6iZNM3oAOdycTESqtrBXyJodJOeP7bn_McgGL9ph28xnUKsguvgBdrb/s1600/rambo.jpg" /></a></div>Luego de participado en el <a href="http://www.novatoz.com/2011/06/rumbo-campus-party.html">campus party</a>, me di cuenta de mi total desconocimiento en temas importantes en la computación, por ejemplo en el desafió de desarrollo con solo saber un algoritmo de búsqueda, habría logrado un mejor puesto. Así que a partir de ahora pienso estudiar - y practicar - algunos temas mas "profundos", en lugar de solo centrarme en el desarrollo web y uso de frameworks, así como por el momento centrarme en aprender python y dejar de lado C.sney2002http://www.blogger.com/profile/11603966854374488214noreply@blogger.com0tag:blogger.com,1999:blog-4027885769698367919.post-53437285456990972312011-06-24T15:47:00.000-05:002011-06-24T15:47:10.056-05:00Rumbo a campus party<div class="separator" style="clear: both; text-align: center;"></div><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi8wKP9ugclnXaO6iJE6guvh03RFgW3lQcx36RxaoiKRVgJqeaqA_IKRUVZ0CZ6Dg_4ndgh-UtlmB14cxzLqBGluXMwCoX0yz1Prhrr0RzKWvzWPPXWuYbKp4wFj8jmnvsdqMFK6j_nDE2C/s1600/buckaroo-banzai_l-420x315.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi8wKP9ugclnXaO6iJE6guvh03RFgW3lQcx36RxaoiKRVgJqeaqA_IKRUVZ0CZ6Dg_4ndgh-UtlmB14cxzLqBGluXMwCoX0yz1Prhrr0RzKWvzWPPXWuYbKp4wFj8jmnvsdqMFK6j_nDE2C/s320/buckaroo-banzai_l-420x315.jpg" width="320" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;"><b>¿Como? ¿que voy a campus party gratis?</b></td></tr>
</tbody></table>Pues eso, por fin puedo ir a campus party y lo mejor de todo: <i>gratis</i>. iré en representación del SENA Vichada a hacer una presentación de <a href="http://code.google.com/p/canvas-event-js">canvas-event</a>, la noticia me sorprendio tanto que solo lo creí hasta el día que fui a comprar los boletos de avión.<br />
<br />
P.D.: Estaré publicando en <a href="http://www.twitter.com/sney2002">twitter</a> lo que me parezcan importantes.sney2002http://www.blogger.com/profile/11603966854374488214noreply@blogger.com0tag:blogger.com,1999:blog-4027885769698367919.post-89488932900487963052011-06-15T17:59:00.001-05:002011-06-15T18:01:17.181-05:00No soy picado ni raro, soy introvertido<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiTMk7zCYn5LWM7u66VXprQdyDMViT32udntejxm9cGKJeyDCUsNDeQQfzyu06Dt_VGhXH1irAUiAVkZKizXh-Mn2-agjF9JOQ8i0HziXt2McRNZGEIv4f6pimne_U5rafJohNcrvgdyjYE/s1600/introverted-depressed-woman-seeks-penguin.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiTMk7zCYn5LWM7u66VXprQdyDMViT32udntejxm9cGKJeyDCUsNDeQQfzyu06Dt_VGhXH1irAUiAVkZKizXh-Mn2-agjF9JOQ8i0HziXt2McRNZGEIv4f6pimne_U5rafJohNcrvgdyjYE/s1600/introverted-depressed-woman-seeks-penguin.jpg" /></a></div>Andando por la web me encontré este <a href="http://jerrybrito.org/post/6114304704/top-ten-myths-about-introverts">interesante articulo</a>, en donde se desmienten algunos de los mitos que se tienen sobre las personas introvertidas, está en perfecto ingles, pero luego de algo de búsqueda encontré una <a href="http://biblioteca-de-babel.heroku.com/10-mitos-acerca-de-los-introvertidos">traducción decente</a>.<br />
<br />
La verdad me parece muy acertado todo, pero resalto los siguientes puntos:<br />
<br />
<div><b>Mito #1 - A los introvertidos no les gusta hablar</b></div><div><div>Esto no es cierto. Es solo que los introvertidos <i>no hablan a menos que tengan algo que decir</i>. Odian la charla. Haz que un introvertido se ponga a hablar acerca de algo que le interesa, y no se callará por días<br />
<br />
<b>Mito #2 - Los introvertidos son tímidos</b><br />
La timidez no tiene nada que ver con ser un introvertido. A los introvertidos no necesariamente les asusta la gente. Lo que necesitan es una razón para interactuar. <i>No interactúan solo por interactuar</i>. Si quieres hablar con un introvertido, solo empieza a hablar. No te preocupes por ser educado.<br />
<br />
<b>Mito #6 - Los introvertidos siempre quieren estar a solas</b><br />
Los introvertidos están perfectamente cómodos con sus propios pensamientos. Piensan mucho. <i>Sueñan despiertos</i>. Les gusta tener problemas en los cuales trabajar, rompecabezas que resolver. Pero también pueden sentirse increiblemente solos si no tienen a nadie con quién compartir sus descubrimientos. Ansían una conexión auténtica y sincera con UNA PERSONA a la vez<br />
<br />
<b>Mito #7 - Los introvertidos son extraños</b><br />
Los introvertidos suelen ser individualistas. No siguen a la multitud. Preferirían ser valorados por sus maneras originales de vivir. Piensan por sí mismos y, por ello, frecuentemente desafían las normas. <i>No toman la mayoría de las decisiones basadas en lo que es popular o está de moda</i>.<br />
<br />
<b>Mito #9 - Los introvertidos no saben cómo relajarse y divertirse</b><br />
Los introvertidos típicamente se relajan en casa o en la naturaleza, no en lugares públicos concurridos. Los introvertidos no son buscadores de emociones fuertes ni adictos a la adrenalina. Si hay mucha charla y ruido, se encierran. Sus cerebros son muy sensibles al neurotransmisor llamado dopamina. Los introvertidos y los extrovertidos tienen diferentes vías neuronales dominantes. Simplemente consúltalo.<br />
<br />
<b>Mito #10 - Los introvertidos pueden arreglarse a sí mismos y volverse extrovertidos</b><br />
... hay todavía abundantes técnicas que un extrovertido puede aprender para interactuar con un introvertido. (Sí, puse al revés esos dos términos a propósito para mostrarte cuán parcializada está nuestra sociedad). Los introvertidos no pueden "arreglarse a si mismos" y <i>merecen respeto por su temperamento natural</i> y contribuciones a la raza humana...<br />
<br />
Como ven, los introvertidos no somos <a href="http://localspanish.com/dictionary/slang/17771-picado-o-picao">picados</a> o raros por el hecho de no hablar mucho ni salir de rumba, es simplemente que como a todos, hay cosas que no nos llenan.</div></div>sney2002http://www.blogger.com/profile/11603966854374488214noreply@blogger.com0