jsTree یکی از افزونههای بسیار محبوب jQuery جهت نمایش ساختارهای سلسله مراتبی، خود ارجاع دهنده و تو در تو است. روش ابتدایی استفاده از آن تعریف یک سری ul و li ثابت در صفحه و سپس فراخوانی این افزونه بر روی آنها است که سبب نمایش درختوارهای این اطلاعات خواهد شد. روش پیشرفتهتر آن به همراه کار با دادههای JSON و دریافت پویای اطلاعات از سرور است که در ادامه به بررسی آن خواهیم پرداخت.
دریافت افزونهی jsTree
برای دریافت افزونهی jsTree میتوان به مخزن کد آن در Github مراجعه کرد و همچنین مستندات آنرا در سایت jstree.com قابل مطالعه هستند.
تنظیمات مقدماتی jsTree
در این مطلب فرض شدهاست که فایل jstree.min.js، در پوشهی Scripts و فایلهای CSS آن در پوشهی Content\themes\default کپی شدهاند.
به این ترتیب layout برنامه چنین شکلی را خواهد یافت:
نمایش راست به چپ اطلاعات
در کدهای این افزونه به تگ body و ویژگی dir آن برای تشخیص راست به چپ بودن محیط دقت میشود. به همین جهت این تعریف را در layout فوق ملاحظه میکنید. برای مثال اگر به فایل jstree.contextmenu.js (موجود در مجموعه سورسهای این افزونه) مراجعه کنید، یک چنین تعریفی قابل مشاهده است:
تهیه ساختاری جهت ارائهی خروجی JSON
با توجه به اینکه قصد داریم به صورت پویا با این افزونه کار کنیم، نیاز است بتوانیم ساختار سلسله مراتبی مدنظر را با فرمت JSON ارائه دهیم. در ادامه کلاسهایی که معادل فرمت JSON قابل قبول توسط این افزونه را تولید میکنند، ملاحظه میکنید:
در اینجا به چند نکته باید دقت داشت:
- هر چند اسامی مانند a_attr، مطابق اصول نامگذاری دات نت نیستند، ولی این نامها را تغییر ندهید. زیرا این افزونه دقیقا به همین نامها و با همین املاء نیاز دارد.
- id، میتواند دقیقا معادل id یک رکورد در بانک اطلاعاتی باشد. Text عنوان گرهای (node) است که نمایش داده میشود. icon در اینجا مسیر یک فایل png است جهت نمایش در کنار عنوان هر گره. توسط state میتوان مشخص کرد که زیر شاخهی جاری به صورت باز نمایش داده شود یا بسته. به کمک خاصیت children میتوان زیر شاخهها را تا هر سطح و تعدادی که نیاز است تعریف نمود.
- خاصیتهای li_attr و a_attr کاملا دلخواه هستند. برای مثال در اینجا دو خاصیت href و data را در کلاسهای مرتبط با آنها مشاهده میکنید. میتوانید در اینجا به هر تعداد ویژگی سفارشی دیگری که جهت تعریف یک گره نیاز است، خاصیت اضافه کنید.
سادهترین مثالی که از ساختار فوق میتواند استفاده کند، اکشن متد زیر است:
در ابتدا لیست گرهها تعریف میشود و سپس برای نمونه در این مثال، دو گره تعریف شدهاند و در ادامه با فرمت JSON در اختیار افزونه قرار گرفتهاند.
بنابراین ساختارهای خود ارجاع دهنده را به خوبی میتوان با این افزونه وفق داد.
فعال سازی اولیه سمت کلاینت افزونه jsTree
برای استفادهی پویای از این افزونه در سمت کلاینت، فقط نیاز به یک DIV خالی است:
سپس jstree را بر روی این DIV فراخوانی میکنیم:
توضیحات
- multiple : false به این معنا است که نمیخواهیم کاربر بتواند چندین گره را با نگه داشتن دکمهی کنترل انتخاب کند.
- check_callback : true کدهای مرتبط با منوی کلیک سمت راست ماوس را فعال میکند.
- در قسمت data کار تبادل اطلاعات با سرور جهت دریافت فرمت JSON ایی که به آن اشاره شد، انجام میشود. متغیر getTreeJsonUrl یک چنین شکلی را میتواند داشته باشد:
- در قسمت themes مشخص کردهایم که از قالب small آن به همراه نمایش یک درمیان پس زمینهی روشن و خاکستری استفاده شود. قالب large نیز دارد.
- در قسمت types که مرتبط است با افزونهای به همین نام، آیکن پیش فرض یک نود جدید ایجاد شده را مشخص کردهایم.
- گزینهی plugins، لیست افزونههای اختیاری این افزونه را مشخص میکند. برای مثال contextmenu منوی کلیک سمت راست ماوس را فعال میکند، dnd همان کشیدن و رها کردن گرهها است در زیر شاخههای مختلف. افزونهی state، انتخاب جاری کاربر را در سمت کلاینت ذخیره و در مراجعهی بعدی او بازیابی میکند. با ذکر افزونهی wholerow سبب میشویم که انتخاب یک گره، معادل انتخاب یک ردیف کامل از صفحه باشد. افزونهی sort کار مرتب سازی خودکار اعضای یک زیر شاخه را انجام میدهد. افزونهی unique سبب میشود تا در یک زیر شاخه نتوان دو عنوان یکسان را تعریف کرد.
- در قسمت contextmenu نحوهی بومی سازی گزینههای منوی کلیک راست ماوس را مشاهده میکنید. در حالت پیش فرض، عناوینی مانند create، rename و امثال آن نمایش داده میشوند که به نحو فوق میتوان آنرا تغییر داد.
با همین حد تنظیم، این افزونه کار نمایش سلسله مراتبی اطلاعات JSON ایی دریافت شده از سرور را انجام میدهد.
ذخیره سازی گرههای جدید و تغییرات سلسله مراتب پویای تعریف شده در سمت سرور
همانطور که عنوان شد، اگر افزونهی اختیاری contextmenu را فعال کنیم، امکان افزودن، ویرایش و حذف گرهها و زیر شاخهها را خواهیم یافت. برای انتقال این تغییرات به سمت سرور، باید به نحو ذیل عمل کرد:
در اینجا نحوهی تحت کنترل قرار دادن رخدادهای مختلف این افزونه را مشاهده میکنید. برای مثال در callback مرتبط با delete_node کار حذف یک گره اطلاع رسانی میشود. create_node مربوط است به ایجاد یک گره یا زیر شاخهی جدید. rename_node پس از تغییر نام یک گره فراخوانی خواهد شد. move_node مربوط است به کشیدن و رها کردن یک گره در یک زیر شاخهی دیگر. copy_node برای copy/paste یک گره تعریف شدهاست. Changed یک callback عمومی است. dblclick برای عکس العمل نشان دادن به رخداد دوبار کلیک کردن بر روی یک گره میتواند بکار گرفته شود. select_node با انتخاب یک گره فعال میشود.
در تمام این حالات، جایی که data در اختیار ما است، میتوان یک چنین ساختار جاوا اسکریپتی را برای ارسال به سرور طراحی کرد:
به این ترتیب در سمت سرور میتوان id یک گره، متن تغییر یافته آن، والد گره و بسیاری از مشخصات دیگر را دریافت و ثبت کرد. پس از تعریف متد postJsTreeOperation فوق، آنرا باید به callbackهایی که پیشتر معرفی شدند، اضافه کرد؛ برای مثال:
در اینجا متد postJsTreeOperation، یک Operation خاص را مانند CreateNode (تعریف شده در enum ایی به نام JsTreeOperation در سمت سرور) به همراه data، به سرور post میکند.
و معادل سمت سرور دریافت کنندهی این اطلاعات، اکشن متد ذیل میتواند باشد:
که در آن ساختار JsTreeOperationData به نحو ذیل تعریف شدهاست:
این ساختار دقیقا با اعضای شیء جاوا اسکریپتی که متد postJsTreeOperation به سمت سرور ارسال میکند، تطابق دارد.
در اینجا Href را نیز مشاهده میکنید. همانطور که عنوان شد، اعضای JsTreeNodeAAttributes اختیاری هستند. بنابراین اگر این اعضاء را تغییر دادید، باید خواص JsTreeOperationData و همچنین اعضای شیء تعریف شده در postJsTreeOperation را نیز تغییر دهید تا با هم تطابق پیدا کنند.
چند نکتهی تکمیلی
اگر میخواهید که با دوبار کلیک بر روی یک گره، کاربر به href آن هدایت شود، میتوان از کد ذیل استفاده کرد:
در callback مرتبط با select_node میتوان به گره انتخابی درستی یافت. سپس میتوان این گره را در callback متناظر با dblclick برای یافتن href و مقدار دهی window.location که معادل redirect سمت کاربر است، بکار برد.
حتی اگر خواستید که با دوبار کلیک بر روی یک گره، گزینهی ویرایش آن فعال شود، کدهای آن را به صورت کامنت مشاهده میکنید.
مثال کامل این بحث را از اینجا میتوانید دریافت کنید:
MvcJSTree.zip
دریافت افزونهی jsTree
برای دریافت افزونهی jsTree میتوان به مخزن کد آن در Github مراجعه کرد و همچنین مستندات آنرا در سایت jstree.com قابل مطالعه هستند.
تنظیمات مقدماتی jsTree
در این مطلب فرض شدهاست که فایل jstree.min.js، در پوشهی Scripts و فایلهای CSS آن در پوشهی Content\themes\default کپی شدهاند.
به این ترتیب layout برنامه چنین شکلی را خواهد یافت:
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width" /> <title>@ViewBag.Title</title> <link href="~/Content/Site.css" rel="stylesheet" /> <link href="~/Content/themes/default/style.min.css" rel="stylesheet" /> <script src="~/Scripts/jquery.min.js"></script> <script src="~/Scripts/jstree.min.js"></script> </head> <body dir="rtl"> @RenderBody() @RenderSection("scripts", required: false) </body> </html>
نمایش راست به چپ اطلاعات
در کدهای این افزونه به تگ body و ویژگی dir آن برای تشخیص راست به چپ بودن محیط دقت میشود. به همین جهت این تعریف را در layout فوق ملاحظه میکنید. برای مثال اگر به فایل jstree.contextmenu.js (موجود در مجموعه سورسهای این افزونه) مراجعه کنید، یک چنین تعریفی قابل مشاهده است:
right_to_left = $("body").css("direction") === "rtl";
تهیه ساختاری جهت ارائهی خروجی JSON
با توجه به اینکه قصد داریم به صورت پویا با این افزونه کار کنیم، نیاز است بتوانیم ساختار سلسله مراتبی مدنظر را با فرمت JSON ارائه دهیم. در ادامه کلاسهایی که معادل فرمت JSON قابل قبول توسط این افزونه را تولید میکنند، ملاحظه میکنید:
using System.Collections.Generic; namespace MvcJSTree.Models { public class JsTreeNode { public string id { set; get; } // نام این خواص باید با مستندات هماهنگ باشد public string text { set; get; } public string icon { set; get; } public JsTreeNodeState state { set; get; } public List<JsTreeNode> children { set; get; } public JsTreeNodeLiAttributes li_attr { set; get; } public JsTreeNodeAAttributes a_attr { set; get; } public JsTreeNode() { state = new JsTreeNodeState(); children = new List<JsTreeNode>(); li_attr = new JsTreeNodeLiAttributes(); a_attr = new JsTreeNodeAAttributes(); } } public class JsTreeNodeAAttributes { // به هر تعداد و نام اختیاری میتوان خاصیت تعریف کرد public string href { set; get; } } public class JsTreeNodeLiAttributes { // به هر تعداد و نام اختیاری میتوان خاصیت تعریف کرد public string data { set; get; } } public class JsTreeNodeState { public bool opened { set; get; } public bool disabled { set; get; } public bool selected { set; get; } public JsTreeNodeState() { opened = true; } } }
- هر چند اسامی مانند a_attr، مطابق اصول نامگذاری دات نت نیستند، ولی این نامها را تغییر ندهید. زیرا این افزونه دقیقا به همین نامها و با همین املاء نیاز دارد.
- id، میتواند دقیقا معادل id یک رکورد در بانک اطلاعاتی باشد. Text عنوان گرهای (node) است که نمایش داده میشود. icon در اینجا مسیر یک فایل png است جهت نمایش در کنار عنوان هر گره. توسط state میتوان مشخص کرد که زیر شاخهی جاری به صورت باز نمایش داده شود یا بسته. به کمک خاصیت children میتوان زیر شاخهها را تا هر سطح و تعدادی که نیاز است تعریف نمود.
- خاصیتهای li_attr و a_attr کاملا دلخواه هستند. برای مثال در اینجا دو خاصیت href و data را در کلاسهای مرتبط با آنها مشاهده میکنید. میتوانید در اینجا به هر تعداد ویژگی سفارشی دیگری که جهت تعریف یک گره نیاز است، خاصیت اضافه کنید.
سادهترین مثالی که از ساختار فوق میتواند استفاده کند، اکشن متد زیر است:
[HttpPost] public ActionResult GetTreeJson() { var nodesList = new List<JsTreeNode>(); var rootNode = new JsTreeNode { id = "dir", text = "Root 1", icon = Url.Content("~/Content/images/tree_icon.png"), a_attr = { href = "http://www.bing.com" } }; nodesList.Add(rootNode); nodesList.Add(new JsTreeNode { id = "test1", text = "Root 2", icon = Url.Content("~/Content/images/tree_icon.png"), a_attr = { href = "http://www.bing.com" } }); return Json(nodesList, JsonRequestBehavior.AllowGet); }
بنابراین ساختارهای خود ارجاع دهنده را به خوبی میتوان با این افزونه وفق داد.
فعال سازی اولیه سمت کلاینت افزونه jsTree
برای استفادهی پویای از این افزونه در سمت کلاینت، فقط نیاز به یک DIV خالی است:
<div id="jstree"> </div>
$('#jstree').jstree({ "core": { "multiple": false, "check_callback": true, 'data': { 'url': '@getTreeJsonUrl', "type": "POST", "dataType": "json", "contentType": "application/json; charset=utf8", 'data': function (node) { return { 'id': node.id }; } }, 'themes': { 'variant': 'small', 'stripes': true } }, "types": { "default": { "icon": '@Url.Content("~/Content/images/bookmark_book_open.png")' }, }, "plugins": ["contextmenu", "dnd", "state", "types", "wholerow", "sort", "unique"], "contextmenu": { "items": function (o, cb) { var items = $.jstree.defaults.contextmenu.items(); items["create"].label = "ایجاد زیر شاخه"; items["rename"].label = "تغییر نام"; items["remove"].label = "حذف"; var cpp = items["ccp"]; cpp.label = "ویرایش"; var subMenu = cpp["submenu"]; subMenu["copy"].label = "کپی"; subMenu["paste"].label = "پیست"; subMenu["cut"].label = "برش"; return items; } } });
- multiple : false به این معنا است که نمیخواهیم کاربر بتواند چندین گره را با نگه داشتن دکمهی کنترل انتخاب کند.
- check_callback : true کدهای مرتبط با منوی کلیک سمت راست ماوس را فعال میکند.
- در قسمت data کار تبادل اطلاعات با سرور جهت دریافت فرمت JSON ایی که به آن اشاره شد، انجام میشود. متغیر getTreeJsonUrl یک چنین شکلی را میتواند داشته باشد:
@{ ViewBag.Title = "Demo"; var getTreeJsonUrl = Url.Action(actionName: "GetTreeJson", controllerName: "Home"); }
- در قسمت types که مرتبط است با افزونهای به همین نام، آیکن پیش فرض یک نود جدید ایجاد شده را مشخص کردهایم.
- گزینهی plugins، لیست افزونههای اختیاری این افزونه را مشخص میکند. برای مثال contextmenu منوی کلیک سمت راست ماوس را فعال میکند، dnd همان کشیدن و رها کردن گرهها است در زیر شاخههای مختلف. افزونهی state، انتخاب جاری کاربر را در سمت کلاینت ذخیره و در مراجعهی بعدی او بازیابی میکند. با ذکر افزونهی wholerow سبب میشویم که انتخاب یک گره، معادل انتخاب یک ردیف کامل از صفحه باشد. افزونهی sort کار مرتب سازی خودکار اعضای یک زیر شاخه را انجام میدهد. افزونهی unique سبب میشود تا در یک زیر شاخه نتوان دو عنوان یکسان را تعریف کرد.
- در قسمت contextmenu نحوهی بومی سازی گزینههای منوی کلیک راست ماوس را مشاهده میکنید. در حالت پیش فرض، عناوینی مانند create، rename و امثال آن نمایش داده میشوند که به نحو فوق میتوان آنرا تغییر داد.
با همین حد تنظیم، این افزونه کار نمایش سلسله مراتبی اطلاعات JSON ایی دریافت شده از سرور را انجام میدهد.
ذخیره سازی گرههای جدید و تغییرات سلسله مراتب پویای تعریف شده در سمت سرور
همانطور که عنوان شد، اگر افزونهی اختیاری contextmenu را فعال کنیم، امکان افزودن، ویرایش و حذف گرهها و زیر شاخهها را خواهیم یافت. برای انتقال این تغییرات به سمت سرور، باید به نحو ذیل عمل کرد:
$('#jstree').jstree({ // تمام تنظیمات مانند قبل }).on('delete_node.jstree', function (e, data) { }) .on('create_node.jstree', function (e, data) { }) .on('rename_node.jstree', function (e, data) { }) .on('move_node.jstree', function (e, data) { }) .on('copy_node.jstree', function (e, data) { }) .on('changed.jstree', function (e, data) { }) .on('dblclick.jstree', function (e) { }) .on('select_node.jstree', function (e, data) { });
در تمام این حالات، جایی که data در اختیار ما است، میتوان یک چنین ساختار جاوا اسکریپتی را برای ارسال به سرور طراحی کرد:
function postJsTreeOperation(operation, data, onDone, onFail) { $.post('@doJsTreeOperationUrl', { 'operation': operation, 'id': data.node.id, 'parentId': data.node.parent, 'position': data.position, 'text': data.node.text, 'originalId': data.original ? data.original.id : data.node.original.id, 'href': data.node.a_attr.href }) .done(function (result) { onDone(result); }) .fail(function (result) { alert('failed.....'); onFail(result); }); }
.on('create_node.jstree', function (e, data) { postJsTreeOperation('CreateNode', data, function (result) { data.instance.set_id(data.node, result.id); }, function (result) { data.instance.refresh(); }); })
و معادل سمت سرور دریافت کنندهی این اطلاعات، اکشن متد ذیل میتواند باشد:
[HttpPost] public ActionResult DoJsTreeOperation(JsTreeOperationData data) { switch (data.Operation) { case JsTreeOperation.CopyNode: case JsTreeOperation.CreateNode: //todo: save data var rnd = new Random(); // آی دی رکورد پس از ثبت در بانک اطلاعاتی دریافت و بازگشت داده شود return Json(new { id = rnd.Next() }, JsonRequestBehavior.AllowGet); case JsTreeOperation.DeleteNode: //todo: save data return Json(new { result = "ok" }, JsonRequestBehavior.AllowGet); case JsTreeOperation.MoveNode: //todo: save data return Json(new { result = "ok" }, JsonRequestBehavior.AllowGet); case JsTreeOperation.RenameNode: //todo: save data return Json(new { result = "ok" }, JsonRequestBehavior.AllowGet); default: throw new InvalidOperationException(string.Format("{0} is not supported.", data.Operation)); } }
namespace MvcJSTree.Models { public enum JsTreeOperation { DeleteNode, CreateNode, RenameNode, MoveNode, CopyNode } public class JsTreeOperationData { public JsTreeOperation Operation { set; get; } public string Id { set; get; } public string ParentId { set; get; } public string OriginalId { set; get; } public string Text { set; get; } public string Position { set; get; } public string Href { set; get; } } }
در اینجا Href را نیز مشاهده میکنید. همانطور که عنوان شد، اعضای JsTreeNodeAAttributes اختیاری هستند. بنابراین اگر این اعضاء را تغییر دادید، باید خواص JsTreeOperationData و همچنین اعضای شیء تعریف شده در postJsTreeOperation را نیز تغییر دهید تا با هم تطابق پیدا کنند.
چند نکتهی تکمیلی
اگر میخواهید که با دوبار کلیک بر روی یک گره، کاربر به href آن هدایت شود، میتوان از کد ذیل استفاده کرد:
var selectedData; // ... .on('dblclick.jstree', function (e) { var href = selectedData.node.a_attr.href; alert('selected node: ' + selectedData.node.text + ', href:' + href); // auto redirect if (href) { window.location = href; } // activate edit mode //var inst = $.jstree.reference(selectedData.node); //inst.edit(selectedData.node); }) .on('select_node.jstree', function (e, data) { //alert('selected node: ' + data.node.text); selectedData = data; });
حتی اگر خواستید که با دوبار کلیک بر روی یک گره، گزینهی ویرایش آن فعال شود، کدهای آن را به صورت کامنت مشاهده میکنید.
مثال کامل این بحث را از اینجا میتوانید دریافت کنید:
MvcJSTree.zip