Chia Yu Pai

Front-End, HTML5, Javascript, CSS3

Posts match “ angular ” tag:

Javascript Getter / Setter

| Comments

Modern browsers' (IE9 up, IE8 only on DOM Object) Javascript Engine supported Getter / Setter for Object

IE6-8 could listen onpropertychange event

我們有兩個常見的方法去實作,例子如下:

Sample A:
透過 __defineSetter____defineGetter__ 預設方法榜定

Dog = function (name) {
    this.name = name || 'Doggy';
  
  // Define setter with __defineSetter__ method.

  this.__defineSetter__("name", function(newVal){
    this.bark('My new name is ' + newVal);
  });
}

Dog.prototype.bark = function (msg){
    msg = msg || "I'm " + this.name;
    alert('Bark! ' + msg);
}

Carl = new Dog();
Carl.name = Carl // Alert: Bark! My new name is Carl

Carl.name // Carl

Sample B:
直接使用 setget 關鍵字定義

Dog = function (name) {
    this._name = name || 'Doggy'; // Default name without alert.

}

Dog.prototype = {
    bark: function (msg) {
    msg = msg || "I'm " + this.name;
        alert('Bark! ' + msg);
  },
  // Define Setter

  set name(newVal) {
    this._name = newVal;
        this.bark('My new name is ' + newVal);
  },
  // Define Getter

  get name() {
    return this._name;
  }
}

Carl = new Dog();
Carl.name = Carl // Alert: Bark! My new name is Carl

Carl.name // Carl

透過 Setter / Getter 的設定可以輕鬆的監聽物件方法的改變,目前最常用到的非屬 two-way binding 的專案。可以直接用十分抽象的方法讓使用者改變 Javascript 中物件的屬性值來換取 DOM 的直接內容變化,達到驚人的易用水準。

Frameworks:

angular 1.2 ngRoute

| Comments

angular 在 1.16 版後將原本的 $route, $routeProvider, $routeParams 獨立出來
從 1.0.x 升級的使用者會無法直接使用原本的 Route Service
必須在對 app 引入 ngRoute module 方可使用
此外 ngRoute 的代碼也是獨立出來,不再包在 angular.js 之中

新的使用方法如下:

script(src='http://ajax.googleapis.com/ajax/libs/angularjs/1.2.0-rc.3/angular.min.js')
script(src='http://code.angularjs.org/1.2.0-rc.3/angular-route.min.js') 

<script>
angular.module('yourAppName', ['ngRoute'], function ($routeProvider){
    $routeProvider
    .when('/', {
        controller: yourController
      templateUrl: templateFileRoute
    });
});
</script>

Yo Angular with CoffeeScript / Stylus / Jade

| Comments

2014-04-11 更新

我 Fork 出來一個可以自動產生的版本了
generator-angular-jade-stylus (GitGub)


yeoman 是十分方便的前端網頁產生模式,透過 angular generator 可以快速的產生良好架構的前端網頁,但 yo angular 預設支援 SASS / pure HTML 跟我的開發習慣不太吻合,研究了一下在 Grunt 加入自動編譯 Stylus / Jade 的方法。

此外,如果要在 generator-angular 啟用 CoffeeScript 請記得在 yo angular [appName] 就要加入 --coffee 參數,才會產生 Coffeescript 版的範例網頁唷。

generator-angular (GitHub)

首先我們需要載入 Grunt Contibutor for Stylus / Jade

npm install grunt-contrib-jade -D
npm install grunt-contrib-stylus -D

接著修改 Gruntfile.js 的配置

於最上方引用外部的 Task 並註冊給 compass

module.exports = function (grunt) {
  grunt.loadNpmTasks('grunt-contrib-stylus');
  grunt.loadNpmTasks('grunt-contrib-jade');
  grunt.registerTask('compass', ['stylus']);
  
  grunt.initConfig({
  //.....

}  

之後就要註冊我們自己的 Task Config

grunt.initConfig({
  jade: {
    dist: {
      options: {
        pretty: true
      },
      files: [{
        expand: true,
        cwd: '<%= yeoman.app %>',
        dest: '.tmp',
        src: ['*.jade', 'views/{,*/}*.jade'],
        ext: '.html'
      }]
    }
  },
  
  /*
  這裡我使用了 compass 協助我合併多個 stylus 的檔案,節省在引用時的 request 數量
  files 的 key 就是合併後的檔案位置 .tmp 是預設的 generator-angular test assets folder
  value 則是 source sheets 的陣列並可使用通用字符
  */
  
  stylus: {
    compile: {
      options: {
        compress: true,
        paths: ['node_modules/grunt-contrib-stylus/node_modules']
      },
      files: {
        '.tmp/styles/main.css': ['<%= yeoman.app %>/styles/*.styl']
      }
    }
  },
  
  /*
  為了可以即時透過 livereload 檢視修改的檔案,也在 watch 中加入監聽條件
  */
  
  watch: {
    jade: {
      files: ['<% yeoman.app %>/*.jade'],
      tasks: ['jade']
    },
    stylus: {
      files: ['app/styles/**/*.styl'],
      tasks: ['stylus']
    },
    // ...

  },
  
  /*
  接著因為我把 HTML 改成 Jade 編譯,所以原本 app 資料夾中僅有 source 檔案,build 過的檔案已經改到 .tmp 中了,要使 bower-install / usemin 可以正常運作在 build 時合併檔案,需要修改一些預設路徑。
  */
  
  'bower-install': {
    app: {
      html: '.tmp/index.html',
      ignorePath: '<%= yeoman.app %>/'
    }
  },
  
  useminPrepare: {
    html: '.tmp/index.html',
    options: {
      dest: '<%= yeoman.dist %>'
    }
  },
  
  // ...

最後只要在 grunt serve / build 的 task 裡面加入 jade 的 call 就可以囉

grunt.registerTask('serve', function (target) {
  if (target === 'dist') {
    return grunt.task.run(['build', 'connect:dist:keepalive']);
  }
  
  grunt.task.run([
    'clean:server',
    'jade',
  // ...

  ]);
});

// jade 的位置很重要,你必須在 usemin 之前先將 html 編譯完成,才可以正常的進行最小化以及 bower-install 的動作


grunt.registerTask('build', [
  'clean:dist',
  'jade',
  // ...

]);

以上就是在 generator-angular 加入 Jade / Stylus 的方法,這樣的修改方法會遇到個問題,就原本的用法 yo angular:controller 等新增動作時,會自動加入 index.html 的引用句會無法使用,因此你將必須手動將

至於修改這個 Bug 的方法,必須直接修改 generator 的 source 本身,這樣每當 generator 更新版本都需要再次修改,等於要維護一個新的 fork 在時間有限的情況下就先將就著用囉,詳細改法有興趣可以來信討論 :)

--
附上一個修改後的 index.jade 方便大家直接套用

doctype html
<!--[if lt IE 7]>
<html class="no-js lt-ie9 lt-ie8 lt-ie7">
<![endif]-->
<!--[if IE 7]>
<html class="no-js lt-ie9 lt-ie8">
<![endif]-->
<!--[if IE 8]>
<html class="no-js lt-ie9">
<![endif]-->
<!--[if gt IE 8]><!-->
<html class="no-js">
<!--<![endif]-->
head
  meta(charset="utf-8")
  meta(http-equiv="X-UA-Compatible", content="IE=edge")
  title
  meta(name="description", content="")
  meta(name="viewport", content="width=device-width")
  // Place favicon.ico and apple-touch-icon.png in the root directory
  // build:css styles/vendor.css
  // bower:css
  link(rel="stylesheet", href="bower_components/bootstrap/dist/css/bootstrap.css")
  // endbower
  // endbuild
  // build:css({.tmp,app}) styles/main.css
  link(rel="stylesheet", href="styles/main.css")
  // endbuild
body(ng-app="cominfinitibeatstyletripplacewebApp")
  <!--[if lt IE 7]>
  p.browsehappy
    | You are using an 
    strong outdated
    |  browser. Please 
    a(href="http://browsehappy.com/") upgrade your browser
    |  to improve your experience.
  <![endif]-->

  // Add your site or application content here 
  div.container(ng-view="")

  // Google Analytics: change UA-XXXXX-X to be your site's ID 
  script.
    (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
    (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
    m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
    })(window,document,'script','//www.google-analytics.com/analytics.js','ga');

    ga('create', 'UA-XXXXX-X');
    ga('send', 'pageview');

  <!--[if lt IE 9]>
  script(src="bower_components/es5-shim/es5-shim.js")
  script(src="bower_components/json3/lib/json3.min.js")
  <![endif]-->

  // build:js scripts/vendor.js 
  // bower:js 
  script(src="bower_components/jquery/jquery.js")
  script(src="bower_components/angular/angular.js")
  script(src="bower_components/bootstrap/dist/js/bootstrap.js")
  script(src="bower_components/angular-resource/angular-resource.js")
  script(src="bower_components/angular-cookies/angular-cookies.js")
  script(src="bower_components/angular-sanitize/angular-sanitize.js")
  script(src="bower_components/angular-route/angular-route.js")
  // endbower 
  // endbuild 

  // build:js({.tmp,app}) scripts/scripts.js 
  script(src="scripts/app.js")
  script(src="scripts/controllers/main.js")
  // endbuild 

最下方的 // endbuild 之上就是我提到要自行加入 script tag 的地方哦。

Angular Service Event

| Comments

AngularJS 的 Event 模組只能在 controller 中使用
當需要跨越不同的 controller scope 使用時
就無法直接使用內建的 $on, $emit 來實作 Event model
有兩種(或更多)解法來完成這樣的跨 controller 需求

Service variable and $scope.$watch

controller 的 $watch 可以監控 Service 的值
透過 injector service 便可做到不同 controller 之間的訊息溝通

angular.module('testApp')
  .controller('TestCtrl', function ($scope, Watcher) {
    $scope.$watch(function(){
      return Watcher.status;
    }, function (newStatus) {
        if (newStatus === 'done') {
        // ...do something

      }
    });
    // Remember:

    // if you want listen values in object, you have to add third parameter with true.

  )
  .controller('TestCtrl2', function ($scope, Watcher) {
    // ...some async code

    var callback = function () {
        Watcher.status = 'done';
    }
  )
  .service('Watcher', function () {
    this.status = null;
  
    return this;
  });
}

Service Scope

直接建立一個 Scope 來支援內建的 Event model
轉發他的 $on, $emit 來給其他 controller 使用

angular.module('testApp')
  .controller('TestCtrl', function ($scope, Watcher) {
    Watcher.$on('fetched', function () {
        // ...do something

    });
    
    // ...some async code

    var callback = function () {
        Watcher.$emit('fetched');
    }
  )
  .service('Watcher', function ($rootScope) {
    _scope = $rootScope.$new();
    
    this.$on = _scope.$on;
    this.$emit = _scope.$emit;
  
    return this;
  });