<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:blogger='http://schemas.google.com/blogger/2008' xmlns:georss='http://www.georss.org/georss' xmlns:gd="http://schemas.google.com/g/2005" xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-3638477104687038182</id><updated>2024-08-28T13:51:05.343+08:00</updated><category term="python"/><category term="Google app engine"/><category term="Javascript"/><category term="Ubuntu"/><category term="AWS"/><category term="Facebook"/><category term="Ionic Framework"/><category term="公司"/><category term="EC2"/><category term="Hybrid App"/><category term="django"/><category term="創業"/><category term="Angualr.js"/><category term="Mac"/><category term="NodeJS"/><category term="ElasticSearch"/><category term="Tutorial"/><category term="mongodb"/><category term="GQL"/><category term="Git"/><category term="Tornado"/><category term="創投"/><category term=".Net Framework"/><category term="Aptana"/><category term="C#"/><category term="Heroku"/><category term="Jenkins"/><category term="MySQL"/><category term="Nginx"/><category term="Office"/><category term="Outlook"/><category term="PostgreSQL"/><category term="Redis"/><category term="Unit Test"/><category term="boto"/><category term="npm"/><category term="rsyslog"/><category term="virtualenv"/><category term="金流"/><category term="Ajax"/><category term="Azure"/><category term="CSS"/><category term="Certificate"/><category term="Continuous Integration"/><category term="Cordova"/><category term="Database"/><category term="DynamoDB"/><category term="Firebase"/><category term="JSONP"/><category term="Java"/><category term="Jinja2"/><category term="OAuth"/><category term="PIL"/><category term="Paypal"/><category term="Pydev"/><category term="S3"/><category term="Sentry"/><category term="Startup"/><category term="Sublime Text 2"/><category term="VSTO"/><category term="XSS"/><category term="auto-scale"/><category term="celery"/><category term="compass"/><category term="daemon"/><category term="google+"/><category term="jQuery"/><category term="pyhton"/><category term="sass"/><category term="supervisor"/><category term="uwsgi"/><category term="vmware"/><category term="日本"/><category term="粉絲頁"/><category term="AMI"/><category term="Add-in"/><category term="Android"/><category term="Apache Benchmark"/><category term="Backbone.js"/><category term="BeautifulSoup"/><category term="Best Practice"/><category term="BitNami"/><category term="CAC"/><category term="CORS"/><category term="CSRF"/><category term="Capy"/><category term="CentOS"/><category term="Chef"/><category term="Churn"/><category term="ClickOnce"/><category term="Cloud Apps"/><category term="CloudFront"/><category term="CloudWatch"/><category term="Code Signing"/><category term="Content Security Policy"/><category term="Convertible Note"/><category term="Data Analysis"/><category term="DoS"/><category term="Dropbox"/><category term="EBS"/><category term="ELB"/><category term="Eclipse"/><category term="Elastic Beanstalk"/><category term="Ember.js"/><category term="Encoding"/><category term="FontTools"/><category term="Gitolite"/><category term="Google Chrome Extension"/><category term="Google IO"/><category term="GraphLab"/><category term="Investment"/><category term="Key-Value Datastore"/><category term="Kibana"/><category term="Knockout.js"/><category term="LTV"/><category term="Lindoe"/><category term="Log Management"/><category term="Microsoft"/><category term="Monit"/><category term="My Empire"/><category term="Noe4j"/><category term="OS tuning"/><category term="OpenID"/><category term="Picasa API"/><category term="Publishing"/><category term="Realtime"/><category term="RedHat"/><category term="Ribbon UI"/><category term="Ruby"/><category term="SMTP"/><category term="SPA"/><category term="SaaS"/><category term="Same Origin Policy"/><category term="Sencha Touch 2"/><category term="Signing"/><category term="Subscription"/><category term="Testing"/><category term="Virtual Machine"/><category term="Visual Studio"/><category term="Web Sites"/><category term="Windows"/><category term="YepNope"/><category term="Zen-Coding"/><category term="async"/><category term="await"/><category term="brew"/><category term="browser extension"/><category term="canvas"/><category term="cmake"/><category term="coffee-script"/><category term="cookie"/><category term="cross-domain"/><category term="distribute"/><category term="docker"/><category term="environment"/><category term="fabric"/><category term="facet"/><category term="fixtures"/><category term="fonten"/><category term="fontforge"/><category term="forever"/><category term="fortran"/><category term="full text search"/><category term="gmail"/><category term="gnuplot"/><category term="html"/><category term="html2jade"/><category term="html5"/><category term="iOS"/><category term="initctl"/><category term="jade"/><category term="less"/><category term="libgd"/><category term="libpng"/><category term="linux"/><category term="logstash"/><category term="middleware"/><category term="mongoengine"/><category term="nGram"/><category term="nimfa"/><category term="numpy"/><category term="nvm"/><category term="open graph"/><category term="opencc"/><category term="pep8"/><category term="pip"/><category term="plurk"/><category term="pymongo"/><category term="recommendation system"/><category term="replace"/><category term="scipy"/><category term="sfntly"/><category term="sharding"/><category term="snippet"/><category term="sqlite3"/><category term="ssh"/><category term="stem"/><category term="supervisord"/><category term="table"/><category term="thread"/><category term="thrift"/><category term="tox"/><category term="twitter"/><category term="upstart"/><category term="user-data"/><category term="zenblip"/><category term="健保"/><category term="在留卡"/><category term="工作簽證"/><category term="心得"/><category term="營業稅"/><category term="簽證"/><category term="網站安全"/><category term="網頁加速"/><category term="線上購物"/><category term="腳踏車"/><category term="謊言"/><category term="遊戲"/><title type='text'>SushiWen&#39;s Blog</title><subtitle type='html'></subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://sushiwens.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3638477104687038182/posts/default?redirect=false'/><link rel='alternate' type='text/html' href='http://sushiwens.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><link rel='next' type='application/atom+xml' href='http://www.blogger.com/feeds/3638477104687038182/posts/default?start-index=26&amp;max-results=25&amp;redirect=false'/><author><name>Unknown</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>164</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-3638477104687038182.post-8224967743942896850</id><published>2015-11-25T13:12:00.000+08:00</published><updated>2015-11-25T15:21:05.943+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Android"/><category scheme="http://www.blogger.com/atom/ns#" term="Cordova"/><category scheme="http://www.blogger.com/atom/ns#" term="Ionic Framework"/><category scheme="http://www.blogger.com/atom/ns#" term="iOS"/><category scheme="http://www.blogger.com/atom/ns#" term="Tutorial"/><title type='text'>Ionic Framework 教學 - 11. App 上架</title><content type='html'>Android 和 iOS 分開上架，上架前要先修改參數，以 Android 為例，我們先移除 console log （注意如果你之後要繼續開發的話要把這個 plugin 加回來）。
&lt;br /&gt;
&lt;pre class=&quot;prettyprint lang-shell&quot;&gt;cordova plugin rm cordova-plugin-console
&lt;/pre&gt;
&lt;br /&gt;
然後可以修改 mysidemenu/platforms/android/AndroidManifest.xml 裡面的 android:versionName=&quot;0.0.1&quot; 
&lt;br /&gt;
之後跑 release build 然後產生 my-release-key.keystore 來簽署我們的 apk ，再用 zipalign 打包好，就可以&lt;a href=&quot;https://play.google.com/apps/publish/&quot;&gt;到 Google Play 上架了&lt;/a&gt;&lt;br /&gt;
&lt;pre class=&quot;prettyprint lang-shell&quot;&gt;cordova build --release android
keytool
cd platforms/android/build/outputs/apk/
keytool -genkey -v -keystore my-release-key.keystore -alias alias_name -keyalg RSA -keysize 2048 -validity 10000
ls
jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore my-release-key.keystore android-release-unsigned.apk alias_name
~/android/android-sdk-macosx/build-tools/22.0.1/zipalign -v 4 android-release-unsigned.apk myapp.apk
&lt;/pre&gt;
&lt;br /&gt;
上架填寫表單的時候要注意不要使用網路上抓來的圖片。
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;</content><link rel='replies' type='application/atom+xml' href='http://sushiwens.blogspot.com/feeds/8224967743942896850/comments/default' title='張貼留言'/><link rel='replies' type='text/html' href='http://sushiwens.blogspot.com/2015/11/ionic-framework-11-app.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3638477104687038182/posts/default/8224967743942896850'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3638477104687038182/posts/default/8224967743942896850'/><link rel='alternate' type='text/html' href='http://sushiwens.blogspot.com/2015/11/ionic-framework-11-app.html' title='Ionic Framework 教學 - 11. App 上架'/><author><name>Unknown</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3638477104687038182.post-4249179698737237285</id><published>2015-11-25T13:11:00.000+08:00</published><updated>2015-11-25T14:07:56.043+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Angualr.js"/><category scheme="http://www.blogger.com/atom/ns#" term="Cordova"/><category scheme="http://www.blogger.com/atom/ns#" term="Ionic Framework"/><category scheme="http://www.blogger.com/atom/ns#" term="Tutorial"/><title type='text'>Ionic Framework 教學 - 10. 使用 Cordova Plugin</title><content type='html'>&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;http://images6.fanpop.com/image/photos/35000000/wanted-spongebob-squarepants-35042475-630-870.jpg&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;http://images6.fanpop.com/image/photos/35000000/wanted-spongebob-squarepants-35042475-630-870.jpg&quot; height=&quot;400&quot; width=&quot;288&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
承&lt;a href=&quot;http://sushiwens.blogspot.tw/2015/11/ionic-framework-9.html&quot;&gt;上一章&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
我們來試著製作一個會用到相機的 App 來練習 Plugin 的使用，相機的功能是藉由 Cordova Plugin 來連接的。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
首先 Shell 指令進入我們的 project，然後增加手機目標平台系統，ios 或是 android
&lt;br /&gt;
&lt;pre class=&quot;prettyprint lang-shell&quot;&gt;cd mysidemenu
ionic platform add android
ionic platform add ios
&lt;/pre&gt;
&lt;br /&gt;
然後安裝 Camera Plugin 和 Canvas2Image Plugin
&lt;br /&gt;
&lt;pre class=&quot;prettyprint lang-shell&quot;&gt;
npm update -g cordova
cordova platform update android@5.0.0
cordova plugin add cordova-plugin-camera
cordova plugin add https://github.com/devgeeks/Canvas2ImagePlugin.git
cordova plugin add cordova-plugin-whitelist
&lt;/pre&gt;
&lt;br /&gt;
在 www/templates/menu.html 增加一個選單
&lt;br /&gt;
&lt;pre class=&quot;prettyprint lang-html&quot;&gt;&amp;lt;ion-item menu-close href=&quot;#/app/photo&quot;&amp;gt;
  Wanted
&amp;lt;/ion-item&amp;gt;
&lt;/pre&gt;
&lt;br /&gt;
然後增加 www/templates/photo.html
&lt;br /&gt;
&lt;pre class=&quot;prettyprint lang-html&quot;&gt;&amp;lt;ion-view&amp;gt;
  
  &amp;lt;!-- add a camera button to take a photo --&amp;gt;
  &amp;lt;ion-nav-buttons side=&quot;secondary&quot;&amp;gt;
    &amp;lt;button
      ng-click=&quot;capturePhoto()&quot;
      class=&quot;button button-icon icon icon-small ion-camera&quot;&amp;gt;
    &amp;lt;/button&amp;gt;
  &amp;lt;/ion-nav-buttons&amp;gt;

  &amp;lt;ion-content&amp;gt;

    &amp;lt;div
      style=&quot;
        width:320px;
        height:453px;
        overflow: hidden;
        margin: 0 auto;
      &quot;&amp;gt;
      &amp;lt;div
        style=&quot;
          width:320px;
          height: 1000px;
          background-image:url(&#39;{{photo.url}}&#39;);
          background-repeat: no-repeat;
          background-size: contain;
        &quot;&amp;gt;
        &amp;lt;img ng-src=&quot;{{framePhoto}}&quot;
          style=&quot;
            width:320px;
          &quot;&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;button class=&quot;button button-full&quot; ng-click=&quot;save()&quot;&amp;gt;Save&amp;lt;/button&amp;gt;
  &amp;lt;/ion-content&amp;gt;

&amp;lt;/ion-view&amp;gt;
&lt;/pre&gt;
&lt;br /&gt;
這裡可以看到我們使用一個 background image ，圖片位置是一個變數 photo.url ，然後前面放一個圖片 img ，還有兩個按鈕對影兩個函式， capturePhoto 用來拍照， save() 用來儲存照片。
&lt;br /&gt;
接下來我們定義一個 Controller 來處理這些邏輯。
&lt;br /&gt;
&lt;pre class=&quot;prettyprint lang-js&quot;&gt;.controller(&#39;PhotoCtrl&#39;, function($scope) {
  
  $scope.framePhoto = &#39;http://i.imgur.com/5WkxqFC.png?1&#39;;
  //photo to show in view page
  $scope.photo = {};
  
  //options to take a photo
  var photoOptions = {};
  if( typeof Camera !== &#39;undefined&#39;){

    //https://github.com/apache/cordova-plugin-camera
    photoOptions = {
      quality: 100,
      destinationType: Camera.DestinationType.FILE_URL,
      sourceType: Camera.PictureSourceType.CAMERA, //[PHOTOLIBRARY, CAMERA, SAVEDPHOTOALBUM]
      encodingType: 1, //jpeg:0 png:1
      targetWidth: 320,
      targetHeight: 453
    };

  }

  /**
   * Capture a photo, when user click the camera icon
   */
  $scope.capturePhoto = function() {
    if(navigator.camera){
      navigator.camera.getPicture(
        onGetPictureSuccess, onGetPictureError, photoOptions);
    }
  };

  /**
   * Called when the photo is taken
   * @param  {string} result photo local url
   */
  function onGetPictureSuccess (result) {
    console.log(&#39;onGetPictureSuccess&#39;, result);
    $scope.photo.url = result;
    $scope.$apply();
  }

  /**
   * Called when the capture photo runs into an error
   * @param  {object} err [description]
   */
  function onGetPictureError (err) {
    console.error(&#39;error&#39;, err);
  }



  /**
   * Load an image from a file path, call &#39;onload&#39; function when loaded.
   * @param  {string} src    image file path
   * @param  {function} onload callback function
   * @return {image}        
   */
  function loadImage(src, onload) {
    console.log(&#39;loadImage&#39;);
    var img = new Image();

    img.onload = onload;
    img.src = src;
    return img;
  }

  
  /**
   * Combines two images in a canvas, use the size of image 1.
   * @param  {string}   src1     file path of image 1
   * @param  {string}   src2     file path of image 2
   * @param  {Function} callback function when 2 images combined
   */
  function combineImages (src1, src2, callback) {
    console.log(&#39;combineImages&#39;, [src1, src2]);
    var canvas = document.createElement(&#39;canvas&#39;);
    var ctx = canvas.getContext(&#39;2d&#39;), dataURL;
    var img1 = loadImage(src1, onload);
    var img2 = loadImage(src2, onload);
    var i = 0, base64Photo;
    function onload() {
      i += 1;
      if(i == 2){ //when 2 images are both loaded
        i = 0;
        canvas.width = img1.width;
        canvas.height = img1.height;
        //draw img2 according to img1&#39;s ratio
        ctx.drawImage(img2, 0, 0, img2.width, img2.height, 0, 0, img1.width, img1.width*img2.height/img2.width);
        ctx.drawImage(img1, 0, 0);
        callback(canvas);
      }
    }
  }

  /**
   * save image from canvas to gallery
   * @param  {canvas} canvas
   */
  function saveImageFromCanvas(canvas){
    window.canvas2ImagePlugin.saveImageDataToLibrary(
      function(msg){
          console.log(&#39;canvas2ImagePlugin&#39;, msg);
      },
      function(err){
          console.log(&#39;canvas2ImagePlugin&#39;, err);
      },
      canvas
    );
  }

  $scope.save = function() {
    //combine 2 images 
    combineImages($scope.framePhoto, $scope.photo.url, saveImageFromCanvas);
  };

})
&lt;/pre&gt;
&lt;br /&gt;
理解程式內容請一邊閱讀程式註解。
最後我們在 www/js/app.js 增加一組 routes
&lt;br /&gt;
&lt;pre class=&quot;prettyprint lang-js&quot;&gt;.state(&#39;app.photo&#39;, {
  url: &#39;/photo&#39;,
  views: {
    &#39;menuContent&#39;: {
      templateUrl: &#39;templates/photo.html&#39;,
      controller: &#39;PhotoCtrl&#39;
    }
  }
})
&lt;/pre&gt;
&lt;br /&gt;
試著執行這個頁面，發現不能執行，因為電腦上面沒有這些 Plugin 對應的手機程式，我們要到手機上去測試執行。
&lt;br /&gt;
有別於之前上傳的測試方式，這次我們用到 Plugin 裡面因為包含原生的程式，我們必須要直接編譯這個 App。
&lt;br /&gt;
請到 App Store 安裝 Xcode 或是到 &lt;a href=&quot;http://developer.android.com/sdk/installing/index.html&quot;&gt;Android 官網下載 Android SDK&lt;/a&gt;，然後安裝好並且把 Android SDK 環境變數加到系統內。
&lt;br /&gt;
接著到命令列 Shell 編譯你需要的對應的手機系統。
&lt;br /&gt;
&lt;pre class=&quot;prettyprint lang-shell&quot;&gt;ionic build ios
ionic build android
&lt;/pre&gt;
&lt;br /&gt;
編譯沒有問題之後，如果你是使用 Android 系統，你可以接上你的手機測試，請打開你手機的 developer mode ，ios 的使用者可以跑 simulator。
&lt;br /&gt;
&lt;pre class=&quot;prettyprint lang-shell&quot;&gt;
ionic run ios
ionic run android
&lt;/pre&gt;
&lt;br /&gt;
遺憾的是，ios simulator 只能檢查你編譯成功，沒有支援相機功能。
&lt;br /&gt;
iOS 的話我們進到 Xcode 打開 mysidemenu project ， File &gt; Open... &gt; mysidemenu/platforms/ios/mysidemenu.xcodeproj
&lt;br /&gt;
連接手機平板裝置後開始編譯。
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;</content><link rel='replies' type='application/atom+xml' href='http://sushiwens.blogspot.com/feeds/4249179698737237285/comments/default' title='張貼留言'/><link rel='replies' type='text/html' href='http://sushiwens.blogspot.com/2015/11/ionic-framework-10.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3638477104687038182/posts/default/4249179698737237285'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3638477104687038182/posts/default/4249179698737237285'/><link rel='alternate' type='text/html' href='http://sushiwens.blogspot.com/2015/11/ionic-framework-10.html' title='Ionic Framework 教學 - 10. 使用 Cordova Plugin'/><author><name>Unknown</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3638477104687038182.post-1592767698909157053</id><published>2015-11-19T13:07:00.000+08:00</published><updated>2015-11-20T10:43:00.665+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Angualr.js"/><category scheme="http://www.blogger.com/atom/ns#" term="Hybrid App"/><category scheme="http://www.blogger.com/atom/ns#" term="Ionic Framework"/><category scheme="http://www.blogger.com/atom/ns#" term="Tutorial"/><title type='text'>Ionic Framework 教學 - 9. 簡易聊天室設定</title><content type='html'>承&lt;a href=&quot;http://sushiwens.blogspot.com/2015/11/ionic-framework-9.html&quot;&gt;第八章&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
我們來做一個設定頁面，讓我們在聊天室講話的時候可以有一個暱稱。&lt;br /&gt;
&lt;br /&gt;
請大家建立一個設定頁面 www/templates/settings.html&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint lang-html&quot;&gt;&amp;lt;ion-view view-title=&quot;設定&quot;&amp;gt;
  &amp;lt;ion-content&amp;gt;
    &amp;lt;ion-list&amp;gt;

      &amp;lt;ion-item class=&quot;item item-input-inset&quot;&amp;gt;
        &amp;lt;label class=&quot;item-input-wrapper&quot;&amp;gt;
          &amp;lt;input type=&quot;text&quot; placeholder=&quot;Nickname&quot; ng-model=&quot;currentUser.displayName&quot;&amp;gt;
        &amp;lt;/label&amp;gt;
        &amp;lt;button class=&quot;button button-small&quot; ng-click=&quot;saveName()&quot;&amp;gt;
          Save
        &amp;lt;/button&amp;gt;
      &amp;lt;/ion-item&amp;gt;

      &amp;lt;label class=&quot;item item-input item-select&quot;&amp;gt;
        &amp;lt;div class=&quot;input-label&quot;&amp;gt;
          Color
        &amp;lt;/div&amp;gt;
        &amp;lt;select class=&quot;&quot; 
          ng-model=&quot;settings.color&quot; 
          ng-options=&quot;c.value as c.name for c in colors&quot; 
          ng-change=&quot;showColor()&quot;&amp;gt;
      &amp;lt;/label&amp;gt;

    &amp;lt;/ion-list&amp;gt;
  &amp;lt;/ion-content&amp;gt;
&amp;lt;/ion-view&amp;gt;
&lt;/pre&gt;
&lt;br /&gt;
這裡使用了 currentUser.displayName 來當做顯示名稱，saveName()函式來儲存名稱，使用 select 來選擇顏色，當顏色被選擇之後會觸發 ng-change ，就可以用 showColor()來看看顏色變數被改變了嗎。&lt;br /&gt;
然後我們來定義這些功能，請增加一個 SettingsCtrl &lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint lang-js&quot;&gt;.controller(&#39;SettingsCtrl&#39;, function($scope, $rootScope) {

  //gloabl variable, local storage
  $scope.currentUser = JSON.parse(window.localStorage.currentUser) || {};

  //global variable, temporary
  $rootScope.settings = $rootScope.settings || {};

  //https://en.wikipedia.org/wiki/Web_colors
  $scope.colors = [
    {
      name: &#39;Navy&#39;,
      value: &#39;#000080&#39;,
    },
    {
      name: &#39;Crimson&#39;,
      value: &#39;#DC143C&#39;
    },
    {
      name: &#39;DimGray&#39;,
      value: &#39;#696969&#39;
    }
  ];

  $scope.showColor = function() {
    console.log($rootScope.settings);
  };

  $scope.saveName = function() {
    alert($scope.currentUser.displayName + &#39; saved!&#39;);
    window.localStorage.currentUser = JSON.stringify($scope.currentUser);
  };

})
&lt;/pre&gt;
&lt;br /&gt;
這裡我們有兩種全域變數的寫法，一種是存在 local storage 一種是存在 $rootScope 裡面，等下我們來看看這兩種用法的差別。&lt;br /&gt;
注意這裡，在 window.localStorage 中必須要用字串的形式儲存，所以我們用 JSON.stringify 來把物件轉換成字串。讀取的時候則用 JSON.parse 來把字串轉換成物件。&lt;br /&gt;
然後我們在 www/js/app.js 增加一個 route state 來處理 settings &lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint lang-js&quot;&gt;.state(&#39;app.settings&#39;, {
  url: &#39;/settings&#39;,
  views: {
    &#39;menuContent&#39;: {
      templateUrl: &#39;templates/settings.html&#39;,
      controller: &#39;SettingsCtrl&#39;
    }
  }
})
&lt;/pre&gt;
然後我們在 www/templates/messages.html 增加一個按鈕可以連到設定頁面，把這個按鈕增加到 ion-view 的裡面，ion-content的上面。&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint lang-html&quot;&gt;  &amp;lt;ion-nav-buttons side=&quot;secondary&quot;&amp;gt;
    &amp;lt;button ui-sref=&quot;app.settings&quot;
      class=&quot;button button-icon icon ion-gear-a&quot;&amp;gt;
    &amp;lt;/button&amp;gt;
  &amp;lt;/ion-nav-buttons&amp;gt;
&lt;/pre&gt;
&lt;br /&gt;
這是一個 header button ， secondary 代表他在次要位置。
&lt;br /&gt;
然後我們修改 www/templates/messages.html 的內容，增加了顯示名稱和顏色，完成後大概成為這樣。
&lt;br /&gt;
&lt;pre class=&quot;prettyprint lang-html&quot;&gt;&amp;lt;ion-view view-title=&quot;{{chatroom.name}}&quot;&amp;gt;
  &amp;lt;ion-nav-buttons side=&quot;secondary&quot;&amp;gt;
    &amp;lt;button ui-sref=&quot;app.settings&quot;
      class=&quot;button button-icon icon ion-gear-a&quot;&amp;gt;
    &amp;lt;/button&amp;gt;
  &amp;lt;/ion-nav-buttons&amp;gt;
  &amp;lt;ion-content&amp;gt;
    &amp;lt;ion-list&amp;gt;
      &amp;lt;ion-item class=&quot;item-input-inset&quot;&amp;gt;
        &amp;lt;label class=&quot;item-input-wrapper&quot;&amp;gt;
          &amp;lt;input type=&quot;text&quot; placeholder=&quot;New Message&quot; ng-model=&quot;newMessage.content&quot;&amp;gt;
        &amp;lt;/label&amp;gt;
        &amp;lt;button class=&quot;button button-small&quot; ng-click=&quot;sendMessage()&quot;&amp;gt;
          Send
        &amp;lt;/button&amp;gt;
      &amp;lt;/ion-item&amp;gt;
      &amp;lt;ion-item class=&quot;item-text-wrap&quot; ng-repeat=&quot;message in messages.slice().reverse()&quot;&amp;gt;
        &amp;lt;strong class=&quot;&quot; ng-show=&quot;message.displayName&quot;&amp;gt;{{message.displayName}}:&amp;lt;/strong&amp;gt;
        &amp;lt;span style=&quot;color:{{message.color || &#39;#000&#39;}}&quot;&amp;gt;
          {{message.content}}
        &amp;lt;/span&amp;gt;
      &amp;lt;/ion-item&amp;gt;
    &amp;lt;/ion-list&amp;gt;
  &amp;lt;/ion-content&amp;gt;
&amp;lt;/ion-view&amp;gt;
&lt;/pre&gt;
&lt;br /&gt;
然後我們回頭修改 MessagesCtrl 來讓送出的 message 夾帶 displayName 和 color 的資訊。
&lt;br /&gt;
&lt;pre class=&quot;prettyprint lang-js&quot;&gt;.controller(&#39;MessagesCtrl&#39;, function($scope, $state, $rootScope, ChatroomService, MessageService) {

  console.log(&#39;Chatroom Id: &#39;, $state.params.roomId);

  //get current chatroom from roomId
  $scope.chatroom = ChatroomService.get($state.params.roomId) || {};
  console.log(&#39;This is chatroom: &#39;, $scope.chatroom.name);

  //get messages in this chatroom
  $scope.messages = MessageService.all($state.params.roomId);

  //global variable, localStorage
  var currentUser = JSON.parse(window.localStorage.currentUser) || {};
  $rootScope.settings = $rootScope.settings || {};
  console.log($rootScope.settings);

  $scope.newMessage = {};

  $scope.sendMessage = function () {
    $scope.newMessage.color = $rootScope.settings.color || &#39;#000&#39;;
    $scope.newMessage.displayName = currentUser.displayName;
    console.log(&#39;New message: &#39;, $scope.newMessage);

    //send message
    MessageService.send($scope.newMessage).then(function() {
      $scope.newMessage.content = &#39;&#39;;
    });
  };

})
&lt;/pre&gt;
&lt;br /&gt;
如果你重新載入程式，你會發現 color 的設定不見了，但是你的暱稱還保留著。&lt;br /&gt;
&lt;br /&gt;
藉由這個練習，我們可以知道 localStorage 和 $rootScope 的身為全域變數的方便強大和危險，強大是在任何地方修改這個變數都有效，危險也是因為你無法確定這個變數會被什麼函式在任何什麼地方修改了，造成不是你預期的效果。
&lt;br /&gt;
&lt;br /&gt;
練習題：使用者設定顏色之後，要去聊天室打字才能知道是什麼顏色，這不是一個好的使用者體驗，如何在選擇完顏色的時候讓使用者知道是什麼顏色呢？&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;</content><link rel='replies' type='application/atom+xml' href='http://sushiwens.blogspot.com/feeds/1592767698909157053/comments/default' title='張貼留言'/><link rel='replies' type='text/html' href='http://sushiwens.blogspot.com/2015/11/ionic-framework-9.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3638477104687038182/posts/default/1592767698909157053'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3638477104687038182/posts/default/1592767698909157053'/><link rel='alternate' type='text/html' href='http://sushiwens.blogspot.com/2015/11/ionic-framework-9.html' title='Ionic Framework 教學 - 9. 簡易聊天室設定'/><author><name>Unknown</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3638477104687038182.post-5641364086926945666</id><published>2015-11-19T13:06:00.001+08:00</published><updated>2015-11-20T10:42:25.433+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Angualr.js"/><category scheme="http://www.blogger.com/atom/ns#" term="Hybrid App"/><category scheme="http://www.blogger.com/atom/ns#" term="Ionic Framework"/><title type='text'>Ionic Framework 教學 - 8. 簡易聊天室</title><content type='html'>承&lt;a href=&quot;http://sushiwens.blogspot.com/2015/11/ionic-framework-6.html&quot;&gt;第六章&lt;/a&gt;和&lt;a href=&quot;http://sushiwens.blogspot.com/2015/11/ionic-framework-7.html&quot;&gt;第七章&lt;/a&gt;&lt;br /&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
設定好 Firebase 後，我們可以來做簡易聊天室，了解資料怎麼和後台連結，為了方便教學，這裡的聊天室我們只做匿名聊天室。&lt;/div&gt;
&lt;div&gt;
首先我們要做一個聊天室列表，我們增加一個 ChatroomsCtrl Controller，注意這裡 Chatrooms 是複數。&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint lang-js&quot;&gt;.controller(&#39;ChatroomsCtrl&#39;, function($scope) {

  $scope.chatrooms = [];
  
  $scope.newChatroom = {};

  $scope.createChatroom = function () {
    console.log($scope.newChatroom);
  };
})
&lt;/pre&gt;
&lt;br /&gt;
記得 $scope 代表是 View 要使用的變數，這裡我們定義了 chatrooms 陣列， newChatroom 物件和 createChatroom 函式。&lt;br /&gt;
console.log 代表在瀏覽器 console 把變數內容印出來看看的意思。 &lt;br /&gt;
然後建立 www/templates/chatrooms.html&lt;br /&gt;
&lt;pre class=&quot;prettyprint lang-html&quot;&gt;&amp;lt;ion-view view-title=&quot;Chatrooms&quot;&amp;gt;
  &amp;lt;ion-content&amp;gt;
    &amp;lt;ion-list&amp;gt;
      &amp;lt;ion-item class=&quot;item-input-inset&quot;&amp;gt;
        &amp;lt;label class=&quot;item-input-wrapper&quot;&amp;gt;
          &amp;lt;input type=&quot;text&quot; placeholder=&quot;New Chatroom Name&quot; ng-model=&quot;newChatroom.name&quot;&amp;gt;
        &amp;lt;/label&amp;gt;
        &amp;lt;button class=&quot;button button-small&quot; ng-click=&quot;createChatroom()&quot;&amp;gt;
          Create
        &amp;lt;/button&amp;gt;
      &amp;lt;/ion-item&amp;gt;
      &amp;lt;ion-item ng-repeat=&quot;chatroom in chatrooms&quot; href=&quot;#/app/chatrooms/{{chatroom.$id}}&quot;&amp;gt;
        {{chatroom.name}}
      &amp;lt;/ion-item&amp;gt;
    &amp;lt;/ion-list&amp;gt;
  &amp;lt;/ion-content&amp;gt;
&amp;lt;/ion-view&amp;gt;
&lt;/pre&gt;
其中 ng-model 代表使用變數，ng-repeat 代表使用陣列中的變數。&lt;br /&gt;
然後在 www/js/app.js 增加一個 route
&lt;br /&gt;
&lt;pre class=&quot;prettyprint lang-js&quot;&gt;  .state(&#39;app.chatrooms&#39;, {
    url: &#39;/chatrooms&#39;,
    views: {
      &#39;menuContent&#39;: {
        templateUrl: &#39;templates/chatrooms.html&#39;,
        controller: &#39;ChatroomsCtrl&#39;
      }
    }
  })
&lt;/pre&gt;
&lt;br /&gt;
我們順便把 www/js/app.js 最下面的 $urlRouterProvider.otherwise(&#39;/app/playlists&#39;); 改掉，這是預設的顯示頁面，如果連到任何未定義的 url 就會改連到這裡，我們改成 /app/chatrooms。
&lt;br /&gt;
&lt;pre class=&quot;prettyprint lang-js&quot;&gt;$urlRouterProvider.otherwise(&#39;/app/chatrooms&#39;);
&lt;/pre&gt;
&lt;br /&gt;
最後在 www/templates/menu.html 增加一個 Chatrooms 選項&lt;br /&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;pre class=&quot;prettyprint lang-html&quot;&gt;&amp;lt;ion-item menu-close href=&quot;#/app/chatrooms&quot;&amp;gt;
  Chatrooms
&amp;lt;/ion-item&amp;gt;
&lt;/pre&gt;
&lt;br /&gt;
現在可以按按看按鈕，理解一下程式內容。
&lt;br /&gt;
然後我們增加一個 www/js/services.js&lt;br /&gt;
&lt;pre class=&quot;prettyprint lang-js&quot;&gt;var firebaseUrl = &quot;https://your-project-id.firebaseio.com/&quot;;
angular.module(&#39;starter.services&#39;, [])
//Firebase datastore reference
.factory(&quot;FirebaseRef&quot;, function($firebaseAuth) {
  return new Firebase(firebaseUrl);
})
//for log in and sign up
.factory(&quot;Auth&quot;, function($firebaseAuth) {
  var usersRef = new Firebase(firebaseUrl+&quot;users&quot;);
  return $firebaseAuth(usersRef);
})
.factory(&quot;ChatroomService&quot;, function($firebaseArray) {
  var roomsRef = new Firebase(firebaseUrl+&quot;chatrooms&quot;);
  var rooms = $firebaseArray(roomsRef);
  return {
    all: function() {
      console.log(rooms);
      return rooms;
    },
    get: function(roomId) {
      return rooms.$getRecord(roomId);
    },
    create: function(chatroom) {
      console.log(&#39;Adding chatroom: &#39;, chatroom.name);
      return rooms.$add(chatroom);
    }
  }
});
&lt;/pre&gt;
&lt;br /&gt;
其中 your-project-id 請填入你在 Firebase 申請的 Project ID ，這裡我們在做一個和 Firebase 溝通的 Chatroom service 。&lt;br /&gt;
這裡我們定義了 ChatroomService 服務，他主要有三個功能 all 來取得所有的聊天室， get 來取得特定 ID 的聊天室， create 來創造一個聊天室。&lt;br /&gt;
我們在第六章有說到，在 www/index.html 中，需要引用 services.js 來讓這個服務可以載入到頁面中。
&lt;br /&gt;
&lt;pre class=&quot;prettyprint lang-html&quot;&gt;...
&amp;lt;script src=&quot;js/app.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script src=&quot;js/services.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
...
&lt;/pre&gt;
&lt;br /&gt;
在第六章也有提到，在 app.js 最上面要加上 starter app 對 &#39;firebase&#39; 和 &#39;starter.services&#39; 的依賴關係，這樣 angular 才知道要引用這些程式和服務。 &lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint lang-js&quot;&gt;angular.module(&#39;starter&#39;, [&#39;ionic&#39;, &#39;firebase&#39;, &#39;starter.services&#39;, &#39;starter.controllers&#39;])
&lt;/pre&gt;
&lt;br /&gt;
然後讓我們的 ChatroomsCtrl 來使用這個 service 。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint lang-js&quot;&gt;.controller(&#39;ChatroomsCtrl&#39;, function($scope, ChatroomService) {

  $scope.chatrooms = ChatroomService.all();
  
  $scope.newChatroom = {};

  $scope.createChatroom = function () {
    console.log($scope.newChatroom);
    ChatroomService.create($scope.newChatroom);
  };
})
&lt;/pre&gt;
&lt;br /&gt;
這樣就可以順利增加 Chatroom 了，如果你到 Firebase project console 也可以看到對應的資料被增加上去了，比如說我增加了三個聊天室，下面圖片亂數的部分就是 Firebase 給這三個聊天室的特殊 ID。&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjvbvTmpe2He42xv-e8Qb5IP9V9q2s7dfR7xdC6uKNa0barAEaBh27Swp7MaZ31xvrwaoZJ0AM-X-JDmM4-WogcOeeVhZ8Q_MwksK1k97dx4wNsq3dH9A2_Ai3xAfN0LHBaiogXicCHiMKN/s1600/firebase-snapshot.png&quot; imageanchor=&quot;1&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjvbvTmpe2He42xv-e8Qb5IP9V9q2s7dfR7xdC6uKNa0barAEaBh27Swp7MaZ31xvrwaoZJ0AM-X-JDmM4-WogcOeeVhZ8Q_MwksK1k97dx4wNsq3dH9A2_Ai3xAfN0LHBaiogXicCHiMKN/s400/firebase-snapshot.png&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
目前Chatroom bar 點下去是沒有用的，因為對應的路徑 #/app/chatrooms/{{chatroom.$id}} 還沒有定義。&lt;br /&gt;
&lt;br /&gt;
現在我們來製作可以發話的聊天室。&lt;br /&gt;
首先我們增加這個聊天室訊息的 Service 。&lt;br /&gt;
在 www/js/services.js 下面增加 MessageService 。&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint lang-js&quot;&gt;.factory(&quot;MessageService&quot;, function($firebaseArray) {
  var messagesRef = new Firebase(firebaseUrl+&quot;messages&quot;);
  var messages;
  return {
    all: function(roomId) {
      console.log(&#39;get messages from: &#39;, roomId);
      var roomRef = new Firebase(firebaseUrl + &quot;chatrooms/&quot; + roomId + &quot;/messages&quot;);
      messages = $firebaseArray(roomRef);
      return messages;
    },
    send: function(message) {
      console.log(&#39;Adding message: &#39;, message);
      return messages.$add(message);
    }
  };
})
&lt;/pre&gt;
&lt;br /&gt;
這裡我們定義了 MessageService 有兩個功能， all 是根據某個 chatroom 的 ID 找出所有的對話訊息的 messages ，send 是用來發送訊息，就是把新的訊息加入 Firebase 中，這個 chatroom 的 messages 陣列。&lt;br /&gt;
&lt;br /&gt;
然後我們就可以在 Controller 中使用這個 Services 了，我們建立一個 MessagesCtrl&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint lang-js&quot;&gt;.controller(&#39;MessagesCtrl&#39;, function($scope, $state, ChatroomService, MessageService) {

  console.log(&#39;Chatroom Id: &#39;, $state.params.roomId);

  //get current chatroom from roomId
  $scope.chatroom = ChatroomService.get($state.params.roomId) || {};
  console.log(&#39;This is chatroom: &#39;, $scope.chatroom.name);

  //get messages in this chatroom
  $scope.messages = MessageService.all($state.params.roomId);

  $scope.newMessage = {};

  $scope.sendMessage = function () {
    console.log(&#39;New message: &#39;, $scope.newMessage);

    //send message
    MessageService.send($scope.newMessage);
  };

})
&lt;/pre&gt;
&lt;br /&gt;
然後我們來建立這個聊天室的訊息頁面，建立 www/templates/messages.html &lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint lang-html&quot;&gt;&amp;lt;ion-view view-title=&quot;{{chatroom.name}}&quot;&amp;gt;
  &amp;lt;ion-content&amp;gt;
    &amp;lt;ion-list&amp;gt;
      &amp;lt;ion-item class=&quot;item-input-inset&quot;&amp;gt;
        &amp;lt;label class=&quot;item-input-wrapper&quot;&amp;gt;
          &amp;lt;input type=&quot;text&quot; placeholder=&quot;New Message&quot; ng-model=&quot;newMessage.content&quot;&amp;gt;
        &amp;lt;/label&amp;gt;
        &amp;lt;button class=&quot;button button-small&quot; ng-click=&quot;sendMessage()&quot;&amp;gt;
          Send
        &amp;lt;/button&amp;gt;
      &amp;lt;/ion-item&amp;gt;
      &amp;lt;ion-item class=&quot;item-text-wrap&quot; ng-repeat=&quot;message in messages.slice().reverse()&quot;&amp;gt;
        {{message.content}}
      &amp;lt;/ion-item&amp;gt;
    &amp;lt;/ion-list&amp;gt;
  &amp;lt;/ion-content&amp;gt;
&amp;lt;/ion-view&amp;gt;
&lt;/pre&gt;
&lt;br /&gt;
這裡可以看到 chatroom 代表當前這個聊天室，newMessage 代表 input 輸入框中新增的訊息， sendMessage 代表按鈕按下去後送出訊息，ng-repeat=&quot;message in messages&quot; 代表列出所有在 messages 中的訊息。
&lt;br /&gt;
然後我們來增加一個 route ，修改 www/js/app.js
&lt;br /&gt;
&lt;pre class=&quot;prettyprint lang-js&quot;&gt;.state(&#39;app.messages&#39;, {
  url: &#39;/chatrooms/:roomId&#39;,
  views: {
    &#39;menuContent&#39;: {
      templateUrl: &#39;templates/messages.html&#39;,
      controller: &#39;MessagesCtrl&#39;
    }
  }
})
&lt;/pre&gt;
因為我們可以有很多個聊天室，每個聊天室的 url 不同，所以注意這裡的 :roomId ，代表這個 url 願意接受變數 roomId，並且把變數 roomId 傳給 MessageCtrl Controller 使用，請對照 chatrooms.html 的 href=&quot;#/app/chatrooms/{{chatroom.$id}}&quot; 觀察，我們把 url 中的 chatroom.$id 對應到 state route 的 roomId ， 使用者進到訊息頁面的時候 route 把 roomId 接受到的值給了新的 controller 中的 $state.params.roomId，我們是用這個介面來達成動態頁面的變數傳遞。
&lt;br /&gt;
&lt;br /&gt;
這裡編輯好之後就可以告一段落了。&lt;br /&gt;
&lt;br /&gt;
發送訊息之後，各位也可以到 Firebase console 去看你的資料結構。&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjloVRobkUwD9gTw9vVee2zBicFu55pel8MdeMv3CilN7SidRZ9RrrF725DmHWAi6DDZNKK-nLxn0Da2V5A5gPr1yqGBoZukZG05wlSKjcaB6wCCPLhL9hMV0B9FjkD5fJ9NtKK-_MY0Zgy/s1600/firebase-chatroom.png&quot; imageanchor=&quot;1&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;165&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjloVRobkUwD9gTw9vVee2zBicFu55pel8MdeMv3CilN7SidRZ9RrrF725DmHWAi6DDZNKK-nLxn0Da2V5A5gPr1yqGBoZukZG05wlSKjcaB6wCCPLhL9hMV0B9FjkD5fJ9NtKK-_MY0Zgy/s400/firebase-chatroom.png&quot; width=&quot;400&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;a href=&quot;http://sushiwens.blogspot.com/2015/11/ionic-framework-9.html&quot;&gt;下一章，簡易聊天室設定&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;</content><link rel='replies' type='application/atom+xml' href='http://sushiwens.blogspot.com/feeds/5641364086926945666/comments/default' title='張貼留言'/><link rel='replies' type='text/html' href='http://sushiwens.blogspot.com/2015/11/ionic-framework-8.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3638477104687038182/posts/default/5641364086926945666'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3638477104687038182/posts/default/5641364086926945666'/><link rel='alternate' type='text/html' href='http://sushiwens.blogspot.com/2015/11/ionic-framework-8.html' title='Ionic Framework 教學 - 8. 簡易聊天室'/><author><name>Unknown</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjvbvTmpe2He42xv-e8Qb5IP9V9q2s7dfR7xdC6uKNa0barAEaBh27Swp7MaZ31xvrwaoZJ0AM-X-JDmM4-WogcOeeVhZ8Q_MwksK1k97dx4wNsq3dH9A2_Ai3xAfN0LHBaiogXicCHiMKN/s72-c/firebase-snapshot.png" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3638477104687038182.post-1222006337887951383</id><published>2015-11-19T13:05:00.002+08:00</published><updated>2015-11-20T09:58:03.295+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Angualr.js"/><category scheme="http://www.blogger.com/atom/ns#" term="Firebase"/><category scheme="http://www.blogger.com/atom/ns#" term="Hybrid App"/><category scheme="http://www.blogger.com/atom/ns#" term="Ionic Framework"/><title type='text'>Ionic Framework 教學 - 7. 簡易登入和登出</title><content type='html'>繼續&lt;a href=&quot;http://sushiwens.blogspot.com/2015/11/ionic-framework-6.html&quot;&gt;上一章&lt;/a&gt;的教學，我們來做 Firebase 的使用者登入。&lt;br /&gt;
這篇也會提到 LocalStorage 的基本用法。 &lt;br /&gt;
首先我們建立一個使用者資訊頁面 www/templates/profile.html &lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint lang-html&quot;&gt;&amp;lt;ion-view view-title=&quot;使用者資訊&quot;&amp;gt;
  &amp;lt;ion-content&amp;gt;
    &amp;lt;div class=&quot;list&quot;&amp;gt;
      &amp;lt;div class=&quot;item&quot;&amp;gt;
        Name: {{currentUser.displayName}}
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/ion-content&amp;gt;
&amp;lt;/ion-view&amp;gt;
&lt;/pre&gt;
&lt;br /&gt;
定義一個 Controller ProfileCtrl&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint lang-js&quot;&gt;...
.controller(&#39;ProfileCtrl&#39;, function() {
})
...
&lt;/pre&gt;
&lt;br /&gt;
然後加入 app.js route
&lt;br /&gt;
&lt;pre class=&quot;prettyprint lang-js&quot;&gt;...
.state(&#39;app.profile&#39;, {
  url: &#39;/profile&#39;,
  views: {
    &#39;menuContent&#39;: {
      templateUrl: &#39;templates/profile.html&#39;,
      controller: &#39;ProfileCtrl&#39;
    }
  }
})
...
&lt;/pre&gt;
&lt;br /&gt;
修改 menu.html 增加 Profile 項目&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint lang-html&quot;&gt;&amp;lt;ion-item menu-close href=&quot;#/app/profile&quot;&amp;gt;
  Profile
&amp;lt;/ion-item&amp;gt;
&lt;/pre&gt;
&lt;br /&gt;
連到 http://localhost:8100/#/app/profile 會發現這個頁面目前沒有正確的內容，因為 currentUser 還沒有定義。&lt;br /&gt;
現在我們來實作登入功能。&lt;br /&gt;
第一步我們直接修改 www/templates/login.html &lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint lang-html&quot;&gt;&amp;lt;ion-modal-view&amp;gt;
  &amp;lt;ion-header-bar&amp;gt;
    &amp;lt;h1 class=&quot;title&quot;&amp;gt;登入&amp;lt;/h1&amp;gt;
    &amp;lt;div class=&quot;buttons&quot;&amp;gt;
      &amp;lt;button class=&quot;button button-clear&quot; ng-click=&quot;closeLogin()&quot;&amp;gt;關閉&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/ion-header-bar&amp;gt;
  &amp;lt;ion-content&amp;gt;
    &amp;lt;form ng-submit=&quot;doLogin()&quot;&amp;gt;
      &amp;lt;div class=&quot;list&quot;&amp;gt;
        &amp;lt;label class=&quot;item item-input&quot;&amp;gt;
          &amp;lt;span class=&quot;input-label&quot;&amp;gt;Email&amp;lt;/span&amp;gt;
          &amp;lt;input type=&quot;text&quot; ng-model=&quot;loginData.email&quot;&amp;gt;
        &amp;lt;/label&amp;gt;
        &amp;lt;label class=&quot;item item-input&quot;&amp;gt;
          &amp;lt;span class=&quot;input-label&quot;&amp;gt;Password&amp;lt;/span&amp;gt;
          &amp;lt;input type=&quot;password&quot; ng-model=&quot;loginData.password&quot;&amp;gt;
        &amp;lt;/label&amp;gt;
        &amp;lt;label class=&quot;item&quot;&amp;gt;
          &amp;lt;button class=&quot;button button-block button-positive&quot; type=&quot;submit&quot;&amp;gt;登入&amp;lt;/button&amp;gt;
        &amp;lt;/label&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/form&amp;gt;
  &amp;lt;/ion-content&amp;gt;
&amp;lt;/ion-modal-view&amp;gt;
&lt;/pre&gt;
&lt;br /&gt;
注意這裡我們改用了 loginData 方便使用上了解。&lt;br /&gt;
然後我們在 controllers.js 定義 loginData 和 doLogin() &lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint lang-js&quot;&gt;...
.controller(&#39;AppCtrl&#39;, function($scope, $ionicModal, $timeout, $rootScope, $state, $ionicLoading, 
  FirebaseRef, Auth) {

  // With the new view caching in Ionic, Controllers are only called
  // when they are recreated or on app start, instead of every page change.
  // To listen for when this page is active (for example, to refresh data),
  // listen for the $ionicView.enter event:
  //$scope.$on(&#39;$ionicView.enter&#39;, function(e) {
  //});

  // Form data for the login modal
  $scope.loginData = {};

  // Create the login modal that we will use later
  $ionicModal.fromTemplateUrl(&#39;templates/login.html&#39;, {
    scope: $scope
  }).then(function(modal) {
    $scope.modal = modal;
  });

  // Triggered in the login modal to close it
  $scope.closeLogin = function() {
    $scope.modal.hide();
  };

  // Open the login modal
  $scope.login = function() {
    $scope.modal.show();
  };

  $scope.loginData = {};

  // Perform the login action when the user submits the login form
  $scope.doLogin = function() {
    console.log($scope.loginData);
    var loginData = $scope.loginData;

    if (loginData &amp;amp;&amp;amp; loginData.email &amp;amp;&amp;amp; loginData.password) {
      $ionicLoading.show({
        template: &#39;登入中...&#39;
      });
      Auth.$authWithPassword({
        email: loginData.email,
        password: loginData.password
      }).then(function (authData) {
        console.log(authData);
        FirebaseRef.child(&quot;users&quot;).child(authData.uid).once(&#39;value&#39;, function (snapshot) {
          var val = snapshot.val();
          console.log(val);
          window.localStorage.currentUser = JSON.stringify(val);
          $scope.$apply(function () {
            $rootScope.currentUser = val;
          });
        }, function(err) {
          console.error(err);
        });
        $ionicLoading.hide();
        $scope.closeLogin();
        $state.go(&#39;app.profile&#39;);
      }).catch(function (error) {
        alert(&quot;登入失敗 &quot; + error.message);
        $ionicLoading.hide();
      });
    } else {
      alert(&quot;輸入 Email 和 Password&quot;)
    }
  };
})
...
&lt;/pre&gt;
&lt;br /&gt;
這裡可以看到使用者登入後，我們把使用者名稱儲存到 window.localStorage.currentUser 中，這個 window.localStorage 儲存內容必須要使用字串，所以我們把用 JSON.stringify 把使用者資訊從 object 轉換成字串。&lt;br /&gt;
window.localStorage 的內容可能被同樣機器上的其他程序看到和使用，所以不建議儲存使用者的秘密資訊，使用者資訊建議儲存在 server 。&lt;br /&gt;
&lt;br /&gt;
我們修改 app.js 的起始執行內容，把 currentUser 的內容代入到 currentUser ，這樣各個 View 都可以使用了。
&lt;br /&gt;
&lt;pre class=&quot;prettyprint lang-js&quot;&gt;...
.run(function($ionicPlatform, $rootScope) {
  $ionicPlatform.ready(function() {
    // Hide the accessory bar by default (remove this to show the accessory bar above the keyboard
    // for form inputs)
    if (window.cordova &amp;amp;&amp;amp; window.cordova.plugins.Keyboard) {
      cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
      cordova.plugins.Keyboard.disableScroll(true);

    }
    if (window.StatusBar) {
      // org.apache.cordova.statusbar required
      StatusBar.styleDefault();
    }
    
    console.log(JSON.parse(window.localStorage.currentUser));
    $rootScope.currentUser = JSON.parse(window.localStorage.currentUser) || null;

  });
})
...
&lt;/pre&gt;
&lt;br /&gt;
接下來實作登出功能。&lt;br /&gt;
修改 profile.html ，增加登出按鈕。&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint lang-html&quot;&gt;&amp;lt;ion-view view-title=&quot;使用者資訊&quot;&amp;gt;
  &amp;lt;ion-content&amp;gt;
    &amp;lt;div class=&quot;list&quot;&amp;gt;
      &amp;lt;div class=&quot;item&quot;&amp;gt;
        Name: {{currentUser.displayName}}
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;button class=&quot;button button-full button-positive&quot; ng-click=&quot;logout()&quot;&amp;gt;登出&amp;lt;/button&amp;gt;
  &amp;lt;/ion-content&amp;gt;
&amp;lt;/ion-view&amp;gt;
&lt;/pre&gt;
&lt;br /&gt;
然後修改 ProfileCtrl
&lt;br /&gt;
&lt;pre class=&quot;prettyprint lang-js&quot;&gt;...
.controller(&#39;ProfileCtrl&#39;, function($scope, $rootScope, Auth) {
  $scope.logout = function () {
    $rootScope.currentUser = null;
    window.localStorage.currentUser = null;
    Auth.$unauth();
  };
})
...
&lt;/pre&gt;
&lt;br /&gt;
現在可以試試看登入和登出了。
&lt;br /&gt;
&lt;br /&gt;
&lt;a href=&quot;http://sushiwens.blogspot.com/2015/11/ionic-framework-8.html&quot;&gt;下一章，簡易聊天室&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;</content><link rel='replies' type='application/atom+xml' href='http://sushiwens.blogspot.com/feeds/1222006337887951383/comments/default' title='張貼留言'/><link rel='replies' type='text/html' href='http://sushiwens.blogspot.com/2015/11/ionic-framework-7.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3638477104687038182/posts/default/1222006337887951383'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3638477104687038182/posts/default/1222006337887951383'/><link rel='alternate' type='text/html' href='http://sushiwens.blogspot.com/2015/11/ionic-framework-7.html' title='Ionic Framework 教學 - 7. 簡易登入和登出'/><author><name>Unknown</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3638477104687038182.post-5898307264725478797</id><published>2015-11-19T13:05:00.001+08:00</published><updated>2015-11-20T10:30:12.317+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Angualr.js"/><category scheme="http://www.blogger.com/atom/ns#" term="Firebase"/><category scheme="http://www.blogger.com/atom/ns#" term="Hybrid App"/><category scheme="http://www.blogger.com/atom/ns#" term="Ionic Framework"/><category scheme="http://www.blogger.com/atom/ns#" term="Tutorial"/><title type='text'>Ionic Framework 教學 - 6. 簡易註冊</title><content type='html'>承續上一章的應用，我們想來試做一個簡易聊天室，在做聊天室之前我們要先做使用者登入，對於使用者註冊和登入沒興趣的朋友可以跳至&lt;a href=&quot;http://sushiwens.blogspot.com/2015/11/ionic-framework-8.html&quot;&gt;第八章&lt;/a&gt;。&lt;br /&gt;
要做 instant messaging 的話，我們必須要有一個即時同步訊息的後台，因為我們這個教學主要是做手機端的開發，所以我們就選用一些現有的即時通訊後台服務吧，比如說：&lt;br /&gt;
&lt;br /&gt;
1. Layer:&amp;nbsp;&lt;a href=&quot;https://layer.com/&quot;&gt;https://layer.com/&lt;/a&gt;&lt;br /&gt;
2. Quickblox:&amp;nbsp;&lt;a href=&quot;http://quickblox.com/&quot;&gt;http://quickblox.com/&lt;/a&gt;&lt;br /&gt;
3. Firebase: &lt;a href=&quot;https://www.firebase.com/&quot;&gt;https://www.firebase.com/&lt;/a&gt;&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
就決定是你了，Firebase，因為我印象中有 Angular-Fire 這種已經成熟的轉接器，可以讓 Firebase 跟 Ionic 中的 AngularJS 做良好的溝通，比較方便我們這次的教學。&lt;br /&gt;
&lt;br /&gt;
Firebase 提供的登入方式基本上有一種簡是易登入，就是用帳號密碼，和另一種是社群登入，像是用 Facebook 的方式來登入， 由於 Firebase 官網已經有一篇 &lt;a href=&quot;https://www.firebase.com/docs/web/libraries/ionic/guide.html&quot;&gt;Ionic&amp;nbsp;+ Facebook 登入的教學&lt;/a&gt;，所以這個部分就留給同學朋友們自己練習，我們來實作一個簡易的帳號密碼登入。&lt;br /&gt;
&lt;br /&gt;
首先，我們要安裝 bower ，這是目前 Ionic 還在用的 web package management tool 。&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint lang-shell&quot;&gt;$ npm install -g bower
&lt;/pre&gt;
&lt;br /&gt;
然後安裝 firebase &amp;amp; angular-fire&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint lang-shell&quot;&gt;$ bower install firebase angularfire
&lt;/pre&gt;
&lt;br /&gt;
我們要在 www/index.html 中引用 app.js 之前的地方引入這個兩個 javascript library&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint lang-html&quot;&gt;...
&amp;lt;!-- Firebase --&amp;gt;
&amp;lt;script src=&quot;lib/firebase/firebase.js&quot;&amp;gt;&amp;lt;/script&amp;gt;

&amp;lt;!-- OR Firebase from CDN --&amp;gt;
&amp;lt;script src=&quot;https://cdn.firebase.com/js/client/2.3.0/firebase.js&quot;&amp;gt;&amp;lt;/script&amp;gt;

&amp;lt;!-- AngularFire --&amp;gt;
&amp;lt;script src=&quot;lib/angularfire/dist/angularfire.min.js&quot;&amp;gt;&amp;lt;/script&amp;gt;

&amp;lt;!-- OR AngularFire from CDN --&amp;gt;
&amp;lt;script src=&quot;https://cdn.firebase.com/libs/angularfire/1.1.3/angularfire.min.js&quot;&amp;gt;&amp;lt;/script&amp;gt;

&amp;lt;!-- your app&#39;s js --&amp;gt;
&amp;lt;script src=&quot;js/app.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
...
&lt;/pre&gt;
&lt;br /&gt;
接下來到 &lt;a href=&quot;https://www.firebase.com/&quot;&gt;Firebase&lt;/a&gt; 申請一個帳號，並且建立一個 Project，下面會用到你的 Project ID。&lt;br /&gt;
申請完之後請到進入你的 Project 管理頁面的 Login &amp;amp; Auth ，並且勾選 Enable Email &amp;amp; Password Authentication 。&lt;br /&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi9itL35e9uhLAoveuy0J72Agbls_9mhTtRwXrd7ya0o7npLfnQXAVXVjVYTyaWFXchNo4hLZM-7UmSOU3GXKhsfMpWMqA7UiJnrqKis7vrcXCQGto1iOIqNHIALhMfKI24EY2KS11YK7VM/s1600/firebase-auth.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;203&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi9itL35e9uhLAoveuy0J72Agbls_9mhTtRwXrd7ya0o7npLfnQXAVXVjVYTyaWFXchNo4hLZM-7UmSOU3GXKhsfMpWMqA7UiJnrqKis7vrcXCQGto1iOIqNHIALhMfKI24EY2KS11YK7VM/s640/firebase-auth.png&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;br /&gt;
然後我們新增一個 www/js/services.js&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint lang-js&quot;&gt;var firebaseUrl = &quot;https://your-project-id.firebaseio.com/&quot;;
angular.module(&#39;starter.services&#39;, [])
.factory(&quot;FirebaseRef&quot;, function($firebaseAuth) {
  return new Firebase(firebaseUrl);
})
.factory(&quot;Auth&quot;, function($firebaseAuth) {
  var usersRef = new Firebase(firebaseUrl+&quot;users&quot;);
  return $firebaseAuth(usersRef);
});
&lt;/pre&gt;
your-project-id 請使用你申請的 Project ID &lt;br /&gt;
這裡我們定義了 FirebaseRef 和 Auth 兩個工廠，他們分別指向 Firebase 的資料庫和使用者工具，等下會用到。&lt;br /&gt;
回到 www/index.html 引用 services.js &lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint lang-html&quot;&gt;...
&amp;lt;script src=&quot;js/app.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script src=&quot;js/services.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
...
&lt;/pre&gt;
&lt;br /&gt;
然後修改 app.js 中 starter app 的依賴關係，增加 firebase 和 starter.services &lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint lang-js&quot;&gt;angular.module(&#39;starter&#39;, [&#39;ionic&#39;, &#39;firebase&#39;, &#39;starter.services&#39;, &#39;starter.controllers&#39;])
&lt;/pre&gt;
&lt;br /&gt;
接下來我們做一個註冊頁面 www/templates/signup.html&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint lang-html&quot;&gt;&amp;lt;ion-view view-title=&quot;註冊&quot;&amp;gt;
  &amp;lt;ion-content class=&quot;padding&quot;&amp;gt;
    &amp;lt;div class=&quot;list&quot;&amp;gt;
      &amp;lt;label class=&quot;item item-input&quot;&amp;gt;
        &amp;lt;span class=&quot;input-label&quot;&amp;gt;Email&amp;lt;/span&amp;gt;
        &amp;lt;input ng-model=&quot;signupData.email&quot; type=&quot;text&quot;&amp;gt;
      &amp;lt;/label&amp;gt;
      &amp;lt;label class=&quot;item item-input&quot;&amp;gt;
        &amp;lt;span class=&quot;input-label&quot;&amp;gt;密碼&amp;lt;/span&amp;gt;
        &amp;lt;input ng-model=&quot;signupData.password&quot; type=&quot;password&quot;&amp;gt;
      &amp;lt;/label&amp;gt;
      &amp;lt;label class=&quot;item item-input&quot;&amp;gt;
        &amp;lt;span class=&quot;input-label&quot;&amp;gt;顯示名稱&amp;lt;/span&amp;gt;
        &amp;lt;input ng-model=&quot;signupData.displayName&quot; type=&quot;text&quot;&amp;gt;
      &amp;lt;/label&amp;gt;
      &amp;lt;button class=&quot;button button-full button-positive&quot; ng-click=&quot;createUser()&quot;&amp;gt;建立&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/ion-content&amp;gt;
&amp;lt;/ion-view&amp;gt;
&lt;/pre&gt;
&lt;br /&gt;
這裡看到我們使用了 signupData 和 createUser ，接下來我們來定義這兩個東西。&lt;br /&gt;
我們增加一個 Controller &lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint lang-js&quot;&gt;...
.controller(&#39;SignupCtrl&#39;, function($scope, $ionicLoading, FirebaseRef, Auth) {

  $scope.signupData = {};

  $scope.createUser = function () {
    var signupData = $scope.signupData;

    if (signupData &amp;amp;&amp;amp; signupData.email &amp;amp;&amp;amp; signupData.password &amp;amp;&amp;amp; signupData.displayName) {
      $ionicLoading.show({
        template: &#39;註冊中...&#39;
      });

      Auth.$createUser({
        email: signupData.email,
        password: signupData.password
      }).then(function (userData) {
        console.log(userData);
        alert(&quot;註冊成功!&quot;);
        // add user info
        FirebaseRef.child(&quot;users&quot;).child(userData.uid).set({
          displayName: signupData.displayName
        });
        $ionicLoading.hide();
      }).catch(function (error) {
        alert(&quot;Error: &quot; + error);
        $ionicLoading.hide();
      });
    } else {
      alert(&quot;請填寫所有資料&quot;);
    }
  }
})
...
&lt;/pre&gt;
&lt;br /&gt;
接著定義路徑，修改 app.js&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint lang-js&quot;&gt;...
.state(&#39;app.signup&#39;, {
  url: &#39;/signup&#39;,
  views: {
    &#39;menuContent&#39;: {
      templateUrl: &#39;templates/signup.html&#39;,
      controller: &#39;SignupCtrl&#39;
    }
  }
})
...
&lt;/pre&gt;
&lt;br /&gt;
然後加入 menu.html&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint lang-html&quot;&gt;...
&amp;lt;ion-item menu-close href=&quot;#/app/signup&quot;&amp;gt;
  Signup
&amp;lt;/ion-item&amp;gt;
...
&lt;/pre&gt;
&lt;br /&gt;
完成了，然後就可以測試看看註冊功能了，在 Firebase console 也可以看到註冊的使用者資料。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;</content><link rel='replies' type='application/atom+xml' href='http://sushiwens.blogspot.com/feeds/5898307264725478797/comments/default' title='張貼留言'/><link rel='replies' type='text/html' href='http://sushiwens.blogspot.com/2015/11/ionic-framework-6.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3638477104687038182/posts/default/5898307264725478797'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3638477104687038182/posts/default/5898307264725478797'/><link rel='alternate' type='text/html' href='http://sushiwens.blogspot.com/2015/11/ionic-framework-6.html' title='Ionic Framework 教學 - 6. 簡易註冊'/><author><name>Unknown</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi9itL35e9uhLAoveuy0J72Agbls_9mhTtRwXrd7ya0o7npLfnQXAVXVjVYTyaWFXchNo4hLZM-7UmSOU3GXKhsfMpWMqA7UiJnrqKis7vrcXCQGto1iOIqNHIALhMfKI24EY2KS11YK7VM/s72-c/firebase-auth.png" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3638477104687038182.post-3666569549417284594</id><published>2015-11-18T10:24:00.000+08:00</published><updated>2015-11-20T09:56:05.686+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Hybrid App"/><category scheme="http://www.blogger.com/atom/ns#" term="Ionic Framework"/><title type='text'>Ionic Framework 教學 - 5. 下載到手機上測試</title><content type='html'>承&lt;a href=&quot;http://sushiwens.blogspot.tw/2015/11/ionic-framework-4.html&quot;&gt;上一章&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
1. 在網站上註冊一個帳號 https://apps.ionic.io/signup&lt;br /&gt;
2. 在手機上下載 Ionic View App&lt;br /&gt;
3. 下指令上傳 project 
&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint lang-shell&quot;&gt;$ ionic upload 
&lt;/pre&gt;
&lt;br /&gt;
然後填入剛剛註冊的帳號，密碼在打的時候不會顯示是正常的。&lt;br /&gt;
&lt;br /&gt;
4. 上傳後進入你在手機上下載下來就可以看到了，試試看。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;a href=&quot;http://sushiwens.blogspot.com/2015/11/ionic-framework-6.html&quot;&gt;下一章 簡易註冊&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;</content><link rel='replies' type='application/atom+xml' href='http://sushiwens.blogspot.com/feeds/3666569549417284594/comments/default' title='張貼留言'/><link rel='replies' type='text/html' href='http://sushiwens.blogspot.com/2015/11/ionic-framework-5.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3638477104687038182/posts/default/3666569549417284594'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3638477104687038182/posts/default/3666569549417284594'/><link rel='alternate' type='text/html' href='http://sushiwens.blogspot.com/2015/11/ionic-framework-5.html' title='Ionic Framework 教學 - 5. 下載到手機上測試'/><author><name>Unknown</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3638477104687038182.post-1039143294521445111</id><published>2015-11-18T10:23:00.003+08:00</published><updated>2015-11-18T10:55:13.823+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Hybrid App"/><category scheme="http://www.blogger.com/atom/ns#" term="Ionic Framework"/><title type='text'>Ionic Framework 教學 - 4. 修改樣式</title><content type='html'>承&lt;a href=&quot;http://sushiwens.blogspot.tw/2015/11/ionic-framework-3.html&quot;&gt;上一章&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
我們之前都是用 Ionic 預設的樣式，但是這樣大家做出來的 App 豈不是都長得一樣？&lt;br /&gt;
我們要怎麼修改樣式呢？我們來修改剛剛做好的大師兄頁面 baseball.html 。&lt;br /&gt;
那個按鈕我們不喜歡，修改一下按鈕顏色和邊框，順便修改裡面的文字，我們把它做好做滿。&lt;br /&gt;
&lt;br /&gt;
首先我們先啟用 sass &lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint lang-shell&quot;&gt;$ ionic setup sass
&lt;/pre&gt;
&lt;br /&gt;
然後修改 &amp;nbsp;baseball.html 增加 button-full 和 homerun 類&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;...
&amp;lt;button ui-sref=&quot;app.surprise&quot;
    class=&quot;button button-positive button-full homerun icon-left ion-ios-baseball-outline&quot;&amp;gt;
    打擊
&amp;lt;/button&amp;gt;
...
&lt;/pre&gt;
&lt;br /&gt;
我們想要把這個按鈕的文字做的又粗又大&lt;br /&gt;
首先編輯 scss/ionic.app.scss&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint lang-scss&quot;&gt;...

// Include all of Ionic
@import &quot;www/lib/ionic/scss/ionic&quot;;

.homerun {
  font-size: 30px;
  font-weight: 900;
}
&lt;/pre&gt;
&lt;br /&gt;
然後我們想要把所有按鈕的邊框加的很厚，是所有的按鈕喔，不是只有畫面中這一個，還包含了以後我們建立的按鈕，所以我們直接修改 Ionic 預設的 css&lt;br /&gt;
修改 www/lib/ionic/scss/_variables.scss&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint lang-scss&quot;&gt;...
$button-border-width:             5px !default;
...
&lt;/pre&gt;
&lt;br /&gt;
lib裡面的東西理論上很少修改，所以沒有自動更新，修改完 lib 裡面的東西我們要手動重新啟動 ionic &lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint lang-shell&quot;&gt;# ctrl + c to stop ionic server
$ ionic serve
&lt;/pre&gt;
&lt;br /&gt;
重新整理頁面後就可以看到修改的東西都實現了。&lt;br /&gt;
&lt;br /&gt;
&lt;a href=&quot;http://sushiwens.blogspot.tw/2015/11/ionic-framework-5.html&quot;&gt;下一章 手機測試&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;</content><link rel='replies' type='application/atom+xml' href='http://sushiwens.blogspot.com/feeds/1039143294521445111/comments/default' title='張貼留言'/><link rel='replies' type='text/html' href='http://sushiwens.blogspot.com/2015/11/ionic-framework-4.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3638477104687038182/posts/default/1039143294521445111'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3638477104687038182/posts/default/1039143294521445111'/><link rel='alternate' type='text/html' href='http://sushiwens.blogspot.com/2015/11/ionic-framework-4.html' title='Ionic Framework 教學 - 4. 修改樣式'/><author><name>Unknown</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3638477104687038182.post-8162666311888454539</id><published>2015-11-18T10:23:00.002+08:00</published><updated>2016-05-16T05:32:06.874+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Hybrid App"/><category scheme="http://www.blogger.com/atom/ns#" term="Ionic Framework"/><title type='text'>Ionic Framework 教學 - 3. 製作一個簡單的頁面</title><content type='html'>&lt;table align=&quot;center&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; class=&quot;tr-caption-container&quot; style=&quot;margin-left: auto; margin-right: auto; text-align: center;&quot;&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;http://i.imgur.com/tSOV2iQ.jpg&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://i.imgur.com/tSOV2iQ.jpg&quot; height=&quot;418&quot; width=&quot;640&quot;&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;tr-caption&quot; style=&quot;text-align: center;&quot;&gt;http://pic.ctitv.com/&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;br /&gt;
承&lt;a href=&quot;http://sushiwens.blogspot.tw/2015/11/ionic-framework-2-app.html&quot;&gt;上一章&lt;/a&gt;&lt;br /&gt;
我們來做一個林智勝的打擊頁面。&lt;br /&gt;
首先在 www/templates/menu.html 頁面增加一個Baseball選項
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&amp;lt;ion-side-menus enable-menu-with-back-views=&quot;false&quot;&amp;gt;
    &amp;lt;ion-side-menu-content&amp;gt;
      &amp;lt;ion-nav-bar class=&quot;bar-stable&quot;&amp;gt;
        &amp;lt;ion-nav-back-button&amp;gt;
        &amp;lt;/ion-nav-back-button&amp;gt;

        &amp;lt;ion-nav-buttons side=&quot;left&quot;&amp;gt;
          &amp;lt;button class=&quot;button button-icon button-clear ion-navicon&quot; menu-toggle=&quot;left&quot;&amp;gt;
          &amp;lt;/button&amp;gt;
        &amp;lt;/ion-nav-buttons&amp;gt;
      &amp;lt;/ion-nav-bar&amp;gt;
      &amp;lt;ion-nav-view name=&quot;menuContent&quot;&amp;gt;&amp;lt;/ion-nav-view&amp;gt;
    &amp;lt;/ion-side-menu-content&amp;gt;

    &amp;lt;ion-side-menu side=&quot;left&quot;&amp;gt;
      &amp;lt;ion-header-bar class=&quot;bar-stable&quot;&amp;gt;
        &amp;lt;h1 class=&quot;title&quot;&amp;gt;Left&amp;lt;/h1&amp;gt;
      &amp;lt;/ion-header-bar&amp;gt;
      &amp;lt;ion-content&amp;gt;
        &amp;lt;ion-list&amp;gt;
          &amp;lt;ion-item menu-close ng-click=&quot;login()&quot;&amp;gt;
            Login
          &amp;lt;/ion-item&amp;gt;
          &amp;lt;ion-item menu-close href=&quot;#/app/search&quot;&amp;gt;
            Search
          &amp;lt;/ion-item&amp;gt;
          &amp;lt;ion-item menu-close href=&quot;#/app/browse&quot;&amp;gt;
            Browse
          &amp;lt;/ion-item&amp;gt;
          &amp;lt;ion-item menu-close href=&quot;#/app/playlists&quot;&amp;gt;
            Playlists
          &amp;lt;/ion-item&amp;gt;
          &amp;lt;ion-item menu-close href=&quot;#/app/baseball&quot;&amp;gt;
            Baseball
          &amp;lt;/ion-item&amp;gt;
        &amp;lt;/ion-list&amp;gt;
      &amp;lt;/ion-content&amp;gt;
    &amp;lt;/ion-side-menu&amp;gt;
  &amp;lt;/ion-side-menus&amp;gt;
&lt;/pre&gt;
&lt;br /&gt;
這個時候 Baseball 點下去是沒有用的，我們要增加對應的頁面。&lt;br /&gt;
首先在 www/templates 資料夾中增加一個 baseball.html ，這個我們等下會用到。&lt;br /&gt;
然後編輯 www/js/controllers.js ，增加一個 Controller &lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;...

.controller(&#39;PlaylistCtrl&#39;, function($scope, $stateParams) {
})
.controller(&#39;BaseballCtrl&#39;, function() {
})
;
&lt;/pre&gt;
&lt;br /&gt;
注意 &quot;...&quot; 是代表省略的意思，不是真的程式碼，因為我盡量只寫有變動的部份，有些同學直接照抄是不行的。&lt;br /&gt;
分號要放在最後面，這個 BaseballCtrl 是用來處理有關 Baseball 的事件處理的，目前內容是空白，我們等下會用到。&lt;br /&gt;
然後編輯 www/js/app.js ，在 routing 路線圖裡面，增加一組 route ，他是告訴 App 遇到什麼狀態 state ，或是什麼 url 的情況，要交給哪個 Controller 來處理，並且指定顯示什麼 template 頁面。&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;...

.config(function($stateProvider, $urlRouterProvider) {
  $stateProvider

  ...

  .state(&#39;app.single&#39;, {
    url: &#39;/playlists/:playlistId&#39;,
    views: {
      &#39;menuContent&#39;: {
        templateUrl: &#39;templates/playlist.html&#39;,
        controller: &#39;PlaylistCtrl&#39;
      }
    }
  })
  .state(&#39;app.baseball&#39;, {
    url: &#39;/baseball&#39;,
    views: {
      &#39;menuContent&#39;: {
        templateUrl: &#39;templates/baseball.html&#39;,
        controller: &#39;BaseballCtrl&#39;
      }
    }
  })
  ;
  // if none of the above states are matched, use this as the fallback
  $urlRouterProvider.otherwise(&#39;/app/playlists&#39;);
});
&lt;/pre&gt;
&lt;br /&gt;
到這裡我們再試試看點擊 Baseball 頁籤，就會出現空白頁面，而且 header 的 title 是錯誤的，我們來修改他。&lt;br /&gt;
編輯 www/templates/baseball.html &lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;  
&amp;lt;ion-view view-title=&quot;請大師兄翻譯翻譯&quot;&amp;gt;
  &amp;lt;ion-content&amp;gt;
    &amp;lt;img alt=&quot;&quot; class=&quot;full-image&quot; src=&quot;http://i.imgur.com/fnU7FHf.jpg?1&quot; /&amp;gt;
    &amp;lt;button class=&quot;button button-positive icon-left ion-ios-baseball-outline&quot;&amp;gt;
      打擊
    &amp;lt;/button&amp;gt;
  &amp;lt;/ion-content&amp;gt;
&amp;lt;/ion-view&amp;gt;
&lt;/pre&gt;
&lt;br /&gt;
我們修改了 header title, 增加了一張大師兄曼妙的身影，和一個打擊按鈕。 &lt;br /&gt;
打擊下去會發生什麼事呢？ &lt;br /&gt;
我們來增加下一個 View &lt;br /&gt;
首先增加 www/templates/surprise.html &lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&amp;lt;ion-view view-title=&quot;什麼叫驚喜&quot;&amp;gt;
  &amp;lt;ion-content&amp;gt;
    &amp;lt;img class=&quot;full-image&quot; src=&quot;http://i.imgur.com/JpsC5xC.jpg?1&quot; alt=&quot;&quot;&amp;gt;
  &amp;lt;/ion-content&amp;gt;
&amp;lt;/ion-view&amp;gt;
&lt;/pre&gt;
&lt;br /&gt;
同樣的這個頁面也需要一個 Controller 和 route ，我們分別在 controllers.js 和 app.js 加上 &lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;  ...
  
  .controller(&#39;SurpriseCtrl&#39;, function() {
  })
  ;
&lt;/pre&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;  ...

  .state(&#39;app.surprise&#39;, {
    url: &#39;/surprise&#39;,
    views: {
      &#39;menuContent&#39;: {
        templateUrl: &#39;templates/surprise.html&#39;,
        controller: &#39;SurpriseCtrl&#39;
      }
    }
  })
  ;
&lt;/pre&gt;
&lt;br /&gt;
這樣完成之後，我們要怎麼讓打擊按鈕連到這個頁面呢？ &lt;br /&gt;
第一種方法，我們剛剛已經知道了，在 menu.html 裡面，用 href 的方式連到對應的 url 。&lt;br /&gt;
我們來試試看第二種方式，用 ui-sref 連到對應的 state 。&lt;br /&gt;
我們修改 baseball.html &lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&amp;lt;ion-view view-title=&quot;請大師兄翻譯翻譯&quot;&amp;gt;
  &amp;lt;ion-content&amp;gt;
    &amp;lt;img class=&quot;full-image&quot; src=&quot;http://i.imgur.com/fnU7FHf.jpg?1&quot; alt=&quot;&quot;&amp;gt;
    &amp;lt;button ui-sref=&quot;app.surprise&quot;
      class=&quot;button button-positive icon-left ion-ios-baseball-outline&quot;&amp;gt;
      打擊
    &amp;lt;/button&amp;gt;
  &amp;lt;/ion-content&amp;gt;
&amp;lt;/ion-view&amp;gt;
&lt;/pre&gt;
&lt;br /&gt;
現在試試看打擊按鈕了。
&lt;br /&gt;
除了用 ur-sref 我們也可以在 Controller 中控制轉場，我們修改打擊按鈕&lt;br /&gt;
&lt;pre class=&quot;prettyprint lang-html&quot;&gt;&amp;lt;button ng-click=&quot;hitTheBall()&quot;
  class=&quot;homerun button button-positive button-full icon-left ion-ios-baseball-outline&quot;&amp;gt;
  打擊
&amp;lt;/button&amp;gt;
&lt;/pre&gt;
讓他被點擊的時候呼叫 hitTheBall ，然後我們修改 Controller&lt;br /&gt;
&lt;pre class=&quot;prettyprint lang-js&quot;&gt;.controller(&#39;BaseballCtrl&#39;, function($scope, $state) {
  $scope.hitTheBall = function() {
    $state.go(&#39;app.surprise&#39;);
  }
})
&lt;/pre&gt;
&lt;br /&gt;
$scope 就是連接 View 中使用的變數，$state 就是用來使用 routing 的 &lt;br /&gt;
可是這樣有什麼好處呢？ 有時候按鈕按下去有很多事情需要處理，處理完才能到下一個 state ，假設我們加一個 loading 的畫面 &lt;br /&gt;
增加 templates/loading.html &lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint lang-html&quot;&gt;&amp;lt;img class=&quot;full-image&quot; src=&quot;http://i.imgur.com/AkbN5f0.jpg?1&quot; alt=&quot;&quot;&amp;gt;
&lt;/pre&gt;
&lt;br /&gt;
然後我們修改 BaseballCtrl &lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;prettyprint lang-js&quot;&gt;.controller(&#39;BaseballCtrl&#39;, function($scope, $state, $ionicLoading, $timeout) {
  $scope.hitTheBall = function() {
    $ionicLoading.show({
      templateUrl: &#39;templates/loading.html&#39;
    });

    //wait for 2 seconds
    $timeout(function() {
      $state.go(&#39;app.surprise&#39;);
      $ionicLoading.hide();
    }, 2000);
    
  };
})
&lt;/pre&gt;
&lt;br /&gt;
這樣你就大概知道一個頁面要怎麼做了，更多 Ionic 預設的元件你可以參考&lt;br /&gt;
http://ionicframework.com/docs/components/&lt;br /&gt;
現在發揮你的創意，換你來練習一下，比如說古巴教練說了一句話，然後發生了什麼事？&lt;br /&gt;
熟悉靜態頁面對於後面的課程很重要！&lt;br /&gt;
&lt;br /&gt;
&lt;a href=&quot;http://sushiwens.blogspot.tw/2015/11/ionic-framework-4.html&quot;&gt;下一章 樣式修改&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;</content><link rel='replies' type='application/atom+xml' href='http://sushiwens.blogspot.com/feeds/8162666311888454539/comments/default' title='張貼留言'/><link rel='replies' type='text/html' href='http://sushiwens.blogspot.com/2015/11/ionic-framework-3.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3638477104687038182/posts/default/8162666311888454539'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3638477104687038182/posts/default/8162666311888454539'/><link rel='alternate' type='text/html' href='http://sushiwens.blogspot.com/2015/11/ionic-framework-3.html' title='Ionic Framework 教學 - 3. 製作一個簡單的頁面'/><author><name>Unknown</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3638477104687038182.post-1110801572275143630</id><published>2015-11-18T09:44:00.000+08:00</published><updated>2015-11-18T10:56:30.669+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Hybrid App"/><category scheme="http://www.blogger.com/atom/ns#" term="Ionic Framework"/><title type='text'>Ionic Framework 教學 - 2. 建立一個測試App</title><content type='html'>承&lt;a href=&quot;http://sushiwens.blogspot.com/2015/11/ionic-framework-hybrid-app-1.html&quot;&gt;上一章&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
接著我們來試著建立一個測試 &amp;nbsp;App&lt;br /&gt;
&lt;pre class=&quot;prettyprint lang-shell&quot;&gt;  ## create mysidemenu based on sidemenu project

  $ ionic start mysidemenu sidemenu

  ## go to mysidemenu folder

  $ cd mysidemenu

  ## list files

  $ ls  # on mac
  $ dir  # on windows

  ## open mysidemenu folder, take a look at the folder structure

  $ open .  # on mac
  $ explorer .  # on windows

  ## start running my app
  ## run this command in mysidemenu folder

  $ ionic serve

  ## (and choose &#39;localhost&#39;)

  ## Then, navigate your browser to http://localhost:8100
  ## At this moment, you may want to install Chrome if you don&#39;t have one.
  ## https://www.google.com/chrome/browser/desktop/index.html


&lt;/pre&gt;
這樣，我們就從 sidemenu 這個樣板建立了一個 mysidemenu 的 App，接著你就可以打開瀏覽器來測試看看，在 Chrome 裡面，你可以使用 Command + Option + i 來開啟開發人員工具（Windows 則使用 F12），在 Network 頁籤你可以看到載入訊息，在 Console 頁籤裡你可以看到錯誤訊息（如果有的話），並且你可以用 Command + Shift + m 來模擬手機螢幕，在這裡你可能會需要重新整理頁面來套用適當的版面。



&lt;br /&gt;
&lt;br /&gt;
如果你還沒有一個很好的程式編輯器，我們可以推薦幾個適合入門又強大的編輯器給你&lt;br /&gt;
&lt;br /&gt;
1. Sublime Text&lt;br /&gt;
http://www.sublimetext.com/2&lt;br /&gt;
2. Atom&lt;br /&gt;
https://atom.io/&lt;br /&gt;
3. Visual Code&lt;br /&gt;
https://www.visualstudio.com/zh-tw/products/code-vs.aspx&lt;br /&gt;
&lt;br /&gt;
前往&lt;a href=&quot;http://sushiwens.blogspot.tw/2015/11/ionic-framework-3.html&quot;&gt;下一章- 製作一個靜態頁面&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;</content><link rel='replies' type='application/atom+xml' href='http://sushiwens.blogspot.com/feeds/1110801572275143630/comments/default' title='張貼留言'/><link rel='replies' type='text/html' href='http://sushiwens.blogspot.com/2015/11/ionic-framework-2-app.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3638477104687038182/posts/default/1110801572275143630'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3638477104687038182/posts/default/1110801572275143630'/><link rel='alternate' type='text/html' href='http://sushiwens.blogspot.com/2015/11/ionic-framework-2-app.html' title='Ionic Framework 教學 - 2. 建立一個測試App'/><author><name>Unknown</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3638477104687038182.post-3805883705705365509</id><published>2015-11-15T07:58:00.000+08:00</published><updated>2015-11-28T21:12:57.248+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Hybrid App"/><category scheme="http://www.blogger.com/atom/ns#" term="Ionic Framework"/><category scheme="http://www.blogger.com/atom/ns#" term="NodeJS"/><category scheme="http://www.blogger.com/atom/ns#" term="npm"/><category scheme="http://www.blogger.com/atom/ns#" term="Tutorial"/><title type='text'>Ionic Framework 製作 Hybrid App - 1.準備環境</title><content type='html'>&lt;table align=&quot;center&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; class=&quot;tr-caption-container&quot; style=&quot;margin-left: auto; margin-right: auto; text-align: center;&quot;&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;http://ionicframework.com/img/blog/angularjs-ionic-framework.jpg&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;http://ionicframework.com/img/blog/angularjs-ionic-framework.jpg&quot; height=&quot;320&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;tr-caption&quot; style=&quot;text-align: center;&quot;&gt;blog.ionic.io&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;br /&gt;
因為一些大學課程需要的關係製作了這一系列的教學，在你深入的看這個教學之前，我們希望你可以先上網了解 Native App, Web App, Hybrid App 等等開發方法的優點和缺點，選擇最適合你的 App 開發方式。&lt;br /&gt;
&lt;br /&gt;
你至少需要具備一些基礎的 &amp;nbsp;HTML 和 Javascript 的知識來跟著我們實作這一系列的教學。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;h4&gt;
準備環境&lt;/h4&gt;
&lt;br /&gt;
我們需要 Node.js, npm, Cordova, and Ionic&lt;br /&gt;
&lt;br /&gt;
&lt;h4&gt;
什麼是 Node.js 和 npm?&lt;/h4&gt;
&lt;br /&gt;
Node.js® is a JavaScript runtime built on Chrome&#39;s V8 JavaScript engine. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient. Node.js&#39; package ecosystem, npm, is the largest ecosystem of open source libraries in the world.&lt;br /&gt;
&lt;br /&gt;
more: https://nodejs.org/en/&lt;br /&gt;
&lt;br /&gt;
&lt;h4&gt;
什麼是 Cordova？&lt;/h4&gt;
&lt;br /&gt;
Mobile apps with HTML, CSS &amp;amp; JS. Target multiple platforms with one code base. Free and open source.&lt;br /&gt;
&lt;br /&gt;
more: https://cordova.apache.org/&lt;br /&gt;
&lt;br /&gt;
&lt;table align=&quot;center&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; class=&quot;tr-caption-container&quot; style=&quot;margin-left: auto; margin-right: auto; text-align: center;&quot;&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;https://blog.codecentric.de/files/2014/11/overview.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;425&quot; src=&quot;https://blog.codecentric.de/files/2014/11/overview.png&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;tr-caption&quot; style=&quot;text-align: center;&quot;&gt;https://blog.codecentric.de/en/2014/11/ionic-angularjs-framework-on-the-rise/&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;br /&gt;
&lt;br /&gt;
什麼是 Ionic Framework?&lt;br /&gt;
&lt;br /&gt;
Create mobile apps with the web technologies you love. Free and open source, Ionic offers a library of mobile-optimized HTML, CSS and JS components, gestures, and tools for building highly interactive apps. Built with Sass and optimized for AngularJS.&lt;br /&gt;
&lt;br /&gt;
more:&amp;nbsp;http://ionicframework.com/
&lt;br /&gt;
&lt;br /&gt;
&lt;script src=&quot;https://gist.github.com/flyakite/88979c4a8f6b07bfde11.js&quot;&gt;&lt;/script&gt;

&lt;br /&gt;
前往&lt;a href=&quot;http://sushiwens.blogspot.tw/2015/11/ionic-framework-2-app.html&quot;&gt;第二章 建立一個測試 App&lt;/a&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;</content><link rel='replies' type='application/atom+xml' href='http://sushiwens.blogspot.com/feeds/3805883705705365509/comments/default' title='張貼留言'/><link rel='replies' type='text/html' href='http://sushiwens.blogspot.com/2015/11/ionic-framework-hybrid-app-1.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3638477104687038182/posts/default/3805883705705365509'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3638477104687038182/posts/default/3805883705705365509'/><link rel='alternate' type='text/html' href='http://sushiwens.blogspot.com/2015/11/ionic-framework-hybrid-app-1.html' title='Ionic Framework 製作 Hybrid App - 1.準備環境'/><author><name>Unknown</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3638477104687038182.post-7123390967547814879</id><published>2015-06-01T21:58:00.001+08:00</published><updated>2015-12-14T00:25:21.944+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="docker"/><title type='text'>docekr 基本指令</title><content type='html'>&lt;br /&gt;
&lt;code&gt;boot2docker init|start|stop|upgrade&lt;/code&gt;
初始｜啟動｜停止｜更新 boot2docker 服務
&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;docker search redis&lt;/code&gt;
搜尋 redis 相關映像
&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;docker pull redis&lt;/code&gt;
下載 redis 映像
&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;docker ps&lt;/code&gt;
列出容器
&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;docker ps -l&lt;/code&gt;
列出最後一個執行的容器 (l stands for last)
&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;docker run -d --name web centos ping&lt;/code&gt;
從 centos 映像跑一個名為 web 的容器，在背景執行
&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;docker run -p 8000:5000 --name web centos python app.py&lt;/code&gt;
跑一個容器，映射一個主機的 2000 port 到容器的 5000 port
&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;docker run -P --name web centos python app.py&lt;/code&gt;
跑一個容器，隨機映射一個主機的 49000~49900 port 到容器的 5000/tcp port
&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;docker run -it centos bash&lt;/code&gt;
跑一個容器並且互動
&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;docker exec -it web bash&lt;/code&gt;
進入方才建立的 web 容器互動
&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;docker run -v /host/src/app:/container/src/app centos node app.js&lt;/code&gt;
跑一個容器並且對應主機的資料夾
&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;docker run --rm ubuntu cname -a&lt;/code&gt;
跑一個容器執行完就刪掉
&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;docker commit -m &quot;comment&quot; web my/web-image&lt;/code&gt;
提交一個映像
&lt;br /&gt;
&lt;br /&gt;
&lt;code&gt;docker rm $(docker ps -a -q)&lt;/code&gt;
刪掉所有開啟的容器
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
順便推薦入門 gitbook&amp;nbsp;&lt;a href=&quot;http://philipzheng.gitbooks.io/docker_practice/content/&quot;&gt;http://philipzheng.gitbooks.io/docker_practice/content/&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;</content><link rel='replies' type='application/atom+xml' href='http://sushiwens.blogspot.com/feeds/7123390967547814879/comments/default' title='張貼留言'/><link rel='replies' type='text/html' href='http://sushiwens.blogspot.com/2015/06/docekr.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3638477104687038182/posts/default/7123390967547814879'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3638477104687038182/posts/default/7123390967547814879'/><link rel='alternate' type='text/html' href='http://sushiwens.blogspot.com/2015/06/docekr.html' title='docekr 基本指令'/><author><name>Unknown</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3638477104687038182.post-3568106476770411728</id><published>2015-04-25T13:52:00.000+08:00</published><updated>2015-05-07T22:15:37.421+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Convertible Note"/><category scheme="http://www.blogger.com/atom/ns#" term="Investment"/><category scheme="http://www.blogger.com/atom/ns#" term="Startup"/><category scheme="http://www.blogger.com/atom/ns#" term="zenblip"/><title type='text'>Convertible Note for Startup （新創公司可轉換債）</title><content type='html'>雖然 Convertible Note（Bond） 在台灣只有上市上櫃公司可以發行，但是我們還是來了解一下在國外新創公司如何使用它來融資。&lt;br /&gt;
&lt;br /&gt;
相對於在公司早期階段，讓創辦人和天使投資人憑著想像力達成一個公司估值的共識來說，讓我們把公司估值交給下一輪投資（比如A輪投資）來決定吧，在下一輪投資的時候再依照估值把這些資金轉換成公司股票，只要保證早期投資人能拿到比下一輪投資人更好的 deal 就好了，不是很棒的想法嗎。&lt;br /&gt;
&lt;br /&gt;
如何能夠保證使用 Convertible Note 的投資人比後進投資人拿到好的 deal 呢？其中一個方法是購買股份時候給的 discount ，這是一個類似早鳥票的概念，早買的風險高，所以附帶一個 discount ，假設A輪一股20元，給早期投資人打八折，變成只要16元就買到一股了。&lt;br /&gt;
&lt;br /&gt;
所以投資人拿出資金投資，當下會買到多少股份不知道，要等到下一輪投資確定公司估值之後才知道，如果下一輪公司估值很低，早期投資人就會拿到很多股份，反之很少。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Convertible Note 原本大概是在這種想法下引入 Startup 的，這背後隱含幾個意義。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
對創辦人來說：&lt;br /&gt;
&lt;br /&gt;
&lt;ol&gt;
&lt;li&gt;你不用現在就說服投資人你的公司多有價值，他多沒眼光，用你之後的努力和市場反應來決勝負吧。&lt;/li&gt;
&lt;li&gt;你一定要很努力，不然早期投資人的資金會轉換成一份很龐大的股權，其他投資人可能會望而卻步。&lt;/li&gt;
&lt;li&gt;Convertible Note 的投資人在下一輪投資之前對你來說不是股東，當然也不是董事，而是債權人，你暫時還不用聽他的，不錯吧，當然你也不能反著刷投資人的毛，因為他很快就會變成你的股東，到時候你會知道誰才是格雷。&lt;/li&gt;
&lt;li&gt;最重要的一點，快速，你最重要的就是你的產品和市場，你根本沒有時間搞其他事情，相比一般種子投資動輒幾十幾百頁的文件和請律師會計師的費用，Convertible Note 可能只要兩三頁就搞定了，因為他是債權關係，還不是股權關係。&lt;/li&gt;
&lt;li&gt;如果不是用債務關係模式，而且下一輪投資買的是種子投資裡的舊股份，而不是純粹發行的新股份，你可能馬上面臨股票交易的稅務問題。&lt;/li&gt;
&lt;/ol&gt;
&lt;br /&gt;
&lt;br /&gt;
對投資人來說：&lt;br /&gt;
&lt;br /&gt;
&lt;ol&gt;
&lt;li&gt;不用現在就說服創辦人你的 idea 和價值有多低，下一輪見真章。&lt;/li&gt;
&lt;li&gt;希望創辦人能夠因此更努力，雖然希望花同樣的資金可以買到比較多的股份，但是買到一小部分的獨角獸還是遠比買到整隻雞好吧。&lt;/li&gt;
&lt;li&gt;在公司清算的時候債權人是優先於其他股東的。&lt;/li&gt;
&lt;li&gt;一樣的優點，快速。&lt;/li&gt;
&lt;li&gt;Convertible Note 是債務，所以一樣是有利息的，但是不是很重要，沒有人投資新創公司是為了賺利息的。&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
對於大部分投資人來說，他們會希望能夠跟其他投資人炫耀說，『你知道嗎，關於某某千里馬公司，我八百年前就伯樂了。』 但是如果這個千里馬在A輪就現形，而且投資人使用的是上述的 Convertible Note 你沒有辦法這麼說，因為你只會買到的千里馬公司很小一部分股份。&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
對此投資人發明了一個概念，叫做 Cap ，就是我最少最少也會保證買到多少股份，如果公司用我的資金一日千里，那就算公司估值超過某個『 Cap 價值』，我們也用那個先講好的那個 Cap 價值來計算我能買到的股份，除了少數像 Ron Conway 這種超級天使，YC 這種超級孵化器，一般投資人幾乎都要求要有 Cap。投資人真的都很&lt;strike&gt;狡猾&lt;/strike&gt;聰明吧。（這個 Cap 通常使用 pre-money 來表示。）&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
這個 Cap 要如何設定呢，這就非常有趣了，因為通常就是根據你的估值的某個倍數來設定，再說一次，Cap 通常是根據你公司現有估值的某個倍數來設定的，聰明的讀者看到這裡有突破盲腸了嗎？ Convertible Note 加上了 Cap 的設計根本沒有解決一開始我們最想解決的創辦人和投資人要達成公司估值共識非常困難的問題。&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
不過也有投資人不會這樣想，與其錯過投資時機，他們會把 Cap 當做一種保護策略，只要談好 Discount ，Caluation Cap 的存在是避免損失太大。&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
另外要注意的就是很少見的 Early Exit ，這是指下一輪投資還沒發生，公司就被買走了，通常這種情況要給早期投資人更好的回報，在文件上要詳細寫明。&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
總結來說，我覺得光就 Convertible Note 的快速特性，就非常值得使用了，因為對新創公司來說最重要的資源就是時間。&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;</content><link rel='replies' type='application/atom+xml' href='http://sushiwens.blogspot.com/feeds/3568106476770411728/comments/default' title='張貼留言'/><link rel='replies' type='text/html' href='http://sushiwens.blogspot.com/2015/04/convertible-note-for-startup.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3638477104687038182/posts/default/3568106476770411728'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3638477104687038182/posts/default/3568106476770411728'/><link rel='alternate' type='text/html' href='http://sushiwens.blogspot.com/2015/04/convertible-note-for-startup.html' title='Convertible Note for Startup （新創公司可轉換債）'/><author><name>Unknown</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3638477104687038182.post-34371022452984878</id><published>2015-03-28T18:04:00.001+08:00</published><updated>2015-03-29T00:10:37.364+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term=".Net Framework"/><category scheme="http://www.blogger.com/atom/ns#" term="async"/><category scheme="http://www.blogger.com/atom/ns#" term="await"/><category scheme="http://www.blogger.com/atom/ns#" term="C#"/><category scheme="http://www.blogger.com/atom/ns#" term="thread"/><title type='text'>C# async 和 await 控制基本教學</title><content type='html'>.net 4.5 中 async 和 await 的語法已經有很大的進步，用以下範例說明&lt;br /&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://i-msdn.sec.s-msft.com/dynimg/IC612215.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;524&quot; src=&quot;https://i-msdn.sec.s-msft.com/dynimg/IC612215.png&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
1. 一個UI event handler 呼叫並且等待 AccessTheWebAsync 方法&lt;br /&gt;
2. AccessTheWebAsync 建立 HttpClient 實例 client，呼叫 client 的 GetStringAsync 方法，並立即回傳一個 Task&lt;tresult&gt; ，其中 TResult 在這裡是一個 string 。&lt;/tresult&gt;&lt;br /&gt;
3. GetStringAsync 可能要等一個網站回應並且下載內容，但是因為 GetStringAsync 立即回傳一個 Task 並且把控制權交還給 GetStringAsync 的呼叫者，就是 AccessTheWebAsync 。&lt;br /&gt;
4. AccessTheWebAsync 繼續同步執行 DoIndependentWork 。&lt;br /&gt;
5. DoIndependentWork 執行完後控制權交還給 AccessTheWebAsync。&lt;br /&gt;
6. AccessTheWebAsync 想要接著做下面的工作，必須取得 GetStringAsync 的 Task 的結果，所以這裡使用 await 來等待結果，這樣執行權會交還給 AccessTheWebAsync 的呼叫者，就是一開始的 UI event handler，這個時候會把 AccessTheWebAsync 的狀態儲存起來以便之後使用。（這裡我的理解是，只要在一個 code block 碰到 await 就會等待，即使有不需要結果也能執行的程序，也不會先執行，都會等）&lt;br /&gt;
7. 當 GetStringAsync 完成了工作， await 會從 getStringTask 裡面取得 Task 的結果，並傳給 urlContents 。&lt;br /&gt;
8. 回傳結果，並讓處於等待狀態的 UI event handler 繼續執行。&lt;br /&gt;
&lt;br /&gt;
&lt;h4&gt;
理解要點：&lt;/h4&gt;
1. await 不會阻止該 async 的 thread 的執行，await 是把剩下的程式變成後續程式（continuation）並且把控制權交給 async 的 caller 。&lt;br /&gt;
2. async 和 await 不會產生新的 thread ，async 方法不需要 multithreading ，因為 async 方法不是跑在他自創的 thread ，而是跑在當前同步的情境中（ context ），只有在一個方法是執行狀態的時候才會花 CPU 時間。&lt;br /&gt;
3. 這種實作方法相對於 BackgroundWorker 的好處是語法簡單而且不用擔心 race condition。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Good video&lt;br /&gt;
http://channel9.msdn.com/Events/TechEd/NorthAmerica/2013/DEV-B319#fbid=&lt;br /&gt;
&lt;br /&gt;
小重點：&lt;br /&gt;
async 可以搭配 void, Task, Task&lt;tresult&gt; 來使用，如果方法沒有回傳值應該使用 async Task 而不是 async void ，async void 一般使用在最頂層的 event handler ，async void 相當於射後不理，讓其他的 UI 介面可以繼續回應使用者，其他情況應該避免使用 async void ，以免造成結果沒有產生就回傳了，或是 race condition等情況。&lt;/tresult&gt;&lt;br /&gt;
使用 async void 的時候，例外狀況要在呼叫的方法的內部處理，因為外部立即返回所以會離開外部try...catch的範圍。&lt;br /&gt;
另外 async 方法應該一路使用到底，比較好除錯。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;</content><link rel='replies' type='application/atom+xml' href='http://sushiwens.blogspot.com/feeds/34371022452984878/comments/default' title='張貼留言'/><link rel='replies' type='text/html' href='http://sushiwens.blogspot.com/2015/03/c-async-await.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3638477104687038182/posts/default/34371022452984878'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3638477104687038182/posts/default/34371022452984878'/><link rel='alternate' type='text/html' href='http://sushiwens.blogspot.com/2015/03/c-async-await.html' title='C# async 和 await 控制基本教學'/><author><name>Unknown</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3638477104687038182.post-3971776834837859082</id><published>2015-03-25T17:36:00.000+08:00</published><updated>2015-03-25T17:40:33.136+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="CAC"/><category scheme="http://www.blogger.com/atom/ns#" term="Churn"/><category scheme="http://www.blogger.com/atom/ns#" term="LTV"/><category scheme="http://www.blogger.com/atom/ns#" term="SaaS"/><category scheme="http://www.blogger.com/atom/ns#" term="Subscription"/><title type='text'>SaaS Subscription business 的衡量指標</title><content type='html'>&lt;h4&gt;
SaaS Subscription business 遇到的問題&lt;/h4&gt;
&lt;div&gt;
&lt;ol&gt;
&lt;li&gt;我的 business 能生存嗎？財務上是可行的嗎？&lt;/li&gt;
&lt;li&gt;怎麼分辨什麼部分是好的？什麼部分是要改進的？&lt;/li&gt;
&lt;li&gt;什麼財務槓桿或指標更應該被關注？&lt;/li&gt;
&lt;li&gt;採取一些加速行動會造成什麼現金流影響？&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
&lt;h4&gt;
SaaS 不同之處，商業目標是&lt;/h4&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;ol&gt;
&lt;li&gt;獲得客戶&lt;/li&gt;
&lt;li&gt;留住客戶&lt;/li&gt;
&lt;li&gt;讓客戶付費&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;
&lt;h4&gt;
SaaS 的 P&amp;amp;L 和現金流&lt;/h4&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;ol&gt;
&lt;li&gt;SaaS business 在一開始需要的付出比較大，從之後長期的關係慢慢回收投資。&lt;/li&gt;
&lt;li&gt;當決定要加速的時候現金流反而會呈現負成長。&lt;/li&gt;
&lt;li&gt;加速會造成公司短期的負債擴大，但是負債期縮短，損益兩平之後更快佔領市場。&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div&gt;
所以假設一個 SaaS business 可以在早期降低風險，就可以增加投資擴大短期負債，因為 SaaS 類型的服務在充分競爭之後往往是贏者全拿的情況。&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
&lt;h4&gt;
Unit Economics 我賺到客戶的錢會大於我拿到客戶所需的成本嗎？&lt;/h4&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
LTV: Lifetime Value of a typical customer&lt;/div&gt;
&lt;div&gt;
CAC: Cost to Acquire a typical Customer&lt;/div&gt;
&lt;div&gt;
Unit Economics: LTV:CAC ratio&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
&lt;h4&gt;
SaaS business 要存活的經驗法則:&lt;/h4&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;ol&gt;
&lt;li&gt;LTV &amp;gt; 3 x CAC&lt;/li&gt;
&lt;li&gt;Months to recover CAC &amp;lt; 12 months&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div&gt;
一般大型公司如電信業者或是信用卡公司可以承受比較長的CAC，比較優良的公司最好能夠把 CAC 控制在 5-7 months。&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
&lt;h4&gt;
客戶保留率&lt;/h4&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
MRR: Monthly Recurring Revenue&lt;/div&gt;
&lt;div&gt;
ARR: Annual Recurring Revenue&lt;/div&gt;
&lt;div&gt;
Cohort: A group of customers joined in a particular month, e.g. January cohort, to see how they decline or grow as a group.&lt;br /&gt;
Customer Churn Rate: Number of customers lost last month(quarter)/starting customers last month(quarter)&lt;br /&gt;
&lt;br /&gt;
Net MRR = New MRR&amp;nbsp;+ Expansion MRR(customers expand their subscription) - Churned MRR(customers cancelled their subscription)&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;
在客戶規模很小的時候 churn rate 不會是一個重要指標，有點類似複利的概念，只是 churn rate 指的是流失率，在客戶規模大的時候是重要指標。根據各種客戶付款商品的不同，Revenue Churn 和 Customer Churn 要分開追蹤。&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
ARPA: Average Revenue per Account per month&lt;br /&gt;
&lt;br /&gt;
&lt;h4&gt;
為何產生 Churn ？&lt;/h4&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;ol&gt;
&lt;li&gt;未達到客戶的預期。&lt;/li&gt;
&lt;li&gt;產品黏著度低，客戶不需要長期使用。&lt;/li&gt;
&lt;li&gt;客戶使用方式不完全、不同預期，或沒有使用到產品有黏著度的地方。&lt;/li&gt;
&lt;li&gt;產品可能賣給非典型客戶。（所以資料分析的時候不同客戶要分群分析。）&lt;/li&gt;
&lt;li&gt;客戶陣亡了。&lt;/li&gt;
&lt;li&gt;沒有使用 pricing scheme 來使客戶續訂。&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;
還有一堆內容... 來自&lt;br /&gt;
http://www.forentrepreneurs.com/saas-metrics-2/&lt;/div&gt;
</content><link rel='replies' type='application/atom+xml' href='http://sushiwens.blogspot.com/feeds/3971776834837859082/comments/default' title='張貼留言'/><link rel='replies' type='text/html' href='http://sushiwens.blogspot.com/2015/03/saas-subscription-business.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3638477104687038182/posts/default/3971776834837859082'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3638477104687038182/posts/default/3971776834837859082'/><link rel='alternate' type='text/html' href='http://sushiwens.blogspot.com/2015/03/saas-subscription-business.html' title='SaaS Subscription business 的衡量指標'/><author><name>Unknown</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3638477104687038182.post-2455007251740653871</id><published>2015-03-06T03:17:00.000+08:00</published><updated>2015-03-06T08:52:17.058+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Startup"/><category scheme="http://www.blogger.com/atom/ns#" term="創業"/><category scheme="http://www.blogger.com/atom/ns#" term="謊言"/><title type='text'>創業家的用語解釋，教你聽懂創業家在說什麼。</title><content type='html'>在 TechCrunch 看到一篇有趣的文章，教你聽懂創業家的用語，翻譯幾個有趣的。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;h4&gt;
Failure（創業失敗）&lt;/h4&gt;
一個最近在失業圈搞的好像很冠冕堂皇值得慶祝的事情。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;h4&gt;
Pivot（創業轉型）&lt;/h4&gt;
同上。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;h4&gt;
SAAS （軟體服務）&lt;/h4&gt;
燒錢。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;h4&gt;
Pre-Money Valuation （投資前估值）&lt;/h4&gt;
一個你憑空捏造的數字。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;h4&gt;
Post-Money Valuation （投資後估值）&lt;/h4&gt;
一個你和投資人一起憑空捏造的數字，含一些你瞬間就會燒完的現金。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;h4&gt;
雇用目的收購（Acqui-hire）&lt;/h4&gt;
你們團隊的成員一流，但是你們在做的點子實在爛透了，好了別做了，來大公司吧。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;h4&gt;
我的工作是負責公共關係。（&quot;I work in PR&quot;）&lt;/h4&gt;
我知道一兩個記者的Email。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;h4&gt;
我是負責商業的部分。（&quot;I&#39;m a business guy.&quot;）&lt;/h4&gt;
我來搭其他創業夥伴的便車。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;h4&gt;
我是企業關鍵成長駭客。（Growth Hacker）&lt;/h4&gt;
我是做行銷或銷售方面的，但是我希望我的職稱有電腦高手的感覺。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;h4&gt;
我是一個連續創業家。（&quot;I&#39;m a serial entrepreneur.&quot; ）&lt;/h4&gt;
我曾經有過兩個創業想法，全都失敗了。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;h4&gt;
我們做的很棒。 （&quot;We&#39;re doing great.&quot;）&lt;/h4&gt;
我們做的很鳥。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;h4&gt;
我們的現金流是正的（Cashflow positive）&lt;/h4&gt;
有個使用者給了我們一元。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;h4&gt;
我們每週成長五百個百分點。（&quot;We&#39;re growing 500 percent week-over-week&quot; ）&lt;/h4&gt;
上週我們有一個使用者，這週我們有六個。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;h4&gt;
我們現在沒有在募資。（&quot;We&#39;re not currently raising&quot;）&lt;/h4&gt;
我們現在非常需要募資。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;h4&gt;
我們是一個以設計為核心的團隊。（&quot;We&#39;re a design-centric organization&quot;）&lt;/h4&gt;
我們不會寫程式。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;h4&gt;
我們看到了很棒的毛利成長，所以我們正在加碼投資我們強健的軟體服務事業。（&quot;We&#39;re seeing great gross margins, and so are investing in growth given our strong, SaaS unit-economics.&quot;）&lt;/h4&gt;
我們入不敷出。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;h4&gt;
... 不僅如此，我們還積極的尋找額外的資本來加大我們成長領先的幅度。 （&quot;... and we are actively seeking additional capital to power our sector-leading growth.&quot;）&lt;/h4&gt;
我們快把錢燒光了，請給我們錢。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;h4&gt;
我們正在大開殺戒！（&quot;We&#39;re Crushing It!&quot;）&lt;/h4&gt;
我們的夢想和投資人的錢都被我們殺死的很徹底！&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
以上內容有少數加上個人的想法方便翻譯，比如&quot;失業圈&quot;是我自己硬湊的，原文是把 Silicon Valley 改寫為 Silly Valley ，比較有意思。
原文： http://techcrunch.com/2014/12/15/how-to-speak-startup/&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;</content><link rel='replies' type='application/atom+xml' href='http://sushiwens.blogspot.com/feeds/2455007251740653871/comments/default' title='張貼留言'/><link rel='replies' type='text/html' href='http://sushiwens.blogspot.com/2015/03/blog-post.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3638477104687038182/posts/default/2455007251740653871'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3638477104687038182/posts/default/2455007251740653871'/><link rel='alternate' type='text/html' href='http://sushiwens.blogspot.com/2015/03/blog-post.html' title='創業家的用語解釋，教你聽懂創業家在說什麼。'/><author><name>Unknown</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3638477104687038182.post-4434694204154496402</id><published>2015-02-28T00:36:00.000+08:00</published><updated>2015-02-28T00:36:13.557+08:00</updated><title type='text'>Subscription / Recurring Payment Gateway 筆記</title><content type='html'>&lt;h3&gt;
What is a Recurring Billing service?&lt;/h3&gt;
Charging someone’s credit card and making sure that the money ends up in your account is what Payment Gateways and Merchant Accounts are all about, but what about handling a subscription that needs to get charged every month? That’s where the subscription and recurring billing systems come in. These services have a credit card vault, and a list of recurring charges, and it’s their job to make sure that every month the right people get charged the right amount.&lt;br /&gt;
&lt;br /&gt;
Some services, like Recurly, focus entirely on this problem, and were built on top of other payment gateways and merchant accounts. Meanwhile, payment gateways have evolved and launched simple recurring billing modules on top of their payment gateways.&lt;br /&gt;
&lt;br /&gt;
While some payment gateways have existed for decades, the recurring billing space is not nearly as old. The companies that focus on recurring billing as their core have developed a bunch of features to make your life as a software developer easier, such as sending emails to your customers with their invoices, pro-rating upgrades and downgrades, and managing dunning (i.e. handling when a credit card has expired or rejected a charge).&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;
What is a Payment Gateway?&lt;/h3&gt;
Payment Gateways are the backbone of online payments and e-commerce of all kinds. If someone is taking your credit card online, they are probably using a payment gateway. It is these guys that connect to all the credit card companies like VISA, Mastercard and AMEX to make it easy to collect any kind of credit card you want without having to submit to Payment Card Industry (PCI) compliance.&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;
What is a Merchant Account?&lt;/h3&gt;
Once a credit card is charged, the money needs to go somewhere. That’s where a merchant account comes in. A merchant account is a type of bank account that allows businesses to accept money from payment cards. If you want to accept credit cards online, you will need a merchant account. You might not realize this because some payment gateways will provide you with a merchant account behind the scenes, but there will always be a merchant account in the mix.&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;
So, How do I pick?&lt;/h3&gt;
&lt;br /&gt;
Here are three questions that will help you narrow down your search.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;h4&gt;
Question 1 – What do your payments look like?&lt;/h4&gt;
Are you doing simple plan-based billing, or are you going to be charging for variable usage? If you’re building something complicated, then you probably want to look at a company that focuses on SaaS as their primary business instead of a “Hybrid” payment gateway. If you’re doing simple plan-based billing, then you’ll be pleasantly surprised at how quick and easy it can be to configure some of the Hybrid gateways to do just that.&lt;br /&gt;
&lt;br /&gt;
&lt;h4&gt;
Question 2 – Where are you?&lt;/h4&gt;
Some providers are only available in a select few countries. For example, Stripe is only available in the USA and Canada, while others, such as Paymill, focus on the European market. Payment processing law is a complicated, region-dependent beast, so you want to make sure that you have the right mix of payment gateway and management layer to help you navigate the legal maze for where your company is headquartered.&lt;br /&gt;
&lt;br /&gt;
&lt;h4&gt;
Question 3 – How much can you spend?&lt;/h4&gt;
Most hybrid gateways don’t charge extra for their subscription layer. That’s great news if you’re doing something simple, but if you need the power of the subscription-focused services, you’re going to have to pay extra. Most companies have a mix of monthly fee plus transaction fee, and don’t charge anything to get started. That said, there are some companies, like Zuora, that charge a five-figure price tag to get set up and don’t work with companies under $1M in yearly revenue.&lt;br /&gt;
&lt;br /&gt;
Read more: http://www.referralsaasquatch.com/how-to-pick-a-recurring-billing-and-payment-provider/&lt;br /&gt;
&lt;br /&gt;&lt;br /&gt;
&lt;h3&gt;
Keys to consider&lt;/h3&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;Payment Gateway Support&lt;/b&gt;: Does the provider offer payment gateway support in your country? Do you have a current account with one of their gateway providers? Do they offer their own payment gateway? Some providers offer their recurring billing, gateway services, and merchant account all in one. Others offer combinations of the two, and still others offer just the recurring billing service.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Payment Methods&lt;/b&gt;: Depending on your business type, you may want to accept different types of payment methods. This could be credit card payments, EFT, ACH, PayPal, checks, purchase orders, wire transfers, etc.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Credit Card Data Portability&lt;/b&gt;: A good recurring billing provider won&#39;t hold your customer credit card data hostage. Look for a provider that will transfer your credit card data to another PCI compliant vendor, should you ever leave their services. Bonus points if they don&#39;t charge you for it.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Physical Goods&lt;/b&gt;: Most recurring billing providers have core functionality around electronic subscriptions. If you&#39;re shipping physical goods, make sure your recurring billing provider can handle the collection of and reporting on shipping information&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Reporting&lt;/b&gt;: Does the billing provider share reports necessary for your business?&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Customer Communication&lt;/b&gt;: Does the billing provider handle automated communication to your customer, or will you need another provider for this functionality?&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Dunning Management&lt;/b&gt;: Does the billing provider follow up automatically on failing transactions? How often? Is it configurable?&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Taxes&lt;/b&gt;: Based on your location, you may want a billing provider that handles sales tax for your purchases&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Multi-currency&lt;/b&gt;: Based on your customer pool, you may want to offer your product in multiple currencies. Does you billing provider support each currency you&#39;d like to list?&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Support&lt;/b&gt;: What sort of support model does the billing provider have for your company? Do you need a dedicated account representative? Phone support? 24x7 support?&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Integration&lt;/b&gt;: Billing providers can vary their integrations from the very simple (A checkout form hosted on their site) to full API integrations on your site. Does the billing provider host the type of integration you&#39;re looking for? Do they offer integration assistance (Typically at a professional services fee)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Billing Model&lt;/b&gt;: This is a big one. Does the billing provider have billing periods (daily, monthly, quarterly, yearly) to fit your billing needs? Do you have any unusual billing requirements like billing on the 3rd Tuesday of every other month? Does the product support free trials, setup fees, multiple subscriptions, additional products, gift subscriptions, pausing subscriptions, upgrading/downgrading plans... etc.&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
http://www.quora.com/Which-is-better-Chargify-Recurly-ChargeBee-or-Spreedly&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;
1. PayPal has partnered with Aria Systems to provide subscription/recurring billing services (see PayPal Subscription+). You can use your own merchant account or PayPal&lt;br /&gt;
2. Recurly: very nice subscription billing platform, but fairly expensive ($29/mo + $0.20/transaction on top of your merchant fees) - requires your own merchant account&lt;br /&gt;
3. Chargify: good features and pricing (they bill by customer, not transaction) - requires your own merchant account&lt;br /&gt;
4. Cheddar Getter: offers a free plan, bills by customer - requires your own merchant account&lt;br /&gt;
5. Spreedly: $19/mo + $0.20/transaction - supports LOTS of different gateways including international&lt;br /&gt;
6. Zuora: If you have lots of money to invest, they could be a good fit - seem to be catering more to enterprise payment processing not SMB&lt;br /&gt;(Jason Read)&lt;br /&gt;
&lt;br /&gt;Read more: http://www.quora.com/What-is-the-best-checkout-tool-for-recurring-payments&lt;br /&gt;&lt;br /&gt;
My personal recommendation for scalable recurring payments is to get a merchant account through FeeFighters.com, use the (usually free/included) default gateway of the account provider (usually Authorize.net), and use the (rather elegant) Recurly.com APIs to handle all the recurring stuff. &amp;nbsp;Just make sure that you&#39;re using Recurly&#39;s vault and not using Auth.net&#39;s CIM so you have full data portability if/when the time comes ( http://blog.recurly.com/2010/05/... ) .&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;
Paypal&lt;/h3&gt;
&lt;br /&gt;
If you&#39;re building a side project or something less ambitious, PayPal&#39;s recurring payments solution is certainly good enough.&lt;br /&gt;
(Sachin Agarwal)&lt;br /&gt;
Also: http://sachinagarwal.com/introduction-to-online-payments-tldr-its-a-to&lt;br /&gt;
Read more:&amp;nbsp;http://www.quora.com/What-is-the-best-checkout-tool-for-recurring-payments&lt;br /&gt;
&lt;br /&gt;
https://www.paypal.com/us/cgi-bin/webscr?cmd=xpt/Marketing/general/RecurringPaymentFAQs-outside&lt;br /&gt;
5. Can a customer without a PayPal account still pay for recurring payments with a credit card?&lt;br /&gt;
Yes, if you offer both PayPal Payments Pro and recurring payments.&lt;br /&gt;
&lt;br /&gt;
But PayPal Payments Pro is only available in US, UK, Canada&lt;br /&gt;&lt;br /&gt;Lack of dunning management&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;
Google Trends&lt;/h3&gt;
http://www.google.com.tw/trends/explore?q=aria%2C+chargebee%2C+chargify%2C+cheddargetter%2C+fusebill%2C+metanga%2C+monexa%2C+recurly%2C+sassy%2C+spreedly%2C+stripe%2C+subscriptionbridage%2C+tract%2C+vindicia%2C+wepay%2C+zuora#q=chargebee%2C%20chargify%2C%20recurly%2C%20%2Fm%2F0hrfb59&amp;amp;cmpt=q&amp;amp;tz=&lt;br /&gt;
&lt;h3&gt;
Recurly&lt;/h3&gt;
https://recurly.com/gateways/&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;
Chargify&lt;/h3&gt;
https://www.chargify.com/payment-gateways/&lt;br /&gt;&lt;h3&gt;
Chargebee&lt;/h3&gt;
https://www.chargebee.com/partners.html&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;
Spreedly&lt;/h3&gt;
https://spreedly.com/gateways&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;
Zuora&lt;/h3&gt;
for Enterprise&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
</content><link rel='replies' type='application/atom+xml' href='http://sushiwens.blogspot.com/feeds/4434694204154496402/comments/default' title='張貼留言'/><link rel='replies' type='text/html' href='http://sushiwens.blogspot.com/2015/02/subscription-recurring-payment-gateway.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3638477104687038182/posts/default/4434694204154496402'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3638477104687038182/posts/default/4434694204154496402'/><link rel='alternate' type='text/html' href='http://sushiwens.blogspot.com/2015/02/subscription-recurring-payment-gateway.html' title='Subscription / Recurring Payment Gateway 筆記'/><author><name>Unknown</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3638477104687038182.post-6545156066752057714</id><published>2015-02-16T22:53:00.001+08:00</published><updated>2015-02-16T22:53:17.133+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Content Security Policy"/><category scheme="http://www.blogger.com/atom/ns#" term="Google Chrome Extension"/><category scheme="http://www.blogger.com/atom/ns#" term="JSONP"/><category scheme="http://www.blogger.com/atom/ns#" term="OAuth"/><title type='text'>Web Authentication in Chrome extension 外掛環境的網站授權筆記</title><content type='html'>這篇筆記主要紀錄在 Google Chrome Extension 環境如何取得使用者 identity 以及使用Google Oauth2 授權。&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;
chrome.identity&lt;/h3&gt;
&lt;br /&gt;
在 Google Chrome 想要拿到一個使用者身分最基本的情況只要使用 &lt;a href=&quot;https://developer.chrome.com/apps/identity&quot;&gt;chrome.identity&lt;/a&gt;&amp;nbsp;就可以了，可以取得 access_token 後丟給 server 取得身分。&lt;br /&gt;
&lt;br /&gt;
1. chrome.identity.getAuthToken 取得 access_token ，這個 token 會被 cache 在 Chrome identity API裡面一陣子，第二次之後取得會很快，相對需要手動 removeCachedAuthToken 來刪除。scope 則是在 plugin 的 manifest.json 中定義。&lt;br /&gt;
&lt;br /&gt;
2. 取得 access_token 後可以在 server 或是 client 使用 &#39;Authorization&#39; header 向 Google API URI 要資料。&lt;br /&gt;
&lt;br /&gt;
在 background page 呼叫 chrome.identity 可以指定 interactive 和登入身分，但是這個登入身分和類似API中的&amp;nbsp;login_hint&amp;nbsp;不同，chrome.identity 限制這個身分是當下 Chrome Browser 使用者的身分之一，而且使用者必須要在登入狀態，跟我們常看到的一個頁面給使用者選擇要登入不同身分登入的情況不同，開發者即使知道欲取得的 user email or user ID 也無法提示使用者這個已知，使用者也無法選擇。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;
Web Server Oauth Process&lt;/h3&gt;
&lt;br /&gt;
另一個方法使用 Google Oauth2 的 web process ，引導使用者跳轉或是開啟一個授權頁面，使用者授權後 Google 會把一組 code 丟給 redirect_uri (你的server)，然後再用這組 code 去取得 access_token and/or refresh_token ，這個方法使用者就可以選擇登入的帳號了，如果知道使用者的 email 還可以先提示 login_hint ，這樣使用者可以少一個選帳號的動作，使用者如果授權過同樣的 scope 這個 interactive 過程會直接帶過，而且一個 Chrome Browser 可以取得多組 Google Account 身分和授權，不受限制。&lt;br /&gt;
&lt;br /&gt;
Server 知道身分之後要怎麼給 client 呢？可以透過 session 讓 client 知道使用者登入了等一些資訊，有點被動，還是要用 long polling / websocket 等牛刀，另外 background page 是沒有存 web cookie 的獨立環境，授權流程如果放進 web page 裡面，還要考慮 Content Security Policy ，我就把外掛裝在 business Gmail 的時候可以運作，但是一登入&amp;nbsp;@gmail.com 的 Gmail 頁面就噴 Content Security Policy Error 了，同樣是 Gmail 介面沒想到 Content Security Policy 官版的比較嚴格。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;
Mix JSONP Authentication Process&lt;/h3&gt;
我的網站沒有提供 Google Account 登入，因為估計目標市場只有很少使用人使用 Google Account for work。我在網站提供一組 JSONP 讓，Chrome Extension 來抓，這個抓授權的 javascript 只能放在 content script 裡面，不能放在 web page 或 background page ， web page 可能有 http/https 限制、Content Security Policy。Google Oauth則在需要取得授權的時候才跑 Web Server Oauth Process.&lt;br /&gt;
&lt;br /&gt;
manifest.json 裡面的 content_security_policy 是限制開發者自帶的 script 的環境，如果要配合 gmail.js 把 script 都丟進 web page ，那就要受到 web page 的環境的(web owner 決定) policy 管制。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;</content><link rel='replies' type='application/atom+xml' href='http://sushiwens.blogspot.com/feeds/6545156066752057714/comments/default' title='張貼留言'/><link rel='replies' type='text/html' href='http://sushiwens.blogspot.com/2015/02/web-authentication-in-chrome-extension.html#comment-form' title='1 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3638477104687038182/posts/default/6545156066752057714'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3638477104687038182/posts/default/6545156066752057714'/><link rel='alternate' type='text/html' href='http://sushiwens.blogspot.com/2015/02/web-authentication-in-chrome-extension.html' title='Web Authentication in Chrome extension 外掛環境的網站授權筆記'/><author><name>Unknown</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3638477104687038182.post-2318598042835411382</id><published>2014-11-21T14:36:00.000+08:00</published><updated>2014-11-21T14:37:09.052+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Certificate"/><category scheme="http://www.blogger.com/atom/ns#" term="Code Signing"/><category scheme="http://www.blogger.com/atom/ns#" term="Visual Studio"/><category scheme="http://www.blogger.com/atom/ns#" term="Windows"/><title type='text'>Code Signing Certificate (from CA) Steps and trouble shooting</title><content type='html'>&lt;div&gt;
Steps:
&lt;/div&gt;
&lt;ol style=&quot;border: 0px; font-family: gotham, Helvetica, Arial, &#39;Droid Sans&#39;, sans-serif; font-size: 14px; line-height: 1.571428em; list-style-position: outside; margin: 0.2857em 0px 0.714285em 2em; padding: 0px;&quot;&gt;
&lt;li style=&quot;border: 0px; line-height: 1.571428em; margin: 0px; padding: 0px;&quot;&gt;Apply certificate from comodo&lt;/li&gt;
&lt;li style=&quot;border: 0px; line-height: 1.571428em; margin: 0px; padding: 0px;&quot;&gt;From Firefox certificate manager, select the certificate and &quot;export all&quot;.&lt;/li&gt;
&lt;li style=&quot;border: 0px; line-height: 1.571428em; margin: 0px; padding: 0px;&quot;&gt;Save the exported certificate as comodo.p12&lt;/li&gt;
&lt;li style=&quot;border: 0px; line-height: 1.571428em; margin: 0px; padding: 0px;&quot;&gt;The .p12 and .pfx have the same format, so just change the extension to .pfx, (comodo.pfx).&lt;/li&gt;
&lt;li style=&quot;border: 0px; line-height: 1.571428em; margin: 0px; padding: 0px;&quot;&gt;Import comodo.pfx with chaining info, this will be the certificate for signing ClickOnce manifest&lt;/li&gt;
&lt;li style=&quot;border: 0px; line-height: 1.571428em; margin: 0px; padding: 0px;&quot;&gt;Timestamp server, e.g. http://timestamp.verisign.com/scripts/timstamp.dll&lt;/li&gt;
&lt;li style=&quot;border: 0px; line-height: 1.571428em; margin: 0px; padding: 0px;&quot;&gt;Clean up related certificate in certmgr&lt;/li&gt;
&lt;li style=&quot;border: 0px; line-height: 1.571428em; margin: 0px; padding: 0px;&quot;&gt;Import certificate with &quot;&lt;b&gt;Mark this key as exportable&lt;/b&gt;&quot;&lt;/li&gt;
&lt;li style=&quot;border: 0px; line-height: 1.571428em; margin: 0px; padding: 0px;&quot;&gt;From certmgr, personal certificate, select &quot;All Tasks&quot; &amp;gt; &quot;Export..&quot;&lt;/li&gt;
&lt;li style=&quot;border: 0px; line-height: 1.571428em; margin: 0px; padding: 0px;&quot;&gt;Select &quot;Yes, export the private key&quot;&lt;/li&gt;
&lt;li style=&quot;border: 0px; line-height: 1.571428em; margin: 0px; padding: 0px;&quot;&gt;In personal information exchange, &lt;b&gt;un-check &quot;Include all certificates in the certification if possible&quot;.&lt;/b&gt;&lt;/li&gt;
&lt;li style=&quot;border: 0px; line-height: 1.571428em; margin: 0px; padding: 0px;&quot;&gt;Export this key to be forAssemblies.pfx&lt;/li&gt;
&lt;li style=&quot;border: 0px; line-height: 1.571428em; margin: 0px; padding: 0px;&quot;&gt;This key may contain KeySpec=1, which is incorrect for code signing.&lt;/li&gt;
&lt;li style=&quot;border: 0px; line-height: 1.571428em; margin: 0px; padding: 0px;&quot;&gt;Open Visual Studio command prompt (VS2012 ARM Cross tools Command Prompt in VS2013), because we need to use certutil&lt;/li&gt;
&lt;li style=&quot;border: 0px; line-height: 1.571428em; margin: 0px; padding: 0px;&quot;&gt;In command prompt, navigate to the forAssemblies.pfx location&lt;/li&gt;
&lt;li style=&quot;border: 0px; line-height: 1.571428em; margin: 0px; padding: 0px;&quot;&gt;&lt;b&gt;certutil -importPFX -user forAssemblies.pfx AT_SIGNATURE&lt;/b&gt;&lt;/li&gt;
&lt;li style=&quot;border: 0px; line-height: 1.571428em; margin: 0px; padding: 0px;&quot;&gt;This command will import the pfx with KeySpec=2&lt;/li&gt;
&lt;li style=&quot;border: 0px; line-height: 1.571428em; margin: 0px; padding: 0px;&quot;&gt;This key is ready for signing assemblies&lt;/li&gt;
&lt;/ol&gt;
&lt;div style=&quot;border: 0px; font-family: gotham, Helvetica, Arial, &#39;Droid Sans&#39;, sans-serif; font-size: 14px; line-height: 1.571428em; margin: 0px; padding: 0px;&quot;&gt;
&lt;br /&gt;
&lt;b&gt;Trouble shooting&lt;/b&gt;:&amp;nbsp;&lt;span style=&quot;line-height: 1.571428em;&quot;&gt;&quot;An attempt was made to reference a token that does not exist.&quot;&lt;/span&gt;&lt;/div&gt;
&lt;div style=&quot;border: 0px; font-family: gotham, Helvetica, Arial, &#39;Droid Sans&#39;, sans-serif; font-size: 14px; line-height: 1.571428em; margin: 0px; padding: 0px;&quot;&gt;
This means the key for signing assembly is incorrect.&amp;nbsp;&lt;/div&gt;
&lt;div style=&quot;border: 0px; font-family: gotham, Helvetica, Arial, &#39;Droid Sans&#39;, sans-serif; font-size: 14px; line-height: 1.571428em; margin: 0px; padding: 0px;&quot;&gt;
Please read&amp;nbsp;&lt;a href=&quot;http://msdn.microsoft.com/en-us/library/aa730868%28v=vs.80%29.aspx&quot; style=&quot;border: 0px; color: #047ac6; cursor: pointer; line-height: 1.571428em; margin: 0px; padding: 0px;&quot;&gt;http://msdn.microsoft.com/en-us/library/aa730868%28v=vs.80%29.aspx&lt;/a&gt;&lt;/div&gt;
&lt;div style=&quot;border: 0px; font-family: gotham, Helvetica, Arial, &#39;Droid Sans&#39;, sans-serif; font-size: 14px; line-height: 1.571428em; margin: 0px; padding: 0px;&quot;&gt;
This is a general error message. There are many possible errors.&lt;/div&gt;
&lt;div style=&quot;border: 0px; font-family: gotham, Helvetica, Arial, &#39;Droid Sans&#39;, sans-serif; font-size: 14px; line-height: 1.571428em; margin: 0px; padding: 0px;&quot;&gt;
Possible solutions:&lt;/div&gt;
&lt;ol style=&quot;border: 0px; font-family: gotham, Helvetica, Arial, &#39;Droid Sans&#39;, sans-serif; font-size: 14px; line-height: 1.571428em; list-style-position: outside; margin: 0.2857em 0px 0.714285em 2em; padding: 0px;&quot;&gt;
&lt;li style=&quot;border: 0px; line-height: 1.571428em; margin: 0px; padding: 0px;&quot;&gt;The key contains chaining information. Import it with private key and export it without chaining info again.&lt;/li&gt;
&lt;li style=&quot;border: 0px; line-height: 1.571428em; margin: 0px; padding: 0px;&quot;&gt;Change KeySpec of the key to 2&lt;ol style=&quot;border: 0px; line-height: 1.571428em; list-style-position: outside; margin: 0px 0px 0px 2em; padding: 0px;&quot;&gt;
&lt;li style=&quot;border: 0px; line-height: 1.571428em; margin: 0px; padding: 0px;&quot;&gt;Read&amp;nbsp;&lt;a href=&quot;http://blogs.msdn.com/b/andrekl/archive/2008/01/08/strong-name-signing-in-visual-studio-2005-requires-keyspec-2-at-signature.aspx&quot; style=&quot;border: 0px; color: #047ac6; cursor: pointer; line-height: 1.571428em; margin: 0px; padding: 0px;&quot;&gt;http://blogs.msdn.com/b/andrekl/archive/2008/01/08/strong-name-signing-in-visual-studio-2005-requires-keyspec-2-at-signature.aspx&lt;/a&gt;&lt;/li&gt;
&lt;li style=&quot;border: 0px; line-height: 1.571428em; margin: 0px; padding: 0px;&quot;&gt;Using the &quot;Certifiates&quot; MMC export the existing keyset (KeySpec=1) to a PFX file.&lt;br /&gt;Note:Please backup this file to a safe location and test if the file can be imported ok on another machine if you really want to play it safe!&lt;/li&gt;
&lt;li style=&quot;border: 0px; line-height: 1.571428em; margin: 0px; padding: 0px;&quot;&gt;Delete the existing certificate from the crypto store (stlll using the MMC).&lt;/li&gt;
&lt;li style=&quot;border: 0px; line-height: 1.571428em; margin: 0px; padding: 0px;&quot;&gt;Open a CMD prompt.&lt;/li&gt;
&lt;li style=&quot;border: 0px; line-height: 1.571428em; margin: 0px; padding: 0px;&quot;&gt;import the PFX file using this command:&lt;/li&gt;
&lt;li style=&quot;border: 0px; line-height: 1.571428em; margin: 0px; padding: 0px;&quot;&gt;certutil -importPFX -user &lt;pfxfilename&gt; AT_SIGNATURE&lt;/pfxfilename&gt;&lt;/li&gt;
&lt;li style=&quot;border: 0px; line-height: 1.571428em; margin: 0px; padding: 0px;&quot;&gt;Enter the passphrase for the pfx when prompted.&lt;/li&gt;
&lt;li style=&quot;border: 0px; line-height: 1.571428em; margin: 0px; padding: 0px;&quot;&gt;You now should have a keyset/Cert with KeySpec=2. If needed you can now export this into another PFX file using the MMC again.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;div style=&quot;border: 0px; font-family: gotham, Helvetica, Arial, &#39;Droid Sans&#39;, sans-serif; font-size: 14px; line-height: 1.571428em; margin: 0px; padding: 0px;&quot;&gt;
&lt;b&gt;Trouble shooting&lt;/b&gt;: &quot;Cannot import the following key file:&amp;nbsp;my.pfx. The key file may be password protected. To correct this, try to import the certificate again or manually install the certificate to the Strong Name CSP with the following key container name:&amp;nbsp;&lt;span style=&quot;line-height: 1.571428em;&quot;&gt;VS_KEY_605BB970F096DC5F&lt;/span&gt;&quot;&lt;/div&gt;
&lt;div style=&quot;border: 0px; font-family: gotham, Helvetica, Arial, &#39;Droid Sans&#39;, sans-serif; font-size: 14px; line-height: 1.571428em; margin: 0px; padding: 0px;&quot;&gt;
&lt;br data-mce-bogus=&quot;1&quot; /&gt;&lt;/div&gt;
&lt;div style=&quot;border: 0px; font-family: gotham, Helvetica, Arial, &#39;Droid Sans&#39;, sans-serif; font-size: 14px; line-height: 1.571428em; margin: 0px; padding: 0px;&quot;&gt;
Possible solutions:&lt;/div&gt;
&lt;ol style=&quot;border: 0px; font-family: gotham, Helvetica, Arial, &#39;Droid Sans&#39;, sans-serif; font-size: 14px; line-height: 1.571428em; list-style-position: outside; margin: 0.2857em 0px 0.714285em 2em; padding: 0px;&quot;&gt;
&lt;li style=&quot;border: 0px; line-height: 1.571428em; margin: 0px; padding: 0px;&quot;&gt;sn this key to container&lt;ol style=&quot;border: 0px; line-height: 1.571428em; list-style-position: outside; margin: 0px 0px 0px 2em; padding: 0px;&quot;&gt;
&lt;li style=&quot;border: 0px; line-height: 1.571428em; margin: 0px; padding: 0px;&quot;&gt;In command prompt, navigate to the AssemblyNoChaining.pfx&amp;nbsp;location&lt;/li&gt;
&lt;li style=&quot;border: 0px; line-height: 1.571428em; margin: 0px; padding: 0px;&quot;&gt;&quot;C:\Program Files (x86)\Microsoft SDKs\Windows\v8.0A\bin\N&lt;br /&gt;ETFX 4.0 Tools\x64\sn.exe&quot; -d VS_KEY_605BB970F096DC5F&lt;/li&gt;
&lt;li style=&quot;border: 0px; line-height: 1.571428em; margin: 0px; padding: 0px;&quot;&gt;&quot;C:\Program Files (x86)\Microsoft SDKs\Windows\v8.0A\bin\N&lt;br /&gt;ETFX 4.0 Tools\x64\sn.exe&quot; -i forAssemblyNoChaining.pfx VS_KEY_605BB970F096DC5F&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li style=&quot;border: 0px; line-height: 1.571428em; margin: 0px; padding: 0px;&quot;&gt;Change KeySpec of the key to 2 as above&lt;/li&gt;
&lt;/ol&gt;
&lt;div style=&quot;border: 0px; font-family: gotham, Helvetica, Arial, &#39;Droid Sans&#39;, sans-serif; font-size: 14px; line-height: 1.571428em; margin: 0px; padding: 0px;&quot;&gt;
reference:&amp;nbsp;https://stackoverflow.com/questions/2815366/cannot-import-the-keyfile-blah-pfx-error-the-keyfile-may-be-password-protec/14644793#14644793&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;&lt;/div&gt;
</content><link rel='replies' type='application/atom+xml' href='http://sushiwens.blogspot.com/feeds/2318598042835411382/comments/default' title='張貼留言'/><link rel='replies' type='text/html' href='http://sushiwens.blogspot.com/2014/11/code-signing-certificate-from-ca-steps.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3638477104687038182/posts/default/2318598042835411382'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3638477104687038182/posts/default/2318598042835411382'/><link rel='alternate' type='text/html' href='http://sushiwens.blogspot.com/2014/11/code-signing-certificate-from-ca-steps.html' title='Code Signing Certificate (from CA) Steps and trouble shooting'/><author><name>Unknown</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3638477104687038182.post-3876836410040132996</id><published>2014-11-02T08:11:00.000+08:00</published><updated>2014-11-02T08:20:40.496+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Certificate"/><category scheme="http://www.blogger.com/atom/ns#" term="ClickOnce"/><category scheme="http://www.blogger.com/atom/ns#" term="Office"/><category scheme="http://www.blogger.com/atom/ns#" term="Outlook"/><category scheme="http://www.blogger.com/atom/ns#" term="Publishing"/><category scheme="http://www.blogger.com/atom/ns#" term="Signing"/><category scheme="http://www.blogger.com/atom/ns#" term="VSTO"/><title type='text'>Office Outlook Customization 開發筆記(3) Signing &amp; Publishing</title><content type='html'>開發最後卡最久的地方是 Signing &amp;amp; Publishing&lt;br /&gt;
&lt;br /&gt;
在 msdn 上說我們可以使用一個 test Certificate 來 sign 我們的 binary / assembly / manifest ，這裡講的 Certificate 是指 Code Signing Certificate 。&lt;br /&gt;
&lt;br /&gt;
但是實際上，使用 test Certificate 是非常看情況的，在我所選擇使用的環境和狀況下( .net framework 4.0, VSTO, ClickOnce )，使用 test Certificate 在安裝 manifest (.vsto)時會引發 System.Security.SecurityException，並沒有讓使用者決定要不要安裝這個不受信任的 Publisher 的軟體。&lt;br /&gt;
&lt;br /&gt;
這種情況研究之後可能有幾種解法，第一個是使用 Intranet 來安裝，Windows 基本上是相信區網的，但是這樣完全失去選用 ClickOnce 的意義，不考慮。第二種方法，乖乖去 CA 購買一個 Certificate，一個一年大約 100美金有找，也有特殊用途的到 500 美金。缺點是，這種第三方購買憑證的期限一定很短，期限到了 ClickOnce 就不會自動更新了，要用特別的方法來更新憑證，我們小戶也不可能一次花個幾百美金買個三四年期的憑證，對比 self-signed Certificate 的使用期限是自己打上去的，可以寫個五十年，五十年不用更換憑證。第三種方法是讓使用者先安裝我們的 test Certificate ，讓這個憑證變成已經信任的，甚至可以用 Group Policy 幫整個公司的電腦裝憑證，缺點是這種大客戶公司的 IT 大概不會讓我這樣搞。第四種方法是安裝前修改客戶電腦的 prompt level ，至少讓客戶有辦法選擇要不要安裝這個可疑的軟體，那至少要有管理員權限，這讓安裝步驟變得麻煩。&lt;br /&gt;
&lt;br /&gt;
結果我研究了兩三天之後還是乖乖的去 CA 買了一個 Certificate &amp;nbsp;...&lt;br /&gt;
&lt;br /&gt;</content><link rel='replies' type='application/atom+xml' href='http://sushiwens.blogspot.com/feeds/3876836410040132996/comments/default' title='張貼留言'/><link rel='replies' type='text/html' href='http://sushiwens.blogspot.com/2014/11/office-outlook-customization-3-signing.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3638477104687038182/posts/default/3876836410040132996'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3638477104687038182/posts/default/3876836410040132996'/><link rel='alternate' type='text/html' href='http://sushiwens.blogspot.com/2014/11/office-outlook-customization-3-signing.html' title='Office Outlook Customization 開發筆記(3) Signing &amp; Publishing'/><author><name>Unknown</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3638477104687038182.post-2270876792192032542</id><published>2014-10-25T03:38:00.001+08:00</published><updated>2014-11-02T08:19:49.394+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term=".Net Framework"/><category scheme="http://www.blogger.com/atom/ns#" term="Add-in"/><category scheme="http://www.blogger.com/atom/ns#" term="C#"/><category scheme="http://www.blogger.com/atom/ns#" term="Office"/><category scheme="http://www.blogger.com/atom/ns#" term="Outlook"/><category scheme="http://www.blogger.com/atom/ns#" term="Ribbon UI"/><category scheme="http://www.blogger.com/atom/ns#" term="VSTO"/><title type='text'>Office Outlook Customization 開發筆記(2)</title><content type='html'>我是從 Windows 7 32bit Outlook 2010 .Net Framework 4.5 開始開發，使用 Visual Studio 2013。&lt;br /&gt;
&lt;br /&gt;
接下來要選擇使用 VBA 或 VSTO ，兩個比較可以參考&lt;a href=&quot;http://stackoverflow.com/questions/16770082/vba-vs-vsto-what-is-the-difference-should-i-upgrade&quot; target=&quot;_blank&quot;&gt;這裡&lt;/a&gt;，因為我會用到 .Net Framework 的一些程式庫，所以決定用 VSTO &amp;nbsp;來開發，語言使用 C#。&lt;br /&gt;
&lt;br /&gt;
然後最好先去下載 Outlook 2010 範例來閱讀和修改，沒看過範例很難起手。&lt;br /&gt;
&lt;br /&gt;
開發上要注意的是，Application, Explorer, Inspector 等的 instance 是可以有多個並存的，因為使用者可以開多個 Outlook、多個郵件視窗，所以各個 instance 要蒐集起來處理，可以自製類別和類別容器來把這些 instance 包起來。&lt;br /&gt;
&lt;br /&gt;
特別要講的是 UI &amp;nbsp;的開發，我選 Custom Ribbon UI 來開發，比較簡單的方法是先用 Ribbon UI Designer 拉出大概想要的介面，再匯出成 XML 的格式來修改，兩種設計方式無法並存，最後決定用 Ribbon XML ，因為屬性要修改比較方便，缺點是要查 msdn 才知道屬性名稱。&lt;br /&gt;
&lt;br /&gt;
載入客製化 UI 的方法是定義 GetCustomUI(string ribbonID) ，依照目前的情境（Explorer, Mail.Read, Mail.Compose, ....）來決定目前要載入哪個搭配的 Custom UI 的 XML 或 Designer UI 檔案。&lt;br /&gt;
&lt;br /&gt;
互動原理是在 Ribbon XML 中指定 Callback 函式名稱，然後在程式中定義 Callback 函式的處理方式，參考&lt;a href=&quot;http://msdn.microsoft.com/en-us/library/aa942866.aspx#RibbonDescriptorFile&quot; target=&quot;_blank&quot;&gt;這裡&lt;/a&gt;。&lt;br /&gt;
&lt;br /&gt;
預設的 Ribbon UI template 會有完整的 customUI &amp;gt; ribbon &amp;gt; tabs &amp;gt; tab&amp;gt; group 的結構，可以讓一個外掛擁有很大的空間，也會自動做 responsive layout ，但是如果我們只是想要改造某個內建的功能，我們可以指定 ControlId / OfficeId 或是 XML 的 idMso 來指定修改某個內建的部份（參考&lt;a href=&quot;http://msdn.microsoft.com/en-us/library/bb608593.aspx&quot; target=&quot;_blank&quot;&gt;這裡&lt;/a&gt;），至於內建 ID 的列表可以從&lt;a href=&quot;http://www.microsoft.com/en-us/download/details.aspx?id=6627&quot; target=&quot;_blank&quot;&gt;這裡&lt;/a&gt;下載。&lt;br /&gt;
&lt;br /&gt;
Ribbon UI 的優點是他是自適應的，在 Ribbon UI XML 中，如果要幫按鈕加上自定的 icon ，可以使用 getImage 方法，然後在 C# 檔案中回傳 BitMap ，路徑範例 MyAddin1.Properties.Resources.myIcon ，不加附檔名。圖片檔可以從 Project 的 Properties 加入。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;a href=&quot;http://sushiwens.blogspot.tw/2014/11/office-outlook-customization-3-signing.html&quot; target=&quot;_blank&quot;&gt;Office Outlook Customization 開發筆記(3) Signing &amp;amp; Publishing&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;</content><link rel='replies' type='application/atom+xml' href='http://sushiwens.blogspot.com/feeds/2270876792192032542/comments/default' title='張貼留言'/><link rel='replies' type='text/html' href='http://sushiwens.blogspot.com/2014/10/office-outlook-customization-2.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3638477104687038182/posts/default/2270876792192032542'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3638477104687038182/posts/default/2270876792192032542'/><link rel='alternate' type='text/html' href='http://sushiwens.blogspot.com/2014/10/office-outlook-customization-2.html' title='Office Outlook Customization 開發筆記(2)'/><author><name>Unknown</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3638477104687038182.post-8562617969565468027</id><published>2014-10-24T21:04:00.000+08:00</published><updated>2014-11-02T08:18:44.408+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term=".Net Framework"/><category scheme="http://www.blogger.com/atom/ns#" term="C#"/><category scheme="http://www.blogger.com/atom/ns#" term="Microsoft"/><category scheme="http://www.blogger.com/atom/ns#" term="Office"/><category scheme="http://www.blogger.com/atom/ns#" term="Outlook"/><title type='text'>Office Outlook Customization 開發筆記(1)</title><content type='html'>嘗試 Outlook 的開發，這次要做一個 Outlook 小工具，記錄一下開發過程。&lt;br /&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
首先要了解環境，建議最好對於客戶或使用者電腦的環境有一定的認知再開始比較好。&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
可能的變異數是：&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
1. 作業系統：不同的作業系統很有可能要跑不同的 build ，也要注意哪些系統微軟已經停止支援，還有雖然 Mac OS 也有 Office ，但是並沒有開發工具可以讓開發者客製化，最接近的情況就是用 AppleScript 做一些自動化的工作，但是可開發功能低很多，也不能改變使用者介面。&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
2. CPU位元：我只有一台 32bit 老電腦，無法開發 64bit CPU 的產品，反之 64bit 可以開發 64bit &amp;amp; 32bit 的。&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
3. .Net Framework 版本：基本上個版本無法向下相容之前的版本，除了 .Net Framework 3.5 包含 3.0 和 2.5 ，還有 4.5 和 4.0 可以相容。 如果知道客戶用的版本可以針對該版本開發，或是安裝的時候幫他裝相應的版本，但此時也要考慮到，比較新的版本的 .Net Framework 是無法安裝在比較舊的作業系統上的。&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
4. Outlook：2013, 2010, 2007, 2003 不同版本我是分不同專案分開開發的，沒有看到好方法可以放在同一個專案開發。&lt;br /&gt;
&lt;br /&gt;
5. 連線環境：跟部屬方式有關，要看客戶的使用環境是否可以連上網路，來選擇不同的方式打包讓客戶安裝。&lt;br /&gt;
&lt;br /&gt;
由此可知，可能的排列組合太多了，非常痛苦，所以最好要對目標用戶的使用環境先了解一下，有些客戶不會看環境，可以丟個&amp;nbsp;whatsmyos.com 請客戶看一下，.Net Framework就要自己想辦法包了。&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
開發微軟的產品會需要很多 license ，自己全買下來會很貴，可以看看 express 版本，校友版本，或是申請 Microsoft BizSpark。&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
&lt;a href=&quot;http://sushiwens.blogspot.tw/2014/10/office-outlook-customization-2.html&quot; target=&quot;_blank&quot;&gt;Office Outlook Customization 開發筆記(2)&lt;/a&gt;&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
</content><link rel='replies' type='application/atom+xml' href='http://sushiwens.blogspot.com/feeds/8562617969565468027/comments/default' title='張貼留言'/><link rel='replies' type='text/html' href='http://sushiwens.blogspot.com/2014/10/office-outlook-customization-1.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3638477104687038182/posts/default/8562617969565468027'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3638477104687038182/posts/default/8562617969565468027'/><link rel='alternate' type='text/html' href='http://sushiwens.blogspot.com/2014/10/office-outlook-customization-1.html' title='Office Outlook Customization 開發筆記(1)'/><author><name>Unknown</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3638477104687038182.post-2589640271975776720</id><published>2014-06-20T10:38:00.000+08:00</published><updated>2014-06-20T10:38:56.320+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Data Analysis"/><category scheme="http://www.blogger.com/atom/ns#" term="Realtime"/><category scheme="http://www.blogger.com/atom/ns#" term="Redis"/><title type='text'>Realtime Scalable Data Analysis with Redis</title><content type='html'>&lt;a href=&quot;http://redis.io/topics/cluster-spec&quot; target=&quot;_blank&quot;&gt;Redis&lt;/a&gt;&amp;nbsp;一般用來做記憶體層的 key-value cache store，一般 data analysis 也不會使用 Redis 當儲存，因為 Redis 跑在單一主機上的情況記憶體容量有限，不方便橫向擴展，&lt;a href=&quot;http://redis.io/topics/cluster-spec&quot; target=&quot;_blank&quot;&gt;Redis cluster&lt;/a&gt;&amp;nbsp;有希望解決橫向擴展的問題，但是目前還在 Beta 階段，不適合放到我們目前這個穩定性優先的project 裡面。&lt;br /&gt;
&lt;br /&gt;
選擇使用 Redis 的主要原因是，Redis 速度非常快，可以做非巨量資料的 realtime 分析，資料結構比 memcached 彈性，我們的專案不需要分析超過一個禮拜以上的資料，只是做重點分析，做沒有特定目的的儲存和分析我們使用其他的資料庫來做，還有我們的專案索需要儲存的資料結構 Redis 都有支援，資料可以自然揮發（expire），另外最最最重要的是 AWS ElastiCache 有直接支援，省掉了 server 架構的時間和維護成本。這樣最後只剩下一個問題，就是橫向擴展問題。&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Consistent Hashing&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;a href=&quot;http://en.wikipedia.org/wiki/Consistent_hashing&quot; target=&quot;_blank&quot;&gt;Consistent Hashing&lt;/a&gt;&amp;nbsp;可以用來解決 key-value datastore 擴展的時候資料搬遷最小化的問題，避免因為使用傳統的 hash 方式造成伺服器擴展的時候，短時間極大量的資料都要往資料的源頭（底層）訪問造成系統失靈。Consistent Hashing 同時也是 DynamoDB 和 Riak 等資料庫重要的核心概念。&lt;br /&gt;
&lt;br /&gt;
我們也可以用 Consistent Hash Ring 來做記憶體陣列來增加橫向擴展的能力，同時兼具擴展時資料最小搬遷的優點，但是有一個問題，使用 Consistent Hash 的資料必須是 key - value 對應，一定要有 key 才能做 hash 對吧，但是 Redis 中許多指令是不對應 key 的，比如說 keys 列出所有的 key ，必須要自己做 &amp;nbsp;broadcast ，還有許多指令根本會失效的，像是 sort ，這種對全部資料演算的都不能用，所以部分索引資料還是要存在特定的 server 上，這樣又有一個問題，就是SPOF(single point of failure) ，對於這樣的情況，我們目前的解法仍然是廣播指令到一定數量的 server 上，也就是應用層的 replication server ，這些 replication server 其實也同樣位於 &amp;nbsp;Consistent Hash Ring 中，這樣會產生另外一個問題，就是資料在 replication servers 中有可能不一致，我們目前的解決方式是 fail-retry-verify 的方式來使資料最終會一致，也因為有可能發生時間差的問題，所以我們仍然無法直接使用這個方法來把 Redis 建構成我們主要的 database，但是用來做重點資料分析，這樣已經綽綽有餘了。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;</content><link rel='replies' type='application/atom+xml' href='http://sushiwens.blogspot.com/feeds/2589640271975776720/comments/default' title='張貼留言'/><link rel='replies' type='text/html' href='http://sushiwens.blogspot.com/2014/06/scalable-realtime-data-analysis.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3638477104687038182/posts/default/2589640271975776720'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3638477104687038182/posts/default/2589640271975776720'/><link rel='alternate' type='text/html' href='http://sushiwens.blogspot.com/2014/06/scalable-realtime-data-analysis.html' title='Realtime Scalable Data Analysis with Redis'/><author><name>Unknown</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3638477104687038182.post-934399881949721213</id><published>2014-02-15T17:20:00.003+08:00</published><updated>2014-02-15T17:21:54.245+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="AWS"/><category scheme="http://www.blogger.com/atom/ns#" term="boto"/><category scheme="http://www.blogger.com/atom/ns#" term="DynamoDB"/><category scheme="http://www.blogger.com/atom/ns#" term="python"/><title type='text'>DynamoDB python 支援現況</title><content type='html'>用了幾天 DynamoDB （with python boto）下來實在是很不順，最主要原因是版本支援凌亂，現在 python boto v2.25 總共有兩個 module, boto.dynamodb, boto.dynamodb2 ，兩個版本的比較可以看&lt;a href=&quot;http://boto.readthedocs.org/en/latest/migrations/dynamodb_v1_to_v2.html&quot; target=&quot;_blank&quot;&gt;這裡&lt;/a&gt;，然後每個版本又分兩個 layer, layer1, layer2，layer2 是 high level api 而且並不是只用最高層最新版的就好了，新版的很多細部功能還沒支援，用底層 api 又會增加很多負擔，比如說像 ACID 更新一個 entity，支援度比較好的是 layer1.update_item，用 layer2 反而要自己建立 request json 內容，另外像是批次抓資料，目前是 boto.dynamodb 的支援度比 boto.dynamodb2 來的好，可以限制抓取特定的 property ，但是要用不同 library 又相似的功能相似的功能名稱造成程式不好閱讀，有時候看到一個 table object 卻不知道是哪一個版本的哪一層的 table 。</content><link rel='replies' type='application/atom+xml' href='http://sushiwens.blogspot.com/feeds/934399881949721213/comments/default' title='張貼留言'/><link rel='replies' type='text/html' href='http://sushiwens.blogspot.com/2014/02/dynamodb-python.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3638477104687038182/posts/default/934399881949721213'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3638477104687038182/posts/default/934399881949721213'/><link rel='alternate' type='text/html' href='http://sushiwens.blogspot.com/2014/02/dynamodb-python.html' title='DynamoDB python 支援現況'/><author><name>Unknown</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3638477104687038182.post-8981793919781806750</id><published>2014-02-07T14:40:00.000+08:00</published><updated>2014-02-07T14:40:07.451+08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Azure"/><category scheme="http://www.blogger.com/atom/ns#" term="Database"/><category scheme="http://www.blogger.com/atom/ns#" term="table"/><title type='text'>Azure Table Development round 1</title><content type='html'>在 Azure 開發相當的緩慢，除了趨近於零的 Community Support 和 python 相關的文件，許多錯誤標準回傳值也讓人摸不著頭緒，比如 Azure Table 很喜歡回傳類似這種 Error

&lt;script src=&quot;https://gist.github.com/flyakite/8858100.js&quot;&gt;&lt;/script&gt;

&lt;br&gt;
不清楚的文件加上這種 One of the request inputs is not valid. 模糊的錯誤訊息，簡直垂頭喪氣</content><link rel='replies' type='application/atom+xml' href='http://sushiwens.blogspot.com/feeds/8981793919781806750/comments/default' title='張貼留言'/><link rel='replies' type='text/html' href='http://sushiwens.blogspot.com/2014/02/azure-table-development-round-1.html#comment-form' title='0 個意見'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3638477104687038182/posts/default/8981793919781806750'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3638477104687038182/posts/default/8981793919781806750'/><link rel='alternate' type='text/html' href='http://sushiwens.blogspot.com/2014/02/azure-table-development-round-1.html' title='Azure Table Development round 1'/><author><name>Unknown</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry></feed>