アーキテクチャ:コンセプト
ここ最近、少しずつではありますが、より効率的にXPages開発するために「Founder」というフレームワークを開発しています。
OpenNTFにある「Extension Library」やTeamStudio様の「XContorls」などと同じように、
カスタムコントロール等で構成された部品群です。
このブログでは、この「Founder」フレームワークで使われているアーキテクチャを通して、下記の開発者向け視点で話をして行こうと思います。
【開発者レベル】
- Notesクライアント向けの開発経験がある
- XPagesの基本的知識がある
- WEB開発についてあまり知識がない
コンセプト
このフレームワークでは、XPages標準で欠けているであろう3つのポイントを克服する目的で考えられました。
1)操作性
XPagesアプリを開発するにあたって、クライアント動作しているDB*1をWEB向けに焼き直しするという話は少なくはありません。 簡単なアプリであれば、標準コントールを使って、ナビゲータやビューの一覧を表示させることは可能でしょう。
しかし、Notesクライアント自体があまりにも素晴らしい出来である為、Web化した時に操作性で劣化してしまう部分が多々あります。
例えば、
- クライアントでビュー表示をした場合、全対象文書が一覧で表示されるにも関わらず、XPagesの場合はページャーでページを切り替えなけれならない。
- XPagesでビューの文書を選択する際、1件1件チェックボックスでチェックしなければならない。
- リッチテキスト機能が劣化する。
などなど。
お金を掛けてWEB化した結果、機能そのままどころか、劣化してしまうことに何の意味があるのでしょうか。
すべてのDBがWeb化され、利用ユーザーのクライアント管理が不要となるのであれば効果があるかもしれませんが、
であれば、Notes Pluginや別のグループウェアに移行した方が良いのでは?と思ってしまいます。
FacebookなどのSNSをはじめ、世の中のWEBサイトは、ユーザーへのストレス軽減を操作性を考慮したアプリが溢れています。 操作性が劣化したアプリを提供していては、「Notesは古い」というイメージを脱却する事はできません。
IBM Champion 加藤様のブログにもありますが、せっかくWeb化するのであれば単純な焼き直しではなく、よりよいアプリを作ろうではありませんか。
IBM Champion 加藤様のブログ jp.teamstudio.com
2)トレンドのデザイン
Notesクライアント向けDBを開発している場合、どこまでデザインを重視されていますか?
クライアントで実現できるUI/UXは限られていいますし、社内向けのDB開発なのでデザインにに対して深く意識することは無かったりすると思います。
ですがWEBとなると、少し変わってくるのではないでしょうか?
ユーザーからすると「Notes」という感覚は無くなり、facebookやブログサイトのような1つのWEBサイトに捉えられます。
他のWEBベースでできたグループウェアと比較しても、昔作ったDBは見劣りしてしまいます。
ずっと、思っていました。Notesは、互換性があり過ぎなのです。互換性があり過ぎるせいで、DBがリニューアルされず、デザインが古く見えてしまう。 青色ベースの最新クライアントになったところで、各アプリは古いままなのです。
デザインには、流行り廃り、逆に操作しずらい、どのデザインが主流になっていくか読めない等あり、難しいところもありますが、Notes4.5時代に作ったような古臭いデザインのままよりは、少しでもこだわった方がよいのではないでしょうか。
XPagesでは、「One UI」というデザインフレームワークが 用意されており、設定だけでポップなデザインにすることができますが、そのデザインもちょっと古くなってきています。
では、どんなデザインがよいのでしょうか。 最近のデザインのトレンドは、SNSやGoogleサイト等を見ればだいたい分かってきます。 ちょっと前まではフレームに丸みを帯びたものが流行っていましたが、最近はフラットデザインが流行っています。
ちまたには、Bootstrap、Material Designをはじめ多くのWebアプリ向けのデザインフレームワークがあり、ガイドラインに沿ったタグ記述やClass定義をすることで、デザインセンスに自身がない人でもオシャレなページを実現できるようになっています。
ただし、XPages上でこのフレームワークを使うにはいろいろと厄介な点が出てきます。(この点は、今後の話で記述していきます。)
3)カスタマイズ性
下記は、私がメインで担当したQA9サイトの質疑・回答画面のXpages設計です。
<設計タブ>
<ソースタブ>
<div class="qaInfo"> <xp:panel styleClass="inlineB"> <span>カテゴリ:</span> <xp:link escape="true" id="category_link"> <xp:this.text><![CDATA[#{javascript:var doc =docQues.getDocument(); doc.getItemValueString("Category"); }]]></xp:this.text> <xp:eventHandler event="onclick" submit="true" refreshMode="complete"> <xp:this.action> <xp:actionGroup> <xp:executeScript> <xp:this.script><![CDATA[#{javascript:var doc =docQues.getDocument(); var Target = doc.getItemValueString("Category"); sessionScope.CategoryName = Target;}]]></xp:this.script> </xp:executeScript> <xp:openPage name="/top.xsp"></xp:openPage> </xp:actionGroup> </xp:this.action> </xp:eventHandler> </xp:link> <span class="separator"> | </span> <span>タグ:</span> <xc:cc_Taglist id="Ques_Tag"> <xc:this.TagField><![CDATA[#{javascript: var Tag =docQues.getItemValue("Tag"); return @Implode(Tag,",") }]]></xc:this.TagField> </xc:cc_Taglist> <span class="separator">  | </span> <span>質問日時:</span> <xp:text styleClass="value" escape="true" id="Ques_Created" value="#{docQues.Created}"> <xp:this.converter> <xp:convertDateTime type="both"> </xp:convertDateTime> </xp:this.converter> </xp:text> <xp:image url="/3096_32.png" id="image5" style="width:20px;height:20px"> <xp:this.rendered><![CDATA[#{javascript:var parentdoc = docQues.getDocument(); //質問者でなければ、非表示 if (Common.getCurrentUser().name!=parentdoc.getItemValueString("Author")){ return false; }else{ return true }}]]></xp:this.rendered> </xp:image> <xp:text escape="false" id="Editlink" style="text-decoration:underline;color:rgb(0,0,255)"> <xp:this.value><![CDATA[#{javascript:var doc =docQues.getDocument(); var editdocID = doc.getUniversalID(); return "<a href=\"x_QuestionCompose.xsp?docId=" + editdocID + "\">" + "編集する" + "</a>"}]]></xp:this.value> <xp:this.rendered><![CDATA[#{javascript:var parentdoc = docQues.getDocument(); //質問者でなければ、非表示 if (Common.getCurrentUser().name!=parentdoc.getItemValueString("Author")){ return false; }else{ return true }}]]></xp:this.rendered> </xp:text> <xp:link escape="true" id="Deletelink" text="削除" style="text-decoration:underline"> <xp:this.rendered><![CDATA[#{javascript:var parentdoc = docQues.getDocument(); //質問者でなければ、非表示 if (Common.getCurrentUser().name!=parentdoc.getItemValueString("Author")){ return false; }else{ return true }}]]></xp:this.rendered> <xp:image id="image7" url="/3159_32.png" style="height:20px;width:20px"> </xp:image> <xp:eventHandler event="onclick" submit="true" refreshMode="complete"> <xp:this.action> <xp:actionGroup> <xp:confirm message="この質問文書を削除してよろしいですか?"> </xp:confirm> <xp:executeScript> <xp:this.script><![CDATA[#{javascript:var targetdoc = docQues.getDocument(); //削除処理 qa_JsLib.DeleteDocument(targetdoc,1); }]]></xp:this.script> </xp:executeScript> <xp:openPage name="/top.xsp"></xp:openPage> </xp:actionGroup> </xp:this.action> </xp:eventHandler> </xp:link> <xp:table style="width:100%"> <xp:tr> <xp:td></xp:td> <xp:td style="text-align:left"> <xc:cc_UserProfile> <xc:this.userName><![CDATA[#{javascript:var Author = docQues.getDocument().getItemValueString("Author"); return @Name("[ABBREVIATE]",Author)}]]></xc:this.userName> </xc:cc_UserProfile> </xp:td> <xp:td> <xp:panel id="Bestpanel"> <xp:this.rendered><![CDATA[#{javascript:if (Common.getCurrentUser().name=="Anonymous") { return false; } else { return true; } }]]></xp:this.rendered> <xp:image url="/2925_64.png" id="image3" title="お気に入り登録する" styleClass="StartIcon"> <xp:this.rendered><![CDATA[#{javascript:var su = Common.getCurrentUser().name; var Favmem = docQues.getItemValue('User_Favorite'); if(@IsMember(su, Favmem) == @True()) { return false ; } else { return true ; }}]]></xp:this.rendered> <xp:eventHandler event="onclick" submit="true" refreshMode="partial" refreshId="Bestpanel" id="eventHandler1"> <xp:this.action><![CDATA[#{javascript:var targetdoc = docQues.getDocument(); //お気に入り!質問文書-ユーザー登録 qa_JsLib.SetFavoriteCountUD(1,targetdoc); //お気に入り!プロフィール-お気に入り先登録 qa_JsLib.SetPersonHistory(targetdoc,3); }]]></xp:this.action> </xp:eventHandler> </xp:image> <xp:image url="/3227.png" id="image4" styleClass="StartIcon" title="お気に入り解除する"> <xp:this.rendered><![CDATA[#{javascript:var su = Common.getCurrentUser().name; var Favmem = docQues.getItemValue('User_Favorite'); if(@IsMember(su, Favmem) == @True()) { return true ; } else { return false ; }}]]></xp:this.rendered> <xp:eventHandler event="onclick" submit="true" refreshMode="partial" refreshId="Bestpanel" id="eventHandler2"> <xp:this.action><![CDATA[#{javascript:var targetdoc = docQues.getDocument(); //お気に入り!質問文書-ユーザー解除 qa_JsLib.SetFavoriteCountUD(0,targetdoc); //お気に入り!プロフィール-お気に入り先解除 qa_JsLib.SetPersonHistory(targetdoc,4); }]]></xp:this.action> </xp:eventHandler> </xp:image> </xp:panel> </xp:td> <xp:td> <xc:c_SelectSnippet></xc:c_SelectSnippet> </xp:td> </xp:tr> </xp:table> </xp:panel> </div>
javascriptもXpagesも経験が浅い状態で作ったので非常に汚いソースでお恥ずかしい部分もありますが、ご了承ください。
まず、設計タブですが、繰り返しコントロールやパネルなど複数のコントロールが入り乱れており、非常に分かりづらい状態となりました。 どこにどういうコントロールを配置したのか分かりませんし、修正したいコントロールを選択しようとしてもなかなか選択できません。
こうなってくると、ソースタブで開発をしていく事になるのですが、こちらは、HTMLタグ、XPages独自タグ*2をはじめ、javascript(ボタン等の処理)やCSS(色や幅等のデザイン)が入り乱れています。 今回のソースでは、基本CSSを別のスタイルシートファイル上に記述するようにしていますが、NotesクライアントDB開発者はどう記述するでしょう? XPagesの各コントロールプロパティ上でもアクションイベントやスタイルを定義できるようになっているわけですから、おそらくこれまでの開発同様、プロパティ内にアクションを記述したり、色を付けたりすることでしょう。
結果、ソースタブには複数の言語が入り乱れ、ソースの管理が難しくなります。
そこで、それぞれのソースの記述を綺麗に分離させ、後から読みやすくカスタマイズ性を持たせる必要がでてきます。
ここに関しては、フレームワークと言うより開発ルールになってきますが、より効率よい開発をするにあたって重要な部分になります。
「操作性」、「デザイン性」、「カスタマイズ性」。
「Founder」フレームワークは、この3つをコンセプトにアーキテクチャを組みました。
次回は、アーキテクチャの概要と基本ルールについてまとめたいと思います。