MVC 3と同様- Ajax.BeginForm は完全なポスト バックを行いますが、jquery.unobtrusive-ajax.min.js への参照がページに存在することを確認しました。さらに、IE 9 では問題なく動作しますが、Firefox と Chrome では、div をその部分ビューのコンテンツに置き換えるのではなく、部分ビューだけを完全な Web ページとして取得します。
私はまだMVC3にかなり慣れていないので、簡単なものだと確信しています。
あらすじ:
私のページ内には、ユーザーがリンクをクリックすると表示されるいくつかの異なる .cshtml ファイルがあります。EquipmentData.cshtml ファイル内にはドロップダウンがあり、変更するとコントローラーへの ajax ポストバックを実行して、エディター テンプレートを div に読み込みます。
そのエディター テンプレート (StorageTankData.cshtml) 内には、データのリストがあり、1 つの項目 (StorageTank.cshtml) の編集テンプレートのプレースホルダーがあります。StorageTank.cshtml は、新しいもの (ID がゼロ) を挿入するために初期化されます。ユーザーがデータ リスト内のアイテムの 1 つをクリックすると、コントローラーを再度呼び出して、その特定のアイテムの StorageTank.cshtml 部分ビューを取得し、その html を edit div に置き換えます。
そのすべてがうまく機能します。StorageTank.cshtml テンプレートには、Ajax.Begin フォームがあります。このフォームは、ストレージ タンク データを保存するために別のコントローラー アクションに投稿し、同じコントローラー メソッドを呼び出して StorageTankData.cshtml 部分ビューを取得します。アイデアは、アイテムのリストを更新し、編集フォームを挿入モードにリセットして、それを div に置き換えるというものです。
これは IE では正常に機能しますが、Chrome と Firefox では、ページ全体ではなく、ページの部分的なビューのみが表示され、置換が行われます。保存ポストバックが、ページ全体を生成するものとは異なるコントローラー メソッドに移動するためだと思いますが、それについてどうすればよいかわかりません。
IE で見たのは、URL が "/Edit" (ページ全体を制御するアクション) のままで、置換がうまく機能していることです。Chrome と FF では、代わりに URL が「/SaveStorageTank」アクションに変わります。それで、これらのブラウザが完了時に部分的なビューだけを表示する理由を説明していると思います。私が理解できないのは、彼らが先に進んで交換を行わない理由です。
再表示するまで、Firebug または Chrome のいずれにもエラーは表示されません。(もちろん、その部分ビューにはjqueryファイルがロードされていないため、$(document).ready ...のものにつまずきます)。しかし、おそらく、これらのエラーをどこでどのように探すべきかわからないだけです。
さて、話が長くなってすみません。ここにいくつかの詳細があります。
EquipmentEdit.cshtml
@model kpa.mko.bll.viewmodels.clientData.ClientDataFormViewModel
<table id="equipment">
<tr class="form-row">
<td class="form-left">Forklift On Site?</td>
<td class="form-right">
@Html.EditorFor(x => x.ForkliftOnSite)
@Html.ValidationMessageFor(x => x.ForkliftOnSite)
</td>
</tr>
<tr class="form-row">
<td class="form-left">Equipment</td>
<td class="form-right">
@Html.HiddenFor(m => m.AccountId)
Equipment Type: <select id="ddlEquipmentType">
<option value="">--select one--</option>
@foreach (var lookupValue in Model.EquipmentTypes.OrderBy(lv => lv.ShortName))
{
<option value="@lookupValue.LookupValueId">@lookupValue.ShortName</option>
}
</select>
<div id="divEquipmentEdit" style="display: none;">
</div>
</td>
</tr>
</table>
<script type="text/javascript">
$(document).ready(function () {
$("#ddlEquipmentType").change(
function () {
var selection = $("#ddlEquipmentType option:selected").text();
if (selection == "Storage Tanks") {
//Try to get the partial view of the editor template }
$.ajax({
url: '@Url.Action("LoadStorageTankDataEditor")',
type: "POST",
contentType: "application/json; charset=utf-8",
data: JSON.stringify({ accountId: $('#AccountId').val() }),
dataType: "html",
success: function (view) {
$('#divEquipmentEdit').show();
$('#divEquipmentEdit').html(view);
},
error: function (xhr, ajaxOptions, thrownError) {
alert('error = ' + thrownError);
alert('xhr resp text = ' + xhr.responseText);
}
});
} else {
$('#divEquipmentEdit').hide();
}
}
);
});
</script>
StorageTankData.cshtml
@model kpa.mko.bll.viewmodels.clientData.StorageTankData
@using kpa.mko.dal.Entities;
Existing Tanks:
<br /><br />
@Html.HiddenFor(m => m.AccountId)
@if (Model.StorageTanks.Count == 0)
{
<div>No tanks for this account.</div>
}
else
{
foreach (StorageTank st in Model.StorageTanks)
{
@st.ListDescription <a href="javascript:getEditor(@st.StorageTankID);">Edit</a><br />
}
}
<br /><br />
<div id="divTankEditor">
</div>
<script type="text/javascript">
$(document).ready(function () {
getEditor(0);
});
function getEditor(storageTankId) {
//Substitute in the StorageTank editor to the div above.
$.ajax({
url: '@Url.Action("LoadStorageTankEditor")',
type: "POST",
contentType: "application/json; charset=utf-8",
data: JSON.stringify({ storageTankId: storageTankId, accountId: $('#AccountId').val() }),
dataType: "html",
success: function (view) {
$('#divTankEditor').html(view);
if (storageTankId == 0) {
$('#btnTankDelete').hide();
} else {
$('#btnTankDelete').show();
}
},
error: function (xhr, ajaxOptions, thrownError) {
alert('error = ' + thrownError);
alert('xhr resp text = ' + xhr.responseText);
}
});
}
</script>
StorageTank.cshtml
@model kpa.mko.bll.viewmodels.clientData.StorageTankForEdit
@using kpa.mko.dal.Entities;
@using kpa.mko.bll.factories;
@using (Ajax.BeginForm("SaveStorageTank", "ClientData", new AjaxOptions { InsertionMode = InsertionMode.Replace, UpdateTargetId = "divEquipmentEdit" }))
{
@Html.HiddenFor(st => st.TankForEdit.StorageTankID)
<div>Tank Placement:@Html.DropDownListFor(m => m.PlacementSelected, Model.Placements)</div>
<div>Department: @Html.DropDownListFor(m => m.DepartmentSelected, Model.Departments)</div>
<div>Volume (gal): @Html.EditorFor(m => m.TankForEdit.VolumeInGallons)</div>
<div>Content of Tank: @Html.DropDownListFor(m => m.StorageTankContentsSelected, Model.StorageTankContents)</div>
<div>Secondary Containment? @Html.EditorFor(m => m.TankForEdit.HasSecondaryContainment)</div>
<div>If so, what type: @Html.EditorFor(m => m.TankForEdit.SecondaryContainmentType)</div>
<div>Tank Has Overfill Alarm? @Html.EditorFor(m => m.TankForEdit.HasOverfillAlarm)</div>
<div>If so, what type: @Html.EditorFor(m => m.TankForEdit.OverfillAlarmType)</div>
<input type="submit" value="Save Tank Data" name="submitButton" /> <input type="submit" value="Delete Tank" id="btnTankDelete" name="submitButton" /> <input type="button" value="Reset" onclick="getEditor(0); return false;" />
}
保存するコントローラーメソッド
public PartialViewResult SaveStorageTank(string submitButton, string accountId)
{
int acctId = int.Parse(accountId);
StorageTankForEdit stfe = new StorageTankForEdit(acctId);
if (TryUpdateModel(stfe))
{
if (!string.IsNullOrEmpty(submitButton) && submitButton.Equals("Delete Tank"))
{
//Delete the tank already.
StorageTankFactory.Delete(stfe.TankForEdit);
}
else
{
stfe.TankForEdit.DepartmentFk = int.Parse(stfe.DepartmentSelected);
stfe.TankForEdit.Placement = (StorageTankPlacement)Enum.Parse(typeof(StorageTankPlacement), stfe.PlacementSelected);
stfe.TankForEdit.StorageTankContentFk = int.Parse(stfe.StorageTankContentsSelected);
//@^*#$^ NHibernate doesn't bother to get the content class upon re-display, so we don't see the content type.
//I think it's caching this tank, so let's try populating the content here and see if it shows up.
stfe.TankForEdit.StorageTankContent = StorageTankContentFactory.GetById(stfe.TankForEdit.StorageTankContentFk);
StorageTankFactory.Save(stfe.TankForEdit);
}
}
//We decided to reset to the insert state.
return LoadStorageTankDataEditor(acctId);
StorageTankData の部分ビューを取得するコントローラー メソッド
public PartialViewResult LoadStorageTankDataEditor(int accountId)
{
StorageTankData std = new StorageTankData(accountId);
std.StorageTanks = StorageTankFactory.GetByAccount(accountId);
return PartialView("EditorTemplates/StorageTankData", std);
}
StorageTank の部分ビューを取得するコントローラー メソッド
public PartialViewResult LoadStorageTankEditor(int storageTankId, int accountId)
{
StorageTankForEdit st = new StorageTankForEdit(accountId);
if (storageTankId > 0)
{
st.TankForEdit = StorageTankFactory.GetById(storageTankId);
}
return PartialView("EditorTemplates/StorageTank", st);
}