Angular.js学习笔记与感悟

Angular.js是google出品的一个非常牛x的前端MVC框架,详细的介绍你可以
百度一下

2013年春节,放假8天,吃了睡,睡了吃的事情,不能让脑子荒废了,如果脑子生锈了,那怎么
拿来上班,哈哈。

花了3天时间细心的研究了angular,怎么觉得名字像“安哥拉”
版权写的也比较有个性“Super-powered by Google ©2010-2012 ( v1.0.3 bouncy-thunder )”
看到这个版权,你或许知道你怎么选择了,文档写的还是非常详细的。从github上看到源码中既有nodejs,也有ruby,
真是google的风格呀;

教程性的东西,我就不写了,angular写的已经够详细的。angular最牛x的是她的 Testacular 测试,太棒了。
单元测试和端对端测试。

用angular写应用非常简单,js的部分非常少,控制器,模型和视图完全分开,filter ,module (和seajs的module补一个概念)
router, $http, form valid, jqlite, multiple view, rest custom service;

下面对这些方面说一下我的理解:


  1. 模型、控制器、视图分离分开很有必要,对服务的封装,让angular用这个更像一个app的高层应用,
    这是一个优点,同时也算是一个缺点,对底层过于封装,太过于严密,比如没有events层,
    这对于mvp基于事件的应用就不太适合, 如果做本地应用就不适合。angular偏向于网络http的WebApp;

  2. filter 很好,很强大,view层表达式,不允许写表达式,angular认为这是controller的工作,要求很严格。写了就执行错误。

  3. angular宣讲自己是声明式,其他的命令式的
  4. moudle 创建太过抽象,对于控制器作用域, 操作起来很麻烦,对于英文文档上的解释(看着很费劲,构造器apply,
    但是对于注入的全局, $watch的原理还不是很明白。如何让自定义事件去改变模型数据,比如webapp的tap, singleTap, swipe, 文档没写)
    所以angularjs 还是偏向于网络应用,如果是桌面应用(webview执行),本地执行file:/// 就一些功能就非常费劲了,
    还有虽说视图和model双向绑定,但是在控制器中,如何方便的操作model来改变view,
    可能我对service的理解还不够深入。总觉得用起来力不从心。

  5. angular极度讨厌全局;

  6. angularjs执行ie8以上浏览器,ie8一下浏览器需要兼容。
  7. url router 这个也非常棒,支持老浏览器和html5新的特性。pushState和hashbang
  8. 内置http, dom element 请求,不用依赖jquery, 有简化版的jqlite
  9. form valid 内置表单验证,ie的兼容性,我没测试。
  10. multiple view 多视图,视图片段,这个很多服务器端开发经常用的功能,布局视图,但问题来了,加载别的视图,
    需要用到ajax去加载,让桌面webview 或 hybird WebApp 增加了不确定性。
  11. rest custom service 简化服务,看清楚,不是rest url, tutorial上演示简化ajax 但这个功能用处不大。
  12. 还是需要events层,前端嘛,还是基于各种事件。
  13. 能看到某些xx服务器端开发语言的特性。

总结

Angular.js还是一个新生事物,对于本地应用还有些局限,如果能在这2个方面,
得到改善,那就完美了! 期待少一些xx语言的影子,多些javascript本身的特性。

2013年春节随想

时间过的飞快,转眼间,一年又过去了,又长大了一岁,遥想2013,肯定是一个非常忙碌的一年,
在计划中的事情就有好几件. 搬家,父亲来北京,孝敬父母,拍结婚照,筹备婚礼…

之前觉得挣钱多么重要,现在发现钱怎么那么不够花呢!

并且工作上还有不少的不确定性,还需要一一去解决的各种事情,事情多,时间不够用,鱼和熊掌不可兼得;

(我是一个不太会讲话的人,一不小心,
容易得罪人,这是2013年要改进的地方,谦卑谨慎也需要一个度。)



2013 年过年于媳妇家

升级Openshif自带的Nodejs

原因:Openshift自带的nodejs 为0.6.20 版本太老了
下面的步骤可以升级为自定义版本。
安全可靠,本人尝试成功;

cd your_project_dir
git remote add upstream -m master git://github.com/openshift/nodejs-custom-version-openshift.git

git pull -s recursive -X theirs upstream master

~# 修改为你想安装的版本,比如:0.8.22
vim .openshfit/markers/NODEJS_VERSION

~# push 到openshift
git push

等着就行了,执行结束,你的openshift的nodejs 就升级为你期望的版本了。

no title

有些懒,不写标题,有段时间没写博客了。

主要是没什么可写的,二来抽不出时间写。

今天媳妇去开会去了, 就我一个人在家,是在没事儿干,把 wangxian.me 的主题稍微
调整了,界面放宽了,并且把整体界面居中了。

下个礼拜五一放假,

连上七天;

Sencha Touch Dev

生成项目 Init Project

  1. 进入sencha sdk 目录, eg: cd touch-2.2.1/
  2. 执行 sencha generate app projectName ../projectPath

生成model

sencha generate model User id:int

构建production应用

sencha app build production

构建native包

sencha app build native

About APP build

  • testing - intended for QA prior to production. All JavaScript and CSS source files are bundled, but not minified, which makes it easier to debug.

  • package - creates a self-contained, redistributable production build that normally runs from the local file system without a web server.

  • production - creates a production build that is normally hosted on a web server and serves multiple clients (devices). The build is offline-capable using HTML 5 application cache, and is enabled to perform over-the-air updates.

  • native - first generates a package build, then packages it as a native application, ready to be deployed to native platforms.

这也行? 还能直接掉起来iOS模拟器

sencha app package run packager.json

然后真的调用起来了一个模拟器了,真是非常的方便。而且生成以后,不用配置。
默认 packager.json 里的 platform 是 iOSSimulator


Controller

有个比较重要的,routers & refs

Ext.define('MyApp.controller.Main', {
    extend: 'Ext.app.Controller',

    config: {
        control: {
            loginButton: {
                tap: 'doLogin'
            },
            'button[action=logout]': {
                tap: 'doLogout'
            }
        },

        refs: {
            loginButton: 'button[action=login]'
        }
    },

    doLogin: function() {
        // called whenever the Login button is tapped
    },

    doLogout: function() {
        // called whenever any Button with action=logout is tapped
    }
});

理解上refs 是引用的别名,control 类似 backbone的 events 的事件绑定。


Views

创建一个view

Ext.create('Ext.Panel', {
  html: 'Welcome to my app - 王见',
  fullscreen: true
});

或先define一个,然后create

Ext.define('MyApp.view.Welcome', {
    extend: 'Ext.Panel',

    config: {
        html: 'Welcome to my app',
        fullscreen: true
    }
});

Ext.create('MyApp.view.Welcome');

下面是官方提供的一个twitter图片展示的完整示例,
注意: config, element, initialize, on 的使用。

Ext.define('MyApp.view.Image', {
   extend: 'Ext.Img',

   config: {
       title: null,
       description: null
   },

   //sets up our tap event listener
   initialize: function() {
       this.callParent(arguments);

       this.element.on('tap', this.onTap, this);
   },

   //this function is called whenever you tap on the image
   onTap: function() {
       Ext.Msg.alert(this.getTitle(), this.getDescription());
   }
});

//creates a full screen tappable image
Ext.create('MyApp.view.Image', {
   title: '图片标题',
   description: '这里是图片信息',

   src: 'http://apod.nasa.gov/apod/image/1202/oriondeep_andreo_960.jpg',
   fullscreen: true
});

必须在initialize中调用 this.callParent(arguments); 来继承父类,
不然组件可能运行不正常。

每一个view等组件上,都有一个element方法,可以添加事件等;

视图隐式 setXXX getXXX applyXXX updateXXX

  • 上一个例子中,在view中会自动生成getTitle, setTitle等,也就是getter,and setter

  • applyXXX 当set发生变化的时候,或当第一次初始化的时候,也会调用。常用来修改变化的值,如把10组装成10px solid dashed

  • updateXXX 当调用完applyXXX后,接着调用updateXXX。常用来把改变的值反应到dom上。

示例:

// as before
Ext.define('MyApp.view.MyView', {
    extend: 'Ext.Panel',

    config: {
        border: 0
    },

    applyBorder: function(value) {
        return value + "px solid red";
    },

    updateBorder: function(newValue, oldValue) {
        this.element.setStyle('border', newValue);
    }
});

// create an instance of MyView with a
// spinner field that updates the border config
var view = Ext.create('MyApp.view.MyView', {
    border: 5,
    fullscreen: true,
    styleHtmlContent: true,
    html: 'Tap the spinner to change the border config option',
    items: {
        xtype: 'spinnerfield',
        label: 'Border size',
        docked: 'top',
        value: 5,
        minValue: 0,
        maxValue: 100,
        increment: 1,
        listeners: {
            spin: function(spinner, value) {
                view.setBorder(value);
            }
        }
    }
});

Device Profiles

为了适应不同尺寸的屏幕,需要检测设备,并且加载不同的类,

需要在app.js中定义

Ext.application({
    name: 'Mail',

    profiles: ['Phone', 'Tablet'],
    models: ['User'],
    views: ['Navigation', 'Login']
});

Phone定义在, app/profile/Phone.js

Ext.define('Mail.profile.Phone', {
    extend: 'Ext.app.Profile',

    config: {
        name: 'Phone',
        views: ['Main']
    },

    isActive: function() {
        return Ext.os.is('Phone');
    }
});

文件的加载顺序:

  1. app/model/User.js
  2. app/view/Navigation.js
  3. app/view/Login.js
  4. app/view/phone/Main.js

可以看到在profile中定义的最后加载。

可以用Ext.os.is来判断设备是Phone还是Tables

并且在profile中定义的load view,是在当前profile名字下的目录里,如app/view/phone/Main.js 如果需要同样在view/Main.js 中,那在config.views 中写全路径:Mail.view.Main

The Launch Process

APP初始化顺序

  1. Controllers are instantiated; each Controller’s init function is called

  2. The Profile’s launch function is called

  3. The Application’s launch function is called.

不同的设备可以定义不同的views, controllers, models

不同的views:

Ext.define('Mail.view.tablet.Main', {
    extend: 'Ext.Container',

    config: {
        layout: 'fit',
        items: [
            {
                xtype: 'messagelist',
                width: 200,
                docked: 'left'
            },
            {
                xtype: 'messageviewer'
            }
        ]
    }
});

Ext.define('Mail.view.phone.Main', {
    extend: 'Ext.Container',

    config: {
        layout: 'card',
        items: [
            {
                xtype: 'messagelist'
            },
            {
                xtype: 'messageviewer'
            }
        ]
    }
});

详细见 http://docs.sencha.com/touch/2.2.0/#!/guide/profiles

History 支持

简单匹配

Ext.define('MyApp.controller.Products', {
    extend: 'Ext.app.Controller',

    config: {
        routes: {
            'products/:id': 'showProduct',
            'products/:id/:format': 'showProductInFormat'
        }
    },

    showProduct: function(id) {
        console.log('showing product ' + id);
    },

    showProductInFormat: function(id, format) {
        console.log('showing product ' + id + ' in ' + format + ' format');
    }
});

复杂匹配

Ext.define('MyApp.controller.Products', {
    extend: 'Ext.app.Controller',

    config: {
        routes: {
            'file/:filename': {
                action: 'showFile',
                conditions: {
                    ':filename': "[0-9a-zA-Z\.]+"
                }
            }
        }
    },

    //opens a new window to show the file
    showFile: function(filename) {
        window.open(filename);
    }
});

复杂匹配的时候,需要把router定义为object,action为处理方法。

感觉: 没有backbone 方便,直接定义正则表达式,不过这感觉和controller的refs controller的感觉差不多。

Restore state and deep link

such as http://myapp.com/#products/123

Ext.define('MyApp.controller.Products', {
    extend: 'Ext.app.Controller',

    config: {
        refs: {
            main: '#mainView'
        },

        routes: {
            'products/:id': 'showProduct'
        }
    },

    /**
     * Endpoint for 'products/:id' routes. Adds a product details view (xtype = productview)
     * into the main view of the app then loads the Product into the view
     *
     */
    showProduct: function(id) {
        var view = this.getMain().add({
            xtype: 'productview'
        });

        MyApp.model.Product.load(id, {
            success: function(product) {
                view.setRecord(product);
            },
            failure: function() {
                Ext.Msg.alert('Could not load Product ' + id);
            }
        });
    }
});

例子中将从服务器端获取数据。

router可以被 Device Profile 重写。

eg:

Ext.define('MyApp.controller.Products', {
    extend: 'Ext.app.Controller',

    config: {
        routes: {
            'products/:id': 'showProduct'
        }
    }
});
Ext.define('MyApp.controller.phone.Products', {
    extend: 'MyApp.controller.Products',

    showProduct: function(id) {
        console.log('showing a phone-specific Product page for ' + id);
    }
});
Ext.define('MyApp.controller.tablet.Products', {
    extend: 'MyApp.controller.Products',

    showProduct: function(id) {
        console.log('showing a tablet-specific Product page for ' + id);
    }
});

Application Dependencies

定义在application 里的views, models, controllers

Ext.application({
    name: 'MyApp',

    views: ['Login'],
    models: ['User'],
    controllers: ['Users'],
    stores: ['Products'],
    profiles: ['Phone', 'Tablet']
});

会加载以下文件:

  • app/view/Login.js
  • app/model/User.js
  • app/controller/Users.js
  • app/store/Products.js
  • app/profile/Phone.js
  • app/profile/Tablet.js

Ext.require 也是可以的

Ext.require([
    'MyApp.view.Login',
    'MyApp.model.User',
    'MyApp.controller.Users',
    'MyApp.store.Products',
    'MyApp.profile.Phone',
    'MyApp.profile.Tablet'
]);

且在profile中也可以定义,eg:

Ext.define('MyApp.profile.Tablet', {
    extend: 'Ext.app.Profile',

    config: {
        views: ['SpecialView'],
        controllers: ['Main'],
        models: ['MyApp.model.SuperUser']
    },

    isActive: function() {
        return Ext.os.is.Tablet;
    }
});

classes在多个子目录里:

Ext.application({
    name: 'MyApp',

    controllers: ['Users', 'nested.MyController'],
    views: ['products.Show', 'products.Edit', 'user.Login']
});

或定义一个 外部的公共 Auth 引用。(个人理解,是多个app公用一个Auth文件夹)

Ext.Loader.setPath({
    'Auth': 'Auth'
});

Ext.application({
    views: ['Auth.view.LoginForm', 'Welcome'],
    controllers: ['Auth.controller.Sessions', 'Main'],
    models: ['Auth.model.User']
});

会加载以下文件,感觉有点controller, refs 的影子,注意需要用 Ext.Loader.setPath 来指定auth目录。

  • Auth/view/LoginForm.js
  • Auth/controller/Sessions.js
  • Auth/model/User.js
  • app/view/Welcome.js
  • app/controller/Main.js

最好把view或controllers 的依赖定义在view本真中,不要放在 app.js 中,
保持app.js的清晰。

完整示例:

// app/views/Main.js
Ext.define('MyApp.view.Main', {
    extend: 'Ext.Container',

    requires: [
        'MyApp.view.Navigation',
        'MyApp.view.MainList'
    ],

    config: {
        items: [
            {
                xtype: 'navigation'
            },
            {
                xtype: 'mainlist'
            }
        ]
    }
});

// app.js
Ext.application({
    views: ['Main']
});

// this is bad approach
Ext.application({
    views: ['Main', 'Navigation', 'MainList']
});

尝试让Sea.js支持CoffeeScript

在seajs 1.2, 1.3 版,有一个plugin-coffee 插件,可以支持coffeescript
写的模块,直接允许,在2.0版本,拿掉了 coffee plugin 故想试着开发一个plugin 插件。

seajs插件开发起来比较简单,又现成的例子可供参考。seajs2 本身带了好几个插件,所以
参考之;

plugin-coffee的原理是使用 CoffeeScript.load 来加载模块,编译.coffee
插件开发完后,发现现实没有想象的那么美好。文件的load是需要用到xhr来加载,chrome
有同源限制,即使是file:/// 也一样。(这也是一个不小的限制,现在开发的项目主要是本地运行。)

随放弃之。

后采用cake 来 watch觉得更简单。

后来在coffeescript.org 上看到了这样的话

While it’s not recommended for serious use …

附上源码:

Cakefile

# fs = require 'fs'

{print} = require 'util'
{spawn} = require 'child_process'

# do like this: cake -o js watch ,
# will output: { arguments: [ 'watch' ], output: 'js' }
#
# option '-o', '--output [DIR]', 'directory for compiled code'

task 'watch', 'Watch and Compile [jsc DIR] output to [js DIR]', (options)->

  coffee = spawn 'coffee', ['-wbco', 'js', 'jsc']

  coffee.stderr.on 'data', (data)->
    print data.toString()

  coffee.stdout.on 'data', (data)->
    print data.toString()

process.on ‘exit’, (code)->
print “Cake will exit(#{code})”

plugin-coffee.js

(function(seajs) {

  seajs.on("resolve", function(data) {

    var uri = seajs.resolve(data.id, data.refUri);
    if ( /\.coffee.js$/.test(uri) ) {
      uri = uri.replace(/\.js(?=$|\?)/, "");
      data.uri = uri;
    }

    // console.log( uri );
  });

  seajs.on("request", function(data) {

    if( /\.coffee$/.test(data.uri) ) {
      data.requested = true;
      // console.log(CoffeeScript);

      CoffeeScript.load(data.uri);
    }
  });

})(seajs);

青岛行

06.25去青岛拍结婚照,顺便去青岛玩玩,好久没出去了。

对于长时间生活在北京的人们,对青岛的第一印象是“青岛的空气真好啊” ,去青岛待了五天,
每天的温度都在20到25度之间,温差5度,PM2.5为 50~60 之间。对于北京400左右PM2.5,
让人情何以堪啊。

青岛的紫外线还真是强,我这个皮糙肉厚的人,一样在海边呆了几个小时,皮肤还是晒伤了。
不得不说,青岛的太阳真不是一般的厉害。

随手拍的一些照片:


出了火车站,青岛第一眼


出了火车站,走几步就是海边了,这火车站离海太近了吧


从海上看青岛


海监船


还有远处的。也是海监船


这些船是不是上过电视?


奥帆中心 —— 奥运会帆船比赛的地方


大海,海堤 —— 奥帆中心


帆船 —— 奥体中心


这里是应该是四方区


台东


青岛天幕城一角

先传这个多,后续补充。

我们几人4天多的时间,走遍了青岛的好多地方。打车,公交,步行,穿行于青岛的大街小巷。
有了百度地图,保证手机有点,就不担心迷路的问题了。和北京比起来,青岛相对较小,基本上要去的地方
打车20元以内都能到达。

永久山地车

周日在京东买的自行车,周三下午送到了,晚上回家,和老婆大人就迫不及待的动手安装,
送来的基本上已经安装完成80%了
前轮是需要动手安装的,折腾了好久才明白前轮怎么安装。发现前轮是便携快拆安装的,
本来还打算把轴承写下来呢,送的安装工具没有一个能用的,原来我们不用,快拆安装,用一个棍放进去,
一扣就行了!

先只安装了个打开,有一个车子的前轮有一个链条调出来了。

晒图





修改jekyll配置

Changelog

  1. 把博客的j k翻页改为了alt+j, alt+k翻页了。以免影响正常的对文章进行评论。
  2. 所有文字加上了.html 扩展名,看着更像静态文件
  3. 把disqus评论系统插件,改为用“多说” 可以用新浪微博登录,进行评论;
  4. 将Jekyll升级到1.0.3 ,卸载了很多系统中无用的gem包