headタグに要素を追加できない。

JavaScriptでheadタグの中にscriptタグを追加しようとしたのですが、jQueryを使うとなぜか上手くいかないみたいです。


こんな感じでscript要素を作って

var script = document.createElement("script");
script.setAttribute("type", "text/javascript");
script.setAttribute("src", "unko.js");


jQueryのappendメソッドでheadタグに追加したのですが、なぜか追加されません。

$("head").append(script); // ダメ


jQueryを使わずに追加したら上手くいきました。

document.getElementsByTagName("head")[0].appendChild(script); // できた


なんでー?

実験したソース

<html>
<head>
</head>
<body>
  <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.js"></script>
  <script type="text/javascript">
    $(function(){
      // scriptを作る
      var make_script = function(url){
        var script = document.createElement("script");
        script.setAttribute("type", "text/javascript");
        script.setAttribute("src", url);
        return script;
      };
      var plain_js = make_script("plain.js");
      var jquery_js = make_script("jquery.js");

      $("head").append(jquery_js);
      document.getElementsByTagName("head")[0].appendChild(plain_js);

      var headHTML = document.getElementsByTagName("head")[0].innerHTML;
      puts(headHTML);

      function puts(str){
        var text = document.createTextNode(str);
        document.body.appendChild(text);
        var br = document.createElement("br");
        document.body.appendChild(br);
      }
    });
  </script>
</body>
</html>

追記

下のページを読んで謎がとけました。
jQueryのappend,prepend,before,afterメソッドにscriptタグを含んだDOM要素なり文字列なりを渡したときは、script以外の部分はちゃんと追加されますが、scriptタグの部分は追加されずに、スクリプトがその場で実行(eval)されてしまうのだそうです。

anything from here jQuery の挙動を解読する(32):jQuery.clean() メソッド解読──jQuery解読(49)

firebugで見てもheadタグに変化はないけど、scriptはちゃんと実行されているから心配しなくてもいいよ。ということみたいです。ほんとうにありがとうございました。

Greasemonkeyメモ

いつの間にかgreasemonkeyの中でjQueryとかのライブラリが使えるようになってました。
他にも色々と知らないことがあったのでメモしておきます。

@require

スクリプトの先頭に色々書くあそこに「@require」って書くと、外部JavaScriptをincludeして使うことができます。

例:
// ==UserScript==
// @name           test
// @include        http://localhost/*
// @require        http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js
// ==/UserScript==

(function($){
  $('body').click(function(){
    alert('クリックしました。');
  });
})(jQuery);
思ったこと

なぜか「$」が使えない。「jQuery」は使えるのに。なのでスクリプト全体を関数で囲んで「jQuery」を渡してあげるのがセオリーみたい。
ライブラリのURLは、 AJAX Libraries API - Google Codeにあるのを使うのが良いです。
@requireを使うスクリプトを作成する場合、右下のサルを右クリックして「新規ユーザースクリプト」で作るとうまくいきませんでした。「@require」って書かれた状態のスクリプトファイルをFirefoxにドラッグ&ドロップして、ユーザースクリプトのインストールをすることで、@requireのファイルがダウンロードされてローカルに保存されてスクリプトから利用できるようになるみたいです。私はこれを知らずに小一時間ほど頭を抱えることになったので要注意です。

@resouce

「@resouce」を使うと、スクリプトの中でCSSや画像ファイルを扱えるようになります。

// @resource name url

って指定して

GM_getResourceText('name');

って書くと、urlで指定したファイルのテキストデータが取得できます。
バイナリファイルの場合は

GM_getResourceURL('name');

って書くと、urlで指定したファイルの中身を示す「data:」形式のURLが取得できます。

例:
// ==UserScript==
// @name           test
// @include        http://localhost/*
// @resource style http://gist.github.com/raw/99181/9d511e468af600a93df53e17c0ab1de1e2f17a90/gistfile1.css
// @resource image http://img.f.hatena.ne.jp/images/fotolife/s/syttru/20080221/20080221011637.png
// ==/UserScript==

(function(){
  // スタイルシートを読み込む
  var style = GM_getResourceText('style'); // style -> ".test_style{ color ... }"
  GM_addStyle(style);
  
  // div要素を作ってスタイルシートを適用してbodyに追加する
  var div = document.createElement('div');
  div.setAttribute('class', 'test_style');
  div.innerHTML = 'そうなのかー';
  document.body.appendChild(div);
  
  // bodyの末尾に画像を追加する
  var img = document.createElement('img');
  var image = GM_getResourceURL('image'); // image -> "data:image/png;base64,iVBORw0K..."
  img.setAttribute('src', image);
  img.setAttribute('alt', 'ソーナンス');
  document.body.appendChild(img);
  
})();
思ったこと

@resourceで読み込むCSSや画像ファイルは「http://」か「https://」か「ftp://」形式のURLで指定しないといけません。つまりファイルを置いておく場所が必要だということです。
画像ファイルはFrickrとかはてなフォトライフとかの画像共有サービスを利用して、CSSとかのテキストファイルはgistとかGoogle CodeのプロジェクトホスティングとかAssemblaとかのソースコードホスティングサービスを使うのがいいんじゃないかなーと思いました。

Amazonの書籍ページにGoogleブックのリンクをはるぐりもん

会社の人からGoogleブック検索がいろんな意味ですごいっていうページを教えてもらい、「これはすごいなあ」と思い、勢いにまかせてAmazonの書籍ページにプレビューページへのリンクをはるGreaseMonkeyを作りました。

ダウンロード

google_books_on_amazon.user.js


FirefoxとGreaseMonkey0.8以上が必要です。

使い方

Googleブック検索でプレビューが読める本の場合、下のようにボタンが表示されます。
プレビューが読めない本だと何も起こりません。


ボタンをクリックするとGoogleブック検索のプレビューページに飛びます。

Google App Engine Javaで遊んでみる

Google App Engineが、Javaにも対応したというニュースを帰りの電車の中で見ました。

Google App Engine Blog: Seriously this time, the new language on App Engine: Java™

上の記事は英語なので何が書かれてるのかよくわかりませんが、Dukeが飛行機に乗ってたので間違いないです。よく見るとGWTの箱も持ってますね。

Getting Started: Java - Google App Engine - Google Code

Getting Startを見ながら遊んでみます。

開発環境をダウンロードする

Downloads - Google App Engine - Google Codeから開発環境をダウンロードして
適当なフォルダに解凍します。
Eclipseプラグインもあったのですが、インストールしようとしたらeclipseが固まってしまったので諦めました。残念です。

C:\work\download\appengine-java-sdk-1.2.0に解凍しました。

デモを動かしてみる

開発環境にデモがついてきました。これを動かしてみます。
コマンドラインから「bin/dev_appserver.cmd」を起動するとサーバーが動くみたいです。パラメータにwarフォルダのパスを渡します。

cd C:\work\download\appengine-java-sdk-1.2.0
bin\dev_appserver.cmd demos\guestbook\war
The server is running at http://localhost:8080/

おー!
動いたような気がする。

ブラウザでhttp://localhost:8080/を開いてみます。

おおおー!

挨拶を書き込むだけのアプリケーションですが、Googleアカウントでの認証ができたり、書き込んだデータがサーバーを再起動しても残ってたりとか、ちゃんとWebアプリケーションとして動いてるみたいです。すごい!

プロジェクトを作る

Creating a Project - Google App Engine - Google Code

Google App EngineServlet標準にのっとった作りになってるそうです。いつものwarプロジェクトと同じフォルダ構成にして、いくつかの独自設定ファイルを置いておけばいいよー。みたいな事が書いてる気がしました。英語が不自由なので確かなことは何一つわかりませんが、多分そんな感じでしょう。

チュートリアルでは「Guestbook」というプロジェクトを作りながら進むようです。
「ecilpseのプラグインを入れた人は簡単にできるよー」と書いてましたが、私はインストールに失敗したのでコツコツと手作りでがんばるしかありません。格差社会です。

「demos/new_project_template」というフォルダがあったので、それをコピーしてフォルダ名を「Guestbook」にしました。

フォルダの中身はこんな感じです。

Guestbook/
 +build.xml
 +COPYING
 +html/
 | +index.html
 +src/
   +log4j.properties
   +logging.properties
   +META-INF/
   | +jdoconfig.xml
   +WEB-INF/
   | +appengine-web.xml
   | +web.xml
   +org/
     +example/
       +HelloAppEngineServlet.java
  • Guestbook/src/META-INF/jdoconfig.xml
  • Guestbook/src/WEB-INF/appengine-web.xml

上の2つは見慣れないファイルですが、それ以外はいつもどおりです。あのファイルにGoogle App Engineの設定を色々と書いていくのでしょう。そういえばpython版にも似たような名前のファイルがあったような気がする。確かyamlだったような。。。
htmlフォルダの中に静的なファイルを入れてビルドするときに直下にコピーするって感じなのだと思います。

javaソースコードがありました。

  • Guestbook/src/org/example/HelloAppEngineServlet.java

中身を見てみます。

package org.example;

import java.io.IOException;
import javax.servlet.http.*;

public class HelloAppEngineServlet extends HttpServlet {
	public void doGet(HttpServletRequest req, HttpServletResponse resp)
			throws IOException {
		resp.setContentType("text/plain");
		resp.getWriter().println("Hello, world");
	}
}

普通のサーブレットクラスでした。

コンパイル

テンプレートフォルダの直下にbuild.xmlがあったので、antを実行してみます。

cd C:\work\download\appengine-java-sdk-1.2.0\workspace\guestbook
ant
Buildfile: build.xml

compile:
    [mkdir] Created dir: C:\work\download\appengine-java-sdk-1.2.0\workspace\guestbook\www\WEB-INF\classes
    [mkdir] Created dir: C:\work\download\appengine-java-sdk-1.2.0\workspace\guestbook\www\WEB-INF\lib
    [javac] Compiling 1 source file to C:\work\download\appengine-java-sdk-1.2.0\workspace\guestbook\www\WEB-INF\classes

enhance:
  [enhance] DataNucleus Enhancer (version 1.1.0) : Enhancement of classes
  [enhance]
  [enhance] DataNucleus Enhancer completed with success for 0 classes. Timings : input=16 ms, enhance=0 ms, total=16 ms. Consult the log for full details
  [enhance] DataNucleus Enhancer completed and no classes were enhanced. Consult the log for full details

war:
     [copy] Copying 1 file to C:\work\download\appengine-java-sdk-1.2.0\workspace\guestbook\www
     [copy] Copying 2 files to C:\work\download\appengine-java-sdk-1.2.0\workspace\guestbook\www\WEB-INF
  [enhance] DataNucleus Enhancer (version 1.1.0) : Enhancement of classes
  [enhance]
  [enhance] DataNucleus Enhancer completed with success for 0 classes. Timings : input=16 ms, enhance=0 ms, total=16 ms. Consult the log for full details
  [enhance] DataNucleus Enhancer completed and no classes were enhanced. Consult the log for full details

BUILD SUCCESSFUL
Total time: 1 second

おおお。成功したみたいです。(enhanceってなんだろう…)
wwwというフォルダができていて、その中にコンパイルされたクラスと設定ファイルとhtmlファイルがwar形式で収まっていました。

サーバー起動

antからサーバーの起動もできるみたいです。

ant runserver
runserver
Buildfile: build.xml

BUILD FAILED
Target "runserver" does not exist in the project "myproject".

Total time: 0 seconds

あれ…?
失敗してしまいました。


build.xmlの中を見たら確かに「runserver」というtargetはなくて、代わりに「dev_appserver」という名前のtargetがありました。こっちを使うみたいですね。

ant dev_appserver
Buildfile: build.xml

compile:

enhance:
  [enhance] DataNucleus Enhancer (version 1.1.0) : Enhancement of classes
  [enhance]
  [enhance] DataNucleus Enhancer completed with success for 0 classes. Timings : input=31 ms, enhance=0 ms, total=31 ms. Consult the log for full details
  [enhance] DataNucleus Enhancer completed and no classes were enhanced. Consult the log for full details

war:
  [enhance] DataNucleus Enhancer (version 1.1.0) : Enhancement of classes
  [enhance]
  [enhance] DataNucleus Enhancer completed with success for 0 classes. Timings : input=15 ms, enhance=0 ms, total=15 ms. Consult the log for full details
  [enhance] DataNucleus Enhancer completed and no classes were enhanced. Consult the log for full details

dev_appserver:
     [java] The server is running at http://localhost:8080/

無事に動きました。


ブラウザでhttp://localhost:8080/を開いてみたらこんな画面が表示されました。

ハローハロー!

Users Service

Using the Users Service - Google App Engine - Google Code
Google App EngineではUserServiceというクラスを使って、現在ログインしているGoogleアカウントの情報を使うことができるみたいです。

UserService userService = UserServiceFactory.getUserService();

UserServiceのインスタンスを取得して、

User user = userService.getCurrentUser(); 

現在のユーザーを取得。簡単ですね。


Javadoc

ログインユーザーのニックネームやEmailが取得できるようです。「AuthDomain」ってのはなんだろう…


試しにやってみます。

package org.example;

import java.io.IOException;
import javax.servlet.http.*;
import com.google.appengine.api.users.User;
import com.google.appengine.api.users.UserService;
import com.google.appengine.api.users.UserServiceFactory;

public class HelloAppEngineServlet extends HttpServlet {
    public void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws IOException {
        UserService userService = UserServiceFactory.getUserService();
        User user = userService.getCurrentUser();

        if (user != null) {
            resp.setContentType("text/plain");
            resp.getWriter().println("Hello, " + user.getNickname());
            resp.getWriter().println("  Email: " + user.getEmail());
            resp.getWriter().println("  AuthDomain: " + user.getAuthDomain());
        } else {
            resp.sendRedirect(userService.createLoginURL(req.getRequestURI()));
        }
    }
}
HTTP ERROR: 500

com/google/appengine/api/users/UserServiceFactory

RequestURI=/helloappengine
Caused by:

java.lang.NoClassDefFoundError: com/google/appengine/api/users/UserServiceFactory

ギャー!


Google App Engineが提供するクラスを使う場合は、src/WEB-INF/libフォルダにjarファイルを入れておかないといけないようです。
「lib/user/appengine-api-1.0-sdk-1.2.0.jar」をプロジェクトフォルダの「src/WEB-INF/lib」にコピーしたらちゃんと動きました。

http://localhost:8080/helloappengine

ログインページにリダイレクトされます。
パスワードを入れる欄がないのはローカルの開発環境だからなのかな?

ログインしたらニックネームとメールアドレスが表示されました。
ローカルなので上手くいってるのかどうかわかりませんが、きっと大丈夫でしょう。

アップロード

夜も遅くなってきましたが、せっかくなのでアップロードして公開するところまでやりたいです。
データストアは飛ばして、アプリケーションのアップロードをやってみます。
うまくいけば***.appspot.comのドメインで、自分の書いたJavaアプリケーションが世界中に公開されるわけです。
楽しみだー。


・・・

cd C:\work\download\appengine-java-sdk-1.2.0
bin\appcfg.cmd update workspace\java-helloworld\www
Reading application configuration data...
Beginning server interaction for java-helloworld...
0% Creating staging directory
5% Scanning for jsp files.
20% Scanning files on local disk.
25% Initiating update.
java.io.IOException: Error posting to URL: http://appengine.google.com/api/appversion/create?app_id=java-helloworld&version=1&
400 Bad Request
Invalid runtime specified.

Unable to upload app: Error posting to URL: http://appengine.google.com/api/appversion/create?app_id=java-helloworld&version=1&
400 Bad Request
Invalid runtime specified.

Please see the logs [C:\DOCUME~1\m_ishida\LOCALS~1\Temp\appcfg7220633167556510812.log] for further information.

ぎゃー!


「Invalid runtime specified」
Javaのランタイムが不正です」ってことなのかな。

うーん。わからない。残念だ。寝よう。

追記

他にもアップロードできない人がいました。

もう少し待てばできるようになるのかなー。

さらに追記

一晩寝て起きたらGoogleからメールが届いていて、全文英語で書かれていたので何がなんだかわからなかったのですが、試しにアップロードしてみたら成功しました!

http://java-helloworld.appspot.com/

やったー!
コメントとかブコメで色々と教えてくれた人たち、ありがとうございます。

scalaの練習問題やってみた

プログラミング言語 Scala Wiki - Scala練習問題

// x + y ただし「+」を使わない
def add(x: Int, y: Int): Int = y match {
  case 0 => x
  case _ => add(succ(x), pred(y))
}
 
// リストの合計
def sum(x: List[Int]): Int = x match {
  case x :: xs => add(x, sum(xs))
  case Nil => 0
}
 
// リストの長さ
def length[A](x: List[A]): Int = x match {
  case x :: xs => 1 + length(xs)
  case Nil => 0
}
 
// リストの要素全部に関数を適用したリストを返す
def map[A, B](x: List[A], f: A => B): List[B] = x match {
  case x :: xs => f(x) :: map(xs, f)
  case Nil => Nil
}
 
// リストから条件に当てはまるものだけ抜き出す
def filter[A](x: List[A], f: A => Boolean): List[A] = x match {
  case x :: xs => if(f(x)) x :: filter(xs, f) else filter(xs, f)
  case Nil => Nil
}
 
// リストとリストをガッチャンコする
def append[A](x: List[A], y: List[A]): List[A] = x match {
  case x :: xs => x :: append(xs, y)
  case Nil => y
}
 
// リストの中のリストを全部ガッチャンコする
def concat[A](x: List[List[A]]): List[A] = x match {
  case x :: xs => append(x, concat(xs))
  case Nil => Nil
}
 
// リストの要素全部に関数を適用して全部ガッチャンコする
def concatMap[A, B](x: List[A], f: A => List[B]): List[B] = concat(map(x, f))
 
// 一番でかいものを返す
def maximum(x: List[Int]): Int = x match {
  case x :: Nil => x
  case x :: y :: xs if(x > y) => maximum(x :: xs)
  case x :: y :: xs => maximum(y :: xs)
  case Nil => throw new IllegalArgumentException
}
 
// リストを逆順にする
def reverse[A](x: List[A]): List[A] = x match {
  case x :: xs => append(reverse(xs), x :: Nil)
  case Nil => Nil
}
 

難しかったー><


maximumだけどうしても分からなかったのでカンニングしてしまいました。
OCaml vs. Scala パターンマッチ:Rainy Day Codings:So-net blog
↑のページ「ガード条件」のところ

gistのlanguage一覧からscalaが消えた!

ソースコードを貼り付けると色を付けてくれて、ついでにソースコードリポジトリホスティングまでしてくれるgistというサービスがあります。はてな記法に見捨てられたscalaのコードも色付けしてくれる便利なサービスだったのですが、こないだ見たらlanguage一覧からscalaの名前が消えていました。



ない!
なんだよスクヘメって!



下の方にもない!!


ついこないだまで確かにあったのに…
残念です。