اشتراک‌ها
کتابخانه jquery-rsSlideIt

Performs a smooth 2D/3D transition from one HTML element A to another element B.  2D Demo  3D Demo

  • Runs a single transition or a sequence of transitions;
  • Transition effect is automatically computed between two slides;
  • Fallbacks to browsers that do not support 3D transformations and CSS3 animations;
  • Highly customizable:
    • Any markup you want. Only restriction is sliders to be contained in a parent blocked element;
    • Strong event driven support;
    • Single transition or a sequence of transitions (movie mode);
    • Transition duration, easing function animation and zoom vertexes;
    • Optional user zoomming and panning between each transition.
  • Responsive design, suitable for any window sizes;
  • Optionally loads images asynchronously to save page load times, when slides are images.
کتابخانه jquery-rsSlideIt
اشتراک‌ها
تعدادی از ویژگی‌های پیشنهادی C# 9.0
// Before
public class Widget  
{
    private readonly int _foo;
    private readonly WidgetConfiguration _config;

    public Widget(int foo, WidgetConfiguration config)
    {
         _foo = foo;
         _config = config;
    }
}


// After
public class Widget  
{
     public Widget(int _foo, WidgetConfiguration _config)
     {
          // If you wanted one of these properties to be publicly accessible, you could define
          // and set one of those here, otherwise the arguments will be privately accessible
          // as fields.
     }
}
تعدادی از ویژگی‌های پیشنهادی C# 9.0
مطالب
Roslyn #3
بررسی Syntax tree

زمانیکه صحبت از Syntax می‌شود، منظور نمایش متنی سورس کدها است. برای بررسی و آنالیز آن، نیاز است این نمایش متنی، به ساختار داده‌ای ویژه‌ای به نام Syntax tree تبدیل شود و این Syntax tree مجموعه‌ای است از tokenها. Tokenها بیانگر المان‌های مختلف یک زبان، شامل کلمات کلیدی، عملگرها و غیره هستند.


در تصویر فوق، مراحل تبدیل یک قطعه کد #C را به مجموعه‌ای از tokenهای معادل آن مشاهده می‌کنید. علاوه بر این‌ها، Roslyn syntax tree شامل موارد ویژه‌ای به نام Trivia نیز هست. برای مثال در حین نوشتن کدها، در ابتدای سطرها تعدادی space یا tab وجود دارند و یا در این بین ممکن است کامنتی نوشته شود. هرچند این موارد از دیدگاه یک کامپایلر بی‌معنا هستند، اما ابزارهای Refactoring ایی که به Trivia دقت نداشته باشند، خروجی کد به هم ریخته‌ای را تولید خواهند کرد و سبب سردرگمی استفاده کنندگان می‌شوند.


در تصویر فوق، اشاره‌گر ادیتور پس از تایپ semicolon قرار گرفته‌است. در این حالت می‌توانید دو نوع trivia مخصوص فضای خالی و کامنت‌ها را در syntax visualizer، مشاهده کنید.
به علاوه پس از هر token بازه‌ای از اعداد را مشاهده می‌کنید که بیانگر محل قرارگیری آن‌ها در سورس کد هستند. این محل‌ها جهت ارائه‌ی خطاهای دقیق مرتبط با آن نقاط، بسیار مفید هستند.
یک Syntax tree از مجموعه‌ای از syntax nodes تشکیل می‌شود و هر node شامل مواردی مانند تعاریف، عبارات و امثال آن است. در افزونه‌ی Syntax visualizer نودهایی که رنگ قرمز متمایل به قهوه‌ای دارند، بیانگر نودهای Trivia، نودهای آبی، Syntax nodes و نودهای سبز، Syntax token هستند.


مفاهیم این رنگ‌ها را با کلیک بر روی دکمه‌ی Legend هم می‌توان مشاهده کرد.


تفاوت Syntax با Semantics

در Roslyn امکان کار با Syntax و Semantics کدها وجود دارد.
یک Syntax، از گرامر زبان خاصی پیروی می‌کند. در Syntax اطلاعات بسیار زیادی وجود دارند که معنای برنامه را تغییر نمی‌دهند؛ مانند کامنت‌ها، فضاهای خالی و فرمت ویژه‌ی کدها. البته فضاهای خالی در زبان‌هایی مانند پایتون دارای معنا هستند؛ اما در سی‌شارپ خیر. همچنین در Syntax، توافق نامه‌ای وجود دارد که بیانگر تعدادی واژه‌ی از پیش رزرو شده، مانند کلمات کلیدی هستند.
اما Semantics در نقطه‌ی مقابل Syntax قرار می‌گیرد و بیانگر معنای سورس کد است. برای مثال در اینجا تقدم و تاخر عملگرها مفهوم پیدا می‌کنند و یا اینکه Type system چیست و چه نوع‌هایی را می‌توان به دیگری نسبت داد و تبدیل کرد. عملیات Binding در این مرحله رخ می‌دهد و مفهوم identifierها را مشخص می‌کند. برای مثال x در این قسمت از سورس کد، به چه معنایی است و به کجا اشاره می‌کند؟


خواص ویژه‌ی Syntax tree در Roslyn

- تمام اجزای کد را شامل عناصر سازنده‌ی زبان و همچنین Trivia، به همراه دارد.
- API آن توسط کتابخانه‌های ثالث قابل دسترسی است.
- Immutable طراحی شده‌است. به این معنا که زمانیکه syntax tree توسط Roslyn ایجاد شد، دیگر تغییر نمی‌کند. به این ترتیب امکان دسترسی همزمان و موازی به آن بدون نیاز به انواع قفل‌های مسایل همزمانی وجود دارد. اگر کتابخانه‌ی ثالثی به Syntax tree ارائه شده دسترسی پیدا می‌کند، می‌تواند کاملا مطمئن باشد که این اطلاعات دیگر تغییری نمی‌کنند و نیازی به قفل کردن آن‌ها نیست. همچنین این مساله امکان استفاده‌ی مجدد از sub treeها را در حین ویرایش کدها میسر می‌کند. به آن‌ها mutating trees نیز گفته می‌شود.
- مقاوم است در برابر خطاها. اگر از قسمت اول به خاطر داشته باشید، Roslyn می‌بایستی جایگزین کامپایلر دومی به نام کامپایلر پس زمینه‌ی ویژوال استودیو که خطوط قرمزی را ذیل سطرهای مشکل دار ترسیم می‌کند، نیز می‌شد. فلسفه‌ی طراحی این کامپایلر، مقاوم بودن در برابر خطاهای تایپی و هماهنگی آن با تایپ کدها توسط برنامه نویس بود. Syntax tree در Roslyn نیز چنین خاصیتی را دارد و اگر مشغول به تایپ شوید، باز هم کار کرده و اینبار خطاهای موجود را نمایش می‌دهد که می‌تواند توسط ابزارهای نمایش دهنده‌ی ویژوال استودیو یا سایر ابزارهای ثالث استفاده شود.


برای نمونه در تصویر فوق، تایپ semicolon فراموش شده‌است؛ اما همچنان Syntax tree در دسترس است و به علاوه گزارش می‌دهد که semicolon مفقود است و تایپ نشده‌است.


Parse سورس کد توسط Roslyn

ابتدا یک پروژه‌ی کنسول ساده‌ی دات نت 4.6 را در VS 2015 آغاز کنید. سپس از طریق خط فرمان نیوگت، دستور ذیل را صادر نمائید:
 PM> Install-Package Microsoft.CodeAnalysis
به این ترتیب API لازم جهت کار با Roslyn به پروژه اضافه خواهند شد.
سپس کدهای ذیل را به آن اضافه کنید:
using System;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
 
namespace Roslyn01
{
    class Program
    {
        static void Main(string[] args)
        {
            parseText();
        }
 
        static void parseText()
        {
            var tree = CSharpSyntaxTree.ParseText("class Foo { void Bar(int x) {} }");
            Console.WriteLine(tree.ToString());
            Console.WriteLine(tree.GetRoot().NormalizeWhitespace().ToString());
 
            var res = SyntaxFactory.ClassDeclaration("Foo")
                .WithMembers(SyntaxFactory.List<MemberDeclarationSyntax>(new[] {
                    SyntaxFactory.MethodDeclaration(
                        SyntaxFactory.PredefinedType(
                            SyntaxFactory.Token(SyntaxKind.VoidKeyword)
                        ),
                        "Bar"
                    )
                    .WithBody(SyntaxFactory.Block())
                }))
                .NormalizeWhitespace();
 
            Console.WriteLine(res);
        } 
    }
}
توضیحات:
کار Parse سورس کد دریافتی، بر اساس سرویس‌های زبان متناظر با آن‌ها آغاز می‌شود. برای مثال سرویس‌هایی مانند VisualBasicSyntaxTree و یا CSharpSyntaxTree مثال فوق که سورس کد مورد آنالیز آن، از نوع سی‌شارپ است.
این کلاس‌های Factory، دارای دو متد Create و ParseText هستند. کار متد ParseText آن مشخص است؛ یک قطعه‌ی متنی از کد را آنالیز کرده و معادل Syntax Tree آن‌را تولید می‌کند. متد Create آن، اشیایی مانند نودهای Syntax visualizer را دریافت کرده و بر اساس آن‌ها یک Syntax tree را تولید می‌کند.
کار با متد Create آنچنان ساده نیست. به همین جهت یکی از اعضای تیم Roslyn برنامه‌ای را به نام Roslyn Quoter ایجاد کرده‌است که نسخه‌ی آنلاین آن‌را در اینجا و سورس کد آن‌را در اینجا می‌توانید بررسی کنید.
جهت آزمایش، همان قطعه‌ی متنی سورس کد مثال فوق را در نسخه‌ی آنلاین آن جهت آنالیز و تولید ورودی متد Create، وارد کنید. خروجی آن‌را می‌توان مستقیما در متد Create بکار برد.


فرمت کردن خودکار کدها به کمک Roslyn

اگر بر روی tree حاصل، متد ToString را فراخوانی کنیم، خروجی آن مجددا سورس کد مورد آنالیز است. اگر علاقمند بودید که Roslyn به صورت خودکار کدهای ورودی را فرمت کند و تمام آن‌ها را در یک سطر نمایش ندهد، متد NormalizeWhitespace را بر روی ریشه‌ی Syntax tree فراخوانی کنید:
 tree.GetRoot().NormalizeWhitespace().ToString()
اینبار خروجی فراخوانی فوق به صورت ذیل است:
class Foo
{
    void Bar(int x)
    {
    }
}


کوئری گرفتن از سورس کد توسط Roslyn

در ادامه قصد داریم با سه روش مختلف کوئری گرفتن از Syntax tree، آشنا شویم. برای این منظور متد ذیل را به پروژه‌ای که در ابتدای برنامه آغاز کردیم، اضافه کنید:
static void querySyntaxTree()
{
    var tree = CSharpSyntaxTree.ParseText("class Foo { void Bar() {} }");
    var node = (CompilationUnitSyntax)tree.GetRoot();
 
    // Using the object model
    foreach (var member in node.Members)
    {
        if (member.Kind() == SyntaxKind.ClassDeclaration)
        {
            var @class = (ClassDeclarationSyntax)member;
 
            foreach (var member2 in @class.Members)
            {
                if (member2.Kind() == SyntaxKind.MethodDeclaration)
                {
                    var method = (MethodDeclarationSyntax)member2;
                    // do stuff
                }
            }
        }
    }
 
 
    // Using LINQ query methods
    var bars = from member in node.Members.OfType<ClassDeclarationSyntax>()
               from member2 in member.Members.OfType<MethodDeclarationSyntax>()
               where member2.Identifier.Text == "Bar"
               select member2;
    var res = bars.ToList();
 
 
    // Using visitors
    new MyVisitor().Visit(node);
}
توضیحات:
روش اول کوئری گرفتن از Syntax tree، استفاده از object model آن است. در اینجا هربار، نوع و Kind هر نود را بررسی کرده و در نهایت به اجزای مدنظر خواهیم رسید. شروع کار هم با دریافت ریشه‌ی syntax tree توسط متد GetRoot و تبدیل نوع آن نود به CompilationUnitSyntax می‌باشد.
روش دوم استفاده از روش LINQ است؛ با توجه به اینکه ساختار یک Syntax tree بسیار شبیه است به LINQ to XML. در اینجا یک سری نود، ریشه و فرزندان آن‌ها را داریم که با روش LINQ بسیار سازگار هستند. برای نمونه در مثال فوق، در ریشه‌ی Parse شده، در تمام کلاس‌های آن، به دنبال متد یا متدهایی هستیم که نام آن‌ها Bar است.
و در نهایت روش مرسوم و متداول کار با Syntax trees، استفاده از الگوی Visitors است. همانطور که در کدهای دو روش قبل مشاهده می‌کنید، باید تعداد زیادی حلقه و if و else نوشت تا به جزء و المان مدنظر رسید. راه ساده‌تری نیز برای مدیریت این پیچیدگی وجود دارد و آن استفاده از الگوی Visitor است. کار این الگو ارائه‌ی متدهایی قابل override شدن است و فراخوانی آن‌ها، در طی حلقه‌هایی پشت صحنه که این Visitor را اجرا می‌کنند، صورت می‌گیرد. بنابراین در اینجا دیگر برای رسیدن به یک متد، حلقه نخواهید نوشت. تنها کاری که باید صورت گیرد، override کردن متد Visit المانی خاص در Syntax tree است.
هر نود در syntax tree دارای متدی است به نام Accept که یک Visitor را دریافت می‌کند. همچنین Visitorهای نوشته شده نیز دارای متد Visit یک نود هستند.
نمونه‌ای از این Visitors را در کلاس ذیل مشاهده می‌کنید:
class MyVisitor : CSharpSyntaxWalker
{
    public override void VisitMethodDeclaration(MethodDeclarationSyntax node)
    {
        if (node.Identifier.Text == "Bar")
        {
            // do stuff
        }
 
        base.VisitMethodDeclaration(node);
    }
}
در اینجا برای رسیدن به تعاریف متدها دیگر نیازی نیست تا حلقه نوشت. بازنویسی متد VisitMethodDeclaration، دقیقا همین کار را انجام می‌دهد و در طی پروسه‌ی Visit یک Syntax tree، اگر متدی در آن تعریف شده باشد، متد VisitMethodDeclaration حداقل یکبار فراخوانی خواهد شد.
کلاس پایه‌ی CSharpSyntaxWalker از کلاس CSharpSyntaxVisitor مشتق شده‌است و به تمام امکانات آن دسترسی دارد. علاوه بر آن‌ها، کلاس CSharpSyntaxWalker به Tokens و Trivia نیز دسترسی دارد.
نحوه‌ی استفاده از Visitor سفارشی نوشته شده نیز به صورت ذیل است:
 new MyVisitor().Visit(node);
در اینجا متد Visit این Visitor را بر روی نود ریشه‌ی Syntax tree اجرا کرده‌ایم.
اشتراک‌ها
کتابخانه نمایش فرمول های ریاضی در React

این کتابخانه که توسط آقای مهدی صادقی ایجاد شده است قابلیت نمایش فرمول‌های ریاضی را به صورت یک کامپوننت ساده و عدم درگیری مستقیم با کتابخانه MathJax و life cycle‌ها ایجاد کرده است:

  <MathJax sanitizeOptions={ {USE_PROFILES: {html: true,mathMl: true}}} style={{'display':'inline-block'}} math={this.props.question.title} />

همچنین به دلیل بکارگیری کتابخانه DOMPurify  امکان مدیریت تگ‌ها و جلوگیری از حملات xss و .. نیز فراهم میباشد.

کتابخانه نمایش فرمول های ریاضی در React
نظرات مطالب
نمایش ساختارهای درختی توسط jqGrid
با سلام؛ در این ساختار درختی از Parent  برای تشخیص ریشه و فرزندان استفاده شده است. سوالم اینه که امکانش هست که ساختار درختی بین دو مدل و توسط کلید خارجی صورت گیرد؟ به عنوان مثال میخام گریدی داشته باشم که شامل لیست استان‌ها باشد و با کلیک بر روی هر استان لیست شهرهای آن نشان داده شود.
مطالب
کار با اسکنر در برنامه های تحت وب (قسمت اول)
در اکثر برنامه‌های سازمانی، مثل برنامه‌های مدیریت آرشیو اسناد، همواره این نیاز جزو خواسته‌های کاربران بوده که بتوانند به صورت مستقیم و از طریق تنها یک کلیک، تصویر مورد نظر را اسکن کرده و به صورت خودکار در برنامه وارد کنند؛ یعنی بدون اینکه نیاز باشد با استفاده از یک برنامه دیگر ابتدا تصویر را اسکن کرده و سپس در فرم وب، فایل اسکن شده را Browse کنند.
این نیاز اساسا به معنی دسترسی به سخت افزار کاربر از طریق مرورگر می‌باشد که به دلایل متعددی امکان پذیر نیست! مشکلات امنیتی ایجاد شده در این راه بسیار جدی است. اما خوشبختانه راههایی برای رسیدن به این هدف وجود دارند:

1- ActiveX : که به صورت native فقط در IE پشتیبانی می‌شود (در نسخه‌های جدیدتر IE نیاز به بروز رسانی پلاگین ActiveX controls می‌باشد) و برای استفاده‌ی از آن در مرورگر‌های Firefox و Chrome هم باید از پلاگین‌های جانبی روی هر مرورگر استفاده کرد که مثلا برای اجرای بر روی Firefox، باید افزونه‌ی IE Tab را نصب کرد که تنها کارش این است که سایت را از طریق موتور IE در پنجره‌ی فایرفاکس اجرا کند! که البته این مورد مثل این می‌ماند که سایت در IE باز شده باشد که ممکن است زیاد خوشایند نباشد؛ در غیر اینصورت باید پروژه را از اول بر مبنای اجرای بر روی IE طراحی و پیاده سازی کرد. در ضمن از مشکلات اجرای پلاگین نوشته شده توسط برنامه نویس در IE و نسخه‌های مختلف آن هم چشم پوشی می‌کنیم. یکی از مزیت‌های این روش نسبت به بقیه این هست که دانلود و نصب پلاگین مورد نیاز به صورت خودکار و توسط مرورگر انجام می‌شود.

2- استفاده از یک کامپوننت جانبی : مثل کامپوننت‌های LEADTOOLS که ابزارهای متنوع و SDK‌های بسیار قدرتمندی را برای اینکار و کارهای دیگر، مانند کار با اسکن تصاویر مغزی، پردازش تصویر، بارکد خوان‌ها، مدیریت اسناد و ...  فراهم کرده است. قیمت این ابزار بسیار زیاد است و در برخی موارد امکانات فراهم آورده بسیار بیشتر است از خواسته‌ی ما. این ابزار، هم در HTML & Javascript و هم در DotNet قابل استفاده است و مستندات نسبتا خوبی هم در این زمینه ارائه کرده است. همچنین کامپوننت Dynamsoft که باز هم غیر رایگان هست و قیمت بالایی نیز دارد.

اگر روال کار کامپوننت‌های بالا را مورد بررسی قرار دهید (از طریق اجرای Demo ها، اینجا و اینجا) متوجه خواهید شد که هر دو نیاز به نصب یک سرویس یا App سمت کلاینت جهت اجرای دستورات خود دارند. پس می‌شود اینطور نتیجه گرفت که انجام اینکار بدون اینکه چیزی سمت کاربر نصب شود، ممکن نیست و در هر دو، لینک نصب فایل exe سرویس برای دانلود قرار داده شده است. بر این اساس به راه حل سومی خواهیم رسید که خودمان این سرویس را جهت تعامل با اسکنر سمت کاربر طراحی و پیاده سازی نماییم.

اما چطور ممکن است که با اجرای یک فایل exe سمت کاربر (با این فرض که کاربر در یک دامین مطمئن قرار دارد و می‌شود درخواست نصب سرویس را نمود) این امکان را برای کاربر فراهم نمود که با یک کلیک در مرورگر، اسکنر به صورت خودکار اسکن را آغاز کرده و سپس تصویر حاصل را به یکی از کنترلر‌های ما در سمت سرور ارسال نماید؟

برای اینکار ما با دو صورت مساله مواجه هستیم؛ اول اینکه چطور تصویر را سمت کاربر اسکن کنیم و دوم اینکه چطور این تصویر را به سرور ارسال نماییم!
برای مساله‌ی اول از کتابخانه Windows Image Acquisition (WIA) استفاده خواهیم نمود که این کتابخانه به ما این امکان را میدهد تا با سخت افزارهایی که از TWAIN پشتیبانی می‌کنند، بتوانیم ارتباط برقرار نماییم.

برای مساله‌ی دوم هم نیاز به پیاده سازی یک WCF Service و اجرای آن (هاست کردن) در سمت کلاینت داریم. در واقع با صدا زدن متدهای این سرویس، از کتابخانه‌ی بالا استفاده کرده و اسکن را انجام می‌دهیم.

ادامه دارد...
مطالب
قابل ویرایش کننده‌ی فوق العاده x-editable ؛ قسمت اول
من در یکی از پروژه‌ها از Kendo UI Treeview استفاده کردم و قصد داشتم قابلیت تغییر نام را به گره‌ها بدهم. به همین جهت پس از جستجو به x-editable برخوردم. این کتابخانه‌ی جاواسکریپتی در ابتدا برای قالب‌های بوت استراپ طراحی شده بود که در حال حاضر اینگونه نیست و به راحتی در هر پروژه‌ای که فقط جی کوئری صدا زده شده باشد، قابل اجرا است و نسخه‌ی مخصوص Angular آن هم در این آدرس قرار دارد. همچنین این قابلیت اختیاری و پیش فرض را دارد که به طور خودکار اطلاعات تغییر یافته را به سمت url ایی که شما تعیین می‌کنید، ارسال کند. برای همین نیازی به استفاده جداگانه از jQuery Ajax برای ارسال اطلاعات نیست و البته در انتهای مقاله هم هنگام استفاده از درخت کندو به مشکلی برخوردم که آن را هم بررسی می‌کنیم.

وارد کردن کتابخانه ها
این کتابخانه شامل دو فایل css و JS می‌باشد که بسته به محیطی که در آن کار می‌کنید متفاوت هستند. در این صفحه شما می‌توانید برای 4 محیط Jquery ,JqueryUI , Bootstrap2 و Bootsrap3 بسته‌ی مخصوصش را یا به صورت دانلود فایل‌ها یا از طریق CDN دریافت نمایید. در اینجا هم یک دمو از قابلیت‌های آن قابل مشاهده است.

برای شروع، کتابخانه‌ی مورد نظر خود را دریافت و آن‌ها را به صفحه‌ی خود اضافه نمایید. در صورتیکه از Bootstrap استفاده می‌کنید، ابتدا فایل‌های زیر را اضافه کنید:
    <link href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css" rel="stylesheet">
    <script src="http://code.jquery.com/jquery-2.0.3.min.js"></script> 
    <script src="//netdna.bootstrapcdn.com/bootstrap/3.0.0/js/bootstrap.min.js"></script>
سپس فایل‌های x-editable را صدا بزنید.

اولین حالتیکه میتوانید با این کتابخانه کار کنید، استفاده از خاصیت *-data است. نمونه زیر را در نظر بگیرید:
  <a href="#" id="favsite" data-type="text" data-pk="1" data-url="@Url.Action(MVC.Categories.EditCategory())" 
data-title="Enter your favorite site">dotnettips.info</a>
به تعداد هر عنصری که نیاز است، اینکار را انجام دهید و به هر کدام یک id انتساب دهید. بعد از آن کد زیر را در قسمت script بنویسید:
 $(document).ready(function () {
        $('#favsite').editable();
    });
در صورتیکه قصد دارید خصوصیاتی از اشیاء را که برای همه‌ی عنصرهای معرفی شده یکسان است، معرفی کنید می‌توانید از کد زیر بهره ببرید:
 $(document).ready(function () {
        $.fn.editable.defaults.mode = 'inline';
        $('#favsite').editable();
    });
کد بالا حالت ویرایش تمام عناصر معرفی شده را به inline تغییر می‌دهد.

حالت بعدی که می‌توان استفاده کرد به شکل زیر است:
  <a href="#" id="favsite" >dotnettips.info</a>

  $.fn.editable.defaults.mode = 'inline';
    $(document).ready(function () {
        $('#favsite').editable({
            type: 'text',
            pk: 1,
            url: '@Url.Action(MVC.Categories.EditCategory())',
            title: 'Enter your favorite site'
        });
    });

خوبی این روش این است که می‌توان اطلاعات بیشتری چون رویدادها را به آن پاس داد. تا الان با نحوه‌ی انتساب آن به اشیاء آشنا شدیم. اجازه دهید تا با خصوصیات آن آشنا شویم.


 AjaxOptions
 همانطور که متوجه شدید به طور خودکار اطلاعات ویرایش شده، به سمت آدرس داده شده، به شیوه Post ارسال می‌گردند. در صورتیکه قصد دست بردن در نوع درخواست را دارید، می‌توانید از این ویژگی استفاده کنید:
    ajaxOptions: {
        type: 'put',
        dataType: 'json'
    }
 Anim
 این ویژگی که تنها در حالت inline پاسخ می‌دهد، می‌تواند زمان بسته شدن x-editable را تغییر دهد که به طور پیش فرض با false مقداردهی شده است. جهت تغییر زمان بسته شدن، کد زیر را وارد نمایید:
anim:'false'
//or
anim: {
                duration: 2000
            }
 autotext  در انتهای جدول آمده است.
 defaultValue 
 در صورتیکه عنصر مورد نظر محتوایی نداشته باشد و این خصوصیت را مقداردهی کنید، موقع ویرایش، این عبارت تعیین شده نمایش می‌یابد. در مثال بالا باید متن تگ a را حذف کرده تا نتیجه را ببینید: (البته فیلد value نباید مقداری داشته باشد)
defaultValue: 'default val'
//or
defaultValue: undefined
//or
defaultValue: null
در بقیه‌ی حالات، ویرایشگر خالی از متن خواهد بود و مقدار پیش فرض آن نال است.
 disabled  false کردن این ویژگی باعث غیرفعال شدن x-editable بر روی کنترل جاری میگردد.
 display 
خاصیت  display یا مقدار بولین false را دریافت می‌کند، یا نال، یا یک تابع callback را می‌توان به آن پاس داد. این خصوصت زمانی صدا زده می‌شود که اطلاعات به سمت آدرس سرور رفته و با موفقیت بازگشت داده می‌شوند (در صورتی که این ویژگی غیرفعال باشد، بلافاصه بعد از تایید کاربر، از اطلاعات وارد شده صدا زده می‌شود) و سپس متن جدید عنصر تغییر می‌یابد. حال اگر این خاصیت نال که مقدار پیش فرض آن است باشد، متن تغییر می‌یابد. ولی اگر false باشد، متن سابق باقی خواهد ماند و در صورتیکه تابعی به آن پاس داده باشید، طبق تابع شما عمل خواهد کرد.
پارامترهایی که تابع شما می‌تواند داشته باشد به شرح زیر است:
value : مقدار جدید
response : پاسخ سرور ( در صورتی که ارسال از طریق Ajax صورت گرفته باشد)

و در صورتیکه عنصر شما checlklist یا select باشد که حاوی منبعی از مقادیر هست، مقادیرشان در قالب یک آرایه با نام sourceData بازگشت خواهد خورد. برای دسترسی به آیتم‌های انتخابی هم از کد زیر استفاده می‌کنیم:
$.fn.editableutils.itemsByValue(value, sourceData)
 emptyclass  معرفی یک کلاس css برای موقعیکه عنصر خالی است.
 emptytext  در صورتی خالی بودن عنصر، این متن را برای عنصر نمایش بده.
 highlight   بعد از به روز رسانی متن عنصر، آن را با این رنگ highlight خواهد کرد و کد رنگی باید در مبنای هگز باشد. مقدار پیش فرض آن false است.
 mode
 دو حالت نمایشی دارد که پیش فرض آن popup است و با باز کردن یک پنجره، مقدار جدید را دریافت می‌کند. مورد بعدی inline است که به جای باز کردن پنجره، متن عنصر را به حالت ویرایش تغییر میدهد.
 name  نام فیلدی که مقدارش تغییر می‌کند.
 onblur زمانی که کاربر فوکوس را از ویرایشگر  می‌گیرد، ویرایشگر چه پاسخی باید به آن بدهد، باز بماند؟ ignore ، بسته شود؟ cancel و یا مقدار داده شده را تایید کند؟submit
 params
پارامترهای درخواست ایجکسی که کنترل در حالت پیش فرض ارسال می‌کند؛ شامل Pk که آن را با id رکورد پر می‌کنیم. name نام فیلدی که تغییر یافته است و value که مقدار جدید است. در صورتیکه دوست دارید اطلاعات اضافی‌تری نیز ارسال شوند، می‌توانید از این خاصیت استفاده کنید و پارامترها را در قالب Object به آن پاس کنید. ولی اگر بخواهید در کل همه‌ی پارامترها را رونویسی کنید باید یک تابع را به آن پاس کنید:

  params: function(params) {
        //در این حالت پارامترهای پیش فرض ارسال نشده 
و تنها پارامترهای معرفی شده در این تابع ارسال می‌شوند
        params.a = 1;
        return params;
    }

 pk  کلید اصلی رکورد شما در دیتابیس یا هان id است. در صورتی که از کلیدهای ترکیبی استفاده می‌کنید، نگران نباشید فکر آن را هم کرده اند.
//کلید عدد
pk:1,
//کلید رشته ای
pk:'dp123'
//کلید ترکیبی
pk:{id: 1, lang: 'en'}

//معرفی یک تابع به آن و بازگشت 
یکی از مقادیر بالا بعد از محاسبات pk:function() { }
 Placement  این ویژگی فقط به درد حالت Popup می‌خورد که پنجره را کجای عنصر نمایش دهد و شامل چهار مقدار left,right,top,bottom می‌شود.
 saveonchange  زمانی که مقدار جدید، برابر مقدار فعلی باشد و این خاصیت false باشد، هیچ تغییر‌ی رخ نخواهد داد. ولی اگر برابر true باشد ،مقدار جدید اسال و جایگزین مقدار فعلی خواهد شد. مقدار پیش فرض آن false است.
 selector
  با استفاده از این خصوصیت در عنصر انتخابی به دنبال عناصری که در selector تعیین شده می‌گردد و حالت ویرایش را روی آن‌ها فعال می‌کند.
در این حالت استفاده از خصوصیات emptytext و autotext در ابتدای امر ممکن نیست و بعد از اولین کلیک قابل استفاده هستند.
نکته بعدی اینکه شما باید کلاس‌های زیر را دستی اضافه کنید.
کلاس  editable-click برای همه کنترل‌ها وکلاس editable-empty به کنترل‌های بدون مقدار و برای مقداردهی کنترلهای بدون مقدار میتوان از خاصیت ''=data-value استفاده کرد.
    <div id="user">
      <!-- empty -->
      <a href="#" data-name="username" 
data-type="text" data-value="" 
title="Username">Empty</a>
      <!-- non-empty -->
      <a href="#" data-name="group" 
data-type="select" data-source="/groups"
 data-value="1" title="Group">Operator</a>
    </div>     
     
    <script>
    $('#user').editable({
        selector: 'a',
        url: '/post',
        pk: 1
    });
    </script>
 send  سه مقدار auto,always و never را دریافت می‌کند. موقعی که شما آن را روی auto تنظیم کنید؛ در صورتی مقادیر به سمت سرور ارسال می‌شوند که دو خاصیت url و pk تعریف شده باشند. در غیر این صورت ویرایش فقط در حالت محلی و روی سیستم کاربر رخ خواهد داد.
 showbuttons   در صورتیکه با false مقداردهی شود، تایید فرم به طور خودکار انجام می‌گیرد و اگر با یکی از مقادیر left یا Bottom پر شود، دکمه‌ها را در آن قسمت نشان می‌دهد.
 success
 اطلاعات به سمت سرور رفته و با موفقیت با کد 200 بازگشت داده شده‌اند. در مستندات نوشته است، هر کد وضعیتی غیر از 200 بازگشت داده شود، به سمت خاصیت error هدایت می‌شو.د ولی آن طور که من با httpresponsemessage تست کردم، چنین چیزی را مشاهده نکردم و مجددا success صدا زده شد. پس بهتر هست داده‌ای را که به سمت کنترل برگشت می‌دهید، خودتان کنترل کنید. به خصوص اگر انتقال اطلاعات صحیح باشد. ولی اگر در دیتابیس، تغییر با خطا روبرو گردد بهتر است نتیجه‌ی آن ارسال شده و از تغییر مقدار فعلی ممانعت به عمل آورید.
    success: function(response, newValue) {
        if(!response.success) return response.msg;
    }
 toggle  اگر قصد دارید که باز و بسته کردن ویرایشگر را بر عهده‌ی مثلا یک دکمه‌ی روی صفحه بگذارید، این خصوصیت به شما کمک می‌کند:
    $('#edit-button').click(function(e) {
        e.stopPropagation();
        $('#favsite').editable('toggle');
    });
به جای toggle نیز می‌توان از show و hide هم استفاده کرد. وجود عبارت e.stopPropagation جهت باز شدن صحیح ویرایشگر الزامی است.

 type نوع ویرایشی را که قرار است انجام گیرد، مشخص می‌کند. text برای متن، date برای تاریخ، textarea جهت متون طولانی‌تر نسبت به text و بسیاری از موارد دیگر
 unsavedclass  این کلاس موقعی اعمال می‌گردد که اطلاعاتی را ویرایش کرده‌اید، ولی اطلاعاتی به سمت سرور ارسال نشده است. مثلا pk مقداردهی نشده یا send=never قرار داید و یا اینکه به صورت محلی ذخیره می‌کنید و می‌خواهید در آخر همه‌ی اطلاعات را ارسال کنید.
این خاصیت به طور پیش فرض با کلاس editable-unsaved مقداردهی شده که می‌توانید با نال کردن، از شرش خلاص شوید.
 url
این خاصیت با آدرس سمت سرور پر می‌شود. ولی میتوان به آن یک تابع هم پاس کرد که این تابع جایگزین درخواست ایجکسی خودش خواهد شد و برای بازگشت دادن نتیجه‌ی این درخواست به سمت تابع‌های callback خودش می‌توانید یک deferred object را برگشت دهید:

 url: function(params) {
        var d = new $.Deferred;
        if(params.value === 'abc') {
//returning error via deferred object 

 return d.reject('error message'); 
        } else {
            //async saving data in js model
            someModel.asyncSaveMethod({
               ..., 
               success: function(){
                  d.resolve();
               }
            }); 
            return d.promise();
        }
    }

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

در صورتی که از نسخه‌ی 1.5.1 به بعد استفاده می‌کنید، دریافت یک object با مقادیر زیر نیز ممکن شده است:
newValue: مقدار جدید و جایگزین مقدار غیر معتبر.
msg : پیام خطا.
به کدهای زیر در سه حالت نگاه کنید:
validate: function (value) {
                if ($.trim(value) == '') {
//در تمامی نسخه‌های یک پیام متنی باز میگردد
                    return 'This field is required';
//1.5.1
//یک مقدار جدید برگشت میدهد که بلافاصله آن را
// تایید میکند و متن عنصر به روز می‌شود
                    return { newValue: 'validated' };
//متن جدید ار ارسال کرده و پیام هشدار را نشان میدهد 
//ولی تایید نمی‌کند و منتظر تایید کاربر است
                    return { newValue: 'validated',
 msg: 'This field is required' }; } }

 value مقدار پیش فرضی که در ویرایشگر نشان می‌دهد و اگر مقداردهی نشود، از متن عنصر استفاده می‌کند.
 autotext  سه مقدار دارد auto (پیش فرض)،always و never.
موقعیکه عنصر شما متنی نداشته باشد و روی auto تنظیم شده باشد، مقدار value را به عنوان متن عنصر نمایش می‌دهد.
always کاری ندارد که عنصر شما متن دارد یا خالی است؛ مقدار value به آن انتساب داده خواهد شد.
never هیچگاه.

  در قسمت بعدی که قسمت پایانی است مطالب را ادامه می‌دهیم.
مطالب
آموزش QUnit #3
در قسمت‌های قبلی با مفهوم تست واحد و کتابخانه quint آشنا شدید و مثالی را نیز با هم بررسی کردیم. در ادامه به قابلیت‌های بیشتر این کتابخانه می‌پردازیم.

توابع اعلان نتایج:
qunit سه تابع را جهت اعلان نتایج تست واحد فراهم نموده است
تابع ok:
تابع پایه‌ای تست واحد، دو پارامتر را به عنوان ورودی دریافت می‌کند و در صورتیکه بررسی نتیجه پارامتر اول برابر true باشد، تست با موفقیت روبرو شده است. پارامتر دوم برای نمایش یک پیام است. در مثال زیر حالت‌های مختلف آن بررسی شده است. مقادیر true، non-empty string به معنی موفقیت و مقادیر false،0،NaN،""،null و undefined به معنی شکست تست می‌باشد. در واقع خروجی تایع ارسالی به اعلان ok یکی از نتایج بالا می‌تواند باشد.
//ok( truthy [, message ] )

test( "ok test", function() {
  ok( true, "true succeeds" );
  ok( "non-empty", "non-empty string succeeds" );
 
  ok( false, "false fails" );
  ok( 0, "0 fails" );
  ok( NaN, "NaN fails" );
  ok( "", "empty string fails" );
  ok( null, "null fails" );
  ok( undefined, "undefined fails" );
});

تابع equal:
این اعلان یک مقایسه ساده بین پارامتر اول و دوم تایع می‌باشد که شرط برابری(==) را بررسی می‌نماید. وقتی مقدار اول و دوم برابر باشند، اعلان موفقیت و در غیر این صورت، تست با شکست رویرو شده و هر دو پارامتر نمایش داده می‌شوند.
//equal( actual, expected [, message ] )

test( "equal test", function() {
  equal( 0, 0, "Zero; equal succeeds" );
  equal( "", 0, "Empty, Zero; equal succeeds" );
  equal( "", "", "Empty, Empty; equal succeeds" );
  equal( 0, 0, "Zero, Zero; equal succeeds" );
 
  equal( "three", 3, "Three, 3; equal fails" );
  equal( null, false, "null, false; equal fails" );
});
زمانی که می‌خواهید مؤکداً شرط === را بررسی نمایید از ()strictEqual استفاده کنید.

تایع deepEqual:
تکمیل شده دو تایع قبل می‌باشد و حتی امکان مقایسه دو شی را نیز با هم دارا است. علاوه بر این، امکان مقایسه NaN، تاریخ، عبارات باقاعده، آرایه‌ها و توابع نیز وجود دارند.
//deepEqual( actual, expected [, message ] )

test( "deepEqual test", function() {
  var obj = { foo: "bar" };
 
  deepEqual( obj, { foo: "bar" }, "Two objects can be the same in value" );
});
در صورتیکه نمی‌خواهید محتوای دو مقدار را با هم مقایسه کنید، از equal استفاده نمایید اما عموما deepEqual انتخاب بهتری می‌باشد.

تست عملیات کاربر:
گاهی لازم است رویداد‌هایی که از عملیات کاربران صدا زده می‌شوند تست شوند. در این موارد با صدا زدن تابع trigger جی‌کوئری، تابع مورد نظر را تست نمایید. به مثال زیر توجه نمایید:
function KeyLogger( target ) {
  if ( !(this instanceof KeyLogger) ) {
    return new KeyLogger( target );
  }
  this.target = target;
  this.log = [];
 
  var self = this;
 
  this.target.off( "keydown" ).on( "keydown", function( event ) {
    self.log.push( event.keyCode );
  });
}
این مثال یک گزارش دهنده است و در صورتیکه کاربر، کلیدی را فشار دهد، کد آن را گزارش می‌دهد و در آرایه log ذخیره می‌نماید. حال لازم است بصورت دستی این رویداد را صدا زده و تایع را تست کنیم. تست را بصورت زیر می‌نویسیم:
test( "keylogger api behavior", function() {
 
  var event,
      $doc = $( document ),
      keys = KeyLogger( $doc );
 
  // trigger event
  event = $.Event( "keydown" );
  event.keyCode = 9;
  $doc.trigger( event );
 
  // verify expected behavior
  equal( keys.log.length, 1, "a key was logged" );
  equal( keys.log[ 0 ], 9, "correct key was logged" );
 
});

برای این کار تابع KeyLogger را با شی document جی کوئری صدا زدیم و نتیجه را در متغییر keys قرار داده‌ایم. بعد رویداد keydown را با کد 9 پرکرده تایع  trigger متغییر doc$ را با مقدار event صدا زده‌ایم که در واقع بصورت دستی، یک رویداد اتفاق افتاده است. در آخر هم با اعلان equal تست واحد را انجام داده‌ایم.

مطالب
React 16x - قسمت 25 - ارتباط با سرور - بخش 4 - یک تمرین
همان مثال backend قسمت 22 را با افزودن وب سرویس‌هایی برای قسمت‌های نمایش لیست فیلم‌ها، ژانرها و سایر صفحات اضافه شده‌ی به برنامه‌ی تکمیل شده‌ی تا قسمت 21، توسعه می‌دهیم. کدهای کامل آن، به علت شباهت و یکی بودن نکات آن با مطلب 22، در اینجا تکرار نخواهند شد و می‌توانید کل پروژه‌ی آن‌را از پیوست انتهای بحث دریافت کنید. سپس فایل dotnet_run.bat آن‌را اجرا کنید تا در آدرس https://localhost:5001 قابل دسترسی شود.


افزودن سرویس httpService.js به برنامه

تا این قسمت، تمام اطلاعات نمایش داده شده‌ی در لیست فیلم‌ها، از سرویس درون حافظه‌ای src\services\fakeMovieService.js و لیست ژانرها از سرویس src\services\fakeGenreService.js، تامین می‌شوند. اکنون در ادامه می‌خواهیم این سرویس‌ها را با سرویس backend یاد شده، جایگزین کنیم تا این برنامه، اطلاعات خودش را از سرور دریافت کند. به همین جهت قبل از هر کاری، سرویس عمومی src\services\httpService.js را که در قسمت قبل توسعه دادیم، به برنامه‌ی نمایش لیست فیلم‌ها نیز اضافه می‌کنیم (فایل آن‌را از پروژه‌ی قبلی کپی کرده و در اینجا paste می‌کنیم)، تا بتوانیم از امکانات آن در اینجا نیز استفاده کنیم. فایل httpService.js، دارای وابستگی‌های خارجی react-toastify و axios است. به همین جهت برای افزودن آن‌ها مراحل زیر را طی می‌کنیم:
- نصب کتابخانه‌های react-toastify و axios از طریق خط فرمان (با فشردن دکمه‌های ctrl+back-tick در VSCode):
> npm i axios --save
> npm i react-toastify --save
سپس به فایل app.js مراجعه کرده و importهای لازم آن‌را اضافه می‌کنیم:
import { ToastContainer } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";
همچنین نیاز است ToastContainer را به ابتدای متد render نیز اضافه کرد:
  render() {
    return (
      <React.Fragment>
        <ToastContainer />


دریافت اطلاعات لیست نمایش ژانرها از سرویس backend

با فراخوانی آدرس https://localhost:5001/api/Genres، می‌توان لیست ژانرهای سینمایی تعریف شده‌ی در سرویس‌های backend را مشاهده کرد. اکنون قصد داریم از این اطلاعات، در برنامه استفاده کنیم. به همین جهت به فایل src\components\movies.jsx مراجعه کرده و تغییرات زیر را اعمال می‌کنیم:
چون نمی‌خواهیم تغییراتی بسیار اساسی را در اینجا اعمال کنیم، قدم به قدم عمل کرده و سرویس قبلی fakeGenreService.js را با یک سرویس جدید که اطلاعات خودش را از سرور دریافت می‌کند، جایگزین می‌کنیم. بنابراین ابتدا فایل جدید src\services\genreService.js را ایجاد می‌کنیم. سپس آن‌را طوری تکمیل خواهیم کرد که اینترفیس آن، با اینترفیس fakeGenreService قبلی یکی باشد:
import { apiUrl } from "../config.json";
import http from "./httpService";

export function getGenres() {
  return http.get(apiUrl + "/genres");
}
همچنین در اینجا import وابستگی config.json را نیز مشاهده می‌کنید که در قسمت قبل در مورد آن توضیح دادیم. به همین جهت برای تمیزتر شدن قسمت‌های مختلف برنامه، فایل config.json را در مسیر src\config.json ایجاد کرده و به صورت زیر تکمیل می‌کنیم:
{
   "apiUrl": "https://localhost:5001/api"
}
apiUrl به ریشه‌ی URLهای ارائه شده‌ی توسط backend service ما، اشاره می‌کند.

پس از تکمیل سرویس جدید src\services\genreService.js، به فایل src\components\movies.jsx بازگشته و سطر قبلی
import { getGenres } from "../services/fakeGenreService";
را با سطر جدید زیر، جایگزین می‌کنیم:
import { getGenres } from "../services/genreService";
تا اینجا اگر برنامه را ذخیره کرده و اجرا کنید، خطای زیر را مشاهده خواهید کرد:
Uncaught TypeError: Object is not a function or its return value is not iterable
علت اینجا است که سرویس قبلی fakeGenreService، دارای متد export شده‌ای به نام getGenres بود که یک آرایه‌ی معمولی را بازگشت می‌داد. اکنون این سرویس جدید نیز همان ساختار را دارد، اما اینبار یک Promise را بازگشت می‌دهد. به همین جهت متد componentDidMount را به صورت زیر اصلاح می‌کنیم:
  async componentDidMount() {
    const { data } = await getGenres();
    const genres = [{ _id: "", name: "All Genres" }, ...data];
    this.setState({ movies: getMovies(), genres });
  }
متد getGenres باید await شود تا نتیجه‌ی آن توسط خاصیت data شیء بازگشتی از آن، قابل دسترسی شود. با این تغییر، نیاز است این متد را نیز به صورت async معرفی کرد.


دریافت اطلاعات لیست فیلم‌ها از سرویس backend

پس از دریافت لیست ژانرهای سینمایی از سرور، اکنون نوبت به جایگزینی src\services\fakeMovieService.js با یک نمونه‌ی متصل به backend است. به همین جهت ابتدا فایل جدید src\services\movieService.js را ایجاد کرده و سپس آن‌را به صورت زیر تکمیل می‌کنیم:
import { apiUrl } from "../config.json";
import http from "./httpService";

const apiEndpoint = apiUrl + "/movies";

function movieUrl(id) {
  return `${apiEndpoint}/${id}`;
}

export function getMovies() {
  return http.get(apiEndpoint);
}

export function getMovie(movieId) {
  return http.get(movieUrl(movieId));
}

export function saveMovie(movie) {
  if (movie.id) {
    return http.put(movieUrl(movie.id), movie);
  }

  return http.post(apiEndpoint, movie);
}

export function deleteMovie(movieId) {
  return http.delete(movieUrl(movieId));
}
سپس شروع به اصلاح کامپوننت movies می‌کنیم.
ابتدا دو متد دریافت لیست فیلم‌ها و حذف یک فیلم را که در این کامپوننت استفاده شده‌اند، import می‌کنیم:
import { getMovies, deleteMovie } from "../services/movieService";
بعد متد getMovies پیشین، که یک آرایه را بازگشت می‌داد، توسط متد جدیدی که یک Promise را بازگشت می‌دهد، جایگزین می‌شود:
  async componentDidMount() {
    const { data } = await getGenres();
    const genres = [{ id: "", name: "All Genres" }, ...data];

    const { data: movies } = await getMovies();
    this.setState({ movies, genres });
  }
همچنین مدیریت حذف رکوردها را نیز به صورت زیر با پیاده سازی «به‌روز رسانی خوشبینانه UI» که در قسمت قبل در مورد آن بیشتر بحث شد، تغییر می‌دهیم. در این حالت فرض بر این است که به احتمال زیاد،  await deleteMovie با موفقیت به پایان می‌رسد. بنابراین بهتر است UI را ابتدا به روز رسانی کنیم تا کاربر حس کار کردن با یک برنامه‌ی سریع را داشته باشد:
  handleDelete = async movie => {
    const originalMovies = this.state.movies;

    const movies = originalMovies.filter(m => m.id !== movie.id);
    this.setState({ movies });

    try {
      await deleteMovie(movie.id);
    } catch (ex) {
      if (ex.response && ex.response.status === 404) {
        console.log(ex);
        toast.error("This movie has already been deleted.");
      }

      this.setState({ movies: originalMovies }); //undo changes
    }
  };
ابتدا ارجاعی را از state قبلی ذخیره می‌کنیم تا در صوت بروز خطایی در سطر await deleteMovie، بتوانیم مجددا state را به حالت اول آن بازگردانیم. به همین منظور پیاده سازی «به‌روز رسانی خوشبینانه UI»، حتما نیاز به درج صریح try/catch را دارد. برای نمایش خطاهای ویژه‌ی 404 نیز از یک toast استفاده شده که نیاز به import زیر را دارد:
import { toast } from "react-toastify";
سایر خطاهای رخ داده، توسط interceptor درج شده‌ی در سرویس http، به صورت خودکار پردازش می‌شوند.


اتصال فرم ثبت و ویرایش یک فیلم به backend server

تا اینجا اگر برنامه را اجرا کنیم، با کلیک بر روی لینک هر فیلم نمایش داده شده‌ی در صفحه، به صفحه‌ی not-found هدایت می‌شویم. برای رفع این مشکل، به فایل src\components\movieForm.jsx مراجعه کرده و ابتدا
import { getGenres } from "../services/fakeGenreService";
import { getMovie, saveMovie } from "../services/fakeMovieService";
قبلی را با نمونه‌ها‌ی جدیدی که با سرور کار می‌کنند، جایگزین می‌کنیم:
import { getGenres } from "../services/genreService";
import { getMovie, saveMovie } from "../services/movieService";
سپس ارجاعات به این سه متد import شده را با await، همراه کرده و متد اصلی را به صورت async معرفی می‌کنیم:
  async componentDidMount() {
    const { data: genres } = await getGenres();
    this.setState({ genres });

    const movieId = this.props.match.params.id;
    if (movieId === "new") return;

    const { data: movie } = await getMovie(movieId);
    if (!movie) return this.props.history.replace("/not-found");

    this.setState({ data: this.mapToViewModel(movie) });
  }
البته می‌توان جهت بهبود کیفیت کدها، از متد componentDidMount، دو متد با مسئولیت‌های مجزای دریافت ژانرها (populateGenres) و سپس نمایش فرم اطلاعات فیلم (populateMovie) را استخراج کرد:
  async populateGenres() {
    const { data: genres } = await getGenres();
    this.setState({ genres });
  }

  async populateMovie() {
    try {
      const movieId = this.props.match.params.id;
      if (movieId === "new") return;

      const { data: movie } = await getMovie(movieId);
      this.setState({ data: this.mapToViewModel(movie) });
    } catch (ex) {
      if (ex.response && ex.response.status === 404)
        this.props.history.replace("/not-found");
    }
  }

  async componentDidMount() {
    await this.populateGenres();
    await this.populateMovie();
  }
در متد populateMovie، اگر movieId اشتباهی وارد شود و یا کلا عملیات دریافت اطلاعات، با شکست مواجه شود، کاربر را به صفحه‌ی not-found هدایت می‌کنیم. یعنی وجود try/catch در اینجا ضروری است. چون اگر movieId اشتباهی وارد شود، اینبار دیگر خطوط بعدی اجرا نمی‌شوند و در همان سطر await getMovie، یک استثناء صادر شده و کار خاتمه پیدا می‌کند. بنابراین نیاز داریم بتوانیم این استثنای احتمالی را مدیریت کرده و کاربر را به صفحه‌ی not-found هدایت کنیم.
پیشتر زمانیکه متد getMovie، یک شیء ساده را از fake service، بازگشت می‌داد، چنین مشکلی را نداشتیم؛ به همین جهت در سطر بعدی آن، هدایت کاربر در صورت نال بودن نتیجه، با یک return صورت می‌گرفت. اما اینجا بجای نال، یک استثناء را ممکن است دریافت کنیم.

مرحله‌ی آخر اصلاح این فرم، اتصال قسمت ثبت اطلاعات آن است که با قرار دادن یک await، پیش از متد saveMovie و async کردن متد آن، انجام می‌شود:
  doSubmit = async () => {
    await saveMovie(this.state.data);

    this.props.history.push("/movies");
  };


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: sample-25-backend.zip و sample-25-frontend.zip