Wednesday, May 21, 2014

Quick tip: CSS for Hanging indentation


Sometimes you need to achieve a fancy effect on the first line of a paragraph, so that just the first line is indented to the left and the other lines be displayed a little bit to the right. Even, I discovered that it has a name 'Hanging Indent':

This is the first line of a very very very very very
    very very very very very very very very very very
    long paragraph. And there is more than one sentence.

    In fact we could reach almost a dozen.


Just apply a combination of a negative text-indent and padding-left (or margin-left). Ex:

p {
  text-align: left;
  text-indent: -25px;
  padding-left: 25px;
}


There is a 'first-letter' property in css that gives you more customization @see this

Monday, May 19, 2014

Quick tip: optimize images from command line

I use this trick for quickly optimize images. Note, I use ubuntu:

just install these tools

sudo apt-get install optipng jpegoptim

and now you can do (beware: input is overwritten)

optipng file.png 
jpegoptim file.jpg

both tools work in the same way

jpegoptim [ options ] filenames
optipng [options] files



to convert between formats, you can use imageMagick

sudo apt-get install imagemagick

and now

convert input.png output.jpg
convert input.jpg output.png 

Profit!

Sunday, March 23, 2014

Handy enhancement for ruby String: Defaults to default text

In my code , I repeat a lot this pattern: if the string is blank / empty, return a default text, otherwise return the original text.

final_text = text.blank? ? "default text" : text content_tag :h1, text.blank? ? "default text" : text, :class => 'mega'


I discovered this little gem in Stackoverflow (I lost the original source), you only need to reopen the String class, in an intializer, or decorator.

class String

  def defaults_to(what)
    self.strip!
    self.blank? ? what : self
  end


end

and, now you can use it in a readable form

text.defaults_to("default text")


Saturday, March 1, 2014

Concerns in Rails (or how to reuse code)

I have been doing some experiments with rails Concerns . While they have been widely publicited for Rails 4, you can use them in Rails 3 as well.

They are like the ruby standard include / extend pattern, but taking care of dependencies. In any case it would be easy to go back to the 'raw' include / exclude game.

This is an example of how to use them.

 require 'active_support/concern'

module Concerns

  module DummyConstants

  end

  module Dummy
    extend ActiveSupport::Concern

    #it seems that things declared here are shared between all inclusions (saving memeory)
    SEXY_REGEXP = /SEXY_REGEXP/i
    MEM_HOGGING = Array.new(1024 * 1024)
    MEMBERSHIP_STATUSES = %w(accepted invited requested rejected_by_group rejected_group)

    included do
      #The included block will be triggered at inclusion time
      before_create :stuff_on_creation

      attr_accessor :accesor_for_the_instance
      class << self
        attr_accessor :accesor_for_the_class
      end

    end

    module ClassMethods
      #Methods in ClassMethods will get added class methods
      def dummy?
        puts "\nCLASS dummy? called\n"
        true
      end

    end

    #these methods are added as instance methods

    def dummy?
      puts "\nINSTANCE dummy? called\n"
      true
    end

    def stuff_on_creation
      puts "\ncreation called\n"
    end

  end


end


Just create a file under model/concerns/dummy.rb and include it

User.send :include, Concerns::Dummy

or

class User
 include Concerns::Dummy end

Note that I repeat the namespace Concerns, but in other examples found in internet people dont. Rails 4 has included models/concerns and controllers/concerns in the autoload paths and in Rails 4 it would work without the namespace. In Rails 3, you need to add the namespace OR using some trick.

I find easier to understand if I include the 'Concern', tough.

Profit!




Saturday, February 8, 2014

Descubriendo los podcasts

Un podcast es basicamente un blog, pero de audio. Y lo realmente importante es que hay miles de podcasts disponibles, con alta calidad y tratando temáticas muy variadas. Sólo tienes que bajarte los audios (mp3) y escucharlos cuando quieras.

--

Todo se inicia con un tweet de @david_bonilla 
"Yo salgo a correr para tener una excusa para escuchar el ". Así descubro que "El amuleto de yendor" es un podcast sobre tecnología, dode 2 chavales hablan sobre cosas que me interesan.


El prime paso, fue descargarme el último programa desde ivoox : me lo escuche de una sentada pues no tiene desperdicio. ivoox está bien para descargarse programas antiguos, pero se queda corto a la hora de descubrir podcasts similares o buscar ciertas temáticas. Además yo quería que me avisaran cuando se grababan episodios nuevos (esto se llama subscripciones, y se maneja mediante rss).

--

Tras un mes de aprendizaje, así es como gestiono mis podcasts:

gPodder.net es el servicio que me permite guardar mis subscripciones . Es gratuito. Necesitas crear  una cuenta y comenzar a añadir los podcasts que te interesen.

Lo siguiente es utilizar un programa que te descarge nuevos audios (episodios) según van apareciendo. Así te ahorras mucha gestion manual.
- para ordenadores tenemos clientes oficiales de gPodder en todas las plataformas http://gpodder.org/downloads así tengo mis WIndows y Linux sincronizados.
- para android yo utilizo ListeUp (no se cual es la diferencia entre la free o la pro de 1€, pero como es un programa que utilizo mucho, acabé pagando) . Con esto cubro mi MotoG y Nexus7 . Esta aplicacion tiene 2 cosas muy buenas: recuerda el audio que estábas escuchando la última vez que apagaste y que puedes manejar los controles desde la pantalla de bloqueo. También sincroniza con gPodder.
- para el iPad utilizo la aplicacion de podcasts oficial . No me gusta mucho porque no tiene integracion con gpodder, y tengo que añadir mis subscripciones a mano. De todas formas los podcasts los suelo escuchar en el movil o en ordenador, asi que no he investigado más.
- Creo que VLC tambien puede manejar subscripciones de podcasts, pero no está integrado con gpodder.
- si no te interesa la integracion con gpodder, en android tienes PodcastAddict, que es una pasada
- si viajas mucho en coche, puedes pasar los episodios a tu reproductor de mp3 y escucharlo en el coche.

Si utilizas gpodder en varios dispositivos, recuerda gestionar que todos están subscritos a las mismas fuentes y sincronizados, desde https://gpodder.net/devices/ Asi te evitas mucho trabajo.

--

Qué escucho?

Basicamente tecnología: iCharlas, PassionGeek, Infoxicados y el amuleto de Yendor.

Aqui estan mis subscripciones: en rss , opml o directamente la página de gpodder

Suelo descubrir nuevos podcasts, navegando por las subscripciones de otros usuarios que estan suscritos a mis mismos podcasts: algunas veces te llevas una sorpresa!

Dejo un enlace con uno de los episodios que mas me gustaron: Javifrechi (de infoxicados) en iCharlas, parte1 y parte2




Wednesday, November 13, 2013

2 lines that must be on your <head>

In few words:

ensure that these lines are included in your <head>
<meta charset="utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>

Thursday, September 5, 2013

Take a photo from Javascript

Finally it is 2013 and you can take photos from your browser without requiring flash.

The API that makes it possible is getUserMedia ( http://caniuse.com/#search=getUserMedia ) and it is available in all modern browsers (requiring some vendor-prefixes, though).

For demo: use this fiddle

The code I paste here, takes a photo, dump it to a canvas and tries to upload it to a (non-existent) server.
- The key here is that both the captured image AND the uploaded image does not need to be the same size (you usually don't want to upload very big files). That is controlled via the OUTPUT_RATIO constant: the final size is given by the output canvas.
- Neither the video or the output are required to be shown, however is good to give visual feedback to users.
- NOTE: Chrome does not allow local files to get access to getUserMedia. You can use fiddle to make the test yourself.
- At the present, the api still needs to be prefixed depending on the browsers:
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;



<!DOCTYPE html>
    <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
        <title>getUserApi</title>
        <meta name="description" content="">
        <meta name="viewport" content="width=device-width">
        <style type="text/css">
        video {
          background: rgba(255,255,255,0.5);
          border: 1px solid #ccc;
        }
        </style>
    </head>
    <body>
        <div id='text'>
            <p>You must grant access to the Camera first.</p>
            <p>Prompt would be shown above this lines, next to the address bar.</p>
            <button type='button' id='button'>Take photo</button>
        </div>
        <video id='video' width="640" height="480"></video>
        <canvas id='canvas' style="display:none;"></canvas>
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.js"></script>
        <script>
        (function(window) {

          var nav = window.navigator,
            doc = window.document,
            //some browsers behave differently
            is_webkit = nav.webkitGetUserMedia,
            is_mozilla = nav.mozGetUserMedia,
            showSnapshot = true,
            showVideo = true,
            OUTPUT_RATIO = 0.5, //the output is X times the captured image (ex: we upload small photos)

            source,
            video,
            canvas,
            button,
            ctx,
            localMediaStream;

          var
              initCamera = function() {
              video = document.getElementById('video'),
              canvas = document.getElementById('canvas'),
              button = document.getElementById('button'),
              ctx = canvas.getContext('2d');

              //make canvas and video the same dimensions
              canvas.width = video.width * OUTPUT_RATIO | 0;
              canvas.height = video.height * OUTPUT_RATIO | 0;

              //turn canvas to visible
              canvas.style.display = showSnapshot ? '' : 'none';
              video.style.display = showVideo ? '' : 'none';
              (button || video).addEventListener('click', takeSnapshot, false); //addEventListener: IE9+ Opera7+ Safari, FFox, Chrome

              // if (is_webkit){
              //   nav.getUserMedia('video', onSuccess, onError);
              // }else{
              nav.getUserMedia({
                video: true
              }, onSuccess, onError);
              // }

            },
            onError = function(e) {
              alert('Camera permission rejected!', e);
            },
            onSuccess = function(stream) {
                if (is_mozilla) {
                  source = window.URL.createObjectURL(stream);
                } else if (is_webkit) {
                  source = window.webkitURL.createObjectURL(stream);
                } else {
                  source = stream;
                }

                video.src = source;
                video.play();
                localMediaStream = stream;
            }, stopCamera = function(){
              localMediaStream.stop();
              video.style.display =  canvas.style.display = 'none';
              localMediaStream = canvas = ctx = null;
              button
            }, takeSnapshot = function() {
            if (localMediaStream) {
              ctx.drawImage(video, 0, 0, (video.width * OUTPUT_RATIO) | 0, (video.height * OUTPUT_RATIO) | 0);
              uploadSnapshot();
            }
          }, uploadSnapshot = function(){
              var dataUrl;

            try {
                dataUrl = canvas.toDataURL('image/jpeg', 1).split(',')[1];
            } catch(e) {
                dataUrl = canvas.toDataURL().split(',')[1];
            }
            $.ajax({
                url: "localhost:3000/uploadTest",
                type: "POST",
                data: {imagedata : dataUrl}, //in the server file.write(Base64.decode64(imagedata)) , https://gist.github.com/pierrevalade/397615
                contentType: "application/json; charset=utf-8",
                dataType: "json",
                success: function () {
                    alert('Image Uploaded!!');
                },
                error: function () {
                    alert("There was some error while uploading Image");
                }
            });
          };


            //some browsers use prefixes
          nav.getUserMedia = nav.getUserMedia || nav.webkitGetUserMedia || nav.mozGetUserMedia || nav.msGetUserMedia;

          if (nav.getUserMedia) {
            initCamera();
          } else {
            alert("Your browser does not support getUserMedia()")
          }
        }(this));
        </script>
    </body>
</html> 

Bonus

For large images you can save some bytes by sending the image as a blob instead of a base64 encoding.

First, encode the dataUrl as a blob

function dataURItoBlob(dataURI, dataTYPE) {
  var binary = atob(dataURI), array = [];
  for(var i = 0; i < binary.length; i++) array.push(binary.charCodeAt(i));
  return new Blob([new Uint8Array(array)], {type: dataTYPE});
}

Then, you have 2 options:

- using the FormData api

function uploadWithFormData(dataUrl){
  // Get our file
  var file = dataURItoBlob(dataUrl, 'image/jpeg'),
  fd = new FormData();
  // Append our Canvas image file to the form data
  fd.append("imageNameHere", file);
  // And send it
  $.ajax({
     url: "/server",
     type: "POST",
     data: fd,
     processData: false,
     contentType: false,
  });
}

- or using the XHR

function uploadWithXHR(dataUrl) {
  var file = dataURItoBlob(dataUrl, 'image/jpeg'),
  xhr = new XMLHttpRequest();
  xhr.open('POST', '/server', true);
  //add the headers you need
  // xhr.setRequestHeader("Cache-Control", "no-cache");
  // xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
  // xhr.setRequestHeader("X-File-Name", file.name || file.fileName || 'image.jpg');
  // xhr.setRequestHeader("X-File-Size", file.size || file.fileSize);
  // xhr.setRequestHeader("X-File-Type", file.type);
  // xhr.setRequestHeader("Content-Type", options.type);
  // xhr.setRequestHeader("Accept","application/json, text/javascript, */*; q=0.01");
  xhr.send(file);
}