Flex でダイナミックにフォームアイテムの並び順とサイズをカスタマイズ可能にしたデモ

遡ること二年前、http://d.hatena.ne.jp/tilfin/20070224/1172319344 というエントリを書きました。 これを強化して公開することにしました。 デモは http://hatena.tilfin.net/CustomizeForm/ です。 左上にフロートするように並べられているフォームです。カスタマイズモードにすると フォームのアイテムをドラッグアンドドロップで並び替えることが可能。 フォームアイテムの右端をドラッグして横幅サイズを変えることが可能。(サイズは最大横幅を 1 として 1/4, 1/3, 1/2, 2/3, 3/4, 1 のいずれか) が可能になります。 特に目的があって作ったわけではないのでコンポーネントとしてきっちり作りこんではいません。ソースはべた貼りしておきます。改変などはご自由にどうぞ。 以前は Flex 2 で今回は Flex 3 です。設計が微妙なのは除いておいて(わかっています)、「ここはもっと簡略して書けるよ!」的なコメントお待ちしています(用意されているプロパティを使っていないとか)。 ファイル構成 src/ net/tilfin/custom/ images/hresize.gif // 横幅変更カーソル DraggingRegion.as DynamicFormExtender.as FormItemsLayoutManager.as Main.mxml メイン MXML 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 <?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical" xmlns:custom="net.tilfin.custom.*"> <mx:Panel title="Demo / customize form items" x="0" y="0" width="100%" height="100%" cornerRadius="0"> <mx:VBox width="100%"> <mx:VBox width="100%"> <mx:HBox> <mx:Button id="buttonToggle" toggle="true" label="Customize Mode" click="dfe.customizeMode = buttonToggle.selected"/> </mx:HBox> </mx:VBox> <mx:Panel width="100%" height="100%" layout="absolute" title="Music Track Data"> <mx:Form id="form" width="100%" height="100%" backgroundColor="#f9fbfe"> <mx:VBox width="100%"> <mx:HBox width="100%"> <mx:FormItem label="Title" width="50%" labelWidth="80"> <mx:TextInput id="textTitle" text="GIFT" width="100%"></mx:TextInput> </mx:FormItem> <mx:FormItem label="Artist" width="50%" labelWidth="80"> <mx:TextInput id="textArtist" text="Mr.Children" width="100%"></mx:TextInput> </mx:FormItem> </mx:HBox> </mx:VBox> <mx:VBox width="100%"> <mx:HBox width="100%"> <mx:FormItem label="リリース日" width="50%" labelWidth="80"> <mx:DateField id="dateRelease" formatString="YYYY/MM/DD" selectedDate="{new Date(2008, 12, 10)}" width="100%"/> </mx:FormItem> <mx:FormItem label="Album" width="50%" labelWidth="80"> <mx:TextInput text="SUPERMARKET FANTASY" width="100%"></mx:TextInput> </mx:FormItem> </mx:HBox> </mx:VBox> <mx:VBox width="100%"> <mx:HBox width="100%"> <mx:FormItem label="Stars" width="33%" labelWidth="80"> <mx:TextInput text="☆☆☆☆☆" width="100%"></mx:TextInput> </mx:FormItem> <mx:FormItem label="Track" width="33%" labelWidth="80"> <mx:TextInput text="13" width="100%"></mx:TextInput> </mx:FormItem> <mx:FormItem label="Length" width="33%" labelWidth="80"> <mx:TextInput text="5:51" width="100%"></mx:TextInput> </mx:FormItem> </mx:HBox> </mx:VBox> <mx:VBox width="100%"> <mx:HBox width="100%"> <mx:FormItem label="Memo" width="100%" labelWidth="80"> <mx:TextInput text="TOY'S FACTORY Inc.(VAP)" width="100%"></mx:TextInput> </mx:FormItem> </mx:HBox> </mx:VBox> </mx:Form> <custom:DynamicFormExtender id="dfe" x="0" y="0" width="100%" height="100%" backgroundAlpha="0.12" backgroundColor="#222222" targetForm="{form}"> <custom:effectOnSelect> <mx:Fade duration="180" alphaFrom="0.0" alphaTo="1.0"/> </custom:effectOnSelect> <custom:effectOnDrop> <mx:Glow duration="720" alphaFrom="0.6" alphaTo="0.0" blurXFrom="30" blurXTo="15" blurYFrom="30" blurYTo="15" color="#ff22aa"/> </custom:effectOnDrop> </custom:DynamicFormExtender> </mx:Panel> </mx:VBox> </mx:Panel> </mx:Application> DraggingRegion package net.tilfin.custom { import mx.containers.Canvas; import mx.controls.Label; internal class DraggingRegion extends Canvas { private var labelSelectItemDisplay:Label; public function DraggingRegion() { labelSelectItemDisplay = new Label(); this.addChild(labelSelectItemDisplay); labelSelectItemDisplay.move(0, 0); this.width = 0; this.height = 0; } public function set textColor(color:String):void { labelSelectItemDisplay.setStyle("color", color); } public function setItemName(itemname:String):void { labelSelectItemDisplay.text = itemname; } } } DynamicFormExtender package net.tilfin.custom { import flash.display.*; import flash.events.*; import flash.geom.Point; import mx.containers.*; import mx.core.*; import mx.effects.*; import mx.events.FlexEvent; import mx.events.MoveEvent; import mx.events.ResizeEvent; /** \* 動的にフォームアイテムのレイアウトを変更可能にするアタッチメントクラス \* \* @author toshi */ \[Event(name="targetItemChanged", type="flash.events.Event")\] public class DynamicFormExtender extends Container { \[Embed("images/hresize.gif")\] private var hResizeCursor:Class; private var manager:FormItemsLayoutManager; private var _targetForm:Form; private var targetMaker:Canvas; private var dropLine:Canvas; private var draggingRegion:DraggingRegion; private var operation:Canvas; private var formInnerWidth:Number; private var offsetMouseX:int = 0; private var offsetMouseY:int = 0; private var draging:Boolean = false; private var downObject:DisplayObject = null; private var isClicked:Boolean = false; private var selectedItem:FormItem; private var dropAt:Number; private var selItemAt:Number; private var selecteffect:Effect; private var dropeffect:Effect; /** * コンストラクタ */ public function DynamicFormExtender() { this.visible = false; dropLine = new Canvas(); dropLine.width = 2; dropLine.height = 30; dropLine.setStyle("backgroundColor", "#ff1122"); dropLine.setStyle("backgroundAlpha", "0.8"); dropLine.visible = false; targetMaker = new Canvas(); targetMaker.setStyle("cornerRadius", "3"); targetMaker.setStyle("borderStyle", "solid"); targetMaker.setStyle("borderThickness", "1"); targetMaker.setStyle("borderColor", "#776699"); targetMaker.setStyle("backgroundColor", "#9988aa"); targetMaker.setStyle("backgroundAlpha", "0.3"); targetMaker.visible = false; draggingRegion = new DraggingRegion(); draggingRegion.setStyle("cornerRadius", 3); draggingRegion.setStyle("borderStyle", "solid"); draggingRegion.setStyle("backgroundAlpha", "0.5"); draggingRegion.setStyle("backgroundColor", "#e6c6fb"); draggingRegion.textColor = "#000066"; operation = new Canvas(); operation.addEventListener(MouseEvent.MOUSE\_DOWN, operation\_mouseDown); operation.addEventListener(MouseEvent.MOUSE\_MOVE, operation\_mouseMove); operation.addEventListener(MouseEvent.MOUSE\_UP, operation\_mouseUp); } /** \* 変更対象アイテムを選択したときに選択範囲を示すブロックに施すエフェクトをセット \* \* @param effect エフェクト */ public function set effectOnSelect(effect:Effect):void { selecteffect = effect; selecteffect.target = targetMaker; } /** \* アイテムの位置を変更したときに起こすエフェクトをセット \* \* @param effect エフェクト */ public function set effectOnDrop(effect:Effect):void { dropeffect = effect; } /** \* カスタマイズモードを切り替える。 \* \* @param value trueならオン */ public function set customizeMode(value:Boolean):void { this.visible = value; if (!value) { selectedItem = null; invisible(targetMaker); } } /** \* @return 変更対象のアイテムを取得できる */ public function get targetItem():DisplayObject { return selectedItem; } /** \* カスタマイズ可能にするフォームをセットする。 \* \* @param target 対象フォーム */ public function set targetForm(target:Form):void { _targetForm = target; _targetForm.addEventListener(FlexEvent.CREATION\_COMPLETE, customForm\_creationComplete); } // ターゲットフォーム初期化後のアイテムセットアップ private function customForm_creationComplete(event:FlexEvent):void { this.addChild(targetMaker); this.addChild(dropLine); this.addChild(draggingRegion); draggingRegion.visible = false; this.addChild(operation); this.move(\_targetForm.x, \_targetForm.y); operation.width = _targetForm.width - operation.x; operation.height = _targetForm.height - operation.y; operation.visible = true; _targetForm.addEventListener(ResizeEvent.RESIZE, \_targetForm\_resize); _targetForm.addEventListener(MoveEvent.MOVE, \_targetForm\_resize); manager = new FormItemsLayoutManager(_targetForm); calcFormInnerWidth(); } private function changeItemPercentWidth(percent:Number):void { invisible(targetMaker); manager.restructWithResizing(selectedItem, percent); dropeffect.target = selectedItem; dropeffect.play(); } private function calcFormInnerWidth():void { formInnerWidth = _targetForm.width - Number(_targetForm.getStyle("paddingLeft")) \- Number(_targetForm.getStyle("paddingRight")); } private function \_targetForm\_resize(e:ResizeEvent):void { this.width = _targetForm.width - _targetForm.x; this.height = _targetForm.height - _targetForm.y; operation.width = this.width; operation.height = this.height; calcFormInnerWidth(); } private function operation_mouseDown(e:MouseEvent):void { if (!e.buttonDown) return; var returnPt:Point = new Point(0, 0); downObject = getChildTargetItem(_targetForm, new Point(e.localX, e.localY), returnPt); if (downObject != null) { offsetMouseX = e.localX - returnPt.x; offsetMouseY = e.localY - returnPt.y; if (downObject != selectedItem) { setSelectedItem(downObject, returnPt); } else { isClicked = true; } } else if (!resizing) { draggingRegion.visible = false; targetMaker.visible = false; selectedItem = null; } } private var resizing:Boolean; private var perwid:Number; private function operation_mouseMove(e:MouseEvent):void { if (selectedItem == null) return; if (!e.buttonDown) { var xpos:Number = selectedItem.contentMouseX; if (xpos >= selectedItem.width - 4 && xpos <= selectedItem.width + 2) { cursorManager.setCursor(hResizeCursor, 4, -10); resizing = true; } else { cursorManager.removeAllCursors(); resizing = false; } return; } if (resizing) { // サイズ変更 var x:Number = _targetForm.contentMouseX - selectedItem.x; if (x < formInnerWidth * 0.29) { perwid = 25; targetMaker.width = formInnerWidth * 0.25; } else if (x < formInnerWidth * 0.42) { perwid = 33; targetMaker.width = formInnerWidth * 0.33; } else if (x < formInnerWidth * 0.58) { perwid = 50; targetMaker.width = formInnerWidth * 0.50; } else if (x < formInnerWidth * 0.71) { perwid = 67; targetMaker.width = formInnerWidth * 0.67; } else if (x < formInnerWidth * 0.87) { perwid = 75; targetMaker.width = formInnerWidth * 0.75; } else { perwid = 100; targetMaker.width = formInnerWidth; } return; } if (!draging) { // ドラッグ開始 draging = true; draggingRegion.width = selectedItem.width; draggingRegion.height = selectedItem.height; draggingRegion.visible = true; } else { var ret:Array; ret = findInsertTargetItem(_targetForm, new Point(e.stageX, e.stageY)); dropAt = ret\[0\]; if (ret\[1\] == 1) { dropLine.visible = true; } else if (selItemAt < dropAt) { dropLine.visible = (dropAt - 1 != selItemAt); } else { dropLine.visible = (dropAt != selItemAt); } } draggingRegion.move(e.localX - offsetMouseX, e.localY - offsetMouseY); } private function operation_mouseUp(e:MouseEvent):void { if (resizing) { changeItemPercentWidth(perwid); resizing = false; } else if (draging) { invisible(draggingRegion); dropLine.visible = false; draging = false; invisible(targetMaker); var perwid:Number = -1; // 段の間に挿入するときは幅をいっぱいにする if (dropLine.width == formInnerWidth) perwid = 100; if (manager.insertItemAt(selectedItem, dropAt, perwid)) { dropeffect.target = selectedItem; dropeffect.play(); } selectedItem = null; selItemAt = -1; dropAt = -1; } else if (isClicked) { invisible(targetMaker); selectedItem = null; } isClicked = false; downObject = null; } // 再帰的に ptの位置にある FormItem を探して返す。returnptにはフォームアイテムの相対的位置がセットされる。 private function getChildTargetItem(parent:Container, pt:Point, returnpt:Point):DisplayObject { for each (var element:DisplayObject in parent.getChildren()) { if (getObjectContains(element, pt)) { pt.x -= element.x; pt.y -= element.y; returnpt.x += element.x; returnpt.y += element.y; if (element is FormItem) { return element; } else { return getChildTargetItem(Container(element), pt, returnpt); } } } return null; } // StagePointの位置から FormItem の挿入位置を探す。 // ドロップマーカーの位置も同時にセット。 private function findInsertTargetItem(parent:Container, sPt:Point):Array { var zeroPt:Point = new Point(0, 0); var gFormPt:Point = _targetForm.contentToGlobal(zeroPt); var pre_item:FormItem = null; var x:Number; var y:Number; var i:Number = 0; for each (var item:FormItem in manager.itemList) { var gPt:Point = item.localToGlobal(zeroPt); if (sPt.y < gPt.y) { if (pre_item) { y = pre_item.localToGlobal(zeroPt).y; if (sPt.y < y + pre_item.height) { dropLine.move(pre_item.localToGlobal(zeroPt).x - gFormPt.x + pre_item.width, y - gFormPt.y); dropLine.setActualSize(2, pre_item.height + 2); return \[i, 0\]; } } dropLine.move(Number(_targetForm.getStyle("paddingLeft")), gPt.y - gFormPt.y - 3); dropLine.setActualSize(formInnerWidth, 2); return \[i, 1\]; } if (sPt.y <= gPt.y + item.height) { if (sPt.x <= gPt.x + item.width \* 0.5) { dropLine.move(gPt.x - gFormPt.x, gPt.y - gFormPt.y); dropLine.setActualSize(2, item.height + 2); return \[i, 0\]; } } pre_item = item; i++; } if (pre_item) { y = pre_item.localToGlobal(zeroPt).y; if (sPt.y < y + pre_item.height) { dropLine.move(pre_item.localToGlobal(zeroPt).x - gFormPt.x + pre_item.width, y - gFormPt.y); dropLine.setActualSize(2, pre_item.height + 2); return \[i, 0\]; } dropLine.move(Number(_targetForm.getStyle("paddingLeft")), gPt.y - gFormPt.y + pre_item.height + 2); } else { dropLine.move(0, 0); } dropLine.setActualSize(formInnerWidth, 2); return \[i, 1\]; } /** private function findInsertTargetItem(parent:Container, pt:Point, returnpt:Point):DisplayObject { for each (var element:DisplayObject in parent.getChildren()) { if (getObjectContains(element, pt)) { pt.x -= element.x; pt.y -= element.y; returnpt.x += element.x; returnpt.y += element.y; if (element is HBox) { return findInsertTargetItemFromHBox(Container(element), pt, returnpt); } else { return findInsertTargetItem(Container(element), pt, returnpt); } } } return null; } private function findInsertTargetItemFromHBox(hbox:Container, pt:Point, returnpt:Point):DisplayObject { var item:DisplayObject = null; for each (item in hbox.getChildren()) { if (pt.x <= item.x + item.width * 0.5) { returnpt.x += item.x; return FormItem(item); } } if (item == null) return null; returnpt.x += item.x + item.width; return FormItem(item); } */ private function getObjectContains(object:DisplayObject, pt:Point):Boolean { return (pt.x >= object.x && pt.x < object.x + object.width && pt.y >= object.y && pt.y < object.y + object.height); } private function setSelectedItem(target:DisplayObject, pt:Point):void { selectedItem = FormItem(target); selItemAt = manager.itemList.getItemIndex(selectedItem); draggingRegion.setItemName(selectedItem.label); targetMaker.move(pt.x, pt.y); targetMaker.width = target.width + 4; targetMaker.height = target.height + 4; targetMaker.visible = true; selecteffect.play(); dispatchEvent(new Event("targetItemChanged")); } private function invisible(object:DisplayObject):void { object.width = 0; object.height = 0; object.visible = false; } } } #### FormItemsLayoutManager package net.tilfin.custom { import flash.display.*; import flash.events.*; import mx.collections.ArrayCollection; import mx.containers.*; import mx.core.*; /** \* フォームアイテムの配置を管理するクラス \* \* @author toshi */ internal class FormItemsLayoutManager { private var form:Form; private var itemlist:ArrayCollection; /** \* コンストラクタ \* \* @param target 対象フォーム */ public function FormItemsLayoutManager(target:Form) { this.form = target; findItems(); } /** \* dest の位置に src を 挿入します。 \* \* @param src 移動するアイテム \* @param dest 挿入位置のアイテム。nullの場合は最後に追加。 */ public function insertItemAt(src:FormItem, destIndex:Number, perwid:Number = -1):Boolean { var obj:Object; var removedIdx:int = itemlist.getItemIndex(src); if (removedIdx < destIndex) destIndex--; if (perwid == -1 && removedIdx == destIndex) return false; if (destIndex < itemList.length) { obj = itemlist.removeItemAt(removedIdx); itemlist.addItemAt(obj, destIndex); } else { obj = itemlist.removeItemAt(removedIdx); itemlist.addItem(obj); } if (perwid != -1) { FormItem(obj).percentWidth = perwid; } restruct(); return true; } public function isMakerAfter(src:FormItem, dest:FormItem):Boolean { return (itemlist.getItemIndex(src) + 1 <= itemlist.getItemIndex(dest)); } public function get itemList():ArrayCollection { return itemlist; } /** \* フォームのアイテム配置を再構築します。 */ public function restruct():void { restructWithResizing(null, 0); } /** \* 幅を変更してフォーム内のアイテム配置を再構築します。 \* 段組みにするための HBox を自動的に組み入れます。 \* \* @param item 幅を変更するアイテム \* @param newwid 新しいPercentWidth */ public function restructWithResizing(item:FormItem, newwid:Number):void { form.removeAllChildren(); var len:Number = 0; var box:HBox = createBox(); for each (var fi:FormItem in itemlist) { var itempw:Number; if (fi == item) { itempw = newwid; } else { itempw = fi.percentWidth; } if (len + itempw > 100) { box = createBox(); len = 0; } box.addChild(fi); if (fi == item) { fi.percentWidth = itempw; } len += itempw; if (len >= 90) { len = 0; box = createBox(); } } } private function findItems():void { itemlist = new ArrayCollection(); findItemsRecusively(form); } private function findItemsRecusively(parent:Container):void { for each (var element:DisplayObject in parent.getChildren()) { if (element is FormItem) { itemlist.addItem(element); } else { findItemsRecusively(Container(element)); } } } private function createBox():HBox { var vbox:VBox = new VBox(); vbox.percentWidth = 100; var hbox:HBox = new HBox(); hbox.percentWidth = 100; form.addChild(vbox); vbox.addChild(hbox); vbox.visible = true; hbox.visible = true; return hbox; } } }

2009年2月11日 · Toshimitsu Takahashi

全検索結果に「損害を与える可能性」と表示してしまった Google のトラブルから見えるもの

タイトルは長いですが中身は短く行こうかと。 詳細は Google検索、世界で不具合 「人的ミス」で全URLに「コンピュータに損害を与える可能性」 - ITmedia ニュース を参考ください。 StopBadware という調査団体からもらった問題のあるサイトURLリストに「/」があったため、全ての検索結果に影響が出たらしい。 自分はリストの確認不足というのは StopBadware の問題だと思うので仕方のないことだと思う。 40分で障害が復旧したことを考えると、以下のように想像できる。 「損害を与える可能性」とする処理は、意外にも「データベースの検索処理後の結果に対して問題のあるサイトかどうかをチェックして画面に表示する」という実装っぽいことだ。 「問題のあるサイト」というデータは対象レコードに何かしらの属性を付加して保持しているものだと思っていた。(このように実装していたら、上記の時間ですべて更新して復旧するのはありえないだろう。)それが検索の後処理だったとは驚きだ。 もしかして Google のクロールデータのスキーマは創業当初からそれほど変わってないということなのだろうか?

2009年2月1日 · Toshimitsu Takahashi

Adobe AIR の SQL 機能を試してみるためにデモアプリを作ってみた

AIR には Flex(MXML/ActionScript) と Ajax(HTML/JavaScript) の二つの作成方法があるが、今回は後者を選んだ。 http://help.adobe.com/ja_JP/AIR/1.5/devappshtml/ http://www.adobe.com/jp/devnet/air/ajax/ 開発環境は Aptana Studio を使った。 デモアプリはテキストエリアに SQL 文を書いて実行すると、結果が表示されるというもの。SELECT であればテーブルに結果が表示される。テーブルの表示は、Adobe だけに Spry のデータセット機能を使ってみることにした。 Aptana Studio でプロジェクトを作成 File -> New.. -> Project -> Adobe AIR Project を選択する。 プロジェクトに「AirSQLiteTest」を設定する。 Import JavaScript Library までそのまま [Next] をクリック。 Adobe Spry 1.6 にチェックを入れて [Finish] をクリック。 ソースコード AirSQLiteTest.html が AIR 本体を構成する。ソースコードは以下。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 <html> <head> <title>AIR SQL Test</title> <script type="text/javascript" src="lib/air/AIRAliases.js"></script> <script type="text/javascript" src="lib/spry/includes/SpryData.js"></script> <style type="text/css"> div { margin:16px; } #textSql { width:100%; height:120px; } #textResult { width:100%; height:60px; } p { margin:4px 0; } table th, table td { border:1px solid gray; } th, td { padding:8px } th { cursor: default; } </style> <script type="text/javascript"> /** \* AIR SQL を操作する DBClient \* \* @param {String} dbname データベース名 \* @param {Function} closure クロージャ(省略可能) \* 指定された場合はクロージャ引数にこのオブジェクトが渡り、 \* 処理終了とともに接続が閉じられる。 */ function DBClient(dbname, closure){ if (closure) { var dbc = new DBClient(dbname); closure(dbc); dbc.close(); } else { this.open(dbname); } } DBClient.prototype = { /* \* データベースを開く \* \* @param {String} dbname データベース名 */ open: function(dbname){ try { this.conn = new air.SQLConnection(); var dbfile = null; if (dbname) { dbfile = air.File.applicationStorageDirectory.resolvePath(dbname + ".db"); } this.conn.open(dbfile); } catch (error) { statustext.innerText = "Error opening database"; air.trace("error.message:", error.message); air.trace("error.details:", error.details); return; } }, /* \* データベース接続を開じる */ close: function(){ if (this.conn) { this.conn.close(); this.conn = null; } }, /* \* SQL文を実行する \* \* @param {String} sqltext SQL文 \* @param {Function} cbsuccess クエリ成功時のコールバック関数 \* @param {Function} cberror クエリ失敗時のコールバック関数 */ execute: function(sqltext, cbsuccess, cberror){ try { var stmt = new air.SQLStatement(); stmt.sqlConnection = this.conn; stmt.text = sqltext; stmt.execute(); cbsuccess(stmt.getResult()); return true; } catch (error) { if (cberror) cberror(error); return false; } } } </script> </head> <body> <div> <div> <p>SQL Statement:</p> <textarea id="textSql">CREATE TABLE IF NOT EXISTS sample ( id INTEGER PRIMARY KEY AUTOINCREMENT, content TEXT, score INTEGER )</textarea> </div> <div> <button id="buttonExecute">execute SQL</button> </div> <div> <p>Query Result:</p> <textarea id="textResult"></textarea> </div> <div> <table spry:region="Results"> <caption>Select Result</caption> <thead> <tr> <th spry:sort="{ds_RowNumberPlus1}">#</th> <th spry:sort="id">id</th> <th spry:sort="content">content</th> <th spry:sort="score">score</th> </tr> </thead> <tbody spry:repeatchildren="Results"> <tr> <td>{ds_RowNumberPlus1}</td><td>{id}</td><td>{content}</td><td>{score}</td> </tr> </tbody> </table> </div> </div> <script type="text/javascript"><!-- function $(id) { return document.getElementById(id) }; var Results = new Spry.Data.DataSet(); $("buttonExecute").onclick = function(e){ // SQLを実行する DBClient("sampledb", function(dbh){ dbh.execute($("textSql").value, function(result){ // 成功 $("textResult").value = "QUERY OK, " + result.rowsAffected + " rows affected"; Results.data = result.data ? result.data : {}; Results.loadData(); }, function(error){ // 失敗 $("textResult").value = error.message + "\\n" + error.detailID + ": " + error.details; }); }); }; //--> </script> </body> </html> 試してみる プロジェクトを選択した状態で、Run -> Run… デフォルトで SQL 文には CREATE TABLE が入っている。 データをインサート。 SELECT でテーブルを表示。 ...

2009年1月30日 · Toshimitsu Takahashi

TrueCrypt で外付け USB ハードディスクを暗号化して使用する

フリーの TrueCrypt - Free Open-Source On-The-Fly Disk Encryption Software for Windows 7/Vista/XP, Mac OS X and Linux を用いてバックアップ用の USB HDD を作ってみた。 http://www.truecrypt.org/downloads.php からパッケージをダウンロードしてインストール。 暗号化ドライブの作成 TrueCrypt を起動して、[Create Volume] ボタンをクリック。 全体を暗号化するので、[Create a volume within a partition/drive] を選択して Next 。 さらに Standard Type を選び次へ、[Select Device] で接続済みの外付けHDDを選ぶ。 あとは使用するアルゴリズムやキーのハッシュアルゴリズムを選択して進める。 ロックのために選んだのはパスワードとキーファイルにした。 Large File を使用する (NTFS) を選んだ。 76GBのIDEハードディスクを非クィックフォーマットするのにだいたい1時間かかった。 マウント HDDを接続する、アクセスできないドライブがエクスプローラに表示される。 TrueCrypt を起動して、[Select Device] ボタンをクリック。 HDDパーティションを選択する。 [Mount] ボタンをクリック。 パスワードと先ほど作成したキーファイルを指定すると、もう一個ドライブがエクスプローラに現れる。 アンマウント TrueCrypt を起動して、Slot から選択、右クリック。 [Dismount] を選択する。 Windows で作成後、Mac でもマウントすることが正常に可能だった。今後はこれで重要なファイルはバックアップしてみることにする。 設定には英語版の説明を見ながら進めたが、 http://www.truecrypt.org/localizations.php に日本語パックも置いてある。

2009年1月30日 · Toshimitsu Takahashi

CentOS 5 に 9Arrows を導入したときのメモ

以前、何かハマって止めていました。もう一度試してみることにして、ようやく成功しました。 PostgreSQL をインストール 普段 MySQL ばかりだったが、9Arrows は Postgres を要求しているのでインストールします。 CentOS なので、例によって yum を使いました。postgres ユーザも作成されます。 devel も入れるのは、このあと pg_config を gem で pg を入れるときに必要になるためです。 # yum install postgresql.i386 # yum install postgresql-devel.i386 PostgreSQL の初期設定 postgres ユーザになって initdb そして postmaster を起動します。 # su - postgres $ initdb $ pg_ctl -D /var/lib/pgsql/data -l logfile start 9arrows_production データベースと Postgres 上に 9arrows ユーザーを作成しておきます。 さらに 9arrows_production データベース に 9arrows ユーザーをパスワード「9arrows」で割り当てます。 $ createdb 9arrows_production CREATE DATABASE $ createuser 9arrows Shall the new role be a superuser? (y/n) n Shall the new role be allowed to create databases? (y/n) n Shall the new role be allowed to create more new roles? (y/n) n CREATE ROLE $ psql 9arrows_production 9arrows_production=# ALTER USER “9arrows” with password ‘9arrows’; ALTER ROLE 9arrows_production=# \q <== [CTRL + D] ...

2009年1月24日 · Toshimitsu Takahashi

WebKit が UserAgent に like Gecko と付けている件

昨日の記事で Gecko のブラウザ判定を userAgent でしていましたが、WebKit が下記のようのユーザーエージェントをしている。 AppleWebKit/525.19 (KHTML, like Gecko) Chrome/1.0.154.43 Safari/525.19 AppleWebKit/525.27.1 (KHTML, like Gecko) Version/3.2.1 Safari/525.27.1" そのため、Safari や Google Chrome まで Gecko として判断してしまった。 userAgent.indexOf("Gecko/") != -1 とすることにした。 「like Gecko」をなんで付けているのか?ライセンス絡みの事情らしい。

2009年1月23日 · Toshimitsu Takahashi

jQuery 1.3.1 リリース

ちょうどタイムリーな話題がわかったので、書いておきます。 jQuery 1.3.1 Released | Official jQuery Blog 23個のバグフィックスリスト Error: Invalid Report Number – jQuery Core - Bug Tracker 1.3 系最初のリビジョンアップとあって、既に使っている方は早速置き換えた方がいいでしょう。

2009年1月22日 · Toshimitsu Takahashi

jQuery を用いて大きな画像などを Google Map ライクにスクロール表示する UI

下記のサンプルは jQuery プラグインとして公開しています。(2009/05/20 追記) jquery-scrollview - This jQuery plugin applies grab-and-drag scroll view to block elements. - Google Project Hosting 壁紙などの大きなサイズの画像を表示するのに Google マップのようなインターフェイスを提供したい。 Google マップのような、 マウスドラッグでスクロール ダブルクリックした所にセンタリング するような処理である。ただ、ズームに関しては実装しない。 ということで実装したサンプルは ScrollViewer Sample である。 ScrollViewer jQuery を必要とする。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 function ScrollViewer(){ this.initialize.apply(this, arguments); } ScrollViewer.prototype = { initialize: function(container){ // // Dragging Cursor Set. // // http://docs.jquery.com/Utilities/jQuery.browser // jQuery.browser Deprecated in jQuery 1.3 // if (navigator.userAgent.indexOf("Gecko/") != -1) { this.grab = "-moz-grab"; this.grabbing = "-moz-grabbing"; } else { this.grab = "default"; this.grabbing = "move"; } // Get container and image. this.m = $(container); this.i = this.m.children().css("cursor", this.grab); this.isgrabbing = false; // Set mouse events. var self = this; this.i.mousedown(function(e){ self.startgrab(); this.xp = e.pageX; this.yp = e.pageY; return false; }).mousemove(function(e){ if (!self.isgrabbing) return true; self.scrollTo(this.xp - e.pageX, this.yp - e.pageY); this.xp = e.pageX; this.yp = e.pageY; return false; }) .mouseout(function(){ self.stopgrab() }) .mouseup(function(){ self.stopgrab() }) .dblclick(function(){ var _m = self.m; var off = _m.offset(); var dx = this.xp - off.left - _m.width() / 2; if (dx < 0) { dx = "+=" + dx + "px"; } else { dx = "-=" + -dx + "px"; } var dy = this.yp - off.top - _m.height() / 2; if (dy < 0) { dy = "+=" + dy + "px"; } else { dy = "-=" + -dy + "px"; } _m.animate({ scrollLeft: dx, scrollTop: dy }, "normal", "swing"); }); this.centering(); }, centering: function(){ var _m = this.m; var w = this.i.width() - _m.width(); var h = this.i.height() - _m.height(); _m.scrollLeft(w / 2).scrollTop(h / 2); }, startgrab: function(){ this.isgrabbing = true; this.i.css("cursor", this.grabbing); }, stopgrab: function(){ this.isgrabbing = false; this.i.css("cursor", this.grab); }, scrollTo: function(dx, dy){ var _m = this.m; var x = _m.scrollLeft() + dx; var y = _m.scrollTop() + dy; _m.scrollLeft(x).scrollTop(y); } } ※ ダブルクリックでのセンタリング時はスムーズにスクロールする。 ※ Firefox(Gecko) はマウスカーソルに[握る]アイコンがあるため、そちらを割り当てている。なお、jQuery 1.3 から jQuery.browser は使用しない方がいいようだ。 ...

2009年1月22日 · Toshimitsu Takahashi

Hatenabarは結局リンクボタンしか使ってないからブックマークツールバーで十分な件

Hatenabar は Firefox のアドオンのツールバーです。 Firefoxでもっと便利に使おう - はてな はてなのサービスにワンクリックで簡単にアクセスできたりブックマークや日記での言及数を見ることができたりします。 でも最近気づいたんですが結局、 はてなブックマークとここはてなダイアリー、そしてたまにはてなカウンター を行き来するリンクボタンとしてしか使ってなかった。 だからブックマークツールバーに必要なものを突っ込めばいいんじゃないかと。 こんな風に… Hatenabar の機能のために Live HTTP Header でデバッグしているときにhatena.ne.jp サーバとのリクエスト・レスポンスが表示されてしまうのでその度にオフにしていたりもしました。 高さが減り表示領域が増えたので快適です。

2009年1月21日 · Toshimitsu Takahashi

JavaScript エンジン V8 を使ってライブラリの開発にユニットテストを利用したい

大きなプログラムになると JavaScript といえどもブラウザから操作してテストする以外にも、単体でコマンドラインからテストしたくなりますね。 JsUnit はどうなのか JavaScript のユニットテストフレームワークというと、xUnit の JsUnit が思い浮かびます。使ったことがなかったので、ちょっと調べてみました。 http://hisasann.com/housetect/2008/04/javascriptunitjsunit.html http://bobchin.ddo.jp/wiki/index.php?Javascript%2FJsUnit どうも JsUnit は html ファイルを開いてブラウザベースでテスト実行するみたいです。共同でテストに使えるサーバーコンポーネントもあるようですが、専用の HTTP サービスを立ち上げて使うようです。 やっぱり CUI で使えるインタプリタとかシェルベースでだと便利です。 V8 を使えないのか 以前 id:amachang さんの Google Chrome の JavaScript エンジン V8 を試す - IT戦記 をみて、V8 の shell を試しにコンパイルしていました。 v8/sample/shell.cc をさらっと見てみると、 // Bind the global ‘print’ function to the C++ Print callback. global->Set(v8::String::New(“print”), v8::FunctionTemplate::New(Print)); // Bind the global ’load’ function to the C++ Load callback. global->Set(v8::String::New(“load”), v8::FunctionTemplate::New(Load)); と print, load がネイティブ関数として組み込まれていることがわかります。 だから JavaScript での Ruby の irb みたいに使えるのです。 ...

2009年1月19日 · Toshimitsu Takahashi