بازخوردهای پروژه‌ها
عدم اجرا در هاست
سلام 
من در هاست‌های قبلی که فقط قابلیت asp.net 4 رو داشتن پروژه mvc  رو آپلود میکردم و اجرا میشد
اما این پروژه اجرا نمیشه باید چکار کنم که view Home بصورت پیش فرض با Action مورد نظر اجرا بشن؟
مرسی
نظرات مطالب
نمایش، ذخیره و چاپ فایل‌های PDF در برنامه‌های Angular
یک نکته‌ی تکمیلی

قسمت «دریافت فایل PDF» از نکته‌ی «HTML5 download attribute » استفاده می‌کند.
<a href="computer.png" download="PC.png">
  Download File
</a>
- استفاده از ویژگی استاندارد download سبب می‌شود که کاربر بجای مرور آدرس، آن‌را دریافت کند.
- اگر این ویژگی به نامی هم مزین شود، این نام، نام فایل دریافتی خواهد بود.
مطالب
توسعه کنترلر و مدل در F# MVC4
در پست قبلی با F# MVC4 Template آشنا شدید. در این پست به توسعه کنترلر و مدل در قالب مثال خواهم پرداخت. برای شروع ابتدا یک پروژه همانند مثال ذکر شده در پست قبلی ایجاد کنید. در پروژه #C ساخته شده که صرفا برای مدیریت View‌ها است یک View جدید به صورت زیر ایجاد نمایید:
@model IEnumerable<FsWeb.Models.Book>
<!DOCTYPE html>
<html>
<head>
    <title>@ViewBag.Title</title>
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <link rel="stylesheet" 
        href="http://code.jquery.com/mobile/1.0.1/jquery.mobile-1.0.1.min.css" />
</head>
 
<body>
    <div data-role="page" data-theme="a" id="booksPage">
        <div data-role="header">
            <h1>Guitars</h1>
        </div>
        <div data-role="content">
        <ul data-role="listview" data-filter="true" data-inset="true"> 
            @foreach(var x in Model) {
                <li><a href="#">@x.Name</a></li>
            }
        </ul>
        </div>
    </div>
    <script src="http://code.jquery.com/jquery-1.6.4.min.js">
    </script>
    <script src="http://code.jquery.com/mobile/1.0.1/jquery.mobile-1.0.1.min.js">
    </script>

    <script>
        $(document).delegate("#bookPage", 'pageshow', function (event) {
            $("div:jqmData(role='content') > ul").listview('refresh');
        });
    </script>
</body>
</html>
از آنجا که هدف از این پست آشنایی با بخش #F پروژه‌های وب است در نتیجه نیازی به توضیح کد‌های بالا دیده نمی‌شود. 
برای ساخت کنترلر جدید، در پروژه #F ساخته شده یک Source File ایجاد نمایید و کد‌های زیر را در آن کپی نمایید:
namespace FsWeb.Controllers

open System.Web.Mvc
open FsWeb.Models

[<HandleError>]
type BooksController() =
    inherit Controller()
    member this.Index () =   
        seq { yield  Book(Name = "My F# Book")
              yield Book(Name = "My C# Book") }
        |> this.View
در کد‌های بالا ابتدا یک کنترلر به نام BookController ایجاد کردیم که از کلاس Controller ارث برده است(با استفاده از inherit).  سپس یک تابع به نام Index داریم(به عنوان Action مورد نظر در کنترلر) که آرایه ای از کتاب‌ها به عنوان پارامتر به تابع View می‌فرستد.( توسط اپراتور Pipe - <|). در نهایت دستور this.View  معادل فراخوانی اکشن ()View در پروژه‌های #C است که View متناظر با اکشن را فراخوانی می‌کند. همان طور که ملاحظه می‌نمایید بسیار شبیه به پیاده سازی #C است.
اما نکته ای که در مثال بالا وجود دارد این است که دو نمونه از نوع Book را برای ساخت seq وهله سازی می‌کند. در نتیجه باید Book Type را به عنوان مدل تعریف کنیم. به صورت زیر:
namespace FsWeb.Models
 type Book = { Id : Guid; Name : string }
البته در F# 3.0 امکانی فراهم شده است به نام Auto-Properties که شبیه تعریف خواص در #C است. در نتیجه می‌توان تعریف بالا را به صورت زیر نیز بازنویسی کرد:
namespace FsWeb.Models 
type Book() = member val Name = "" with get, set
Attribute‌ها مدل
اگر همچون پروژه‌های #C قصد دارید با استفاده از Attribute‌ها مدل خود را اعتبارسنجی نمایید می‌توانید به صورت زیر اقدام نمایید:
open System.ComponentModel.DataAnnotations
 type Book() = [<Required>] member val Name = "" with get, set
هم چنین می‌توان Attribute‌های مورد نظر برای مدل EntityFramework را نیز اعمال نمود(نظیر Key):
namespace FsWeb.Models 
open System 
open System.ComponentModel.DataAnnotations 
type Book() = [<Key>] member val Id = Guid.NewGuid() with get, set 
[<Required>] member val Name = "" with get, set

نکته: دستور open معادل با using در #C است.
در پست بعدی برای تکمیل مثال جاری، روش طراحی Repository با استفاده از EntityFramework بررسی خواهد شد.
پاسخ به بازخورد‌های پروژه‌ها
نیاز به کمی Refactoring
از اینکه به این پروژه توجه کردید از شما ممنونم امیدوارم با راهنمایی‌ها شما کیفیت پروژه و کیفیت کار خودم رو بالا ببرم. 
-  پیشنهاد شما اینه پوشه آیکون‌ها که به ازای هر پسوند یک عکس وجود داره اسکن بشه و متناظر با پسوند یه عکس برگردونه بشه ، برای اینکه به ازای هر درخواست یکبار پوشه اسکن نشه باید یک لیست یا دیتابیس و یا کش درست کنیم. موردی که من لحاظ کردم اینه که اگر خواستیم به ازای ده‌ها پسوند فایل تصویری یک آیکون نشون داده بشه نیاز به ده آیکون تکراری وجود نداشته باشه هم پوشه آیکون‌ها ممکنه خیلی شلوغ بشه (مثلا هزار پسوند) و هم اینکه مرورگر کاربر به ازای هر پسوند باید یک کانکشن به سایت بزنه .
- برایrefactoring  و سرویس چون از حالت آموزش شروع کردم گفتم در مراحل بعد refactoring  رو انجام بدم ولی وقتی شما ایراد گرفتید حتما باید درست کنم. 
- اگر بدنه کنترل‌ها رو با سرویس سبک کنم کافی نیست ؟اگه بخوام کنترلر  FileManager رو به چند کنترلر کوچکتر کنم برای نام گذاری چه روشی پیشنهاد می‌کنید که از خوانایی برنامه کم نشه؟ 
مطالب
Syntax highlighting در بلاگر!

اگر علاقمند باشید که syntax highlighting را به سورس کدهای ارسالی در بلاگر اضافه کنید، روش کار به صورت زیر است:
از آنجائیکه دسترسی به سرور و راه‌ حل‌های سمت سرور را نخواهیم داشت، تنها راه حل باقیمانده استفاده از روش‌های سمت کلاینت است. کتابخانه زیر این امر را میسر می‌سازد:
http://code.google.com/p/syntaxhighlighter/
این کتابخانه، کار Syntax highlighting سمت کلاینت را با استفاده از JavaScript انجام می‌دهد.

پس از دریافت آن (احتمالا به یک پروکسی نیاز پیدا خواهید کرد ...)، فایل‌ها را در یک سرور قرار دهید. (برای مثال در Google pages)
سپس به قسمت ویرایش html قالب سایت مراجعه کنید و کدهای زیر را به آن اضافه نمائید (درصورت نیاز مسیرهای فایل‌ها را ویرایش کنید):
<link href='http://vahid.nasiri.googlepages.com/SyntaxHighlighter.css' rel='stylesheet' type='text/css'/>
<script src='http://vahid.nasiri.googlepages.com/shCore.js' type='text/javascript'/>
<script src='http://vahid.nasiri.googlepages.com/shBrushCpp.js' type='text/javascript'/>
<script src='http://vahid.nasiri.googlepages.com/shBrushCSharp.js' type='text/javascript'/>
<script src='http://vahid.nasiri.googlepages.com/shBrushCss.js' type='text/javascript'/>
<script src='http://vahid.nasiri.googlepages.com/shBrushJava.js' type='text/javascript'/>
<script src='http://vahid.nasiri.googlepages.com/shBrushJScript.js' type='text/javascript'/>
<script src='http://vahid.nasiri.googlepages.com/shBrushSql.js' type='text/javascript'/>
<script src='http://vahid.nasiri.googlepages.com/shBrushXml.js' type='text/javascript'/>

<script class='javascript'>
//<![CDATA[
function FindTagsByName(container, name, Tag)
{
var elements = document.getElementsByTagName(Tag);
for (var i = 0; i < elements.length; i )
{
if (elements[i].getAttribute("name") == name)
{
container.push(elements[i]);
}
}
}
var elements = [];
FindTagsByName(elements, "code", "pre");
FindTagsByName(elements, "code", "textarea");

for(var i=0; i < elements.length; i ) {
if(elements[i].nodeName.toUpperCase() == "TEXTAREA") {
var childNode = elements[i].childNodes[0];
var newNode = document.createTextNode(childNode.nodeValue.replace(/<br\s*\/?>/gi,'\n'));
elements[i].replaceChild(newNode, childNode);

}
else if(elements[i].nodeName.toUpperCase() == "PRE") {
brs = elements[i].getElementsByTagName("br");
for(var j = 0, brLength = brs.length; j < brLength; j ) {
var newNode = document.createTextNode("\n");
elements[i].replaceChild(newNode, brs[0]);
}
}
}
// dp.SyntaxHighlighter.ClipboardSwf =
//"http://vahid.nasiri.googlepages.com/clipboard.swf";
dp.SyntaxHighlighter.HighlightAll("code");
//]]>
</script>


خطوط فوق باید پس از تگ‌های زیر در قالب استاندارد قرار داده شوند:
</div></div> <!-- end outer-wrapper -->

از این پس جهت استفاده از این قابلیت تنها کافی است از تگ‌های pre یا textarea استفاده کنید (در قسمت html ارسال مطلب) و name را مساوی code قرار داده و language را مساوی زبان مورد نظر. برای مثال:

<div align="left" dir="ltr">
<pre name='code' language='sql'>
--get login time
SELECT login_time FROM master..sysprocesses WHERE spid = 1
</pre>
</div>

که نتیجه نهایی به صورت زیر خواهد بود:
--get login time
SELECT login_time FROM master..sysprocesses WHERE spid = 1

همچنین باید دقت داشت که مجاز به ارسال کاراکترهای غیرمجاز در xml (<>\'&) در کدهای خود نیستید و این کاراکترها سبب خواهند شد که کد شما نمایش داده نشوند. به همین جهت همانطور که پیشتر نیز ذکر شد می‌توان از سرویس سایت http://www.elliotswan.com/postable/ استفاده کرد.

ماخذ:
http://developertips.blogspot.com/2007/08/syntaxhighlighter-on-blogger.html



مطالب
معرفی برنامه‌ی Subtitle Tools

این روزها زیرنویس‌های فارسی فیلم‌های روز دنیا را راحت می‌شود در اینترنت یافت، اما مشکلات زیادی هم به همراه این نوع فایل‌ها وجود دارند:
- گاها با فیلم دریافت شده هماهنگ نیستند.
- عموما با فرمت windows-1256 تهیه می‌شوند که برای استفاده از آن‌ها در سیستم‌های مختلف بهتر است به UTF8 تبدیل شوند.
- اکثر برنامه‌های موجود برای کار با زیر نویس‌ها و ویرایش آن‌ها، درکی از یونیکد ندارند.
- عموما نیاز است جهت استفاده از آن‌ها در یک جمع، تعدادی از سطور آن‌ها را با حفظ شماره بندی فایل، حذف کرد!
و ...

به همین جهت نیاز به یک برنامه‌ی جمع و جور جهت کار با زیر نویس‌ها داشتم که نتیجه‌ی آن تهیه‌ی برنامه‌ی زیر شد:

الف) تغییر encoding فایل دریافتی به UTF-8
هنگام گشودن اکثر فایل‌های زیر نویس فارسی با تصویر زیر روبرو خواهید شد:



برای تبدیل آن به فرمت یونیکد تنها کافی است بر روی دکمه‌ی To UTF-8 کلیک کنید. در این حالت نتیجه به صورت زیر خواهد بود:



ب) هماهنگ سازی زیر نویس با فیلم دریافتی
ممکن است دیالوگ‌های زیر نویس دریافتی شما به اندازه‌ی مدت زمان خاصی، از دیالوگ‌های فیلم در حال پخش، عقب‌تر یا جلوتر باشند. در این حالت ابتدا باید این مدت زمان را با گوش فرا دادن به ابتدای فیلم و مقایسه‌ی آن با اولین شماره‌ی زیرنویس، پیدا کنید. مثلا پس از بررسی متوجه شده‌اید که به کلیه زمان‌های زیرنویس جاری باید 7 ثانیه اضافه گردد. اینکار را مطابق تصویر ذیل می‌توان انجام داد:



ج) یکی کردن زمان‌های دو زیر نویس با هم
گاهی از اوقات مشکلات زمانی یک زیرنویس وخیم‌تر از مورد ب است. به این معنا که شماره‌های مختلف آن، زمان‌های کاملا بی‌ربطی داشته و به صورت هماهنگ، قابل افزایش یا کاهش نیستند. در این حالت ابتدا زیر نویس مورد نظر را در برنامه باز کنید. سپس یک زیر نویس خوب انگلیسی هماهنگ با فیلم را نیز پیدا کنید و از طریق دکمه‌ی Merge times به برنامه معرفی نمائید. در این حالت به صورت خودکار زمان‌های صحیح از زیر نویس دوم به زیر نویس اول منتقل خواهند شد.

د) یکی کردن دو فایل زیر نویس با هم
ممکن است فیلم دریافتی تنها از یک فایل تشکیل شده باشد، اما شما فقط توانسته‌اید زیر نویس مرتبط با نگارش دو سی دی این فیلم را پیدا کنید. بنابراین نیاز است تا این دو فایل زیر نویس یکی شوند با این شرط که زمان‌های سی دی دوم از انتهای سی دی اول در فایل نهایی یکی شده‌ی تولیدی شروع گردد.
برای این منظور ابتدا فایل زیرنویس سی دی اول را در برنامه باز کرده و سپس به گزینه‌ی join files در برنامه مراجعه کنید. در اینجا ابتدا مسیر فایل زیرنویس سی دی 2 را مشخص نمائید. برنامه سعی خواهد کرد تا زمان آغاز قسمت دوم را بر اساس آخرین زمان سی دی اول و اولین زمان سی دی دوم حدس بزند و نمایش دهد. یا می‌توانید این مقدار پیش فرض را پذیرفته و بر روی دکمه‌ی join کلیک نمائید و یا امکان تغییر دستی آن هم میسر است.



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



در این حالت اگر نیاز است سطری حذف شود، آن‌را انتخاب نموده و بر روی دکمه‌ی Delete row کلیک نمائید. در این حالت علاوه بر حذف ردیف، کلیه شماره‌های موجود در زیر نویس نیز به صورت خودکار مجددا تولید و مرتب خواهند شد. (کلیک راست بر روی هر ردیف نیز این گزینه‌ را نمایش می‌دهد)
یا اگر مشغول به ویرایش متنی شدید، پس از ویرایش، کلیک کردن بر روی دکمه‌ی Save را فراموش نکنید (در حالت حذف نیازی به اینکار نیست).

ز) پیدا کردن زیرنویس یک فایل بر اساس امضای دیجیتال آن
سایت opensubtitles.org یک API را جهت پیدا کردن زیرنویسی مطابق با هش یک فایل ویدیویی ارائه داده است، که در برنامه‌ی جاری، کلاینتی برای آن تهیه شده است:



فقط کافی است فایل ویدیویی خود را در این قسمت انتخاب نمائید. برنامه هش فایل را محاسبه کرده و سپس با کمک سرویس XML-RPC سایت opensubtitles.org سعی در یافتن زیرنویس هماهنگ با آن خواهد کرد. در اینجا دیگر مهم نیست نام فایل انتخابی چیست؛ امضای دیجیتال آن مهم است. برای دریافت موارد مورد نظر، ابتدا آن‌ها را تیک زده و سپس بر روی دکمه‌ی دریافت کلیک کنید. کلیک راست بر روی ردیف مورد نظر نیز این امکان دریافت را لحاظ کرده است.
همچنین قسمتی هم برای آپلود زیرنویس به این سایت پیش بینی شده است (لطفا مصرف کننده‌ی صرف یا به قولی لیچر نباشید!)



در اینجا انتخاب فایل ویدیویی، فایل زیرنویس هماهنگ با آن و همچنین زبان زیر نویس الزامی است. از فایل ویدیویی جهت محاسبه‌ی هش آن برای یافتن ساده‌تر زیرنویس‌ها در دفعات آتی استفاده می‌گردد.

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


مطالب
React 16x - قسمت 17 - مسیریابی - بخش 3 - یک تمرین
به عنوان تمرین، همان برنامه‌ی طراحی گریدی را که تا قسمت 14 تکمیل کردیم، با معرفی مسیریابی بهبود خواهیم بخشید. برای این منظور یک NavBar بوت استرپی را به بالای صفحه اضافه می‌کنیم که دارای سه لینک movies ،customers و rentals است. به همین جهت نیاز به دو کامپوننت مقدماتی customers و rentals نیز وجود دارد که تنها یک h1 را نمایش می‌دهند. به علاوه منوی راهبری برنامه نیز باید بر اساس مسیر فعال جاری، با رنگ مشخصی، فعال بودن مسیریابی گزینه‌ی انتخابی را مشخص کند. در این برنامه اگر کاربر، آدرس نامعتبری را وارد کرد، باید به صفحه‌ی not-found هدایت شود. همچنین می‌خواهیم تمام عناوین فیلم‌های نمایش داده شده‌ی در جدول، تبدیل به لینک‌هایی به صفحه‌ی جدید جزئیات آن‌ها شوند. در این صفحه باید یک دکمه‌ی Save هم وجود داشته باشد تا با کلیک بر روی آن، به صورت خودکار به صفحه‌ی movies هدایت شویم.


برپایی پیش‌نیازها

ابتدا کتابخانه‌ی react-router-dom را نصب می‌کنیم:
 npm i react-router-dom --save
سپس کامپوننت App را با BrowserRouter آن در فایل index.js محصور می‌کنیم؛ تا کار انتقال مدیریت تاریخچه‌ی مرور صفحات در مرورگر، به درخت کامپوننت‌های React انجام شود:
import { BrowserRouter } from "react-router-dom";

//...

ReactDOM.render(
  <BrowserRouter>
    <App />
  </BrowserRouter>,
  document.getElementById("root")
);


ایجاد کامپوننت‌های جدید مورد نیاز

برای تکمیل نیازمندی‌هایی که در مقدمه عنوان شد، این کامپوننت‌های جدید را ایجاد می‌کنیم:
کامپوننت بدون حالت تابعی src\components\customers.jsx با این محتوا:
import React from "react";

const Customers = () => {
  return <h1>Customers</h1>;
};

export default Customers;

کامپوننت بدون حالت تابعی src\components\rentals.jsx با این محتوا:
import React from "react";

const Rentals = () => {
  return <h1>Rentals</h1>;
};

export default Rentals;

کامپوننت بدون حالت تابعی src\components\notFound.jsx با این محتوا:
import React from "react";

const NotFound = () => {
  return <h1>Not Found</h1>;
};

export default NotFound;

کامپوننت بدون حالت تابعی src\components\movieForm.jsx با این محتوا:
import React from "react";

const MovieForm = () => {
  return (
    <div>
      <h1>Movie Form</h1>
      <button className="btn btn-primary">Save</button>
    </div>
  );
};

export default MovieForm;


ثبت مسیریابی‌های مورد نیاز برنامه

پس از نصب کتابخانه‌ی مسیریابی و راه اندازی آن، اکنون نوبت به تعریف مسیریابی‌های مورد نیاز برنامه در فایل app.js است:
import "./App.css";

import React from "react";
import { Redirect, Route, Switch } from "react-router-dom";

import Customers from "./components/customers";
import Movies from "./components/movies";
import NotFound from "./components/notFound";
import Rentals from "./components/rentals";

function App() {
  return (
    <main className="container">
      <Switch>
        <Route path="/movies" component={Movies} />
        <Route path="/customers" component={Customers} />
        <Route path="/rentals" component={Rentals} />
        <Route path="/not-found" component={NotFound} />
        <Redirect to="/not-found" />
      </Switch>
    </main>
  );
}

export default App;
- در اینجا ابتدا چهار مسیریابی جدید را جهت نمایش صفحات کامپوننت‌هایی که ایجاد کردیم، تعریف و سپس نکته‌ی «مدیریت مسیرهای نامعتبر درخواستی» قسمت قبل را نیز با افزودن کامپوننت Redirect، پیاده سازی کرده‌ایم. به علاوه پیشتر نمایش کامپوننت Movies را داخل container تعریف شده داشتیم که اکنون با وجود این مسیریابی‌ها، نیازی به تعریف المان آن نیست و از return تعریف شده، حذف شده‌است.
تا اینجا اگر برنامه را اجرا کنیم، بلافاصله به http://localhost:3000/not-found هدایت می‌شویم. از این جهت که هنوز مسیریابی را برای / یا ریشه‌ی سایت که در ابتدا نمایش داده می‌شود، تنظیم نکرده‌ایم. به همین جهت Redirect زیر را پیش از آخرین Redirect تعریف شده اضافه می‌کنیم تا با درخواست ریشه‌ی سایت، به آدرس /movies هدایت شویم:
<Redirect from="/" to="/movies" />
و هانطور که در بخش 1 این قسمت بررسی کردیم، چون این مسیریابی با تمام آدرس‌های شروع شده‌ی با / تطابق پیدا می‌کند، وجود Switch در اینجا ضروری است؛ تا پس از انطباق با اولین مسیر ممکن، کار مسیریابی به پایان برسد. به علاوه با تعریف این Redirect، اگر مثلا آدرس نامعتبر http://localhost:3000/xyz را درخواست کنیم، به آدرس movies/ هدایت می‌شویم؛ چون / با xyz/ تطابق پیدا کرده و کار در همینجا به پایان می‌رسد. به همین جهت ذکر ویژگی exact در تعریف این Redirect ویژه ضروری است؛ تا صرفا به ریشه‌ی سایت پاسخ دهد:
<Redirect from="/" exact to="/movies" />


افزودن منوی راهبری به برنامه

ابتدا فایل جدید src\components\navBar.jsx را ایجاد می‌کنیم؛ با این محتوا:
import React from "react";
import { Link, NavLink } from "react-router-dom";

const NavBar = () => {
  return (
    <nav className="navbar navbar-expand-lg navbar-light bg-light">
      <Link className="navbar-brand" to="/">
        Home
      </Link>
      <button
        className="navbar-toggler"
        type="button"
        data-toggle="collapse"
        data-target="#navbarNavAltMarkup"
        aria-controls="navbarNavAltMarkup"
        aria-expanded="false"
        aria-label="Toggle navigation"
      >
        <span className="navbar-toggler-icon" />
      </button>
      <div className="collapse navbar-collapse" id="navbarNavAltMarkup">
        <div className="navbar-nav">
          <NavLink className="nav-item nav-link" to="/movies">
            Movies
          </NavLink>
          <NavLink className="nav-item nav-link" to="/customers">
            Customers
          </NavLink>
          <NavLink className="nav-item nav-link" to="/rentals">
            Rentals
          </NavLink>
        </div>
      </div>
    </nav>
  );
};

export default NavBar;
توضیحات:
- ساختار کلی NavBar ای را که ملاحظه می‌کنید، دقیقا از مثال‌های رسمی مستندات بوت استرپ 4 گرفته شده‌است و تمام classهای آن با className جایگزین شده‌اند.
- سپس تمام anchor‌های موجود در یک منوی راهبری بوت استرپ را به Link و یا NavLink تبدیل کرده‌ایم تا برنامه به صورت SPA عمل کند؛ یعنی با کلیک بر روی هر لینک، بارگذاری کامل صفحه در مرورگر صورت نگیرد و تنها محل و قسمتی که توسط کامپوننت‌های Route مشخص شده، به روز رسانی شوند. تفاوت NavLink با Link در کتابخانه‌ی react-router-dom، افزودن خودکار کلاس active به المانی است که بر روی آن کلیک شده‌است. به این ترتیب بهتر می‌توان تشخیص داد که هم اکنون در کجای منوی راهبری قرار داریم.
- پس از تبدیل anchor‌ها به Link و یا NavLink، مرحله‌ی بعد، تبدیل href‌های لینک‌های قبلی به ویژگی to است که هر کدام باید به یکی از مسیریابی‌های تنظیم شده، مقدار دهی گردد.

پس از تعریف کامپوننت منوی راهبری سایت، به app.js بازگشته و این کامپوننت را پیش از مسیریابی‌های تعریف شده اضافه می‌کنیم:
import NavBar from "./components/navBar";
// ...

function App() {
  return (
    <React.Fragment>
      <NavBar />
      <main className="container">
        // ...
      </main>
    </React.Fragment>
  );
}

export default App;
در اینجا چون نیاز به بازگشت دو المان NavBar و main وجود داشت، از React.Fragment برای محصور کردن آن‌ها استفاده کردیم.

به علاوه به فایل index.css برنامه مراجعه کرده و padding این navBar را صفر می‌کنیم تا از بالای صفحه و بدون فاصله‌ای نمایش داده شود و container اصلی نیز اندکی از پایین آن فاصله پیدا کند:
body {
  margin: 0;
  padding: 0 0 0 0;
  font-family: sans-serif;
}

.navbar {
  margin-bottom: 30px;
}

.clickable {
  cursor: pointer;
}
با این تغییر، اکنون ظاهر برنامه به صورت زیر در خواهد آمد:


اگر دقت کنید چون آدرس http://localhost:3000/movies در حال نمایش است، در منوی راهبری، گزینه‌ی متناظر با آن، با رنگی دیگر مشخص (فعال) شده‌است.


لینک کردن عناوین فیلم‌های نمایش داده شده به کامپوننت movieForm

برای تبدیل عناوین نمایش داده شده‌ی در جدول فیلم‌ها به لینک، به کامپوننت src\components\moviesTable.jsx مراجعه کرده و تغییرات زیر را اعمال می‌کنیم:
- در قدم اول باید بجای ذکر خاصیت Title در آرایه‌ی ستون‌های جدول:
class MoviesTable extends Component {
  columns = [
    { path: "title", label: "Title" },
یک محتوای لینک شده را نمایش دهیم:
class MoviesTable extends Component {
  columns = [
    {
      path: "title",
      label: "Title",
      content: movie => <Link to={`/movies/${movie._id}`}>{movie.title}</Link>
    },
در اینجا خاصیت content اضافه شده‌است تا یک المان React را مانند Link، بازگشت دهد و چون می‌خواهیم id هر فیلم نیز در اینجا ذکر شود، آن‌را به صورت arrow function تعریف کرده‌ایم تا شیء movie را گرفته و لینک به آن‌را تولید کند. در اینجا از یک template literal برای تولید پویای رشته‌ی منتسب به to استفاده کرده‌ایم.
همچنین این Link را هم باید در بالای این ماژول import کرد:
import { Link } from "react-router-dom";
تا اینجا عناوین فیلم‌ها را تبدیل به لینک‌هایی کردیم:



تعریف مسیریابی نمایش جزئیات یک فیلم انتخابی

اگر به تصویر فوق دقت کنید، به آدرس‌هایی مانند http://localhost:3000/movies/5b21ca3eeb7f6fbccd47181a رسیده‌ایم که به همراه id هر فیلم هستند. اکنون می‌خواهیم کلیک بر روی این لینک‌ها را جهت فعالسازی صفحه‌ی نمایش جزئیات فیلم، تنظیم کنیم. به همین جهت به فایل app.js مراجعه کرده و مسیریابی زیر را به ابتدای Switch تعریف شده اضافه می‌کنیم:
<Route path="/movies/:id" component={MovieForm} />
که نیاز به این import را هم دارد:
import MovieForm from "./components/movieForm";


تکمیل کامپوننت نمایش جزئیات یک فیلم

اکنون می‌خواهیم صفحه‌ی نمایش جزئیات فیلم، به همراه نمایش id فیلم باشد و همچنین با کلیک بر روی دکمه‌ی Save آن، کاربر را به صفحه‌ی movies هدایت کند. به همین جهت فایل src\components\movieForm.jsx را به صورت زیر ویرایش می‌کنیم:
import React from "react";

const MovieForm = ({ match, history }) => {
  return (
    <div>
      <h1>Movie Form {match.params.id} </h1>
      <button
        className="btn btn-primary"
        onClick={() => history.push("/movies")}
      >
        Save
      </button>
    </div>
  );
};

export default MovieForm;
توضیحات:
- چون این کامپوننت، یک کامپوننت تابعی بدون حالت است، props را باید از طریق آرگومان خود دریافت کند و البته در همینجا امکان Object Destructuring خواصی که از آن نیاز داریم، مهیا است؛ مانند { match, history } که ملاحظه می‌کنید.
- سپس شیء match، امکان دسترسی به params ارسالی به صفحه را مانند id فیلم، میسر می‌کند.
- با استفاده از شیء history و متد push آن می‌توان علاوه بر به روز رسانی تاریخچه‌ی مرورگر، به مسیر مشخص شده بازگشت که در همینجا و به صورت inline، تعریف شده‌است.


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: sample-17.zip
مطالب
ذخیره TreeView ساخته شده توسط KendoUI در Asp.net MVC
همانطور که از نمونه مثال‌های خود Kendo UI مشاهده میشود ، نحوه استفاده از TreeView آن به صورت زیر است :
<div>
@(Html.Kendo().TreeView()
        .Name("treeview")
        .TemplateId("treeview-template")
        .HtmlAttributes(new { @class = "demo-section" })
        .DragAndDrop(true)
        .BindTo(Model.Where(e=>e.ParentFolderID==null).OrderBy(e=>e.Order), mappings => 
        {
            mappings.For<DAL.Folder>(binding => binding
                    .ItemDataBound((item, folder) =>
                    {
                        item.Text = folder.FolderName;
                        item.SpriteCssClasses = "folder";
                        item.Expanded=true;
                        item.Id = folder.FolderID.ToString();
                    })
                    .Children(folder => folder.Folder1));
            mappings.For<DAL.Folder>(binding => binding
                    .ItemDataBound((item, folder) =>
                    {
                        item.Text = folder.FolderName;
                        item.SpriteCssClasses = " folder";
                        item.Expanded = true;
                        item.Id = folder.FolderID.ToString();
                    }));
        })
)
</div>

<style type="text/css" scoped>
    .demo-section {
        width: 200px;
    }
    
    #treeview  .k-sprite ,#treeview2 .k-sprite {
        background-image: url("@Url.Content("/Content/kendo/images/coloricons-sprite.png")");
    }
    .rootfolder { background-position: 0 0; }
    .folder { background-position: 0 -16px; }
    .pdf { background-position: 0 -32px; }
    .html { background-position: 0 -48px; }
    .image { background-position: 0 -64px; }
    .delete-link,.edit-link {
        width: 12px;
        height: 12px;
        overflow: hidden;
        display: inline-block;
        vertical-align: top;
        margin: 2px 0 0 3px;
        -webkit-border-radius: 5px;
        -mox-border-radius: 5px;
        border-radius: 5px;
    }
    .delete-link{
        background: transparent url("@Url.Content("/Content/kendo/images/close.png")") no-repeat 50% 50%;
    }
    .edit-link{
        background: transparent url("@Url.Content("/Content/kendo/images/edit.png")") no-repeat 50% 50%;
    }
</style>
استفاده از این TreeView ساده است ولی اگر احتیاج داشته باشیم که پس از drag&drop کردن گره‌ها آن را ذخیره کنیم چگونه باید عمل کنیم؟ این ریالسمت در خود Kendo تعبیه نشده است ، پس به صورت زیر عمل میکنیم :
پس از ساختن TreeView و اصلاح آن به شکل دلخواه لینک زیر را در ادامه اش می‌آوریم :
<a href="#" id="serialize">ذخیره</a>
سپس در تگ اسکریپت‌های خود این کد جاوا اسکریپت را برای serialize کردن تمام گره‌های TreeView مینویسیم :
$('#serialize').click(function () {
            serialized = serialize();
            window.location.href = "Folder/SaveMenu?serial=" + serialized + "!";
        });

        function serialize() {
            var tree = $("#treeview").data("kendoTreeView");
            var json = treeToJson(tree.dataSource.view());
            return JSON.stringify(json);
        }

        function treeToJson(nodes) {

            return $.map(nodes, function (n, i) {

                var result = { id: n.id};
                //var result = { text: n.text, id: n.id, expanded: n.expanded, checked: n.checked };
                if (n.hasChildren)
                    result.items = treeToJson(n.children.view());

                return result;
            });
        }
حال به تشریح کدها میپردازیم :
تابع serialize  درخط اول تمام عناصر داخلی treeview را گرفته ، به صورت یک datasource قابل انقیاد درآورده وسپس datasource آن را به یک نمایش قابل تجزیه تبدیل میکند و به متد treeToJSON میفرستد.
var tree = $("#treeview").data("kendoTreeView");
var json = treeToJson(tree.dataSource.view());

تابع  treeToJSON درخت را به عنوان یکسری گره گرفته و تمام عناصر آن را به فرمت json میبرد . (قسمتی که به صورت توضیحی درآمده میتواند برای بدست آوردن تمام اطلاعات گره از جمله متن و انتخاب شدن checkbox آن و غیره مورد استفاده قرار بگیرد) در ادامه این تابع اگر گره درحال استفاده فرزندی داشته باشد به صورت بازگشتی همین تابع برای آن فراخوانده میشود.
var result = { id: n.id};
 //var result = { text: n.text, id: n.id, expanded: n.expanded, checked: n.checked };
 if (n.hasChildren)
 result.items = treeToJson(n.children.view());

سپس اطلاعات برگشتی که در فرمت json هستند در خط آخر serialaize به رشته تبدیل میشوند و به رویداد کلیک که از آن فراخوانده شده بود بازمیگردند. 
return JSON.stringify(json);
خط آخر رویداد نیز یک Action در Controller مورد نظر را هدف قرار میدهد و رشته بدست آمده را به آن ارسال میکند و صفحه redirect میشود.
window.location.href = "Folder/SaveMenu?serial=" + serialized + "!";
رشته ای که با عنوان serialize به کنترلر ارسال میشود مانند زیر است :
"[{\"id\":\"2\"},{\"id\":\"5\",\"items\":[{\"id\":\"3\"},{\"id\":\"6\"},{\"id\":\"7\"}]}]!"
این رشته مربوط به درختی به شکل زیر است :

همانطور که میبینید گره دوم که "پوشه چهارم45" نام دارد شامل سه فرزند است که در رشته داده شده با عنوان item شناخته شده است. حال باید این رشته با برنامه نویسی سی شارپ جداسازی کرد :

string serialized;
Dictionary<int, int> numbers = new Dictionary<int, int>();   
public ActionResult SaveMenu(string serial)
        {
            var newfolders = new List<Folder>();
            serialized = serial;
            calculte_serialized(0);
            return RedirectToAction("Index");
        }
void calculte_serialized(int parent)
        {
            while (serialized.Length > 0)
            {
                var id_index=serialized.IndexOf("id");
                if (id_index == -1)
                {
                    return;
                }
                serialized = serialized.Substring(id_index + 5);
                var quote_index = serialized.IndexOf("\"");
                var id=serialized.Substring(0, quote_index);
                numbers.Add(int.Parse(id), parent);
                serialized = serialized.Substring(quote_index);
                var condition = serialized.Substring(0,3);
                switch (condition)
                {
                    case "\"},":
                        break;
                    case "\",\"":
                        calculte_serialized(int.Parse(id));
                        break;
                    case "\"}]":
                        return;
                        break;
                    default:
                        break;
                }
            }
        }

calculte_serialized با گرفتن 0 کار خود را شروع میکند یعنی از گره هایی که پدر ندارند و تمام id‌ها را همراه با پدرشان در یک دیکشنری میریزد و هرکجا که به فرزندی برخورد به صورت بازگشتی فراخوانی میشود. پس از اجرای کامل آن ما درخت را در یک دیکشنری به صورت عنصرهای مجزا در اختیار داریم که میتوانیم در پایگاه داده ذخیره کنیم.