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

ساختار درختی
در بسیاری از مواقع ما با گروهی از اشیاء یا داده‌هایی سر و کار داریم که هر کدام از آن‌ها به گروهی دیگر مرتبط هستند. در این حالت از ساختار خطی نمی‌توانیم برای توصیف این ارتباط استفاده کنیم. پس بهترین ساختار برای نشان دادن این ارتباط ساختار شاخه ای Branched Structure است.
یک ساختار درختی یا یک ساختار شاخه‌ای شامل المان‌هایی به اسم گره Node است. هر گره می‌تواند به یک یا چند گره دیگر متصل باشد و گاهی اوقات این اتصالات مشابه یک سلسه مراتب hierarchically می‌شوند.
درخت‌ها در برنامه نویسی جایگاه ویژه‌ای دارند به طوری که استفاده‌ی از آن‌ها در بسیاری از برنامه‌ها وجود دارد و بسیاری از مثال‌های واقعی پیرامون ما را پشتیبانی می‌کنند.
در نمودار زیر مثالی وجود دارد که در آن یک تیم نرم افزاری نمایش داده شده‌است. در اینجا هر یک از بخش‌ها وظایف و مسئولیت‌هایی را بر دوش خود دارند که این مسئولیت‌ها به صورت سلسله مراتبی در تصویر زیر نمایش داده شده‌اند.

ما در ساختار بالا متوجه می‌شویم که چه بخشی زیر مجموعه‌ی چه بخشی است و سمت بالاتر هر بخش چیست. برای مثال ما متوجه شدیم که مدیر توسعه دهندگان، "سرپرست تیم" است که خود نیز مادون "مدیر پروژه" است و این را نیز متوجه می‌شویم که مثلا توسعه دهنده‌ی شماره یک هیچ مادونی ندارد و مدیر پروژه در راس همه است و هیچ مدیر دیگری بالای سر او قرار ندارد.

اصطلاحات درخت
برای اینکه بیشتر متوجه روابط بین اشیا در این ساختار بشویم، به شکل زیر خوب دقت کنید:

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

در شکل بالا به هر یک از دایره‌ها یک گره Node می‌گویند و به هر خط ارتباط دهنده بین گره‌ها لبه Edge گفته می‌شود. گره‌های 19 و 21 و 14 زیر گره‌های گره 7 محسوب می‌شوند. گره‌هایی که به صورت مستقیم به زیر گره‌های خودشان اشاره می‌کنند را گره‌های والد Parent می‌گویند و زیرگره‌های 7 را گره‌های فرزند ChildNodes. پس با این حساب می‌توانیم بگوییم گره‌های 1 و 12 و 31 را هم فرزند گره 19 هستند و گره 19 والد آن هاست. همچنین گره‌های یک والد را مثل 19 و 21 و 14 که والد مشترک دارند، گره‌های خواهر و برادر یا حتی همنژاد Sibling می‌گوییم. همچنین ارتباط بین گره 7 و گره‌های سطح دوم  و الی آخر یعنی 1 و 12 و 31 و 23 و 6 را که والد بودن آن به صورت غیر مستقیم است را جد یا ancestor می‌نامیم و نوه‌ها و نتیجه‌های آن‌ها را نسل descendants.

ریشه Root: به گره‌ای می‌گوییم که هیچ والدی ندارد و خودش در واقع اولین والد محسوب می‌شود؛ مثل گره 7.

برگ  Leaf: به گره‌هایی که هیچ فرزندی ندارند، برگ می‌گوییم. مثال گره‌های 1 و12 و 31 و 23 و 6

گره‌های داخلی Internal Nodes: گره هایی که نه برگ هستند و نه ریشه. یعنی حداقل یک فرزند دارند و خودشان یک گره فرزند محسوب می‌شوند؛ مثل گره‌های 19 و 14.

مسیر Path: راه رسیدن از یک گره به گره دیگر را مسیر می‌گویند. مثلا گره‌های 1 و 19 و 7 و 21 به ترتیب یک مسیر را تشکیل می‌دهند ولی گره‌های 1 و 19 و 23 از آن جا که هیچ جور اتصالی بین آن‌ها نیست، مسیری را تشکیل نمی‌دهند.

طول مسیر Length of Path: به تعداد لبه‌های یک مسیر، طول مسیر می‌گویند که می‌توان از تعداد گره‌ها -1 نیز آن را به دست آورد. برای نمونه : مسیر 1 و19 و 7 و 21 طول مسیرشان 3 هست.

عمق Depth: طول مسیر یک گره از ریشه تا آن گره را عمق درخت می‌گویند. عمق یک ریشه همیشه صفر است و برای مثال در درخت بالا، گره 19 در عمق یک است و برای گره 23 عمق آن 2 خواهد بود.

تعریف خود درخت Tree: درخت یک ساختار داده برگشتی recursive است که شامل گره‌ها و لبه‌ها، برای اتصال گره‌ها به یکدیگر است.

جملات زیر در مورد درخت صدق می‌کند:

  • هر گره می‌تواند فرزند نداشته باشد یا به هر تعداد که می‌خواهد فرزند داشته باشد.
  • هر گره یک والد دارد و تنها گره‌ای که والد ندارد، گره ریشه است (البته اگر درخت خالی باشد هیچ گره ای وجود ندارد).
  • همه گره‌ها از ریشه قابل دسترسی هستند و برای دسترسی به گره مورد نظر باید از ریشه تا آن گره، مسیری را طی کرد.
ار تفاع درخت Height: به حداکثر عمق یک درخت، ارتفاع درخت می‌گویند.
درجه گره Degree: به تعداد گره‌های فرزند یک گره، درجه آن گره می‌گویند. در درخت بالا درجه گره‌های 7 و 19 سه است. درجه گره 14 دو است و درجه برگ‌ها صفر است.
ضریب انشعاب Branching Factor: به حداکثر درجه یک گره در یک درخت، ضریب انشعاب آن درخت گویند.

پیاده سازی درخت

برای پیاده سازی یک درخت، از دو کلاس یکی جهت ساخت گره که حاوی اطلاعات است <TreeNode<T و دیگری جهت ایجاد درخت اصلی به همراه کلیه متدها و خاصیت هایش <Tree<T کمک می‌‌گیریم.

public class TreeNode<T>
{
    // شامل مقدار گره است
    private T value;
 
    // مشخص می‌کند که آیا گره والد دارد یا خیر
    private bool hasParent;
 
    // در صورت داشتن فرزند ، لیست فرزندان را شامل می‌شود
    private List<TreeNode<T>> children;
 
    /// <summary>سازنده کلاس </summary>
    /// <param name="value">مقدار گره</param>
    public TreeNode(T value)
    {
        if (value == null)
        {
            throw new ArgumentNullException(
                "Cannot insert null value!");
        }
        this.value = value;
        this.children = new List<TreeNode<T>>();
    }
 
    /// <summary>خاصیتی جهت مقداردهی گره</summary>
    public T Value
    {
        get
        {
            return this.value;
        }
        set
        {
            this.value = value;
        }
    }
 
    /// <summary>تعداد گره‌های فرزند را بر میگرداند</summary>
    public int ChildrenCount
    {
        get
        {
            return this.children.Count;
        }
    }
 
    /// <summary>به گره یک فرزند اضافه می‌کند</summary>
    /// <param name="child">آرگومان این متد یک گره است که قرار است به فرزندی گره فعلی در آید</param>
    public void AddChild(TreeNode<T> child)
    {
        if (child == null)
        {
            throw new ArgumentNullException(
                "Cannot insert null value!");
        }
 
        if (child.hasParent)
        {
            throw new ArgumentException(
                "The node already has a parent!");
        }
 
        child.hasParent = true;
        this.children.Add(child);
    }
 
    /// <summary>
    /// گره ای که اندیس آن داده شده است بازگردانده می‌شود
    /// </summary>
    /// <param name="index">اندیس گره</param>
    /// <returns>گره بازگشتی</returns>
    public TreeNode<T> GetChild(int index)
    {
        return this.children[index];
    }
}
 
/// <summary>این کلاس ساختار درخت را به کمک کلاس گره‌ها که در بالا تعریف کردیم میسازد</summary>
/// <typeparam name="T">نوع مقادیری که قرار است داخل درخت ذخیره شوند</typeparam>
public class Tree<T>
{
    // گره ریشه
    private TreeNode<T> root;
 
    /// <summary>سازنده کلاس</summary>
    /// <param name="value">مقدار گره اول که همان ریشه می‌شود</param>
    public Tree(T value)
    {
        if (value == null)
        {
            throw new ArgumentNullException(
                "Cannot insert null value!");
        }
 
        this.root = new TreeNode<T>(value);
    }
 
    /// <summary>سازنده دیگر برای کلاس درخت</summary>
    /// <param name="value">مقدار گره ریشه مثل سازنده اول</param>
    /// <param name="children">آرایه ای از گره‌ها که فرزند گره ریشه می‌شوند</param>
    public Tree(T value, params Tree<T>[] children)
        : this(value)
    {
        foreach (Tree<T> child in children)
        {
            this.root.AddChild(child.root);
        }
    }
 
    /// <summary>
    /// ریشه را بر میگرداند ، اگر ریشه ای نباشد نال بر میگرداند
    /// </summary>
    public TreeNode<T> Root
    {
        get
        {
            return this.root;
        }
    }
 
    /// <summary>پیمودن عرضی و نمایش درخت با الگوریتم دی اف اس </summary>
    /// <param name="root">ریشه (گره ابتدایی) درختی که قرار است پیمایش از آن شروع شود</param>
    /// <param name="spaces">یک کاراکتر جهت جداسازی مقادیر هر گره</param>
    private void PrintDFS(TreeNode<T> root, string spaces)
    {
        if (this.root == null)
        {
            return;
        }
 
        Console.WriteLine(spaces + root.Value);
 
        TreeNode<T> child = null;
        for (int i = 0; i < root.ChildrenCount; i++)
        {
            child = root.GetChild(i);
            PrintDFS(child, spaces + "   ");
        }
    }
 
    /// <summary>متد پیمایش درخت به صورت عمومی که تابع خصوصی که در بالا توضیح دادیم را صدا می‌زند</summary>
    public void TraverseDFS()
    {
        this.PrintDFS(this.root, string.Empty);
    }
}
 
/// <summary>
/// کد استفاده از ساختار درخت
/// </summary>
public static class TreeExample
{
    static void Main()
    {
        // Create the tree from the sample
        Tree<int> tree =
            new Tree<int>(7,
                new Tree<int>(19,
                    new Tree<int>(1),
                    new Tree<int>(12),
                    new Tree<int>(31)),
                new Tree<int>(21),
                new Tree<int>(14,
                    new Tree<int>(23),
                    new Tree<int>(6))
            );
 
        // پیمایش درخت با الگوریتم دی اف اس یا عمقی
        tree.TraverseDFS();
 
        // خروجی
        // 7
        //       19
        //        1
        //        12
        //        31
        //       21
        //       14
        //        23
        //        6
    }
}
کلاس TreeNode وظیفه‌ی ساخت گره را بر عهده دارد و با هر شیء‌ایی که از این کلاس می‌سازیم، یک گره ایجاد می‌کنیم که با خاصیت Children و متد AddChild آن می‌توانیم هر تعداد گره را که می‌خواهیم به فرزندی آن گره در آوریم که باز خود آن گره می‌تواند در خاصیت Children یک گره دیگر اضافه شود. به این ترتیب با ساخت هر گره و ایجاد رابطه از طریق خاصیت children هر گره درخت شکل می‌گیرد. سپس گره والد در ساختار کلاس درخت Tree قرار می‌گیرد و این کلاس شامل متدهایی است که می‌تواند روی درخت، عملیات پردازشی چون پیمایش درخت را انجام دهد.


پیمایش درخت به روش عمقی (DFS (Depth First Search

هدف از پیمایش درخت ملاقات یا بازبینی (تهیه لیستی از همه گره‌های یک درخت) تنها یکبار هر گره در درخت است. برای این کار الگوریتم‌های زیادی وجود دارند که ما در این مقاله تنها دو روش DFS و BFS را بررسی می‌کنیم.

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

همین درخت را در نظر بگیرید:


 پیمایش درخت را از گره 7 آغاز می‌کنیم و آن را به عنوان ریشه در نظر می‌گیریم. حتی می‌توانیم پیمایش را از گره مثلا 19 آغاز کنیم و آن را برای پیمایش ریشه در نظر بگیریم ولی ما از همان 7 پیمایش را آغاز می‌کنیم:

ابتدا گره 7 ملاقات شده و آن را می‌نویسیم. سپس فرزندانش را بررسی می‌کنیم که سه فرزند دارد. یکی از فرزندان مثل گره 19 را انتخاب کرده و آن را ملاقات می‌کنیم (با هر بار ملاقات آن را چاپ می‌کنیم) سپس فرزندان آن را بررسی می‌کنیم و یکی از گره‌ها را انتخاب می‌کنیم و ملاقاتش می‌کنیم؛ برای مثال گره 1. از آن جا که گره یک، برگ است و فرزندی ندارد یک مرحله به سمت بالا برمی‌گردیم و برگ‌های 12 و 31 را هم ملاقات می‌کنیم. حالا همه‌ی فرزندان گره 19 را بررسی کردیم، بر می‌گردیم یک مرحله به سمت بالا و گره 21 را ملاقات می‌کنیم و از آنجا که گره 21 برگ است و فرزندی ندارد به بالا باز می‌گردیم و بعد گره 14 و فرزندانش 23 و 6 هم بررسی می‌شوند. پس ترتیب چاپ ما اینگونه می‌شود:

7-19-1-12-31-21-14-23-6


پیمایش درخت به روش (BFS (Breadth First Search 

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

7-19-21-14-1-12-31-23-6

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

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

  • ریشه  وارد صف Q می‌شود.
  • دو مرحله زیر مرتبا تکرار می‌شوند:
  1. اولین گره صف به نام V را از Q در یافت می‌کنیم و آن را چاپ می‌کنیم.
  2. فرزندان گره V  را به صف اضافه می‌کنیم.
این نوع پیمایش، پیاده سازی راحتی دارد و همیشه نزدیک‌ترین گره‌ها به ریشه را می‌خواند و در هر مرحله گره‌هایی که می‌خواند از ریشه دورتر و دورتر می‌شوند.
نظرات مطالب
Blazor 5x - قسمت یازدهم - مبانی Blazor - بخش 8 - کار با جاوا اسکریپت
یک نکته‌ی تکمیلی: امکان نوشتن فایل‌های CSS اختصاصی کامپوننت‌ها در برنامه‌های Blazor

در نکته‌ی قبلی، در مورد JavaScript Isolation بحث شد؛ یک چنین قابلیتی در مورد فایل‌های css. هم وجود دارد و جزو تازه‌های Blazor 5x است. در این حالت (CSS Isolation) می‌توان شیوه‌نامه‌ای را تهیه کرد که تنها مختص به یک کامپوننت است و بر روی سایر کامپوننت‌ها تاثیری ندارد. این قابلیت نیاز به کدنویسی خاصی نداشته و به سادگی قابل استفاده‌است: در این حالت اگر کامپوننتی برای مثال Counter.razor نام دارد، برای ایجاد فایل css مختص به آن، تنها کافی است در کنار فایل آن، فایل Counter.razor.css را ایجاد و تکمیل کرد. به این ترتیب هر شیوه‌نامه‌ای که در این فایل تنظیم شود، فقط بر روی کامپوننت Counter تاثیرگذار خواهد بود؛ حتی اگر از این کامپوننت در کامپوننت‌های دیگر نیز استفاده شود.
h1 {
    color:red;
}
برای نمونه شیوه‌نامه‌ی فوق تنها به المان h1 کامپوننت Counter اعمال می‌شود و هیچ تاثیری را بر روی سایر کامپوننت‌های برنامه نخواهد داشت:


همانطور که مشاهده می‌کنید، در زمان build برنامه، تمام این شیوه‌نامه‌های اختصاصی کامپوننت‌های مختلف برنامه، تبدیل به یک فایل خواهند شد (با نام project_name.styles.css) که توسط ویژگی‌های HTML ای که به صورت خودکار تولید می‌شوند، از یکدیگر متمایز خواهند شد.

 اگر علاقمند باشیم تا این شیوه‌نامه به فرزندان کامپوننت Counter نیز به ارث برسد، باید از روش زیر استفاده کرد (استفاده از deep combinator):
::deep h1 {
   color:red;
}
مطالب
شروع به کار با AngularJS 2.0 و TypeScript - قسمت دوم - معرفی کامپوننت‌ها
در قسمت قبل، پیشنیازهای کار با AngularJS 2.0 مرور و دریافت شدند. اگر مطالب آن‌را پیگیری کرده باشید، هم اکنون باید در پوشه‌ی node_modules واقع در ریشه‌ی پروژه، تمام اسکریپت‌های لازم جهت شروع به کار با AngularJS 2.0 موجود باشند.


تعاریف مداخل فایل index.html یک سایت AngularJS 2.0

پروژه‌ای که در اینجا در حال استفاده است یک برنامه‌ی ASP.NET MVC 5.x است؛ اما الزامی هم به استفاده‌ی از آن وجود ندارد. یا یک فایل index.html را به ریشه‌ی پروژه اضافه کنید و یا فایل Views\Shared\_Layout.cshtml را به نحو ذیل تغییر دهید:
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>@ViewBag.Title - My ASP.NET Application</title>
 
    <link href="~/node_modules/bootstrap/dist/css/bootstrap.css" rel="stylesheet" />
    <link href="~/app/app.component.css" rel="stylesheet"/>
    <link href="~/Content/Site.css" rel="stylesheet" type="text/css" />
 
    <!-- 1. Load libraries -->
    <!-- IE required polyfills, in this exact order -->
    <script src="~/node_modules/angular2/es6/dev/src/testing/shims_for_IE.js"></script>
    <script src="~/node_modules/es6-shim/es6-shim.min.js"></script>
    <script src="~/node_modules/systemjs/dist/system-polyfills.js"></script>
 
    <script src="~/node_modules/angular2/bundles/angular2-polyfills.js"></script>
    <script src="~/node_modules/systemjs/dist/system.src.js"></script>
    <script src="~/node_modules/rxjs/bundles/Rx.js"></script>
    <script src="~/node_modules/angular2/bundles/angular2.dev.js"></script>
 
    <!-- Required for http -->
    <script src="~/node_modules/angular2/bundles/http.dev.js"></script>
 
    <!-- Required for routing -->
    <script src="~/node_modules/angular2/bundles/router.dev.js"></script> 
 
    <!-- 2. Configure SystemJS -->
    <script>
        System.config({
            packages: {
                app: {
                    format: 'register',
                    defaultExtension: 'js'
                }
            }
        });
        System.import('app/main')
              .then(null, console.error.bind(console));
    </script>
</head>
<body>
    <div>
        @RenderBody()
        <pm-app>Loading App...</pm-app>
    </div>
 
    @RenderSection("Scripts", required: false)
</body>
</html>
در اینجا ابتدا تعاریف مداخل بوت استرپ و css‌های سفارشی برنامه را مشاهده می‌کنید.
سپس کتابخانه‌های جاوا اسکریپتی مورد نیاز جهت کار با AngularJS 2.0 به ترتیبی که ذکر شده‌، باید تعریف شوند.
ذکر /~ در ابتدای آدرس‌ها، مختص به ASP.NET MVC است. اگر از آن استفاده نمی‌کنید، نیازی به ذکر آن هم نیست.
در ادامه تعاریف System.JS ذکر شده‌است. System.JS کار بارگذاری ماژول‌های برنامه را به عهده دارد. به این ترتیب دیگر نیازی نیست تا به ازای هر قسمت جدید برنامه، مدخلی را در اینجا اضافه کرد و کار بارگذاری آن‌ها خودکار خواهد بود. فرمت register ایی که در اینجا ذکر شده‌است، تا ماژول‌های استاندارد با فرمت ES 6 را نیز پشتیبانی می‌کند. همچنین با ذکر و تنظیم پسوند پیش فرض به js، دیگر نیازی نخواهد بود تا در حین تعریف importها در قسمت‌های مختلف برنامه، پسوند فایل‌ها را به صورت صریح ذکر کرد. مبحث improtها مرتبط است به مفاهیم ماژول‌ها در ES 6 و همچنین TypeScript.
سطر System.import کار بارگذاری اولین ماژول برنامه را از پوشه‌ی app قرار گرفته در ریشه‌ی سایت انجام می‌دهد. این ماژول main نام دارد.


نوشتن اولین کامپوننت AngularJS 2.0

برنامه‌های AngularJS 2.0 متشکل هستند از تعدادی کامپوننت و سرویس:


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


- یک قالب یا Template: با استفاده از HTML تعریف می‌شود و کار تشکیل View و نحوه‌ی رندر کامپوننت را مشخص می‌کند. در این Viewها با استفاده از امکانات binding و directives موجود در AngularJS 2.0 کار دسترسی به داده‌ها صورت می‌گیرد.
یک کلاس: کار این کلاس که توسط TypeScript تهیه می‌شود، فراهم آوردن کدهای مرتبط با قالب است. برای مثال این کلاس حاوی تعدادی خاصیت خواهد بود که از اطلاعات آن‌ها در View مرتبط استفاده می‌شود. همچنین این کلاس می‌تواند حاوی متدهای مورد نیاز در View نیز باشد؛ برای مثال متدی که کار نمایش یا مخفی سازی یک تصویر را با کلیک بر روی دکمه‌ای انجام می‌دهد.
- متادیتا: متادیتا (یا decorator در اینجا) به AngularJS 2.0 اعلام می‌کند که این کلاس تعریف شده، صرفا یک کلاس ساده نیست و باید به آن به صورت یک کامپوننت نگاه شود.

در ذیل، کدهای یک کامپوننت نمونه‌ی AngularJS 2.0 را مشاهده می‌کنید:
import { Component } from 'angular2/core';
 
@Component({
    selector: 'pm-app',
    template:`
    <div><h1>{{pageTitle}}</h1>
        <div>My First Component</div>
    </div>
    `
})
export class AppComponent {
    pageTitle: string = "DNT AngularJS 2.0 APP";
}
در انتهای کدها، یک کلاس را مشاهده می‌کنید که کار تعریف خواص و متدهای مورد نیاز توسط View را انجام می‌دهد.
بلافاصله در بالای این کلاس، متد decorator ایی را به نام Component مشاهده می‌کنید. این متادیتا است که به AngularJS 2.0 اعلام می‌کند، کلاس AppComponent تعریف شده، یک کامپوننت است و نه تنها یک کلاس ساده.
در متد Component تعریف شده، قالب یا template نحوه‌ی رندر این کامپوننت را مشاهده می‌کنید.
در ابتدای این ماژول نیز کار import تعاریف مرتبط با متد ویژه‌ی Component، از هسته‌ی AngularJS 2.0 انجام شده‌است تا کامپایلر TypeScript بتواند این فایل ts را کامپایل کند.


مروری بر نحوه‌ی تعریف class در TypeScript

مرور جامع کلاس‌ها در TypeScript را در مطلب «مبانی TypeScript؛ کلاس‌ها» می‌توانید مطالعه کنید. در اینجا جهت یادآوری، خلاصه‌ای از آن‌را که نیاز داریم، بررسی خواهیم کرد:
- جهت تعریف یک کلاس، ابتدا واژه‌ی کلیدی class به همراه نام کلاس ذکر می‌شوند.
- در AngularJS 2.0 مرسوم است که نام کلاس را به صورت نام ویژگی مدنظر به همراه پسوند Component ذکر کنیم؛ مانند AppComponent مثال فوق. این نام pascal case است و با حروف بزرگ شروع می‌شود.
- همچنین مرسوم است در برنامه‌های AngularJS 2.0، کامپوننت ریشه‌ی سایت نیز AppComponent نامیده شود.
- در مثال فوق، واژه‌ی کلیدی export را نیز پیش از واژه‌ی کلیدی class مشاهده می‌کنید. به این ترتیب این کلاس خارج از ماژولی که در آن تعریف می‌شود، قابل دسترسی خواهد بود. اکنون این کلاس و فایل، تبدیل به ماژولی خواهند شد که توسط module loader معرفی شده‌ی در ابتدای بحث یا همان System.JS به صورت خودکار بارگذاری می‌شود و دیگر نیازی به تعریف مدخل script متناظر با آن در فایل index.html نخواهد بود.
- در بدنه‌ی کلاس، کار تعریف متدها و خواص مورد نیاز View صورت می‌گیرند. برای نمونه در اینجا تنها یک خاصیت «عنوان صفحه» تعریف شده‌است. در جاوا اسکریپت مرسوم است که نام خواص را camel case شروع شده با حروف کوچک تعریف کنیم. سپس نوع این خاصیت به صورت رشته‌ای تعریف شده‌است و در آخر مقدار پیش فرض این خاصیت ذکر گردیده‌است.
البته باید دقت داشت که الزامی به ذکر نوع خاصیت، در TypeScript وجود ندارد. همینقدر که مقدار پیش فرض این خاصیت رشته‌ای است، بر اساس ویژگی به نام Type inference در TypeScript، نوع این خاصیت نیز رشته‌ای درنظر گرفته خواهد شد و دیگر نمی‌توان برای مثال یک عدد را به آن انتساب داد.
- سطح دسترسی خواص تعریف شده‌ی در یک کلاس TypeScript به صورت پیش فرض public است. بنابراین در اینجا نیازی به ذکر صریح آن نبوده‌است.


مروری بر متادیتا یا decorator یک کلاس در AngularJS 2.0

خوب، تا اینجا کلاس AppComponent تعریف و همچنین export شد تا توسط system.js به صورت خودکار بارگذاری شود. اما این کلاس به خودی خود صرفا یک کلاس TypeScript ایی است و توسط AngularJS شناسایی نمی‌شود. برای معرفی این کلاس به صورت یک کامپوننت، از یک تزئین کننده یا decorator ویژه به نام Component استفاده می‌شود که بلافاصله در بالای تعریف کلاس قرار می‌گیرد؛ چیزی شبیه به data annotations یا attributes در زبان #C.
یک decorator متدی است که اطلاعاتی اضافی را به یک کلاس، اعضاء و متدهای آن و یا حتی آرگومان‌های آن متدها، الصاق می‌کند. این ویژگی قرار است به صورت استاندارد در ES 2016 یا نگارش بعدی جاوا اسکریپت حضور داشته باشد و در حال حاضر توسط TypeScript پشتیبانی شده و در نهایت به کدهای ES 5 قابل اجرای در تمام مرورگرها ترجمه می‌شود.
یک decorator همیشه با @ شروع می‌شود و AngularJS 2.0 به همراه تعدادی decorator توکار است؛ مانند Component. از آنجائیکه decorator یک متد است، همیشه به همراه یک جفت پرانتز () ذکر می‌شود و در انتهای آن نیازی به ذکر سمی‌کالن نیست. در اینجا تزئین کننده‌ی Component یک شیء را می‌پذیر که به همراه تعدادی خاصیت است. به همین جهت پارامتر آن به صورت {} ذکر شده‌است.
خاصیت selector یک کامپوننت مشخص می‌کند که نام directive متناظر با این کامپوننت چیست:
 selector: 'pm-app',
 از این نام، به صورت یک المان جدید و سفارشی HTML جهت تعریف این کامپوننت استفاده خواهیم کرد. برای مثال اگر به کدهای ابتدای بحث دقت کنید، نام pm-app به صورت ذیل و به شکل یک تگ جدید HTML استفاده شده‌است:
    <div>
        @RenderBody()
        <pm-app>Loading App...</pm-app>
    </div>
همچنین یک کامپوننت همواره به همراه یک قالب است که نحوه‌ی رندر آن‌را مشخص می‌کند:
  template:`
 <div><h1>{{pageTitle}}</h1>
<div>My First Component</div>
 </div>
 `
 در اینجا از back tick مربوط به ES 6.0 که توسط TypeScript نیز پشتیبانی می‌شود، جهت تعریف یک رشته‌ی چندسطری جاوا اسکریپتی، استفاده شده‌است.
همچنین {{}} به معنای تعریف data binding است. به این ترتیب مقداری که قرار است به صورت تگ h1 رندر شود، از خاصیت pageTitle کلاس مزین شده‌ی توسط این ویژگی یا decorator تامین خواهد شد؛ یعنی مقدار پیش فرض خاصیت pageTitle در کلاس AppComponent.


import اطلاعات مورد نیاز جهت کامپایل یک فایل TypeScript

تا اینجا مفاهیم موجود در کلاس AppComponent را به همراه متادیتای آن بررسی کردیم. اما این متادیتای جدید کامپوننت، به صورت پیش فرض ناشناخته‌است:


همانطور که مشاهده می‌کنید، در اینجا ذیل کامپوننت، خط قرمزی جهت یادآوری عدم تعریف آن، کشیده شده‌است. در TypeScript و یا ES 6، پیش از استفاده از یک کلاس یا متد خارجی، نیاز است به module loader اعلام کنیم تا آن‌را باید از کجا تامین کند. اینکار توسط عبارت import انجام می‌شود که شبیه به عبارت using در زبان سی‌شارپ است. عبارت import جزئی از استاندارد ES 6 است و همچنین در TypeScript نیز پشتیبانی می‌شود. به این ترتیب امکان دسترسی به ویژگی‌های export شده‌ی از سایر ماژول‌ها را در ماژول فعلی (فایل فعلی در حال کار) خواهیم یافت. نمونه‌ی آن‌را با ذکر واژه‌ی کلیدی export پیش از کلاس AppComponent پیشتر ملاحظه کردید.
این ماژول‌های خارجی می‌توانند سایر ماژول‌ها و فایل‌های ts نوشته شده‌ی توسط خودمان و یا حتی اجزای AngularJS 2.0 باشند. طراحی AngularJS 2.0 نیز ماژولار است و از ماژول‌هایی مانند angular2/core، angular2/animation، angular2/http و angular2/router تشکیل شده‌است.
برای نمونه متادیتای کامپوننت، در ماژول angular2/core قرار دارد. به همین جهت نیاز است در ابتدای ماژول فعلی آن‌را import کرد:
import { Component } from 'angular2/core';
کار با ذکر واژه‌ی کلیدی import شروع می‌شود. سپس جزئی را که نیاز داریم داخل {} ذکر کرده و در آخر مسیر یافتن آن‌را مشخص می‌کنیم.


ساخت کامپوننت ریشه‌ی یک برنامه‌ی AngularJS 2.0

در ابتدای بحث که تعاریف مداخل مورد نیاز جهت اجرای یک برنامه‌ی AngularJS 2.0 ذکر شدند، عنوان شد که system.js به دنبال ماژول آغازین app/main می‌گردد.
بنابراین در ریشه‌ی پروژه، پوشه‌ی جدیدی را به نام app ایجاد کنید.
سپس یک فایل TypeScript جدید را به نام app.component.ts به این پوشه اضافه کنید. قالب این نوع فایل‌ها در add new item و با جستجو typescript در دسترس است و یا حتی یک فایل متنی ساده را هم با پسوند ts ایجاد کنید، کافی است.


نامگذاری این فایل‌ها هم مرسوم است به صورت ذکر نام ویژگی به همراه یک دات و سپس ذکر کامپوننت صورت گیرد. در اینجا چون قصد داریم کامپوننت ریشه‌ی برنامه را ایجاد کنیم، نام ویژگی آن app خواهد بود و نام کامل فایل به این ترتیب app.component.ts می‌شود.
سپس محتوای این فایل را به دقیقا معادل کدهای قسمت «نوشتن اولین کامپوننت AngularJS 2.0» ابتدای بحث تغییر دهید (همان import، متادیتا و کلاس AppComponent).

تا اینجا کامپوننت ریشه‌ی برنامه ایجاد شد. اما چگونه باید از آن استفاده کنیم و چگونه AngularJS 2.0 آن‌را شناسایی می‌کند؟ به این فرآیند آغازین شناسایی و بارگذاری ماژول‌ها، اصطلاحا bootstrapping می‌گویند. تنها صفحه‌ی واقعی موجود در یک برنامه‌ی تک صفحه‌ای وب، همان فایل index.html است و سایر صفحات و محتوای آن‌ها به محتوای این صفحه‌ی آغازین اضافه یا کم خواهند شد.
<div>
    @RenderBody()
    <pm-app>Loading App...</pm-app>
</div>
در اینجا برای نمایش اولین کامپوننت تهیه شده، نام تگ selector آن که توسط متادیتای کلاس AppComponent تعریف شد، در body فایل index.html به نحو فوق به صورت یک تگ سفارشی جدید اضافه می‌شود. به آن directive نیز می‌گویند.
خوب، اکنون module loader یا system.js چگونه این pm-app یا کامپوننت ریشه‌ی برنامه را شناسایی می‌کند؟
 System.import('app/main')
این شناسایی توسط سطر System.import تعریف شده‌ی در فایل index.html انجام می‌شود. در اینجا system.js، در پوشه‌ی app واقع در ریشه‌ی سایت، به دنبال ماژول راه اندازی به نام main می‌گردد. یعنی نیاز است فایل TypeScript جدیدی را به نام main.ts به ریشه‌ی پوشه‌ی app اضافه کنیم. محتوای این فایل ویژه‌ی بوت استرپ AngularJS 2.0 به صورت ذیل است:
/// <reference path="../node_modules/angular2/typings/browser.d.ts" />
 
import { bootstrap } from "angular2/platform/browser";
 
// Our main component
import { AppComponent } from "./app.component";
 
bootstrap(AppComponent);
این فایل ویژه را نیز مانند کلاس AppComponent که پیشتر بررسی کردیم، نیاز است از انتها به ابتدا بررسی کرد.
در انتهای این فایل متد bootstrap مربوط به AngularJS 2.0 را مشاهده می‌کنید. کار آن بارگذاری اولین ماژول و کامپوننت برنامه یا همان AppComponent است.
در ادامه نیاز است AppComponent را به این ماژول معرفی کرد؛ در غیراینصورت کامپایل نخواهد شد. برای این منظور سطر import این کلاس را از فایل app.component، مشاهده می‌کنید. در اینجا نیازی به ذکر پسوند ts. فایل app.component نیست.
سپس نیاز است محل تعریف متد بوت استرپ را نیز مشخص کنیم. این متد در ماژول angular2/platform/browser قرار دارد که به عنوان اولین import این فایل ذکر شده‌است.
سطر اول، مربوط است به تعریف فایل‌های d.ts. مربوط به TypeScript جهت شناسایی نوع‌های مرتبط با AngularJS 2.0. اگر اینکار صورت نگیرد، خطاهای ذیل را در حین کامپایل فایل‌های TypeScript دریافت خواهید کرد:
 node_modules\angular2\src\core\application_ref.d.ts(171,81): error TS2304: Build: Cannot find name 'Promise'.
node_modules\angular2\src\core\change_detection\differs\default_keyvalue_differ.d.ts(23,15): error TS2304: Build: Cannot find name 'Map'.
تهیه فایل main.ts تنها یکبار صورت می‌گیرد و دیگر با آن کاری نخواهیم داشت.

تا اینجا پوشه‌ی app واقع در ریشه‌ی سایت، یک چنین شکلی را پیدا می‌کند:



و اکنون اگر برنامه را اجرا کنیم (فشردن دکمه‌ی F5)، خروجی آن در مرورگر به صورت ذیل خواهد بود:

با توجه به اینکه در حال کار با یک برنامه‌ی جاوا اسکریپتی هستیم، باز نگه داشتن developer tools مرورگر، جهت مشاهده‌ی خطاهای احتمالی ضروری است.

در اینجا اگر خطایی وجود داشته باشد، یا اطلاعات اضافی مدنظر باشد، در console لاگ خواهند شد. برای مثال در اینجا عنوان شده‌است که برنامه در حالت توسعه در حال اجرا است. بهتر است برای ارائه‌ی نهایی، متد enableProdMode را در فایل index.html فراخوانی کنید.

همچنین در اینجا می‌توان HTML نهایی تزریق شده‌ی به صفحه را بهتر مشاهده کرد:



کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: MVC5Angular2.part2.zip
برای اجرای آن، ابتدا به فایل project.json مراجعه کرده و یکبار آن‌را ذخیره کنید تا وابستگی‌های اسکریپتی پروژه از اینترنت دریافت شوند (این موارد در قسمت قبل مرور شدند). سپس یکبار هم پروژه را Build کنید تا تمام فایل‌های ts آن کامپایل شوند و در آخر، اجرای نهایی پروژه.


خلاصه‌ی بحث

یک برنامه‌ی AngularJS 2.0 متشکل است از تعدادی کامپوننت. بنابراین کلاسی را ایجاد خواهیم کرد تا کدهای پشتیبانی کننده‌ی View این کامپوننت را تولید کند. سپس این کلاس را با متادیتایی مزین کرده و توسط آن تگ سفارشی ویژه‌ی این کامپوننت و تگ‌های HTML تشکیل دهنده‌ی این کامپوننت را به AngularJS 2.0 معرفی می‌کنیم. در اینجا در صورت نیاز وابستگی‌های تعریف این متادیتا را توسط واژه‌ی کلیدی import دریافت می‌کنیم. نام این کلاس بهتر است Pascal case بوده و با حروف بزرگ شروع شود و همچنین به صورت نام ویژگی ختم شده‌ی به کلمه‌ی Component باشد. در اینجا حتما نیاز است این کلاس export شود تا توسط module loader قابل استفاده و بارگذاری گردد. اگر View این کامپوننت نیاز به دریافت اطلاعاتی دارد، این اطلاعات به صورت خواصی در کلاس کامپوننت تعریف می‌شوند. این خواص تعریف شده‌ی با سطح دسترسی عمومی، مرسوم است به صورت camel case تعریف شوند و حروف اول آن‌ها کوچک باشد.
مطالب
آموزش فریم ورک Vuetify قسمت دوم - UI Components بخش اول

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

یک کامپوننت در واقع از دو بخش تشکیل شده‌است:

1 - اول اینکه چگونه به نظر می‌رسد ( UI ).

2 – دوم اینکه چگونه کار میکند ( UX ).

این عناصر رابط ( component ) شامل :

Input Controls : check boxes, radio buttons, drop down lists, list boxes, buttons, toggles, text fields, date field

Navigational Components : breadcrumb, slider, search field, pagination, slider, tags, icons

Informational Components : tool tips, icons, progress bar, notifications, message boxes, modal windows

Containers : accordion 

اما باید توجه داشت که فقط به این موارد محدود نمی‌شوند.

در این قسمت به طور مختصر با این دست از کامپوننت‌ها ( UI Component ) آشنا می‌شویم. 


کامپوننت v-alert 

کامپوننت V-Alert برای انتقال اطلاعات مهم به کاربر مورد استفاده قرار می‌گیرد. این کامپوننت چهار نوع اطلاعات را به کاربر گوشزد می‌کند که شامل موفقیت‌ها، اطلاعات، هشدارها و خطاها می‌باشد.

هشدارها می‌توانند یک رنگ خاص را داشته باشند که به طور پیش فرض نمایش داده نمی‌شوند. 

در مثال پایین، کامپوننت v-alert شامل دو مقدار است که برای آن تنظیم شده‌است. مقدار (value) که شامل یک مقدار Boolean است و مقدار (type) که مشخص کننده نوع هشدار است (موفقیت ، اطلاعات ، هشدار و خطا).

در قطعه کد پایین، این چهار نوع اطلاعات قابل نمایش به کاربر مشخص شده‌اند:

<div id="app">
  <v-app id="inspire">
    <div>
      <v-alert :value="true" type="success">
        This is a success alert.
      </v-alert>
  
      <v-alert :value="true" type="info">
        This is a info alert.
      </v-alert>
  
      <v-alert :value="true" type="warning">
        This is a warning alert.
      </v-alert>
  
      <v-alert :value="true" type="error">
        This is a error alert.
      </v-alert>
    </div>
  </v-app>
</div>



برای کامپوننت V-Alert می‌توان properties‌های مختلفی را مشخص نمود که از جمله آنها می‌توان به موارد زیر اشاره کرد:

Color : به وسیله این property می‌توان رنگ پیغام را مشخص نمود. هم به وسیله نام رنگ میتوان رنگ مورد نظر را مشخص کرد و هم به وسیله‌ی کد RGB این کار را انجام داد. dismissible : این تنظیم مشخص می‌کند که پیغام، قابلیت بسته شدن را دارد یا خیر که حاوی یک مقدار Boolean است.

icon : مشخص کننده یک نماد خاص است که درون جعبه پیغام قرار می‌گیرد.

type :  مشخص کننده نوع پیام است که پیشتر در مورد آن توضیح داده شد. 


کامپوننت v-avatar 

کامپوننت v-avatar برای تغییر اندازه تصاویر مورد استفاده قرار می‌گیرد که معمولا جهت نمایش عکس پروفایل استفاده می‌شود.

طریقه استفاده :

avatar، دارای یک اندازه‌ی پویا است که می‌تواند برای هر وضعیتی تغییر کند.

برای این کامپوننت سه properties قابل تنظیم است:

color : به وسیله این property می‌توان رنگ دلخواهی را برای آواتار مشخص نمود. هم به وسیله نام رنگ می‌توان رنگ مورد نظر را مشخص کرد و هم به وسیله کد RGB این کار را صورت داد.

size : به طور پیش‌فرض برای avatar، سایز 48 تنظیم شده‌است که می‌توان این میزان را کم و یا زیاد کرد.

tile : همانند border radius در css عمل می‌کند. با تنظیم این گزینه میتوانیم یک آواتار گرد داشته باشیم.


کامپوننت v-badge

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

این کامپوننت نیز properties خاص خود را دارد که از جمله آن می‌توان به color , left , mode , overlab  و غیره اشاره کرد.

قطعه کد پایین نشان دهنده چگونگی عملکرد این کامپوننت است:

<div id="app">
  <v-app id="inspire">
    <div>
      <v-badge color="purple" left overlap>
        <template v-slot:badge>
          <v-icon dark small>
            done
          </v-icon>
        </template>
        <v-icon color="grey lighten-1" large>
          account_circle
        </v-icon>
      </v-badge>
        <v-badge overlap color="orange">
        <template v-slot:badge>
          <v-icon dark small>
            notifications
          </v-icon>
        </template>
        <v-icon large color="grey darken-1">
          account_box
        </v-icon>
      </v-badge>
    </div>
  </v-app>
</div>

نتیجه قطعه کد بالا بدین ترتیب است:


کامپوننت v-bottom-nav 

این کامپوننت را می‌توان جایگزین sidebar‌ها نمود. این کامپوننت در درجه اول در موبایل مورد استفاده قرار میگیرد که می‌تواند شامل متن و یا آیکن باشد. 

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

این کامپوننت نیز properties خاص خود را دارد که از جمله آن می‌توان به active-sync (برای نشان دادن فعال یا غیر فعال بودن گزینه انتخاب شده)، fixed ( برای مشخص کردن موقعیت کامپوننت در صفحه) و موارد دیگر اشاره کرد.

تقسیم بندی اجزاء این کامپوننت به شرح زیر است:

1 - محل قرار گیری کامپوننت

2- آیکن غیر فعال

3- برچسب غیر فعال

4 - آیکن فعال

5- برچسب فعال

قطعه کد پایین نشان دهنده چگونگی یک bottom navbar است:

<div id="app">
  <v-app id="inspire">
    <v-card height="200px" flat>
      <div>
        Active: {{ bottomNav }}   // 
      </div>
      <v-bottom-nav :active.sync="bottomNav" :value="true" absolute color="transparent">
        <v-btn color="teal" flat value="recent">
          <span>Recent</span>
          <v-icon>history</v-icon>
        </v-btn>
  
        <v-btn color="teal" flat value="favorites">
          <span>Favorites</span>
          <v-icon>favorite</v-icon>
        </v-btn>
  
        <v-btn color="teal" flat value="nearby">
          <span>Nearby</span>
          <v-icon>place</v-icon>
        </v-btn>
      </v-bottom-nav>
    </v-card>
  </v-app>
</div>



کامپوننت v-btn  

این کامپوننت برای ایجاد یک دکمه چه به صورت متن و یا آیکن مورد استفاده قرار میگیرد. دکمه‌ها به کاربران این امکان را می‌دهند تا اقداماتی را انجام دهند و انتخاب‌های خود را تنها با یک کلیک انجام دهند.

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

کامپوننت v-btn نیز مانند سایر کامپوننت‌ها تنظیمات خاص خود را دارد که از جمله آن می‌توان به کوچکی و بزرگی دکمه، فعال یا غیر فعال بودن دکمه، نوع متن یا آیکن بودن دکمه اشاره نمود. 

حالت‌های مختلفی از دکمه‌ها وجود دارند که میتوانند به بهتر شدن UI برنامه ما کمک کنند. برای مثال می‌توان به موارد زیر اشاره کرد:

button drop-down variants : دکمه‌های کرکره‌ای که معمولا برای نظم و کم جا بودن در صفحه مورد استفاده قرار می‌گیرند.

icons : آیکن‌ها می‌توانند برای محتوای اصلی یک دکمه مورد استفاده قرار بگیرند تا ظاهر زیباتری را به دکمه ما بدهند.

floating : این دکمه‌ها حالت آیکن را دارند؛ با این تفاوت که آیکن مورد نظر، درون یک محتوا قرار می‌گیرد.

loaders : به وسیله این دکمه‌ها می‌توان کاربر را متوجه انجام یک پردازش نمود. به صورت پیشفرض بعد از فشردن این نوع دکمه‌ها محتوای دکمه فشرده شده تغییر ظاهر داده و به شکل یک دایره در حال چرخش در می‌آید. البته می‌توان این پیشفرض را به حالت‌های دیگری نیز تغییر داد.

round : این نوع دکمه‌ها دقیقا کارآیی دکمه‌های معمولی را دارند؛ با این تفاوت که این دکمه‌ها دارای لبه‌هایی گرد هستند.

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

<div id="app">
  <v-app id="inspire">
    <div>
      <v-btn color="success">Success</v-btn>
      <v-btn color="error">Error</v-btn>
      <v-btn color="warning">Warning</v-btn>
      <v-btn color="info">Info</v-btn>
    </div>
  </v-app>
</div>




کامپوننت v-calendar

یکی از کامپوننت‌هایی که به تازگی به vuetify اضافه شده است، کامپوننت تقویم یا v-calendar است. از این کامپوننت برای نمایش تاریخ، روز، هفته، ماه و سال استفاده می‌شود. یک تقویم دارای یک نوع و یک مقدار است که تعیین می‌کند چه نوع تقویمی، در طول چه مدت زمانی نمایش داده شود.

حالت‌های مختلفی برای نمایش تقویم در صفحه وجود دارد که برای مثال می‌توان به موارد زیر اشاره کرد: 

events : به وسیله این گزینه میتوان برای هر روز یک رخداد خاص را مشخص نمود که به وسیله کلیک بر روی آن، اطلاعات آن رخداد نمایش داده شود.

weekly : می‌توان یک تقویم هفتگی را ایجاد نمود و رخدادهای هفتگی را برای آن تنظیم کرد.

نمونه ایجاد یک تقویم در پایین آمده است:

<div id="app">
  <v-app id="inspire">
    <v-layout wrap>
      <v-flex xs12>
        <v-sheet height="500">
          <v-calendar ref="calendar" v-model="start" :type="type" :end="end" color="primary">
          </v-calendar>
        </v-sheet>
      </v-flex>
  
      <v-flex sm4 xs12>
        <v-btn @click="$refs.calendar.prev()">
          <v-icon dark left>
            keyboard_arrow_left
          </v-icon>
          Prev
        </v-btn>
      </v-flex>
      <v-flex sm4 xs12>
        <v-select v-model="type" :items="typeOptions" label="Type">
        </v-select>
      </v-flex>
      <v-flex sm4 xs12>
        <v-btn @click="$refs.calendar.next()">
          Next
          <v-icon right dark>
            keyboard_arrow_right
          </v-icon>
        </v-btn>
      </v-flex>
    </v-layout>
  </v-app>
</div>
js قطعه کد
new Vue({
  el: '#app',
  data: () => ({
    type: 'month', //مشخص کننده نوع تقویم که در اینجا تقویم به صورت ماهانه است
    start: '2019-01-01',
    end: '2019-01-06',
    typeOptions: [
      { text: 'Day', value: 'day' },
      { text: '4 Day', value: '4day' },
      { text: 'Week', value: 'week' },
      { text: 'Month', value: 'month' },
      { text: 'Custom Daily', value: 'custom-daily' },
      { text: 'Custom Weekly', value: 'custom-weekly' }
    ]
  })
})





مطالب
پیاده سازی پروژه‌های React با TypeScript - قسمت هشتم - تعیین نوع کامپوننت‌های کلاسی
با ارائه‌ی React Hooks، استفاده‌ی از کامپوننت‌های کلاسی، کمتر و کمتر شده‌اند. اما جهت تکمیل و خاتمه‌ی سری جاری، این نوع را نیز در این قسمت بررسی می‌کنیم.


تعیین نوع props و state در کامپوننت‌های کلاسی

برای این منظور ابتدا فایل جدید src\components\BigC.tsx را ایجاد کرده و سپس توسط میانبر rcc، ساختار ابتدایی این کامپوننت را ایجاد می‌کنیم. در ادامه آن‌را در کامپوننت src\App.tsx استفاده خواهیم کرد:


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

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


در اینجا نیز همانند کامپوننت‌های تابعی، باید ابتدا نوع Props را تعیین کرد و تنها تفاوت آن، روش معرفی این type جدید به کامپوننت است:
import React, { Component } from "react";

type Props = {
  title: string;
};

type State = {
  status: string;
};

export default class BigC extends Component<Props, State> {
  render() {
    return (
      <div>
        <h1>I'm in a class component</h1>
      </div>
    );
  }
}
در کامپوننت‌های کلاسی، نوع State و نوع Props، از طریق آرگومان‌های جنریک Component، به نحوی که مشاهده می‌کنید، مشخص می‌شوند.
مابقی نکات آن مانند props اختیاری و غیره، تفاوت خاصی را با کامپوننت‌های تابعی که پیشتر بررسی کردیم، ندارند و یکی هستند.


مقایسه‌ای بین Types و Interfaces

در این سری بیشتر از types استفاده شد، تا اینترفیس‌های تایپ‌اسکریپت. برای مثال بجای نوع زیر:
type State = {
   rValue: boolean;
};
می‌توان از اینترفیس معادل آن نیز استفاده کرد:
interface State {
  rValue: boolean;
}
اما از کدامیک باید استفاده کرد؟
در تایپ‌اسکریپت، از type بیشتر برای تعریف یک نوع جدید استفاده می‌شود، اما از اینترفیس‌ها برای تعریف موجودیت‌ها و entities. برای مثال از type می‌توان برای تعریف نامی برای نوع‌های primitive, union, intersection نیز استفاده کرد، اما اینترفیس‌ها همواره برای تعیین نوع اشیاء مورد استفاده قرار می‌گیرند. نمی‌توان دو نوع هم‌نام را تعریف کرد، اما اگر دو اینترفیس هم نام را تعریف کنید، با هم یکی می‌شوند. نوع‌ها برخلاف اینترفیس‌ها قابلیت پیاده سازی نداشته و بیشتر جنبه‌ی یک اعلان را دارند و ... در نهایت استفاده‌ی از هر کدام بیشتر یک انتخاب شخصی است.


منابع تکمیلی

یکی از بهترین منابع تکمیلی استفاده‌ی از TypeScript در React، مخزن کد react-typescript-cheatsheet است که به همراه مجموعه‌ای از نکات مرتبط است.
مطالب
آموزش (jQuery) جی کوئری 5#
در ادامه مطلب قبلی  آموزش (jQuery) جی کوئری 4# به ادامه بحث  می‌پردازیم.
در پست قبل به بررسی انتخاب عناصر بر اساس موقعیت پرداختیم، در این پست به بحث "استفاده از انتخاب کننده‌های سفارشی jQuery" خواهیم پرداخت.

4-1- استفاده از انتخاب کننده‌های سفارشی jQuery
در پست‌های قبلی (^ و ^ ) تعدادی از انتخاب کننده‌های CSS که هر کدامشان موجب قدرت و انعطاف پذیری انتخاب اشیا موجود در صفحه می‌شوند را بررسی کردیم. با این وجود  فیلتر‌های انتخاب کننده قدرتمندتری وجود دارند که توانایی ما را برای انتخاب بیشتر می‌کنند.
به عنوان مثال اگر بخواهید از میان تمام چک باکس ها، گزینه هایی را که تیک خورده اند انتخاب نمایید، از آنجا که تلاش برای مطابقت حالت‌های اولیه کنترل‌های HTML را بررسی می‌کنیم، jQuery انتخابگر سفارشی checked: را پیشنهاد می‌کند، که مجموعه از عناصر را که خاصیت checked آنها فعال باشد را برای ما برمی گرداند. براس مثال انتخاب کننده input تمامی المان‌های <input> را انتخاب می‌کند، و انتخاب کننده input:checked تمامی inputهایی را انتخاب می‌کند که checked هستند. انتخاب کننده سفارشی checked:یک انتخاب کننده خصوصیت CSS عمل می‌کند (مانند [foo=bar]). ترکیب این انتخاب کننده‌ها می‌تواند قدرت بیشتری به ما بدهد، انتخاب کننده هایی مانند radio:checked: و checkbox:checked: .
همانطور هم که قبلا بیان شد، jQuery علاوه بر پشتیبانی از انتخاب کننده‌های CSS تعدادی انتخاب کننده سفارشی را نیز شامل می‌شود که در جدول 3-2 شرح داده شده است.

جدول 3-2: انتخاب کننده‌های سفارشی jQuery
 توضیح انتخاب کننده
 عناصری را انتخاب می‌کند که تحت کنترل انیمیشن می‌باشند. در پست‌های بعدی انیمیشن‌ها توضیح داده می‌شوند.
animated:
 عناصر دکمه را انتخاب می‌کند، عناصری مانند (input[type=submit]، input[type=reset]، input[type=button]،  یا button) 
button:
عناصر Checkbox را انتخاب می‌کند، مانند ([input[type=checkbox).
checkbox:
عناصر checkboxها یا دکمه‌های رادیویی را انتخاب می‌کند که در حالت انتخاب باشند.
checked:
عناصری ر انتخاب می‌کند که دارای عبارت foo باشند.
contains(foo) //c:
عناصر در حالت disabled را انتخاب می‌کند. disabled:
عناصر در حالت enabledرا انتخاب می‌کند.
enabled:
عناصر فایل را انتخاب می‌کند، مانند ([input[type=file).
file:
عناصر هدر مانند h1 تا h6 را انتخاب می‌کند.
header:
عناصر مخفی شده را انتهاب می‌کند.
hidden:
عناصر تصویر را انتخاب می‌کند، مانند ([input[type=image).
image:
عناصر فرم مانند input ، select، textarea، button را انتخاب می‌کند.
input:
انتخاب کننده‌ها را برعکس می‌کند.
not(filter)//c:
عناصری که فرزندی دارند را انتخاب می‌کند.
parent:
عناصر password را انتخاب می‌کند، مانند ([input[type=password). password:
عناصر radio را انتخاب می‌کند، مانند ([input[type=radio). 
radio:
دکمه‌های reset را انتخاب می‌کند، مانند  ([input[type=reset یا [button[type=reset).
raset:
عناصری (عناصر option) را انتخاب می‌کند که در وضعیت selected قراردارند.
selected:
دکمه‌های submit را انتخاب می‌کند، مانند  ([input[type=submit یا [button[type=submit). submit:
عناصر text را انتخاب می‌کند، مانند ([input[type=text).   
text:
عناصری را که در وضعیت visibleباشند انتخاب می‌کند.
visible:
بسیاری از انتخاب کننده‌های سفارشی jQuery بررسی شده برای انتخاب عناصر فرم ورود اطلاعات کاربر استفاده می‌شوند. این فیلتر‌ها قابلیت ادغام را دارند، برای مثال در زیر دستوری را به منظور انتخاب آن دسته از گزینه‌های Checkbox که تیک خورده اند و فعال هستند را مشاهده می‌کنید:
:checkbox:checked:enabled

این فیلتر‌ها و انتخاب کننده‌ها کاربردهای وسیعی در صفحات اینترنتی دارند، آیا آنها حالت معکوسی نیز دارند؟

استفاده از فیلتر not:
برای آنکه نتیجه انتخاب کننده‌ها را معکوس کنیم می‌توانیم از این فیلتر استفاده کنیم. برای مثال دستور زیر تمام عناصری را که checkBox نیستند را انتخاب می‌کند:
input:not(:checkbox)
اما استفاده از این فیلتر دقت زیادی را می‌طلبد زیرا به سادگی ممکن است با نتیجه ای غیر منتظره مواجه شویم.

استفاده از فیلتر has:
در اینجا دیدیم که CSS انتخاب کننده قدرتمندی را ارایه کرده است که فرزندران یک عنصر را در هر سطحی که باشند (حتی اگر فرزند مستقیم هم نباشند) انتخاب می‌کند. برای مثال دستور زیر تمام عناصر span را که در div معرفی شده باشند را انتخاب می‌کند:
div span

اما اگر بخواهیم انتخابی برعکس این انتخاب داشته باشیم، باید چه کنیم؟ برای این کار باید تمام divهایی که دارای عنصر span می‌باشد را انتخاب کرد. برای چنین انتخابی از فیلتر has: استفاده می‌کنیم. به دستور زیر توجه نمایید، این دستور تمام عناصر div را که در آنها عنصر span معرفی شده است را انتخاب می‌کند:
div:has(span)

برای برخی انتخاب‌های پیچیده و مشکل، این فیلتر و مکانیزم بسیار کارا می‌باشد و به سادگی ما را به هدف دلخواه می‌رساند. فرض کنید می‌خواهیم آن خانه از جدول که دارای یک عنصر عکس خاص می‌باشد را پیدا کنیم. با توجه به این نکته که آن عکس از طریق مقدار src قابل تشخیص می‌باشد، با استفاده از فیلتر has: دستوری مانند زیر می‌نویسیم:
$('tr:has(img[src$="foo.png"])')

این دستور هر خانه از جدول را که این عکس در آن قرار گرفته باشد را انتخاب می‌کند.
همانگونه که دیدیم jQuery گزینه‌های بسیار متعددی را به منظور انتخاب عناصر موجود در صفحه برای ما مهیا کرده است که می‌توانیم هر عنصری از صفحه را انتخاب و سپس تغییر دهیم که تغییر این عناصر در پست‌های آینده بحث خواهد شد.


موفق و موید باشید.
مطالب
Angular Material 6x - قسمت ششم - کار با فرم‌ها و دیالوگ‌ها
در این قسمت قصد داریم به لیست فعلی کاربران و تماس‌های تعریف شده، تماس‌های جدیدی را اضافه کنیم و می‌خواهیم این‌کار را توسط دیالوگ‌های Popup بسته‌ی Angular Material انجام دهیم.


معرفی سرویس MatDialog

توسط سرویس MatDialog می‌توان modal dialogs بسته‌ی Angular Material را نمایش داد که به همراه طراحی متریال و پویانمایی مخصوص آن است.
 let dialogRef = dialog.open(UserProfileComponent,  { height: '400px’,  width: '600px’  });
در اینجا یک صفحه‌ی دیالوگ، توسط متد open آن باز خواهد شد. پارامتر اول آن کامپوننتی است که باید بارگذاری شود و پارامتر دوم آن یک شیء تنظیمات اختیاری است. خروجی این متد وهله‌ای است از MatDialogRef و توسط آن می‌توان به دیالوگ باز شده دسترسی یافت:
dialogRef.afterClosed().subscribe(result => {
   console.log(`Dialog result: ${result}`);
});
dialogRef.close('value');
از آن می‌توان برای بستن dialog و یا دریافت پیامی پس از بسته شدن دیالوگ، استفاده کرد.
در این مثال اگر dialogRef را با متد close و پارامتر value فراخوانی کنیم، سبب بسته شدن این دیالوگ خواهیم شد. این پارامتر در قسمت Dialog result پیام دریافتی پس از بسته شدن دیالوگ نیز قابل دسترسی است.
کامپوننت‌هایی که توسط سرویس MatDialog نمایش داده می‌شوند، می‌توانند توسط سرویس جنریک MatDialogRef، صفحه‌ی دیالوگ باز شده را ببندند:
@Component({/* ... */})
export class YourDialog {

   constructor(public dialogRef: MatDialogRef<YourDialog>) { }

   closeDialog() {
      this.dialogRef.close('Value….!’);
   }
}
نکته‌ی مهم: چون سرویس MatDialog کار وهله سازی و نمایش کامپوننت‌ها را به صورت پویا انجام می‌دهد، محل تعریف این نوع کامپوننت‌های ویژه در قسمت entryComponents ماژول مرتبط است. به این ترتیب به A head of time compiler اعلام می‌کنیم که component factory این کامپوننت پویا است و باید به نحو ویژه‌ای مدیریت شود.

نحوه‌ی طراحی یک دیالوگ نیز به کمک تعدادی کامپوننت و دایرکتیو میسر است:
<h2 mat-dialog-title>Delete all</h2>
<mat-dialog-content>Are you sure?</mat-dialog-content>
<mat-dialog-actions>
    <button mat-button mat-dialog-close>No</button>
    <!-- Can optionally provide a result for the closing dialog. -->
    <button mat-button [mat-dialog-close]="true">Yes</button>
</mat-dialog-actions>
دایرکتیو mat-dialog-title سبب نمایش عنوان دیالوگ می‌شود. mat-dialog-content محتوای قابل اسکرول این دیالوگ را در بر می‌گیرد. mat-dialog-actions محلی است برای قرارگیری action buttons در پایین صفحه‌ی دیالوگ. در اینجا اگر ویژگی mat-dialog-close به true تنظیم شود، آن دکمه قابلیت بستن دیالوگ را پیدا می‌کند.


ایجاد دکمه‌ی نمایش دیالوگ افزودن تماس‌ها و کاربران جدید

قبل از هر کاری نیاز است دکمه‌ی افزودن یک کاربر جدید را به صفحه اضافه کنیم. برای اینکار یک منوی ویژه را در سمت راست، بالای صفحه ایجاد می‌کنیم. بنابراین ابتدا به مستندات toolbar و menu مراجعه می‌کنیم تا با نحوه‌ی تعریف دکمه‌ها و منوها به toolbar آشنا شویم. سپس فایل قالب toolbar\toolbar.component.html را به صورت زیر تکمیل می‌کنیم:
<mat-toolbar color="primary">
  <button mat-button fxHide fxHide.xs="false" (click)="toggleSidenav.emit()">
    <mat-icon>menu</mat-icon>
  </button>

  <span>Contact Manager</span>

  <span fxFlex="1 1 auto"></span>
  <button mat-button [matMenuTriggerFor]="menu">
    <mat-icon>more_vert</mat-icon>
  </button>
  <mat-menu #menu="matMenu">
    <button mat-menu-item>New Contact</button>
  </mat-menu>
</mat-toolbar>
توسط یک span و سپس fxFlex تعریف شده‌ی آن سبب خواهیم شد تا mat-button بعدی و محتوای پس از آن به گوشه‌ی سمت راست toolbar هدایت شوند؛ در غیراینصورت دقیقا در کنار عبارت Contact manager ظاهر خواهند شد.
سپس ابتدا یک mat-button را با آیکن more_vert (آیکن علامت بیشتر عمودی) تعریف کرده‌ایم:


این دکمه توسط ویژگی matMenuTriggerFor به template reference variable ایی به نام menu متصل شده‌است تا با کلیک بر روی آن، این mat-menu را نمایش دهد:



ایجاد دیالوگ افزودن تماس‌ها و کاربران جدید

پس از تعریف دکمه و منویی که سبب نمایش عبارت افزودن یک تماس جدید می‌شوند، به رخ‌داد کلیک آن متدی را جهت نمایش صفحه‌ی دیالوگ جدید اضافه می‌کنیم:
 <button mat-menu-item (click)="openAddContactDialog()">New Contact</button>
سپس در کدهای کامپوننت toolbar، کار مدیریت آن‌را انجام خواهیم داد. اما پیش از آن بهتر است کامپوننت جدیدی را که قرار است نمایش دهد به برنامه اضافه کنیم:
 ng g c contact-manager/components/new-contact-dialog --no-spec
این دستور علاوه بر تولید کامپوننت جدید new-contact-dialog در پوشه‌ی components، کار تعریف مدخل آن‌را در ماژول این قسمت نیز انجام می‌دهد. اما همانطور که پیشتر نیز عنوان شد، باید آن‌را به لیست entryComponents اضافه کنیم تا بتوان آن‌را به صورت پویا بارگذاری کرد (در غیر اینصورت در زمان نمایش پویای آن، خطای no component factory for را دریافت می‌کنیم). بنابراین فایل contact-manager.module.ts را گشوده و به صورت زیر تکمیل می‌کنیم:
import { NewContactDialogComponent } from "./components/new-contact-dialog/new-contact-dialog.component";

@NgModule({
  declarations: [
    NewContactDialogComponent],
  entryComponents: [
    NewContactDialogComponent
  ]
})
export class ContactManagerModule { }
اکنون می‌توانیم سرویس MatDialog را به سازنده‌ی کامپوننت toolbar تزریق کرده و از آن برای نمایش این کامپوننت جدید استفاده کنیم:
import { Component, EventEmitter, OnInit, Output } from "@angular/core";
import { MatDialog } from "@angular/material";

import { NewContactDialogComponent } from "../new-contact-dialog/new-contact-dialog.component";

@Component({
  selector: "app-toolbar",
  templateUrl: "./toolbar.component.html",
  styleUrls: ["./toolbar.component.css"]
})
export class ToolbarComponent implements OnInit {

  @Output() toggleSidenav = new EventEmitter<void>();

  constructor(private dialog: MatDialog) { }

  ngOnInit() { }

  openAddContactDialog(): void {
    const dialogRef = this.dialog.open(NewContactDialogComponent, { width: "450px" });
    dialogRef.afterClosed().subscribe(result => {
      console.log("The dialog was closed", result);
    });
  }
}
در اینجا سرویس MatDialog به سازنده‌ی کامپوننت تزریق شده و سپس از آن در متد openAddContactDialog که متصل به منوی نمایش «new contact» است، جهت نمایش پویای کامپوننت NewContactDialogComponent، استفاده می‌شود. اکنون اگر برنامه را اجرا کنیم و گزینه‌ی «new contact» را از منو انتخاب کنیم، چنین تصویری حاصل خواهد شد:



تکمیل قالب کامپوننت تماس جدید

در ادامه می‌خواهیم فرم افزودن یک تماس جدید را به همراه فیلدهای ورودی آن، به قالب new-contact-dialog.component.html اضافه کنیم:
<h2 mat-dialog-title>Add new contact</h2>
<mat-dialog-content>
  <div fxLayout="column">

  </div>
</mat-dialog-content>
<mat-dialog-actions>
  <button mat-button color="primary" (click)="save()">
    <mat-icon>save</mat-icon> Save
  </button>
  <button mat-button color="primary" (click)="dismiss()">
    <mat-icon>cancel</mat-icon> Cancel
  </button>
</mat-dialog-actions>
تا اینجا ساختار مقدماتی صفحه دیالوگ را مطابق توضیحاتی که در ابتدای بحث عنوان شد، تکمیل کردیم. یک عنوان توسط mat-dialog-title به آن اضافه شده‌است. سپس mat-dialog-content اضافه شده که در ادامه آن‌را تکمیل می‌کنیم. در آخر هم دکمه‌های action به این دیالوگ استاندارد اضافه شده‌اند. برای تکمیل متدهای save و dismiss این دکمه‌ها، کدهای ذیل را به کامپوننت new-contact-dialog.component.ts اضافه می‌کنیم:
import { Component, OnInit } from "@angular/core";
import { MatDialogRef } from "@angular/material";

@Component()
export class NewContactDialogComponent implements OnInit {

  constructor(
    private dialogRef: MatDialogRef<NewContactDialogComponent>
  ) { }

  ngOnInit() {
  }

  save() {
  }

  dismiss() {
    this.dialogRef.close(null);
  }
}
در اینجا توسط سرویس MatDialogRef از نوع NewContactDialogComponent، می‌توانیم به ارجاعی از سرویس MatDialog باز کننده‌ی آن دسترسی پیدا کنیم و برای نمونه در متد dismiss سبب بسته شدن این دیالوگ شویم.
تا اینجا اگر برنامه را اجرا کنیم، به چنین شکلی خواهیم رسید:



تکمیل فیلدهای ورود اطلاعات فرم ثبت یک تماس جدید

تا اینجا ساختار فرم دیالوگ ثبت اطلاعات جدید را تکمیل کردیم. این فرم، به شیء user متصل خواهد شد. همچنین لیستی از avatars را هم جهت انتخاب، نمایش می‌دهد. به همین جهت این دو خاصیت عمومی را به کدهای کامپوننت آن اضافه می‌کنیم:
import { User } from "../../models/user";

@Component()
export class NewContactDialogComponent implements OnInit {

  avatars = ["user1", "user2", "user3", "user4", "user5", "user6", "user7", "user8"];
  user: User = { id: 0, birthDate: new Date(), name: "", avatar: "", bio: "", userNotes: null };
در ادامه فیلدهای آن‌را به صورت زیر در قسمت mat-dialog-content اضافه خواهیم کرد:


الف) فیلد نمایش و انتخاب avatar کاربر
    <mat-form-field>
      <mat-select placeholder="Avatar" [(ngModel)]="user.avatar">
        <mat-select-trigger>
          <mat-icon svgIcon="{{user.avatar}}"></mat-icon> {{ user.avatar }}
        </mat-select-trigger>
        <mat-option *ngFor="let avatar of avatars" [value]="avatar">
          <mat-icon svgIcon="{{avatar}}"></mat-icon> {{ avatar }}
        </mat-option>
      </mat-select>
    </mat-form-field>


در اینجا از کامپوننت mat-select برای انتخاب avatar کاربر استفاده شده‌است که نتیجه‌ی نهایی انتخاب آن به خاصیت user.avatar متصل شده‌است.
گزینه‌های این لیست (mat-option) بر اساس آرایه‌ی avatars که در کامپوننت تعریف کردیم، تامین می‌شوند که در اینجا از mat-icon برای نمایش آیکن مرتبط نیز استفاده شده‌است. در این مورد در قسمت قبل چهارم، بخش «بارگذاری و معرفی فایل svg نمایش avatars کاربران به Angular Material» بیشتر توضیح داده شده‌است.
کار mat-select-trigger، سفارشی سازی برچسب نمایشی این کنترل است.

ب) فیلد دریافت نام کاربر به همراه اعتبارسنجی آن
    <mat-form-field>
      <input matInput placeholder="Name" #name="ngModel" [(ngModel)]="user.name" required>
      <mat-error *ngIf="name.invalid && name.touched">You must enter a name</mat-error>
    </mat-form-field>


در اینجا فیلد نام کاربر، به user.name متصل و همچنین توسط ویژگی required، پر کردن آن الزامی اعلام شده‌است. به همین جهت در تصویر فوق یک ستاره را نیز کنار آن مشاهده می‌کند که به صورت خودکار توسط Angular Material نمایش داده شده‌است.
از کامپوننت mat-error برای نمایش خطاهای اعتبارسنجی یک فیلد استفاده می‌شود که نمونه‌ای از آن‌را در اینجا با بررسی خواص invalid  و touched فیلد نام که بر اساس ویژگی required فعال می‌شوند، مشاهده می‌کنید. بدیهی است در اینجا به هر تعدادی که نیاز است می‌توان mat-error را قرار داد.

ج) فیلد دریافت تاریخ تولد کاربر توسط یک date picker
    <mat-form-field>
      <input matInput [matDatepicker]="picker" placeholder="Born" [(ngModel)]="user.birthDate">
      <mat-datepicker-toggle matSuffix [for]="picker"></mat-datepicker-toggle>
      <mat-datepicker #picker></mat-datepicker>
    </mat-form-field>


در اینجا از کامپوننت mat-datepicker برای انتخاب تاریخ تولید یک شخص استفاده شده‌است و نتیجه‌ی آن به خاصیت user.birthDate متصل خواهد شد.
برای افزودن آن ابتدا یک mat-datepicker را به mat-form-field اضافه می‌کنیم. سپس یک template reference variable را به آن نسبت خواهیم داد. از آن هم در فیلد ورودی با انتساب آن به ویژگی matDatepicker و هم در کامپوننت mat-datepicker-toggle که سبب نمایش آیکن انتخاب تقویم می‌شود، در ویژگی for آن استفاده خواهیم کرد.

د) فیلد چند سطری دریافت توضیحات و شرح‌حال کاربر
    <mat-form-field>
      <textarea matInput placeholder="Bio" [(ngModel)]="user.bio"></textarea>
    </mat-form-field>


در اینجا برای دریافت توضیحات چندسطری، از یک text area استفاده شده‌است که به خاصیت user.bio متصل است.

بنابراین همانطور که ملاحظه می‌کنید، روش طراحی فرم‌های Angular Material ویژگی‌های خاص خودش را دارد:
- دایرکتیو matInput را می‌توان به المان‌های استاندارد input و textarea اضافه کرد تا داخل mat-form-field نمایش داده شوند. این mat-form-field است که کار اعمال CSS ویژه‌ی طراحی متریال را انجام می‌دهد و امکان نمایش پیام‌های خطای اعتبارسنجی و پویانمایی ورود اطلاعات را سبب می‌شود.
- قسمت mat-dialog-content را توسط fxLayout به حالت ستونی تنظیم کردیم:
<mat-dialog-content>
  <div fxLayout="column">

  </div>
</mat-dialog-content>
این مورد است که سبب خواهد شد المان‌های فرم از بالا به پایین نمایش داده شوند. در غیراینصورت mat-form-fieldها دقیقا در کنار هم قرار می‌گیرند.
برای مثال اگر خواستید المان‌های فرم با فاصله‌ی بیشتری از هم قرار بگیرند، می‌توان از fxLayoutGap استفاده کرد که در مورد آن در قسمت دوم «معرفی Angular Flex layout» بیشتر توضیح داده شد.


تکمیل سرویس کاربران جهت ذخیره‌ی اطلاعات تماس کاربر جدید

در ادامه می‌خواهیم با کلیک کاربر بر روی دکمه‌ی Save، ابتدا این اطلاعات به سمت سرور ارسال و سپس در سمت سرور ذخیره شوند. پس از آن، Id این کاربر جدید به سمت کلاینت بازگشت داده شود، دیالوگ جاری بسته و در آخر این شیء جدید به لیست تماس‌های نمایش داده‌ی شده‌ی در sidenav اضافه گردد.

الف) تکمیل سرویس Web API سمت سرور
در ابتدا متد Post را به Web API برنامه جهت ذخیره سازی اطلاعات User ارسالی از سمت کلاینت اضافه می‌کنیم. کدهای کامل آن‌را از فایل پیوستی انتهای بحث می‌توانید دریافت کنید:
namespace MaterialAspNetCoreBackend.WebApp.Controllers
{
    [Route("api/[controller]")]
    public class UsersController : Controller
    {
        private readonly IUsersService _usersService;

        public UsersController(IUsersService usersService)
        {
            _usersService = usersService ?? throw new ArgumentNullException(nameof(usersService));
        }

        [HttpPost]
        public async Task<IActionResult> Post([FromBody] User user)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }
            await _usersService.AddUserAsync(user);
            return Created("", user);
        }
    }
}

ب) تکمیل سرویس کاربران سمت کلاینت
سپس به فایل user.service.ts مراجعه کرده و دو تغییر زیر را به آن اضافه می‌کنیم:
@Injectable({
  providedIn: "root"
})
export class UserService {

  private usersSource = new BehaviorSubject<User>(null);
  usersSourceChanges$ = this.usersSource.asObservable();

  constructor(private http: HttpClient) { }

  addUser(user: User): Observable<User> {
    const headers = new HttpHeaders({ "Content-Type": "application/json" });
    return this.http
      .post<User>("/api/users", user, { headers: headers }).pipe(
        map(response => {
          const addedUser = response || {} as User;
          this.notifyUsersSourceHasChanged(addedUser);
          return addedUser;
        }),
        catchError((error: HttpErrorResponse) => throwError(error))
      );
  }

  notifyUsersSourceHasChanged(user: User) {
    this.usersSource.next(user);
  }
}
کار متد addUser، ارسال اطلاعات فرم ثبت یک تماس جدید به سمت سرور و Web API برنامه است. پس از ثبت موفقیت آمیز کاربر در سمت سرور، متد return Created آن:
 return Created("", user);
سبب خواهد شد تا بتوانیم در سمت کلاینت، به Id اطلاعات رکورد جدید دسترسی داشته باشیم. مزیت آن امکان افزودن این رکورد به لیست کاربران sidenav و همچنین فعالسازی مسیریابی آن است که بر اساس این Id واقعی کار می‌کند.
بنابراین نیاز است از طریق این سرویس به کامپوننت sidenav، در مورد تغییرات لیست کاربران اطلاعات رسانی کنیم که روش کار آن‌را پیشتر در مطلب «صدور رخدادها از سرویس‌ها به کامپوننت‌ها در برنامه‌های Angular» نیز مرور کرده‌ایم. برای این منظور یک BehaviorSubject از نوع User را تعریف کرده‌ایم که اشتراک به آن از طریق خاصیت عمومی usersSourceChanges میسر است. هر زمانیکه متد next آن فراخوانی شود، تمام مشترکین به آن، از افزوده شدن کاربر جدید، به همراه اطلاعات کامل آن مطلع خواهند شد.

ج) تکمیل متد save کامپوننت new-contact-dialog
پس از تکمیل سرویس کاربران جهت افزودن متد addUser به آن، اکنون می‌توانیم از آن در کامپوننت دیالوگ افزودن اطلاعات تماس جدید استفاده کنیم:
import { UserService } from "../../services/user.service";

@Component()
export class NewContactDialogComponent {

  user: User = { id: 0, birthDate: new Date(), name: "", avatar: "", bio: "", userNotes: null };

  constructor(
    private dialogRef: MatDialogRef<NewContactDialogComponent>,
    private userService: UserService
  ) { }

  save() {
    this.userService.addUser(this.user).subscribe(data => {
      console.log("Saved user", data);
      this.dialogRef.close(data);
    });
  }
}
در اینجا در متد save، ابتدا متد addUser سرویس افزودن اطلاعات جدید فراخوانی می‌شود. سپس در صورت موفقیت آمیز بودن عملیات، توسط سرویس dialogRef، این صفحه‌ی دیالوگ نیز به صورت خودکار بسته خواهد شد. همچنین به متد close آن data دریافتی از سرور ارسال شده‌است. این data در toolbar.component در قسمت dialogRef.afterClosed قابل دسترسی خواهد بود.

د) تکمیل کامپوننت sidenav جهت واکنش نشان دادن به افزوده شدن اطلاعات تماس جدید
اکنون که سرویس کاربران به صفحه دیالوگ افزودن اطلاعات یک تماس جدید متصل شده‌است، نیاز است بتوانیم اطلاعات کاربر جدید را به لیست تماس‌های sidenav اضافه کنیم. به همین جهت به sidenav.component مراجعه کرده و مشترک usersSourceChanges سرویس کاربران خواهیم شد:
import { UserService } from "../../services/user.service";

@Component()
export class SidenavComponent implements OnInit, OnDestroy {

  users: User[] = [];
  subscription: Subscription | null = null;

  constructor(
    private userService: UserService) { }

  ngOnInit() {
    this.subscription = this.userService.usersSourceChanges$.subscribe(user => {
      if (user) {
        this.users.push(user);
      }
    });
  }

  ngOnDestroy() {
    if (this.subscription) {
      this.subscription.unsubscribe();
    }
  }
}
ابتدا در ngOnInit توسط سرویس کاربران، مشترک تغییرات usersSourceChanges خواهیم شد. در اینجا اگر کاربر جدیدی به لیست اضافه شده باشد، آن‌را توسط متد push به لیست کاربران جاری sidenav اضافه می‌کنیم تا بلافاصله در لیست نمایش داده شود.


استفاده از کامپوننت Snackbar جهت نمایش موفقیت آمیز بودن ثبت اطلاعات

متد save کامپوننت دیالوگ یک تماس جدید را به صورت زیر تکمیل کردیم:
  save() {
    this.userService.addUser(this.user).subscribe(data => {
      console.log("Saved user", data);
      this.dialogRef.close(data);
    });
در اینجا data ارسال شده‌ی به متد close در کامپوننت toolbar در قسمت dialogRef.afterClosed قابل دسترسی خواهد بود:
  openAddContactDialog(): void {
    const dialogRef = this.dialog.open(NewContactDialogComponent, { width: "450px" });
    dialogRef.afterClosed().subscribe(result => {
      console.log("The dialog was closed", result);
    });
  }
بنابراین در ادامه قصد داریم از آن جهت نمایش یک snackbar به همراه ارائه لینک هدایت به صفحه‌ی جزئیات تماس جدید، استفاده کنیم:


کدهای کامل این تغییرات را در ذیل مشاهده می‌کنید:
@Component()
export class ToolbarComponent {

  @Output() toggleSidenav = new EventEmitter<void>();

  constructor(private dialog: MatDialog, private snackBar: MatSnackBar,  private router: Router) { }

  openAddContactDialog(): void {
    const dialogRef = this.dialog.open(NewContactDialogComponent, { width: "450px" });
    dialogRef.afterClosed().subscribe((result: User) => {
      console.log("The dialog was closed", result);
      if (result) {
        this.openSnackBar(`${result.name} contact has been added.`, "Navigate").onAction().subscribe(() => {
          this.router.navigate(["/contactmanager", result.id]);
        });
      }
    });
  }

  openSnackBar(message: string, action: string): MatSnackBarRef<SimpleSnackBar> {
    return this.snackBar.open(message, action, {
      duration: 5000,
    });
  }
}
توضیحات:
برای گشودن snackbar که نمونه‌ای از آن‌را در تصویر فوق ملاحظه می‌کنید، ابتدا نیاز است سرویس MatSnackBar را به سازنده‌ی کلاس تزریق کرد. سپس توسط آن می‌توان یک کامپوننت مستقل را همانند دیالوگ‌ها نمایش داد و یا می‌توان یک متن را به همراه یک Action منتسب به آن، به کاربر نمایش داد؛ مانند متد openSnackBar که در کامپوننت فوق از آن استفاده می‌شود. این متد در رخ‌داد پس از بسته شدن dialog، نمایش داده شده‌است.
پارامتر اول آن پیامی است که توسط snackbar نمایش داده می‌شود و پارامتر دوم آن، برچسب دکمه مانندی است کنار این پیام، که سبب انجام عملی خواهد شد و در اینجا به آن Action گفته می‌شود. برای مدیریت آن باید متد onAction را فراخوانی کرد و مشترک آن شد. در این حالت اگر کاربر بر روی این دکمه‌ی action کلیک کند، سبب هدایت خودکار او به صفحه‌ی نمایش جزئیات اطلاعات تماس کاربر خواهیم شد. به همین جهت سرویس Router نیز به سازنده‌ی کلاس تزریق شده‌است تا بتوان از متد navigate آن استفاده کرد.



کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: MaterialAngularClient-05.zip
برای اجرای آن:
الف) ابتدا به پوشه‌ی src\MaterialAngularClient وارد شده و فایل‌های restore.bat و ng-build-dev.bat را اجرا کنید.
ب) سپس به پوشه‌ی src\MaterialAspNetCoreBackend\MaterialAspNetCoreBackend.WebApp وارد شده و فایل‌های restore.bat و dotnet_run.bat را اجرا کنید.
اکنون برنامه در آدرس https://localhost:5001 قابل دسترسی است.
مطالب
آشنایی بیشتر با AngularJS Directive

در مطلب آشنایی با Directive‌ها در AngularJS با نحوه‌ی ایجاد Directive آشنا شدیم. هدف از این مطلب، آشنایی بیشتر با Directive در AngularJS است؛ یکی از بهترین فریم ورک‌های جاوااسکریپتی، با قابلیت ایجاد کتابخانه‌هایی از کامپوننت‌ها که می‌توانند به HTML اضافه شوند .

کتابخانه‌های جاوااسکریپتی زیادی وجود دارند. به عنوان مثال Bootstrap یکی از محبوب‌ترین "front-end framework" ها است که امکان تغییر در ظاهر المنت‌ها را فراهم می‌کند و شامل تعدادی کامپوننت جاوااسکریپتی نیز می‌باشد. مشکل کار، در هنگام استفاده از کامپوننت ها است. شخصی که در حال توسعه‌ی HTML است باید در کد جاوااسکریپتی خود از  jQuery استفاده کند و بعنوان مثال یک Popover  را فعال یا غیر فعال کند و این، یک فرآیند خسته کننده و مستعد خطا است. 


یک مثال ساده از Directives AngularJS و بررسی آن

var m = angular.module("myApp");
 
myApp.directive("myDir", function() {
  return {
  restrict: "E",   
  scope: {     
   name: "@",   
   amount: "=",  
   save: "&"    
  },
  template:    
   "<div>" +
   "  {{name}}: <input ng-model='amount' />" +
   "  <button ng-click='save()'>Save</button>" +
   "</div>",
  replace: true,   
  transclude: false, 
  controller: [ "$scope", function ($scope) { …  }],
  link: function (scope, element, attrs, controller) {…}
  }
});

به الگوی نامگذاری directive دقت کنید. پیشوند my شبیه به یک namespace است. بنابراین اگر یک Application از دایرکتیوهای قرار گرفته در Module ‌های متفاوت استفاده کند، به راحتی می‌توان محل تعریف یک directive را تشخیص داد. این نام می‌تواند نشان دهنده‌ی این باشد که این directive را خودتان توسعه داده‌اید یا از یک directive توسعه داده شده توسط شخص دیگری در حال استفاده هستید. به هر حال این نحوه‌ی نام گذاری یک اجبار نیست و به عنوان یک پیشنهاد است.

سازنده directive یک شیء را با تعدادی خاصیت باز می‌گرداند که تمامی آنها در سایت AngularJS توضیح داده شده‌اند. در اینجا قصد داریم تا توضیحی مختصر در مورد کاری که این خصوصیات انجام می‌دهند داشته باشیم.

· restrict : تشخیص می‌دهد که آیا directive در HTML استفاده خواهد شد. گزینه‌های قابل استفاده ‘A’ ،  ‘E’ ، ‘C’ برای attribute ، element ، class و یا comment است . پیش فرض ‘A’ برای attribute است. اما ما بیشتر علاقه به استفاده از ویژگی element برای ایجاد المنت‌های UI داریم.

· scope : ایجاد یک scope ایزوله که متعلق به directive است و موجب ایزوله شدن آن از scope صدا زننده directive می‌شود. متغیرهای scope پدر از طریق خصوصیات تگ directive ارسال می‌شوند. این ایزوله کردن زمانی کاربردی است که در حال ایجاد کامپوننت هایی با قابلیت استفاده مجدد هستیم، که نباید متکی به scope پدر باشند. شیء scope در directive نام و نوع متغیرهای scope را تعیین می‌کنند. در مثال بالا سه متغیر برای scope تعریف شده است:

-   name: "@" (by value, one-way) : علامت @ مشخص می‌کند که مقدار متغیر ارسال می‌شود. Directive یک رشته را دریافت می‌کند که شامل مقدار ارسال شده از scope پدر می‌باشد. Directive می‌تواند از آن استفاده کند، اما نمی‌تواند مقدار آن را در scope پدر تغییر دهد.

-   amount: "=" (by reference, two-way) : علامت = مشخص می‌کند این متغیر با ارجاع ارسال می‌شود. Directive یک ارجاع به مقدار متغیر در scope اصلی دریافت می‌کند. مقدار می‌تواند هر نوع داده ای، شامل یک شیء complex یا یک آرایه باشد. Directive می‌تواند مقدار را در scope پدر تغییر دهد. این نوع متغیر، زمانیکه نیاز باشد directive مقدار را در scope پدر تغییر دهد، استفاده می‌شود.

-   save: "&" (expression) : علامت & مشخص می‌کند این متغیر یک expression را که در scope پدر اجرا می‌شود، نگهداری می‌کند. اکنون directive قابلیت انجام کارهایی فراتر از تغییر یک مقدار را دارد. به عنوان مثال می‌توان یک تابع را از scope پدر فراخوانی و نتیجه‌ی اجرا را دریافت کرد.

· template :  الگوی رشته ای که جایگزین المنت تعریف شده می‌شود. فرآیند جایگزینی تمامی خصوصیات را از المنت قدیمی به المنت جدید انتقال می‌دهد. به نحوه استفاده از متغیر‌های تعریف شده در scope ایزوله دقت کنید. این مورد به شما امکان تعریف directive های macro-style را می‌دهد که نیاز به کد اضافه‌ای، ندارند. اگرچه در بیشتر موارد الگو  یک تگ ساده <div> است که از کد‌های link که در زیر توضیح داده شده است استفاده می‌کند.

· replace : تعیین می‌کند که آیا الگوی directive باید جایگزین المنت شود. مقدار پیش فرض false است.

· transclude : تعیین کننده این است که محتوای directive باید در المنت کپی شود یا خیر. در مثال زیر المنت tab شامل المنت‌های HTML دیگر است پس transclude برابر true است.  

<body ng-app="components">
  <h3>BootStrap Tab Component</h3>
    <tabs>
       <pane title="First Tab">
          <div>This is the content of the first tab.</div>
       </pane>
       <pane title="Second Tab">
          <div>This is the content of the second tab.</div>
       </pane>
    </tabs>
</body>
 

· link : این تابع بیشتر منطق directive را شامل می‌شود. Link وظیفه دستکاری DOM ، ایجاد event listener ‌ها و... را دارد. تابع Link پارامترهای زیر را دریافت می‌کند:

-   scope : ارجاع به scope ایزوله شده directive دارد.

-   element : ارجاع به المنت‌های DOM که directive را تعریف کرده اند. تابع link معمولا برای دستکاری المنت از jQuery استفاده می‌کند. (یا از Angular's jqLite در صورتی که jQuery بارگذاری نشده باشد)

-   controller : در مواقعی که از دایرکتیو‌های تو در تو استفاده می‌شود کاربرد دارد. این پارامتر یک directive فرزند با ارجاعی به پدر را فراهم می‌کند، بنابراین موجب ارتباط  directive ‌ها می‌شود.

به عنوان مثال،  این directive  که پیاده سازی bootstrap tab را انجام داده است، می‌توانید مشاهده نمایید.

موفق باشید

مطالب
گذری بر مفاهیم relationship
متاسفانه کاربران زیادی وجود دارند که هنوز درک صحیحی از جامعیت داده‌های ارجاعی (referential Integrity) ندارند. نمی‌دانند که relationship چیزی جز قید کلید خارجی (foreign key) نیست. در ادامه مفاهیم زیر را در حد آشنایی توضیح خواهم داد:
  • کلید خارجی ترکیبی (composite foreign key)
  • خود ارجاعی (self referencing)
  • اعمال تغییرات به صورت آبشاری (cascade)
  • چندین مسیر برای اعمال (multiple cascading path)
  • جدول اتصال (junction table)- ارتباط یک به یک

توسط دستور create table به دو شکل می‌توانیم بر روی ستون‌ها قید (کلید اولیه، check، کلید خارجی، کلید یونیک...) تعریف نمود:
  1. قید ستونی
  2. قید جدولی

syntax مربوط به قید کلید خارجی در مدل ستونی به صورت زیر است:

 <column_constraint> ::= 
[ CONSTRAINT constraint_name ] 
{    ...

  | [ FOREIGN KEY ] 
        REFERENCES [ schema_name . ] referenced_table_name [ ( ref_column ) ] 
        [ ON DELETE { NO ACTION | CASCADE | SET NULL | SET DEFAULT } ] 
        [ ON UPDATE { NO ACTION | CASCADE | SET NULL | SET DEFAULT } ] 
        [ NOT FOR REPLICATION ] 

  ... 
}
نکته: بطور پیش فرض برای کلید خارجی اعمال update و delete روی وضعیت no action تنظیم شده است. به این معنا که اگر سعی کنیم کلید اولیه جدول مرجع را بروز رسانی یا حذف کنیم ممانعت به عمل خواهد آمد. برای رفع این مشکل هم میتوانید از طریق design اقدام کنید و هم در هنگام ساخت جدول توسط DDL (همانطور که در دستورات فوق مشاهده میشود).

کلید خارجی ترکیبی
زمانی که در جدول والد (parent) کلید اولیه ترکیبی باشد، هر جدولی که بخواهد به کلید جدول والد ارجاعی داشته باشد باید از ترکیب دو ستون برای ساخت کلید خارجی استفاده کند.

فرض کنید جدول parent به این صورت است (ترکیب دو ستون col1 و col2 کلید اولیه است)
create table parent
(
col1 int not null,
col2 int not null,
col3 char(1) null,
-- Composite Primary Key
primary key(col1, col2)
);
در اینجا چون ترکیب دو ستون کلید اولیه هست باید توسط "قید جدولی" اقدام به تعریف کلید کرد

و جدول child که دارای قید کلید خارجی ترکیبی به نام fk_comp است و به جدول parent ارجاع داده است:

create table child
(
col0 int primary key,
col1 int null,
col2 int null,
-- Composite Foreing Key Constraint
constraint fk_comp
foreign key (col1, col2)
references parent(col1, col2)
);

در این DDL هم از قید جدولی برای تعریف کلید خارجی ترکیبی استفاده شده است.

نمودار این دو جدول:

پس به عنوان نتیجه گیری، هرگاه جدول اصلی دارای کلید ترکیبی بود در جداول child نیز باید از کلید خارجی ترکیبی برای ایجاد relationship استفاده نمود.

اما این دو جدول را به یک شیوه دیگر نیز می‌توان طراحی نمود. در جدول parent ترکیب دو ستون col1 و col2 را منحصربفرد (unique) گرفته و ستونی دیگر (مثلا از نوع identity) را به عنوان کلید اولیه در نظر گرفت (یا یک ستون از نوع محاسباتی تعریف کرده و آن را کلید قرار داد)

create table parent
(
col0 int not null primary key identity,
col1 int not null,
col2 int not null,
col3 char(1) null,
-- Composite Unique Key
unique(col1, col2)
);

create table child
(
col0 int primary key,
col1 int null references parent
);
خود ارجاعی و multiple cascading path
فرض کنید بخش‌های مختلف یک سازمان که بصورت چارت است را توسط جدول پیاده سازی کردیم. ستون‌های جدول به این شرح هستند:
  1. کد بخش
  2. نام بخش
  3. کد بخش بالایی
ستون "کد بخش بالایی" نیز خود یک بخش است. برای پیاده سازی این چنین ساختارهایی از جدول زیر کمک گرفته می‌شود:
create table chart
(
chart_nbr int not null primary key,
parent_nbr int null references chart,
chart_name varchar(5) null
);
تصویر نمودار جدول chart


حالا فرض کنید میخواهیم اطلاعات نامه هایی که بین بخش‌ها رد و بدل میشود را در یک جدول ذخیره کنیم. جدول دارای ستون‌های زیر خواهد بود:
  1. شماره نامه 
  2. کد بخش فرستنده
  3. کد بخش گیرنده
ستون شماره نامه کلید اولیه و دو ستون دیگه کلید‌های خارجی هستند که به جدول chart مراجعه می‌کنند:
create table letters
(
letter_nbr int primary key,
sec_sender int not null references chart,
sec_reciver int not null references chart
);

نمودار جدول نامه‌ها و چارت:

نکته ای که در اینجا وجود دارد این است که اگر کلید جدول chart بروز شود آنگاه SQL Server از دو راه می‌تواند جدول letters را بروز رسانی کند، به این علت پیغام خطایی با عنوان multiple cascading paths صادر می‌شود. برای رفع این مشکل باید از trigger کمک گرفت.



جدول اتصال (junction table)
برای پیاده سازی رابطه N-N از جدول واسط کمک گرفته می‌شود. برای این منظور رابطه N-N را باید به دو رابطه 1-N تجزیه کرد.
فرض کنید یک جدول مربوط به خلبانان و جدول دیگر مربوط به مسیرهای پروازی (مثل مسیر ایران-ترکیه، ایران-عربستان...) است. یک خلبان ممکن است در چند مسیر پروازی هواپیما را هدایت کرده باشد و یا بالعکس یک مسیر پروازی ممکن است توسط N خلبان طی شده باشد.
برای پیاده سازی اینگونه سیستم هایی باید یک جدول ایجاد نمود که دارای دو کلید خارجی باشد یکی آنها به جدول خلبانان و دیگری به مسیرهای پروازی مرتبط است.

می‌توان ترکیب دو کلید خارجی جدول واسط را کلید اولیه در نظر گرفت.
پس خواهیم داشت:
create table pilot
(
pilot_code int primary key,
pilot_name varchar(20) 
);

create table paths
(
path_code int primary key,
path_name varchar(20)
);

create table junction
(
pilot_code int references pilot,
path_code int references paths,
primary key (pilot_code, path_code)
);

و نمودار آن:

رابطه یک به یک
زمانی که نمونه‌های محدودی از یک موجودیت دارای مقدار برای یکسری خصیصه هستند بهتر است جدول به دو جدول تجزیه شود تا فضای اضافی صرف جدول نشود. مثلا در مدرسه تنها 10 درصد دانش آموزان جزء تیم فوتبال هستند حال اگر بخواهیم اطلاعات مربوط به تیم فوتبال مثل تعداد گل زده، تعداد بازی ... در جدول اصلی ذخیره کنیم برای 90 درصد دانش آموزان مقداری نخواهیم داشت. برای حل این مساله ارتباط یک به یک پیشنهاد می‌شود.
create table student
(
std_code int primary key,
std_name varchar(25) not null
);

create table football
(
std_code int primary key 
  constraint one_to_one_fk
  references student,
std_cnt_goal int not null 
  default (0)
);

توجه داشته باشید که ستون std_code هم کلید اولیه هست و هم کلید خارجی که به جدول student ارجاع داده شده است.



نتیجه گیری

یک ستون همزمان می‌تواند کلید اولیه باشد و هم کلید خارجی (مثلا در ارتباط یک به یک)
همانطور که کلید اولیه ترکیبی داریم به همان شکل هم کلید خارجی ترکیبی داریم.
یک جدول می‌تواند به خودش ارجاع دهد که به آن اصطلاحا self-referencing می‌گویند
relationship چیزی جز کلید خارجی نیست و کلید خارجی نیز چیزی جز یک قید برای جامعیت داده‌ها نیست
جامعیت داده ارجاعی را می‌توان توسط trigger پیاده سازی کرد
اگر SQL Server بیش از یک مسیر برای تغییر جدول child داشته باشد با مشکل مواجه خواهید شد

مطالب
شروع کار با Apache Cordova در ویژوال استودیو #5

همانطور که در قسمت قبل گفته شد، در این قسمت با روش کار jQuery Mobile و plugin‌های مربوط به Cordova آشنا خواهیم شد.


تگ متای زیر برای تنظیمات مربوط به viewport است و برای jQuery Mobile توصیه می‌شود.
<!DOCTYPE html> 
<html> 
<head> 
<meta charset="utf-8"> 
<title>Title</title> 
<meta name="viewport" content="width=device-width, initial-scale=1">
device-width  نشان می‌دهد که می‌خواهیم مقیاس محتوای ما به اندازه‌ی عرض دستگاه(device) مورد نظر باشد و initial-scale هم مقدار زوم را برای Web page ما مشخص می‌کند. شما می‌توانید با مقدار دهی user-scalable=no هم امکان تغییر زوم را به کاربر ندهید. این متا تگ را در تمام صفحات html خود بعد از تگ title قرار دهید.

روال کار jQuery Mobile
برای اینکه بتواند سند HTML ما را برای استفاده‌ی در موبایل بهینه کند، ابتدا آن را لود می‌کند و سپس بر  اجزایی که با ویژگیdata-role علامت گذاری شده‌اند، CSS3 بهینه شده برای موبایل را اعمال می‌کند.


از آنجایی که مستندات jQuery Mobile به قدر کافی کامل هست، نیازی نیست تا در مورد تک تک آنها مثال بزنیم و از اصل مطلب دور شویم. در هر مثالی که زده خواهد شد، در صورت استفاده از ویجتی خاص، با آن آشنا خواهیم شد.

لیست کامل اتریبیوت‌های -data به همراه مقادیری که می‌پذیرند 

دموی مربوط به ویجت‌ها  

لیست تمام رخدادها 

شما می‌توانید از امکانات Theme Roller برای شخصی سازی تم‌های مورد نیاز استفاده کنید.

لیست کامل کلاس‌های CSS  



Cordova Plugins

از این قسمت http://plugins.cordova.io/#/viewAll و این قسمت  http://plugreg.com/plugins می‌توانید سراغ پلاگین‌های مورد نیاز خود بگردید. برای مثال وارد بخش کانفیگ پروژه شده و از قسمت plugins  و تب Core یکسری از پلاگین‌هایی را که در Cordova گنجانده شده است، مشاهده می‌کنید. با کلیک بر روی دکمه‌ی Add می‌توانید آن را دانلود کرده و از API‌های آن استفاده کنید.



برای مثال پلاگین Notification را به پروژه اضافه می‌کنم. سپس یک فایل js را با نام custom.js به فولدر scripts در ریشه پروژه اضافه کرده و  محتوای فایل‌های index.html , custome.js را به شکل زیر در نظر می‌گیرم:


$(function() {
    $("#alert").on('tap', function(event) {
        navigator.notification.alert("اطلاعات ذخیره شد",null, "alert", "تایید");
    });

    $("#prompt").on('tap', function(event) {
        navigator.notification.prompt("برای تائید نام خود را وارد کنید", onPrompt, "prompt", "تایید", "لغو"],"نام خود"]);
    });

    function onPrompt(results) {
        navigator.notification.alert(results.buttonIndex + "\n" + results.input1, null);
    }
    $("#confirm").on('tap', function(event) {
        navigator.notification.confirm("حذف انجام شود؟", onConfirm, "confirm", ["بله", "خیر", "نمیدانم"]);
    });

    function onConfirm(buttonIndex) {
        navigator.notification.alert(buttonIndex , null);
    }
    $("#beep").on('tap', function(event) {
        navigator.notification.beep(1);
    });

});

رخداد tap زمانی صادر می‌شود که کاربر، دکمه‌ی مورد نظر را لمس کند و یکی از رخداد‌های jQuery Mobile می‌باشد. بعد از نصب پلاگین Notification، با استفاده از navigator.notification می‌توانید به متد‌های مورد نظر که در بالا مشخص است، دسترسی پیدا کنید.

برای آشنایی با این پلاگین می‌توانید داکیومنت آن را مطالعه کنید.

در کد بالا با استفاده از متد‌های callback توانسته‌ایم اطلاعاتی در مورد نوع عملکرد کاربر با notification ما بدست آوریم.


<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>CordovaApp01</title>
   <meta name="viewport" content="width=device-width, initial-scale=1"/> 
    <!-- CordovaApp01 references -->
    <link href="css/index.css" rel="stylesheet" />
    <link href="jquery.mobile.rtl/css/themes/default/rtl.jquery.mobile-1.4.0.css" rel="stylesheet" />
</head>
<body>
<div data-role="page" id="page1">
    <div data-role="header">
        <h2>
            تست پلاگین Notification
        </h2>
    </div>
    <div data-role="content">
        <a href="#page2" data-transition="pop" data-rel="dialog" data-role="button" data-inline="true" data-icon="back">page 2</a>
       
        <button data-role="button" id="alert" data-inline="true" >alert</button>
        <button data-role="button" id="confirm" data-inline="true">confirm</button>
        <button data-role="button" id="beep" data-inline="true" >beep</button>
        <button data-role="button" id="prompt" data-inline="true" >prompt</button>

    </div>
    <div data-role="footer">
        <h2>من فوتر هستم</h2>
    </div>
</div>
    <div data-role="page" id="page2">
        <div data-role="header">
            <h1>Header</h1>
        </div>
        <div data-role="content">
            Content
        </div>
        <div data-role="footer">
            <h1>Footer</h1>
        </div>
    </div>
<!-- Cordova reference, this is added to your app when it's built. -->
    <script src="scripts/jquery-2.1.3.min.js"></script>
    <script src="cordova.js"></script>
    <script src="scripts/platformOverrides.js"></script>
    <script src="scripts/index.js"></script>
    <script src="jquery.mobile.rtl/js/rtl.jquery.mobile-1.4.0.js"></script>
    <script src="scripts/custom.js"></script>
</body>
</html>

در کد بالا 4 تا button دیده می‌شود که ویژگی data-role آنها مقدار button در نظر گرفته شده‌است تا توسط jQuery Mobile به عنوان button شناخته شوند و استایل‌های لازم بر روی آن‌ها اعمال گردد. قرار است طبق کد js ایی که نوشته‌ایم، با لمس کردن هر کدام از دکمه‌ها، notification هایی نمایش داده شوند.


برای اینکار شبیه ساز YouWave را دانلود کرده و نصب کنید. سپس در قسمت toolbar ویژوال، گزینه‌ی Device را به جای شبیه ساز Ripple انتخاب کنید. نرم افزار youwave را اجرا کنید حال اگر برنامه را اجرا کنید با خطای زیر مواجه خواهید شد:

Error447C:\Users\Administrator\Documents\Visual Studio 2013\Projects\CordovaApp-01\CordovaApp-01\bld\Debug\platforms\android\cordova\node_modules\q\q.js:126CordovaApp-01
Error448throw e;CordovaApp-01
Error449^CordovaApp-01
Error450Error : DEP10201 : Failed to deploy to device, no devices found.CordovaApp-01
مشخصا خطا، مبنی بر پیدا نشدن دستگاه خارجی است. برای رفع این مشکل می‌بایست شبیه ساز youwave را به ویژوال استودیو وصل کنیم. برای این منظور دستور زیر را در cmd اجرا کنید.
adb connect localhost:5558

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


علاوه بر این ما در سند HTML خود در بالا، یک page و یک تگ a قرار داده‌ایم. 
 <a href="#page2" data-transition="pop" data-rel="dialog" data-role="button" data-inline="true" data-icon="back">page 2</a>
data-role: با مقدار button در نظر گرفته شده است؛ لذا به شکل 4 دکمه دیگر رندر خواهد شد.
data-transition: با مقدار pop در نظر گرفته شده است که مشخص کننده‌ی افکت ظاهر شدن صفحه‌ای است که قرار است بار گذاری شود.
data-rel: مشخص می‌کند که صفحه‌ی مورد نظر من به صورت دیالوگ باز شود.
data-icon: با استفاده از این ویژگی می‌توان icon مورد نظر خود را برای المنت در نظر گرفت.
data-inline: برای به خط کردن دکمه‌ها کنار هم استفاده می‌شود.
با لمس کردن این دکمه، نتیجه به شکل زیر خواهد بود:

در مقاله‌ی بعد، به مباحث Database در Cordova خواهیم پرداخت.

ادامه دارد...