WebSurfer's Home

トップ > Blog 1   |   ログイン
APMLフィルター

textarea のキャレット位置に文字列を挿入

by WebSurfer 2012年1月9日 23:04

JavaScript を使って textarea のキャレット(カーソル)位置に文字列を挿入するサンプルです。

textarea のキャレット位置に文字列を挿入

挿入する文字列を入力するために jQuery UI の dialog を利用していますが、それ以外は JavaScript のみを使用しています。

Firefox などの場合は、選択された部分の先頭、末尾の index (整数型)を、それぞれ selectionStart、selectionEnd で取得できますが、IE の場合はそれに該当するプロパティがないのが問題です。

IE の場合は、document.selection プロパティで selection オブジェクト(textarea 要素そのものではなく、textarea 要素の中の選択された文字の部分)を取得し、さらに createRange メソッドを使って TextRange オブジェクトを作成し、それを操作することになります。

TextRange オブジェクトについては、MSDN ライブラリの TextRangeオブジェクトの使用 を参照してください。

そのサンプルコードは以下の通りです。上の画像は、このコードを実行したときのブラウザの画面です。実際に動かして試せるよう 実験室 にアップしましたので、興味のある方は試してみてください。

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <title>textarea のキャレット位置に文字列を挿入</title>
  <script src="Scripts/jquery-1.6.2.min.js" type="text/javascript"></script>
  <script src="Scripts/jquery-ui-1.8.16.custom.min.js" type="text/javascript"></script>
  <link href="css/smoothness/jquery-ui-1.8.16.custom.css" rel="stylesheet" type="text/css" />
  <script type="text/javascript">
  //<![CDATA[

    // IE の場合、[ダイアログ表示]ボタンクリックの時点で
    // textarea 内の選択された文字部分の TextRange オブジェ
    // クトを取得して維持しておく。そうしないと、textbox に
    // 入力中に textarea からフォーカスが外れ、選択範囲がリ
    // セットされてしまう。
    // Firefox 等は textarea からフォーカスが外れても、選択
    // 範囲は維持されるので、このような処置は不要。
    var textRange = null;

    function getTextRange(textarea) {
      // textarea の文字が選択されてない場合はフォーカスを
      // 当てないとうまくいかない。
      textarea.focus();

      // document.selection プロパティで selection オブジ
      // ェクト(testarea の中の選択された文字の部分)を取
      // 得する。文字が選択されていない場合、キャレット位
      // 置の空の selection オブジェクトになる。
      // selection オブジェクトに何か処理を行う場合は、
      // createRange メソッドで TextRange オブジェクトを作
      // 成して処理する。
      return document.selection.createRange();
    }

    function insertText(textarea, txt) {
      if (textRange == null) {
        // IE 以外の場合

        // 選択部分の先頭の index と長さを取得
        var index = textarea.selectionStart;
        var length = textarea.selectionEnd - index;

        // 文字列を挿入
        textarea.value = textarea.value.substr(0, index) +
          txt + textarea.value.substr(index + length);

        // キャレット位置を挿入した文字列の最後尾に移動
        textarea.focus();
        var newCaretPosition = index + txt.length;
        textarea.setSelectionRange(
          newCaretPosition, newCaretPosition);
      } else {
        // IE の場合

        // TextRange.text プロパティにダイアログの文字列を
        // 設定し、testarea の中の選択された文字部分を置き
        // 換える。
        textRange.text = txt;

        // キャレット位置を挿入した文字列の最後尾に移動
        textRange.select();
      }
    }

    // ---------------------------------------------------
    // 挿入する文字列の入力用に jQuery UI の dialog を利用
    $(function () {
      $("#dialog").dialog({
        bgiframe: true,
        autoOpen: false,
        modal: true,
        resizable: false,
        buttons: {
          'Insert': function () {
            insertText(
              $('#textarea').get(0),
              $('#textbox').val()
            );
            $(this).dialog('close');
          },
          'Cancel': function () {
            $(this).dialog('close');
          }
        },
        open: function () {
          $('#textbox').val('')
        }
      });
      $('#buttonShowDialog').click(function () {
        if (document.selection != undefined) {
          textRange = getTextRange($('#textarea').get(0));
        }
        $('#dialog').dialog('open');
      });
    });
  //]]>
  </script>
</head>
<body style="font-size: 12px;">
  <h1>キャレット位置に文字列を挿入</h1>
  <textarea id="textarea" cols="50" rows="5">textarea のキャレット位置に文字列を挿入します。</textarea><br />
  <button id="buttonShowDialog">ダイアログ表示</button>

  <div id="dialog" title="挿入する文字列">
    <p>ここに入力:<br />
    <input type="text" id="textbox" size="30" /></p>
  </div>
</body>
</html>

textarea の文字列が選択されている場合、その文字列を dialog に入力した文字列で置き換えます。文字列を選択していない場合(キャレットのみの場合)、キャレットの位置に dialog に入力した文字列を挿入します。

IE9, Firefox 9.0.1, Safari 5.1.2, Opera 11.60 で検証して期待通りに動くことを確認しました。(その他のブラウザは検証してないです)

Tags: ,

JavaScript

TableAdapterManager

by WebSurfer 2011年12月21日 22:19

Visual Studio には、型指定された DataSet + TableAdapter をウィザードベースで自動生成する機能があります。さらに、Visual Studio 2008 以降では、階層更新を実現する TableAdapterManager クラスが追加で自動生成されるようになりました。

今さらながらですが、TableAdapterManager について調べて、いろいろ発見があったので忘れないように書いておきます。

TableAdapterManager を利用したアプリケーション

階層更新とは、簡単に言うと、複数のテーブルで構成される DataSet が持つ更新データを、データベースの整合性に関する規則(参照整合性規則)を守って、データベースに挿入、更新、削除するプロセスのことです。(詳しい説明は MSDN ライブラリの 階層更新 を参照してください)

例として、上の画像のような、Northwind サンプルデータベースの Customers と Orders テーブル両方を同時に管理するアプリケーションを考えます。(Customers テーブルは CustomerID を主キーとして持ち、Orders テーブルは CustomerID を外部キーとして持っています。)

新しい顧客から注文があった場合は、先に新しい顧客レコードを Customers テーブルに Insert してから、注文レコードを Orders テーブルに Insert しなければなりません。

Customers テーブルから顧客レコードを Delete する場合は、先に Orders テーブルの当該顧客の注文レコードをすべて Delete してからにしなければなりません。

さらに、Insert、Update、Delete の順序も重要です。例えば、Customers テーブルの既存のレコードの主キー値を変更する場合、以下のような手順にしなければなりません。

  1. Customers テーブルに、新しい CustomerID 値で、新しいレコードを Insert
  2. Orders テーブルの当該レコードの CustomerID を新しい値に Update
  3. Customers テーブルの旧 CustomerID のレコードを Delete

従って、Insert => Update => Delete の順で行う必要があります(TableAdapterManager のデフォルトがこれ。他に、Update => Insert => Delete とすることも可能)。

Visual Studio 2005 以前のバージョンでは、上記のような参照整合性規則を守って更新を行う(即ち、階層更新を実現する)ためのコードを自力で実装する必要がありました。具体的には以下ようなコードを実装しなければなりません。

  1. まず、DataTable の Select メソッドを使用して特定の RowState(Added、ModifiedCurrent、Deleted)を持つ行だけを参照する DataRow 配列を取得します。(DataAdapter によるデータ ソースの更新 の「挿入、更新、削除の順序」のセクション参照)
  2. その後、各テーブルの TableAdapter の Update(DataRow[]) メソッドを適切な順序で呼び出し、各テーブルから RowState 別に取得した DataRow 配列を適切な順序で渡して処理します。
  3. 当然ながらトランザクション処理も必要です。

Visual Studio 2008 から新しく追加された TableAdapterManager クラスには、階層更新を実現するロジックを持つ UpdateAll メソッドが実装されています。MS-DTC を使わない手動トランザクション処理も実装されています。

実際に、MSDN ライブラリのチュートリアル SQL Server Express データベースの作成SQL Server Express データベース内のデータへの接続 (Windows フォーム) を参考に、型指定された DataSet + TableAdapter を作って、その中の TableAdapterManager のコードを見てみました。(もっと詳しいチュートリアルがありました。下の 2011/12/22 追記の「その1」を参照ください)

TableAdapterManager の UpdateAll メソッドは、Customers => Orders の順で両方のテーブルに対して Insert を行い、次に Customers => Orders の順で両方のテーブルに対して Update を行い、そして、最後に Orders => Customers の順で両方のテーブルに対して Delete を行うというように、上に述べた参照整合性規則を守って更新を実行するためのコードが実装されているのが確認できました。

TableAdapterManager は万能ではないと思いますが、多くのケースで、自力で階層更新を実現するためのコードを書く必要がなくなったのではないでしょうか。

自動生成されたコードで対応できない場合は、TableAdapterManager クラスは partial として定義されているので、自力でコードを書いてそれを拡張すれば、Visual Studio 2005 以前のときと比べて比較的簡単に対応可能と思います。

------------- 2011/12/22 追記 -------------

その1

Customers テーブルと Orders テーブルを使ったアプリケーションの作成は、上記のページより、10 行でズバリ !! 非接続型のデータ アクセス (ADO.NET) (C#) の方が詳しく書いてあって、参考になると思います。

その2

単一テーブルのアプリケーションにおいて、既存のレコードを Delete して、そのレコードと同じ主キーを持つレコードを新たに Insert する場合、Delete => Insert の順でないとうまくいきません。

TableAdapterManager は、Insert => Update => Delete(デフォルト)または Update => Insert => Delete のいずれかしか更新順序は選べませんので、上記のケースには対応できないということになります。

本文に述べた Customers テーブルの既存のレコードの主キー値を変更するケースより、上記のケースの方が多そうな気がするのですが、Microsoft は何故 Delete を最初にしなかったのでしょう? 気になります。(2015/11/12 追記: Delete => Insert などという乱暴なことを許すと、ユーザーのミスで問題が出る可能性が高くなるからではないかと最近思い初めています。Update すれば済む話ですから)

Tags:

ADO.NET

TableAdapter と Transaction

by WebSurfer 2011年12月18日 20:33

Visual Studio を利用してウィザードベースで自動生成する型付 DataSet + TableAdapter を使ってデータベースを更新する際、手動トランザクション処理するにはどうしたらいいでしょうか?(MS-DTC を使う自動トランザクションではなくて)

Visual Studio 2008 から TableAdapter に Transaction プロパティが追加されたので、それと、それ以前からある Connection プロパティを使えば、Visual Studio 2005 以前と比較して手動トランザクションが容易に設定できます。

でも、Visual Studio 2008 以降を使っているなら、そもそも手動トランザクションをかけるコードを自力で書く必要はなくて、自動生成される TableAdapterManager クラスの UpdateAll メソッドを使用すればいいです。

実は知らなかったのですが、TableAdapterManager.UpdateAll メソッドのコードをよく見てみると、そのメソッドの中でトランザクションがかかるようになってました。

もともと、TableAdapterManagerは 階層更新 を実現する ため(即ち、複数のテーブルを、データベース内の制約によって定義される一貫性規則を守って関連レコードの挿入、更新、および削除を制御するため)のものということらしいですが、単一テーブルの場合もトランザクションは有効に働きます。

という訳で、Visual Studio 2008 以降なら TableAdapterManager の UpdateAll メソッドを使うのが正解だと思います。

ちなみに、Visual Studio 2005 以前の場合は Transaction プロパティは自動生成されたコードには定義されません。TableAdapter は partial クラスとして定義されているので、自動生成されたファイルとは別に、Visual Studio 2008 以降で生成されるコードを参考に、自力で Transaction プロパティを定義して使うのがいいと思います。SqlClient の場合は以下のような感じです。

private System.Data.SqlClient.SqlTransaction _transaction;

internal System.Data.SqlClient.SqlTransaction Transaction {
  get {
    return this._transaction;
  }
  set {
    this._transaction = value;
    for (int i = 0; i < this.CommandCollection.Length; i++) {
      this.CommandCollection[i].Transaction = this._transaction;
    }
    if (((this.Adapter != null) 
        && (this.Adapter.DeleteCommand != null))) {
      this.Adapter.DeleteCommand.Transaction = this._transaction;
    }
    if (((this.Adapter != null) 
        && (this.Adapter.InsertCommand != null))) {
      this.Adapter.InsertCommand.Transaction = this._transaction;
    }
    if (((this.Adapter != null) 
        && (this.Adapter.UpdateCommand != null))) {
      this.Adapter.UpdateCommand.Transaction = this._transaction;
    }
  }
}

どうも、Visual Studio 2008 から TableAdapter に Transaction プロパティが追加された理由は、TableAdapterManager.UpdateAll メソッドで手動トランザクションをかけるためのようです。

Tags: ,

ADO.NET

About this blog

2010年5月にこのブログを立ち上げました。主に ASP.NET Web アプリ関係の記事です。

Calendar

<<  2024年5月  >>
2829301234
567891011
12131415161718
19202122232425
2627282930311
2345678

View posts in large calendar