FlexGrid
編集
機能
編集
Excel 形式の編集
FlexGrid では、Excel 形式の高速なセル内編集が組み込みでサポートされています。 余分な列を追加して、そこに表示モードと編集モードを切り替えるための[編集]ボタンを置く必要はありません。
ユーザーは、任意のセルでキー入力するだけで編集を開始できます。これは、 「クイック編集」モードと呼ばれます。 このモードでは、矢印キーを押すと編集が終了し、グリッドの選択範囲が移動します。 [F2]キーを押すか、セルをダブルクリックすることで、編集を開始することもできます。 これは、「完全編集」モードと呼ばれます。 このモードでは、矢印キーを押すとエディタ内のカレットが移動し、編集を終了するには、ユーザーは [Enter]、[Tab]、または[Escape]キーを押す必要があります。
グリッドオブジェクトまたは列オブジェクトの isReadOnly プロパティを使用して、 グリッドレベルまたは列レベルで編集を無効にすることができます。
モバイルデバイス: モバイルデバイスは、ダブルクリックイベントを使用してズームインとズームアウトを行い、 デフォルトではキーボードが表示されません。モバイルデバイスのセルの編集を開始するには、 セルをクリックして選択してから、再度クリックして編集の開始を示すだけです。
自動型検証/型強制: ユーザーが列に無効な値(数値列または日付列に対して「hello」など)を入力すると、 その編集は適用されず、 セルは元の値を維持します。日時は、列に割り当てられた書式で解析されます。
チェックボックス: デフォルトでは、(Excel と異なり)Boolean 値はチェックボックスとして表示されます。ユーザー は、スペースバーをクリックするか押して、チェックボックスの値を変更できます。 チェックボックスは、「TRUE」文字列や 「FALSE」文字列を含むフィールドよりも読みやすく、編集が容易です。
複数行: 列の multiline プロパティを true に設定すると、列内のセルのコンテンツは 改行文字(\n)で折り返されます。Excel と同様に、[Alt]+[Enter]を押して改行を入力できます。
編集モード: データを更新するには、次の 2 つのモードがあります。 デフォルトでは、編集が終了すると更新操作がサーバーにコミットされます。 ユーザーが更新操作をデータソースサーバーにコミットする場合は、Update アクション、Delete アクション、または Create アクションの URL を提供する必要があります。 データソースを更新するために使用するコードは、対応するアクション内に記述する必要があります。 もう 1 つのモードは BatchEdit です。ユーザーは複数の項目を更新、作成、または削除できます。 これらの変更は、確認後に 1 回だけデータソースに対してコミットされます。これで、これらの変更はクライアント側の CollectionView の commit メソッドによってコミットできます。 ユーザーは、ソート動作、ページング動作、またはフィルタ処理動作によってこれらをコミットすることもできます。
AutoRowHeights:データまたはグリッドレイアウトが変更されると、行のサイズを自動的に変更します。 グリッドのコンテンツをワードラップするように構成された列がある場合、およびグリッドの行数が比較的少ない場合に特に便利です。
RefreshOnEdit:これは、セルの編集後にグリッドがすべてのセルを更新するかどうかを決定します。 デフォルトではTrueになっていますが、これを無効にしてパフォーマンスを向上させることができます。
ShowPlaceholders:セルを編集するときにグリッドが列ヘッダをプレースホルダーとして使用するかどうかを決定します。このプロパティは、グリッドの組み込みエディタでのみ機能します。Internet Explorerは、フォーカスされた入力要素にプレースホルダーを表示しないため、このプロパティはIEでは利用できません。
典型的な編集可能グリッドの例として、「Description」列にはテキストが複数行で表示されます。
項目の編集
ポップアップ編集
ポップアップ編集は、Excel 形式のクイックデータ入力に対してネイティブ編集を有効にしたまま、 [詳細の編集]ボタンを追加して、ユーザーが項目の詳細を編集できるフォームを呼び出します。
この動作を確認するには、グリッドの項目を選択し、下の[詳細の編集]ボタンをクリックします。 これにより、現在の選択項目のデータをユーザーが編集できるフォームが表示されます。
詳細フォームは、より多くのスペースを必要とする一方で場合によってはデータ入力が容易になる 特別な入力コントロールを使用します。このフォームには、変更をコミットする[OK]ボタンと 元のデータを復元する[キャンセル]ボタンがあります。どちらのアクションも、グリッドのデータソースとして使用される CollectionView の 1 回の呼び出しで実行されます。
インライン編集
何らかの理由で Excel 形式の編集を避け、 各行に編集ボタンを追加する場合は(編集可能 HTML テーブルなど)、 1 つの ItemFormatter といくつかのコントローラメソッドを使用して実現できます。
次のグリッドは、この方法を示します。セルのボタンは、 コントローラのメソッドを呼び出して必要なアクションを実行します。
using C1.Web.Mvc; using System; using System.Linq; using C1.Web.Mvc.Serialization; using Microsoft.AspNetCore.Mvc; using MvcExplorer.Models; using System.Data.SqlClient; using Microsoft.EntityFrameworkCore; namespace MvcExplorer.Controllers { public partial class FlexGridController : Controller { private readonly C1NWindEntities _db; public FlexGridController(C1NWindEntities db) { _db = db; } public ActionResult Editing() { return View(); } public ActionResult GridBindCategory([C1JsonRequest] CollectionViewRequest<Category> requestData) { return this.C1Json(CollectionViewHelper.Read(requestData, _db.Categories.ToList())); } public ActionResult GridUpdateCategory([C1JsonRequest]CollectionViewEditRequest<Category> requestData) { return Update(requestData, _db.Categories); } public ActionResult GridCreateCategory([C1JsonRequest]CollectionViewEditRequest<Category> requestData) { var category = requestData.OperatingItems.First(); if (category.CategoryName == null) { category.CategoryName = ""; } return Create(requestData, _db.Categories); } public ActionResult GridDeleteCategory([C1JsonRequest]CollectionViewEditRequest<Category> requestData) { return Delete(requestData, _db.Categories, item => item.CategoryID); } public ActionResult GridBindCustomer([C1JsonRequest] CollectionViewRequest<Customer> requestData) { return this.C1Json(CollectionViewHelper.Read(requestData, _db.Customers.ToList())); } public ActionResult GridUpdateCustomer([C1JsonRequest]CollectionViewEditRequest<Customer> requestData) { return Update(requestData, _db.Customers); } public ActionResult GridCreateCustomer([C1JsonRequest]CollectionViewEditRequest<Customer> requestData) { return Create(requestData, _db.Customers); } public ActionResult GridDeleteCustomer([C1JsonRequest]CollectionViewEditRequest<Customer> requestData) { return Delete(requestData, _db.Customers, item => item.CustomerID); } private ActionResult Update<T>(CollectionViewEditRequest<T> requestData, DbSet<T> data) where T : class { return this.C1Json(CollectionViewHelper.Edit<T>(requestData, item => { string error = string.Empty; bool success = true; try { _db.Entry(item as object).State = EntityState.Modified; _db.SaveChanges(); } catch (Exception e) { error = GetExceptionMessage(e); success = false; } return new CollectionViewItemResult<T> { Error = error, Success = success, Data = item }; }, () => data.ToList<T>())); } private ActionResult Create<T>(CollectionViewEditRequest<T> requestData, DbSet<T> data) where T : class { return this.C1Json(CollectionViewHelper.Edit<T>(requestData, item => { string error = string.Empty; bool success = true; try { data.Add(item); _db.SaveChanges(); } catch (Exception e) { error = GetExceptionMessage(e); success = false; } return new CollectionViewItemResult<T> { Error = error, Success = success, Data = item }; }, () => data.ToList<T>())); } private ActionResult Delete<T>(CollectionViewEditRequest<T> requestData, DbSet<T> data, Func<T, object> getKey) where T : class { return this.C1Json(CollectionViewHelper.Edit(requestData, item => { string error = string.Empty; bool success = true; try { T resultItem = null; foreach (var i in data) { if(string.Equals(getKey(i).ToString(), getKey(item).ToString())) { resultItem = i; break; } } if (resultItem != null) { data.Remove(resultItem); _db.SaveChanges(); } } catch (Exception e) { error = GetExceptionMessage(e); success = false; } return new CollectionViewItemResult<T> { Error = error, Success = success, Data = item }; }, () => data.ToList())); } /// <summary> /// In order to get the real exception message. /// </summary> private static SqlException GetSqlException(Exception e) { while(e != null && !(e is SqlException)) { e = e.InnerException; } return e as SqlException; } // Get the real exception message internal static string GetExceptionMessage(Exception e) { var msg = e.Message; var sqlException = GetSqlException(e); if (sqlException != null) { msg = sqlException.Message; } return msg; } } }
@model C1NWindEntities @{ ViewBag.DemoDescription = false; } @section Summary{ @Html.Raw(FlexGridRes.Editing_Text15) } <h3> @Html.Raw(FlexGridRes.Editing_Editing) </h3> <h4> @Html.Raw(FlexGridRes.Editing_ExcelStyleEditing) </h4> <p>@Html.Raw(FlexGridRes.Editing_Text0)</p> <p>@Html.Raw(FlexGridRes.Editing_Text1)</p> <div class="collapsed-content collapse"> <p>@Html.Raw(FlexGridRes.Editing_Text2)</p> <p>@Html.Raw(FlexGridRes.Editing_Text3)</p> <p>@Html.Raw(FlexGridRes.Editing_Text4)</p> <p>@Html.Raw(FlexGridRes.Editing_Text5)</p> <p>@Html.Raw(FlexGridRes.Editing_Text6)</p> <p>@Html.Raw(FlexGridRes.Editing_Text7)</p> <p>@Html.Raw(FlexGridRes.Editing_Text8)</p> <p>@Html.Raw(FlexGridRes.Editing_Text16)</p> <p>@Html.Raw(FlexGridRes.Editing_Text17)</p> </div> <input type="button" value="@FlexGridRes.Editing_ReadMore" class="btn collapse in" data-toggle="collapse" data-target=".collapsed-content, .btn.collapse" /> <p>@Html.Raw(FlexGridRes.Editing_Text9)</p> <c1-flex-grid id="editGrid" auto-generate-columns="false" allow-add-new="true" auto-row-heights="true" allow-delete="true" refresh-on-edit="false" show-placeholders="true" style="height:400px"> <c1-flex-grid-column binding="CategoryID" is-read-only="true" format="d"></c1-flex-grid-column> <c1-flex-grid-column binding="CategoryName" word-wrap="true"></c1-flex-grid-column> <c1-flex-grid-column binding="Description" word-wrap="true" width="*" multi-line="true"></c1-flex-grid-column> <c1-items-source read-action-url="@Url.Action("GridBindCategory")" update-action-url="@Url.Action("GridUpdateCategory")" create-action-url="@Url.Action("GridCreateCategory")" delete-action-url="@Url.Action("GridDeleteCategory")"></c1-items-source> </c1-flex-grid> <!-- a dialog for editing item details --> <div class="modal fade" id="dlgDetail"> <div class="modal-dialog"> <div class="modal-content"> <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-hidden="true"> × </button> <h4 class="modal-title">@Html.Raw(FlexGridRes.Editing_DialogTitle)</h4> </div> <div class="modal-body"> <div class="form-horizontal"> <div class="form-group"> <label class="col-md-3 control-label">@Html.Raw(FlexGridRes.Editing_CategoryID)</label> <div class="col-md-9"> <input id="CategoryID" class="form-control" disabled /> </div> </div> <div class="form-group"> <label class="col-md-3 control-label">@Html.Raw(FlexGridRes.Editing_CategoryName)</label> <div class="col-md-9"> <input id="CategoryName" class="form-control" /> </div> </div> <div class="form-group"> <label class="col-md-3 control-label">@Html.Raw(FlexGridRes.Editing_Description)</label> <div class="col-md-9"> <textarea id="Description" class="form-control"></textarea> </div> </div> </div> </div> <div class="modal-footer"> <button type="button" class="btn btn-primary" data-dismiss="modal" onclick="commitUpdate()"> <span class="glyphicon glyphicon-ok"></span> @Html.Raw(FlexGridRes.Editing_OK) </button> <button type="button" class="btn btn-warning" data-dismiss="modal"> <span class="glyphicon glyphicon-ban-circle"></span> @Html.Raw(FlexGridRes.Editing_Cancel) </button> </div> </div><!-- modal-content --> </div><!-- modal-dialog --> </div><!-- modal --> <div class="well grid-sort-group"> <!-- edit details in a popup --> <button id="showEditDialogBtn" class="btn btn-default" data-toggle="modal" data-backdrop="static" data-keyboard="false" data-target="#dlgDetail" onclick="showEditDialog()"> <span class="glyphicon glyphicon-new-window"></span> @Html.Raw(FlexGridRes.Editing_EditDetail) </button> </div> <h4> @Html.Raw(FlexGridRes.Editing_PopupEditing) </h4> <p>@Html.Raw(FlexGridRes.Editing_Text10)</p> <p>@Html.Raw(FlexGridRes.Editing_Text11)</p> <p>@Html.Raw(FlexGridRes.Editing_Text12)</p> <br /> <h4> @Html.Raw(FlexGridRes.Editing_InlineEditing) </h4> <p>@Html.Raw(FlexGridRes.Editing_Text13)</p> <p>@Html.Raw(FlexGridRes.Editing_Text14)</p> <c1-flex-grid id="inlineEditGrid" is-read-only="true" selection-mode="None" pinning-type="SingleColumn" pinned-column="pinnedColumn" auto-generate-columns="false" item-formatter="itemFormatter" style="height:400px" refresh-on-edit="false" scroll-position-changed="scrollPositionChanged" resizing-column="resizingColumn" dragging-column="draggingColumn" sorting-column="sortingColumn" pinning-column="pinningColumn"> <c1-items-source read-action-url="@Url.Action("GridBindCustomer")" update-action-url="@Url.Action("GridUpdateCustomer")" create-action-url="@Url.Action("GridCreateCustomer")" delete-action-url="@Url.Action("GridDeleteCustomer")"></c1-items-source> <c1-flex-grid-column binding="CustomerID" width="80" align="right" is-read-only="true"></c1-flex-grid-column> <c1-flex-grid-column binding="Country" name="Country"></c1-flex-grid-column> <c1-flex-grid-column binding="Address" width="*" name="Address"></c1-flex-grid-column> <c1-flex-grid-column name="Buttons" width="170"></c1-flex-grid-column> </c1-flex-grid> @section Scripts{ <script type="text/javascript" src="~/Scripts/jquery.js"></script> <script type="text/javascript" src="~/Scripts/BootStrap/bootstrap.js"></script> <script> var editGrid, editCV, minHeight, showEditDialogBtn, idInput, categoryNameInput, descriptionInput; c1.documentReady(function () { editGrid = wijmo.Control.getControl('#editGrid'); editCV = editGrid.collectionView; showEditDialogBtn = document.getElementById('showEditDialogBtn'); idInput = document.getElementById('CategoryID'); categoryNameInput = document.getElementById('CategoryName'); descriptionInput = document.getElementById('Description'); updateButton(); editCV.currentChanged.addHandler(updateButton); minHeight = editGrid.rows.defaultSize; autoSizeEditGridRows(); editCV.collectionChanged.addHandler(autoSizeEditGridRows); }); function autoSizeEditGridRows(s, e) { if (e && e.action == 0) return; editGrid.autoSizeRows(); for (var i = 0, len = editGrid.rows.length; i < len; i++) { var row = editGrid.rows[i]; var height = row.height == undefined ? 0 : row.height; row.height = Math.max(28, height); } } function showEditDialog() { var current; if (!editCV || !editCV.currentItem) { return; } current = editCV.currentItem; // fill the current item data to the inputs. idInput.value = current.CategoryID || ''; categoryNameInput.value = current.CategoryName || ''; descriptionInput.value = current.Description || ''; } function updateButton() { if (!showEditDialogBtn) { return; } if (!editCV || !editCV.currentItem) { showEditDialogBtn.disabled = true; } else { showEditDialogBtn.disabled = false; } } function commitUpdate() { if (!editCV) { return; } var editItem = editCV.currentItem; // begin to edit the current item editCV.editItem(editItem); //update the data editItem.CategoryName = categoryNameInput.value; editItem.Description = descriptionInput.value; // commit the edit editCV.commitEdit(); } var inlineEditGrid, inlineEditCV, editIndex = -1; c1.documentReady(function () { inlineEditGrid = wijmo.Control.getControl('#inlineEditGrid'); inlineEditGrid.rows.defaultSize = 44; inlineEditCV = inlineEditGrid.collectionView; }); function itemFormatter(panel, r, c, cell) { var isLocalHost = (location.hostname === "localhost" || location.hostname === "127.0.0.1"); var col, html, hasUpdated = false; if (panel.cellType == wijmo.grid.CellType.Cell) { col = panel.columns[c]; if (r == editIndex) { switch (col.name) { case 'Country': html = '<input id="theCountry" class="form-control" onkeydown="keyDown(event)" value="' + panel.getCellData(r, c, true) + '"/>'; hasUpdated = true; break; case 'Address': html = '<input id="theAddress" class="form-control" onkeydown="keyDown(event)" value="' + panel.getCellData(r, c, true) + '"/>'; hasUpdated = true; break; case 'Buttons': html = '<div>' + ' ' + '<button class="btn btn-primary btn-sm" onclick="commitRow(' + r + ')">' + '<span class="glyphicon glyphicon-ok"></span> @Html.Raw(FlexGridRes.Editing_OK)' + '</button>' + ' ' + '<button class="btn btn-warning btn-sm" onclick="cancelRow(' + r + ')">' + '<span class="glyphicon glyphicon-ban-circle"></span> @Html.Raw(FlexGridRes.Editing_Cancel)' + '</button>' + '</div>'; hasUpdated = true; break; } } else { switch (col.name) { case 'Buttons': hasUpdated = true; html = '<div>' + ' ' + '<button class="btn btn-default btn-sm" onclick="editRow(' + r + ')">' + '<span class="glyphicon glyphicon-pencil"></span> @Html.Raw(FlexGridRes.Editing_Edit)' + '</button>' + ' ' + '<button class="btn btn-default btn-sm" ' + (isLocalHost ? '' : 'style="display:none"') + ' onclick="deleteRow(' + r + ')">' + '<span class="glyphicon glyphicon-remove"></span> @Html.Raw(FlexGridRes.Editing_Delete)' + '</button>' + '</div>'; break; } } if (hasUpdated) { cell.innerHTML = html; cell.style.padding = '3px'; } } } function scrollPositionChanged() { cancelEditingMode(); } function resizingColumn() { cancelEditingMode(); } function draggingColumn() { cancelEditingMode(); } function sortingColumn() { cancelEditingMode(); } function pinningColumn() { cancelEditingMode(); console.log("Column is pinning."); } function pinnedColumn() { console.log("Column has pinned."); } function cancelEditingMode() { if (editIndex > -1) { cancelRow(editIndex); } } function keyDown(e) { e.stopPropagation(); } function editRow(rowIndex) { if (!inlineEditGrid || !inlineEditCV) { return; } editIndex = rowIndex; inlineEditGrid.invalidate(); } function deleteRow(rowIndex) { if (!inlineEditCV) { return; } editIndex = -1; inlineEditCV.removeAt(rowIndex); } function commitRow(rowIndex) { var countryInput, addressInput, editItem; if (!inlineEditCV) { return; } //update the data inlineEditCV.editItem(inlineEditCV.items[rowIndex]); editItem = inlineEditCV.currentEditItem; if (!editItem) { return; } countryInput = document.getElementById('theCountry'); addressInput = document.getElementById('theAddress'); editItem.Country = countryInput.value; editItem.Address = addressInput.value; editIndex = -1; inlineEditCV.commitEdit(); } function cancelRow() { editIndex = -1; inlineEditGrid.invalidate(); } </script> }