مطالب
فرمت کردن اطلاعات نمایش داده شده به کمک Kendo UI Grid
پیشنیازهای بحث:
- «صفحه بندی، مرتب سازی و جستجوی پویای اطلاعات به کمک Kendo UI Grid »
- «استفاده از Kendo UI templates»

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


مباحث قسمت سمت سرور این مثال با مطلب «صفحه بندی، مرتب سازی و جستجوی پویای اطلاعات به کمک Kendo UI Grid» دقیقا یکی است. فقط یک خاصیت AddDate نیز در اینجا اضافه شده‌است.


تغییر نحوه‌ی نمایش pager

اگر به قسمت pager تصویر فوق دقت کنید، یک دکمه‌ی refresh، تعداد موارد هر صفحه و امکان وارد کردن دستی شماره صفحه، در آن پیش بینی شده‌است. این موارد را با تنظیمات ذیل می‌توان فعال کرد:
            $("#report-grid").kendoGrid({
                // ...
                pageable: {
                    previousNext: true, // default true
                    numeric: true, // default true
                    buttonCount: 5, // default 10
                    refresh: true, // default false
                    input: true, // default false
                    pageSizes: true // default false
                },


بومی سازی پیغام‌های گرید

پیغام‌های فارسی را که در تصویر فوق مشاهده می‌کنید، حاصل پیوست فایل kendo.fa-IR.js هستند:
 <!--https://github.com/loudenvier/kendo-global/blob/master/lang/kendo.fa-IR.js-->
<script src="js/messages/kendo.fa-IR.js" type="text/javascript"></script>


گروه بندی اطلاعات

برای گروه بندی اطلاعات در Kendo UI Grid دو قسمت باید تغییر کنند.
ابتدا باید فیلد پیش فرض گروه بندی در قسمت data source گرید تعریف شود:
            var productsDataSource = new kendo.data.DataSource({
                // ...
                group: { field: "IsAvailable" },
                // ...
            });
همین تنظیم، گروه بندی را فعال خواهد کرد. اگر علاقمند باشید که به کاربران امکان تغییر دستی گروه بندی را بدهید، خاصیت groupable را نیز true کنید.
$("#report-grid").kendoGrid({
// ...
groupable: true, // allows the user to alter what field the grid is grouped by
// ...
در این حالت با کشیدن و رها کردن یک سرستون، به نوار ابزار مرتبط با گروه بندی، گروه بندی گرید بر اساس این فیلد انتخابی به صورت خودکار انجام می‌شود.


اضافه کردن ته جمع‌های ستون‌ها

این ته جمع‌ها که aggregate نام دارند باید در دو قسمت فعال شوند:
            var productsDataSource = new kendo.data.DataSource({
                //...
                aggregate: [
                    { field: "Name", aggregate: "count" },
                    { field: "Price", aggregate: "sum" }
                ]
                //...
            });
ابتدا در قسمت data source مشخص می‌کنیم که چه تابع تجمعی قرار است به ازای یک فیلد خاص استفاده شود.
سپس این متدها را می‌توان مطابق فرمت hash syntax قالب‌های Kendo UI در قسمت footerTemplate هر ستون تعریف کرد:
            $("#report-grid").kendoGrid({
                // ...
                columns: [
                    {
                        field: "Name", title: "نام محصول",
                        footerTemplate: "تعداد: #=count#"
                    },
                    {
                        field: "Price", title: "قیمت",
                        footerTemplate: "جمع: #=kendo.toString(sum,'c0')#"
                    }
                ]
                // ...
            });


فرمت شرطی اطلاعات

در ستون قیمت، می‌خواهیم اگر قیمتی بیش از 2490 بود، با پس زمینه‌ی قهوه‌ای و رنگ زرد نمایش داده شود. برای این منظور می‌توان یک قالب Kendo UI سفارشی را طراحی کرد:
    <script type="text/x-kendo-template" id="priceTemplate">
        #if( Price > 2490 ) {#
        <span style="background:brown; color:yellow;">#=kendo.toString(Price,'c0')#</span>
        #} else {#
        #= kendo.toString(Price,'c0')#
        #}#
    </script>
سپس نحوه‌ی استفاده‌ی از آن به صورت ذیل خواهد بود:
            $("#report-grid").kendoGrid({
                //...
                columns: [
                    {
                        field: "Price", title: "قیمت",
                        template: kendo.template($("#priceTemplate").html()),
                        footerTemplate: "جمع: #=kendo.toString(sum,'c0')#"
                    }
                ]
                //...
            });
توسط متد kendo.template امکان انتساب یک قالب سفارشی به خاصیت template یک ستون وجود دارد.


فرمت تاریخ میلادی به شمسی در حین نمایش

برای تبدیل سمت کلاینت تاریخ میلادی به شمسی از کتابخانه‌ی moment-jalaali.js کمک گرفته شده‌است:
 <!--https://github.com/moment/moment/-->
<script src="js/cultures/moment.min.js" type="text/javascript"></script>
<!--https://github.com/jalaali/moment-jalaali-->
<script src="js/cultures/moment-jalaali.js" type="text/javascript"></script>
پس از آن تنها کافی است متد فرمت این کتابخانه را در قسمت template ستون تاریخ و توسط hash syntax قالب‌های Kendo UI بکار برد:
            $("#report-grid").kendoGrid({
                //...
                columns: [
                    {
                        field: "AddDate", title: "تاریخ ثبت",
                        template: "#=moment(AddDate).format('jYYYY/jMM/jDD')#"
                    }
                ]
                //...
            });


اضافه کردن یک دکمه به نوار ابزار گرید

نوار ابزار Kendo UI Grid را نیز می‌توان توسط یک قالب سفارشی آن مقدار دهی کرد:
            $("#report-grid").kendoGrid({
                // ...
                toolbar: [
                    { template: kendo.template($("#toolbarTemplate").html()) }
                ]
                // ...
            });
برای نمونه toolbarTemplate فوق را به نحو ذیل تعریف کرده‌ایم:
    <script>
        // این اطلاعات برای تهیه خروجی سمت سرور مناسب هستند
        function getCurrentGridFilters() {
            var dataSource = $("#report-grid").data("kendoGrid").dataSource;
            var gridState = {
                page: dataSource.page(),
                pageSize: dataSource.pageSize(),
                sort: dataSource.sort(),
                group: dataSource.group(),
                filter: dataSource.filter()
            };
            return kendo.stringify(gridState);
        }
    </script>

    <script id="toolbarTemplate" type="text/x-kendo-template">
        <a class="k-button" href="\#" onclick="alert('gridState: ' + getCurrentGridFilters());">نوار ابزار سفارشی</a>
    </script>
دکمه‌ی اضافه شده، وضعیت فیلتر data source متصل به گرید را بازگشت می‌دهد. برای مثال مشخص می‌کند که در چه صفحه‌ای با چه تعداد رکورد قرار داریم و همچنین وضعیت مرتب سازی، فیلتر و غیره چیست. از این اطلاعات می‌توان در سمت سرور برای تهیه‌ی خروجی‌های PDF یا اکسل استفاده کرد. وضعیت فیلتر اطلاعات مشخص است. بر همین مبنا کوئری گرفته و سپس می‌توان نتیجه‌ی آن‌را تبدیل به منبع داده تهیه خروجی مورد نظر کرد.



کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید:
KendoUI05.zip
مطالب
طرح پیشنهادی برای بارگذاری پویای ماژول‌های JS
آقای Domenic Denicola در نسخه‌های بعدی، طرح پیشنهادی را مطرح کرده است که مربوط به بارگذاری داینامیک ماژول‌های JS می‌باشد. البته کتابخانه‌ها و روش‌هایی در حال حاضر برای این کار وجود دارند. با هم مثال‌هایی از این قابلیت را بررسی میکنیم. 

در نسخه جدید Javascript قابلیتی برای import کردن ماژول‌ها وجود دارد؛ ولی این قابلیت کاملا استاتیک می‌باشد. کد زیر را مشاهده کنید:
import someModule from './dir/someModule.js';
خوب سوالی که مطرح می‌شود این است که چه نیازی به بارگذاری داینامیک ماژول‌ها داریم؟               
جواب این سوال خیلی مشخص است. در import معمولی و استاتیک JS، ما تمام ماژول‌های مورد نیاز در پروژه را فراخوانی میکنیم. اما شاید خیلی از این ماژول‌ها در طول اجرای پروژه مورد نیاز نباشند و بر حسب رفتارهای کاربر نیاز به این ماژول‌ها داشته باشیم. در این صورت هست که بارگذاری داینامیک ماژول‌ها مطرح می‌شود. این قابلیت در جاوا اسکریپت به صورت built-in وجود ندارد. ولی با کتابخانه‌هایی مانند RequireJS این قابلیت قابل استفاده هست. این Proposal توسط آقای Domenic Denicola مطرح شده است.                  
 کد زیر مثال ساده از این قابلیت می‌باشد:
import('./dir/someModule.js')
    .then(someModule => someModule.foo());
                   
 یا یک مثال عملیاتی؛ فرض کنید با کلیک بر روی دکمه‌ای می‌خواهیم یک Dialog را باز کنیم که منطق و قوائد مخصوص به خود را دارد و به صورت یک ماژول جداگانه نوشته شده‌است. کد زیر را مشاهده کنید:
 button.addEventListener('click', event => {
        import('./dialogBox.js')
        .then(dialogBox => {
            dialogBox.open();
        })
        .catch(error => {
            /* Error handling */
        })
    });
                 
 این قابلیت هم وجود دارد که دو ماژول را که در یک فایل نوشته شده‌اند نیز به صورت جداگانه استفاده کنید.
import('./myModule.js')
    .then(({export1, export2}) => {
        export1.run();
        export2.fire();
    });
             
 شما حتی می‌توانید چند ماژول را باهم بارگذاری کنید و بعد از پایان بارگذاری همه ماژول‌ها، یک عمل خاصی را انجام دهید. کد زیر را مشاهده کنید:
Promise.all([
        import('./module1.js'),
        import('./module2.js'),
        import('./module3.js'),
    ])
    .then(([module1, module2, module3]) => {
        // code 
    });
این موضوع را به کمک Promise با متد all انجام دادیم.                

 حتی شما می‌توانید با قابلیت async و await نیز کدهای تمیزتر و با قابلیت خوانایی بالاتری را بنویسید. مثال زیر را مشاهده کنید:
async function main() {
        const myModule = await import('./myModule.js');
    
        myModule.getInfo();

        const {export1, export2} = await import('./myModule.js');      
        
        export1.run();
        
        export2.fire()
    }
    main();
                    
 خوب خوشبختانه طرافداران NodeJS ماژول مربوط به این قابلیت را قبل از ارائه این قابلیت جدید در JS به صورت Packages در NPM فراهم کرده‌اند. لینک زیر را مشاهده کنید:
                       
 و Developer‌های که از Webpack در پروژه‌های خود استفاده میکنند می‌توانند از ماژول زیر استفاده کنند که توسط تیم Airbnb تهیه شده است:
                   
 و Developer‌هایی که از نسخه ۲ Webpack استفاده میکنند، می‌توانند بحث Code Splitting را در راهنمای زیر مشاهده کنند:
                      

البته آقای Jake Archibald کد جالبی را برای این قابلیت پیشنهاد داده‌است که ترکیبی از import استاتیک ES6 می‌باشد:

function importModule (url) {
  return new Promise((resolve, reject) => {
    const script = document.createElement("script");
    const tempGlobal = "__tempModuleLoadingVariable" + Math.random().toString(32).substring(2);
    script.type = "module";
    script.textContent = `import * as m from "${url}"; window.${tempGlobal} = m;`;

    script.onload = () => {
      resolve(window[tempGlobal]);
      delete window[tempGlobal];
      script.remove();
    };

    script.onerror = () => {
      reject(new Error("Failed to load module script with URL " + url));
      delete window[tempGlobal];
      script.remove();
    };

    document.documentElement.appendChild(script);
  });
}               
مطالب
واکشی اطلاعات سرویس Web Api با استفاده از TypeScript و AngularJs
در پست‌های قبلی با TypeScript، AngularJs و Web Api آشنا شدید. در این پست قصد دارم از ترکیب این موارد برای پیاده سازی عملیات واکشی اطلاعات سرویس Web Api در قالب یک پروژه استفاده نمایم. برای شروع ابتدا یک پروژه Asp.Net MVC ایجاد کنید.
در قسمت مدل ابتدا یک کلاس پایه برای مدل ایجاد خواهیم کرد:
public abstract class Entity
    {
        public Guid Id { get; set; }
    }
حال کلاسی به نام Book ایجاد می‌کنیم:
public class Book : EntityBase
    {
        public string Name { get; set; }
        public decimal Author { get; set; }
    }
در پوشه مدل یک کلاسی به نام BookRepository ایجاد کنید و کد‌های زیر را در آن کپی نمایید(به جای پیاده سازی بر روی بانک اطلاعاتی، عملیات بر روی لیست درون حافظه انجام می‌گیرد):
 public class BookRepository
    {
        private readonly ConcurrentDictionary<Guid, Book> result = new ConcurrentDictionary<Guid, Book>();

        public IQueryable<Book> GetAll()
        {
            return result.Values.AsQueryable();
        }        

        public Book Add(Book entity)
        {
            if (entity.Id == Guid.Empty) entity.Id = Guid.NewGuid();

            if (result.ContainsKey(entity.Id)) return null;

            if (!result.TryAdd(entity.Id, entity)) return null;

            return entity;
        }     
    }

نوبت به کلاس کنترلر می‌رسد. یک کنترلر Api به نام BooksController ایجاد کنید و سپس کد‌های زیر را در آن کپی نمایید:
 public class BooksController : ApiController
    {
        public static BookRepository repository = new BookRepository();       

public BooksController()
        {
            repository.Add(new Book 
            {
                Id=Guid.NewGuid(),
                Name="C#",
                Author="Masoud Pakdel"
            });

            repository.Add(new Book
            {
                Id = Guid.NewGuid(),
                Name = "F#",
                Author = "Masoud Pakdel"
            });

            repository.Add(new Book
            {
                Id = Guid.NewGuid(),
                Name = "TypeScript",
                Author = "Masoud Pakdel"
            });
        }

        public IEnumerable<Book> Get()
        {
            return repository.GetAll().ToArray();
        }          
    }

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

حال نویت به عملیات سمت کلاینت میرسد. برای استفاده از قابلیت‌های TypeScript و AngularJs در Vs.Net از این مقاله کمک بگیرید. بعد از آماده سازی در فولدر script، پوشه ای به نام app می‌سازیم و یک فایل TypeScript به نام  BookModel  در آن ایجاد می‌کنیم:
module Model {
    export class Book{
        Id: string;
        Name: string;
        Author: string;
    }
}
واضح است که ماژولی به نام Model داریم که در آن کلاسی به نام Book ایجاد شده است. برای انتقال اطلاعات از طریق سرویس http$ در Angular نیاز به سریالایز کردن این کلاس به فرمت Json خواهیم داشت. قصد داریم View مورد نظر را به صورت زیر ایجاد نماییم:
 <div ng-controller="Books.Controller">       
        <table class="table table-striped table-hover" style="width: 500px;">
            <thead>
                <tr>
                    <th>Name</th>
                    <th>Author</th>              
                </tr>
            </thead>
            <tbody>
                <tr ng-repeat="book in books">
                    <td>{{book.Name}}</td>
                    <td>{{book.Author}}</td>                                     
                </tr>
            </tbody>
        </table>
    </div>

توضیح کد‌های بالا:
ابتدا یک کنترلری که به نام Controller که در ماژولی به نام Book تعریف شده است باید ایجاد شود. اطلاعات تمام کتب ثبت شده باید از سرویس مورد نظر دریافت و با یک ng-repeat در جدول نمایش داده خواهند شود.
در پوشه app یک فایل TypeScript دیگر برای تعریف برخی نیازمندی‌ها به نام  AngularModule ایجاد می‌کنیم که کد آن به صورت زیر خواهد بود:
declare module AngularModule {
    export interface HttpPromise {
        success(callback: Function) : HttpPromise;       
    }
    export interface Http {
        get(url: string): HttpPromise;   
    }
}
در این ماژول دو اینترفیس تعریف شده است. اولی به نام HttpPromise است که تابعی به نام success  دارد. این تابع باید بعد از موفقیت آمیز بودن  عملیات فراخوانی شود. ورودی آن از نوع Function است. بعنی اجازه تعریف یک تابع را به عنوان ورودی برای این توابع دارید.
در اینترفیس Http نیز تابعی به نام get تعریف شده  است که  برای دریافت اطلاعات از سرویس api، مورد استفاده قرار خواهد گرفت. از آن جا که تعریف توابع در اینترفیس فاقد بدنه است در نتیجه این جا فقط امضای توابع مشخص خواهد شد. پیاده سازی توابع به عهده کنترلر‌ها خواهد بود:
مرحله بعد مربوط است به تعریف کنترلری  به نام BookController تا اینترفیس بالا را پیاده سازی نماید. کد‌های آن به صورت زیر خواهد بود:
/// <reference path='AngularModule.ts' />
/// <reference path='BookModel.ts' />

module Books {
    export interface Scope {        
        books: Model.Book[];
    }

    export class Controller {
        private httpService: any;

        constructor($scope: Scope, $http: any) {
            this.httpService = $http;

            this.getAllBooks(function (data) {
                $scope.books = data;
            });
            var controller = this;
    }

        getAllBooks(successCallback: Function): void {
            this.httpService.get('/api/books').success(function (data, status) {
                successCallback(data);
            });
        }
    }
}


توضیح کد‌های بالا:
برای دسترسی به تعاریف انجام شده در سایر ماژول‌ها باید ارجاعی به فایل تعاریف ماژول‌های مورد نظر داشته باشیم. در غیر این صورت هنگام استفاده از این ماژول‌ها با خطای کامپایلری روبرو خواهیم شد. عملیات ارجاع به صورت زیر است:
/// <reference path='AngularModule.ts' />
/// <reference path='BookModel.ts' />
در پست قبلی توضیح داده شد که برای مقید سازی عناصر بهتر است یک اینترفیس به نام Scope تعریف کنیم تا بتوانیم متغیر‌های مورد نظر برای مقید سازی را در آن تعریف نماییم در این جا تعریف آن به صورت زیر است:
export interface Scope {  
        books: Model.Book[];      
    }
در این جا فقط نیاز به لیستی از کتاب‌ها داریم تا بتوان در جدول مورد نظر در View آنرا پیمایش کرد. تابعی به نام getAllBooks در کنترلر مورد نظر نوشته شده است که ورودی آن یک تابع خواهد بود که باید بعد از واکشی اطلاعات از سرویس، فراخوانی شود. اگر به کد‌های بالا دقت کنید می‌بینید که در ابتدا سازنده کنترلر،سرویس http$ موجود در Angular به متغیری به نام httpService نسبت داده می‌شود. با فراخوانی تابع get و ارسال آدرس سرویس که با توجه به مقدار مسیر یابی پیش فرض کلاس WebApiConfig باید با api شروع شود به راحتی اطلاعات مورد نظر به دست خواهد آمد. بعد از واکشی در صورت موفقیت آمیز بودن عملیات تابع success اجرا می‌شود که نتیجه آن انتساب مقدار به دست آمده به متغیر books تعریف شده در scope$ می‌باشد.

در نهایت خروجی به صورت زیر خواهد بود:


سورس پیاده سازی مثال بالا در Visual Studio 2013
مطالب
ساخت تم سفارشی در انگیولار متریال ۲ - بخش اول

در  قسمت قبل  بیان شد همراه با نصب Angular Material، تعدادی تم از قبل ساخته شده نیز نصب خواهند شد که شامل یکسری استایل با رنگهای مشخصی هستند. این تم‌ها عبارتند از:

  • indigo-pink
  • deeppurple-amber
  • purple-green
  • pink-bluegrey 

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


مقدمه

تم در انگیولار متریال، از ترکیب چند پالت رنگی، ساخته می‌‌شود. پالت‌های رنگ را در طراحی متریال ( Material Design ) در  اینجا می‌توانید مشاهده کنید. انگیولار متریال رنگهای مورد استفاده خود را در گروه‌های زیر دسته بندی کرده است. 

  • Primary : این پالت رنگی به صورت گسترده در بخشهای مختلف صفحه و کامپوننت‌ها مورد استفاده قرار می‌گیرد.
  • Accent : این پالت رنگی برای دکمه‌های شناور و همچنین المنتهای تعاملی مورد استفاده قرار می‌گیرد.
  • Warn : این پالت رنگی برای مشخص کردن حالت‌های خطا، مورد استفاده قرار می‌گیرد.
  • Foreground : این پالت رنگی برای متون و آیکونها مورد استفاده قرار می‌گیرد.
  • Background : این پالت رنگی برای المنت‌های پس زمینه مورد استفاده قرار می‌گیرد.

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


تعریف تم سفارشی

برای ساخت تم سفارشی نیاز به یک فایل Sass خواهیم داشت. پس در مسیر /src یک فایل Sass را  با نام my-custom-theme.scss ایجاد می‌کنیم (شما می‌توانید از هر نام دیگری برای فایل Sass استفاده کنید). اگر از AngularCLI برای برنامه‌های خود استفاده می‌کنید، بایستی فایل Sass ایجاد شده را به لیست استایل‌ها در فایل angular-cli.json اضافه کنید. این کار باعث می‌شود AngularCLI این فایل Sass را در زمان build به css کامپایل کند. 

نکته: استفاده از فایل Sass برای ساختن تم سفارشی به این معنی نیست که شما از Sass برای سایر Style های برنامه خود استفاده کنید.

"styles": [
  "styles.css",
  "my-custom-theme.scss"
],

اگر از AngularCLI استفاده نمی‌کنید، شما نیاز به ابزاری برای کامپایل فایل Sass به css خواهید داشت.  ابزارهای بسیاری در این زمینه وجود دارند از جمله: gulp-sass و grunt-sass . ولی ساده‌ترین ابزار برای این کار node-sass می‌باشد. کافی است بعد از نصب، دستور زیر را اجرا کنید تا فایل sass به css کامپایل شود. فایل css تولید شده را مستقیما در صفحه index.html خود می‌توانید استفاده کنید.

node-sass src/my-custom-theme.scss dist/my-custom-theme.css

در فایل تم ایجاد شده ( my-custom-theme.scss ) ابتدا بایستی فایل Sass اصلی انگیولار متریال را وارد کنید.

@import '~@angular/material/theming';

در قدم بعدی mixin تعریف شده با نام mat-core  را در فایل Sass  انگیولار متریال، وارد می‌کنیم. این mixin شامل تمامی Styleهای مشترکی است که توسط کامپوننت‌های مختلف استفاده می‌شود. 

@include mat-core();

نکته: مطمئن شوید فقط یک بار این mixin را در سرتاسر برنامه خود وارد کرده باشید. در غیر این صورت، فایل css تولید شده شامل یکسری Style تکراری خواهد بود و این باعث بزرگ و حجیم شدن فایل css نهایی خواهد شد. 


تا اینجا فایل تم ایجاد شده اینگونه خواهد بود:  

@import '~@angular/material/theming';
@include mat-core();

حالا نوبت تعریف تم سفارشی است. ولی قبل از آن باید با سیستم رنگها در طراحی متریال ( Material Design ) آشنایی داشته باشیم. در طراحی متریال ۱۹ پالت رنگی با نام‌های مختلف وجود دارند. برای ۱۶ پالت رنگی، ۱۴ طیف رنگی و برای ۳ پالت رنگی دیگر، ۱۰ طیف رنگی در نظر گرفته شده است. هر کدام از این طیف‌های رنگی، دارای یک مقدار عددی است. یعنی یک رنگ در سیستم طراحی متریال متشکل از یک نام رنگ و یک شماره طیف رنگ است که مقدار پیش فرض طیف رنگ، عدد ۵۰۰ می‌باشد. 


حالا با استفاده از تابع mat-palette تعریف شده در فایل Sass انگیولار متریال، سه متغیر را برای رنگهای Primary ، Accent و Warn در فایل my-custom-theme.scss ، به شکل زیر تعریف می‌کنیم. 

$my-app-primary: mat-palette($mat-indigo);
$my-app-accent:  mat-palette($mat-pink, 500, A100, A400);
$my-app-warn:    mat-palette($mat-deep-orange);

تابع mat-palette در فایل Sass اصلی انگیولار متریال، به شکل زیر تعریف شده است. 

@function mat-palette($base-palette, $default: 500, $lighter: 100, $darker: 700)

این تابع یک پارامتر اجباری دارد و بقیه پارامترها اختیاری هستند.

  • base-palette $: نام رنگ را دریافت می‌کند. این پارامتر اجباری است و باید مشخص شود. 
  • default$: با این پارامتر، طیف پیش‌فرض رنگ انتخاب شده را مشخص می‌کنیم. این پارامتر اختیاری است و مقدار پیش فرض آن 500 است.
  • lighter$: با این پارامتر، طیف روشن رنگ انتخاب شده را مشخص می‌کنیم. این پارامتر اختیاری است و مقدار پیش فرض آن 100 است.
  • darker$: با این پارامتر، طیف تیره رنگ انتخاب شده را مشخص می‌کنیم. این پارامتر اختیاری است و مقدار پیش فرض آن 700 است.

در قدم آخر با استفاده از تابع mat-light-theme یا mat-dark-theme، رنگهای تعریف شده در مرحله قبل را ترکیب کرده و نتیجه را به عنوان ورودی به mixin به نام angular-material-theme  ارسال و بارگذاری می‌کنیم. 

تابع mat-light-theme و mat-dark-theme سه پارامتر را دریافت می‌کند. پارارمتر اول پالت رنگ ایجاد شده توسط تابع mat-palette برای گروه Primary ، پارامتر دوم پالت رنگ ایجاد شده برای گروه Accent و پارامتر سوم پالت رنگ ایجاد شده برای گروه Warn را دریافت می‌کند. دو پارامتر اول اجباری و پارامتر سوم اختیاری با مقدار پیش فرض mat-palette($mat-red) می‌باشد. 

شکل کلی فایل Sass در نهایت به شکل زیر خواهد بود. 

@import '~@angular/material/theming';
@include mat-core();
$my-app-primary: mat-palette($mat-teal);
$my-app-accent:  mat-palette($mat-amber, 500, A100, A400);
$my-app-warn:    mat-palette($mat-deep-orange);

$my-app-theme: mat-light-theme($my-app-primary, $my-app-accent, $my-app-warn);

@include angular-material-theme($my-app-theme);

برای استفاده از پالت رنگ‌های ایجاد شده، از خصوصیت color در المنت‌های انگولار متریال استفاده می‌کنیم. برای نمونه بعد از تغییر فایل Sass به شکل بالا و حذف لینک تم از پیش ساخته شده که در پست قبلی به Style.cs اضافه کرده بودیم، می‌توانیم کار خود را به صورت زیر آزمایش کنیم. در فایل app.component.html در تگ main کدهای زیر را اضافه کنید. 

<md-card>
  <button md-raised-button color="primary">
    Primary
  </button>
  <button md-raised-button color="accent">
    Accent
  </button>
  <button md-raised-button color="warn">
    Warning
  </button>
</md-card>

خروجی زیر را مشاهده خواهید کرد. 

همچنین می‌توانید به جای استفاده از تابع mat-light-theme از تابع mat-dark-theme استفاده کنید. دراین صورت خروجی زیر را خواهید دید. 

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

کدهای این قسمت را از اینجا دریافت کنید:  ساخت-تم-سفارشی-در-انگولار-متریال-۲---بخش-اول.rar 

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

purchase.js
function purchaseProduct(){
  console.log("Function : purchaseProduct");
  var credits = getCredits();
  if(credits > 0){
    reserveProduct();
    return true;
  }
  return false;
}
product.js
function reserveProduct(){
  console.log("Function : reserveProduct"); 
  return true;
}
credits.js
function getCredits(){
  console.log("Function : getCredits"); 
  var credits = "100";
  return credits;
}
همان طور که در فایل‌های بالا مشاهده می‌کنید در فایل purchase.js از دو فایل دیگر استفاده شده است. در فایل main.js پروژه کد زیر را برای استفاده از فایل‌های بالا می‌نویسیم:
main.js
var result = purchaseProduct();
فرض کنید در فایل Html تگ‌های script ما به صورت زیر باشد:
<script src="products.js"></script>
<script src="purchase.js"></script>
<script src="main.js"></script>
<script src="credits.js"></script>
از آنجا که لود فایل purchase.js وابستگی مستقیم به لود فایل credits.js دارد و از طرفی دیگر به دلیل این که فایل credit.Js بعد از تمام فایل‌ها لود می‌شود در نتیجه برنامه اجرا نخواهد شد و با خطای زیر روبرو می‌شویم:

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

 RequireJs چگونه برای حل این مشکل به ما کمک می‌کند؟
با استفاده از فریم ورک RequireJs کد‌های ما به ماژول‌های کوچک‌تر شکسته می‌شود و وابستگی ماژول‌ها در تنظیمات لود فایل ثبت می‌شود. در ضمن این فریم ورک با مرورگرها جدید و محبوب کاملا سازگار است. برای شروع فایل‌های مورد نیاز را از اینجا دانلود نمایید. البته می‌توانید از nuget هم استفاده کنید:
PM> Install-Package RequireJS
در مثال بالا فایل‌های جاوااسکریپ به صورت زیر تغییر خواهد کرد:
purchase.js
define(["credits","products"], function(credits,products) { 
  console.log("Function : purchaseProduct"); 
  return {
    purchaseProduct: function() { 
      var credit = credits.getCredits();
      if(credit > 0){
        products.reserveProduct();
        return true;
      }
      return false;
    }
  }
});
همان طور که مشاهده می‌کنید در فایل بالا از دستور define برای تعریف ماژول استفاده شده است و نام دو ماژول products و credits را به صورت پارامتر برای مشخص کردن وابستگی ماژول‌ها تعریف کردیم.
products.js
define(function(products) {
  return {
    reserveProduct: function() {
      console.log("Function : reserveProduct"); 
      return true;
    }
  }
});
credits.js
define(function() {
  console.log("Function : getCredits"); 
  return {
    getCredits: function() {
      var credits = "100";
      return credits;
    }
  }
});
به دلیل این که فایل‌های بالا وابستگی به ماژول‌های دیگر ندارند در نتیجه دستور define فقط شامل تعریف تابع است.
کافیست در فایل main.js، برای استفاده از فایل purchase.js از دستور require استفاده کنید.
require(['purchase'], function( purchase ) {
    var result = purchase.purchaseProduct(); 
});
مهم‌ترین مزیتی که این روش دارد این است که دیگر نیازی به نوشتن تگ‌های Script (آن هم به ترتیب درست) برای فراخوانی فایل‌های جاوااسکریپتی نخواهد بود؛ از طرفی دیگر وابستگی بین این فایل‌ها در هنگام تعریف ماژول مشخص خواهد شد. از آن به بعد وظیفه تامین فایل‌های مورد نیاز برای لود ماژول بر عهده RequireJs است.

تفاوت بین دستور require و define
دستور define صرفا برای تعیین وابستگی ماژول استفاده می‌شود در حالی که دستور require برای فراخوانی و لود ماژول کاربرد دارد و به نوعی به عنوان نقطه شروع اجرای برنامه خواهد بود.
در پست بعدی به پیاده سازی مثال بالا به کمک RequireJs در قالب یک پروژه Asp.Net MVC خواهم پرداخت.
بازخوردهای دوره
بوت استرپ (نگارش 3) چیست؟
سلام
میخواستم بدونم چرا به جای خواندن جی کوئری از سایت آن
 < script src = "http://code.jquery.com/jquery-latest.min.js" ></ script >
اون رو هم مثل بقیه فایل‌های جاوا اسکریپت دانلود نمیکنیم تا به صورت local استفاده کنیم؟
 < script src = "Scripts/jquery-latest.min.js  " ></ script >
مطالب
Pipeها در Angular 2 – قسمت سوم – Pipeهای Pure و Impure
 در قسمت قبل بیان شد که Angular برای اعمال Pipe بر روی Template expressions باید تمامی رخدادهای برنامه را تحت نظر قرار داده و با مشاهده‌ی هر تغییری بر روی عبارت ورودی Pipe، فراخوانی Pipe را آغاز کند. از جمله این رخدادها می‌توان به رخدادهای mouse move، timer tick، server response و فشرده شدن کلیدهای ماوس و یا کیبورد اشاره کرد. واضح است که بررسی تغییرات عبارت در این همه رخداد می‌تواند مخرب باشد و بر روی کارآئی (Performance) تاثیر منفی خواهد گذاشت. اما Angular برای حل این مشکل و همچنین هنگام مشاهده سریع تغییرات هنگام استفاده از Pipeها، الگوریتم‌های سریع و ساده‌ای در نظر گرفته است که آن‌ها را در این بخش مورد برسی قرار خواهیم داد.


Pipeهای Pure و Impure

Pipeها کلا در دو دسته‌ی Pure و Impure قرار می‌گیرند. هنگام ساخت Pipe سفارشی در صورتیکه نوع Pipe مشخص نشود، به صورت پیش فرض از نوع Pure خواهد بود. برای تعریف Pipeهایی از نوع Impure کافی است در متادیتای Pipe@، پرچم Pure را به مقدار false تنظیم کنید.
@Pipe({ name: 'impurePipe', pure: false })
تفاوت این Pipeها در زمان فراخوانی دوباره آنها است.


Pure Pipe

این نوع Pipeها تنها زمانی فراخوانی مجدد می‌شوند که یک تغییر محض (Pure Change) بر روی عبارت ورودی آنها رخ دهد. هر نوع تغییری بر روی عبارات ورودی از جنس string ، number ، Boolean ، Symbol و عبارات اولیه، یا هرنوع تغییری در ارجاع یک شیء مانند  Date ، Array ، Function و Object نیز تغییر محض محسوب می‌شود. به عنوان مثال هیچکدام از تغییرات زیر یک تغییر محض محسوب نمی‌شوند:
numbers.push(10);
obj.name = ‘javad’;
زیرا با اضافه شدن عنصری به یک آرایه یا تغییر خصوصیتی از یک شیء، باعث تغییری در ارجاع آنها نمی‌شود و همانطور که اشاره شد، در عبارات از نوع آرایه و Object، فقط تغییر در ارجاع آن‌ها یک تغییر محض محسوب می‌شود.
حالا می‌توان به این نتیجه رسید که اضافه شدن مقداری به آرایه یا به‌روزرسانی یک property از object، باعث فراخوانی مجدد Pure Pipe نخواهد نشد. شاید این نوع از Pipeها محدود کننده باشند، اما بسیار سریع هستند (برسی تغییر در ارجاع یک شیء بسیار سریعتر از بررسی کامل یک شیء، صورت می‌گیرد).


Impure Pipe

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

مثال: قصد داریم Pipe سفارشی را پیاده سازی کنیم تا آرایه‌ای از اعداد را دریافت و فقط اعداد زوج را فیلتر کرده و نمایش دهد.
برای این منظور یک فایل جدید را با نام even-numbers.pipe.ts با محتویات زیر ایجاد می‌کنیم: 
import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'evenNumbers'
})
export class EvenNumbersPipe implements PipeTransform {
  transform(numbers: Array<number>): Array<number> {
    var x=numbers.filter(r => r % 2 == 0);
    return x;
  }
}
همانطور که مشخص است این Pipe در متد transform، آرایه‌ای از اعداد را دریافت کرده و فقط اعداد زوج را بازگشت می‌دهد. حالا باید Pipe تعریف شده خود را در AppModule در قسمت declares تعریف کنیم.
// . . .
import { EvenNumbersPipe } from './pipes/even-numbers.pipe'
@NgModule({
  declarations: [
    . . .
    EvenNumbersPipe
  ],
 . . .
})
export class AppModule { }

سپس در کامپوننت مورد نظر خود متغیری را به نام numbers از نوع آرایه، با مقدار اولیه‌ی اعداد از یک تا ده، تعریف می‌کنیم:
numbers: Array<number> = [1,2,3,4,5,6,7,8,9,10];
برای نمایش این اعداد در رابط کاربری تگ‌های زیر را به قالب کامپوننت خود اضافه می‌کنیم:
<h1>All numbers</h1>
<span *ngFor="let number of numbers">
  {{number}}
</span>
همچنین با استفاده از تگ‌های زیر یک input برای اضافه کردن مقدار جدید به آرایه درنظر می‌گیریم:
<p>
  <input type="text" #number />
  <input type="button" (click)="numbers.push(number.value)" value="Add number"/>
</p>

تگ‌های زیر را نیز برای اعمال Pipe نمایش اعداد زوج، به قالب کامپوننت اضافه می‌کنیم:
<h1>even numbers</h1>
<span *ngFor="let number of numbers | evenNumbers">
  {{number}}
</span>
بعد از اجرای برنامه، یک عدد جدید زوج را به آرایه اضافه کنید. متوجه خواهید شد با اینکه لیست اعداد در قسمت All numbers به‌روز می‌شوند، ولی Pipe، متوجه تغییری بر روی آرایه نشده‌است و همچنان اعداد قبلی را نمایش می‌دهد. دلیل این امر همانطور که قبلا نیز اشاره شد، بخاطر Pure بودن Pipe و عدم فراخوانی مجدد این نوع Pipe‌ها در زمان اضافه شدن مقداری به آرایه یا تغییری در خصوصیت یک شیء است.

برای حل این مشکل، هنگام اضافه شدن عدد به آرایه، اگر ارجاع آرایه را تغییر دهیم، Pure Pipe متوجه تغییرات خواهد شد و لیست اعداد را به‌روز رسانی می‌کند (تغییر در ارجاع یک شیء، از نوع تغییرات محض است):
<p>
  <input type="text" #number />
  <input type="button" (click)="numbers = numbers.concat(number.value)" value="Add number"/>
</p>
با تغییر نحوه اضافه شدن عنصر به آرایه به شکل بالا خواهیم دید که با افزودن اعداد جدید، لیست اعداد زوج نیز در لحظه اعمال خواهند شد. این راه‌حل همیشه کارآمد نخواهد بود. همیشه تشخیص محل اضافه شدن عنصر به آرایه در برنامه کار ساده‌ای نیست تا در آنجا ارجاع آرایه را نیز تغییر دهیم. راه‌حل، استفاده از Impure Pipe است. کافی است متادیتای Pipe@ را هنگام تعریف به شکل زیر تغییر دهید:
@Pipe({
  name: 'evenNumbers',
  pure: false
})
export class EvenNumbersPipe implements PipeTransform {
   //…
}

کسانیکه با Angular 1.x آشنایی دارند، شاید اکنون متوجه این شده‌اند که چرا در Angular به مشابه Angular 1.x دیگر خبری ازfilter و orderBy نیست. با توجه به اینکه این دو فیلتر فقط با عبارات از نوع object سروکار داشتند، پیاده‌سازی آنها فقط با Impure Pipeها امکان پذیر بود و با توجه به اینکه Impure Pipeها در هر بار چرخه تغییرات کامپوننت اجرا خواهند شد، باعث کندی در صفحات خواهند شد. 
اشتراک‌ها
ایجاد دیالوگ bootstrap با استفاده از Tag Helper
<modal title="Modal title">
    <modal-body>
        <p>One fine body&hellip;</p>
    </modal-body>
    <modal-footer>
        <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
        <button type="button" class="btn btn-primary">Save changes</button>
    </modal-footer>
</modal>
ایجاد دیالوگ bootstrap با استفاده از Tag Helper
بازخوردهای دوره
پیاده سازی امتیاز دهی ستاره‌ای به مطالب به کمک jQuery در ASP.NET MVC
بعد از آپدیت قسمت نظرات با ajax اسکریپت از کار میفته.توی این مثال از متد On چطوری باید استفاده کنیم؟ توی فایل jquery.StarRating باید تغییرات بدیم یا توی اسکریپتی که داخل صفحه مینویسیم؟ 
مطالب
آموزش Knockout.Js #2
در پست قبلی با مفاهیم و ویژگی‌های کلی KO آشنا شدید. KO از الگوی طراحی MVVM استفاده می‌کند. از آن جا که یکی از پیش نیاز‌های KO آشنایی اولیه با مفاهیم View و Model است نیاز به توضیح در این موارد نیست اما اگر به هر دلیلی با این مفاهیم آشنایی ندارید می‌توانید از اینجا شروع کنید. اما درباره ViewModel که کمی مفهوم متفاوتی دارد، این نکته قابل ذکر است که KO از ViewModel برای ارتباط مستقیم بین View و Model استفاده می‌کند، چیزی شبیه به منطق MVC با این تفاوت که ViewModel به جای Controller قرار خواهد گرفت.

ابتدا باید به شرح برخی مفاهیم در KO بپردازم:
»Observable(قابل مشاهده کردن تغییرات)
KO از Observable برای ردیابی و مشاهده تغییرات خواص ViewModel استفاده می‌کند. در واقع Observable دقیقا شبیه به متغیر‌ها در JavaScript عمل می‌کنند با این تفاوت که به KO اجازه می‌دهند که تغییرات این خواص را پیگیری کند و این تغییرات را به بخش‌های مرتبط View اعمال نماید. اما سوال این است که KO چگونه متوجه می‌شود که این تغییرات بر کدام قسمت در View تاثیر خواهند داشت؟ جواب این سوال در مفهوم Binding است.
»Binding
برای اتصال بخش‌های مختلف View به Observable‌ها باید از binding(مقید سازی) استفاده کنیم. بدون عملیات binding، امکان اعمال تغییرات Observable‌ها بر روی عناصر HTML امکان پذیر نیست.
برای مثال در شکل زیر یکی از خواص ViewModel را به View متناظر مقید شده است.

با کمی دقت در شکل بالا این نکته به دست می‌آید که می‌توان در یک ViewModel، فقط خواص مورد نظر را به عناصر Html مقید کرد.

دانلود فایل‌های مورد نیاز

فایل‌های مورد نیاز برای KO رو می‌توانید از اینجا دانلود نمایید و به پروژه اضافه کنید. به صورت پیش فرض فایل‌های مورد نیاز KO، در پروژه‌های MVC 4 وجود دارد و نیاز به دانلود آن‌ها نیست و شما باید فقط مراحل BundleConfig را انجام دهید.

تعریف ViewModel

برای تعریف ViewModel و پیاده سازی مراحل Observable و binding باید به صورت زیر عمل نمایید:

<html lang='en'>
<head>
<title>Hello, Knockout.js</title>
<meta charset='utf-8' />
<link rel='stylesheet' href='style.css' />
</head>
<body>
<h1>Hello, Knockout.js</h1>
<script type='text/javascript' src='knockout-2.1.0.js'>   
      <script type='text/javascript'>
             var personViewModel = {
                  firstName: "Masoud",
                  lastName: "Pakdel"
                };
                 ko.applyBindings(personViewModel);
       </script>
</script>
</body>
</html>
مشاهده می‌کنید که ابتدا یک ViewModel به نام person ایجاد کردم همراه با دو خاصیت به نام‌های firstName و lastName. تابع applyBinding برای KO بدین معنی است که این آبجکت به عنوان یک ViewModel در این صفحه مورد استفاده قرار خواهد گرفت. اما برای مشاهده تغییرات باید یک عنصر HTML را یه این ViewModel مقید(bind) کنیم.

مقید سازی عناصر HTML

برای مقید سازی عناصر HTML به ViewModel‌ها باید از data-bind attribute استفاده نماییم. برای مثال:
<p><span data-bind='text: firstName'></span>'s Shopping Cart</p>
اگر به data-bind در تگ span بالا توجه کنید خواهید دید که مقدار text در این تگ را به خاصیت firstName در viewModel این صفحه bind شده است. تا اینجا KO می‌داند که چه عنصر از DOM به کدام خاصیت از ViewModel مقید شده است اما هنوز دستور ردیابی تغییرات(Observable) را برای KO تعیین نکردیم.

چگونه خواص را Observable کنیم
در پروژه‌های WPF، فقط در صورتی تغییرات خواص یک کلاس ردیابی می‌شوند که اولا کلاس اینترفیس INotifyPropertyChanged را پیاده سازی کرده باشد ثانیا، در متد set این خواص، متد OnPropertyChanged(البته این متد می‌تواند هر نام دیگری نیز داشته باشد) صدا زده شده باشد. نکته مهم و اساسی در KO نیز همین است که برای اینکه KO بتواند تغییرات هر خاصیت را مشاهده کند حتما خواص مورد نظر  باید Observable  شوند. برای این کار کافیست به صورت عمل کنید:
var personViewModel = {
  firstName: ko.observable("Masoud"),
  lastName: ko.observable("Pakdel")
};
مزیت اصلی برای اینکه حتما خواص مورد نظرتان  Observable شوند این است که، در صورتی که مایل نباشید تغییرات یک خاصیت  بر روی View اعمال شود کافیست از دستور بالا استفاده نکنید. درست مثل اینکه هرگز مقدار آن تغییر نکرده است.

پیاده سازی متد‌های get و set
همان طور که متوجه شدید، Observable‌ها متغیر نیستند بلکه تابع هستند در نتیجه برای دستیابی به مقدار یک observable کافیست آن را بدون پارامتر ورودی صدا بزنیم و برای تغییر در مقدار آن باید همان تابع را با مقدار جدید صدا بزنیم. برای مثال:
personViewModel.firstName() // Get
personViewModel.firstName("Masoud") // Set
البته این نکته را هم متذکر شوم که در ViewModel‌های خود می‌توانید توابع سفارشی مورد نیاز را بنویسید و از آن‌ها در جای مناسب استفاده نماید(شبیه به مفاهیم Command‌ها در WPF)
مقید سازی تعاملی
اگر با WPF آشنایی دارید می‌دانید که در این گونه پروژه‌ها می‌توان رویداد‌های مورد نظر را به Command‌های خاص در ViewModel مقید کرد. در KO نیز این امر به آسانی امکان پذیر است که به آن Interactive Bindings می‌گویند. فقط کافیست در data-bind attribute  از نام رویداد استفاده نماییم. مثال:
ایتدا بک ViewModel به صورت زیر خواهیم داشت:
function PersonViewModel() {
   this.firstName = ko.observable("Masoud");
   this.lastName = ko.observable("Pakdel");
   this.clickMe= function() {
    alert("this is test!");
  };
};
تنها نکته قابل ذکر تعریف تابع سفارشی به نام clickMe است که به نوعی معادل Command مورد نظر ما در WPF است.  در عنصر HTML مورد نظر که در این جا button است باید data-binding به صورت زیر باشد:
<button data-bind='click: clickMe'>Click Me...</button>
در نتیجه بعد از کلیک بر روی button بالا تابع مورد نظر در viewModel اجرا خواهد شد.
پس به صورت خلاصه:
  • ابتدا ViewModel مورد نظر را ایجاد نمایید؛
  • سپس با استفاده از data-bind عملیات مقید سازی بین View و ViewModel را انجام دهید
  • در نهایت با استفاده از Obsevable تغییرات خواص مورد نظر را ردیابی نمایید.

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