2012年9月9日日曜日

chrome extensionからのform submit


Chromeの拡張を使って、開いているWebページのformをsubmitしたくて色々調べていました。

結論からいうと、とりあえずできなかった。

やろうとしてたのはこんな検索formのwebサイト。

まず調査したこと。

Q1. Chrome拡張から表示しているサイトのjavascriptを実行できるのか?
A1. できない

残念。

Q2. Chrome拡張から表示しているサイトのDOMを操作できるのか?
A2. できる。

おお!

content scriptというのを使うとできるらしい。

content scriptでできることできないこと。

できること。
  • linkされていないURLを探し出し、hyperlinkに変換する
  • フォントサイズを大きくする
  • DOM操作


できないこと。
  • chrome.* APIの利用(chrome.extensionは利用できる)
  • 拡張で定義されたjavascript関数や変数の利用
  • Webページや他のcontent scriptで定義したjavascript関数や変数の利用

dom操作ができるということで、document.forms['fname'].submit();とかdocument.getElementByName('fname')とかでsubmit出来ないかと次いで調べてみた。

調べたところ、どうやら、そもそも<form>要素のなかで<input type="submit">を定義してしまうと、<form>の外からsubmitが出来ないようだ。

具体的なコードを示して説明する。
下記のようなコードだと、submitされなかった(/foo/barにページ遷移されなかった)
submit


しかし、次のように<input type="submit">をなくすと、きちんとsubmitされた(/foo/barにページ遷移された)
submit


操作したかったページには<input type="submit">が定義されているので、dom操作でsubmitするのは無理そう。

なんか他にないだろうか。











2012年7月9日月曜日

Address already in useとなったときの対処法[Mac/Eclipse/GAE]


Eclipseのアプリケーションサーバを停止できなくなったときの対処法。

EclipseでGAE開発とかしていると、アプリケーションサーバが異常終了するときがあります。

その際Eclipse上では停止しているように見えるけど、実際にはポートがlistenになっていて、新規に立ち上げることも停止することも出来なくなります。


そのような挙動になったときの対処法を備忘録として残しておきます。

使ってるポート番号が8080の場合で説明します。

1. ポートを利用しているプログラムのPIDを調べる

%>lsof -i:8080

するとこんな結果が返ってくる。

COMMAND  PID   USER   FD   TYPE             DEVICE SIZE/OFF NODE NAME
Python  1592 admin    7u  IPv4 ********      0t0  TCP localhost:http-alt (LISTEN)

PIDの所をみると1592と書いてあり、これによりプロセスID(PID)が1592とわかる。


2.プロセスを殺す

%>kill 1592

プロセスが死んだので、listen状態のポートも解放される。

これでEclipseからアプリケーションサーバが立ち上がるようになります。

2012年4月15日日曜日

フォロワーさんのTwitterクライアントを表示するアプリ

フォロワーさんのTwitterクライアントを表示するアプリを作ってみました。

ソースはここアプリはここ

Twitter4jとGoogle Apps EngineとjQuery Mobile使いました。

まず認証部分。
Twitter4Jを使って、ユーザにアプリを承認させます。
ユーザさんが承認してくれるとgetOAuthRequestTokenの引数で指定したコールバックに指定した画面へかえってくる。


 private void doOAuth(HttpServletRequest req, HttpServletResponse resp)
   throws IOException {

  Twitter twitter = (new TwitterFactory()).getInstance();

  try {

   RequestToken reqToken = twitter
   .getOAuthRequestToken(req.getRequestURL() + "?" + QUERY_MODE + "=" + QUERY_CALLBACK);
   HttpSession session = req.getSession();
   session.setAttribute(SESSION_REQUEST_TOKEN, reqToken);
   resp.sendRedirect(reqToken.getAuthenticationURL());
  } catch (TwitterException e) {
   Logger logger = Logger.getLogger(this.getClass().getName());
   logger.warning(e.getMessage());
  }

 }
そしてコールバックされてきたときの処理。

ここでさっきセッション中に保存したRequest TokenとTwitterから返されたVerifierを使ってAccess Tokenを取得しにいく。

 private void doCallBack(HttpServletRequest req, HttpServletResponse resp)
   throws IOException {

  HttpSession session = req.getSession();
  AccessToken accessToken = null;
  accessToken = (AccessToken)session.getAttribute(SESSION_ACCESS_TOKEN);

  // in case of already having access token
  if(accessToken != null){
   doWriteHTML(req, resp);
  }else{
   Twitter twitter = (new TwitterFactory()).getInstance();

   String verifier = req.getParameter(SESSION_AUTH_VERIFIER);

   try {
    accessToken = twitter.getOAuthAccessToken((RequestToken) session
      .getAttribute(SESSION_REQUEST_TOKEN), verifier);
   } catch (TwitterException e) {
    Logger logger = Logger.getLogger(this.getClass().getName());
    logger.warning(e.getMessage());
   }

   if (accessToken != null) {
    session.setAttribute(SESSION_ACCESS_TOKEN, accessToken);
    session.removeAttribute(SESSION_REQUEST_TOKEN);
    doWriteHTML(req, resp);

   } else {
    resp.setContentType("text/plain");
   }
  }
 }
無事取得できた後は画面表示のためのHTMLを動的に生成していく

まずフォロワーさん一覧を取得するためにgetFriendsIDs()を呼び出す。
その後そのフォロワーさんのステータスを取得するためにlookupUsers()を呼び出す。
Twitter利用者の使ってるクライアントソフト名はStatusクラスのsorceというプロパティに入ってるので、
User->Status->Sourceとたどっていくことで欲しい情報がとれます。
 private void writeBodyContentList(HttpServletRequest req, HttpServletResponse resp)throws IOException
 {
  HttpSession session = req.getSession();
  Twitter twitter = TwitterFactory.getSingleton();

  AccessToken token = (AccessToken) session
    .getAttribute(SESSION_ACCESS_TOKEN);
  if (token == null) {
   logger.warning("access token missing!");
  }

  twitter.setOAuthAccessToken(token);
  try {
   IDs ids = twitter.getFriendsIDs(-1);
   long[] friendIds = ids.getIDs();
   ResponseList userList = twitter.lookupUsers(friendIds);

   for (User user : userList) {
    String name = user.getName();
    URL imageURL = user.getProfileImageURL();

    resp.getWriter().print("<li>");
    resp.getWriter().print("<img src='http://");
    resp.getWriter().print(imageURL.getHost());
    resp.getWriter().print(imageURL.getPath());
    resp.getWriter().print("'/>");

    resp.getWriter().print(name);

    String source = user.getStatus().getSource();
    // composing a <a> tag, since when the source is "web", API return <a> tag.
    if ("web".equals(source)) {
     source = "<a href = http://twitter.com>web</a>";
    }
    resp.getWriter().println(source);
    resp.getWriter().println("</li>");
   }

  } catch (TwitterException e) {
   resp.getWriter().println(e);
  }
 }

2012年3月21日水曜日

Twitter Clientライクな可変長UITableViewCell

Twitterクライアントでよく見る可変長のUITableViewCellを作成してみました。(ソース)

ざっくりと調べたところ、UILabelを用いると簡単に実装できるみたいなんですが、これの問題点はhrefなどのリンクが使えないところ。

僕のよく使ってるechofonなんかは、aタグだけじゃなく@XXXXとかもリンクで飛べる実装にしているみたいなので、これも視野に入れて実装してみたいな、と。

で作ってみた。





実装は大まかに以下のよう。


1.tableView:heightForRowAtIndexPathにとりあえずの高さ100.0を返す。

2.tableView:cellForRowAtIndexPathでUITableViewCellを作成する。高さ1のUIWebViewを作成し、loadHTMLStringでテキストを読ませておく。

3.tableViewをreloadするためにタイマーをセット

4.再度、tableView:heightForRowAtIndexPathが呼ばれるのでUIWebView.scrollView.contentSize.heightによりテキスト表示に必要な高さを取得し、呼び出しもとに返す。

5.UIWebViewのdrawRectも呼ばれるので、高さを再調整。
ソースコードの概要を説明していきます。


詳細はgithubみてください。



まずはUITableViewController

@implementation VHTableViewController
{
// 省略
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{

    int row = [indexPath row];
    
    VHTableViewCell *cell = [cellDict objectForKey:[NSNumber numberWithInt:row]];
    if(cell == nil){
        cell = [[VHTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil index:[indexPath row]];
        // 一度作ったセルを覚えておく
        [cellDict setObject:cell forKey:[NSNumber numberWithInt:[indexPath row]]];
        // 3秒後にtableViewをリロードするようにタイマーをセット。
        // タイマーにしておくと、連続して再描画要求が来たときもOSがいい感じにはしょってくれてる。
        [self setTableReloadTimer];
    } 
    return cell;
}

-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{

    int row = [indexPath row];
    VHTableViewCell *cell = [cellDict objectForKey:[NSNumber numberWithInt:row]];
    if(cell == nil){// はじめに呼ばれた場合
        return 100.0;
    }
    // 二回目以降は必要な高さ
    return cell.webView.scrollView.contentSize.height;
    
}
// 省略
}
@end

んでUITableViewCell(のサブクラス)のコード 本当はUITableViewCellのサブクラス一個でこれを実現できると、使い回しが相当よかったんだけど、ちょっとやり方思いつかなかった。

高さ可変のセルなんか需要たくさんあるだろうから、apple様に標準提供していただけるとうれしいんだけど、作り的に難しいのかなー。