Posts Tagged javascript

Cross-Domain LocalStorage

As you may know, LocalStorage is domain based. You can’t read or write from localstorage that’s on different domain, even if that’s subdomain. But there is iframe trick that you can use to store data from domain to it’s subdomain.

Besically you create an iframe that’s hosted on your subdomain that set document.domain to the parent domain. Then you send PostMessage to that iframe and inside iframe you set that value of localStorage.

Here is the code

iFrame


document.domain = "domain.com";
window.onmessage = function(e) {
  if (e.origin !== "http://domain.com") {
    return;
  }
  var payload = JSON.parse(e.data);
  localStorage.setItem(payload.key, JSON.stringify(payload.data));
};

main file


<script>
window.onload = function() {
    var win = document.getElementsByTagName('iframe')[0].contentWindow;
    var obj = {
       name: "Jack"
    };
    win.postMessage(JSON.stringify({key: 'storage', data: obj}), "*");
};
</script>
<iframe style="display:none" src="http://sub.domain.com/iframe.html"></iframe>

If you want to save and load value from LocalStorage, you can PostMessage with method key that will indicate what action it need to take. You can also send message back from iframe to it’s parent with data read from localStorage.

iframe


document.domain = "domain.com";
window.onmessage = function(e) {
    if (e.origin !== "http://domain.com") {
        return;
    }
    var payload = JSON.parse(e.data);
    switch(payload.method) {
        case 'set':
            localStorage.setItem(payload.key, JSON.stringify(payload.data));
            break;
        case 'get':
            var parent = window.parent;
            var data = localStorage.getItem(payload.key);
            parent.postMessage(data, "*");
            break;
        case 'remove':
            localStorage.removeItem(payload.key);
            break;
    }
};

Your main page on domain.com


window.onload = function() {
    var win = document.getElementsByTagName('iframe')[0].contentWindow;
    var obj = {
       name: "Jack"
    };
    // save obj in subdomain localStorage
    win.postMessage(JSON.stringify({key: 'storage', method: "set", data: obj}), "*");
    // load previously saved data
    win.postMessage(JSON.stringify({key: 'storage', method: "get"}), "*");
    window.onmessage = function(e) {
        if (e.origin != "http://sub.domain.com") {
            return;
        }
        // this will log "Jack"
        console.log(JSON.parse(e.data).name);
    };
};

,

1 Comment

40 JavaScript libraries you may not know about

Here is a list of useful libraries I had in my bookmarks, libraries that I need to use in some of my future projects.

  • Drawing and animation
  • Language extensions
  • Utilites
    • URI.js – library for handling urls
    • rangy – Text range library
    • color-thief – pick color(s) from image
    • stacktrace.js – get the stacktrace
    • XRegExp – extended regular expressions
    • -prefix-free – CSS3 without prefixes
    • mathjs – An extensive math library for JavaScript and Node.js
    • numeraljs – library for formatting and manipulating numbers
    • String.js – string manipulation library
    • Ocrad.js – Optical Character Recognition in Javascript
    • filer.js – wrapper over HTML5 File API
  • DOM
  • Events
    • jwerty – keyboard events
    • jquery-simulate – simulate keyboard and mouse events
    • Mousetrap – keyboard shortcuts
    • jQuery hashchange – hashchange event
    • jquery-waypoints – Execute a function when you sroll to the element
    • Steady.js – A jank-free module to do logic on the onscroll event without performance regressions in a @media-query like conditions.
    • sysend.js – Send messages between open pages in the browser (this one is actually my own)

,

1 Comment

Shell without ssh or root on your shared hosting

My site jcubic.pl is on shared hosting that don’t have ssh, and I wanted to have a shell access, so using my jQuery Terminal, I’ve stared a project leash. It’s a shell written in javascript with help from php and python cgi. It will give you a shell access, no need to install any new software, and you don’t need to be root (like with other shells like shell in a box, AjaxTerm or Buterfly).

, , , ,

Leave a comment

Using exceptions to simulate tail recursion in JavaScript

JavaScript is very powerful language but it don’t have tail call elimination. But it turn out that you can simulate it using exception system. Here is very simple recursive function that calculate factorial:

function factorial(n) {
    function recur(n, result) {
        if (n == 0) {
            throw result;
        } else {
            recur(n-1, result*n);
        }
    }
    try {
        recur(n, 1);
    } catch(e) {
        return e;
    }
}

It turn out that in JavaScript (I read that in Douglas Crockford book JavaScript: The Good Parts) you can use any expression in throw and it will be send to variable in catch statement.

So what the above code does it simple exit from the recursive loop and pass result to catch statement. And this is exactly what tail recursion is, in language scheme this happen by default when inner function (you also need to create inner function in scheme) have recursive call as last expression. Here is scheme version of tail recursive factorial:

(define (factorial n)
  (let recur ((n n) (result 1))
     (if (= n 0)
        result
        (recur (- n 1) (* result n)))))

the code use named let but it can be rewriten with inner function and invocation. (this kind of trick is needed in script-fuGimp extension based on scheme).

, ,

6 Comments

Add methods to Number object in JavaScript

In JavaScript everything is an object, even functions and numbers, and all objects even functions are first class citizens (which allow functional programming).

So this mean that you can call methods over numbers, and you can add new methods using Number.prototype. Here is example of range functions similar to that from Python, but bit different. (Python one return number from n to m – or from 0 to n – but this one return n numbers (n is this) starting from number passed as argument)

Number.prototype.range = function(n, result) {
    result = result || [];
    n = typeof n === 'undefined' ? 0 : n;
    if (this <= 0) {
        return result.reverse();
    } else {
        return this.range.call(this-1, n, result.concat([this-1+n]));
    }
};

And you can call this function/method using:

10..range(1)

It will return array [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].

Note that there are two dots, it’s because when you use one it’s interpret as float number and thorw exception "SyntaxError: Unexpected token ILLEGAL" because it wait for a digit as next character.

Using this you can write factorial using

10..range(1).reduce(function(a, b) { return a*b; });

reduce function was added to Array.prototype in ECMAScript 5, You can check what browsers support them in this page.

You can wrap that code with a function and you can add it as Number prototype

Number.prototype.factorial = function() {
    return this.range(1).reduce(function(a, b) { return a*b; });
};

You can call it as:

10..factorial();

Here is example of times function (similar to that from Ruby)

Number.prototype.times = function(fn, self) {
    return this.range().forEach(fn, self);
};

You can use this function using:

10..times(function(i) { console.log(i); });

Leave a comment

Image color picker in javascript that work without canvas in every browser

If you want to create color picker from image in javascript you probably will use canvas, but what if you need it work in IE as I needed. You can use some server side help to fetch pixels data from the server. I use php for that and GD library. The code is below.

cross-browser color picker

Server side code that return json pixel data


<?php

function rgb($color) {
    $result[] = ($color >> 16) & 0xFF;
    $result[] = ($color >> 8) & 0xFF;
    $result[] = $color & 0xFF;
    return $result;
}

function imagecreatefrom($filename) {
    $path = pathinfo($filename);
    switch($path['extension']) {
        case 'png':
            return imagecreatefrompng($filename);
        case 'jpg':
            return imagecreatefromjpeg($filename);
        case 'gif':
            return imagecreatefromgif($filename);
        default:
            return null;
    }
}

function getImageData($filename) {
    list($width, $height) = getimagesize($filename);
    $image = imagecreatefrom($filename);
    $image_data = array();
    for ($y = 0; $y < $height; ++$y) {
        $row = array();
        for ($x = 0; $x < $width; ++$x) {
            $row[] = rgb(imagecolorat($image, $x, $y));
        }
        $image_data[] = $row;
    }
    return $image_data;
}

if (isset($_GET['filename'])) {
    $filename = $_GET['filename'];
    if (file_exists($filename)) {
        $result = array(
            'error' => null,
            'result' => getImageData($filename)
        );
    } else {
        $result = array(
            'error' => "The file '$filename' don't exist",
            'result' => null
        );
    }
} else {
    $result = array(
        'error' => "You need to put filename",
        'result' => null
    );
}
header('Content-Type: application/json');
echo json_encode($result);

?>

Then you can fetch the data using ajax and add get invidial pixels on mousemove


    var img = $('img');
    $.getJSON('image_data.php', {filename: img.attr('src')}, function(data) {
        if (data.error) {
            alert(data.error);
        } else {
            $('.eyedropper').click(function() {
                picker = true;
                return false;
            });
            img.mousemove(function(e) {
                if (picker) {
                    var x = Math.round(e.pageX - offset.left);
                    var y = Math.round(e.pageY - offset.top);
                    if (x >= 0) {
                        $('.color').css('background-color',
                                        'rgb('+data.result[y][x].join(',')+')');
                    }
                }
            }).click(function(e) {
                picker = false;
            });
        }
    });

Now all you need to have is element with class eyedropper like a link in your html:


<a href="#" class="eyedropper">pick the color</a>

, ,

3 Comments

How To Add Translation Feature To Twitter

I don’t know about you but, I follow few peple that sometimes tweet in different language then English. And English is the only foreign language I know. So if I want to see what they tweet about sometimes I use google translate to read the content.

I always thought that it will be nice feature for Twitter to have translate button, so I created one, using WebSockets and Ruby.

So how I did this, first I downloaded websockets ruby library from github web-socket-ruby, I already had translation script written in Ruby that use google translate, so I added websocket server to it.

#!/usr/bin/ruby

require 'net/http'
require 'uri'
require 'optparse'
require 'json'
require 'socket'
require 'web_socket'

class NotConnectedException < Exception
end

def server(port, domains)
  server = WebSocketServer.new(
    :accepted_domains => domains,
    :port => port)
  puts("Server is running at port %d" % server.port)
  server.run() do |ws|
    puts("Connection accepted")
    puts("Path: #{ws.path}, Origin: #{ws.origin}")
    if ws.path == "/translate"
      ws.handshake()
      while data = ws.receive()
        printf("Received: %p\n", data)
        data = JSON.parse(data)
        response = translate(data['text'], nil, data['to_lang']).join("\n")
        ws.send(response)
        printf("Sent: %p\n", response)
      end
    else
      ws.handshake("404 Not Found")
    end
    puts("Connection closed")
  end
end

def escape(o)
  o.gsub(/([^ a-zA-Z0-9_.-]+)/n) {
    '%' + $1.unpack('H2' * $1.size).join('%').upcase
  }.tr(' ', '+')
end

def translate(text, from=nil, to=nil, cookie=nil)
  url = URI.parse("http://translate.google.com/translate_a/t")
  query = "?hl=en&client=t&text=#{escape(text)}&multires=1&otf=1&pc=0&sc=1&ie=UTF-8&oe=UTF-8"
  query += "&sl=" + (from ? from : 'auto')
  if to
    query += "&tl=#{to}"
  end
  begin
    http = Net::HTTP.new(url.host)
    res = http.get(url.path + query)
    res.response['content-type'] =~ /charset=(.*)/
    charset = $1
    JSON.parse(res.body.gsub(/,{2,}/, ',').gsub(/,\]/, ']'))[0].map {|i|
      # google sometimes put spaces around numbers
      i[0].gsub(/\{ *([0-9]) *\}/, '{\1}')
    }
  rescue NoMethodError
    raise NotConnectedException
  rescue SocketError
    raise NotConnectedException
  rescue 
  end  
 
end

def msg(str, type='info')
  system("zenity --#{type} --title='translation' --text='#{str}'")
end

params = ARGV.getopts('i:o:gcsp:')

def error(msg, gui=false)
  if gui
    msg(msg, 'error')
  else
    puts msg
  end
end

def usage()
  puts "usage:"
  puts "translate [-i <INPUT LANG>] -o <OUTPUT LANG> [MORE OPTIONS]"
  puts "-g - show zenity dialog"
  puts "-c - get input from clipboard"
  puts "-s - run as server"
  puts "-p - server port"
end

begin
  if params['s']
     begin
       server(params['p'] ? params['p'].to_i() : 8080, 'twitter.com')
     rescue Interrupt
       puts "Server Exit"
     end
  else
    if params['c']
      input = `xclip -o -sel clip`
    else
      input = ARGF.read()
    end
    translation = translate(input, params['i'], params['o'])
    if params['g']
      msg(translation.join(". "))
    else
      translation.each{|sentence|
        if sentence != ''
          puts sentence
        end
      }
    end
  end
rescue JSON::ParserError => e
  error("Response Error: " + e.message, params['g'])
rescue NotConnectedException
  error("sorry but it seems that your internet connection is down", params['g'])
end

Then I created this bookmarklet. (I notice that profile page use jQuery.noConflict() so I can’t access it from bookmarklet. So here is updated code that insert jQuery script again – in use continuation to block execution of the script until jQuery is loaded). To use bookmarklet just copy code below and insert into url address bar when twitter tab is active

javascript:(function(continuation) {
    function attr(elem, key, value) {
        elem.setAttribute(document.createAttribute(key, value));
    }
    var script = (function() {
        var head = document.getElementsByTagName('head')[0];
        return function(src) {
            var script = document.createElement('script');
            script.setAttribute('src', src);
            script.setAttribute('type', 'text/javascript');
            script.setAttribute('async', 'false');
            head.appendChild(script);
            return script;
        };
    })();
    script('https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js');
    var interval = 100;
    setTimeout(function() {
        if (jQuery) {
            continuation(jQuery.noConflict());
        } else {
            setTimeout(arguments.callee, interval);
        }
    }, interval);
})(function($) {
    function translator(fun, lang) {
        var socket = new WebSocket("ws://localhost:8080/translate");
        socket.onopen = function() {
            console.log("Socket has been opened!");
        };
        socket.onclose = function() {
            console.log("closed");
        };
        return fun(function(text, respond) {
            socket.onmessage = function(msg) {
                respond(msg.data);
            };
            var data = JSON.stringify({to_lang: lang, text: text});
            socket.send(data);
        });
    }
    var translate_tweet = translator(function(translate) {
        return function(content) {
            var tweet_container = content.find('.js-tweet-text');
            var tweet = tweet_container.html();
            content.data('original', tweet);
            var links = [];
            var i = 0;
            tweet = tweet.replace(/<a[^>]+>[^<]+<\/a>|<a[^>]+><s>(#|@)<\/s><b>[^<]+<\/b><\/a>/g, function(link) {
                links.push(link);
                return '{' + i++ + '}';
            });
            translate(tweet, function(result) {
                for (var i=links.length; i--;) {
                    result = result.replace('{' + i + '}', links[i]);
                }
                tweet_container.html(result);
                var actions = content.find('.tweet-actions');
                if (actions.find('.action-orig-container').length == 0) {
                    actions.append('<li class="action-orig-container"><a href="#">Original</a></li>');
                }
            });
        };
    }, prompt('Select Language: af - afrikaans, sk - albánskej, ar - عربي, be - Беларускі, bg - Български, zh - 荃湾, zh - 太阳, hr - Hrvatski, cs - Český, da - Danske, et - Eesti, tl - filipiński, fi - Suomi, fr - Français, gl - galijski, el - Ελληνικά, iw - עברית, hi - हिन्दी, es - Español, nl - Nederlands, id - indonezyjski, ga - Gaeilge, is - Íslenska, ja - 日本語, yi - ייִדיש, ca - Català, ko - 한국의, lt - Lietuvos, lv - Latvijas, mk - Македонски, ms - Melayu, mt - Malti, de - Deutsch, no - Norsk, fa - فارسی, pl - polski, ru - Русский, ro - Română, sr - Српски, sk - Slovenský, sl - Slovenski, sw - Swahili, sv - Svenska, th - ภาษาไทย, tr - Türk, uk - Український, cy - walijski, hu - Magyar, vi - Việt, it - Italiano'));
    $('.content').unbind('mouseover').live('mouseover', function() {
        var $this = $(this);
        if ($this.find('.action-trans-container').length == 0) {
            $this.find('.action-fav-container').
                after('<li class="action-trans-container"><a href="#">Translate</a></li>');
        }
    });
    $('.action-orig-container').unbind('onclick').live('click', function() {
        var content = $(this).parents('.content');
        content.find('.js-tweet-text').html(content.data('original'));
        content.find('.action-orig-container').remove();
        return false;
    });
    $('.action-trans-container').unbind('onclick').live('click', function() {
        translate_tweet($(this).parents('.content'));
        return false;
    });
});

CODE LICENSE: you can use the code for whatever purpose you like it’s realeas on Sharing Agreement.

, ,

2 Comments

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: