نظرات مطالب
Blazor 5x - قسمت یازدهم - مبانی Blazor - بخش 8 - کار با جاوا اسکریپت
من از فایل زیر استفاده میکنم که برای نمایش منو می‌باشد.
<script src="assets/js/core/app-menu.js"></script>
<script src="assets/js/core/app.js"></script>
در فایل app-menu.js نیاز به یک تگ divدر صفحه  هست ولی چون قبل از لود کامل صفحه فایل js اجرا می‌شود و تگ (menuToggle )div رو نمیشناسه چون هنوز ساخته نشده خطا میدهد.
نظرات مطالب
نحوه ایجاد یک اسلایدشو به صورت داینامیک
کاری که در اینجا انجام شده ساخت div زیر به صورت پویا است (بر اساس لیست تصاویر دریافت شده از سرور):
<div id="featured"> 
     <img src="img1.jpg" alt="img1" />
     <img src="img2.jpg" alt="img2" />
     <img src="img3.jpg" alt="img3" />
</div>
و سپس فراخوانی متد زیر روی آن:
$('#featured').orbit();

مطالب
React 16x - قسمت 11 - طراحی یک گرید - بخش 1 - کامپوننت صفحه بندی
در طی چند قسمت قصد داریم مثال قسمت قبل را که کار نمایش لیستی از فیلم‌ها را انجام می‌دهد، تبدیل به یک کامپوننت گرید کنیم که دارای امکانات صفحه بندی، فیلتر کردن و مرتب سازی اطلاعات است.


بررسی ساختار کامپوننت Pagination

شبیه به کامپوننت Like که در قسمت قبل ایجاد کردیم، می‌خواهیم کامپوننت جدید Pagination نیز به طور کامل از اشیاء movie مستقل باشد؛ تا در آینده بتوان از آن در جاهای دیگری نیز استفاده کرد. به همین جهت فایل جدید src\components\common\pagination.jsx را ایجاد کرده و سپس با استفاده از میانبرهای imrc و cc در VSCode، ساختار ابتدایی این کامپوننت را ایجاد می‌کنیم. هرچند می‌توان این کامپوننت را به صورت «Stateless Functional Component» نیز طراحی کرد؛ چون state و متد دیگری بجز render نخواهد داشت و تمام اطلاعات خودش را از والد خود دریافت می‌کند.
سپس به کامپوننت movies مراجعه کرده و این کامپوننت خالی را import می‌کنیم:
import Pagination from "./common/pagination";
پس از آن به متد رندر کامپوننت movies مراجعه کرده و پس از بسته شدن tag المان جدول، قصد داریم این کامپوننت صفحه بندی را نمایش دهیم. به همین جهت المان آن‌را در این محل قرار می‌دهیم تا بتوانیم اینترفیس ابتدایی آن‌را پیش از پیاده سازی آن، طراحی کنیم:
<Pagination
  itemsCount={this.state.movies.length}
  pageSize={this.state.pageSize}
  onPageChange={this.handlePageChange}
/>
این کامپوننت نیاز به تعداد کل آیتم‌های فیلم‌ها را دارد؛ به علاوه‌ی اندازه‌ی صفحه، یا همان تعداد ردیفی که قرار است در هر صفحه نمایش داده شود. به این ترتیب کامپوننت صفحه بندی می‌تواند تعداد صفحات مورد نیاز را محاسبه کرده و سپس آن‌ها را رندر کند.
  state = {
    movies: getMovies(),
    pageSize: 4
  };

به همین جهت دو ویژگی itemsCount و pageSize را پیش از هرکاری به تعریف المان صفحه بندی اضافه کرده‌ایم تا داده‌های ورودی آن مشخص شوند. ویژگی itemsCount از تعداد اعضای آرایه‌ی movies حاصل می‌شود و pageSize را به عنوان یک خاصیت جدید شیء منتسب به state تعریف و با عدد 4 مقدار دهی کرده‌ایم.
همچنین در لیست صفحاتی که توسط این کامپوننت رندر می‌شود، باید با کلیک بر روی هر کدام، اطلاعات آن صفحه رندر شود. به همین جهت در اینجا ویژگی onPageChange تعریف شده و به متد جدید handlePageChange در کامپوننت movies، متصل گردیده تا عدد page را دریافت کرده و به آن واکنش نشان دهد:
  handlePageChange = page => {
    console.log("handlePageChange", page);
  };
تا اینجا اینترفیس کامپوننت صفحه بندی را پیش از پیاده سازی آن تعریف کردیم (تعیین ورودی‌ها و خروجی آن). در مرحله‌ی بعد، این کامپوننت را تکمیل می‌کنیم.


نمایش شماره صفحات گرید، در کامپوننت صفحه بندی

برای رندر کامپوننت صفحه بندی، از کلاس‌های مخصوص اینکار که در بوت استرپ تعریف شده‌اند، استفاده می‌کنیم که ساختار کلی آن به صورت زیر است و از یک المان nav که داخل آن ul ای با کلاس pagination و liهایی با کلاس page-item هستند، تشکیل می‌شود. هر li، به همراه یک anchor است؛ با کلاس page-link تا لینک به صفحه‌ای خاص را ارائه دهد که در اینجا بجای لینک، از کلیک بر روی آن‌ها استفاده خواهیم کرد:
import React, { Component } from "react";

class Pagination extends Component {
  render() {
    return (
      <nav>
        <ul className="pagination">
          <li className="page-item">
            <a className="page-link">1</a>
          </li>
        </ul>
      </nav>
    );
  }
}

export default Pagination;

تا اینجا اگر برنامه را ذخیره کرده و اجرا کنید، عدد 1 را در پایین جدول فیلم‌ها مشاهده خواهید کرد:


اکنون باید رندر این liها را بر اساس ورودی‌های این کامپوننت که پیشتر معرفی کردیم، یعنی pageSize و itemsCount، پویا کنیم. به همین جهت نیاز به آرایه‌ای داریم که بر اساس این ورودی‌ها، شماره‌ی صفحات مانند [1,2,3] را ارائه دهد تا بر روی آن متد Array.map را فراخوانی کرده و liهای مورد نیاز را به صورت پویا رندر کنیم:
class Pagination extends Component {
  // ...
  getPageNumbersArray() {
    const { itemsCount, pageSize } = this.props;
    const pagesCount = Math.ceil(itemsCount / pageSize);
    if (pagesCount === 1) {
      return null;
    }

    const pages = new Array();
    for (let i = 1; i <= pagesCount; i++) {
      pages.push(i);
    }
    return pages;
  }
}
در اینجا بر اساس ورودی‌ها، تعداد صفحات محاسبه شده و سپس بر اساس آن‌ها آرایه‌ای از این شماره صفحه‌ها تشکیل و بازگشت داده می‌شود. همچنین اگر تعداد صفحات 1 بود، می‌خواهیم این کامپوننت چیزی را رندر نکند. به همین جهت در اینجا null بازگشت داده شده‌است. دلیل استفاده‌ی از Math.ceil که کوچکترین عدد صحیح بزرگتر یا مساوی خروجی را بازگشت می‌دهد، نیز همین مورد است. توسط این متد، خروجی float دریافتی به integer تبدیل شده و سپس قابلیت مقایسه‌ی با 1 را پیدا می‌کند. برای مثال اگر تعداد ردیف‌های صفحه را به 10 تنظیم کنیم، خروجی این تقسیم در این مثال، 0.9 خواهد بود که شرط بررسی pagesCount === 1 را برآورده نمی‌کند. به همین جهت توسط متد Math.ceil، این خروجی به عدد 1 تقریب زده شده و سبب بازگشت نال از این متد خواهد شد.
سپس به کمک متد map، اعضای این آرایه را تبدیل به لیست liهای نمایش شماره صفحات می‌کنیم. در اینجا key هر li را نیز به شماره صفحه که منحصربفرد است، تنظیم کرده‌ایم:
class Pagination extends Component {
  render() {
    const pages = this.getPageNumbersArray();
    if (!pages) {
      return null;
    }

    return (
      <nav>
        <ul className="pagination">
          {pages.map(page => (
            <li key={page} className="page-item">
              <a className="page-link">{page}</a>
            </li>
          ))}
        </ul>
      </nav>
    );
  }

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


با داشتن 9 فیلم در آرایه‌ی movies و نمایش 4 فیلم به ازای هر صفحه، به 3 صفحه خواهیم رسید که به درستی در اینجا رندر شده‌است. یکبار هم برای آزمایش بیشتر، مقدار pageSize را در کامپوننت movies به 10 تنظیم کنید. در این حالت کامپوننت صفحه بندی نباید رندر شود.


مدیریت انتخاب شماره‌های صفحات

در این قسمت می‌خواهیم مدیریت onPageChange={this.handlePageChange} را که به تعریف المان صفحه بندی در کامپوننت movies اضافه کردیم، تکمیل کنیم. برای این منظور در کامپوننت صفحه بندی، قسمت anchor را به صورت زیر تغییر می‌دهیم تا با کلیک بر روی آن، رخداد onPageChange صادر شود:
<a
   onClick={() => this.props.onPageChange(page)}
   className="page-link"
   style={{ cursor: "pointer" }}
>
   {page}
</a>

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

اکنون می‌خواهیم اگر صفحه‌ای انتخاب شد، شماره‌ی آن صفحه با رنگی دیگر نمایش داده شود. در بوت استرپ برای اینکار تنها کافی است کلاس active را به className هر li اضافه کنیم و برعکس. یعنی اگر page ای مساوی صفحه‌ی جاری انتخاب شده بود (currentPage در اینجا)، آنگاه کلاس page-item active، به المان li اضافه شود. بنابراین در این کامپوننت نیاز است عدد currentPage را نیز دریافت کنیم. به همین جهت ویژگی currentPage را به تعریف المان Pagination در کامپوننت movies اضافه می‌کنیم:
<Pagination
  itemsCount={this.state.movies.length}
  pageSize={this.state.pageSize}
  onPageChange={this.handlePageChange}
  currentPage={this.state.currentPage}
/>
این ویژگی نیز مقدار خودش را از state به روز شده دریافت می‌کند:
class Movies extends Component {
  state = {
    movies: getMovies(),
    pageSize: 4,
    currentPage: 1
  };
به روز رسانی state نیز در متد handlePageChange که به ویژگی onPageChange متصل است، بر اساس page دریافتی از کامپوننت صفحه بندی، رخ می‌دهد:
  handlePageChange = page => {
    console.log("handlePageChange", page);
    this.setState({currentPage: page});
  };
بنابراین هرگاه که بر روی یک شماره صفحه در کامپوننت صفحه بندی کلیک می‌شود، رخ‌داد onPageChange متصل به تعریف المان Pagination درج شده‌ی در کامپوننت movies، روی داده و به همراه آن شماره صفحه‌ای، به متد رخ‌دادگران متصل به آن در کامپوننت movies که در اینجا handlePageChange نام دارد، ارسال می‌شود. در این متد state کامپوننت به روز شده و این امر سبب فراخوانی مجدد متد رندر می‌شود که در انتهای آن کامپوننت Pagination درج شده‌است. بنابراین به روز رسانی state، سبب رندر مجدد کامپوننت صفحه بندی با currentPage جدیدی که به آن رسیده‌است، خواهد شد.

پس از این تغییرات، به کامپوننت صفحه بندی مراجعه کرده و بر اساس currentPage دریافتی، کلاس active را به المان li اعمال می‌کنیم:
<li
  key={page}
  className={
    page === this.props.currentPage
    ? "page-item active"
    : "page-item"
  }
>
پس از اعمال این تغییرات، اکنون برنامه در مرورگر به صورت زیر به نظر می‌رسد:


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


نمایش صفحه بندی شده‌ی اطلاعات

تا اینجا لیستی که نمایش داده می‌شود، حاوی تمام اطلاعات آرایه‌ی this.state.movies است و بر اساس شماره‌ی صفحه‌ی انتخابی، تغییر نمی‌کند. به همین جهت با استفاده از متد slice، تکه‌ای از آرایه‌ی movies را که بر اساس شماره صفحه‌ی انتخابی و تعداد ردیف‌ها در هر صفحه نیاز است نمایش داده شود، انتخاب کرده و بازگشت می‌دهیم:
  paginate() {
    const first = (this.state.currentPage - 1) * this.state.pageSize;
    const last = first + this.state.pageSize;
    return this.state.movies.slice(first, last);
  }
اکنون در متد رندر کامپوننت movies، بجای کار با کل آرایه‌ی this.state.movies، آرایه‌ی جدید slice شده را توسط متد paginate دریافت کرده:
  render() {
    const { length: count } = this.state.movies;

    if (count === 0) return <p>There are no movies in the database.</p>;

    const movies = this.paginate();
و سپس در همین متد رندر، فراخوانی this.state.movies.map را به movies.map تغییر می‌دهیم، تا تنها لیست مرتبط با هر صفحه‌ی انتخابی نمایش داده شود.

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



بررسی صحت نوع پارامترهای ارسالی به کامپوننت‌ها

تا اینجا فرض بر این است که مصرف کننده‌ی کامپوننت صفحه بندی، دقیقا همان ویژگی‌هایی را که ما طراحی کرده‌ایم، با همان نام‌ها و همان نوع‌ها را حتما به آن ارسال می‌کند. همچنین اگر افزونه‌ی eslint را هم در VSCode نصب کرده باشید، به همراه نصب وابستگی‌های زیر در خط فرمان:
> npm i -g typescript eslint tslint eslint-plugin-react-hooks jshint babel-eslint eslint-plugin-react eslint-plugin-mocha

 به ازای هر خاصیت props استفاده شده‌ی در کامپوننت صفحه بندی، اخطاری را مانند «'currentPage' is missing in props validation eslint(react/prop-types)» مشاهده خواهید کرد:



که عنوان می‌کند props validation این خاصیت استفاده شده، فراموش شده‌است.
در نگارش‌های قبلی React، امکانات بررسی نوع‌های ارسالی به کامپوننت‌ها، جزئی از بسته‌ی اصلی آن بود؛ اما از نگارش 15 به بعد، به بسته‌ی مستقلی منتقل شده‌است که باید به صورت جداگانه‌ای در ریشه‌ی پروژه نصب شود:
> npm i prop-types --save

البته اگر TypeScript را بر روی سیستم خود نصب کرده باشید، دیگر نیازی به نصب بسته‌ی npm فوق را ندارید و prop-types، جزئی از آن است که عموما در یک چنین مسیری قرار دارد و برای کار کردن با آن، تنها ذکر import مرتبط با PropType در ماژول‌های برنامه کافی بوده و برنامه در این حالت بدون مشکل کامپایل می‌شود:
 C:/Users/{username}/AppData/Local/Microsoft/TypeScript/3.6/node_modules/@types/prop-types/index

اکنون در ابتدای فایل کامپوننت صفحه بندی، تعریف زیر را اضافه می‌کنیم:
 import PropTypes from "prop-types";
سپس در انتهای این فایل، اعتبارسنجی props آن‌را تعریف خواهیم کرد:
Pagination.propTypes = {
  itemsCount: PropTypes.number.isRequired,
  pageSize: PropTypes.number.isRequired,
  currentPage: PropTypes.number.isRequired,
  onPageChange: PropTypes.func.isRequired
};

export default Pagination;
همانطور که مشاهده می‌کنید، در اینجا خاصیت جدید propTypes (دقیقا با همین نگارش؛ در غیراینصورت بررسی نوع‌ها کار نخواهد کرد)، به تعریف کلاس Pagination اضافه شده‌است (پس از تعریف کلاس کامپوننت به صورت مستقل اضافه می‌شود).
سپس مقدار این خاصیت جدید را به شیءای تنظیم می‌کنیم که نام خواص آن، دقیقا همان نام خواص و رویدادهای props استفاده شده‌ی در این کامپوننت است. در ادامه توسط PropTypes ای که در ابتدای ماژول import می‌شود، کار تعریف نوع این خواص و اجباری بودن آن‌ها را می‌توان مشخص کرد که برای مثال در اینجا سه خاصیت تعریف شده از نوع عددی و اجباری بوده و onPageChange، از نوع func است.
پس از اضافه کردن Pagination.propTypes و مقدار دهی آن، خطاهای eslint ای که در تصویر فوق مشاهده کردید، برطرف می‌شوند. همچنین اگر فراخوان کامپوننت Pagination مثلا بجای itemsCount یک رشته‌ی فرضی را وارد کند (برای آزمایش آن در کامپوننت movies، در تعریف المان Pagination، مقدار itemsCount را یک رشته وارد کنید)، چنین خطایی در مرورگر ظاهر خواهد شد که عنوان می‌کند itemsCount یک عدد را می‌پذیرد و نوع ورودی آن اشتباه است:

البته این خطا فقط در حالت development مشاهده می‌شود و در حالت توزیع برنامه، خیر.

بنابراین تعریف propTypes یک best practice ایجاد کامپوننت‌های React است که نه فقط بررسی نوع‌ها را فعال می‌کند، بلکه می‌تواند به عنوان مستندات آن نیز در جهت تعیین props مورد نیاز، همچنین نوع و اجباری بودن آن‌ها، اطلاعات کاملی را ارائه کند.


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: sample-11.zip
مطالب
Pipeها در Angular 2 - بخش اول
برای تغییر نحوه نمایش یک عبارت در رابط کاربری، از Pipe استفاده می‌شود. مثلا ممکن است تاریخ تولد به صورت میلادی از سرور دریافت شده باشد، می‌خواهیم بدون تغییری در متغیر حامل تاریخ میلادی و فقط در لایه رابط کاربری، کاربر تاریخ را به صورت شمسی مشاهده کند. به عبارت دیگر برای تغییر نحوه نمایش مقدار نمایشی (display-value) در صفحات HTML خود، از Pipe استفاده می‌شود. 

نحوه استفاده از Pipe

Pipe یک متغیر یا عبارت را به عنوان ورودی دریافت کرده و آن‌را به شکل دیگری برای نمایش تغییر می‌دهد. Pipeها معمولا در صفحات HTML مورد استفاده قرار می‌گیرند. با استفاده از عملگر Pipe (|) به شکل زیر می‌توانید Pipe مورد نظر خود را اعمال کنید.

import { Component } from '@angular/core';

@Component({
  selector: 'hero-birthday',
  template: `<p>The hero's birthday is {{ birthday | date }}</p>`
})
export class HeroBirthdayComponent {
  birthday = new Date(1988, 3, 15); // April 15, 1988
}

در این مثال Pipe از قبل ساخته شده date را بر روی متغییر birthday اعمال می‌کنیم. خروجی کار به شکل زیر خواهد بود.

The hero's birthday is April 15, 1988

لازم به ذکر است Pipe هیچگونه اثری بر روی متغییر birthday نداشته و فقط نحوه نمایش آن را تغییر می‌دهد.

 

Pipeهای از پیش ساخته شده

در انگیولار ۲ یکسری Pipe از پیش ساخته شده مانند DatePipe، UpperCasePipe، LowerCasePipe، CurrencyPipe و PercentPipe وجود دارند که شما در تمامی صفحات HTML می‌توانید بدون هیچ تنظیم اضافه‌ای از آنها استفاده کنید. لیست Pipeهای از پیش ساخته شده را اینجا مشاهده کنید. 


ارسال پارامتر به Pipe

  Pipeها در انگیولار ۲ می‌توانند تعدادی پارامتر ورودی را برای ارائه خروجی مدنظر دریافت کنند. برای فرستادن پارامتر به Pipe، بلافاصله بعد از نام Pipe با استفاده از دو نقطه (:) پارامتر مورد نظر را ارسال می‌کنیم (برای مثال 'currency:'EUR). درصورتیکه Pipe چند پارامتر را دریافت می‌کند، هر پارامتر با یک دو نقطه (:) از هم جدا می‌شوند؛ برای مثال slice:1:5.
  برای نمونه اگر بخواهیم تاریخ موجود در متغیر birthday در مثال قبل را به صورت «04/15/88» نمایش دهیم کافی است پارامتر «MM/dd/yy» را به datePipe ارسال کنیم.
<p>The hero's birthday is {{ birthday | date:"MM/dd/yy" }} </p>

مقدار پارامتر، هر نوع مجازی از Template expressions می‌تواند باشد (از جمله عبارت رشته‌ای یا حتی یک خصوصیت از کامپوننت). بنابراین در سرتاسر برنامه و در هر زمان می‌توانید با تغییر پارامتر در لحظه، خروجی مدنظر خود را به کاربرنهایی نمایش دهید.

 

Template expressions: عباراتی (expressions) که بعد از اجرا توسط انگیولار، تبدیل به یک مقدار جهت نمایش در HTML، کامپوننت یا دایرکتیو می‌شود.


 

استفاده زنجیره‌ای از Pipeها

برای اعمال چند Pipe بر روی یک عبارت، می‌توان از Pipeها به صورت پشت سر هم استفاده کرد. برای مثال در ادامه می‌خواهیم علاوه بر اعمال DatePipe با پارامتر fullDate جهت نمایش تاریخ به صورت Friday, April 15, 1988، حروف را نیز به صورت UpperCase نمایش دهیم. لازم به ذکر است برای نمایش حروف به صورت UpperCase از Pipe به همین نام استفاده می‌کنیم. 

<p>The hero's birthday is {{ birthday | date:"fullDate" | uppercase}} </p>

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

The hero's birthday is FRIDAY, APRIL 15, 1988


استفاده از Pipe در TypeScript

برای کسانیکه با انگیولار یک آشنایی دارند، Pipe در انگولار ۲ معادل filter در انگولار یک است. در انگیولار یک با تزریق سرویس filter$ به کنترلرها یا سرویس‌ها، می‌توانستیم filterهای تعریف شده شخصی و از پیش ساخته شده را جهت اعمال بر روی متغیر‌های خود، استفاده کنیم. در انگیولار دو نیز این امکان فراهم شده‌است؛ ولی به سادگی تزریق filter$ نیست. یعنی لازم است علاوه بر تزریق Pipe به سرویس یا کامپوننت‌های خود، Pipe مورد نظر خود را در لیست providers ماژول خود نیز اضافه کنید. برای نمونه اگر بخواهیم DatePipe را در component خود (نه در template) مورد استفاده قرار دهیم به شکل زیر عمل می‌کنیم:

import { Component } from '@angular/core';

// اضافه کردن DatePipe از @angular/common
import { DatePipe } from '@angular/common';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'app works!';
  birthDay = new Date(1988, 3, 15);
  strBirthDay = "";

// تزریق DatePipe
  constructor(private datePipe: DatePipe) {
    this.strBirthDay = this.datePipe.transform(this.birthDay, 'yyyy-MM-dd');
  }
}

پس از وارد کردن DatePipe از angular/common@ به کامپوننت و تزریق آن از طریق سازنده کامپوننت، برای اعمال Pipe بر روی عبارت مورد نظر خود، از متد transform استفاده می‌کنیم.

  تمامی Pipeها به واسطه پیاده سازی PipeTransform دارای متد transform هستند. این متد در اولین پارامتر خود، عبارتی را که قرار است Pipe بر روی آن اعمال شود، دریافت می‌کند. در صورتیکه Pipe مورد نظر دارای پارامتر باشد از طریق پارامتر دوم به بعد آنرا دریافت می‌کند.
همانطور که قبلا اشاره شد، علاوه بر تزریق Pipe در کامپوننت یا سرویس، Pipe باید در لیست providers ماژول نیز اضافه شود.
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';
import { DatePipe } from '@angular/common'
import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule
  ],
  // افزودن Pipe به Providers
  providers: [DatePipe],
  bootstrap: [AppComponent]
})
export class AppModule { }

نکته‌ای در مورد DatePipe و CurrencyPipe

Pipeهای Date و Currency به دلیل استفاده از شی Intl در داخل خود نیاز به ECMAScript Internationalization API دارند. مرورگرهای قدیمی و همچنین مرورگر Safari به دلیل عدم پشتیبانی از این قضیه به هنگام استفاده از این Pipeها دچار مشکل می‌شوند. برای حل این مشکل کافی است اسکریپت زیر را به صفحه خود اضافه کنید. 

<script src="https://cdn.polyfill.io/v2/polyfill.min.js?features=Intl.~locale.en"></script>

در بخش‌های بعدی نحوه ساخت Pipe‌های سفارشی و همچنین نکات تکمیلی در مورد Pipeها را بررسی خواهیم کرد.

نظرات مطالب
PersianDatePicker یک DatePicker شمسی به زبان JavaScript که از تاریخ سرور استفاده می‌کند
با سلام.
من از این کامپوننت برای پروژه MVC استفاده کردم و همه چیز عالی کار میکنه.سوال اینه که چه طوری میشه برای فیلد مورد نظر ValidationMessageFor رو اضافه کرد؟ در ViewModel مربوطه این فیلد Required در نظر گرفته شده ولی موقع submit هیچ client-side-validation ی اتفاق نمی‌افته. و فقط بعد از postback که ModelState.IsValid بررسی شد،از سمت سرور validate میشه. چه طور میشه client-side-validation  رو براش فعال کرد؟
با تشکر.
مطالب
CheckBoxList در ASP.NET MVC

ASP.NET MVC به همراه HtmlHelper توکاری جهت نمایش یک ChekBoxList نیست؛ اما سیستم Model binder آن، این نوع کنترل‌ها را به خوبی پشتیبانی می‌کند. برای مثال، یک پروژه جدید خالی ASP.NET MVC را آغاز کنید. سپس یک کنترلر Home جدید را نیز به آن اضافه کنید. در ادامه، برای متد Index آن، یک View خالی را ایجاد نمائید. سپس محتوای این View را به نحو زیر تغییر دهید:
@{
ViewBag.Title = "Index";
}
<h2>
Index</h2>
@using (Html.BeginForm())
{
<input type='checkbox' name='Result' value='value1' />
<input type='checkbox' name='Result' value='value2' />
<input type='checkbox' name='Result' value='value3' />
<input type="submit" value="submit" />
}

و کنترلر Home را نیز مطابق کدهای زیر ویرایش کنید:
using System.Web.Mvc;

namespace MvcApplication21.Controllers
{
public class HomeController : Controller
{
[HttpGet]
public ActionResult Index()
{
return View();
}

[HttpPost]
public ActionResult Index(string[] result)
{
return View();
}
}
}

یک breakpoint را در تابع Index دوم که آرایه‌ای را دریافت می‌کند، قرار دهید. سپس برنامه را اجرا کرده، تعدادی از checkboxها را انتخاب و فرم نمایش داده شده را به سرور ارسال کنید:


بله. همانطور که ملاحظه می‌کنید، تمام عناصر ارسالی انتخاب شده که دارای نامی مشابه بوده‌اند، به یک آرایه قابل بایند هستند و سیستم model binder می‌داند که چگونه باید این اطلاعات را دریافت و پردازش کند.
از این مقدمه می‌توان به عنوان پایه و اساس نوشتن یک HtmlHelper سفارشی CheckBoxList استفاده کرد.
برای این منظور یک پوشه جدید را به نام app_code، به ریشه پروژه اضافه نمائید. سپس یک فایل خالی را به نام Helpers.cshtml نیز به آن اضافه کنید. محتوای این فایل را به نحو زیر تغییر دهید:

@helper CheckBoxList(string name, List<System.Web.Mvc.SelectListItem> items)
{
<div class="checkboxList">
@foreach (var item in items)
{
@item.Text
<input type="checkbox" name="@name"
value="@item.Value"
@if (item.Selected) { <text>checked="checked"</text> }
/>
< br />
}
</div>
}

و برای استفاده از آن، کنترلر Home را مطابق کدهای زیر ویرایش کنید:

using System.Collections.Generic;
using System.Web.Mvc;

namespace MvcApplication21.Controllers
{
public class HomeController : Controller
{
[HttpGet]
public ActionResult Index()
{
ViewBag.Tags = new List<SelectListItem>
{
new SelectListItem { Text = "Item1", Value = "Val1", Selected = false },
new SelectListItem { Text = "Item2", Value = "Val2", Selected = false },
new SelectListItem { Text = "Item3", Value = "Val3", Selected = true }
};
return View();
}

[HttpPost]
public ActionResult GetTags(string[] tags)
{
return View();
}

[HttpPost]
public ActionResult Index(string[] result)
{
return View();
}
}
}

و در این حالت View برنامه به شکل زیر درخواهد آمد:
@{
ViewBag.Title = "Index";
}
<h2>
Index</h2>
@using (Html.BeginForm())
{

<input type='checkbox' name='Result' value='value1' />
<input type='checkbox' name='Result' value='value2' />
<input type='checkbox' name='Result' value='value3' />
<input type="submit" value="submit" />
}

@using (Html.BeginForm(actionName: "GetTags", controllerName: "Home"))
{
@Helpers.CheckBoxList("Tags", (List<SelectListItem>)ViewBag.Tags)
<input type="submit" value="submit" />
}

با توجه به اینکه کدهای Razor قرار گرفته در پوشه خاص app_code در ریشه سایت، به صورت خودکار در حین اجرای برنامه کامپایل می‌شوند، متد Helpers.CheckBoxList در تمام Viewهای برنامه در دسترس خواهد بود. در این متد، یک نام و لیستی از SelectListItemها دریافت می‌گردد. سپس به صورت خودکار یک CheckboxList را تولید خواهد کرد. برای دریافت مقادیر ارسالی آن به سرور هم باید مطابق متد GetTags تعریف شده در کنترلر Home عمل کرد. در اینجا Value عناصر انتخابی به صورت آرایه‌ای از رشته‌ها در دسترس خواهد بود.

روشی جامع‌تر
در آدرس زیر می‌توانید یک HtmlHelper بسیار جامع را جهت تولید CheckBoxList در ASP.NET MVC بیابید. در همان صفحه روش استفاده از آن، به همراه چندین مثال ارائه شده است:
https://github.com/devnoob/MVC3-Html.CheckBoxList-custom-extension