WebSurfer's Home

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

window.opener を使って PostBack

by WebSurfer 2010年11月4日 21:47
window.opener を使って PostBack

JavaScript の window.opener を利用すると、例えばページ A から window.open でページ B を開いたとすると、ページ B からページ A にポストバックをかけることが可能です。

先の記事 ModalPopup で編集・更新操作 で書いた、「一覧画面にレコード一覧を表示してユーザーに選択させ、一覧画面は開いたまま別に編集画面を開いてそこでレコードを編集してもらい、編集画面の[更新]ボタンクリックで DB を更新すると共に編集画面は閉じて、更新結果を一覧画面に反映する」というシナリオに使えます。

それを知らなかったので、先の記事では「開いたままの一覧画面に、編集画面による更新結果を反映するのは難しいです(というよりほとんど無理と思います)。」と書きましたが、そんなことはなかったです。(汗)

以下に、備忘録として例を書いておきます。

検証用に、一覧表示のための GridView を持つ親ページ (RecordList.aspx) と、編集・更新のための DetailsView を持つ子ページ (EditAndUpdate.aspx) を用いてアプリを作ってみました。手順は以下のようになります。

  1. まず親ページを要求する。
  2. 親ページは、DB からデータを取得し、GirdView にレコード一覧を表示。
  3. GridView 上のレコードを選択すると、親ページは開いたまま別ウィンドウを開いて子ページを要求。
  4. 子ページは、選択されたレコードを DB から取得し DetailsView に Edit モードで表示。
  5. 子ページでレコードを編集し、ボタンクリックでポストバックして DB を更新。同時に、親ページにポストバックをかけるための JavaScript を追加する。
  6. 子ページが再描画された時、追加した JavaScript が働いて親ページにポストバックをかける。
  7. 親ページはサーバー側で更新後のレコードを取得し GridView に表示する。
  8. 子ページを自動的に閉じる。

コードは以下のとおりです。

RecordList.aspx(親ページ)

<%@ Page Language="C#" %>
<%@ import namespace="System.Data" %>

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

<script runat="server">
  protected void GridView1_RowDataBound(object sender, GridViewRowEventArgs e)
  {
    if (e.Row.RowType == DataControlRowType.DataRow)
    {
      LinkButton lb = (LinkButton)e.Row.FindControl("LinkButton1");
      DataRowView rowView = (DataRowView)e.Row.DataItem;
      string script = String.Format(
        "javascript:window.open('EditAndUpdate.aspx?customerId={0}', " +
        "null, 'width=400, height=300'); return false;", 
        rowView["CustomerID"]);
      lb.OnClientClick = script;
    }
  }

  protected void Page_Load(object sender, EventArgs e)
  {
      if (Page.IsPostBack)
      {
          GridView1.DataBind();
      }
  }
</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
  <title>一覧画面</title>
</head>
<body>
  <form id="form1" runat="server">
  <div>
    <h1>一覧画面</h1>
    <asp:SqlDataSource ID="SqlDataSource1" runat="server" 
      ConnectionString="<%$ ConnectionStrings:Northwind %>" 
      SelectCommand=
        "SELECT [CustomerID], [CompanyName], [ContactName], 
          [ContactTitle], [Phone] 
        FROM [Customers]">
    </asp:SqlDataSource>
    <asp:GridView ID="GridView1" 
      runat="server" 
      AutoGenerateColumns="False" 
      DataKeyNames="CustomerID" 
      DataSourceID="SqlDataSource1" 
      EnableModelValidation="True" 
      OnRowDataBound="GridView1_RowDataBound"
      AllowPaging="True">
      <Columns>
        <asp:BoundField DataField="CustomerID" 
          HeaderText="CustomerID" 
          ReadOnly="True" 
          SortExpression="CustomerID" />
        <asp:BoundField DataField="CompanyName" 
          HeaderText="CompanyName" 
          SortExpression="CompanyName" />
        <asp:BoundField DataField="ContactName" 
          HeaderText="ContactName" 
          SortExpression="ContactName" />
        <asp:BoundField DataField="ContactTitle" 
          HeaderText="ContactTitle" 
          SortExpression="ContactTitle" />
        <asp:BoundField DataField="Phone" 
          HeaderText="Phone" 
          SortExpression="Phone" />
        <asp:TemplateField ShowHeader="False">
          <ItemTemplate>
            <asp:LinkButton ID="LinkButton1" 
              runat="server" 
              CausesValidation="False" 
              CommandName="Select" 
              Text="編集">
            </asp:LinkButton>
          </ItemTemplate>
        </asp:TemplateField>
      </Columns>
    </asp:GridView>
  </div>
  </form>
</body>
</html>

EditAndUpdate.aspx(子ページ)

<%@ Page Language="C#" %>

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

<script runat="server">
  protected void Page_Load(object sender, EventArgs e)
  {
    string customerid = Request.QueryString["customerId"];
    if (!String.IsNullOrEmpty(customerid))
    {
      DetailsView1.DefaultMode = DetailsViewMode.Edit;
      Panel1.Visible = true;
      Panel2.Visible = false;
      Label1.Text = customerid;
    }
    else
    {
      Panel1.Visible = false;
      Panel2.Visible = true;
    }
  }

  protected void  LinkButton1_Click(object sender, EventArgs e)
  {
    DetailsView1.UpdateItem(false);
    string csname = "PostBackSourceWindowScript";
    Type cstype = this.GetType();
    ClientScriptManager cs = Page.ClientScript;
    if (!cs.IsStartupScriptRegistered(cstype, csname))
    {
      String cstext =
      @"window.onload = function() {
        if (!window.opener || window.opener.closed) {
          self.close();
        }
        else {
          window.opener.document.getElementById('form1').submit();
          self.close();
        }
      };";
      cs.RegisterStartupScript(cstype, csname, cstext, true);
    }
  }
</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
  <title>編集画面</title>
</head>
<body>
  <form id="form1" runat="server">
  <div>
    <asp:Panel ID="Panel1" runat="server">
      <h1>編集画面</h1>
      <span>
        選択された Customer ID: <asp:Label ID="Label1" runat="server" />
      </span>

      <asp:SqlDataSource ID="SqlDataSource2" runat="server" 
        ConnectionString="<%$ ConnectionStrings:Northwind %>" 
        SelectCommand=
          "SELECT [CustomerID], [CompanyName], [ContactName], 
             [ContactTitle], [Phone] 
          FROM [Customers] 
          WHERE ([CustomerID] = @CustomerID)" 
        UpdateCommand=
          "UPDATE [Customers] 
          SET [CompanyName]=@CompanyName, [ContactName]=@ContactName, 
            [ContactTitle]=@ContactTitle, [Phone]=@Phone 
          WHERE [CustomerID] = @CustomerID">
        <SelectParameters>
          <asp:ControlParameter ControlID="Label1" 
            Name="CustomerID" 
            PropertyName="Text" 
            Type="String" />
        </SelectParameters>
        <UpdateParameters>
          <asp:Parameter Name="CompanyName" Type="String" />
          <asp:Parameter Name="ContactName" Type="String" />
          <asp:Parameter Name="ContactTitle" Type="String" />
          <asp:Parameter Name="Phone" Type="String" />
          <asp:Parameter Name="CustomerID" Type="String" />
        </UpdateParameters>
      </asp:SqlDataSource>

      <asp:DetailsView ID="DetailsView1" 
        runat="server" 
        AutoGenerateRows="False" 
        DataKeyNames="CustomerID" 
        DataSourceID="SqlDataSource2" 
        EnableModelValidation="True">
        <Fields>
          <asp:BoundField DataField="CustomerID" 
            HeaderText="CustomerID" 
            ReadOnly="True" 
            SortExpression="CustomerID" />
          <asp:BoundField DataField="CompanyName" 
            HeaderText="CompanyName" 
            SortExpression="CompanyName" />
          <asp:BoundField DataField="ContactName" 
            HeaderText="ContactName" 
            SortExpression="ContactName" />
          <asp:BoundField DataField="ContactTitle" 
            HeaderText="ContactTitle" 
            SortExpression="ContactTitle" />
          <asp:BoundField DataField="Phone" 
            HeaderText="Phone" 
            SortExpression="Phone" />
        </Fields>
      </asp:DetailsView>
      <div>
        <asp:LinkButton ID="LinkButton1" 
          runat="server" 
          onclick="LinkButton1_Click" 
          Text="Save" />
        <asp:LinkButton ID="LinkButton2" 
          runat="server"
          CausesValidation="False" 
          Text="Cancel"
          OnClientClick="self.close(); return false;" />
      </div>
    </asp:Panel>
    <asp:Panel ID="Panel2" runat="server">
      <p>クエリ文字列 customerId が指定されていません。</p>
    </asp:Panel>
  </div>
  </form>
</body>
</html>

先の記事 ModalPopup で編集・更新操作 で紹介したアプリと比べると、一覧画面の[編集]ボタンの二度押し防止と、更新された行を GirdView 上でハイライトする操作が実装できていません。解決策がないか考えてみましたが、思いつきません。というわけで、先の記事の例の方がよさそうです。

Tags: ,

ASP.NET

SWFUpload を使ってみました

by WebSurfer 2010年11月3日 15:45
SWFUpload ライブラリを使ってみました

Flash を使って一度に複数ファイルをアップロードできる SWFUpload というライブラリを ASP.NET 環境下で試してみました。いろいろ分かったことがありますので、備忘録として Flash のクッキーがらみのバグなどの注意点を書いておきます。

複数ファイルを一度にアップロードしながらプログレスも表示できるという条件で、自分の期待を満たしそうなライブラリを探してみたところ、SWFUploadMultiple File Upload の 2 つが見つかりました。このうち、前者の SWFUpload を選んで試してみました。

なぜ SWFUpload の方を選んだかといえば、単に見た目が気に入ったからです。(笑) ただし、ASP.NET のコントロールのように Visual Studio の画面上でドラッグ&ドロップしてプロパティを設定するだけで使えるものではなく、ブラウザ側での操作にかなりの量の JavaScript のコードを書く必要があるのがちょっと重いです。でも、サンプルが提供されていますので、それをそのまま転用すればさほど面倒ではありません。

後者の Multiple File Upload の方は、ドキュメントを読んだ限りですが、ASP.NET 向けに特化されているようです。SWFUpload と同等な機能を ASP.NET のカスタムコントロールにラップして、ASP.NET に慣れたプログラマにとって使いやすくしているような感じです。

上の画像のアプリは、SWFUpload のサンプルの SimpleDemo と applicationdemo.net を合体して作ったものです。applicationdemo.net には、アップロードされた jpg ファイルからサーバー側のプログラムで Thumbnail を作って Session に保持し、クライアント側の JavaScript で動的に img タグを作ってそれに表示するコードが含まれています。

実際のアプリでは、Thumbnail を作って Session に保持する代わりに、サーバーのフォルダや DB に保存することになります。そのサンプルは提供されていませんが、従来の ASP.NET コントロールの FileUpload を使った場合と同等な処置となるので、ASP.NET アプリ開発経験者には簡単に実装できるはずです。

アプリの作り方を簡単に書いておきます。

ソリューションエクスプローラ
  1. Visual Studio で、新規に ASP.NET Web サイトプロジェクトを作ります。
  2. サンプルを Google のサイト からダウンロードします。現時点での最新版は SWFUpload v2.2.0.1 Samples.zip です。demos というフォルダの中に多数のサンプルが含まれています。
  3. demos\simpledemo フォルダの index.php が参照している .css, .js, .swf ファイルを使用しますので、適当なフォルダ(例: css, Scripts, Swfupload)をアプリケーションルート設けて、そこに参照されているファイルをコピーしてください。同名のファイルが複数含まれていますので、index.php が参照しているファイルのフォルダに注意してください。
  4. handlers.js に Thumbnail 書き込みのための JavaScript コードを追加します。上の手順でコピーした handlers.js を Visual Studio で開き、そこに demos\applicationdemo.net\js\handlers.js の addImage, fadeIn メソッドを丸ごとコピーします。さらに、uploadSuccess メソッドに addImage("thumbnail.aspx?id=" + serverData); を追加します。下の例を参考にしてください。
  5. アプリケーションルートに demos\applicationdemo.net フォルダの Global.asax, thumbnail.aspx, thumbnail.aspx.cs, upload.aspx, upload.aspx.cs をコピーしてください。
  6. アプリケーションルートに App_Code フォルダを作成し、demos\applicationdemo.net\App_Code フォルダの Thumbnail.cs をコピーしてください。
  7. アプリケーションルートに images フォルダを作成し、demos\images, demos\simpledemo\images, demos\applicationdemo.net\images フォルダの画像ファイルをコピーしてください。default.css を開いて、画像の url(3 ヶ所あります)を修正してください。
  8. Default.aspx, Default.aspx.cs, web.config は、ASP.NET の Web サイトプロジェクトを作った際に自動生成されたものに手を加えて使用します。ここまでで Web サイトプロジェクトの構成は上のソリューションエクスプローラの画像のようになっているはずです。
  9. 大きなファイルをアップロードする場合は、web.config に httpRuntime 要素の設定変更が必要です。設定例は、samples\asp.net フォルダの web.config にあります。加えて、IIS7 では applicationHost.config の requestFiltering 要素の設定も要注意です。
  10. 続いて、Deafult.aspx.cs の Page_Load イベントハンドラに Session.Clear(); を追加します。
  11. Deafult.aspx に index.php のコードを移植します。その際、 Flash のパラメータ flash_url, post_params を修正し、Thumbnail 画像を格納する div タグを追加します。ヘッダーのロゴのリンク先も適宜変更してください。下の例を参考にしてください。

handler.js(一部のみ)

function uploadSuccess(file, serverData) {
  try {
    // この行追加
    addImage("thumbnail.aspx?id=" + serverData);

    var progress = 
      new FileProgress(file, this.customSettings.progressTarget);
    progress.setComplete();
    progress.setStatus("Complete.");
    progress.toggleCancel(false);
  } catch (ex) {
    this.debug(ex);
  }
}

// 以下の function 2 つを追加
function addImage(src) {
  var newImg = document.createElement("img");
  newImg.style.margin = "5px";
  ・・・中略・・・
}

function fadeIn(element, opacity) {
  var reduceOpacityBy = 5;
  var rate = 30; // 15 fps
  ・・・中略・・・
}

Default.aspx.cs

public partial class _Default : System.Web.UI.Page 
{
  protected void Page_Load(object sender, EventArgs e)
  {
    // Clear the user's session
    Session.Clear();
  }
}

Default.aspx

<%@ Page Language="C#" 
  AutoEventWireup="true"  
  CodeFile="Default.aspx.cs" 
  Inherits="_Default" %>

<!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 runat="server">
  <title>SWFUpload Demos - Simple Demo</title>
  <link href="css/default.css" rel="stylesheet" type="text/css" />
  <script src="Swfupload/swfupload.js" type="text/javascript"></script>
  <script src="Scripts/swfupload.queue.js" type="text/javascript"></script>
  <script src="Scripts/fileprogress.js" type="text/javascript"></script>
  <script src="Scripts/handlers.js" type="text/javascript"></script>
  <script type="text/javascript">
  var swfu;

  window.onload = function() {
    var settings = {
      flash_url: "swfupload/swfupload.swf",

      // アップロードされたファイルをサーバーで保存するなどの
      // 処置(このサンプルでは Thumbnail の表示)を行うページ
      // を指定する。
      upload_url: "upload.aspx",

      // Flash のバグによるセッション切れ、Forms 認証切れ問題
      // の対応。セッションクッキーと Forms 認証クッキーを、そ
      // れぞれ ASPSESSID, AUTHID という名前でフォームに含めて
      // 送信する。Global.asax の BeginRequest ハンドラでそれら
      // を取得して、応答クッキーを書き換える。
      post_params: {
        "ASPSESSID": "<%=Session.SessionID %>",
        "AUTHID": "<%=Request.Cookies[FormsAuthentication.FormsCookieName] == null? String.Empty : Request.Cookies[FormsAuthentication.FormsCookieName].Value %>" 
      },
      file_size_limit: "100 MB",
      file_types: "*.*",
      file_types_description: "All Files",
      file_upload_limit: 100,
      file_queue_limit: 0,
      custom_settings: {
        progressTarget: "fsUploadProgress",
        cancelButtonId: "btnCancel"
      },
      debug: false,

      // Button settings
      button_image_url: "images/TestImageNoText_65x29.png",
      button_width: "65",
      button_height: "29",
      button_placeholder_id: "spanButtonPlaceHolder",
      button_text: '<span class="theFont">Hello</span>',
      button_text_style: ".theFont { font-size: 16; }",
      button_text_left_padding: 12,
      button_text_top_padding: 3,

      // The event handler functions are defined in handlers.js
      file_queued_handler: fileQueued,
      file_queue_error_handler: fileQueueError,
      file_dialog_complete_handler: fileDialogComplete,
      upload_start_handler: uploadStart,
      upload_progress_handler: uploadProgress,
      upload_error_handler: uploadError,
      upload_success_handler: uploadSuccess,
      upload_complete_handler: uploadComplete,

      // Queue plugin event
      queue_complete_handler: queueComplete
    };

    swfu = new SWFUpload(settings);
  };
  </script>
</head>
<body>
  <form id="form1" runat="server">
    <div id="header">
      <h1 id="logo"><a href="index.html">SWFUpload</a></h1>
      <div id="version">v2.2.0</div>
    </div>
    <div id="content">
      <h2>Simple Demo</h2>
      <p>This page demonstrates a simple usage of SWFUpload.  
         It uses the Queue Plugin to simplify uploading or 
         cancelling all queued files.</p>
      <div class="fieldset flash" id="fsUploadProgress">
        <span class="legend">Upload Queue</span>
      </div>
      <div id="divStatus">0 Files Uploaded</div>
      <div>
        <span id="spanButtonPlaceHolder"></span>
        <input id="btnCancel" 
          type="button" 
          value="Cancel All Uploads" 
          onclick="swfu.cancelQueue();" 
          disabled="disabled" 
          style="margin-left: 2px; font-size: 8pt; height: 29px;" />
        <%--Thumbnail 画像表示のため追加--%>
        <div id="thumbnails"></div>
      </div>   
    </div>
  </form>
</body>
</html>

以上で Web アプリは動くはずです。[Hello]ボタンをクリックするとファイルの選択ダイアログが開くので、jpg ファイルを選択して(複数選択可)、ダイアログの[開く(O)]ボタンをクリックすれば選択したファイルがアップロードされ、upload.aspx で処置されて、上の画像のような Thumbnail が表示されます。

問題の Flash のバグの話を以下に書きます。Firefox, Chrome, Safari など、IE 以外のブラウザを使用した場合は Flash がページを要求する際、クッキーが送られないという問題があります。

Flash が起動されている状態でも、HyperLink クリックでページを要求した場合や、ブラウザのアドレスバーに URL を打ち込んで要求する場合はクッキーは送られま���。問題は、あくまで Flash がページを要求する場合ということに注意してください。今回の例では、Flash が flash_url パラメータに設定されている upload.aspx を要求する場合がそれに該当します。

当然ながら、クッキーが送られないとセッションが維持できません。今回の例では、Session に Thumbnail 画像を保持しているので、要求にクッキーが含まれないとセッションが維持できず、アップロード後に画像の Thumbnail を表示するところがうまくいきません。

ファイルがアップロードされると、Flash はパラメータ upload_url で指定されている upload.aspx を自動的に要求しますが、このときクッキーは送信されません。それ故、サーバー側で正しく Session が取得できず、別の Session に Thumbnail を書き込んでしまいます。結果、Thumbnail 画像は表示されません。

回避策は、post_params に "ASPSESSID": "<%=Session.SessionID %>" を設定し(こうすることにより、ブラウザはフォーム変数にセッション ID を含めて送信します)、Global.asax の BeginRequest イベントのハンドラでフォーム変数からセッション ID を取得し、それでクッキーを書き換えてやることです。

いままで作成したコードに回避策が折り込み済みです。なので、Thumbnail は問題なく表示されます。Default.aspx の post_params の設定と、Global.asax の BeginRequest ハンドラを見てください。

Forms 認証を行う場合も、同様に、Flash のバグによる認証クッキー切れの問題があります。

例えば、Forms 認証を設定し、login.aspx ページを設け、ルートの web.config で deny user="?" として全ページ(upload.aspx を含め)匿名アクセス不可とした場合を考えます。ログインすれば Default.aspx は表示され、アップロードするファイルを選択するところまでは行きます。ところが、いざアップロードしようとすると HTTP 302 が返されてアップロードに失敗します。これは、Flash が upload.aspx を要求したとき、認証クッキーが送られてこないので、login.aspx にリダイレクトされてしまうからです。

この問題は、セッションクッキーの場合と同様な方法で、post_params で認証クッキーをフォーム変数に含めて送るように設定し、Global.asax の BeginRequest ハンドラで認証クッキーを書き換えることで回避できます。この回避策もいままで作成したコードに折り込み済みです。

Default.aspx の post_params の設定と、Global.asax の BeginRequest ハンドラを見てください。この対応で、Flash のバグによるセッションクッキーと Forms 認証クッキーが切れる問題は回避でき、ASP.NET 環境で SWFUpload を使えるようになるはずです。

なお、くどいようですが、Flash のバグによるクッキー切れの問題は、Firefox, Chrome, Safari など、IE 以外のブラウザを使用した場合のみですので注意してください。

Tags: , ,

Upload Download

ListView と DropDownList

by WebSurfer 2010年10月31日 19:32

ListView の InsertItemTemplate に DropDownList を配置し、DropDownList.SelectedValue を DB に挿入するにはどのようにすればよいかという話です。ただし、.NET 4 では問題なく、.NET 3.5 に限った話です。

何故かは不明ですが、InsertItemTemplate に DropDownList を配置した場合は、DropDownList の SelectedValue プロパティを以下のように設定してもうまくいきません。「Eval()、XPath()、および Bind() のようなデータバインド メソッドは、データバインドされたコントロールのコンテキストでのみ使用することができます。」というエラーになります。

<InsertItemTemplate>
  <tr style="">
  ・・・中略・・・
    <td>
      <asp:SqlDataSource ID="SqlDataSource1" ・・・
      ・・・中略・・・
      <asp:DropDownList ID="DropDownList1" 
        runat="server"
        DataSourceID="SqlDataSource1" 
        DataTextField="CategoryName" 
        DataValueField="CategoryName"
        AppendDataBoundItems="True"
        SelectedValue='<%# Bind("memo") %>'>
        <asp:ListItem Value="">空白</asp:ListItem>
      </asp:DropDownList>
    </td>
  </tr>
</InsertItemTemplate>

なお、上記と同様な形で EditItemTemplate に配置した場合は問題なく、DB は SelectedValue でちゃんと更新されます。

この問題の回避策は、以下のように SqlDataSource.Inserting イベントハンドラで処置することです。

protected void SqlDataSource1_Inserting(object sender, 
    SqlDataSourceCommandEventArgs e)
{
  DropDownList ddl = 
    (DropDownList)ListView1.InsertItem.FindControl("DropDownList1");
  e.Command.Parameters["@memo"].Value = ddl.SelectedValue;
}

何故、InsertItemTemplate に DropDownList を配置した場合ダメなのかが分からなかったし、もっとスマートな解決策があるのではないか(ひょっとして、とんでもなく間抜けなのことしているのではないか)ということが気になっていましたが、どうやら .NET 3.5 のバグらしいということで、安心(?)しています。(笑)

Tags: ,

ASP.NET

About this blog

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

Calendar

<<  2024年4月  >>
31123456
78910111213
14151617181920
21222324252627
2829301234
567891011

View posts in large calendar