AngularJS v1.5.xの始め方

はじめに

作業メモ。あとで清書予定。

必要なツールのインストール

以下のツールをインストールする

$ npm install -g yo grunt-cli gulp bower typings karma
$ npm install -g generator-angular
$ gem install compass

プロジェクト作成

$ mkdir ng001
$ cd ng001
$ yo angular ng001
     _-----_
    |       |    .--------------------------.
    |--(o)--|    |    Welcome to Yeoman,    |
   `---------´   |   ladies and gentlemen!  |
    ( _´U`_ )    '--------------------------'
    /___A___\
     |  ~  |
   __'.___.'__
 ´   `  |° ´ Y `

Out of the box I include Bootstrap and some AngularJS recommended modules.

? Would you like to use Gulp (experimental) instead of Grunt? No
? Would you like to use Sass (with Compass)? Yes
? Would you like to include Bootstrap? Yes
? Would you like to use the Sass version of Bootstrap? Yes
? Which modules would you like to include? angular-animate.js, angular-cookies.js, angular-resource.js, angular-route.js, angular-sanitize.js, angular-touch.js
   create app/styles/main.scss
   create app/index.html
   create bower.json
   create .bowerrc
   create package.json
   create Gruntfile.js
   create README.md
   invoke   angular:common:/Users/myomi/.anyenv/envs/ndenv/versions/v5.3.0/lib/node_modules/generator-angular/app/index.js
   create     .editorconfig
   create     .gitattributes
   create     .jscsrc
   create     .jshintrc
   create     .yo-rc.json
   create     .gitignore
   create     test/.jshintrc
   create     app/404.html
   create     app/favicon.ico
   create     app/robots.txt
   create     app/views/main.html
   create     app/images/yeoman.png
   invoke   angular:main:/Users/myomi/.anyenv/envs/ndenv/versions/v5.3.0/lib/node_modules/generator-angular/app/index.js
   create     app/scripts/app.js
   invoke   angular:controller:/Users/myomi/.anyenv/envs/ndenv/versions/v5.3.0/lib/node_modules/generator-angular/app/index.js
   create     app/scripts/controllers/main.js
   create     test/spec/controllers/main.js
Error angular ng001

You don't seem to have a generator with the name karma:app installed.
You can see available generators with npm search yeoman-generator and then install them with npm install [name].
To see the 53 registered generators run yo with the `--help` option.

yo angular --minsafe

ブログ記事では、yo angular 実行時に --minsafe オプションをつける記事がよく見受けられるが、このオプションはすでに削除されている。

参考

依存ライブラリの解決

$ npm install
$ bower install

VSCodeでインテリセンスを有効にする

プロジェクトルートにjsconfig.jsonを作成

{
    "compilerOptions": {
        "target": "ES5",
        "module": "commonjs"
    }
}
$ typings init
$ typings install --save --ambient node
$ typings install --save --ambient angular
$ typings install --save --ambient angular-cookie
$ typings install --save --ambient angular-animate

VS Code起動中なら再起動する。

動作確認

$ grunt serve

以下のエラーが出る場合がある。

Fatal error: Port 35729 is already in use by another process.

自分の環境ではVisual Studio Codeが利用しているポートと衝突した。

この場合は、Gruntfile.js のlivereload のポート番号を被らないように変更し、もう一度 grunt serve を実行する

    // The actual grunt server settings
    connect: {
      options: {
        port: 9000,
        // Change this to '0.0.0.0' to access the server from outside.
        hostname: 'localhost',
        livereload: 35729
      },

f:id:myomi:20160403164912p:plain

nibファイルを使わないテンプレートを作る(2)View-Based Application

(前の記事:nibファイルを使わないテンプレートを作る(1)Window-Based Application

今回は、iPhone用のView-Based Application をカスタマイズします。

テンプレート View-Based Application をコピーする

/Developer/Platforms/iPhoneOS.platform/Developer/Library/Xcode/Project Templates/Application

にある View-Based Application フォルダを

~/Library/Application Support/Developer/Shared/Xcode/Project Templates/Application/

にコピー。
フォルダ名を My View-Based Application に変更します。

main.m を編集する

以下の1行を編集します。

変更前
int retVal = UIApplicationMain(argc, argv, nil, nil);
変更後
int retVal = UIApplicationMain(argc, argv, nil, @"___PROJECTNAMEASIDENTIFIER___AppDelegate");

MainWindow.xib を削除する

削除しちゃいます。

Info.plist を編集する

Main nib file base name を削除します

___PROJECTNAMEASIDENTIFIER___AppDelegate.m を編集する

applicationDidFinishLaunching:メソッドを編集します。

変更前
- (void)applicationDidFinishLaunching:(UIApplication *)application {    

    // Override point for customization after application launch
    [window addSubview:viewController.view];
    [window makeKeyAndVisible];
}
変更後
- (void)applicationDidFinishLaunching:(UIApplication *)application {
    UIWindow *w = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    self.window = w;
    self.viewController = [[___PROJECTNAMEASIDENTIFIER___ViewController alloc] initWithNibName:nil bundle:nil];
    [window addSubview:viewController.view];
    [window makeKeyAndVisible];
    [w release];
}

___PROJECTNAMEASIDENTIFIER___ViewController.xib を削除する

削除しちゃいます。

___PROJECTNAMEASIDENTIFIER___ViewController.m を編集する

loadView メソッドを以下のように実装します。

- (void)loadView {
    UIView *contentView = [[UIView alloc] initWithFrame: [[UIScreen mainScreen] applicationFrame]];
    contentView.backgroundColor = [UIColor whiteColor];
    self.view = contentView;
    [contentView release];
}

完成

次はTab Bar Applicationをカスタマイズする予定。

nibファイルを使わないテンプレートを作る(1)Window-Based Application

Interface Builder を使いこなせません。もう使いたくありません。

そこで、Xcode付属のテンプレートをカスタマイズして、nibファイルを使わないテンプレートを作ることにしました。
(参考:d:id:griffin-stewie:20090315:p1

まずは、iPhone用のWindow-Based Application をカスタマイズします。

テンプレート Window-Based Application をコピーする

/Developer/Platforms/iPhoneOS.platform/Developer/Library/Xcode/Project Templates/Application

にある Window-Based Application フォルダを

~/Library/Application Support/Developer/Shared/Xcode/Project Templates/Application/

にコピー。
フォルダ名を My Window-Based Application に変更します。

main.m を編集する

以下の1行を編集します。

変更前
int retVal = UIApplicationMain(argc, argv, nil, nil);
変更後
int retVal = UIApplicationMain(argc, argv, nil, @"___PROJECTNAMEASIDENTIFIER___AppDelegate");

MainWindow.xib を削除する

削除しちゃいます。

Info.plist を編集する

Main nib file base name を削除します

___PROJECTNAMEASIDENTIFIER___AppDelegate.m を編集する

applicationDidFinishLaunching:メソッドを編集します。

変更前
- (void)applicationDidFinishLaunching:(UIApplication *)application {    

    // Override point for customization after application launch
    [window makeKeyAndVisible];
}
変更後
- (void)applicationDidFinishLaunching:(UIApplication *)application {
    UIWindow *w = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    self.window = w;
    [w release];
    [window makeKeyAndVisible];

}

完成

この調子でどんどん作るよ。

ant で class ファイルを dump するタスクを作った

package myomi.ant.taskdefs;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;

import org.apache.tools.ant.DirectoryScanner;
import org.apache.tools.ant.taskdefs.MatchingTask;
import org.apache.tools.ant.types.Path;

import sun.misc.CharacterEncoder;
import sun.misc.HexDumpEncoder;

public class Dump extends MatchingTask {
    
    private Path classDirs;
    private File destDir;

    public Dump() throws ClassNotFoundException, SecurityException, NoSuchMethodException {
        super();
    }

    public void setClassDir(Path classDir) {
        if (classDirs == null) {
            classDirs = classDir;
        } else {
            classDirs.append(classDir);
        }
    }
    
    public void setDestDir(File destDir) {
        this.destDir = destDir;
    }
    
    public void execute() {
        log("start dump task");
        try {
            for (String dir: classDirs.list()) {
                File f = getProject().resolveFile(dir);
                DirectoryScanner scanner = getDirectoryScanner(f);
                String[] classes = scanner.getIncludedFiles();
                for (String classPath: classes) {
                    javap(dir, classPath);                    
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        log("end dump task");
    }
    
    private void javap(String classDir, String classPath) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException, SecurityException, NoSuchMethodException, ClassNotFoundException, IOException {
        FileInputStream in = null;
        FileOutputStream out = null;
        try {
            // mkdir
            File tmp = new File(destDir.getAbsolutePath() + File.separator + classPath);
            File dir = tmp.getParentFile();
            if (!dir.exists()) dir.mkdirs();
            // dump
            in = new FileInputStream(classDir + File.separator + classPath);
            File file = new File(dir, tmp.getName().replaceAll(".class", ".dump"));
            out = new FileOutputStream(file);
            CharacterEncoder encoder = new HexDumpEncoder();
            encoder.encodeBuffer(in, out);
            log("execute dump: " + file.getAbsolutePath());
        } finally {
            try {
                if (in != null) in.close();
            } finally {
                if (out != null) out.close();
            }
        }
    }
}

使い方

<target name="dump">
  <mkdir dir="${dump.dir}"/>
  <taskdef name="dump" classname="myomi.ant.taskdefs.Dump" 
    classpath="${lib.dir}/myomiTask.jar"/>
  <dump classdir="${classes.dir}" destdir="${dump.dir}"/>
</target>

HexDumpEncoderの存在はここで知った。
http://homepage2.nifty.com/ann/Windows/tools/javap-HexDumpEncoder.htm


「ant で javap するタスクを作った」で作ったタスクと合わせて、これからいろいろと遊んでみます。

ant で javap するタスクを作った

こんなの。

package myomi.ant.taskdefs;

import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import org.apache.tools.ant.DirectoryScanner;
import org.apache.tools.ant.taskdefs.MatchingTask;
import org.apache.tools.ant.types.Path;

import sun.tools.javap.Main;

public class Javap extends MatchingTask {
    private Path clazz;
    private File destDir;

    public Javap() {
        super();
    }

    public void setClassDir(Path classDir) {
        if (clazz == null) {
            clazz = classDir;
        } else {
            clazz.append(classDir);
        }
    }
    
    public void setDestDir(File destDir) {
        this.destDir = destDir;
    }
    
    public void execute() {
        log("start javap task");
        try {
            for (String s: clazz.list()) {
                File f = getProject().resolveFile(s);
                DirectoryScanner scanner = getDirectoryScanner(f);
                String[] files = scanner.getIncludedFiles();
                javap(s, files);
            }
        } catch (SecurityException e) {
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        log("end javap task");
    }
    
    private void javap(String classDir, String[] pathes) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException, SecurityException, NoSuchMethodException, ClassNotFoundException, IOException {
        for (String path: pathes) {
            PrintWriter writer = null;
            try {
                // mkdir
                File tmp = new File(destDir.getAbsolutePath() + File.separator + path);
                File dir = tmp.getParentFile();
                if (!dir.exists()) dir.mkdirs();
                // javap
                Main main = null;
                File file = new File(dir, tmp.getName().replaceAll(".class", ".javap"));
                file.createNewFile();
                writer = new PrintWriter(file);
                main = new Main(writer);
                Class c = Class.forName("sun.tools.javap.Main");
                Method m = c.getDeclaredMethod("perform", new Class[]{Class.forName("[Ljava.lang.String;")});
                m.setAccessible(true);
                Object result = m.invoke(main, new Object[]{new String[]{"-verbose", "-classpath", classDir, path.replaceAll(".class", "")}});
                log("execute javap: " + file.getAbsolutePath());
            } finally {
                if (writer != null) writer.close();
            }
        }
    }
}

使い方。
(Javap.classを、myomiTask.jarに固めて使ってます)

  <target name="javap">
    <mkdir dir="javap"/>
    <taskdef name="javap" classname="myomi.ant.taskdefs.Javap" 
      classpath="myomiTask.jar"/>
    <javap classdir="classes" destdir="javap"/>
  </target>

ちょっと息抜き GaucheでXMLから内容を抽出する

XPathが使えますよ(SXPath)。

(use rfc.http)
(use sxml.ssax)
(use sxml.sxpath)
(let-values (((status head body) (http-get "zip.ricollab.jp" "/5340026")))
  (let ((xml (call-with-input-string body (cut ssax:xml->sxml <> '((xhtml . "http://www.w3.org/1999/xhtml"))))))
    ; //xhtml:dd[@class = 'address']
    ((sxpath '(// (xhtml:dd (@ (equal? (class "address")))))) xml)))
gosh> ((xhtml:dd (|@| (class "address")) (xhtml:a (|@| (href "/大阪府") (class "prefecture")) "大阪府") (xhtml:a (|@| (href "/大阪府/大阪市都島区") (class "city")) "大\
阪市都島区") (xhtml:a (|@| (href "/大阪府/大阪市都島区/網島町") (class "town")) "網島町")))

括弧で目が痛い。。。まだまだ研鑽が足りんのか。

ちょっと息抜き GaucheでXMLを読み込む

仕事では、XMLを扱うことが多いのです。『プログラミングGauche』を写経し始めたばかりで、まだまだへっぽこな私ですが、早速、日頃の業務に使ってみようかなぁと思っています。

そこで手習いとして、GaucheXMLを扱った処理をいくつか書いてみます。最初は読み込みから。手元に手ごろなXMLが無いので、ricollab 郵便番号検索を使って、適当な住所を検索します。

(use rfc.http)
(use sxml.ssax)
; 郵便番号534-0026を検索
(let-values (((status head body) (http-get "zip.ricollab.jp" "/5340026")))
  (call-with-input-string body (cut ssax:xml->sxml <> ())))
gosh> (*TOP* (http://www.w3.org/1999/xhtml:html (|@| (xml:lang "ja")) (http://www.w3.org/1999/xhtml:head (http://www.w3.org/1999/xhtml:title "〒534-0026")) (http://www\
.w3.org/1999/xhtml:body (http://www.w3.org/1999/xhtml:h1 "〒534-0026") (http://www.w3.org/1999/xhtml:dl (http://www.w3.org/1999/xhtml:dt "番号") (http://www.w3.org/199\
9/xhtml:dd (|@| (class "zipcode")) "5340026") (http://www.w3.org/1999/xhtml:dt "住所") (http://www.w3.org/1999/xhtml:dd (|@| (class "address")) (http://www.w3.org/1999\
/xhtml:a (|@| (href "/大阪府") (class "prefecture")) "大阪府") (http://www.w3.org/1999/xhtml:a (|@| (href "/大阪府/大阪市都島区") (class "city")) "大阪市都島区") (http\
://www.w3.org/1999/xhtml:a (|@| (href "/大阪府/大阪市都島区/網島町") (class "town")) "網島町")) (http://www.w3.org/1999/xhtml:dt "フリガナ") (http://www.w3.org/1999/xh\
tml:dd (|@| (class "yomi")) (http://www.w3.org/1999/xhtml:a (|@| (class "prefecture")) "オオサカフ") (http://www.w3.org/1999/xhtml:a (|@| (class "city")) "オオサカシミ\
ヤコジマク") (http://www.w3.org/1999/xhtml:a (|@| (class "town")) "アミジマチョウ"))))))

検索結果が取り出せました。続いてはXMLの加工です。

名前空間が。。。

出力結果をよく見ると、すべての要素に名前空間URIが付与されている。。。

解決した

手続き ssax:xml->sxml の引数で指定すればよい

(let-values (((status head body) (http-get "zip.ricollab.jp" "/5340026")))
  (call-with-input-string body (cut ssax:xml->sxml <> '((xhtml . "http://www.w3.org/1999/xhtml")))))
gosh> (*TOP* (|@@| (*NAMESPACES* (xhtml "http://www.w3.org/1999/xhtml"))) (xhtml:html (|@| (xml:lang "ja")) (xhtml:head (xhtml:title "〒534-0026")) (xhtml:body (xhtml:\
h1 "〒534-0026") (xhtml:dl (xhtml:dt "番号") (xhtml:dd (|@| (class "zipcode")) "5340026") (xhtml:dt "住所") (xhtml:dd (|@| (class "address")) (xhtml:a (|@| (href "/大\\
阪府") (class "prefecture")) "大阪府") (xhtml:a (|@| (href "/大阪府/大阪市都島区") (class "city")) "大阪市都島区") (xhtml:a (|@| (href "/大阪府/大阪市都島区/網島町") (\
class "town")) "網島町")) (xhtml:dt "フリガナ") (xhtml:dd (|@| (class "yomi")) (xhtml:a (|@| (class "prefecture")) "オオサカフ") (xhtml:a (|@| (class "city")) "オオサ\\
カシミヤコジマク") (xhtml:a (|@| (class "town")) "アミジマチョウ"))))))