پیاده سازی عملیات CRUD در Kendo UI Treeview - Asp.net mvc


این مقاله، یکی از مقاله های خودن در سایت Dotnettips.info است. امیدوارم استفاده کنید.

در این مقاله می‌خواهیم عملیات CRUD را بر روی Telerik kendo treeview  در یک پروژه‌ی ASP.NET MVC پیاده سازی کنیم. شکل کلی این پروژه به صورت زیر می‌باشد:

که اینجا دکمه‌ها از سمت راست به چپ، عملیات افزودن، عدم انتخاب، ویرایش و حذف را انجام می‌دهند. کدهای HTML این پنل را در ادامه مشاهده می‌کنید:


<div id="CrudPanel" > <div > <input type="text" id="txtLocationTitle" /> </div> <div > <button data-toggle="tooltip" data-placement="left" title="افزودن" id="btnAddLocation" > <i ></i> </button> <button data-toggle="tooltip" data-placement="left" title="عدم انتخاب" id="btnUnSelect" > <i ></i> </button> <button data-toggle="tooltip" data-placement="left" title="ویرایش" id="btnEditLocation" > <i ></i> </button> <button data-toggle="tooltip" data-placement="left" title="حذف" id="btnDeleteLocation" > <i ></i> </button> </div> </div>

و قطعه کد ذیل مربوط به پنل ویرایش است که در ابتدای کار کلاس hide به آن انتساب داده شده و پنهان می‌شود:


<div id="EditPanel" > <div > <input type="text" id="txtLocationEditTitle" /> </div> <div > <input type="button" value="ویرایش" id="btnEditPanelLocation" data-code="" data-parentId="" /> <input type="button" value="انصراف" id="btnCancle" /> </div> </div>

در آخر این تکه کد نیز مربوط به KendoUI TreeView است:


<div > @(Html.Kendo() .TreeView() .Name("treeview") .DataTextField("Title") .DragAndDrop(false) .DataSource(dataSource => dataSource .Model(model => model.Id("Id")) .Read(read => read.Action(MVC.Admin.Location.ActionNames.GetAllAssetGroupTree, MVC.Admin.Location.Name))) ) </div>

یک نکته

- کلاس k-rtl مربوط به خود treeview می‌باشد و با این کلاس، درخت ما راست به چپ می‌شود.

در ادامه css‌های مربوط به کلاس‌های treeview-style ،hide و treeview-panel بررسی خواهند شد:


.treeview-style { min-height: 86px; max-height: 300px; overflow: scroll; overflow-x: hidden; position: relative; } .treeview-panel { background-color: #eee; padding: 25px 0 25px 0; } .hide { display: none; }

تا اینجای مقاله، کدهای Html و Css موجود را بررسی کردیم. حالا سراغ قسمت اصلی خواهیم رفت. یعنی عملیات CRUD.

لازم به ذکر است در ابتدای قسمت script  باید این چند خط کد نوشته شود:


var treeview = null; $(window).load(function () { treeview = $("#treeview").data("kendoTreeView"); });

در اینجا بعد از بارگذاری کامل صفحه، درخت مورد نظر ما ساخته خواهد شد و می‌توان به متغیر treeview در تمام قسمت script دسترسی داشت.

پیاده سازی عملیات افزودن: 


$(document).on('click', '#btnAddLocation', function () { var title = $('#txtLocationTitle').val(); var selectedNodeId = null; var selectedNode = treeview.select(); if (selectedNode.length == 0) { selectedNode = null; } else { selectedNodeId = treeview.dataItem(selectedNode).id;// گرفتن آی دی گره انتخاب شده } $.ajax({ url: '@Url.Action(MVC.Admin.Location.CreateByAjax())', type: 'POST', data: { Title: title, ParentId: selectedNodeId }, success: function (data) { debugger; showMessage(data.message, data.notificationType); if (data.result) treeview.dataSource.read(); }, error: function () { showMessage('لطفا مجددا تلاش نمایید', 'warning'); } }); });

توضیحات: مقدار گره جدید را خوانده و در متغیر title قرار می‌دهیم. گره انتخاب شده را توسط این خط


var selectedNode = treeview.select();

می گیریم و سپس در ادامه بررسی خواهیم کرد تا اگر گره‌ای انتخاب نشده باشد، به کاربر پیغامی را نشان دهد؛ در غیر این صورت توسط ajax، مقادیر مورد نظر، به اکشن ما در LocationController ارسال می‌شوند:


[HttpPost] public virtual ActionResult CreateByAjax(AddLocationViewModel locationViewModel) { if (ModelState.IsNotValid()) return JsonResult(false, "عنوان نباید خالی و یا کمتر از دو کاراکتر باشد.", NotificationType.Error); var result = _locationService.Add(locationViewModel);//سرویس مورد نظر برای اضافه کردن به دیتابیس switch (result) { case AddStatus.AddSuccessful: _uow.SaveChanges(); return JsonResult(true, Messages.SaveSuccessfull, NotificationType.Success); case AddStatus.Faild: return JsonResult(false, Messages.SaveFailed, NotificationType.Error); case AddStatus.Exists: return JsonResult(false, Messages.DataExists, NotificationType.Warning); default: return JsonResult(false, Messages.SaveFailed, NotificationType.Error); } }


public virtual JsonResult JsonResult(bool result, string message, string notificationType) { return Json(new { result = result, message = message, notificationType = notificationType }, JsonRequestBehavior.AllowGet); }

اکشن JsonResult  که مقادیر نتیجه، پیغام و نوع اطلاع رسانی را می‌گیرد و یک آبجکت از نوع json را به تابع success ای‌جکس، ارسال می‌کند.


public class AddLocationViewModel { [DisplayName("عنوان")] [Required(ErrorMessage ="لطفا عنوان گروه را وارد نمایید"),MinLength(2,ErrorMessage ="طول عنوان خیلی کوتاه می‌باشد ")] public string Title { get; set; } [DisplayName("گروه پدر")] public Guid? ParentId { get; set; } }

این کلاس viewModel ما می‌باشد.


public enum AddStatus { AddSuccessful, Faild, Exists }

و این مورد هم کلاس AddStatus از نوع enum.


public class Messages { #region Fields public const string SaveSuccessfull = "اطلاعات با موفقیت ذخیره شد"; public const string SaveFailed = "خطا در ثبت اطلاعات"; public const string DeleteMessage = "کابر گرامی ، آیا از حذف کردن این رکورد مطمئن هستید ؟"; public const string DeleteSuccessfull = "اطلاعات با موفقیت حذف شد"; public const string DeleteFailed = "خطا در حذف اطلاعات ، لطفا مجددا تلاش نمایید"; public const string DeleteHasInclude = "کاربر گرامی ، رکورد مورد نظر هم اکنون در بانک اطلاعاتی سیستم در حال استفاده توسط منابع دیگر می‌باشد"; public const string NotFoundData = "اطلاعات یافت نشد"; public const string NoAttachmentSelect = "تصویری انتخاب نشده است"; public const string DataExists = "اطلاعات وارد شده در بانک اطلاعاتی موجود می‌باشد"; public const string DeletedRowHasIncluded = "کاربر گرامی ، رکوردی که قصد حذف آن را دارید هم اکنون در بانک اطلاعاتی سیستم ، توسط سایر بخش‌ها در حال استفاده می‌باشد"; #endregion }

و این موارد هم مقادیر ثابت فیلد‌های مورد استفاده‌ی ما در کلاس Message.

پیاده سازی عملیات حذف

به طور اختصار، عملیات حذف را توضیح می‌دهم تا به قسمت اصلی مقاله یعنی ویرایش بپردازیم:


$(document).on('click', '#btnDeleteLocation', function () { var selectedNode = treeview.select(); var currentNode = treeview.dataItem(selectedNode); if (selectedNode.length == 0) { showMessage('گزینه ای انتخاب نشده است. لطفا یک گزینه انتخاب نمایید', 'warning'); } else { var selectedNodeId = treeview.dataItem(selectedNode).id; if (currentNode.hasChildren) { var title = 'کاربر گرامی ، با حذف شدن این گره، تمام زیر شاخه‌های آن حذف می‌شود. آیا مطمئن هستید ؟ '; DeleteConfirm(selectedNodeId, '@Url.Action(MVC.Admin.Location.DeleteByAjax())', title); } else { $.ajax({ url: '@Url.Action(MVC.Admin.Location.DeleteByAjax())', type: 'POST', data: { id: selectedNodeId }, success: function (data) { debugger; showMessage(data.message, data.notificationType); if (data.result) treeview.remove(selectedNode); }, error: function () { showMessage('لطفا مجددا تلاش نمایید', 'warning'); } }); } } });

این مورد نیز همانند عملیات افزودن عمل می‌کند. یعنی ابتدا چک می‌کند که آیا گره‌ای انتخاب شده است یا خیر؟ و اگر گره انتخابی ما دارای فرزند باشد، به کاربر پیغامی را نشان می‌دهد و می‌گوید «گره مورد نظر، دارای فرزند است. آیا مایل به حذف تمام فرزندان آن هستید؟» مانند تصویر زیر:

در نهایت چه گره انتخابی دارای فرزند باشد و چه نباشد، به یک مسیر مشترک ارسال می‌شوند:


public virtual ActionResult DeleteByAjax(Guid id) { var result = _locationService.Delete(id); switch (result) { case DeleteStatus.Successfull: _uow.SaveChanges(); return DeleteJsonResult(true, Messages.DeleteSuccessfull, NotificationType.Success); case DeleteStatus.NotFound: return DeleteJsonResult(false, Messages.NotFoundData, NotificationType.Error); case DeleteStatus.Failed: return DeleteJsonResult(false, Messages.DeleteFailed, NotificationType.Error); case DeleteStatus.ThisRowHasIncluded: return DeleteJsonResult(false, Messages.DeletedRowHasIncluded, NotificationType.Warning); default: return DeleteJsonResult(false, Messages.DeleteFailed, NotificationType.Error); } }

در سرویس مورد نظر ما یعنی Delete، اگه گره‌ای دارای فرزند باشد، تمام فرزندان آن را حذف می‌کند. حتی فرزندان فرزندان آن را:


public DeleteStatus Delete(Guid id) { var model = GetAsModel(id); if (model == null) return DeleteStatus.NotFound; if (!CanDelete(model)) return DeleteStatus.ThisRowHasIncluded; _uow.MarkAsSoftDelete(model, _userManager.GetCurrentUserId()); if (model.Children.Any()) DeleteChildren(model); return DeleteStatus.Successfull; }


private void DeleteChildren(Location model) { foreach (var item in model.Children) { _uow.MarkAsSoftDelete(item, _userManager.GetCurrentUserId()); if (item.Children.Any()) DeleteChildren(item); } }


public class Location:BaseEntity,ISoftDelete { public string Title { get; set; } public Location Parent { get; set; } public Guid? ParentId { get; set; } public bool IsDeleted { get; set; } public virtual ICollection<Location> Children { get; set; } }

 و این هم مدل Location که سمت سرور از مدل استفاده می‌کنیم.

پیاده سازی عملیات ویرایش

حالا به قسمت اصلی مقاله رسیدیم. در اینجا قرار است گره‌ای را انتخاب نماییم و با زدن دکمه ویرایش و باز شدن پنل آن، آن را ویرایش کنیم. با زدن دکمه ویرایش، کدهای زیر اجرا می‌شوند:


Code

// Open Edit Panel $(document).on('click', '#btnEditLocation', function () { debugger; var selectedNode = treeview.select(); var currentNode = treeview.dataItem(selectedNode);// با استفاده از این خط، گره انتخاب شده جاری را می‌گیریم. if (selectedNode.length == 0) { //این شرط به ما می‌گوید اگر گره ای انتخاب نشده بود پیغامی به کاربر نمایش بده showMessage('گزینه ای انتخاب نشده است. لطفا یک گزینه انتخاب نمایید', 'warning'); } else { var selectedNodeCode = treeview.dataItem(selectedNode).Code; var selectedNodeTitle = treeview.dataItem(selectedNode).Title; var selectedNodeParentId = treeview.dataItem(selectedNode).ParentId; // آی دی یا کد، عنوان و آی دی پدر گره انتخاب شده را با استفاده از این سه خط در اختیار می‌گیریم $('#CrudPanel').toggleClass('hide'); //المنت کرادپنل که در حال حاضر کاربر آن را می‌بیند، با این خط کد، پنهان می‌شود $('#EditPanel').toggleClass('hide'); //المنت ادیت پنل که در حال حاضر از دید کاربر پنهان است، قابل نمایش می‌شود $("#txtLocationEditTitle").val(selectedNodeTitle); //عنوان گره ای که می‌خواهیم آن را ویرایش کنیم در تکست باکس مورد نظر قرار می‌گیرد $("#txtLocationEditTitle").focusTextToEnd(); // با استفاده از این پلاگین، کرسر ماوس در انتهای مقدار دیفالت تکست باکس قرار می‌گیرد $("#btnEditPanelLocation").attr('data-code', selectedNodeCode); $("#btnEditPanelLocation").attr('data-parentId', selectedNodeParentId == null ? '' : selectedNodeParentId); //مقادیر پرنت آی دی و کد را در دیتا اتریبیوت‌های موجود در المنت خودمان قرار می‌دهیم // Disable clicking in treeview $("#treeview").children().bind('click', function () { return false; }); } }); (function ($) { $.fn.focusTextToEnd = function () { this.focus(); var $thisVal = this.val(); this.val('').val($thisVal); return this; } }(jQuery));

کد زیر باعث می‌شود تا زمانیکه پنل ویرایش باز است، کاربر نتواند هیچ کلیکی را در عناصر داخل درخت ما، داشته باشد.


$("#treeview").children().bind('click', function () { return false; });

و در نهایت با زدن دکمه ویرایش، پنل ویرایش ما به صورت زیر باز می‌شود:

همانطور که در تصویر بالا مشاهده می‌کنید، با انتخاب ساختمان مرکزی و زدن دکمه ویرایش، پنل CRUD ما پنهان و پنل ویرایش ظاهر می‌گردد. همچنین عنوان گره انتخابی به عنوان پیش فرض تکست باکس ما تنظیم می‌شود و کاربر نمی‌تواند گره دیگری را انتخاب کند؛ به شرط آنکه این پنل ویرایش بسته شود.

با تغییر عنوان تکست باکس و زدن دکمه‌ی ویرایش، رویداد زیر رخ می‌دهد:


// Edit tree node $(document).on('click', '#btnEditPanelLocation', function () { debugger; var code = $("#btnEditPanelLocation").attr('data-code'); var parentId = $("#btnEditPanelLocation").attr('data-parentId'); var title = $("#txtLocationEditTitle").val().trim(); $.ajax({ url: '@Url.Action(MVC.Admin.Location.EditByAjax())', type: 'POST', data: { Code: code, Title: title, ParentId: parentId.length === 0 ? null : parentId }, success: function (data) { debugger; showMessage(data.message, data.notificationType); if (data.result) { treeview.dataSource.read(); CloseEditPanel(); } }, error: function () { showMessage('لطفا مجددا تلاش نمایید', 'warning'); } }); });


[HttpPost] public virtual ActionResult EditByAjax(EditLocationViewModel editLocationViewModel) { if (ModelState.IsNotValid()) return JsonResult(false,"عنوان نباید خالی و یا کمتر از دو کاراکتر باشد.", NotificationType.Error); var result = _locationService.Edit(editLocationViewModel); switch (result) { case EditStatus.Successful: _uow.SaveChanges(); return JsonResult(true, Messages.SaveSuccessfull, NotificationType.Success); case EditStatus.NotFound: return JsonResult(false, Messages.NotFoundData, NotificationType.Error); case EditStatus.Faild: return JsonResult(false, Messages.SaveFailed, NotificationType.Error); case EditStatus.Exists: return JsonResult(false, Messages.DataExists, NotificationType.Warning); default: return JsonResult(false, Messages.SaveFailed, NotificationType.Error); } }

تابع CloseEditPanel  بعد از اتمام ویرایش هر گره و یا با زدن دکمه انصراف در شکل بالا، فراخوانی می‌شود که کد آن به شکل زیر است:


function CloseEditPanel() { $('#CrudPanel').toggleClass('hide'); //پنل کراد ما که در حال حاضر از دید کاربر پنهان است با این خط ظاهر می‌گردد $('#EditPanel').toggleClass('hide'); //پنل ویرایش ما که در حال حاضر کاربر آن را می‌بیند، پنهان می‌شود از دید کاربر $("#txtLocationEditTitle").val(''); //مقدار تکست باکس خالی می‌شود $("#btnEditPanelLocation").attr('data-code', ''); $("#btnEditPanelLocation").attr('data-parentId', ''); //دیتا اتریبیوت‌های ما که مقادیر کد و آی دی والد در آن قرار گرفته نیز خالی می‌شود // Enable clicking in treeview $("#treeview").children().unbind('click').bind('click', function () { return true; }); //اگر یادتان باشد با یک خط کد به کاربر اجازه ندادیم که با باز شدن پنل ویرایش، گره دیگری را انتخاب نمایی. حالا این خط کد عکس کد قبلیست و به کاربر اجازه می‌دهد در المنت مورد نظر کلیک کند }


// Cancle edit Node tree $(document).on('click', '#btnCancle', function () { CloseEditPanel(); });


$(document).on('click', '#btnUnSelect', function () { //رویداد عدم انتخاب treeview.select(null); });