低薪陷阱就是當你花越多時間工作,你就會越依靠它來生存,因為你根本沒多餘的時間尋求其他的機會脫離這種狀態。常看到的現象就是大家辛苦加班,然後再把錢浪費在補償損失的睡眠/吃飯時間。這也是為什麼窮人在醫療方面的支出比富人更多。唯一逃脫的辦法就是不吃不喝地拼命工作,希望自己有天能累積到足夠的餘裕去找尋其他機會。但這個方法要是遇到突發事件恐怕就會失敗。

廉價的薪水意味著昂貴的生活。窮人花的更多,不管是金錢或是時間。在美國和許多發達國家 便宜的房子總是在比較危險或遙遠的地方,窮人都必須花更多錢在通勤上,被搶劫、遭小偷的機率也大大提升。又好比公車。公車很便宜,窮人都會搭公車,有時候他可能需要換兩班公車。早上六點半就要起來,等著每10~15分鐘就應該要來的公車。但有時候,公車30分鐘才來。在那多浪費掉的20分鐘,除了淋雨,你還能夠做什麼嗎?只有等待。這就是另一個貧窮的成本:等待。你等著公車來、在公車上等著司機一站一站的開往終點;你等著付帳單、等著找工作、等發薪水, 等降價、等著獎學金貸款福利的申請結果;你等著下一期的樂透頭獎開在你家。

至於等待的成本呢?對時薪1千美金的有錢人來說,每個月花10小時在通勤上完全是可以負擔的,大不了少買一雙prada給女朋友。但對時薪7.95的窮人花10小時在交通?少了80塊就是要省吃 藥也不買了 這個月的水電費帳單剛好還有$80繳不出來…

越窮,就有越多成本產生。這些成本可能表現在實質的開銷、時間的浪費、生活的不便利、身體的疲勞、生命安全的威脅。真正經歷窮困的人才會知道這些成本究竟有多高。

討論: https://www.facebook.com/alvinwoon/posts/10151155808809071

Appfog /tmp folder

Appfog is still working on their persistent file storage. If you app involves saving files to a temporary folder, you are pretty much out of luck. There’s a tutorial showing how to upload files to Amazon s3 directly but if you want to do server-side image manipulation on-the-fly before sending images to s3, you need a temporary storage.

Such tmp folder does exist on appfog cloud after some digging around. So here goes:

define('TMP_FOLDER', realpath($_SERVER['DOCUMENT_ROOT'].'/../tmp'));

Depending on future cloudfoundry design or appfog cloud design, this /tmp folder might change or disappear but for now it works. Also, all content within this folder are being flushed every time you do a code push.

Fixing “(#3502) Object at URL page_url has og:type of ‘website’” Facebook Open Graph error

The problem is usually caused by a server race condition – the application’s inability to handle two simultaneous HTTP requests. Sometimes when a user performs an action and the application calls a FB open graph request right away, chances are the new object page hasn’t been generated yet. There are a couple solutions to the problem:

1.) Delegate the Facebook Open Graph request to a worker. Even better, use two workers. Submit a facebook scraping request first and upon receiving a successful callback, execute the second Open Graph request.

2.) Use a time delay.

If you use Facebook JS SDK, you can skip worker and do this all on client side. Closure-fest alert so one should clean it up. :)

var openGraphUrl = '/me/' + OG_NAMESPACE + ':' + OG_ACTION + '?' + OG_OBJECT + '=' + siteURL;
var scrapeUrl    = '/?ids='+siteURL+'&scrape=true';
_.setTimeout(function(){
  FB.api(scrapeUrl, 'post',
    function(response) {
      if (!response || response.error) {
        console.log('scrap: '+response.error.message);
      } else {
        FB.api(openGraphUrl, 'post',
          function(response) {
	    if (!response || response.error) {
	      console.log('OG: '+response.error.message);
	    } else {
	      console.log('Successful! Action ID: ' + response.id);
            }
          }
        );
      }
    });
}
, 5000);

Iframe opens in new window problem in Firefox

Developers use iframe for many different things: ‘ajax’ file upload, loading content in an overlay, storing data, calling a REST page and etc.

Turns out if you create an iframe without specifying the name attribute, it will open the iframe in a new window or tab in firefox.

I’m putting this under the ‘blog it to remind my future self’ category.

Search on multiple social networks via realtimesearch.me

The 4-hours hackathon project turned out to be a blast. My project concept is simple – provide a service where user can search for content on major social sites with apparent ease. It was a fun hack and I met some great people from the event. It also gave me an opportunity to play around with Twitter’s bootstrap, jquery History and handlebarJS. It has never been easier to throw up a quick web prototype with all these free lovings going around :).

Check it out – realtimesearch.me

Javascript HTML5 geolocation with geo-IP fallback

Javascript Geo location helps you find your user position(latitude and longitude) via javascript. It will at first try to locate user position via native html5 geolocation position and if that fails, fallback to GEO-IP api via freegeoip.net. This script requires jQuery.

Existing api has be known to be quirky, usually unstable and outputting inaccurate result for many developers. This library minimizes the pain and provide a better user-interaction and better fallback method.

Javascript Geo Location

Nodejs, LESS, Express and Html5 boilerplate

So how to easily generate a project template that contains these lovely web muffins?

First, assuming you have express installed, generate a simple express project using LESS framework

express -c less project_name

This will generate a file structure like this for your project.

Replace the codes inside layout.jade with:

!!! 5
//if lt IE 7
  <html class="no-js ie6 oldie" lang="en">
//if IE 7
  <html class="no-js ie7 oldie" lang="en">
//if IE 8
  <html class="no-js ie8 oldie" lang="en">
//[if gt IE 8]><!
html(class='no-js', lang='en')
  //<![endif]
  head
    meta(charset='utf-8')
    meta(http-equiv='X-UA-Compatible', content='IE=edge,chrome=1')

    title
    meta(name='description', content='')
    meta(name='author', content='')

    meta(name='viewport', content='width=device-width,initial-scale=1')

    link(rel='stylesheet', href='stylesheets/style.css')

    script(src='javascripts/libs/modernizr-2.0.6.min.js')

  body!=body
    #container
      header
      #main(role='main')
      footer

    script(src='//ajax.googleapis.com/ajax/libs/jquery/1.6.3/jquery.min.js')
    script window.jQuery || document.write('<script src="javascripts/libs/jquery-1.6.3.min.js"><\\/script>')

    script(defer, src='javascripts/plugins.js')
    script(defer, src='javascripts/script.js')

    script
      var _gaq=[['_setAccount','UA-XXXXX-X'],['_trackPageview'],['_trackPageLoadTime']];
      (function(d,t){var g=d.createElement(t),s=d.getElementsByTagName(t)[0];
      g.src=('https:'==location.protocol?'//ssl':'//www')+'.google-analytics.com/ga.js';
      s.parentNode.insertBefore(g,s)}(document,'script'));

    //if lt IE 7
      script(src='//ajax.googleapis.com/ajax/libs/chrome-frame/1.0.3/CFInstall.min.js', defer)
      script(defer) window.attachEvent('onload',function(){CFInstall.check({mode:'overlay'})})

Next up, style.less:

/*-------------------------------------------------------------------------------------*\
        HTML5 Boilerplate Lite

http://html5boilerplate.org

        less.css same as style.css with added LESS CSS nesting, mixins, and variables
        --
        credit is left where credit is due.
\*-------------------------------------------------------------------------------------*/

/*---------------------------------------------------------------------------*\
                                                                LESS CSS
\*---------------------------------------------------------------------------*/
/*........................................................*\
                                                Variables
\*........................................................*/

/* Colors */
        @white: rgba(255, 255, 255, 1);
        @black: rgba(0, 0, 0, 1);
        @yellow: #ff9;
        @blue: #007DC5;
        @darkBlue: #33589F;
        @veryDarkBlue: #143352;
        @grey: #999999;
        @pink: #FF5E99;

/* Font */
        @font: 'Droid Sans', arial, sans-serif;

        @small: 12px;
        @medium: 15px;
        @large: 24px;

/*........................................................*\
                                                        Mixins
\*........................................................*/
.border-radius (@radius: 5px) { border-radius: @radius; }
.box-shadow (@hor: 3px, @vert: 2px, @blur: 5px, @shadow: #000) { box-shadow: @hor @vert @blur @shadow; }
.text-shadow (@hor: 3px, @vert: 2px, @blur: 5px, @shadow: #000) { text-shadow: @hor @vert @blur @shadow; }

.clear-text-shadow { text-shadow: none; }
.clear-box-shadow { box-shadow: none; }

.hide { display: none; }
.show { display: block; }
.invisible { visibility: hidden; }
.left { float: left; }
.right { float: right; }

/*---------------------------------------------------------------------------*\
                                                                Styles Reset
\*---------------------------------------------------------------------------*/
a, abbr, acronym, address, applet, article, aside, audio,b, blockquote,big, body,center, canvas, caption, cite, code, command,datalist, dd, del, details, dfn, dl, div, dt, em, embed,fieldset, figcaption, figure, font, footer, form, h1, h2, h3, h4, h5, h6, header, hgroup, html,i, iframe, img, ins,kbd, keygen,label, legend, li, meter,nav,object, ol, output,p, pre, progress,q, s, samp, section, small, span, source, strike, strong, sub, sup,table, tbody, tfoot, thead, th, tr, tdvideo, tt,u, ul, var { background: transparent; border: 0 none; font-size: 100%; margin: 0; padding: 0; vertical-align: baseline; }
article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section { display: block; }

blockquote, q   {       quotes: none;
                                        &:before { content: ''; content: none; }
                                        &:after { content: ''; content: none; }
                                }
ins { text-decoration: none; }
mark { background-color: @yellow; color: @black; font-weight: bold; }
abbr[title], dfn[title] { border-bottom: 1px dotted; cursor: help; }
table { border-collapse: collapse; border-spacing: 0; }
hr { display:block; height:1px; border:0; border-top:1px solid #ccc; margin:1em 0; padding:0; }
input, select { vertical-align: middle; }

/*---------------------------------------------------------------------*\
                                                        Typography
        fonts.css from the YUI Library: http://developer.yahoo.com/yui/
\*---------------------------------------------------------------------*/
body { font:13px/1.231 @font; }
pre, code, kbd, samp { font-family: 'Courier New', monospace, sans-serif; }

/* -:[ Fibonacci based heading scale ratio ]:- */
        h1 { font-size: 4.4em; font-weight: normal; }
        h2 { font-size: 2.8em; font-weight: bold; }
        h3 { font-size: 1.6em; font-weight: bold; }
        h4 { font-size: 1.2em; font-weight: bold; }

/* -:[ Remove text-shadow from text selection for better readability: http://twitter.com/miketaylr/status/12228805301  ]:- */
::-moz-selection{ background: @pink; color: @white; .clear-text-shadow; }
::selection { background: @pink; color: @white; .clear-text-shadow; } 

/*---------------------------------------------------------------------------------------------*\
                                                                        Shortcuts & Helpers
\*---------------------------------------------------------------------------------------------*/

.separator { clear: both; float: left; height: 1px; width: 100%; }      /* -:[ If  ain't enough ]:- */

/*---------------------------------------------------------------------------------------------------------*\
                Clearfix

http://j.mp/bestclearfix

http://blueprintcss.lighthouseapp.com/projects/15318/tickets/5-extra-margin-padding-bottom-of-page

\*---------------------------------------------------------------------------------------------------------*/
.clearfix:before, .clearfix:after { content: "\0020"; display: block; height: 0; visibility: hidden; }
.clearfix:after { clear: both; }
.clearfix { zoom: 1; }

/*----------------------------------------------------------------------------------*\
                                                                Real styles start here
\*----------------------------------------------------------------------------------*/

body    {
                        /*      background: ;
                                color: ;
                                font: ..px @font;       */
            }

header  {
                        .inside {  }

                        nav     {
                                        ul      {
                                                        li      {
                                                                        a       {
                                                                                        &:hover {  }
                                                                                        &:active {  }
                                                                                }
                                                                }
                                                }
                                }
                }

#wrap   { 

                }

footer  {
                        .inside {  }
                }

/*------------------------------------------------------------------------------*\
                                                                Media queries
\*------------------------------------------------------------------------------*/
@media all and (orientation:portrait) { /* Style adjustments for portrait mode goes here */ }
@media all and (orientation:landscape) { /* Style adjustments for landscape mode goes here */ }

/*      ........................................................................
        Grade-A Mobile Browsers (Opera Mobile, iPhone Safari, Android Chrome)
        link: www.cloudfour.com/css-media-query-for-mobile-is-fools-gold/

        Stop iOS and WinMobile from mobile-optimize the text:
        link: http://j.mp/textsizeadjust
                html { -webkit-text-size-adjust:none; -ms-text-size-adjust:none; }
        ........................................................................*/
@media screen and (max-device-width: 480px) {  }

/*----------------------------------------------------------------------------------------------*\
                                                                                Print styles

        Inlined to avoid required HTTP connection - link: http://www.phpied.com/delay-loading-your-print-css/
\*----------------------------------------------------------------------------------------------*/
@media print {
                                  * { background: transparent !important; color: #444 !important; .clear-text-shadow; }
                                  a, a:visited { color: #444 !important; text-decoration: underline; }
                                  a:after { content: " (" attr(href) ")"; }
                                  abbr:after { content: " (" attr(title) ")"; }
                                  .ir a:after { content: ""; }  /* Don't show links for images */
                                  pre, blockquote { border: 1px solid #999; page-break-inside: avoid; }
                                  thead { display: table-header-group; } /* css-discuss.incutio.com/wiki/Printing_Tables */
                                  tr, img { page-break-inside: avoid; }
                                  @page { margin: 0.5cm; }
                                  p, h2, h3 { orphans: 3; widows: 3; }
                                  h2, h3{ page-break-after: avoid; }
                        }

That’s it.

Distinguish between Jquery click and double click

I wish there’s a better way. Or maybe perhaps there is?

var flag = 1;

$(element).click(function(){
    setTimeout(function(){
        if(flag){
            console.log('this is a click');
        }
    }, 500);
    flag = 1;
})

$(element).dblclick(function(){
    console.log('this is a double click');
    flag = null;
})

Unicode in nodeJS and passing arguments with Cloudmade api

Not all NodeJS libraries come with unicode support out-of-the-box, but some do very well (props to express). I found most of the problems in those libraries can be fixed if you look for the writeHead function argument. More likely than not, javascript is served as text/javascript and no charset is set so if the browser requests something else by default (iso-8859-15 in many case), the resource gets interpreted as such. It can either be fixed by changing generated tags to request type=”text/javascript;charset=utf-8″ or by serving the actual resource with a “text/javascript;charset=utf-8″ header. Watch out for the content length call as well.

//before
res.writeHead(code,
    { "Content-Type": "text/json" , "Content-Length": body.length }
);

//after
res.writeHead(code,
    { "Content-Type": "text/javascript; charset=utf-8" , "Content-Length": Buffer.byteLength(body) }
);

Moving on to the next thing. While working on stupidmap.com, I came across a simple trick to pass additional argument via Cloudmade LatLng API. They should have documented this in the API doc :P

//third parameter onwards can be used to pass arguments
var marker = new CM.Marker(new CM.LatLng(lat, lon),
{ icon: CloudMadeIcon, title: title, n_argument: data });

//pass data as an argument
CM.Event.addListener(marker, 'click', function(){
    var _data = this._options.n_argument;
    infoWindow(_data, this);
});

I assume you can do the same thing with Google Maps and Leaflet API.

4 dollars 7-Eleven’s t-shirt

There are an unbelievable number of 7-Eleven’s in Taipei. One for every 6200 people in fact, compared to one for every 48000 people in the US. I have always been intrigued by the concept of disposable, no-frills garments that are sold there. They are packaged and sold like something you would see coming out of a vending machine.

So I bought one. They cost like 4 dollars USD each. Nicely folded. None of those weird fresh scent. Herpes-free as far as my human eyes can tell. The fabric is a little bit light – it is no American Apparel that’s for sure. But it is still pleasantly comfy. Fit nicely too.

I can imagine buying 50 of these and just stock them in my closet. Takes care of the daily annoyance of having to figure out what to wear.