コンポーネント

□未翻訳

□翻訳中

□翻訳完了(細田謙二)

■レビュー(Omi Chiba)

コンポーネント

コンポーネントは、ウェブページの機能的に自律した部品です。

A component is a functionally autonomous part of a web page.

コンポーネントは、モジュール、コントローラ、ビューから構成されることになります。しかし、ウェブページに埋め込まれたとき、HTMLのタグ(たとえば、DIV、SPAN、IFRAMEなど)のなかに局所化されなければならないこと、また、残りのページとは独立にタスクを実行しなければならないこと、以外は厳密な要求はありません。ここでは特に、ページ内でロードされ、Ajaxを介してコンポーネントのコントローラ関数と交信するコンポーネントに注目します。

A component may be composed of modules, controllers and views, but there is no strict requirement other than, when embedded in a web page, it must be localized within an html tag (for example a DIV, a SPAN, or an IFRAME) and it must perform its task independently of the rest of the page. We are specifically interested in components that are loaded in the page and communicate with the component controller function via Ajax.

コンポーネントの1つの例は、DIVの中に保持され、ユーザーのコメントと新規コメントをポストするフォームを表示する"コメント・コンポーネント"です。フォームがサブミットされるとき、フォームはAjaxを介してサーバーに送られ、リストが更新され、コメントはデータベースにおいてサーバーサイドに保存されます。DIVの中身は残りのページをリロードすることなく更新されます。

An example of a component is a "comments component" that is contained into a DIV and shows users' comments and a post-new-comment form. When the form is submitted, it is sent to the server via Ajax, the list is updated, and the comment is stored server-side in the database. The DIV content is refreshed without reloading the rest of the page.

web2pyのLOAD関数は、明示的なJavaScript/Ajaxの知識やプログラミンなしに、容易にそれを行うことを可能にします。

The web2py LOAD function makes this easy to do without explicit JavaScript/Ajax knowledge or programming.

ここでの目的は、コンポーネントをページレイアウトへと組み立てて、ウェブアプリケーションを開発できるようになることです。

Our goal is to be able to develop web applications by assembling components into page layouts.

デフォルトの雛形アプリを拡張子した"test"というweb2pyのアプリを考えます。これは、"models/db_comments.py"ファイルにおいて次のような独自モデルを持ちます:

Consider a simple web2py app "test" that extends the default scaffolding app with a custom model in file "models/db_comments.py":

1.

2.

3.

4.

5.

6.

db.define_table('comment',

Field('body','text',label='Your comment'),

Field('posted_on','datetime',default=request.now),

Field('posted_by',db.auth_user,default=auth.user_id))

db.comment.posted_on.writable=db.comment.posted_on.readable=False

db.comment.posted_by.writable=db.comment.posted_by.readable=False

"controllers/comments.py"における1つのアクションです

one action in "controllers/comments.py"

1.

2.

3.

4.

@auth.requires_login()

def post():

return dict(form=crud.create(db.comment),

comments=db(db.comment.id>0).select())

そして、対応する"views/comments/post.html"です

and the corresponding "views/comments/post.html"

1.

2.

3.

4.

5.

6.

7.

8.

{{extend 'layout.html'}}

{{for comment in comments:}}

<div class="comment">

on {{=comment.posted_on}} {{=comment.posted_by.first_name}}

says <span class="comment_body">{{=comment.body}}</span>

</div>

{{pass}}

{{=form}}

通常と同じようにアクセス可能です:

You can access it as usual at:

http://127.0.0.1:8000/test/comments/post

これまでは、このアクションにおいて特別なことは行っていません。しかし、レイアウトを拡張しない".load"拡張子の付いた新しいビューを定義することにより、これをコンポーネントに替えることができます。

So far there is nothing special in this action, but we can turn it into a component by defining a new view with extension ".load" that does not extend the layout.

そこで、"views/comments/post.load"を作成します:

Hence we create a "views/comments/post.load":

1.

2.

3.

4.

5.

6.

7.

8.

{{#extend 'layout.html' <- notice this is commented out!}}

{{for comment in comments:}}

<div class="comment">

on {{=comment.posted_on}} {{=comment.posted_by.first_name}}

says <span class="comment_body">{{=comment.body}}</span>

</div>

{{pass}}

{{=form}}

これには次のURLでアクセスできます

We can access it at the URL

http://127.0.0.1:8000/test/comments/post.load

これは次のように表示されます:

and it will look like this:

これは、単に次のようにするだけで任意のページに埋め込むことができるコンポーネントです。

This is a component that we can embed into any other page by simply doing

1.

{{=LOAD('comments','post.load',ajax=True)}}

たとえば"controllers/default.py"において次のように編集し、

For example in "controllers/default.py" we can edit

1.

2.

def index():

return dict()

対応するビューにおいて、そのコンポーネントを次のように加えます:

and in the corresponding view add the component:

1.

2.

3.

{{extend 'layout.html'}}

<p>{{='bla '*100}}</p>

{{=LOAD('comments','post.load',ajax=True)}}

このページに訪れると

Visiting the page

http://127.0.0.1:8000/test/default/index

通常のコンテンツとコメントのコンポーネントが表示されます:

will show the normal content and the comments component:

{{=LOAD(...)}}コンポーネントは次のようにレンダリングされます:

The {{=LOAD(...)}} component is rendered as follows:

1.

2.

3.

<script type="text/javascript"><!--

web2py_component("/test/comment/post.load","c282718984176")

//--></script><div id="c282718984176">loading...</div>

(実際に生成されるコードは、LOAD関数に渡されるオプションに依存します)。

(the actual generated code depends on the options passed to the LOAD function).

web2py_component(url,id)関数は "web2py_ajax.html"において定義されていて、すべての魔法を実行します。これはurlをAjaxを介して呼び出し、そのレスポンスを対応するidのあるDIVに埋め込みます。そして、すべてのフォームのサブミットをDIVに補足し、Ajaxを介してそれらフォームをサブミットします。Ajaxのターゲットは常にそのDIV自身です。

The web2py_component(url,id) function is defined in "web2py_ajax.html" and it performs all the magic: it calls the url via Ajax and embeds the response into the DIV with correspondingid; it traps every form submission into the DIV and submits those forms via Ajax. The Ajax target is always the DIV itself.

LOADヘルパーのすべての用法は以下の通りです:

The full signature of the LOAD helper is the following:

1.

2.

3.

4.

LOAD(c=None, f='index', args=[], vars={},

extension=None, target=None,

ajax=False, ajax_trap=False,

url=None):

ここでは:

Here:

  • 最初の2つの引数cとfはそれぞれ呼び出したいコントローラと関数です。

    • the first two arguments c and f are the controller and the function that we want to call respectively.

  • argsとvarsは関数に渡したい引数と変数です。前者はリスト、後者は辞書です。

  • args and vars are the arguments and variables that we want to pass to the function. The former is a list, the latter is a dictionary.

  • extensionは省略可能な拡張子です。なお、拡張子は、f='index.load'のように、関数の一部としても渡すことができます。

  • extension is an optional extension. Notice that the extension can also be passed as part of the function as in f='index.load'.

  • targetはターゲットとなるDIVのidです。指定されていない場合は、ランダムなターゲットidが生成されます。

  • target is the id id the target DIV. If it is not specified a random target id is generated.

  • ajaxは、DIVがAjaxを介して書き込まれる場合にTrueを設定します。現在のページが返される前に書きこむ必要がある場合はFalseにします(Ajax呼び出しをさけることになります)。

  • ajax should be set to True if the DIV has to be filled via Ajax and to False if the DIV has to be filled before the current page is returned (thus avoiding the Ajax call).

  • ajax_trap=Trueは、DIV内の任意のフォームのサブミットが補足されAjaxを介してサブミットされ、レスポンスがそのDIV内でレンダリングされることを意味します。ajax_trap=Falseは、フォームが通常どおりサブミットされ、したがって、ページ全体がリロードされるを示しています。ajax=Trueの場合は、ajax_trapは無視され、Trueとして想定されます。

  • ajax_trap=True means that any form submission in the DIV must be captured and submitted via Ajax, and the response must be rendered inside the DIV. ajax_trap=Falseindicates that forms must be submitted normally, thus reloading the entire page.ajax_trap is ignored and assumed to be True if ajax=True.

  • urlが、もし指定されているならば、c、f、args、vars、extensionの値を上書きし、urlにあるコンポーネントをロードします。他のアプリケーションによって供給されるコンポーネント・ページをロードするのに使用されます(web2pyによって作例されているかは問いません)。

  • url, if specified, overrides the values of c, f, args, vars, and extension and loads the component at the url. It is used to load as components pages served by other applications (which my or may not be created with web2py).

.loadビューが指定されていない場合には、アクションによって返される辞書をレイアウトなしにレンダリングするgeneric.loadがあります。これが最もよく機能するのは、単一のアイテムを保持する辞書の場合においてです。

If no .load view is specified, there is a generic.load that renders the dictionary returned by the action without layout. It works best if the dictionary contains a single item.

.load拡張子を持ち、他のアクション(たとえばログインフォーム)へリダイレクトするコントローラ関数を持つコンポーネントをLOADする場合、.load拡張子は継承され、新しいurl(リダイレクト先)もまた.load拡張子を持つようになります。

If you LOAD a component having the .load extension and the corresponding controller function redirects to another action (for example a login form), the .load extension propagates and the new url (the one to redirect too) is also loaded with a .load extension.

以下のことに留意してください:Ajaxのポストはマルチパートのフォーム、つまり、ファイルアップロードをサポートしていないことから、アップロードフィールドはLOADコンポーネントでは機能しません。個別の.loadビューからPOSTが行われた場合、アップロードフィールドは通常どおり機能するので、これが機能するかのようにだまされるかもしれません。代わりに、アップロードはajax互換のサードパーティ製ウィジェットとweb2pyの手動アップロードを行うstoreコマンドによって行われます。

*Please note:* Because Ajax post does not support multipart forms, i.e. file uploads, upload fields will not work with the LOAD component. You could be fooled into thinking it would work because upload fields will function normally if POST is done from the individual component's .load view. Instead, uploads are done with ajax-compatible 3rd-party widgets and web2py manual upload store commands.

クライアント-サーバー・コンポーネントの通信

Client-Server Component Communications

コンポーネントのアクションがAjaxを介して呼ばれるとき、web2pyはリクエストにおいて2つのHTTPヘッダを渡します:

When the action of a component is called via Ajax, web2py passes two HTTP headers with the request:

1.

2.

web2py-component-location

web2py-component-element

これは次の変数を介してアクションでアクセスできます:

which can be accessed by the action via the variables:

1.

2.

request.env.http_web2py_component_location

request.env.http_web2py_component_element

後者はまた次のようにアクセスできます:

The latter is also accessible via:

1.

前者は、コンポーネントのアクションを呼んだページのURLを保持します。後者はレスポンスを包含することになるDIVのidを保持します。

The former contains the URL of the page that called the component action. The latter contains the id of the DIV that will contain the response.

コンポーネントのアクションはまた、2つの特別なHTTPレスポンスのヘッダにおいてデータを保存します。これらは、レスポンス上の全体のページによって解釈されます。それは次の通りです:

The component action can also store data in two special HTTP response headers that will be interpreted by the full page upon response. They are:

1.

2.

web2py-component-flash

web2py-component-command

これらは、次のものを介して設定することができます:

and they can be set via:

1.

2.

response.headers['web2py-component-flash']='....'

response.headers['web2py-component-command']='...'

または、(アクションがコンポーネントによって呼び出されたなら)自動的に次のようにすることもできます:

or (if the action is called by a component) automatically via:

1.

2.

response.flash='...'

response.js='...'

前者はレスポンス上でフラッシュさせたいテキストを保持します。後者はレスポンス上で実行させたいJavaScriptを保持します。改行文字を含むことはできません。

The former contains text that you want to be flashed upon response. The latter contains JavaScript code that you want to be executed upon response. It cannot contain newlines.

例として、ユーザーが質問できるような連絡フォームを"controllers/contact/ask.py"に定義しましょう。コンポーネントは、システム管理者に質問をメールし、"thank you"メッセージをフラッシュし、ページからそのコンポーネントを取り除きます。

As an example, let's define a contact form component in "controllers/contact/ask.py" that allows the user to ask a question. The component will email the question to the system administrator, flash a "thank you" message, and remove the component from the page:

1.

2.

3.

4.

5.

6.

7.

8.

9.

10.

11.

12.

13.

def ask():

form=SQLFORM.factory(

Field('your_email',requires=IS_EMAIL()),

Field('question',requires=IS_NOT_EMPTY()))

if form.accepts(request.vars,session):

if mail.send(to='admin@example.com',

subject='from %s' % form.vars.your_email,

message = form.vars.question):

response.flash = 'Thank you'

response.js = "jQuery('#%s').hide()" % request.cid

else:

form.errors.your_email = "Unable to send the email"

return dict(form=form)

最初の4行はフォームを定義し、それを受理します。送信するためのメールオブジェクトはデフォルトの雛型アプリケーションにおいて定義されています。最後の4行は、HTTPリクエストヘッダからデータを取得し、HTTPリクエストヘッダを設定することによって、コンポーネント固有のロジックを実装します。

The first four lines define the form and accept it. The mail object used for sending is defined in the default scaffolding application. The last four lines implement all the component-specific logic by getting data from the HTTP request headers and setting the HTTP response headers.

これで、この連絡フォームを次のようにして任意のページに埋め込むことができるようになります

Now you can embed this contact form in any page via

1.

{{=LOAD('contact','ask.load',ajax=True)}}

このaskコンポーネントに対して.loadビューを定義しなかったことに気づいてください。なぜならこれは単一のオブジェクト(form)を返し、したがって、"generic.load"で十分なので、そうする必要がありません。

Notice that we did not define a .load view for our ask component. We do not have to because it returns a single object (form) and therefore the "generic.load" will do just fine.

捕捉されたAjaxリンク

Trapped Ajax Links

通常、リンクは捕捉されません。したがって、コンポーネント内のリンクをクリックすると、リンクしたページ全体がロードされます。時として、リンクしたページがコンポーネント内にロードされるようにしたい場合があります。これは次のようにAヘルパを用いて実現することができます:

Normally a link is not trapped, and by clicking in a link inside a component, the entire linked page is loaded. Sometimes you want the linked page to be loaded inside the component. This can be achieved using the A helper:

1.

{{=A('linked page',_href='http://example.com',cid=request.cid)}}

cidが指定される場合、リンクしたページはAjaxを介してロードされます。cidはロードしたページの中身を置くための場所であるhtml要素のidです。この例では、request.cid、すなわち、リンクを生成したコンポーネントのidを設定しています。リンクされたページは、URLコマンドを用いて生成された内部URLにすることが可能で、普通はそうします。

If cid is specified, the linked page is loaded via Ajax. The cid is the id of the html element where to place the loaded page content. In this case we set it to request.cid, i.e. the id of the component that generates the link. The linked page can be and usually is an internal URL generated using the URL command.

関数へのアクセスをコンポーネントのajax呼び出し以外からブロックすることができます。これは、次のようなcidを参照するデコレータを用います:

You can block access to a function except from a component ajax call by using a decorator that looks for the cid:

1.

@auth.requires(request.cid)