<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>PageTalks &#187; Robin</title>
	<atom:link href="http://pagetalks.com/author/robin/feed" rel="self" type="application/rss+xml" />
	<link>http://pagetalks.com</link>
	<description>Pure Web Development &#38; Design Ideas</description>
	<lastBuildDate>Thu, 19 Jan 2012 12:06:33 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.2.1</generator>
		<item>
		<title>Wrangling with Deflate, Base64 and GZip</title>
		<link>http://pagetalks.com/2012/01/19/wrangling-with-deflate-base64-and-gzip.html</link>
		<comments>http://pagetalks.com/2012/01/19/wrangling-with-deflate-base64-and-gzip.html#comments</comments>
		<pubDate>Thu, 19 Jan 2012 11:35:56 +0000</pubDate>
		<dc:creator>Robin</dc:creator>
				<category><![CDATA[Javascript]]></category>
		<category><![CDATA[NodeJS]]></category>
		<category><![CDATA[Programming]]></category>
		<category><![CDATA[base64]]></category>
		<category><![CDATA[deflate]]></category>
		<category><![CDATA[gzip]]></category>
		<category><![CDATA[inflate]]></category>
		<category><![CDATA[js]]></category>
		<category><![CDATA[zlib]]></category>

		<guid isPermaLink="false">http://pagetalks.com/?p=549</guid>
		<description><![CDATA[最近不小心被一些编码相关的东西困扰了。这些从Web诞生便产生的一些编码问题，在2012年的今天仍然可以让一个程序员抓狂。项目的大致需求是这样的，客户端需要将一个字符串进行如下处理... ]]></description>
			<content:encoded><![CDATA[<p>最近不小心被一些编码相关的东西困扰了。这些从Web诞生便产生的一些编码问题，在2012年的今天仍然可以让一个程序员抓狂。项目的大致需求是这样的，客户端需要将一个字符串进行如下处理：</p>
<ol>
<li>将字符串进行Deflate压缩</li>
<li>将压缩后的结果再进行Base64编码</li>
</ol>
<p>那么服务器端，需要进行上述流程的反过程。问题是，由于这是个开放接口，客户端可以使用各种不同的平台进行开发，如JS、Java、Ruby、PHP等。好在，大多数语言都是基于<a href="http://zlib.net/">zlib</a>包装了自己的相关API，那么也算是实现标准了。窘迫的事情时，NodeJS里面zlib的实现是在0.6以后加入的，我们使用的技术仍然是0.4版本的。</p>
<p>迫于时间有限，切换到0.6或者实现zlib的binding都是不靠谱的，于是我打开了万能的Github。果然功夫不负有心人，找到了Deflate的纯JS实现——<a href="https://github.com/dankogai/js-deflate">RawDeflate</a>。顿时对作者的崇拜之心油然而生。殊不知，悲剧就是从这里开始的。<span id="more-549"></span><br />
<h3>Base64算法</h3>
<p>为了测试有效性，我用JS做了客户端编码工作，然后用Ruby实现了服务器端解码工作。代码如下:</p>
<ul>
<li>被我修改过的<a href="https://gist.github.com/1638963#file_deflate.js">deflate.js</a></li>
<li>JS编码脚本,<a href="https://gist.github.com/1638963#file_encode_v1.js">v1</a></li>
<li>Ruby解码脚本,<a href="https://gist.github.com/1638963#file_decode_v1.rb">v1</a></li>
</ul>
<p>这里只把编码脚本和解码脚本贴出来，其他的已经在上面给出了Gist链接。</p>
<h4>编码脚本, v1</h4>
<pre><code>var RawDeflate = require("./deflate"),
    Buffer = require("buffer").Buffer,
    fs = require("fs");

function encode(str) {
  return new Buffer(RawDeflate.deflate(str)).toString("base64");
}

console.log(encode("hello, world!"));
fs.writeFileSync("data.txt", encode("hello, world!"));</code></pre>
<h4>解码脚本，v1</h4>
<pre><code>require 'base64'
require 'zlib'

def decode(str)
  Zlib::Inflate.new(-Zlib::MAX_WBITS).inflate(Base64.decode64(str))
end

str = File.open("data.txt", "r") do |data|
  data.readlines().join()
end

p decode(str)</code></pre>
<p>运行的结果是空串哦，亲！也就是说Ruby竟然不能解出来。Ruby是直接在Zlib上进行包装的，那么其他的语言PHP、Python什么的也应该是同样的结果。</p>
<p>好吧，难道我JS的算法有误差？那么和Ruby的算法进行对比以下吧⋯⋯</p>
<p><a href="https://gist.github.com/1638963#file_compare_test.rb">Ruby的测试脚本</a>，算出的每个步骤都进行md5：</p>
<pre><code>require 'base64'
require 'zlib'
require 'digest/md5'

def deflate(str)
  Zlib::Deflate.new(nil, -Zlib::MAX_WBITS).deflate(str, Zlib::FINISH)
end

def base64(str)
  Base64.encode64(str)
end

def md5(str)
  s = Digest::MD5.hexdigest(str)
end

def encode(str)
  base64(deflate(str))
end

def decode(str)
  Zlib::Inflate.new(-Zlib::MAX_WBITS).inflate(Base64.decode64(str))
end

str = 'hello, world'

p "Test Base64"
p base64(str)
p md5(base64(str))

p "Test Deflate"
p deflate(str)
p md5(deflate(str))

p "Test Encode Defalte and Base64"
p encode(str)
p md5(encode(str))

p "Test Decode Deflate and Base64"
p decode(encode(str))</code></pre>
<p>运行发现，Ruby自己处理自己压缩出来的东西毫无问题。那么JS呢？</p>
<pre><code>var Base64 = require("./b64"),
    RawDeflate = require("./deflate"),
    Buffer = require("buffer").Buffer,
    crypto = require("crypto");

function decode (str) {
  return RawDeflate.inflate(new Buffer(str, "base64").toString());
}

function md5 (str) {
  return crypto.createHash("md5").update(str).digest("hex");
}

function base64 (str) {
  return new Buffer(str).toString("base64");
}

function deflate (str) {
  return RawDeflate.deflate(str);
}

function encode (str) {
  return base64(deflate(str));
}

var str = "hello, world";
console.log("Testing string: " + str);

console.log("Base64 Test");
console.log(base64(str));
console.log(md5(base64(str)));

console.log("Defalte Test");
console.log(deflate(str));
console.log(md5(deflate(str)));

console.log("Test Encode");
console.log(encode(str));
console.log(md5(encode(str)));

console.log("Test Decode");
console.log(decode(encode(str)));</code></pre>
<p>好吧，JS的算法自己处理自己的结果也是正确的。那么比较以下两边各个步骤的md5，可以发现几个现象：</p>
<p><a href="http://www.flickr.com/photos/28352704@N06/6724942605" title="View 'Screen Shot 2012-01-19 at 下午6.43.35' on Flickr.com"><img border="0" style="display:block; margin-left:auto; margin-right:auto;" height="396" src="http://farm8.staticflickr.com/7012/6724942605_db603df767_o.png" alt="Screen Shot 2012-01-19 at 下午6.43.35" width="560" title="Screen Shot 2012-01-19 at 下午6.43.35"/></a></p>
<ul>
<li>仅仅Base64后MD5便不一致</li>
<li>仅仅Deflate后MD5一致</li>
<li>Deflate后再Base64,结果MD5不一致</li>
</ul>
<p>那么就是NodeJS的Base64这么算可能不对了，这时我已经凌乱了，算了，干脆找个JS的Base64实现吧。我的大神啊，原来RawDeflate的作者自己也写了个<a href="https://github.com/dankogai/js-base64">Base64的实现</a>，我觉得这里面有阴谋⋯⋯</p>
<p>用上这位<a href="https://gist.github.com/1638963#file_base64.js">大神的Base64</a>，同时我也发现了RawDeflate里面有一个使用实例。例子里面在Deflate之前，把字符串进行了Base64.<a href="http://h30097.www3.hp.com/docs/base_doc/DOCUMENTATION/V40F_HTML/MAN/MAN1/0398____.HTM">utob</a>操作。但是我们用的测试字符是“hello,world&#8221;貌似没有非ACSII字符。</p>
<p>那么我们<a href="https://gist.github.com/1638963#file_encode_v2.js">新的编码脚本</a>如下：</p>
<pre><code>var RawDeflate = require("./deflate"),
    Buffer = require("buffer").Buffer,
    fs = require("fs"),
    Base64 = require("./base64");

function encode(str) {
  return Base64.toBase64(RawDeflate.deflate(Base64.utob(str)));
}

console.log(encode("hello, world!"));
fs.writeFileSync("data.txt", encode("hello, world!"));</code></pre>
<p>我们仍然使用之前的<a href="https://gist.github.com/1638963#file_decode_v1.rb">解码脚本</a>，结果竟然通过了！ruby得到了“hello,world“！</p>
<p>其中原理不解，有待高人解答，但是灾难还没有结束哦。</p>
<h3>RFC1951、RFC1950和RFC1951</h3>
<p>因为对方服务器目前使用的是JAVA，那么我必须用JAVA测试才能OK。</p>
<p>JAVA的代码如下，只贴出核心代码吧：</p>
<pre><code>InputStream in = new InflaterInputStream(new Base64InputStream(new FileInputStream(compressed)));</code></pre>
<p>第一次试验的结果是，无法解压的，想哭啊，得到“unknown compression method”的错误。</p>
<p>我开始质疑JAVA的实现了。虽然也是zlib的前缀，但是InflaterInputStream<a href="http://docs.oracle.com/javase/1.4.2/docs/api/java/util/zip/InflaterInputStream.html">这个类的JavaDoc</a>里却没有写清楚它对应的算法。</p>
<p>我们知道，HTTP传输里面经常会用到gzip、deflate来压缩内容，只用在header里面写明“Content-encoding&#8221;。Web服务器和浏览器其实都是相当默契的。好吧，我必须承认，浏览器不愧是Web里面最复杂的软件。因为它handle了太多的东西。</p>
<p><a href="http://www.ietf.org/rfc/rfc1950.txt">RFC1950</a>是zlib算法，<a href="http://www.google.com/url?sa=t&#038;rct=j&#038;q=rfc%201951&#038;source=web&#038;cd=1&#038;sqi=2&#038;ved=0CCMQFjAA&#038;url=http%3A%2F%2Fwww.ietf.org%2Frfc%2Frfc1951.txt&#038;ei=E_sXT9OPNsWpiAfVtdybCw&#038;usg=AFQjCNFifaDqn62RJhQ3ODDJ12pO1LsQAA">RFC1951</a>是deflate算法，<a href="http://www.google.com/url?sa=t&#038;rct=j&#038;q=rfc%201952&#038;source=web&#038;cd=1&#038;ved=0CCAQFjAA&#038;url=http%3A%2F%2Fwww.ietf.org%2Frfc%2Frfc1952.txt&#038;ei=I_sXT-ykEpCWiQe0wdmgCw&#038;usg=AFQjCNFA13Eq1NXCeQAGsDxtXuMSMI7bog">RFC1952</a>是Gzip算法。那么他们是三个独立的算法么？</p>
<p>我以为是，其实不是！</p>
<p>在HTTP 1.1的定义里面，也就是<a href="http://www.w3.org/Protocols/rfc2616/rfc2616.html">RFC2616</a>是这么定义delfate传输压缩的：</p>
<p><quote>deflate &#8211; The &#8220;zlib&#8221; format defined in RFC 1950 [31] in combination with the &#8220;deflate&#8221; compression mechanism described in RFC 1951 [29].</quote></p>
<p>原来zlib和deflate是同时使用的！zlib仅仅作为一种容器格式，来承载通过deflate算法压缩的内容。就像AVI其实是个视频容器格式，里面的压缩算法可以是XVID也可以H.262！</p>
<p>回来想想RawDeflate的主页上也仅仅说明是RFC1951的实现，而且人家是”Raw“Deflate，Raw！看来是我自己不懂啊⋯⋯</p>
<p>跟我一样苦逼的人还是不少的，看这个<a href="http://stackoverflow.com/questions/3932117/handling-http-contentencoding-deflate">帖子</a>，有一个回复已经给出了JAVA里面的解决方案。</p>
<p>如果要解决单纯使用Deflate压缩的文件，必须传入一个自定义Inflater。</p>
<pre><code>InputStream in = new InflaterInputStream(conn.getInputStream()), new Inflater(true));</code></pre>
<p>更新了JAVA脚本之后，的确问题解决了。顺手把其他语言解码方案也试验出来了。基本上，除了JAVA需要，其他实现都不需要进行额外的判断，所以JAVA实在不是一个敏捷语言啊⋯⋯</p>
<h4>Ruby解码实现</h4>
<pre><code>require 'base64'
require 'zlib'

def decode(str)
  Zlib::Inflate.new(-Zlib::MAX_WBITS).inflate(Base64.decode64(str))
end

File.open("data.txt", "r") do |data|
  str = data.readlines
  p decode str.join
end</code></pre>
<h4>Python解码实现</h4>
<p>Python在压缩的时候，想得到纯粹的Deflate流也是需要额外的加工的，详见此处：<a href="http://stackoverflow.com/questions/1089662/python-inflate-and-deflate-implementations">http://stackoverflow.com/questions/1089662/python-inflate-and-deflate-implementations</a>。</p>
<p><a href="https://gist.github.com/1638963#file_inflate.py">Python的解码</a>的实现也很直白：</p>
<pre><code>import zlib
import base64

str = ""
with open("data.txt") as data:
    for line in data:
        print line
        str += line

def decode_base64_and_inflate(str):
    decoded_data = base64.b64decode( str )
    return zlib.decompress( decoded_data , -zlib.MAX_WBITS)

print decode_base64_and_inflate(str)</code></pre>
<h4>PHP解码实现</h4>
<p>PHP一定要使用gzinflate才能解压纯Deflate流，gzuncompress是用来解压HTTP采用“deflate”方式压缩的数据的！</p>
<pre><code>$data = file_get_contents("data.txt");

echo $data;
echo gzinflate(base64_decode($data));

// this command will throw an exception: data error
// echo gzuncompress(base64_decode($data));
</code></pre>
<h4>JAVA实现</h4>
<p><a href="https://gist.github.com/1638963#file_inflate.java">JAVA实现</a>好累啊，这么罗嗦。还需要一个额外的一个JAR进行Base64操作。这里用的是Apache的<a href="http://commons.apache.org/codec/download_codec.cgi">Common Codec</a>。</p>
<pre><code>//http://stackoverflow.com/questions/3932117/handling-http-contentencoding-deflate

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.util.zip.DataFormatException;
import java.util.zip.Inflater;
import java.util.zip.InflaterInputStream;
import java.util.zip.ZipException;

import org.apache.commons.codec.binary.Base64InputStream;

public class Inflate {

    public static void inflate(File compressed, OutputStream out) throws IOException {
        InputStream in = new InflaterInputStream(new Base64InputStream(new FileInputStream(compressed)), new Inflater(true));
        shovelInToOut(in, out);
        in.close();
        out.close();
    }

    /**
     * Shovels all data from an input stream to an output stream.
     */
    private static void shovelInToOut(InputStream in, OutputStream out) throws IOException {
//    The following code should be working but it produces strange suffix like "[B@13fcf0ce" now and then
//        byte[] buffer = new byte[1000];
//        int len;
//        while((len = in.read(buffer)) > 0) {
//            out.write(buffer, 0, len);
//            System.out.println(buffer);
//        }

        String line;
        InputStreamReader isr = new InputStreamReader(in);
        BufferedReader br = new BufferedReader(isr);
        while((line = br.readLine()) != null) {
            out.write(line.getBytes());
        }
    }

    /**
     * Main method to test it all.
     */
    public static void main(String[] args) throws IOException, DataFormatException {
        File compressed = new File("data.txt");
        try {
            inflate(compressed, System.out);
        } catch(ZipException e) {
            System.out.println(e.getMessage());
            throw e;
        }
    }

}</code></pre>
<h3>总结</h3>
<p>编码问题总是一个挥之不去的阴影！</p>
]]></content:encoded>
			<wfw:commentRss>http://pagetalks.com/2012/01/19/wrangling-with-deflate-base64-and-gzip.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>More About Crypto Module In NodeJS</title>
		<link>http://pagetalks.com/2012/01/18/more-about-crypto-module-in-nodejs.html</link>
		<comments>http://pagetalks.com/2012/01/18/more-about-crypto-module-in-nodejs.html#comments</comments>
		<pubDate>Wed, 18 Jan 2012 05:31:45 +0000</pubDate>
		<dc:creator>Robin</dc:creator>
				<category><![CDATA[Javascript]]></category>
		<category><![CDATA[NodeJS]]></category>
		<category><![CDATA[cryptography]]></category>
		<category><![CDATA[js]]></category>
		<category><![CDATA[node]]></category>
		<category><![CDATA[nodejs]]></category>

		<guid isPermaLink="false">http://pagetalks.com/?p=545</guid>
		<description><![CDATA[在上一篇文章里讨论了crypto模块里的一些常用方法。在0.6系列以后，crypto模块的改动非常大，增添了DiffieHellman、pbkdf2、randomByes三大块。这三个也是非常有趣的东西，在这里和大家分享一下。 Di... ]]></description>
			<content:encoded><![CDATA[<p>在<a href="http://pagetalks.com/2012/01/14/crypto-module-and-security-in-nodejs.html">上一篇文章</a>里讨论了crypto模块里的一些常用方法。在0.6系列以后，crypto模块的改动非常大，增添了DiffieHellman、pbkdf2、randomByes三大块。这三个也是非常有趣的东西，在这里和大家分享一下。</p>
<h3><a href="http://en.wikipedia.org/wiki/Diffie–Hellman_key_exchange">DiffieHellman</a></h3>
<p>看着名字很恐怖，不过却是很有趣的东西。有没有想过，大家彼此不告诉对方敏感信息，确能够共同知道一个密钥？DiffieHellman算法就可以实现这一点。</p>
<p><quote>The Diffie–Hellman <strong>key exchange method</strong> allows two parties that have no prior knowledge of each other to <strong>jointly establish a shared secret key</strong> over an <strong>insecure communications</strong> channel.</quote></p>
<p>亮点在加粗的部分，密钥交换方法，而且不需要走https或者其他专用链接。<a href="http://en.wikipedia.org/wiki/Diffie–Hellman_key_exchange">wiki文章</a>已经对该算法做了很详细的解释，我也仿造这里面的说明，做了一个简单的实现，希望能阐明其工作方式。NodeJS的实现有如下特点：</p>
<ul>
<li>算法中的初始根(Primitive Root)始终是2</li>
<li>算法中可以帮你选择Private Integer和Private Key，详见<a href="http://nodejs.org/docs/v0.6.7/api/crypto.html#diffieHellman.generateKeys">generateKEys方法</a></li>
<li>如果不指明，生成的密钥、公钥全都是binary格式</li>
<li>我本想实现wiki中的那个例子，可是node的实现似乎不允许使用那么小的prime；手工计算A、B的合法性、可能性似乎也值的怀疑</li>
</ul>
<pre><code>var crypto = require("crypto"),
    Buffer = require("buffer").Buffer;

var alice, bob, A, a, B, b, p, s1, s2;

alice = crypto.createDiffieHellman(8);//using a 8 bits length prime
A = alice.generateKeys("hex");
a = alice.getPrivateKey("hex");//this is secret
p = alice.getPrime("hex");
console.log("Public Key of alcie: ", alice.getPublicKey("hex"), A);
console.log("Private Key of alice: ", a);
console.log("Prime: ", p); 

//sending p and A to Bob
bob = crypto.createDiffieHellman(p, "hex");//Bob should use the same prime
B = bob.generateKeys("hex");
b = bob.getPrivateKey("hex");
console.log("Public Key of bob", bob.getPublicKey("hex"), B);
console.log("Private Key of bob: ", b);
s2 = bob.computeSecret(A, "hex", "hex");

//sending B to Alice
s1 = alice.computeSecret(B, "hex", "hex");

console.log("Shared Secret:", s1, s2);

</code></pre>
<p><span id="more-545"></span><br />
<h3>PBKDF2</h3>
<p>PBKDF(Password-Based Key Derivation Function)可以用来生成一个更高强度的密码。之前谈到的不管是对称加密（Symmetric Cryptography）、还是像RSA这样的非对称加密（Asymmetric Cryptography、Public－Key Cryptography），都会涉及一个“初始的密码”。这个密码用来生成更加复杂的其他密码。那么可以想像，如果这个“初始密码”能够被猜到或者被伪造，那么整个加密体系的就脆弱了。理论上来说，由人主观想出来的密码都可以通过一定次数的尝试试验出来——因为你不总是喜欢用有规律的字符（123456、65431、1q2w3e4r5t之类）、生日等来生成邮箱密码么？</p>
<p>node里面的PBKDF2是根据HMAC算法来进行制定次数的迭代运算，将用户指定的初始密码进行演化，得到指定长度的新密码。Wifi里用到的WPA、WPA2和Mac文件系统的FileVault都是使用的该算法来生成可用的密码。</p>
<p>我们可以用node的API来模拟WPA2里面的密钥生成，回调中的key是binary格式：</p>
<pre><code>var crypto = require("crypto"),
    Buffer = require("buffer").Buffer;

var passphrase, salt;

passphrase = "my-wifi-passcode";
salt = "wifi-ssid";

crypto.pbkdf2(passphrase, salt, 4096, 256, function(err, key) {
  //key is in binary
  console.log("Encrypt wifi networkd data with key :", new Buffer(key, "binary").toString("hex"));
});</code></pre>
<h3>randomBytes</h3>
<p>这个函数可以帮你生成伪随即数据，目前我知道的作用，大概是可以帮助自动化测试吧⋯⋯</p>
<p><a href="http://nodejs.org/docs/v0.6.7/api/crypto.html#randomBytes">node的文档</a>里面有同步和异步方法的实例，这里不在赘述。</p>
]]></content:encoded>
			<wfw:commentRss>http://pagetalks.com/2012/01/18/more-about-crypto-module-in-nodejs.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Immigrate  WordPress &#8211; A Real Case</title>
		<link>http://pagetalks.com/2012/01/14/immigrate-wordpress-a-real-case.html</link>
		<comments>http://pagetalks.com/2012/01/14/immigrate-wordpress-a-real-case.html#comments</comments>
		<pubDate>Sat, 14 Jan 2012 12:39:12 +0000</pubDate>
		<dc:creator>Robin</dc:creator>
				<category><![CDATA[Site Development]]></category>
		<category><![CDATA[Technique]]></category>
		<category><![CDATA[Wordpress]]></category>
		<category><![CDATA[linux]]></category>
		<category><![CDATA[mysql]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[server]]></category>

		<guid isPermaLink="false">http://pagetalks.com/?p=539</guid>
		<description><![CDATA[上个星期，我终于决定把elfvision.com上的博客移到robinqu.me这个新域名上。由于使用wordpress已经很多年了，前前后后也做过不少wordpress的维护工作了，自我感觉良好，不料这次遇到不少麻烦，遂整... ]]></description>
			<content:encoded><![CDATA[<p>上个星期，我终于决定把elfvision.com上的博客移到robinqu.me这个新域名上。由于使用wordpress已经很多年了，前前后后也做过不少wordpress的维护工作了，自我感觉良好，不料这次遇到不少麻烦，遂整理成文。</p>
<p>分析一下我这里的案例，其实是要做以下这些事情（原站点简称A，新站点简称B）：</p>
<ol>
<li>制作A在B下的镜像</li>
<li>修改B下面的镜像Wordpress中域名设置</li>
<li>对A站相关地址进行转向到B站域名</li>
<li>清理A站的旧文件</li>
<li>在A站和B站上同时发出公告，安抚、告知用户</li>
</ol>
<h3>制作站点镜像</h3>
<p>开头很重要，如果你的站点无时无刻有访问量并产生数据，你面临两种选择：1、临时关闭站点，挂出维护通知 2、站点切换后再灌入差量数据。所幸，Wordpress站点往往基本没有实时数据，我的做法是没有关闭原站，而是直接复制数据的。</p>
<p>假设你可以登陆你的服务器，那么直接远程SSH就可以完成对文件的复制，即将wordpress站点的根目录直接复制到新位置。如果不能，那么就通过FTP慢慢拖吧⋯⋯</p>
<p>至于数据库部分，如果安装了phpMyAdmin，可以登陆到管理后台，直接选中数据库，然后在Operation选项卡中有Copy database的功能。</p>
<p>否则就得登陆mysql客户端，执行mysqldump了：</p>
<pre><code>mysqldump [db1] -u root -ppassword --add-drop-table | mysql [db2] -u root -ppassword</code></pre>
<p><span id="more-539"></span><br />
<h3>切换域名</h3>
<p>到这个时候，如果的B站点域名指向和主机配置都正确，访问B站应该被转向了A站，因为B站内部的wordpress设置的域名还是A站的。</p>
<p>切换域名分两方面：</p>
<ul>
<li>WordPress配置内部的域名设置</li>
<li>文章内容里面的链接内容</li>
</ul>
<p>对于前者，有两种办法，一种是直接登陆MYSQL，用SQL修改wp-options表的两条纪录，即siteurl和home的option_value。</p>
<pre><code>UPDATE  `wp`.`wp_options` SET  `option_value` =  'new_site_url' WHERE  `wp_options`.`option_name` ='home';
UPDATE  `wp`.`wp_options` SET  `option_value` =  'new_wordpress_url' WHERE  `wp_options`.`option_name` ='siteurl';
</code></pre>
<p>或者修改B站下面的wp_config.php，加入如下两句：</p>
<pre><code>define('WP_SITEURL', 'new_wordpress_url');
define('WP_HOME', 'new_site_url');</code></pre>
<h3>对A站的地址进行转向</h3>
<p>不少注重SEO的同学应该会单行A域名的PR值突然下降，那么我们可以设置nginx或者Apache的主机配置文件对以往链接进行304转向，将伤害降到尽可能小。</p>
<pre><code>server {
    listen 80;
    server_name www.siteB.com;

    index index.php index.html;
    root /var/www/siteB;

    location / {
        error_page 404 = /index.php?q=$uri;
		<strong>rewrite ^/(\d+)/(\d+)/(.*)$ http://siteB$request_uri permanent;</strong>
    }

}</code></pre>
<p>注意替换rewrite规则中的域名。详细文档：<a href="http://wiki.nginx.org/HttpRewriteModule#rewrite">http://wiki.nginx.org/HttpRewriteModule#rewrite</a>。</p>
<p>rewrite后面接受的第一个参数是url中path部分的正则匹配，这个正则应该按照之前wordpress的permanent链接的模式来写，最后一个参数permanent表示是304永久重定向。</p>
<p>注意，如果你是将Wordpress程序在同一个域名中迁移，例如从http://siteA.com/blog/迁移到http://siteA.com/，那么你可以尝试nginx的try_files规则：<a href="http://wiki.nginx.org/HttpCoreModule#try_files">http://wiki.nginx.org/HttpCoreModule#try_files</a>。</p>
<p>有一篇文章专门降到了利用这个指令来进行同域名不同虚拟目录的迁移：<a href="http://michaelshadle.com/2009/03/19/finally-using-nginxs-try-files-directive">http://michaelshadle.com/2009/03/19/finally-using-nginxs-try-files-directive</a>。</p>
<p>对于文章内部的链接数据，则要再次登陆MYSQL控制台或phpMyAdmin进行操作了。</p>
<p><code>
<pre>UPDATE wp_posts SET guid = replace(guid, 'http://www.siteA.com','http://www.siteB.com');
UPDATE wp_posts SET post_content = replace(post_content, 'http://www.siteA.com', 'http://www.siteB.com');
</pre>
<p></code></p>
<h3>清理旧文件</h3>
<p>登陆FTP或者SSH执行文件删除吧，不过个人建议不要删除，把所有文件都备份到另一个目录，带迁移成功后的一两个月之后再考虑是否删除。这样可以避免突发事件时的尴尬。</p>
<h3>通知用户</h3>
<p>好吧， 在两边都贴上大大的告示吧，以免用户感到困惑⋯⋯</p>
<h3>Next…</h3>
<p>我更新了另外一篇文章来阐述node里面的一些新加入crypto相关的API：<a href="http://pagetalks.com/2012/01/18/more-about-crypto-module-in-nodejs.html">http://pagetalks.com/2012/01/18/more-about-crypto-module-in-nodejs.html</a></p>
]]></content:encoded>
			<wfw:commentRss>http://pagetalks.com/2012/01/14/immigrate-wordpress-a-real-case.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Crypto Module and Security In NodeJS</title>
		<link>http://pagetalks.com/2012/01/14/crypto-module-and-security-in-nodejs.html</link>
		<comments>http://pagetalks.com/2012/01/14/crypto-module-and-security-in-nodejs.html#comments</comments>
		<pubDate>Sat, 14 Jan 2012 11:09:14 +0000</pubDate>
		<dc:creator>Robin</dc:creator>
				<category><![CDATA[Javascript]]></category>
		<category><![CDATA[NodeJS]]></category>
		<category><![CDATA[https]]></category>
		<category><![CDATA[js]]></category>
		<category><![CDATA[nodejs]]></category>
		<category><![CDATA[security]]></category>

		<guid isPermaLink="false">http://pagetalks.com/?p=535</guid>
		<description><![CDATA[NodeJS已经在各个领域都有应用了，大部分敏感数据在公共网络上传播时都会遇到各种安全问题，在分布式系统上尤为明显。J2EE等主流应用框架都已经对HTTPS、各种加密算法有了良好支持，但是... ]]></description>
			<content:encoded><![CDATA[<p>NodeJS已经在各个领域都有应用了，大部分敏感数据在公共网络上传播时都会遇到各种安全问题，在分布式系统上尤为明显。J2EE等主流应用框架都已经对HTTPS、各种加密算法有了良好支持，但是对于新型的NodeJS引擎，其版本号甚至没有达到“1”却已经掀起了业界的改革狂潮。周末抽空，来整整NodeJS里面加密和HTTPS的相关只是，希望对大家有用。</p>
<p>其实NodeJS的相关实现就是把C的库套了一层壳，那么JS下面的openssl等模块其实就代表了NodeJS相关的功能和性能了。编译NodeJS时务必要安装openssl模块，否则crypto模块基本不可用。</p>
<p>该文章的所有代码实例均是Node 0.6.4、OpenSSL 0.9.8r on MacOS Lion 64bit。演示代码的源代码都在Github上：<a href="https://github.com/RobinQu/crypto-demo">https://github.com/RobinQu/crypto-demo</a></p>
<p><a href="http://www.flickr.com/photos/28352704@N06/6693641209" title="View 'openssl-commands' on Flickr.com"><img border="0" style="display:block; margin-left:auto; margin-right:auto;" height="421" src="http://farm8.staticflickr.com/7155/6693641209_f2fa5a1f68_o.png" alt="openssl-commands" width="560" title="openssl-commands"/></a></p>
<h3>Hash算法</h3>
<p>先从最基本的Hash算法说起。以当前平台为例，支持的Digest算法不少，最常用的MD5、SHA1等。大家都知道我们这用的Hash都是不可逆的，所以这是少数几个不成对出现的方法之一，详细文档：<a href="http://nodejs.org/docs/v0.6.4/api/crypto.html#crypto.createHash">http://nodejs.org/docs/v0.6.4/api/crypto.html#crypto.createHash</a>。</p>
<pre></code>var crypto = require("crypto");

var md5hash, result;
//Warning: MD5 collision is made easier and easier. Use SHA1 instead!
md5hash = crypto.createHash("md5");
md5hash.update("hello world!");
md5hash.update("hello nodejs!");
result = md5hash.digest("hex");
console.log(result); //7d962c953bb09058460f3f47650b1ab2
</code></pre>
<h3>HMAC算法</h3>
<p>以前我也不怎么用HMAC，所以临时<a href="http://en.wikipedia.org/wiki/HMAC">wiki</a>了一下。HMAC结果通常是带私钥两次hash后的一串字符，用来同时验证数据完整性(data integrity)和真实性(authenticity)。wiki里面的图表什么的已经说的相当详细了。NodeJS里面的相关文档：<a href="http://nodejs.org/docs/v0.6.4/api/crypto.html#crypto.createHmac">http://nodejs.org/docs/v0.6.4/api/crypto.html#crypto.createHmac</a>。</p>
<pre><code>var crypto = require("crypto");

var hmac, result;
hmac = crypto.createHmac("sha1", "i'm a secret!");

hmac.update("hello world!");
hmac.update("hello nodejs!");
result = hmac.digest("hex");
console.log(result); //9e7b9239f03d2e03cb041a8518977ac84ab4c9b9
</code></pre>
<p><span id="more-535"></span><br />
<h3>对称加密算法</h3>
<p>如果跟我一样不是安全专家的话，建议读一下MS上的一篇<a href="http://support.microsoft.com/kb/246071/zh-cn">启蒙文章</a>。</p>
<p>从这里开始，NodeJS的方法都是成对出现了。NodeJS相关文档：<a href="http://nodejs.org/docs/v0.6.4/api/crypto.html#crypto.createCipher">http://nodejs.org/docs/v0.6.4/api/crypto.html#crypto.createCipher</a>。</p>
<p>这里的加密操作要注意输入、输出的编码；早期版本的node对于<a href="https://github.com/joyent/node/issues/738/">base64编码</a>的输出有bug。所以，请尽量使用hex编码输出。</p>
<p>API文档里要求password是binary格式，但是我直接给了一个utf8的字符串也没事⋯⋯</p>
<pre><code>var crypto = require("crypto");

var cipher, ciphered, decipher, deciphered, password;

password = "i'm the password";

cipher = crypto.createCipher("aes-256-ecb", password);
//important! Update cipher content in "utf8" encoding; To be transformed to a "hex" string!
ciphered = cipher.update("hello world!", "utf8", "hex");
ciphered += cipher.update("hello nodejs!", "utf8", "hex");
ciphered += cipher.final("hex");
console.log(ciphered);//6cc3c04d5fc076ba32b742e1f3439880adbe251e87fe0be9f42a16be4d69a4b8

decipher = crypto.createDecipher("aes-256-ecb", password);
//important! Update decipher content is "hex" string; To be transfromed to a "utf8" result!
deciphered = decipher.update(ciphered, "hex", "utf8");
deciphered += decipher.final("utf8");
console.log(deciphered);//hello world!hello nodejs!
</code></pre>
<p>这一对方法有一个小细节，就是还有一对createCipheriv和createDecipheriv方法。后者需要多传一个iv。什么是iv呢？果断wiki啊：<a href="http://en.wikipedia.org/wiki/Initialization_vector">Initialization Vector</a>。简单的说，这个iv就是一组随即信息，根据这组信息生成的key在对称加密过程中更加安全。事实上，createCipher和createDecipher的第二个参数password并不是直接作为对称加密的key，而是用来生成iv的。</p>
<h3>签名算法</h3>
<p>这个可能大家更常用一些，这个能够提供相比对称加密更高的安全级别。之前说的HMAC也是一种签名算法，但前者不涉及到非对称加密、CA等问题。NodeJS里面的createSign和createVerifier方法是需要配合公钥、私钥、CA一起使用的。</p>
<p>接下来的一些操作需要自己生成公钥、私钥。可能需要参考一些openssl命令：<a href="http://www.madboa.com/geek/openssl/">http://www.madboa.com/geek/openssl/</a>。</p>
<p>以下例子将对内容进行RSA-SHA256算法进行签名，该算法足以应付大部分的安全需求。该算法以<a href="http://dkim.org/specs/rfc4871-dkimbase.html#FIPS.180-2.2002">SHA256</a>进行digest，再将结果以<a href="http://dkim.org/specs/rfc4871-dkimbase.html#RFC3447">RSA</a>算法进行加密，得到的密文作为签名。</p>
<pre><code>openssl genrsa -out mykey.pem 1024
openssl rsa -in mykey.pem -pubout > mypub.pem
</code></pre>
<p>这两个命令生成了一个1024位的公钥、私钥对。Node里面对内容签名的流程很清晰，diegest后用私钥加密，密文为签名。验证流程需要传入正文内容、公钥、签名，verify方法返回一个标识验证是否成功的布尔值标识签名和内容是否匹配。</p>
<pre><code>var crypto = require("crypto"),
    fs = require("fs");

var signer, sign, verifier, privateKey, publicKey, result;

privateKey = fs.readFileSync("mykey.pem", "utf8");
publicKey = fs.readFileSync("mypub.pem", "utf8");

signer = crypto.createSign("RSA-SHA256");
signer.update("hello world!");
signer.update("hello node!");
sign = signer.sign(privateKey, "hex");
console.log(sign);//82071cf4bf80e8c0445351102444ce144017054c25e4832cc050a580aa13035526d432d74a33ebc8aee81376d51e0372a75bc796ece3cadea16bff58b15eccc4e7399c21e0391353c7855391e19cf48aefa148aeb73d1ad4e4e2af156d14da2a84bbf616d18353b0bb4d3570f513056c2edb20af9dfcba71695221f16f4ef182

verifier = crypto.createVerify("RSA-SHA256");
verifier.update("hello world!");
verifier.update("hello node!");
result = verifier.verify(publicKey, sign, "hex");
console.log(result);//true</code></pre>
<h3>HTTPS模块</h3>
<p>打开node的<a href="https://github.com/joyent/node/blob/master/lib/https.js">https模块的源码</a>，你会发现，这是tls模块的一个轻度封装。</p>
<p>最新版的Node已经对tls服务的支持相当不错了（node 0.4左右版本就是惨不忍睹的），文档里已经有非常丰富的例子：<a href="http://nodejs.org/docs/v0.6.7/api/tls.html">http://nodejs.org/docs/v0.6.7/api/tls.html</a></p>
<p>大家可以根据里面的代码利用X509自签名证书来做出来一个tls服务器、客户端的例子。这里就不在赘述。</p>
]]></content:encoded>
			<wfw:commentRss>http://pagetalks.com/2012/01/14/crypto-module-and-security-in-nodejs.html/feed</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Flickry &#8211; Step By Step to Create A Flickr App, Chapter 4</title>
		<link>http://pagetalks.com/2011/02/20/flickry-step-by-step-to-create-a-flickr-app-chapter-4.html</link>
		<comments>http://pagetalks.com/2011/02/20/flickry-step-by-step-to-create-a-flickr-app-chapter-4.html#comments</comments>
		<pubDate>Sun, 20 Feb 2011 12:12:04 +0000</pubDate>
		<dc:creator>Robin</dc:creator>
				<category><![CDATA[HTML5]]></category>
		<category><![CDATA[SproutCore]]></category>
		<category><![CDATA[route]]></category>
		<category><![CDATA[sproutcore]]></category>
		<category><![CDATA[url]]></category>

		<guid isPermaLink="false">http://pagetalks.com/?p=527</guid>
		<description><![CDATA[In the previous chapter, we finally make flickry to work in the wild. But this doesn&#8217;t satisfy me, there are still some exciting improvements that could be done! In this chapter, we will discuss about url routing or history management in this app. ... ]]></description>
			<content:encoded><![CDATA[<p><a href="http://www.w3.org/html/logo/" class="alignleft"><br />
<img src="http://www.w3.org/html/logo/badge/html5-badge-h-css3-performance-semantics-storage.png" width="229" height="64" alt="HTML5 Powered with CSS3 / Styling, Performance &amp; Integration, Semantics, and Offline &amp; Storage" title="HTML5 Powered with CSS3 / Styling, Performance &amp; Integration, Semantics, and Offline &amp; Storage"></a></p>
<p>In the previous chapter, we finally make flickry to work in the wild. But this doesn&#8217;t satisfy me, there are still some exciting improvements that could be done!</p>
<p>In this chapter, we will discuss about url routing or history management in this app.</p>
<h3>Save The URL</h3>
<p>Web pages or apps are url-based. However, Sproutcore apps are single-page app whose url never changes. This feature may sometimes challenge the habbit of users.</p>
<p>Apps like Gmail have already been enhanced for url routing support, which can be used to navigate inside app with the string after &#8220;#&#8221;. e.g.</p>
<p><strong>http://yourapp.com/<em>#controller/action/parameter</em></strong></p>
<p>The content of emphasized string won&#8217;t make browser to refresh or go to any other address, but it&#8217;s useful to send state info to your app in order to decide which page or state you really want to see.</p>
<p>And users can really bookmark your url and click the back and forward buttons of browser!</p>
<p>In Sproutcore, url routing can be done with the help from SC.routes.</p>
<h3>Important Changes</h3>
<p>Before going any further, I am afraid that you have to do some patches on the existing files due to my own negligence. </p>
<p>In short, I&#8217;ve fixed a lot wired problems in Flickry and forgot to track down the minor changes to the project. You&#8217;d better pull the latest files from <a href="https://github.com/RobinQu/FlickrySteps/tree/chp4">brach chp4</a>.</p>
<p>Now I&#8217;m going to explain some of the most crucial ones.<span id="more-527"></span><br />
<h4>Supplement More Fields in Model</h4>
<p>So far as the model is concerned, we only use limited number of fields available from Flickr. We added some more fields to Flickry.Photo:</p>
<pre>Flickry.Photo = SC.Record.extend(
/** @scope Flickry.Photo.prototype */ {

  primaryKey: "id",//SC default to "guid", but inside flickr response we only have "id" field
  farm: SC.Record.attr(Number),
  server: SC.Record.attr(Number),
  secret: SC.Record.attr(String),
  photo: SC.Record.attr(Number, {key: "id"}),
  title: SC.Record.attr(String),
  ownername: SC.Record.attr(String),
  tags: SC.Record.attr(String),
  searchIndex: function() {//for full text search
    return this.get("title") + this.get("tags");
  }.property("title", "tags").cacheable(),
  url: function() {
    return "http://farm%@1.static.flickr.com/%@2/%@3_%@4_s.jpg".fmt(this.get("farm"), this.get("server"), this.get("photo"), this.get("secret"));
  }.property("farm", "server", "photo", "secret").cacheable()//produce url for me!

});</pre>
<h4>Filter Using SC.Query Instead</h4>
<p>Remember we manually process the response to filter by username? Now, we build SC.Query with query conditions instead. No extra human interference needed.</p>
<h4>Correct The Result Set</h4>
<p>We built the query object like</p>
<pre>SC.Query.local(Flickry.Photo);</pre>
<p>This was not correct query object here. So every time we searched, we got all photos in the local datastore.</p>
<h4>More&#8230;</h4>
<p>There are many more fixs. You can run diff if you are interested. Let&#8217;s move on to talk about the URL route. </p>
<h3>Listen To Hash Changes</h3>
<p>SC.routes are a simple SC object that contains some useful methods and properties. Detailed docs and examples are available on <a href="http://docs.sproutcore.com/">http://docs.sproutcore.com/</a>, which haven&#8217;t been updated for years.</p>
<p>Let&#8217;s begin by adding listener to url hash changes.</p>
<p>Open flickry/core.js, add the following line at the end of this file:</p>
<pre>SC.routes.add(":action", Flickry, "routeDidChange");</pre>
<p>SC.routes.add method simply register a url hash pattern it will listen to. Here is a comment snippet from foundation/system/routes.js:</p>
<pre>    Adds a route handler. Routes have the following format:
      - 'users/show/5' is a static route and only matches this exact string,
      - ':action/:controller/:id' is a dynamic route and the handler will be
        called with the 'action', 'controller' and 'id' parameters passed in a
        hash,
      - '*url' is a wildcard route, it matches the whole route and the handler
        will be called with the 'url' parameter passed in a hash.

    Route types can be combined, the following are valid routes:
      - 'users/:action/:id'
      - ':controller/show/:id'
      - ':controller/ *url' (ignore the space, because of jslint)</pre>
<p>It explains a lot about the &#8220;:&#8221; prefix of the first parameter I passed.</p>
<p>The other two parameters means that I want SC.routes to call Flickry. routeDidChange method whenever url hash is changed with the given pattern.</p>
<h3>Routing Handlers</h3>
<p>Before going on to implement the route method on Flickry. We have to think about the relationships between url routing and state machine.</p>
<p>We have our states ready already and it&#8217;s reasonable to lead us into a certain state by url hash. </p>
<p>Think again!</p>
<p>Alternatively url hash can be updated after state changes.</p>
<p><a href="http://www.flickr.com/photos/28352704@N06/5461370190" title="View 'Route Policy' on Flickr.com"><img border="0" style="display:block; margin-left:auto; margin-right:auto;" height="701" src="http://farm6.static.flickr.com/5255/5461370190_4330a410e6_o.png" alt="Route Policy" width="400" title="Route Policy"/></a></p>
<p>The later was first realized in the early version of Flickry in this repo: <a href="https://github.com/RobinQu/flickry">https://github.com/RobinQu/flickry</a></p>
<p>The former method can be found in the Unit Test App inside Sproutcore itself.</p>
<p>See the code in abbot: <a href="https://github.com/sproutcore/sproutcore/tree/master/apps/tests">https://github.com/sproutcore/sproutcore/tree/master/apps/tests</a></p>
<p>The core concept is that we don&#8217;t want to mixup the state machine and url router.</p>
<p>That means without url router, state machine should still work properly.</p>
<p>Obviously, building url router upon state machine is much easier as you can see in my repo above. However I am going to implement url router in a way that url router works as a kind of enhancement to state machine. Technically, it will not and should not affect present mechanism of state machine.</p>
<p>Save some words for review of state machine we have accomplished in the last chapter. Let&#8217;s see the detail of url routing upon the state machine.</p>
<p>Right here, state machine only needs to notify the app to update the location url. So in the entry point of every state, we call:</p>
<pre>Flickry.updateRoute(<em>urlHash</em>, <em>parameter</em>)</pre>
<p>which we will implement on Flickry later.</p>
<p>Suppose the state machine have done the right things to maintain the url in the location bar. You can now only care about the url driven interactions.</p>
<p>The workflow the url router can be (hopefully) explained in the graph below: </p>
<p><a href="http://www.flickr.com/photos/28352704@N06/5461359684" title="View 'route-flowchart' on Flickr.com"><img border="0" style="display:block; margin-left:auto; margin-right:auto;" height="1044" src="http://farm6.static.flickr.com/5179/5461359684_2796487217_o.png" alt="route-flowchart" width="438" title="route-flowchart"/></a></p>
<p>It&#8217;s a bit tricky. I am sure you will cost hours to understand and you&#8217;d better read the <a href="https://github.com/RobinQu/FlickrySteps/blob/chp4/apps/flickry/core.js">code</a> and play with it yourself.</p>
<p>So I decide that I shall save some words here and list some hints:</p>
<ul>
<li>START state is deferred so that the first route direction (if any) is captured</li>
<li>Flickry.pendingRoute is an important switch to avoid repeat Flickry.updateRoute</li>
<li>You may find some bugs regarding url router&#8230;&#8230;</li>
</ul>
<p>Thankfully, this app is now functional with all features I have promised at the first chapter.</p>
<p>I will maintain this app and enhance it with more features in the near future. It&#8217;s not the end of this tutorial.</p>
<p>Just want some discussions here!</p>
]]></content:encoded>
			<wfw:commentRss>http://pagetalks.com/2011/02/20/flickry-step-by-step-to-create-a-flickr-app-chapter-4.html/feed</wfw:commentRss>
		<slash:comments>9</slash:comments>
		</item>
		<item>
		<title>Flickry &#8211; Step By Step to Create A Flickr App, Chapter 3</title>
		<link>http://pagetalks.com/2011/02/09/flickry-step-by-step-to-create-a-flickr-app-chapter-3.html</link>
		<comments>http://pagetalks.com/2011/02/09/flickry-step-by-step-to-create-a-flickr-app-chapter-3.html#comments</comments>
		<pubDate>Wed, 09 Feb 2011 08:17:56 +0000</pubDate>
		<dc:creator>Robin</dc:creator>
				<category><![CDATA[HTML5]]></category>
		<category><![CDATA[SproutCore]]></category>
		<category><![CDATA[flickr]]></category>
		<category><![CDATA[json]]></category>
		<category><![CDATA[sproutcore]]></category>

		<guid isPermaLink="false">http://pagetalks.com/?p=525</guid>
		<description><![CDATA[Bad Luck! I forgot to save the draft before quitting my blog editor. Tragically, I wrote this article twice. It also upsets me that there were a lot post views but no responses. Well, in this chapter we are going to make this app actually search the flic... ]]></description>
			<content:encoded><![CDATA[<p><a href="http://www.w3.org/html/logo/" class="alignleft"><br />
<img src="http://www.w3.org/html/logo/badge/html5-badge-h-css3-performance-semantics-storage.png" width="229" height="64" alt="HTML5 Powered with CSS3 / Styling, Performance &amp; Integration, Semantics, and Offline &amp; Storage" title="HTML5 Powered with CSS3 / Styling, Performance &amp; Integration, Semantics, and Offline &amp; Storage"></a></p>
<p>Bad Luck! I forgot to save the draft before quitting my blog editor. Tragically, I wrote this article twice.</p>
<p>It also upsets me that there were a lot post views but no responses.</p>
<p>Well, in this chapter we are going to make this app actually search the flickr database other than fetch fixtures locally. In addition, we will add support for url routing, which makes our app looks more interesting.</p>
<h3>Proxy, Proxy, Proxy</h3>
<p>Everyone knows same-origin policy in the browser sandbox. It is such an old friend to a web developer that we&#8217;ve got many workarounds, JSONP, YQL, etc.</p>
<p>For the time being, I don&#8217;t want make things complicated. I just want to take advantages of sc-server to proxy my responses to remote flickr server.</p>
<p>It&#8217;s pretty easy to configure sc-server with some proxy rules to help us achieve cross-domain ajax requests.</p>
<p>Open Buildfile in the root folder of our project, add the following code at the end.</p>
<pre>proxy '/services', :to => 'api.flickr.com'</pre>
<p>This rules instruct sc-server to route any requests heading for &#8220;/services&#8221; to re-route to &#8220;api.flickr.com&#8221;.<br />
<span id="more-525"></span><br />
<h3>Handling DataSource</h3>
<p>It&#8217;s time to create our own datasource. In this app, we don&#8217;t have too many queries. Generally speaking, getting the photo stream from flickr can be achieved by a simple GET request. So, our job is to take care of fetch method and its callback in our own datasource.</p>
<p>Open flickry/data_sources/flickr.js and edit:</p>
<pre>/*global SC, Flickry */
Flickry.FlickrDataSource = SC.DataSource.extend({
  api: "/services/rest/?api_key={your_api_key}&amp;format=json&amp;nojsoncallback=1&amp;method=flickr.photos.search&amp;extras=owner_name",

  fetch: function(store, query) {
    console.log("Fetching data");
    if(query === Flickry.PHOTO_QUERY) {
      var url = this.get("api");
      if(query.get("keyword")) {
        url+= "&amp;text=%@".fmt(query.get("keyword"));
      }
      SC.Request.getUrl(url).json()
                .notify(this, 'didFetchPhotos', store, query)
                .send();
      return YES;
    }
    return NO;
  }
});</pre>
<p>To put it simple, we directly subclass from SC.DataSource. Of course, you need a flickr api key in api path.</p>
<p>Inside fetch method, we check query type and keyword, and then fire the request. We also register a callback &#8220;didFetechPhotos&#8221; on datasource itself. When we receive any response, this method will be invoked.</p>
<p>Needless to say, in didFetechPhotos method, we are going to manipulate the json response from flickr to make it usable in our app, and then load into our data store.</p>
<p>Apart from that, we have to implement the username filter here because I can&#8217;t find any search conditions related to username in flickr api. Add this method to Flickry.FlickrDataSource:</p>
<pre>  didFetchPhotos: function(response, store, query) {
    var data, len, user = Flickry.searchController.get("userName"), i, cur, result = [];
    if(SC.ok(response)) {
      try {
        data = response.get("body").photos.photo;
      } catch(e) {
        data = [];
      }
      len = data.length;

      for(i=0;i&lt;len;i++) {
        cur = data[i];
        if(user&#038;&#038; (cur["ownername"].indexOf(user) === -1)) {//filter username
          continue;
        }
        result.push(cur);
        data.date = new Date(data.date);
      }

      store.loadRecords(Flickry.Photo, result);
      store.dataSourceDidFetchQuery(query);

    } else {
      store.dataSourceDidErrorQuery(query, response);
    }
  }</pre>
<p>It&#8217;s worth noting that SC.ok() works properly only if the response is returned by a real http server. And it&#8217;s absolutely necessary to call loadRecords to store the json data into SC.Store and dataSourceDidFetchQuery to make other magic of datastore frameworks happen.</p>
<h3>Use It and Test It</h3>
<p>Our flickr datasource just lies there. We have to do some extra work before we can benefit from it.</p>
<p>First, we should change our datasource to Flcikry.FlickrDataSource. Open flickry/core.js and locate:</p>
<pre>store: SC.Store.create().from(SC.Record.fixtures)</pre>
<p>Replace this line with:</p>
<pre>store: SC.Store.create().from("Flickry.FlickrDataSource")</pre>
<p>Next, we are going to change the way our app searches.</p>
<p>Open flickry/states/search.js and find search method. If you have read about Datastore frameworks, you should have known that the record array is immediately returned with no actually contents after you find with a query object. It just waits its status to be changed and load its actual contents. To follow this pattern, we have to rewrite search method:</p>
<pre>  submit: function() {
    Flickry.photosController.set("isLoading", YES);//will show "loading"
    var query, photos;
    query = Flickry.searchController.get("content");
    Flickry.PHOTO_QUERY = SC.Query.local(Flickry.Photo, query);
    photos = Flickry.store.find(Flickry.PHOTO_QUERY);
    Flickry.photosController.set("content", photos);
    <strong>photos.addObserver("status", this, this.photosStatusDidChange);
    this.set("photos", photos);
</strong>
    Flickry.makeFirstResponder(Flickry.STREAM);
  }</pre>
<p>As is shown, we add an observer to monitor changes of its status property and we store a reference to the record array on the responder.</p>
<p>You may notice we added a callback that will invoked whenever the status is changed. After appending the callback function, this file should look like: </p>
<pre>/*global Flickry, SC */
Flickry.SEARCH = SC.Responder.create({

  didBecomeFirstResponder: function(){
    console.log("STATE: enter SEARCH");

    var pane;
    pane = Flickry.getPath('searchPage.mainPane');
    pane.append(); // show on screen
    Flickry.photosController.set("content", null);//clear the photos before search
    pane.makeFirstResponder(pane.contentView.keyword); // focus first field
  },

  willLoseFirstResponder: function() {
    console.log("STATE: lose SEARCH");
    Flickry.getPath('searchPage.mainPane').remove();
  },

  submit: function() {
    Flickry.photosController.set("isLoading", YES);//will show "loading"
    var query, photos;
    query = Flickry.searchController.get("content");
    Flickry.PHOTO_QUERY = SC.Query.local(Flickry.Photo, query);
    photos = Flickry.store.find(Flickry.PHOTO_QUERY);
    Flickry.photosController.set("content", photos);
    photos.addObserver("status", this, this.photosStatusDidChange);
    this.set("photos", photos);

    Flickry.makeFirstResponder(Flickry.STREAM);
  },

  photosStatusDidChange: function() {
    var photos = this.get("photos"),
        status = photos.get("status");

    if(status &#038; SC.Record.READY) {
      Flickry.photosController.set("content", photos);
      console.log("finnal content:", photos);
      Flickry.photosController.set("isLoading", NO);
    } else {
      //error handling
    }
  },

  cancel: function(){
    Flickry.makeFirstResponder(Flickry.STREAM);
  }

});</pre>
<p>Open your browser and type the keywords. Have a try!</p>
]]></content:encoded>
			<wfw:commentRss>http://pagetalks.com/2011/02/09/flickry-step-by-step-to-create-a-flickr-app-chapter-3.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Flickry &#8211; Step By Step to Create A Flickr App, Chapter 2</title>
		<link>http://pagetalks.com/2011/02/07/flickry-step-by-step-to-create-a-flickr-app-chapter-2.html</link>
		<comments>http://pagetalks.com/2011/02/07/flickry-step-by-step-to-create-a-flickr-app-chapter-2.html#comments</comments>
		<pubDate>Mon, 07 Feb 2011 15:19:23 +0000</pubDate>
		<dc:creator>Robin</dc:creator>
				<category><![CDATA[HTML5]]></category>
		<category><![CDATA[SproutCore]]></category>
		<category><![CDATA[css3]]></category>
		<category><![CDATA[sproutcore]]></category>

		<guid isPermaLink="false">http://pagetalks.com/?p=519</guid>
		<description><![CDATA[In the last chapter, we have accomplished some skeleton of our app as well as the important model layer. In this chapter, I will guide you to complete the state management in this app and share some of my knowledge about state charts. Statecharts What ar... ]]></description>
			<content:encoded><![CDATA[<p><a href="http://www.w3.org/html/logo/" class="alignleft"><br />
<img src="http://www.w3.org/html/logo/badge/html5-badge-h-css3-performance-semantics-storage.png" width="229" height="64" alt="HTML5 Powered with CSS3 / Styling, Performance &amp; Integration, Semantics, and Offline &amp; Storage" title="HTML5 Powered with CSS3 / Styling, Performance &amp; Integration, Semantics, and Offline &amp; Storage"><br />
</a></p>
<p>In the last chapter, we have accomplished some skeleton of our app as well as the important model layer. In this chapter, I will guide you to complete the state management in this app and share some of my knowledge about state charts.</p>
<h3>Statecharts</h3>
<p>What are state charts? I got to know about state charts when learning about UML, unified modeling language. State chart is one of 23 classical diagrams. In fact, when you are creating model layer, chances are that you will already have used Class Diagram, which is another commonly used diagram of UML.</p>
<p>Of course, State charts exist prior to UML. It was invented by <a href="http://en.wikipedia.org/wiki/David_Harel">David Harel</a> with his outstanding paper, A Visual Formalism for Complex Systems around 1986.</p>
<p>Sproutcore implements of statecharts inside. All views and event system are driven by it. So, it&#8217;s advisable to employ state charts to help us to organize app in case of complicated transitions of various views,  concurrent status, etc.</p>
<p>There are three completely different statecharts frameworks for Sproutcore.</p>
<ul>
<li>SC.Responder and other related classes inside SC core</li>
<li>Statecharts of Erich Ocean in the master</li>
<li>Ki</li>
</ul>
<p><span id="more-519"></span>Responder is the easiest way to implement statecharts in your app. Statecharts are more powerful, but less object-oriented. Ki, is just another story, much more powerful, but in the same time, more difficult to use.</p>
<p>See some discussions about these three frameworks in this post: <a href="http://markmail.org/message/o3y4lv2mvokrdxhz#query:+page:1+mid:o3y4lv2mvokrdxhz+state:results">http://markmail.org/message/o3y4lv2mvokrdxhz#query:+page:1+mid:o3y4lv2mvokrdxhz+state:results</a></p>
<p>Before I interpret statecharts of Flcikry, you&#8217;d get familiar with knowledge in following articles:</p>
<ul>
<li><a href="http://blog.bruzilla.com/post/701083456/statecharts-and-responders-in-sproutcore-a-simple-case">Statecharts and Responders in SproutCore: a simple case</a></li>
<li><a href="http://blog.bruzilla.com/post/783010782/routes-and-responders-in-sproutcore-routes-app">Routes and Responders in SproutCore: the Routes sample app</a></li>
<li><a href="http://www.itsgotwhatplantscrave.com/2009/02/22/building-sproutcore-apps-with-statecharts-part-1/">Building Simple Apps with Statecharts, part 1</a></li>
<li><a href="http://www.itsgotwhatplantscrave.com/2009/02/22/building-sproutcore-apps-with-statecharts-part-2/">Building Simple Apps with Statecharts, part 2</a></li>
<li><a href="http://groups.google.com/group/sproutcore/msg/494e51d25ae3dfa3">How to use StateCharts in a SproutCore app</a></li>
</ul>
<h3>States of Flickry</h3>
<p>For this step, we will create three states of Flickry. Of course, in the real life, you app could have many more states to manage. For a later chapter, we will add some sub-states to make the story exciting.</p>
<p>Recall our mockup of this app. Click to see the full size.</p>
<p><a href="http://farm5.static.flickr.com/4150/5412424966_4f3904b2ab_o.png" title="View 'flickry-mockups' on Flickr.com"><img border="0" style="display:block; margin-left:auto; margin-right:auto;" height="500" src="http://farm5.static.flickr.com/4150/5412424966_08f73014f8.jpg" alt="flickry-mockups" width="384" title="flickry-mockups"/></a></p>
<p>It&#8217;s easy to find out that interactions in this mockup will trigger following events:</p>
<ul>
<li>
Search Submit: When you click on the &#8220;ok&#8221; button of the search dialog, the app will undergo a search action and waits for the search response.</li>
<li>Return from PhotoStream: When you click &#8220;return&#8221; button on the stream page. That means you want another search</li>
<li>Search Cancel: When you click &#8220;Cancel&#8221; button of the search dialog, the dialog will dismiss and enter stream but without any photos.</li>
</ul>
<p><a href="http://www.flickr.com/photos/28352704@N06/5420615158" title="View 'statecharts' on Flickr.com"><img border="0" style="display:block; margin-left:auto; margin-right:auto;" height="539" src="http://farm6.static.flickr.com/5131/5420615158_24218f0ed8_o.png" alt="statecharts" width="473" title="statecharts"/></a></p>
<p>The diagram above illustrates the flow of each states, which are driven by different events with the system.</p>
<h3>Meet Responders</h3>
<p>As mentioned before, we currently have three ways to implement state charts in SC. Considering the fact that statecharts are not the main theme of this tutorial, we choose the easiest way, SC.Responder. </p>
<p>In the world of Sproutcore, we not only have MVC, but also have three other friends, namely SDR. See more at: <a href="http://wiki.sproutcore.com/w/page/12412848/Basics-Introducing%20SproutCore%20MVC">http://wiki.sproutcore.com/w/page/12412848/Basics-Introducing%20SproutCore%20MVC</a></p>
<p>As is called, a responder can respond events dispatched by RootResponder or directly assigned by some special views.</p>
<p>Inside responder, we fill the logics of switching of views, changing the look and feel at a larger context, etc.</p>
<p>Let&#8217;s create a folder named responder, and then add three empty js files to represent three states in our app: start.js, stream.js ,search.js.</p>
<p>Now, the START state. It&#8217;s a transition state of this app, encountered when the app is loaded for the first time. It will check wether we have an existing queries. It seems a little meaningless at present, though.</p>
<p>Open and edit start.js</p>
<pre>/*global Flickry, SC */

Flickry.START = SC.Responder.create({
  didBecomeFirstResponder: function() {
    console.log("STATE: enter START");
    var state = Flickry.searchController.get("hasQuery") ? Flickry.STREAM : Flickry.SEARCH;
    Flickry.invokeLater(Flickry.makeFirstResponder, 1, state);
  },

  willLoseFirstResponder: function() {
    console.log("STATE: lose START");
  }
});</pre>
<p>didBecomeFirstResponder and willLoseFirstResponder are two special methods that are called upon becoming and losing the status of first responder. We insert a simple console to demonstrate the flow of state transitions. Inside didBecomeFirstResponder method, we check if we &#8220;hasQuery&#8221;. Due to the fact that we haven&#8217;t implemented this customized property yet, it will always make Flickry.SEARCH as its next first responder.</p>
<p>Wo do it in other two files too.</p>
<p>stream.js</p>
<pre>/*global Flickry, SC */

Flickry.STREAM = SC.Responder.create({

  didBecomeFirstResponder: function(){
    console.log("STATE: enter STREAM");
  },
  willLoseFirstResponder: function() {
    console.log("STATE: lose STREAM");
  }

});</pre>
<p>search.js</p>
<pre>/*global Flickry, SC */

Flickry.SEARCH = SC.Responder.create({

  didBecomeFirstResponder: function(){
    console.log("STATE: enter SEARCH");
  },
  willLoseFirstResponder: function() {
    console.log("STATE: lose SEARCH");
  }

});</pre>
<p>And we have make Flickry.START the first responder at startup, so in the main.js, we add</p>
<pre>Flickry.makeFirstResponder(Flickry.START);</pre>
<p>after</p>
<pre>Flickry.getPath('mainPage.mainPane').append();</pre>
<p>Now, open browse console, refresh. You ought to see the output just like this: </p>
<p><a href="http://www.flickr.com/photos/28352704@N06/5420735796" title="View 'flickry-state-test-step3' on Flickr.com"><img border="0" style="display:block; margin-left:auto; margin-right:auto;" height="480" src="http://farm6.static.flickr.com/5175/5420735796_6a26682510_o.png" alt="flickry-state-test-step3" width="517" title="flickry-state-test-step3"/></a></p>
<p>Up to now, the code of this project is at branch &#8220;chp1-2&#8243; of the live repo: <a href="https://github.com/RobinQu/FlickrySteps/tree/chp1-2">https://github.com/RobinQu/FlickrySteps/tree/chp1-2</a></p>
<h3>Decorate Flickry</h3>
<p>In this step, we will finally see something tangible. Let&#8217;s move on to setup the views.</p>
<p>Firstly, rename flickry/resources/main_page.js to stream_page.js. In the resources folder, we also need to create an empty page for our search panel. Call it search_page.js.</p>
<p>StreamPage is most important page in this app. It will display search results, if any. In case of empty result or search dialog is canceled, it will show a search button, which will trigger the search dialog.</p>
<p>Open and edit stream_page.js:</p>
<pre>Flickry.streamPage = SC.Page.design({

  mainPane: SC.MainPane.design({
    childViews: "title busket".w(),
    defaultResponder: Flickry,
    title: SC.LabelView.design({
      layout: { left: 0, right: 0, top: 10, heigth: 30 },
      textAlign: SC.ALIGN_CENTER,
      value: "Flickry - a Flickr demo by RobinQu",
      tagName: "h1",
      controlSize: SC.HUGE_CONTROL_SIZE
    }),
    busket: SC.ContainerView.design({
      layout: { left: 0, top: 40, bottom: 0, right: 0 },
      nowShowingBinding: "Flickry.photosController.nowShowing"
    })
  }),

  searchBtn: SC.ButtonView.design({
    textAlign: SC.ALIGN_CENTER,
    layout: { width: 120, height: 24, centerY: 0, centerX: 0 },
    title: "Search Photos",
    action: "search"
  }),

  stream: SC.View.design({
    childViews: "reset list".w(),
    layout: {top:0, left:0, bottom:0, right:0},
    reset: SC.ButtonView.design({
      layout: { centerX: 0, height: 24, width: 100, top: 0},
      title: "Return",
      action: "reset"
    }),
    list: SC.ScrollView.design({
      layout: {left: 0, right: 0, bottom: 0, top: 30},
      contentView: SC.GridView.design({
        exampleView: SC.ImageView,
        columnWidth: 75,
        rowHeight: 75,
        contentValueKey: "url",
        contentBinding: "Flickry.photosController.arrangedObjects"
      })
    })
  }),

  loading: SC.LabelView.design({
    tagName: "h2",
    textAlign: SC.ALIGN_CENTER,
    layout: { left: 0, right: 0, height: 24, centerY: 0 },
    controlSize: SC.HUGE_CONTROL_SIZE,
    value: "Loading..."
  })

});</pre>
<p>The code is pretty easy to understand. Put away the layout specs, we only have two child views in main pane of stream page. One is title and another is &#8220;busket&#8221;, a container view. The container will display correct view according to circumstances. </p>
<p>How can we do that? The magic binding! nowShowing property of a container view can point to a view class or the property path or name of a view. And we bind this view to another property on a controller. </p>
<p>Open and edit flickry/controllers/photos.js:</p>
<pre>Flickry.photosController = SC.ArrayController.create(
/** @scope Flickry.photosController.prototype */ {

  isLoading: NO,
  nowShowing: function() {
    if(this.get("isLoading")) {
      return "loading";
    } else {
      return this.get("hasContent") ? "stream" : "searchBtn";
    }
  }.property("hasContent", "isLoading")

}) ;
</pre>
<p>nowShowing is, in fact, maintained in this controller. isLoading is a switch to represents the loading status of photo results.</p>
<p>Anyway, the result is that when we have results, the &#8220;busket&#8221; will show Fliclkry.streamPage.mainPane.stream, otherwise Fliclkry.streamPage.mainPane.searchButton.</p>
<p>You should also note that defaultResponder property of mainPane points to Flickry itself. By doing so, the actions generated by user interactions or app itself will be routed to Flickry, which is also a subclass of SC.Responder, first.</p>
<p>Refresh the page, you should now see the title and search button.</p>
<h3>Need A Search Panel?</h3>
<p>Yes, we need a search panel to carry on the job. Open and edit search_page.js:</p>
<pre>/*global SC, Flickry */
Flickry.searchPage = SC.Page.design({
  mainPane: SC.PanelPane.design({
    layout: { centerX: 0, width: 400, centerY: 0, height: 240 },
    defaultResponder: Flickry,
    contentView: SC.View.design({
      childViews: "title keyword username go cancel".w(),
      title: SC.LabelView.design({
        tagName: "h2",
        value: "Enter a Query",
        layout: { top: 10, right: 0, left:0, height: 20 },
        textAlign: SC.ALIGN_CENTER
      }),
      keyword: SC.TextFieldView.design({
        layout: { centerX: 0, top: 40, width: 300, height: 30 },
        hint: "Keyword to Match",
        valueBinding: "Flickry.searchController.keyword"
      }),
      username: SC.TextFieldView.design({
        layout: { centerX: 0, top: 80, width: 300, height: 30 },
        hint: "By Who?",
        valueBinding: "Flickry.searchController.userName"
      }),
      go: SC.ButtonView.design({
        layout: { width: 100, height: 24, left: 50, bottom: 10 },
        title: "Search",
        action: "submit",
        textAlign: SC.ALIGN_CENTER
      }),
      cancel: SC.ButtonView.design({
        layout: {width: 100, height: 24, bottom: 10, right: 50 },
        title: "Cancel",
        action: "cancel",
        textAlign: SC.ALIGN_CENTER
      })
    })
  })
});</pre>
<p>As you can see, we create a simple form SC.PanelPane. Besides, we bind the value of inputs to Flickry.serachController. Now, Flickry.serachController acts like a query proxy that stores all the info about current query.</p>
<h3>Link Up The Interfaces</h3>
<p>Up to now, we have completed the task of defining most user interfaces. But it needs a lot logics to make it really work. In this step, we are going to make this app work like the one in our earlier mockup, but it will not fetch any results for us.</p>
<p>Open the console, you can find that the app actually enter the SEARCH state, it&#8217;s just missing a search dialog. We are going to show the panel upon entering this state and remove this panel when leaving this state.</p>
<p>Open and edit flickry/states/search.js:</p>
<pre>/*global Flickry, SC */

Flickry.SEARCH = SC.Responder.create({

  didBecomeFirstResponder: function(){
    console.log("STATE: enter SEARCH");

    var pane;
    pane = Flickry.getPath('searchPage.mainPane');
    pane.append(); // show on screen
    Flickry.photosController.set("content", null);//clear the photos before search
    pane.makeFirstResponder(pane.contentView.keyword); // focus first field
  },

  willLoseFirstResponder: function() {
    console.log("STATE: lose SEARCH");
    Flickry.getPath('searchPage.mainPane').remove();
  }

});</pre>
<p>It&#8217;s pretty clear how we use didBecomeFirstResponder and willLoseFirstResponder to manage the display of the search panel.</p>
<p>Refresh the page, and you should see the search dialog along with an overlay behind it. Recall the state chart we made before. If we don&#8217;t have any query existing, we move to SEARCH state.</p>
<p>In fact, when you click on any buttons in this app now, it will trigger action and transmit these actions through responder chains. As responders we created have been add to that chain when we call makeFirstResponder method, they are capable of handling these actions! And they should.</p>
<p>States, represented by a SC.Responder instance, should take charge of what happening during this state.</p>
<p>You can find the action names in the action property of button views. So, in flickry/states/search.js, we need to handle following actions:</p>
<ul>
<li>Submit, triggered when &#8220;submit&#8221; is clicked</li>
<li>Cancel, triggered when &#8220;cancel&#8221; is clicked</li>
</ul>
<p>Open and edit this file:</p>
<pre>/*global Flickry, SC */
Flickry.SEARCH = SC.Responder.create({

  didBecomeFirstResponder: function(){
    console.log("STATE: enter SEARCH");

    var pane;
    pane = Flickry.getPath('searchPage.mainPane');
    pane.append(); // show on screen
    Flickry.photosController.set("content", null);//clear the photos before search
    pane.makeFirstResponder(pane.contentView.keyword); // focus first field
  },

  willLoseFirstResponder: function() {
    console.log("STATE: lose SEARCH");
    Flickry.getPath('searchPage.mainPane').remove();
  },

  submit: function() {
    Flickry.makeFirstResponder(Flickry.STREAM);
  },

  cancel: function(){
    Flickry.makeFirstResponder(Flickry.STREAM);
  }

});
</pre>
<p>Well, we simply change the first responder of the responder chain. That means we want to enter another state.</p>
<p>Open flickry/states/stream.js, we add a handler for &#8220;search&#8221; and &#8220;reset&#8221; action, so it looks like:</p>
<pre>/*global Flickry, SC */
Flickry.STREAM = SC.Responder.create({
  didBecomeFirstResponder: function(){
    console.log("STATE: enter STREAM");
  },

  willLoseFirstResponder: function() {
    console.log("STATE: lose STREAM");
  },

  search: function() {
    Flickry.makeFirstResponder(Flickry.SEARCH);
  },

  reset: function() {
    Flickry.makeFirstResponder(Flickry.SEARCH);
  }

});</pre>
<p>Refresh the page, you can now play with these buttons.</p>
<h3>Find those photos</h3>
<p>First, we should add support to &#8220;hasQuery&#8221; property in Flickry.searchController:</p>
<pre>Flickry.searchController = SC.ObjectController.create(
/** @scope Flickry.searchController.prototype */ {
  content: SC.Object.create({}),

  hasQuery: function() {
    var query = this.get("content"), k = query.get("keyword"), u = query.get("userName");
    if(query &#038;&#038; k &#038;&#038; k !=="") {//query exists and has keyword or username as the condition(s)
      return YES;
    }
    return NO;
  }.property("content").cacheable()

});</pre>
<p>and open flickry/states/search.js. We have to modify submit method a bit:</p>
<pre>submit: function() {
    Flickry.photosController.set("isLoading", YES);//will show "loading"
    var query, photos;
    query = Flickry.searchController.get("content");
    Flickry.PHOTO_QUERY = SC.Query.local(Flickry.Photo, query);
    photos = Flickry.store.find(Flickry.PHOTO_QUERY);
    Flickry.photosController.set("content", photos);
    Flickry.photosController.set("isLoading", NO);

    Flickry.makeFirstResponder(Flickry.STREAM);
  }</pre>
<p>Now, this method build a local query with info inside the search controller and stores the results in photos controller. After completion, we enter STREAM state to enjoy the photos.</p>
<p>As we are playing with fixture data. We can only see three photos, no matter what you have typed into the search dialog.</p>
<p>In the next chapter we will discuss how to implement a datasource for flickr so that we can do the real search from flickr database.</p>
<p>All code in this chapter can be found at <a href="https://github.com/RobinQu/FlickrySteps/tree/chp1-2">brach chp1-2</a> on Github.</p>
]]></content:encoded>
			<wfw:commentRss>http://pagetalks.com/2011/02/07/flickry-step-by-step-to-create-a-flickr-app-chapter-2.html/feed</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Flickry &#8211; Step by Step to Create A Flickr App, Chapter 1</title>
		<link>http://pagetalks.com/2011/02/03/flickry-step-by-step-to-create-a-flickr-app-chapter-1.html</link>
		<comments>http://pagetalks.com/2011/02/03/flickry-step-by-step-to-create-a-flickr-app-chapter-1.html#comments</comments>
		<pubDate>Thu, 03 Feb 2011 04:18:17 +0000</pubDate>
		<dc:creator>Robin</dc:creator>
				<category><![CDATA[HTML5]]></category>
		<category><![CDATA[SproutCore]]></category>
		<category><![CDATA[flickr]]></category>
		<category><![CDATA[sc]]></category>
		<category><![CDATA[sproutcore]]></category>

		<guid isPermaLink="false">http://pagetalks.com/?p=507</guid>
		<description><![CDATA[Happy Chinese New Year! I hardly slept because of the all the cracks and fireworks outside. Well, in this post, we will go through a lot of step to implement a simple flickr demo, which will take advantages of powerful HTML5 frameworks, Sproutcore. If yo... ]]></description>
			<content:encoded><![CDATA[<p><a href="http://www.w3.org/html/logo/" class="alignleft"><br />
<img src="http://www.w3.org/html/logo/badge/html5-badge-h-css3-performance-semantics-storage.png" width="229" height="64" alt="HTML5 Powered with CSS3 / Styling, Performance &amp; Integration, Semantics, and Offline &amp; Storage" title="HTML5 Powered with CSS3 / Styling, Performance &amp; Integration, Semantics, and Offline &amp; Storage"><br />
</a></p>
<p>Happy Chinese New Year! I hardly slept because of the all the cracks and fireworks outside.</p>
<p>Well, in this post, we will go through a lot of step to implement a simple flickr demo, which will take advantages of powerful HTML5 frameworks, <a href="http://www.sproutcore.com/">Sproutcore</a>.</p>
<p>If you just heard about Sproutcore in this post, you&#8217;d better check it out. I believe it&#8217;s the next big thing in the near future.</p>
<p>In addition, this tutorial is intended for intermediate users. In case you are unfamiliar to Sproutcore, better choice is that you practice the official <a href="http://wiki.sproutcore.com/Todos%C2%A0Intro">TODO Demo</a>.</p>
<p>Inside this demo, following important features will be mentioned:</p>
<ul>
<li>Controllers</li>
<li>Statechart</li>
<li>Datasource</li>
<li>Routes</li>
</ul>
<p>All code of this app can be found at Github: <a href="https://github.com/RobinQu/flickry">https://github.com/RobinQu/flickry</a></p>
<p>Code revisions and history is available on <a href="https://github.com/RobinQu/FlickrySteps">https://github.com/RobinQu/FlickrySteps</a>. This repo will be updated with this tutorial.</p>
<p>Because of the scale of this tutorial, I decided to complete it in separate chapters. This is the first chapter of this tutorial.<br />
<span id="more-507"></span><br />
<h3>Design and Concepts</h3>
<p>I created a mockups to showcase the interaction of this simple app. Click to see the full size copy.</p>
<p><a href="http://farm5.static.flickr.com/4150/5412424966_4f3904b2ab_o.png" title="View 'flickry-mockups' on Flickr.com"><img border="0" style="display:block; margin-left:auto; margin-right:auto;" height="500" src="http://farm5.static.flickr.com/4150/5412424966_08f73014f8.jpg" alt="flickry-mockups" width="384" title="flickry-mockups"/></a></p>
<p>Basically, you type a keyword and the app show the results. Here it&#8217;s the preview. Click to see the full size.</p>
<p><a href="http://farm6.static.flickr.com/5255/5411824329_4de3b4bbff_o.png" title="View 'flicrky-preview' on Flickr.com"><img border="0" style="display:block; margin-left:auto; margin-right:auto;" height="354" src="http://farm6.static.flickr.com/5255/5411824329_4de3b4bbff_o.png" alt="flicrky-preview" width="500" title="flicrky-preview"/></a></p>
<h3>Step 1: Setup Your SC App</h3>
<p>First we initialize the app. cd into your desired folder:</p>
<pre>sc-init Flickry
cd Flickry</pre>
<p>As we need two controllers and a single model. I&#8217;d like to build up the skeleton of these components using sc-gen in advance, so:</p>
<p>A data model for photo object</p>
<pre>sc-gen model Flickry.Photo</pre>
<p>Flickry.photosController to manage all photos.</p>
<pre>sc-gen controller Flickry.photosController SC.ArrayController</pre>
<p>Flickry.searchController to manage the current search action</p>
<pre>sc-gen controller Flickry.searchController</pre>
<p><del>Finally, you have to manually create a folder for data_source and flickr.js inside it as sc-gen doesn&#8217;t have a generator for data source?!</del></p>
<p>santhosh kumar told me about <a href="http://groups.google.com/group/sproutcore/msg/c3ca66e232a19745?hl=en">data source generator</a>.</p>
<p>Thanks to the suggestion, we can now:</p>
<pre>sc-gen data-source Flickry.FlickrDataSource</pre>
<p>Now, the app folder looks like this:</p>
<p><a href="http://www.flickr.com/photos/28352704@N06/5411856675" title="View 'flickry-folder-step1' on Flickr.com"><img border="0" style="display:block; margin-left:auto; margin-right:auto;" height="614" src="http://farm5.static.flickr.com/4122/5411856675_5e36e98442_o.png" alt="flickry-folder-step1" width="401" title="flickry-folder-step1"/></a></p>
<h3>Setup Your Model</h3>
<p>There&#8217;s only one model in this app, a simple model to represent the photo object for <a href="http://www.flickr.com/services/api/flickr.photos.search.html">flickr photo search api</a>.</p>
<p><a href="http://www.flickr.com/photos/28352704@N06/5419908157" title="View 'scheme' on Flickr.com"><img border="0" style="display:block; margin-left:auto; margin-right:auto;" height="190" src="http://farm6.static.flickr.com/5011/5419908157_14d20ca846_o.png" alt="scheme" width="180" title="scheme"/></a></p>
<p>Here&#8217;s the code needed for this model:</p>
<pre>Flickry.Photo = SC.Record.extend(
/** @scope Flickry.Photo.prototype */ {

  primaryKey: "id",//SC default to "guid", but inside flickr response we only have "id" field
  farm: SC.Record.attr(Number),
  server: SC.Record.attr(Number),
  secret: SC.Record.attr(String),
  photo: SC.Record.attr(Number, {key: "id"}),
  url: function() {
    return "http://farm%@1.static.flickr.com/%@2/%@3_%@4_s.jpg".fmt(this.get("farm"), this.get("server"), this.get("photo"), this.get("secret"));
  }.property("farm", "server", "photo", "secret").cacheable()//produce url for me!

});</pre>
<p>and it&#8217;s advisable to have a little test of this model. </p>
<p>In order to test it locally, we have supplement some fixture data for this model. Edit Flickry/apps/flickry/fixtures/photo.js:</p>
<pre>sc_require('models/photo');

Flickry.Photo.FIXTURES = [

  {
    id: 1,
    photo: 5312020956,
    secret: "a7c6b23e36",
    server: 5169,
    farm: 6
  },
  {
    id: 2,
    photo: 5312096780,
    secret: "01aa7acedb",
    server: 5290,
    farm: 6
  },
  {
    id: 3,
    photo: 5312045004,
    secret: "5617e73c6d",
    server: 5085,
    farm: 6
  }

];
</pre>
<p>Run development server inside the Flickry folder. </p>
<pre>sc-server</pre>
<p>Open the app in browser and type in the console. Let&#8217;s play with the model and fixture.</p>
<p><a href="http://farm6.static.flickr.com/5020/5412539142_eac5da3991_o.png" title="View 'flickry-play-with-fixture-and-model-step2' on Flickr.com"><img border="0" style="display:block; margin-left:auto; margin-right:auto;" height="321" src="http://farm6.static.flickr.com/5020/5412539142_eac5da3991_o.png" alt="flickry-play-with-fixture-and-model-step2" width="500" title="flickry-play-with-fixture-and-model-step2"/></a></p>
<p>Oh, it&#8217;s time to lunch&#8230;..See you in next Chapter!</p>
]]></content:encoded>
			<wfw:commentRss>http://pagetalks.com/2011/02/03/flickry-step-by-step-to-create-a-flickr-app-chapter-1.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Logical CSS</title>
		<link>http://pagetalks.com/2010/12/09/logical-css.html</link>
		<comments>http://pagetalks.com/2010/12/09/logical-css.html#comments</comments>
		<pubDate>Thu, 09 Dec 2010 03:05:01 +0000</pubDate>
		<dc:creator>Robin</dc:creator>
				<category><![CDATA[CSS]]></category>
		<category><![CDATA[HTML5]]></category>
		<category><![CDATA[css3]]></category>
		<category><![CDATA[less]]></category>
		<category><![CDATA[sass]]></category>

		<guid isPermaLink="false">http://pagetalks.com/?p=498</guid>
		<description><![CDATA[前端是目前互联网上最疯狂的领域之一。不仅仅是HTML5和CSS3，而是因为两者的产生激发了人们的无限想象。大家开始实用之前制作网页的方法来制作应用程序。程序逻辑和数据的问题可以交给Se... ]]></description>
			<content:encoded><![CDATA[<p>前端是目前互联网上最疯狂的领域之一。不仅仅是HTML5和CSS3，而是因为两者的产生激发了人们的无限想象。大家开始实用之前制作网页的方法来制作应用程序。程序逻辑和数据的问题可以交给Sencha Touch和Sproutcore之类的框架进行解决，而Look&#038;Feel的问题，就只能由CSS3来解决了。</p>
<p>但是CSS是被设计来渲染页面的，而不是描述程序外观的。即使是CSS3，在如今应该也是不能满足这个要求的。虽然现在有大量的项目拥有上万行的CSS，但这样的项目对于开发者来说是一个非常巨大的考研。CSS没有变量、命名空间、包这些当今程序思想中最为基本的概念，开发者只能通过事先口头约定进行协同开发。即便是一个人维护CSS，你也不一定记得你几天前写的那些样式是否会和你今天写的有重复的ID，或是你是否在定义冗余的样式。<span id="more-498"></span>诸如此类的问题仔细想象，会是数不尽的。也许大家会告诉我，这个世界上有Blueprint和960.gs之类的CSS框架啊～可是这些框架在需求超过“布局”、“设备兼容性”之后，你会发现，他们其实并没有解决问题。更本质上的那些由于CSS语言本身而引发的问题，其实是需要一门新的“语言”来解决的。在这里介绍两个这样的“语言”，SASS和{Less}，两者均是CSS3的超集。</p>
<h3><a href="http://lesscss.org/">{Less}</a></h3>
<p>{Less}想做的事情就是就如同其名字一样，减少CSS的代码量。它在遵循CSS语法的前提下，提供了变量、函数、层级(Nesting)、数值操作等功能。</p>
<p><a href="http://www.flickr.com/photos/28352704@N06/5245625324" title="View 'less' on Flickr.com"><img border="0" style="display:block; margin-left:auto; margin-right:auto;" height="81" src="http://farm6.static.flickr.com/5163/5245625324_9fce1400b2_o.png" alt="less" width="199" title="less"/></a></p>
<p>感觉应该直接上代码来：</p>
<h4>Variables</h4>
<pre>@brand_color: #4D926F;

#header {
  color: @brand_color;
}

h2 {
  color: @brand_color;
}</pre>
<p>上面的代码演示了less里面，CSS如果有变量是多么方便的一件事。</p>
<h4>Mixins</h4>
<pre>.rounded_corners (@radius: 5px) {
  -moz-border-radius: @radius;
  -webkit-border-radius: @radius;
  border-radius: @radius;
}

#header {
  .rounded_corners;
}

#footer {
  .rounded_corners(10px);
}</pre>
<p>你曾经抱怨过写圆角要写N句浏览器前缀的语法么？通过实用mixin，你可以如此潇洒的写那些该死的样式。</p>
<h4>Nesting</h4>
<pre>#header {
  color: red;
  a {
    font-weight: bold;
    text-decoration: none;
  }
}</pre>
<p>CSS的子选择器，往往会让你写N多的ID和Class串联起来，实际上你得确定你如果命名层级，最多多少层，等等。这些东西，都只能口头约定。但是如果你通过如上语法进行编写，这些问题其实你已经不需考虑了。</p>
<h4>Operation</h4>
<pre>@the-border: 1px;
@base-color: #111;

#header {
  color: @base-color * 3;
  border-left: @the-border;
  border-right: @the-border * 2;
}

#footer {
  color: (@base-color + #111) * 1.5;
}</pre>
<p>最后一个特性，数值操作。不多说了，这个特性省了很多JS代码了。</p>
<h4>Deployment</h4>
<p>官方要求将这种样式保存为.less扩展名。并且需要命令行工具“翻译”为浏览器能够理解的原生CSS。</p>
<p>当然，还有有一个GUI界面的程序能够进行自动化操作的，<a href="http://incident57.com/less">Less.app</a>，不过是Mac only的。</p>
<h3><a href="http://sass-lang.com/">SASS</a>, aka, Syntactically Awesome Style Sheet</h3>
<p>SASS和Less的原理是一样的，都是在语法上提供CSS3的超集来解决矛盾，并且通过“翻译”生成CSS。但是SASS提供了更为强大的语法和特性。</p>
<p><a href="http://www.flickr.com/photos/28352704@N06/5245022809" title="View 'sass' on Flickr.com"><img border="0" style="display:block; margin-left:auto; margin-right:auto;" height="238" src="http://farm6.static.flickr.com/5201/5245022809_b0a66a80aa_o.gif" alt="sass" width="217" title="sass"/></a></p>
<ul>
<li>SASS支持任何位置的变量，不仅仅可以将其作为属性的数值，还可以作为属性名或者属性名的一部分。</li>
<li>SASS内置了更多处理函数，例如色彩的精确调节等</li>
<li>SASS内置了很多你经常会用到的mixin，例如圆角、阴影等，不需要你从头开始写</li>
<li>&#8230;</li>
</ul>
<p>基本来说，SASS和Less思想一致，但确实两个规模完全是两个概念。可能这也是为什么Sencha本身就是用SASS来写自己主题样式的原因了吧。</p>
<p>详细情况可以看看他们的<a href="http://sass-lang.com/tutorial.html">教程</a>，<a href="http://sass-lang.com/docs/yardoc/file.SASS_REFERENCE.html">文档</a>或是<a href="http://sass-lang.com/download.html">安装</a>来看看。</p>
<p>PS 无论是SASS还是{Less}都是需要Ruby环境的，因为其命令行工具都是ruby写的。Windows下的ruby环境其实很不方便，Windows sucks，换Linux或者Mac吧⋯⋯</p>
]]></content:encoded>
			<wfw:commentRss>http://pagetalks.com/2010/12/09/logical-css.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Sencha Touch</title>
		<link>http://pagetalks.com/2010/12/09/sencha-touch.html</link>
		<comments>http://pagetalks.com/2010/12/09/sencha-touch.html#comments</comments>
		<pubDate>Thu, 09 Dec 2010 02:09:30 +0000</pubDate>
		<dc:creator>Robin</dc:creator>
				<category><![CDATA[HTML5]]></category>
		<category><![CDATA[Javascript]]></category>
		<category><![CDATA[mobile]]></category>
		<category><![CDATA[sencha]]></category>

		<guid isPermaLink="false">http://pagetalks.com/?p=496</guid>
		<description><![CDATA[Sencha的前身是Ext，而现在的Sencha Touch更是将几个著名的项目一起整合起来， 即Sencha Touch ＝ Ext JS + jQTouch + Raphael。Ext是老牌的JS框架，源自YUI，但跟YUI有着截然不容的发展方向，jQTouch是基于jQuery... ]]></description>
			<content:encoded><![CDATA[<p><a href="http://www.flickr.com/photos/28352704@N06/5244961441" title="View 'sencha touch' on Flickr.com"><img border="0" style="display:block; margin-left:auto; margin-right:auto;" height="332" src="http://farm6.static.flickr.com/5286/5244961441_4e7b728281_o.png" alt="sencha touch" width="485" title="sencha touch"/></a></p>
<p>Sencha的前身是Ext，而现在的Sencha Touch更是将几个著名的项目一起整合起来，	即Sencha Touch ＝ Ext JS + jQTouch + Raphael。Ext是老牌的JS框架，源自YUI，但跟YUI有着截然不容的发展方向，jQTouch是基于jQuery的UI组件库，Raphael则是非常成功的一个数据可视化的工具库。</p>
<p>简单的说，Sencha Touchi是将Ext强大的逻辑处理能力进行精简，然后提供了一套可用性和完成度都非常高的UI组件。这个和Sproutcore的方向极为相似。WebApp向CloudApp进化的过程必定是网页开发到应用程序开发的变化过程。在这个过程中，Sproutcore的思想过于超前，而Sencha Touch则是一个承上启下的框架，大多数前端工程师是可以进行无痛过渡的。</p>
<p>进一步说，Sencha Touch团队对于文档和社区的建设都比Sproutcore好上不少。又有一部分开发者对Ext本身就比较熟悉，可谓是驾轻就熟。<span id="more-496"></span>如今，WebOS的概念在Chrome OS发布之后，则是更进一步的重要了。无论是手机上的CloudApp还是Chrome OS里的应用，能够很好解决问题的目前只有Sencha touch和我一直都在等待的Sproutcore Mobile才能解决的。</p>
<p>目前Sencha Touch是完全免费的，并且在GPL协议下开源。</p>
<p>如果你对Sencha Touch感兴趣，可以：</p>
<ul>
<li>去看看<a href="http://www.sencha.com/products/touch/demos.php">Demo</a></li>
<li>拜见一下<a href="http://dev.sencha.com/deploy/touch/docs/">文档</a></li>
<li><a href="http://www.sencha.com/products/touch/download.php">下载</a>过来玩玩</li>
</ul>
<p>目前本人也是花时间看这个框架，并且写了一个手机联系人的Demo，如果感兴趣，可以在Github上找到<a href="https://github.com/RobinQu/Phone-Demo">源码</a>。</p>
]]></content:encoded>
			<wfw:commentRss>http://pagetalks.com/2010/12/09/sencha-touch.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
<!-- WP Super Cache is installed but broken. The path to wp-cache-phase1.php in wp-content/advanced-cache.php must be fixed! -->
