مطالب
React 16x - قسمت 29 - احراز هویت و اعتبارسنجی کاربران - بخش 4 - محافظت از مسیرها
در قسمت قبل، دکمه‌ی new movie را برای کاربران وارد نشده‌ی به سیستم، از صفحه‌ی نمایش لیست فیلم‌ها، مخفی کردیم. اما ... اگر آدرس http://localhost:3000/movies/new مستقیما در مرورگر وارد شود، هنوز هم برای عموم کاربران قابل دسترسی است.


روش محافظت از مسیریابی‌های تعریف شده‌ی در برنامه

شبیه به روشی را که در قسمت قبل، برای انتقال شیء user، به مسیریابی کامپوننت Movies استفاده کردیم:
<Route
     path="/movies"
     render={props => <Movies {...props} user={this.state.currentUser} />}
/>
در اینجا نیز می‌توان برای محافظت از یک مسیریابی، استفاده کرد. به همین جهت به app.js مراجعه کرده و مسیریابی فعلی کامپوننت MovieForm را:
<Route path="/movies/:id" component={MovieForm} />
به صورت زیر تغییر می‌دهیم:
<Route
  path="/movies/:id"
  render={props => {
    if (!this.state.currentUser) {
      return <Redirect to="/login" />;
    }
    return <MovieForm {...props} />;
  }}
/>
اینبار نیز بجای ویژگی component، از ویژگی render استفاده می‌کنیم تا بتوان در اینجا به صورت پویا، کدنویسی کرد. ابتدا بررسی می‌کنیم که آیا کاربر جاری تنظیم شده‌است؟ اگر خیر، او را به صفحه‌ی لاگین هدایت می‌کنیم؛ در غیراینصورت، همان کامپوننت MovieForm را به همراه تمام props مرتبط با آن، بازگشت می‌دهیم.

اکنون اگر این تغییرات را ذخیره کرده و در حالت Logout، مسیر http://localhost:3000/movies/new را مستقیما درخواست دهیم، به صفحه‌ی لاگین هدایت خواهیم شد.


ایجاد کامپوننتی با قابلیت استفاده‌ی مجدد، برای محافظت از مسیریابی‌ها

هرچند روشی که تا اینجا برای محافظت از مسیریابی‌ها معرفی شد، بدون مشکل کار می‌کند، اما اگر قرار باشد برای تمام مسیریابی‌های اینگونه، استفاده شود، به تکرار بیش از اندازه‌ی کدهای یکسانی خواهیم رسید. به همین جهت می‌توان این منطق را تبدیل به یک کامپوننت با قابلیت استفاده‌ی مجدد کرد؛ تا دیگر نیازی به تکرار این if/else‌ها نباشد. برای این منظور، فایل جدید src\components\common\protectedRoute.jsx را ایجاد می‌کنیم. کامپوننت جدید protectedRoute را هم در پوشه‌ی common قرار داده‌ایم؛ چون وابستگی به دومین این برنامه نداشته و می‌تواند در سایر برنامه نیز مورد استفاده قرار گیرد. سپس با استفاده از میانبرهای imrc و sfc، یک کامپوننت تابعی بدون حالت را به نام ProtectedRoute ایجاد کرده و در آن، همان کامپوننت اصلی Route را بازگشت می‌دهیم. بنابراین هر زمانیکه از ProtectedRoute استفاده شود، خروجی آن، همان کامپوننت استاندارد Route خواهد بود که اینبار قرار است از وضعیت کاربر جاری وارد شده‌ی به سیستم، مطلع باشد. به همین جهت در اولین قدم، همان قطعه کد Route فوق را که به همراه if/else نوشتیم، از فایل app.js کپی کرده و به اینجا، داخل متد رندر کامپوننت، منتقل می‌کنیم. سپس شروع می‌کنیم به متغیر کردن عباراتی که در آن به صورت صریح و ثابت، مقدار دهی شده‌اند تا به یک کامپوننت با قابلیت استفاده‌ی مجدد برسیم:
import React from "react";
import { Route, Redirect } from "react-router-dom";
import * as auth from "../../services/authService";

const ProtectedRoute = ({ path, component: Component, render, ...rest }) => {
  return (
    <Route
      {...rest}
      render={props => {
        if (!auth.getCurrentUser())
          return (
            <Redirect
              to={{
                pathname: "/login",
                state: { from: props.location }
              }}
            />
          );
        return Component ? <Component {...props} /> : render(props);
      }}
    />
  );
};

export default ProtectedRoute;
- در ابتدا بجای ذکر props بعنوان پارامتر این کامپوننت، از طریق Object Destructuring، خواصی را که قرار است به صورت props دریافت کنیم، مشخص کرده‌ایم. مزیت اینکار، مشخص شدن اینترفیس این کامپوننت به نحو واضحی است. برای مثال بجای ذکر مقدار ویژگی path، به صورت یک رشته‌ی ثابت، آن‌را از طریق یک متغیر دریافت می‌کنیم.
- در این کامپوننت نیاز است اطلاعات کاربر جاری وارد شده‌ی به سیستم در دسترس باشد. یا می‌توان آن‌را به عنوان یکی از خواص props دریافت کرد و یا همانند این مثال، امکان دریافت مستقیم آن از  authService نیز وجود دارد.
- در ادامه اگر CurrentUser مقدار دهی نشده باشد، کامپوننت Redirect را که کاربر را به صفحه‌ی لاگین هدایت می‌کند، بازگشت می‌دهیم. در غیراینصورت نیاز است یک کامپوننت را بجای برای مثال MovieForm، بازگشت دهیم. علت استفاده‌ی از component: Component این است که React انتظار دارد، کامپوننت‌ها با نام بزرگ شروع شوند. به همین جهت خاصیت component را از props دریافت کرده و آن‌را به Component تغییر نام می‌دهیم.
- زمانیکه از کامپوننت Route استاندارد استفاده می‌شود، یا از ویژگی component آن استفاده می‌شود و یا از ویژگی render آن که یک تابع است، تا بتوان داخل آن، کدهای پویایی را درج کرد. به همین جهت ممکن است که مقدار متغیر کامپوننت دریافت شده، نال باشد. بنابراین در اینجا بررسی می‌شود که آیا Component، مقدار دهی شده‌است یا خیر؟ اگر بله، همان کامپوننت را به همراه props آن بازگشت می‌دهیم. در غیراینصورت، متد render مقدار دهی شده را به همراه props ارسالی به آن، بازگشت خواهیم داد.
- علت وجود پارامتر rest نیز این است که این کامپوننت علاوه بر ویژگی‌هایی که تاکنون پیش بینی کرده‌ایم، ممکن است در آینده ویژگی‌های دیگری را نیز نیاز داشته باشد. به همین جهت مابقی آن‌ها را توسط {rest...}، به صورت خودکار در اینجا درج می‌کنیم. برای نمونه در اینجا ذکر path={path} را مشاهده نمی‌کنید؛ چون توسط همان {rest...} به صورت خودکار تامین می‌شود.

اکنون به app.js بازگشته و کدهای قبلی را با این کامپوننت جدید ProtectedRoute، جایگزین می‌کنیم:
import ProtectedRoute from "./components/common/protectedRoute";
// ...

<ProtectedRoute path="/movies/:id" component={MovieForm} />
اینبار نحوه‌ی تعریف ProtectedRoute، همانند نحوه‌ی تعریف کامپوننت Route استاندارد است؛ با این تفاوت که این کامپوننت در پشت صحنه، از وضعیت کاربر جاری سیستم مطلع است و بر اساس آن واکنش نشان می‌دهد.


مدیریت بازگشت کاربران، پس از لاگین به سیستم

پس از خروج از برنامه، اگر سعی در ویرایش یکی از فیلم‌های موجود کنیم، به صفحه‌ی لاگین هدایت خواهیم شد. پس از لاگین موفق، مجددا به ریشه‌ی سایت بازگشت داده می‌شویم و نه به صفحه‌ای که پیش از لاگین، مدنظر کاربر بوده‌است. برای رفع این مشکل نیاز است بتوان به آدرس قبلی درخواستی، دسترسی یافت و این مورد توسط سیستم مسیریابی، به کامپوننت‌ها به صورت خودکار تزریق می‌شود. برای مثال اگر در کامپوننت ProtectedRoute، مقدار شیء props دریافتی را لاگ کنیم:
  return (
    <Route
      {...rest}
      render={props => {
        console.log(props);
و سپس بر روی لینک به مشاهده‌ی جزئیات و ویرایش یک فیلم کلیک کنیم، تصویر زیر حاصل می‌شود:


همانطور که مشخص است، شیء location دریافتی از props، به همراه اطلاعات آدرسی است که پیش از هدایت خودکار به صفحه‌ی لاگین، درخواست کرده بودیم. به همین جهت یک چنین تنظیمی، در تعاریف کامپوننت ProtectedRoute درنظر گرفته شده‌اند:
<Redirect
              to={{
                pathname: "/login",
                state: { from: props.location }
              }}
            />
در کامپوننت Redirect، مقدار to می‌تواند یک رشته و یا یک شیء باشد. اگر حالت انتساب یک شیء را انتخاب کردیم، خاصیت pathname آن مانند قبل است و مکان نهایی Redirect را مشخص می‌کند. اما کار خاصیت state آن، ارسال اطلاعاتی اضافی است به کامپوننتی که قرار است کار Redirect به آن صورت گیرد. برای مثال در تنظیم فوق، شیء ای که دارای خاصیت from و با مقدار props.location است، به صورت خودکار به کامپوننت مقصد ارسال می‌شود.
اکنون که این شیء، به کامپوننت لاگین، پس از Redirect خودکار ارسال می‌شود، نیاز است به src\components\loginForm.jsx مراجعه کرده و تغییرات زیر را اعمال کنیم:
  doSubmit = async () => {
    try {
      const { data } = this.state;
      await auth.login(data.username, data.password);

      const { state } = this.props.location;
      window.location = state ? state.from.pathname : "/";
    } catch (ex) {
      //...
در اینجا خاصیت state، از شیء location تزریق شده‌ی به props این کامپوننت، استخراج می‌شود. سپس با مقدار دهی window.location به from.pathname آن، کار هدایت کاربر را پس از لاگین موفق، به آدرس قبلی مدنظر او، انجام می‌دهیم.

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


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

آخرین تغییری را که در اینجا اعمال خواهیم کرد، رفع مشکل امکان مشاهده‌ی مجدد صفحه‌ی لاگین، با وارد کردن مستقیم آدرس آن در مرورگر، پس از ورود موفقیت آمیز به سیستم است. برای این منظور، ابتدای متد رندر کامپوننت فرم لاگین را به صورت زیر تغییر می‌دهیم تا اگر کاربر، پیشتر به سیستم وارد شده بود، به صورت خودکار به ریشه‌ی سایت هدایت شده و مجددا فرم لاگین برای او رندر نشود:
import { Redirect } from "react-router-dom";
//...


  render() {
    if (auth.getCurrentUser()) return <Redirect to="/" />;


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: sample-29-backend.zip و sample-29-frontend.zip
مطالب
React 16x - قسمت 33 - React Hooks - بخش 4 - useContext Hook
در سری بررسی اعتبارسنجی و احراز هویت کاربران در React، برای انتقال داده‌های کاربر وارد شده‌ی به سیستم، از روش انتقال props، از بالاترین کامپوننت موجود در component tree، به پایین‌ترین کامپوننت آن، به این نحو فرضی استفاده کردیم:
ابتدا شیء user، در بالاترین سطح، دریافت شده و به صفحه‌ای خاص از طریق ویژگی‌های props ارسال می‌شود:
<Page user={user}  />
سپس این کامپوننت Page، کامپوننت PageLayout را رندر می‌کند که آن نیز باید به اطلاعات کاربر دسترسی داشته باشد. بنابراین شیء user را مجددا به این کامپوننت از طریق props ارسال می‌کنیم:
<PageLayout user={user} />
بعد همین کامپوننت PageLayout، کامپوننت NavBar را رندر می‌کند که آن نیز باید بداند کاربر وارد شده‌ی به سیستم کیست؟ به همین جهت یکبار دیگر از طریق props، اطلاعات کاربر را به کامپوننت بعدی موجود در درخت کامپوننت‌ها انتقال می‌دهیم:
<NavigationBar user={user}  />
و همینطور الی آخر. به این روش props drilling گفته می‌شود و ... الگوی مذمومی است. در دنیای واقعی، اطلاعات کاربر و یا خصوصا تنظیمات برنامه مانند آدرس REST API endpoints استفاده شده‌ی در آن، باید بین بسیاری از کامپوننت‌ها به اشتراک گذاشته شود و عموما سطوح به اشتراک گذاری آن، بسیار عمیق‌تر است از سطوحی که در این مثال ساده عنوان شدند. از زمان ارائه‌ی React 16.3.0، راه حل بهتری برای مدیریت اینگونه مسایل با ارائه‌ی React Context ارائه شده‌است که آن‌را در ادامه در دو حالت کامپوننت‌های کلاسی و همچنین تابعی، بررسی خواهیم کرد.


ایجاد شیء Context در برنامه‌های React

React Context، راه حلی است جهت به اشتراک گذاری داده‌ها، در بین انواع و اقسام کامپوننت‌های یک برنامه، بدون اینکه نیازی باشد این اطلاعات را توسط props، از یک سطح، به سطحی دیگر، به صورت دستی انتقال داد. برای ایجاد یک نمونه‌ی از آن، ابتدا پوشه‌ی جدید src\contexts را افزوده و سپس فایل src\contexts\userContext.js را درون آن، با محتوای زیر ایجاد می‌کنیم:
import React from "react";

export const UserContext = React.createContext({ user: {} });

export const UserProvider = UserContext.Provider;
export const UserConsumer = UserContext.Consumer;
متد React.createContext، یک شیء Context را بازگشت می‌دهد. این شیء، دو کامپوننت مهم Provider و Consumer را به همراه دارد که امکان اشتراک به داده‌های مرتبط با آن‌را میسر می‌کنند. زمانیکه React کامپوننتی را رندر می‌کند که مشترک یک شیء Context است، این کامپوننت، امکان خواندن اطلاعات شیء Context را از نزدیک‌ترین کامپوننتی در درخت کامپوننت‌ها که یک Provider را برای آن ارائه داده‌است، خواهد داشت.


تامین یک شیء Context در برنامه، در یک کامپوننت کلاسی و یا تابعی

تا اینجا یک شیء Context را به همراه اجزای export شده‌ی Provider و Consumer آن ایجاد کردیم. اکنون نوبت به پیاده سازی قسمت Provider آن است:
import "../../App.css";

import React, { Component } from "react";

import { UserProvider } from "../../contexts/userContext";
import Main from "./Main";

class App extends Component {
  state = {
    user: { name: "User 1" }
  };

  componentDidMount() {
    // get user from the server or local storage and then set the currently logged in user to the this.state
  }

  render() {
    return (
      <>
        <h1>App Class</h1>
        <UserProvider value={this.state.user}>
          <Main />
        </UserProvider>
      </>
    );
  }
}

export default App;
در این کامپوننت کلاسی (و یا تابعی، نحوه‌ی تعریف UserProvider در هر دو یکی است)، خاصیت user، به state کامپوننت اضافه شده‌است. سپس برای مثال می‌توان این خاصیت را در رویداد componentDidMount از سرور و یا محل ذخیره سازی دیگری دریافت و آنگاه state را بر این اساس به روز رسانی کرد.
در ادامه قصد داریم اطلاعات این شیء user موجود در state را با تمام کامپوننت‌هایی که در درخت رندر کامپوننت جاری قرار می‌گیرند و با کامپوننت Main شروع می‌شوند، به اشتراک بگذاریم. این به اشتراک گذاری با import شیء UserProvider از ماژول contexts/userContext به نحوی که مشاهده می‌کنید، انجام می‌شود. شیء UserProvider، کار محصور سازی کامپوننت Main را انجام می‌دهد. سپس این Provider می‌تواند مقداری را توسط ویژگی value خود دریافت کند که برای مثال در اینجا شیء user است. اکنون این value تا n سطح بعدی که از کامپوننت Main مشتق می‌شوند نیز در دسترس خواهد بود.

یک نکته: متد React.createContext به همراه یک آرگومان defaultValue اختیاری است که در اختیار Consumerهای آن قرار داده می‌شود؛ اگر Provider متناظر با آن‌، در درخت کامپوننت‌های برنامه، یافت نشود. یعنی تعریف Provider الزامی نیست. اگر نیاز است مقدار ثابتی را بین چندین کامپوننت به اشتراک بگذارید، فقط کافی است آن‌ها را توسط React.createContext مقدار دهی اولیه کرده و ... استفاده کنید:
export const DefaultRouteContext = React.createContext({ path: '/welcome' });


خواندن شیء Context در کامپوننتی دیگر

اکنون که یک تامین کننده‌ی Context را ایجاد کردیم، برای خواندن اطلاعات آن در درخت کامپوننت‌های محصور شده‌ی توسط UserProvider، می‌توان به صورت زیر عمل کرد:
import React from "react";

import { UserConsumer } from "../../contexts/userContext";

export default function Main(props) {
  return (
    <>
      <UserConsumer>
        {value => <div>User name: {value.name}.</div>}
      </UserConsumer>
    </>
  );
}
ابتدا UserConsumer را از ماژول contexts/userContext دریافت می‌کنیم. سپس برای دسترسی به خاصیت name شیء ارائه شده‌ی توسط UserProvider، باید قسمتی از متد رندر کامپوننت را توسط شیء UserConsumer، محصور کرد و سپس value آن‌را به نحوی که مشاهده می‌کنید، خواند. Consumer، یک تابع را به عنوان فرزند دریافت می‌کند. این تابع مقدار شیء تامین شده‌ی توسط Context را دریافت کرده (همان value={this.state.user} نزدیک‌ترین کامپوننتی که به همراه یک Provider است) و سپس یک المان React را بازگشت می‌دهد که در این محل رندر خواهد شد.

خروجی برنامه پس از این تغییرات به صورت زیر است:



ساده سازی دسترسی به UserConsumer توسط useContext Hook

نحوه‌ی تعریف یک Provider و محصور سازی فرزندانی که باید از آن ارث‌بری کنند، در بین کامپوننت‌های کلاسی و تابعی، یکی است. اما در کامپوننت‌های تابعی حداقل می‌توان نحوه‌ی دسترسی به UserConsumer را به نحو زیر توسط useContext Hook ساده کرد:
import React, { useContext } from "react";

import { UserContext } from "../../contexts/userContext";

export default function Main() {
  const value = useContext(UserContext);
  return (
    <>
      <div>User name: {value.name}.</div>
    </>
  );
}
متد useContext ابتدا شیء UserContext مهیا شده‌ی توسط ماژول contexts/userContext را دریافت می‌کند. سپس خروجی آن، همان value تنظیم شده‌ی توسط نزدیک‌ترین Provider آن در component tree است. این روش، بار ذهنی کمتری را نسبت به حالت قبلی استفاده‌ی از UserConsumer و کار با یک تابع درون آن‌را به همراه دارد؛ ساده‌تر خوانده می‌شود، ساده‌تر استفاده می‌شود. فقط باید دقت داشت که این متد، کل شیء Context را دریافت می‌کند و نه فقط شیء UserConsumer آن‌را.

مزیت دیگر این روش، ساده سازی کار با چندین شیء Context است. برای مثال اگر دو شیء Context را تعریف کرده باشید، خواندن دو مقدار از آن‌ها، پیشتر چنین شکل تو در تویی را توسط دو Consumer پیدا می‌کرد:
function HeaderBar() {
  return (
    <CurrentUser.Consumer>
      {user =>
        <Notifications.Consumer>
          {notifications =>
            <header>
              Welcome back, {user.name}!
              You have {notifications.length} notifications.
            </header>
          }
      }
    </CurrentUser.Consumer>
  );
}
اما اکنون با استفاده از useContext، نوشتن و خواندن آن به سادگی چند سطر زیر است که بسیار منطقی‌تر و عادی‌تر به نظر می‌رسد:
function HeaderBar() {
  const user = useContext(CurrentUser);
  const notifications = useContext(Notifications);
return (
    <header>
      Welcome back, {user.name}!
      You have {notifications.length} notifications.
    </header>
  );
}


ارسال اطلاعات به کامپوننت Context Provider، از طریق کامپوننت‌های فرزند

تا اینجا با استفاده از React Context، اطلاعات یک Provider را با فرزندان آن به اشتراک گذاشتیم؛ عکس این عمل نیز میسر است. برای اینکار، همانند تمام کامپوننت‌های دیگری که برای ارسال اطلاعات به فراخوان خود از طریق رخ‌دادها عمل می‌کنند، می‌توان یک متد رویدادگردان را در کامپوننت والد، به استفاده کنند‌ه‌ی از Context ارسال کرد:
import "../../App.css";

import React, { Component } from "react";

import { UserProvider } from "../../contexts/userContext";
import Main from "./Main2";

class App extends Component {
  state = {
    user: { name: "User 1" }
  };

  componentDidMount() {
    // get user from the server or local storage and then set the currently logged in user to the this.state
  }

  logout = () => {
    console.log("logout");
    this.setState({ user: {} });
  };

  render() {
    const contextValue = {
      user: this.state.user,
      logoutUser: this.logout
    };
    return (
      <>
        <h1>App Class</h1>
        <UserProvider value={contextValue}>
          <Main />
        </UserProvider>
      </>
    );
  }
}

export default App;
در اینجا ابتدا به خاصیت logout، متدی را نسبت داده‌ایم که با فراخوانی آن، اطلاعات شیء user موجود در state کامپوننت جاری را پاک می‌کند. سپس این خاصیت را به صورت یک خاصیت جدید، به شیءای که به ویژگی value شیء UserProvider انتساب داده شده، اضافه می‌کنیم.
اکنون تمام استفاده کننده‌های از این UserProvider می‌توانند با فراخوانی متد منتسب به logout، سبب پاک شدن اطلاعات کاربر موجود در state کامپوننت App، به روز رسانی state و در نتیجه‌ی آن، رندر مجدد کامپوننت و ارائه‌ی یک UserProvider جدید، با اطلاعاتی جدید به فرزندان آن شوند:
import React, { useContext } from "react";

import { UserContext } from "../../contexts/userContext";

export default function Main() {
  const { user, logoutUser } = useContext(UserContext);
  return (
    <>
      <div>User name: {user.name}.</div>
      <button type="button" className="btn btn-primary" onClick={logoutUser}>
        Logout user
      </button>
    </>
  );
}
در این کامپوننت مصرف کننده‌ی Context، اینبار، مقدار دریافتی، یک شیء با چندین خاصیت است. بنابراین می‌توان با استفاده از Object Destructuring، خواص آن‌را استخراج و استفاده کرد. برای مثال با انتساب onClick={logoutUser} به دکمه‌ی خروج، این کامپوننت می‌تواند اطلاعات state و سپس Context ارائه شده‌ی در کامپوننت App را تغییر دهد.

روش انجام اینکار بدون استفاده از useContext را نیز در ادامه مشاهده می‌کنید که در ابتدا نیاز به تعریف تابعی را دارد که همان خواص استخراجی را دریافت می‌کند. سپس باید بر اساس آن‌ها، المان‌های مدنظر نمایش نام کاربر و دکمه‌ی خروج او را بازگشت داد:
import React from "react";

import { UserConsumer } from "../../contexts/userContext";

export default function Main(props) {
  return (
    <>
      <UserConsumer>
        {({ user, logoutUser }) => (
          <>
            <div>User name: {user.name}.</div>
            <button
              type="button"
              className="btn btn-primary"
              onClick={logoutUser}
            >
              Logout user
            </button>
          </>
        )}
      </UserConsumer>
    </>
  );
}


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: sample-30-part-04.zip
مطالب
Zen Coding در Visual Studio 2012
Zen Coding روشی سریع برای نوشتن کدهای HTML با استفاده از گرامر selector‌های CSS است. این روش برای اولین بار در سال 2009 توسط Sergey Chikuyonok  معرفی شد و در طول این مدت، تبدیل به روشی عالی برای نوشتن کد‌های HTML تکراری و یکنواخت شده است.
برای استفاده از این روش در ویژوال استادیو 2012، احتیاج به نصب افزونه‌ی محبوب و پر طرفدار Web Essentials است. این افزونه که توسط  Mads Kristensen  توسعه پیدا کرده است،؛ علاوه بر Zen Coding ، ویژگی‌های بسیار زیادی در زمینه‌ی توسعه وب به ویژوال استادیو اضافه می‌کند و توصیه‌ی اکید دارم که حتما این افزونه‌ی فوق العاده را نصب کنید و از امکانات دیگر آن بهره برید. 
گرامر Zen Coding، یک سری عملگر دارد که مرجعی از همه‌ی آن‌ها را در زیر مشاهده می‌کنید.
#   ایجاد خاصیت id 
.    ایجاد خاصیت class
[]  ایجاد خاصیت دلخواه
<  ایجاد عنصر فرزند
+  ایجاد عنصر برادر یا خواهر (Sibling)
^  رجوع به والد
*  تکثیر کننده‌ی عنصر است. هر عنصری را می‌تواند n بار تکرار کند
$  با شمارنده جایگزین می‌شود
$$  با شمارنده دارای padding جایگزین می‌شود
{}  متن داخل آکولاد را در عنصر مورد نظر قرار می‌دهد
()  امکان گروه بندی عبارات را می‌دهد.
lorem  اطلاعات ساختگی برای آزمایش برنامه ایجاد می‌کند
مشخص است که بسیاری از عملگر‌ها با selector‌های CSS یکسان اند. قبل از اینکه به بررسی دقیق‌تر هر کدام از این عملگر‌ها بپردازیم، دو مثال را با هم بررسی می‌کنیم تا به قدرت آن پی ببریم.
 پس از نصب افزونه‌ی Web Essentials ، یک صفحه‌ی html یا cshtml و یا هر قسمتی که بتوان در آن کد html  نوشت را گشوده و سپس کد زیر را در آن بنویسید و در نهایت کلید Tab را فشار دهید. 
table#tblUsers.table.table-striped.table-bordered.table-hover>thead>tr>th{row}+th{Name}+th{Last Name}+th{Operations}^^tbody>tr*6>(td{row}+td{FirstName}+td{LastName}+td>button.btn.btn-primary{Edit}+button.btn.btn-danger{Delete})
پس از فشردن کلید Tab، خروجی زیر را مشاهده خواهید کرد: 
<table id="tblUsers" class="table table-striped table-bordered table-hover">
        <thead>
            <tr>
                <th>row</th>
                <th>Name</th>
                <th>Last Name</th>
                <th>Operations</th>
            </tr>
        </thead>
        <tbody>
            <tr>
                <td>row</td>
                <td>FirstName</td>
                <td>LastName</td>
                <td>
                    <button>Edit</button>
                    <button>Delete</button></td>
            </tr>
            <tr>
                <td>row</td>
                <td>FirstName</td>
                <td>LastName</td>
                <td>
                    <button class="btn btn-primary">Edit</button>
                    <button class="btn btn-danger">Delete</button></td>
            </tr>
            <tr>
                <td>row</td>
                <td>FirstName</td>
                <td>LastName</td>
                <td>
                    <button class="btn btn-primary">Edit</button>
                    <button class="btn btn-danger">Delete</button></td>
            </tr>
            <tr>
                <td>row</td>
                <td>FirstName</td>
                <td>LastName</td>
                <td>
                    <button class="btn btn-primary">Edit</button>
                    <button class="btn btn-danger">Delete</button></td>
            </tr>
            <tr>
                <td>row</td>
                <td>FirstName</td>
                <td>LastName</td>
                <td>
                    <button class="btn btn-primary">Edit</button>
                    <button class="btn btn-danger">Delete</button></td>
            </tr>
            <tr>
                <td>row</td>
                <td>FirstName</td>
                <td>LastName</td>
                <td>
                    <button class="btn btn-primary">Edit</button>
                    <button class="btn btn-danger">Delete</button></td>
            </tr>
        </tbody>
    </table>
به نظر پیچیده می‌آید و حتما با خودتان می‌گویید که همان HTML خام را بنویسم، راحت‌تر هستم. شاید الان برای شما پیچیده به نظر آید ولی اگر تا انتهای این مقاله را مطالعه کنید، این قول را به شما می‌دهم که برای شما نوشتن این گونه کدها بسیار ساده خواهد بود و بعید به نظر می‌آید که شما دیگر HTML را به شیوه‌های قدیمی بنویسید، همان طور که بنده نیز با چند بار آزمون و خطا، توانستم کد بالا را بنویسیم.
یک مثال دیگر می‌تواند به این شکل باشد:
ul>li[ng-repeat="user in Users"]>p[ng-bind="{{user.UserName}}"]+a{Details}
که خروجی زیر را در بر دارد:
<ul>
        <li ng-repeat="user in Users">
            <p ng-bind="{{user.UserName}}"></p>
            <a href="">Details</a>
        </li>
</ul>
از کدهای بالا این نتیجه را می‌توان گرفت که Zen Coding؛ بسیار انعطاف پذیر، ساده و قدرتمند است.
در ادامه نگاهی دقیق‌تر به عملگرهای استفاده شده در کدهای بالا می‌اندازیم.
 
خواص id (#) و class (.)
به مانند گرامر CSS، به راحتی می‌توانید id یا class خاصی را به عنصر مورد نظر نسبت دهید:
<!-- Type this -->
div.container

<!-- Creates this -->
<div class="container"></div>
<!-- Type this -->
ul#userList

<!-- Creates this -->
<ul id="userList"></ul>
<!-- Type this -->
div.container#wrapper

<!-- Creates this -->
<div class="container" id="wrapper"></div>
<!-- Type this -->
button.btn.btn-primary

<!-- Creates this -->
<button class="btn btn-primary"></button>
نکته: اگر شما عنصر مورد نظری را که می‌خواهید بر روی آن کلاس و یا آیدی را اعمال کنید، مشخص نکنید به طور پیش فرض، Zen Coding آن عنصر را div در نظر می‌گیرد.
مثال:
<!-- Type this -->
.container

<!-- Creates this -->
<div class="container"></div>
< عناصر فرزند (Child Elements)
با استفاده از عملگر (<)، عنصر مورد نظر و عناصر فرزند درون آن را می‌توانید ایجاد کنید.
<!-- Type this -->
div.container>div#header

<!-- Creates this -->
<div class="container">
        <div id="header"></div>
</div>
<!-- Type this -->
div.navbar>div.navbar-inner>ul.navbar

<!-- Creates this -->
<div class="navbar">
        <div class="navbar-inner">
            <ul class="navbar"></ul>
        </div>
</div>

[] خواص سفارشی (Custom Attributes)
با نوشتن خاصیت سفارشی خود درون براکت []، به راحت می‌توانید آن عنصر و خاصیت سفارشی مورد نظر خود را ایجاد کنید.
<!-- Type this -->
div[title]

<!-- Creates this -->
<div title=""></div>
و یا  اگر بخواهید چندین خاصیت را به صورت همزمان اعمال کنید:
<!-- Type this -->
input[type="text" placeholder="First Name"]

<!-- Creates this -->
<input type="text" value="" placeholder="First Name" />
<!-- Type this -->
ul[ng-repeat="user in Users"]>li[ng-model="user.FirstName"]

<!-- Creates this -->
<ul ng-repeat="user in Users">
        <li ng-model="user.FirstName"></li>
</ul>

{} متن (Text)

این عملگر، متن مورد نظر شما را داخل عنصر قرار می‌دهد.

<!-- Type this -->
div>h1{My Title}

<!-- Creates this -->
<div>
      <h1>My Title</h1>
</div>

+ عناصر خواهر و برادر (Siblings Elements)
با قرار دادن عملگر + ، عناصر خواهر و برادر را ایجاد کنید.
<!-- Type this -->
form>input[type="text"]+input[type="checkbox"]

<!-- Creates this -->
<form>
        <input type="text" value="" />
        <input type="checkbox" value="" />
</form>
<!-- Type this -->
div#header>img+h1{Title}

<!-- Creates this -->
<div id="header">
        <img src="" alt="" />
        <h1>Title</h1>
</div>

 ^  بالا رفتن از عناصر (Climbing Elements)
این عملگر تقریبا برعکس عملگر < عمل می‌کند. با عملگر ^ شما می‌توانید به والد یک عنصر تا هر تعداد سطحی که بخواهید برسید. اگر شما به طور مثال بخواهید که دو سطح به والد بالاتر برگردید، باید از ^^ استفاده کنید. 
<!-- Type this -->
table>thead>tr>th{row}^^tbody>tr>td{row1}

<!-- Creates this -->
<table>
        <thead>
            <tr>
                <th>row</th>
            </tr>
        </thead>
        <tbody>
            <tr>
                <td>row1</td>
            </tr>
        </tbody>
</table>

* تکثیر عناصر (Elements Multiplication)
با عملگر *، هر تعداد عنصر را که می‌خواهید تکثیر کنید.
<!-- Type this -->
ul>li*3>p{Hello}

<!-- Creates this -->
<ul>
        <li>
            <p>
                Hello
            </p>
        </li>
        <li>
            <p>
                Hello
            </p>
        </li>
        <li>
            <p>
                Hello
            </p>
        </li>
</ul>

<!-- Type this -->
(div.container>h1{title}+div.content{Some Text Here})*2

<!-- Creates this -->
<div class="container">
        <h1>title</h1>
        <div class="content">
            Some Text Here
        </div>
    </div>
    <div class="container">
        <h1>title</h1>
        <div class="content">
            Some Text Here
        </div>
</div>
$ شماره گذاری آیتم‌ها (Item Numbering)
هنگامی که شما با کمک *، از روی یک عنصر n عنصر را تکثیر می‌کنید، می‌توانید با استفاده ازعملگر $ ، به عدد متناظر با آن حلقه‌ی تکرار دست پیدا کنید.
<!-- Type this -->
ul>li*2>p{item $}

<!-- Creates this -->
<ul>
        <li>
            <p>
                item 1
            </p>
        </li>
        <li>
            <p>
                item 2
            </p>
        </li>
</ul>
نکته: استفاده از چند عملگر $، باعث قرارگرفتن همان تعداد صفر، پیش از عدد می‌شود.
<!-- Type this -->
ul>li*2>p{item $$$}

<!-- Creates this -->
<ul>
        <li>
            <p>
                item 001
            </p>
        </li>
        <li>
            <p>
                item 002
            </p>
        </li>
</ul>
() گروه بندی (Grouping)
گروه بندی یکی از امکانات فوق العاده‌ی Zen Coding است که به شما امکان نوشتن عبارات پیچیده را می‌دهد. کاربرد آن در مثال زیر کاملا واضح است، و به راحتی با یک عبارت، کل ساختار Dom را پیاده سازی شده است. 
<!-- Type this -->
div>(header>div)+section>(ul>li*2>a)+footer>(div>span)

<!-- Creates this -->
<div>
        <header>
            <div></div>
        </header>
        <section>
            <ul>
                <li><a href=""></a></li>
                <li><a href=""></a></li>
            </ul>
            <footer>
                <div>
                    <span></span>
                </div>
            </footer>
        </section>
</div>
Lorem داده‌های ساختگی ( Dummy Data) 
حتما بارها و بارها این اتفاق افتاده است که برای تست قالب برنامه‌ی خود، آن را با داده‌های ساختگی پر کرده باشید. معمولا شما این کار را به صورت دستی انجام می‌دادید. اما این بار عبارت lorem را نوشته و کلید Tab را فشار دهید. به طور پیش فرض برای شما 30 کلمه می‌نویسد. اگر تعداد مشخصی کلمه می‌خواهید کافی است تعداد آن را جلوی کلمه‌ی lorem بنویسید. برای مثال lorem4.
حتی از lorem می‌توانید در عبارات Zen Coding استفاده کنید: 
<!-- Type this -->
div>(h1>lorem5)+(h3>lorem3)

<!-- Creates this -->
<div>
      <h1>Lorem ipsum dolor sit amet.</h1>
      <h3>Lorem ipsum dolor.</h3>
</div>

مطالب
React 16x - قسمت 1 - معرفی و شروع به کار
React یک کتابخانه‌ی جاوا اسکریپتی، برای ساخت رابط‌های کاربری سریع و تعاملی است. توسعه‌ی آن از سال 2011 در فیسبوک شروع شد و در حال حاضر محبوب‌ترین کتابخانه‌ی جاوا اسکریپتی در این رده‌است:


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


ساختار کلی یک برنامه‌ی React

کامپوننت‌ها (جزئی از یک رابط کاربری) قلب هر برنامه‌ی React ای را تشکیل می‌دهند. برای ساخت یک برنامه‌ی React، تعدادی کامپوننت مستقل را تهیه و با هم ترکیب می‌کنیم تا به رابط کاربری نهایی برسیم.
هر برنامه‌ی React، حداقل از یک کامپوننت تشکیل می‌شود که به آن Root component هم می‌گویند. این کامپوننت بیانگر کل برنامه‌است و دربرگیرنده‌ی مابقی Child components برنامه است. بنابراین ساختار هر برنامه‌ی React، شبیه به درختی از کامپوننت‌ها است. اگر با Angular 2 به بعد کار کرده باشید، این مفهوم برای شما آشنا است.
یک مثال: فرض کنید می‌خواهیم UI برنامه‌ای را به مانند رابط کاربری Twitter، ایجاد کنیم. هر قسمت یک صفحه‌ی توئیتر، به کامپوننت‌هایی شکسته می‌شود؛ مانند منوی راهبری، نمایش پروفایل شخص، نمایش لیست آخرین اخبار مورد علاقه‌ی شخص و نمایش فید. اگر بخواهیم این ساختار را توسط یک برنامه‌ی React شبیه سازی کنیم، در بالاترین سطح، کامپوننت root را خواهیم داشت که کار ترکیب و نمایش سایر کامپوننت‌های برنامه مانند nav bar ، trends ، profile و feed را انجام می‌دهد. اکنون در این ساختار ایجاد شده، برای مثال کامپوننت feed نیز می‌تواند از چندین کامپوننت مجزا تشکیل شود؛ مانند کامپوننت‌های tweet و like.
بنابراین هر کامپوننت، قسمتی از UI را تشکیل می‌دهد. هر کدام از آن‌ها به صورت مجزای از دیگری ساخته شده و سپس در کنار هم قرار می‌گیرند تا UI نهایی را شکل دهند:



هر کامپوننت در React به صورت یک کلاس ES6، با ساختاری که دارای یک شیء state و متد render است، تشکیل می‌شود:
class Tweet {
 state = {};
 
 render() {
 } 
}
state در اینجا همان اطلاعاتی است که قرار است در زمان نمایش این کامپوننت، رندر شود. کار متد render نیز همانطور که از نام آن نیز مشخص است، بیان نحوه‌ی تشکیل و رندر UI است. خروجی این متد، یک React Element است که در حقیقت یک شیء جاوا اسکریپتی خالص است و در نهایت به المان‌های DOM، نگاشت می‌شود. یک React Element، یک DOM Element واقعی نیست؛ بلکه تنها یک شیء جاوا اسکریپتی بیانگر DOM Element، در حافظه‌است. بنابراین یک برنامه‌ی React تشکیل شده‌است از لیستی از React Elementها در حافظه که به آن Virtual DOM هم گفته می‌شود.
مزیت کارکردن با Virtual DOM، سادگی ایجاد، تغییر و به روز رسانی آن در مقایسه با DOM واقعی است که در نهایت کار رندر عناصر UI را در مرورگر انجام می‌دهد. زمانیکه در state کامپوننتی تغییری رخ می‌دهد، یک React Element جدید تولید می‌شود. سپس React این شیء جدید را با نمونه‌ی قبلی آن مقایسه کرده و تغییرات رخ‌داده را محاسبه می‌کند. در آخر این تغییرات را به DOM واقعی اعمال می‌کند تا با Virtual DOM موجود هماهنگ شود.
بنابراین در حین کار با React، دیگر همانند کار با جاوا اسکریپت خالص و یا jQuery، مستقیما عناصر UI و DOM واقعی را تغییر نمی‌دهیم. در اینجا فقط state یک کامپوننت را تغییر می‌دهیم و سپس React، کار ایجاد شیء UI درون حافظه‌ای متناظر با آن و سپس اعمال آن‌را به UI نهایی قابل مشاهده‌ی در مرورگر، انجام می‌دهد. به همین جهت به این کتابخانه React می‌گویند! چون به تغییرات state کامپوننت‌ها واکنش نشان می‌دهد و سپس DOM واقعی را به روز می‌کند.


Angular یا React؟!

هر دوی React و Angular از لحاظ طراحی کامپوننت‌ها بسیار شبیه به هم هستند؛ اما Angular یک فریم‌ورک است و React تنها یک کتابخانه. تنها کاری را که React انجام می‌دهد، رندر View است و هماهنگ نگه داشتن آن با state کامپوننت‌ها. این تمام کاری است که React انجام می‌دهد؛ نه بیشتر و نه کمتر! بنابراین یادگیری React، بسیار سریع‌تر و ساده‌تر از Angular است. بدیهی است یک برنامه‌ی تک صفحه‌ای وب، از اجزای دیگری مانند مسیریابی و یا کار با سرویس‌های HTTP نیز تشکیل می‌شود. در React شما مختار هستید که کتابخانه‌های جانبی فراهم شده‌ی برای آن‌را خودتان انتخاب کرده و استفاده کنید؛ برخلاف روشی که در Angular مرسوم است و به صورت مشخص و ثابتی به همراه این فریم‌ورک ارائه می‌شوند.


برپایی محیط توسعه‌ی React

اولین برنامه‌ای را که برای کار با React باید نصب کنید، node.js است. البته ما در این سری قرار نیست با node.js کار کنیم؛ اما از یکی از اجزای آن به نام node package manager یا npm، برای نصب کتابخانه‌ی جاوا اسکریپتی ثالث، زیاد استفاده خواهیم کرد. پس از نصب آن، به خط فرمان مراجعه کرد و دستور زیر را صادر کنید:
> npm install -g npm@latest
این دستور npm قدیمی موجود بر روی سیستم را به روز رسانی می‌کند (اگر پیشتر یک node.js قدیمی را نصب و اکنون آن‌را به روز رسانی کرده‌اید).

اگر هم خیلی پیشترها node.js را نصب کرده‌اید (برای مثال چند سال قبل!)، نصب نگارش جدید آن احتمالا کار نخواهد کرد. حتی عزل و نصب مجدد آن نیز کارساز نیست. در این حالت باید پس از عزل آن، پوشه‌های قدیمی آن‌را یکی یکی یافته و دستی حذف کنید . سپس مجددا آن‌را نصب کنید.

در ادامه در خط فرمان و توسط npm، قالب create-react-app را نصب خواهیم کرد:
> npm i -g create-react-app
در اینجا سوئیچ i به معنای install است و g یعنی نصب global و سراسری بسته‌ی create-react-app. نصب سراسری یک بسته یعنی در هر پوشه‌ای می‌توان به امکانات آن دسترسی یافت و از آن استفاده کرد. اگر از سوئیچ g استفاده نمی‌شد، این بسته تنها در پوشه‌ی جاری و با سطح دید مختص به آن، نصب و قابل استفاده می‌شد.

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

و یا می‌توانید این فایل را اجرا کرده و تعدادی از افزونه‌های مفید VSCode را یکجا نصب کنید: install-addons.zip

همچنین قابلیت فرمت‌کردن پس از Save را نیز در VSCode فعال کنید تا پس از هربار Save، اعمال این افزونه‌ها به صورت خودکار صورت گیرد. برای این منظور گزینه‌ی file->preferences->settings را در VSCode انتخاب کرده و سپس save را جستجو کرده و Format On Save را انتخاب کنید:


علاوه بر این‌ها، جهت کار بهتر با VSCode، بهتر است بررسی کننده‌های کدهای جاوا اسکریپتی (static code analyzers) را نیز با اجرای دستور زیر نصب کنید:
> npm i -g typescript eslint tslint eslint-plugin-react-hooks

پس از این تغییرات، نیاز است یکبار VSCode را بسته و مجددا باز کنید. سپس مجددا گزینه‌ی file->preferences->settings را در VSCode انتخاب کرده و ابتدا eslint را در اینجا جستجو کنید. در صفحه‌ی نمایش تنظیمات آن، گزینه‌ی Auto fix on save آن‌را انتخاب نمائید. در آخر در همین قسمت settings، عبارت prettier را انتخاب کنید. در اینجا اگر گزینه‌ی قدیمی یکپارچگی با eslint آن هنوز وجود دارد، آن‌را از حالت انتخاب شده خارج کنید (به صورت قرمز و deprecated نمایش داده می‌شود) تا افزونه‌ی prettier بدون مشکل و خطا کار کند (disable Prettier ESLint integration).


ایجاد قالب اولین برنامه‌ی React

در ادامه برای ایجاد اولین برنامه‌ی React، از بسته‌ی create-react-app که پیشتر آن‌را نصب کردیم، استفاده می‌کنیم. برای این منظور در خط فرمان دستور زیر را صادر کنید:
> create-react-app sample-01
در اینجا sample-01 یک نام دلخواه است و در حین اجرای این دستور باید به اینترنت متصل باشید تا وابستگی‌های مرتبط با پروژه را نیز دریافت کند. برای بار اول، اجرای آن ممکن است کمی طول بکشد. اما از دفعات آتی، چون بسته‌های مرتبط را در npm-cache سیستم نیز ذخیره می‌کند، اجرای آن بسیار سریع خواهد بود.
این قالب نه تنها React را نصب می‌کند، بلکه یک development server را برای اجرا و مشاهده‌ی سریع برنامه، webpack را برای یکی کردن فایل‌ها (bundling & minification)، Babel را برای کامپایل کدهای فایل‌های JSX و ... نیز نصب می‌کند. بنابراین به این ترتیب، یک پروژه‌ی تنظیم شده و آماده‌ی استفاده و توسعه را شاهد خواهیم بود که نیازی به تنظیمات اولیه‌ی آن نیست.
پس ایجاد برنامه، وارد پوشه‌ی sample-01 شده و دستور npm start را صادر کنید:
> cd sample-01
> npm start
به این ترتیب برنامه بر روی پورت 3000، قابل دسترسی و مشاهده می‌شود:


development server آن، تغییرات فایل‌های برنامه را تحت نظر قرار می‌دهد و با هر تغییری، به صورت خودکار برنامه را در مرورگر بارگذاری مجدد خواهد کرد.


بررسی ساختار اولین پروژه‌ی React ایجاد شده

ساختار پوشه‌ها و فایل‌های مثال اولیه‌ی ایجاد شده توسط قالب create-react-app به صورت زیر است:


البته شما در این تصویر پوشه‌ی node_modules را که در کنار این پوشه‌ها قرار دارد، مشاهده نمی‌کنید. وجود یک چنین پوشه‌ی سنگینی با هزاران فایل داخل آن، کار نمایشی IDEها را با مشکل مواجه می‌کند (مصرف حافظه‌ی بالا، به همراه کند شدن شدید آن). اگر نمی‌خواهید این پوشه نمایش داده شود، در مسیر file->preferences->settings، عبارت npm را جستجو کرده و سپس در قسمت npm: exclude آن، بر روی لینک edit in settings.json کلیک کنید:


 و سپس در فایل باز شده، یک چنین تنظیمی را می‌توانید اضافه و یا ویرایش و تکمیل کنید:
  "files.exclude": {
    "**/.git": true,
    "**/.svn": true,
    "**/.hg": true,
    "**/CVS": true,
    "**/.DS_Store": true,
    "**/node_modules": true,
    "**/wwwroot": true,
    "**/bower_components": true,
    "**/**/bin": true,
    "**/**/obj": true,
    "**/packages": true
  },

در ادامه پوشه‌ی public این پروژه را مشاهده می‌کنید. تمام فایل‌هایی که قرار است به صورت عمومی توسط برنامه ارائه شوند، مانند favicon.ico و غیره، در این پوشه قرار می‌گیرند.
در این پوشه بر روی فایل index.html آن کلیک کنید تا بتوان محتوای آن‌را بهتر بررسی کرد. برای مثال در ابتدای آن، درج تعدادی متادیتا را که یکی از آن‌ها ذکر manifest.json است، مشاهده می‌کنید. کار فایل manifest.json، ارائه‌ی یک سری متادیتای خاص مخصوص دستگاه‌های موبایل است که در آن‌ها بجای favicon.ico، می‌توان از تصاویر و یا آیکن‌های بزرگتری مانند فایل‌های png موجود در پوشه‌ی public، استفاده کرد. در ادامه‌ی این فایل، به تنظیم زیر می‌رسیم:
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
div با id مساوی root، محل ارائه‌ی کل برنامه‌ی React ما است.

در پوشه‌ی src و فایل App.js آن، شاهد یک کامپوننت ابتدایی هستید که کار رندر صفحه‌ی مشکی پیش‌فرض این قالب را انجام می‌دهد. در این فایل، شاهد بازگشت یک چنین تگ‌هایی هستیم:
  return (
    <div className="App">
      <header className="App-header">
       ... 
      </header>
    </div>
  );
احتمالا تابحال چنین return ای را در برنامه‌های جاوا اسکریپتی مشاهده نکرده‌اید؛ چون درج آن‌ها در فایل‌های js به این نحو، غیرمجاز است. این تگ‌ها نه رشته‌ای هستند و نه HTML خالص. به آن jsx گفته می‌شود که مخفف JavaScript XML می‌باشد. کار آن ارائه‌ی ساختار UI ای است که قرار است رندر شود. یک چنین کدی برای اینکه قابل تفسیر و اجرا باشد، از درون کامپایلر ویژه‌ای به نام Babel عبور می‌کند و تبدیل به کدهای جاوا اسکریپتی خالصی می‌شود که برای مرورگرها قابل درک و اجرا است.
برای درک بهتر آن به آدرس https://babeljs.io/repl مراجعه کنید. سپس در سمت چپ صفحه، یک قطعه کد jsx را به یک ثابت انتساب دهید:
const element = <h1>Hello World!</h1>;


همانطور که مشاهده می‌کنید، این قطعه کد jsx (که یک رشته‌ی معمولی نیست)، توسط Babel به یک قطعه کد کاملا جاوا اسکریپتی قابل درک برای مرورگر تبدیل شده‌است:
"use strict";

var element = React.createElement("h1", null, "Hello World!");

بدیهی است نوشتن کدهای jsx، ساده‌تر از نوشتن قطعه کد فوق است و درک آن نیز به علت شباهت آن به HTML، آسان‌تر است. به همین جهت در کدهای React، ما از jsx استفاده می‌کنیم و تفسیر آن‌را به Babel واگذار خواهیم کرد.

در پوشه‌ی src، فایل مهم دیگری که وجود دارد، index.js است. این فایل نقطه‌ی آغازین برنامه را مشخص می‌کند. در قسمت‌های بعدی، محتویات این فایل را بیشتر بررسی خواهیم کرد.
در اینجا فایل serviceWorker.js را نیز مشاهده می‌کنید. این فایل به صورت خودکار توسط قالب create-react-app ایجاد شده‌است و کار آن کمک به ارائه‌ی محلی برنامه، توسط development server آن است. بنابراین ما کاری با این فایل نخواهیم داشت.


نوشتن اولین برنامه‌ی React

به پوشه‌ی src ایجاد شده مراجعه کرده و تمام فایل‌های موجود و پیش‌فرض آن‌را حذف کنید. در ادامه خودمان آن‌ها را از صفر ایجاد خواهیم کرد. برای این منظور فایل جدید و خالی src\index.js را ایجاد می‌کنیم. در ابتدای کار نیاز است تعدادی ماژول React را import کنیم.
import React from "react";

const element = <h1>Hello World!</h1>;
console.log(element);
در اینجا شیء React از ماژول react دریافت شده و سپس یک ثابت را با یک عبارت jsx مقدار دهی کرده‌ایم. چون از jsx استفاده می‌کنیم، ذکر import ابتدای فایل الزامی است؛ از این جهت که Babel به کمک آن است که می‌تواند معادل React.createElement را تولید کند.
اگر هنوز برنامه توسط دستور npm start در حال اجرا است، هر بار که فایل index.js را ذخیره می‌کنیم، خروجی نهایی را در مرورگر نمایش می‌دهد (اگر هم آن‌را بسته‌اید، یکبار از طریق خط فرمان، دستور npm start را در ریشه‌ی پروژه، صادر کنید). به این قابلیت hot module reloading هم گفته می‌شود.
در این حالت اگر به مرورگر مراجعه کنید، یک صفحه‌ی سفید را مشاهده خواهید کرد. اکنون دکمه‌ی F12 را فشرده (و یا ctrl+shift+i) و developer console مرورگر را باز کنید.


شیءای را که در اینجا مشاهده می‌کنید، همان حاصل console.log کدهای فوق است؛ به عبارتی Babel، عبارت jsx ما را تبدیل به یک شیء جاوا اسکریپتی قابل فهم برای مرورگر کرده‌است که از دیدگاه React، جزئی از همان Virtual DOM ای است که پیشتر معرفی شد (نمایش درون حافظه‌ای DOM مختص React، جهت محاسبه‌ی تغییرات، با تغییر state هر کامپوننت و سپس اعمال آن‌ها به DOM اصلی در مرورگر).
اکنون می‌خواهیم این المان را در DOM اصلی، رندر کرده و نمایش دهیم:
import React from "react";
import ReactDOM from "react-dom";

const element = <h1>Hello World!</h1>;
console.log(element);

ReactDOM.render(element, document.getElementById("root"));
برای این منظور نیاز است از متد ReactDOM.render استفاده کرد. این شیء در ماژول react-dom قرار دارد؛ به همین جهت در ابتدای فایل import شده‌است. سپس در متد render آن، ابتدا المانی که قرار است رندر شود ذکر خواهد شد و سپس محل درج آن‌را مشخص می‌کنیم که دقیقا به همان div با id مساوی root در فایل public\index.html اشاره می‌کند.
اکنون پس از ذخیره سازی فایل index.js، اگر به مرورگر مراجعه کنید، عبارت Hello World! را مشاهده خواهید کرد:


همانطور که در این تصویر نیز مشخص است، المان h1 ما را داخل div ای با id مساوی root، درج کرده‌است.

هدف از این مثال ساده، نمایش نحوه‌ی کارکرد React، در پشت صحنه بود. در یک برنامه‌ی واقعی، بجای رندر یک المان ساده در DOM، کار رندر App component را انجام خواهیم داد. کامپوننت App، کامپوننت ریشه‌ای برنامه بوده و می‌تواند شامل درختی از کامپوننت‌ها که UI نهایی را تشکیل می‌دهند، شود.


نگاهی به تنظیمات پروژه‌ی ایجاد شده

اگر فایل package.json پروژه را باز کنید، یک چنین بسته‌هایی در آن درج شده‌است:
{
  "name": "sample-01",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "react": "^16.11.0",
    "react-dom": "^16.11.0",
    "react-scripts": "3.2.0"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": "react-app"
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}
در اینجا صرفا سه بسته‌ی react، react-dom و react-scripts را در قسمت dependencies مشاهده می‌کنید که کل Importهای ما را تشکیل می‌دهند.
بسته‌ی react-scripts است که کار مدیریت چهار جزء قسمت scripts این فایل را انجام می‌دهد. برای نمونه دستور npm start ای که در اینجا تعریف شده، سبب اجرای react-scripts start می‌شود. در ادامه اگر دستور npm run build را اجرا کنیم، یک بسته‌ی نهایی بهینه سازی شده را تولید می‌کند.
آخرین دستور آن eject است. اگر دستور npm run eject را اجرا کنید، امکان سفارشی سازی پشت صحنه‌ی create-react-app را خواهید داشت؛ اما در نهایت به یک فایل package.json بسیار شلوغ خواهیم رسید (اینبار ارجاعات به Babel، Webpack و تمام ابزارهای دیگر نیز ظاهر می‌شوند). همچنین این عملیات نیز یک طرفه‌است. یعنی از این پس قرار است کنترل تمام این پشت صحنه، در اختیار ما باشد و به روز رسانی‌های بعدی create-react-app را با مشکل مواجه می‌کند. این گزینه صرفا مختص توسعه دهندگان پیشرفته‌ی React است.


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

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

فرمت‌های قابل قبول برای ورود تاریخ عبارتند از:
۹۰۰۸۱۴ ۱۴۰۸۹۰ ۱۳۹۰۰۸۱۴ ۱۴/۸/۹۰ ۹۰/۸/۱۴ ۱۴/۸/۱۳۹۰ ۱۳۹۰/۸/۱۴ ۱۴-۸-۹۰ ۹۰-۸-۱۴ ۱۴-۸-۱۳۹۰ ۱۳۹۰-۸-۱۴ 
و فرمت‌های ویژه:
۰۸۱۴ ۱۴۰۸ ۱۴-۸ ۸-۱۴ ۱۴/۸ ۸/۱۴ ۱۴
در فرمت‌های ویژه که سال و ماه وارد نشده‌اند، سال و ماه فعلی به حساب خواهد آمد.
در فرمت‌هایی که سال مشخص نشده باشد، دو رقم ابتدایی در صورت امکان روز محاسبه خواهند شد.
بنابراین قادر خواهیم بود که در خروجی یک فرمت استاندارد داشته باشیم حتی با فرمت‌های مختلفی که کاربر وارد خواهد کرد.

روش به کارگیری تقویم در Blazor

در ابتدا فایل‌های مورد نیاز را دانلود کرده (AMIB_jsPersianCal_0.2.1.rar) و به پروژه اضافه می‌کنیم.
سپس به _layout رفته و ارجاعات زیر را برای افزودن فایل‌های css و js به پروژه اضافه می‌کنیم:
<link href="css/js-persian-cal.css" rel="stylesheet"/>
<script src="js/js-persian-cal.min.js"></script>
حال برای استفاده از دیت پیکر در کامپوننت‌ها از تگ input به شکل زیر استفاده می‌کنیم:
<input type="text" id="pcal1" />
Id آن مهم است زیرا توسط آن به تابع جاوااسکریپتی معرفی می‌شود. می‌توان هر اسمی را اختیار کرد فقط بهتر است تمامی دیت پیکرهای موجود در صفحه یک اسم داشته باشند اما با ایندکس‌های مختلف مانند pcal1، pcal2 و ... . دلیل آن این است که می‌توان تمامی دیت پیکرهای را توسط یک حلقه به تابع مربوطه معرفی کرد.
همانطور که می‌دانید برای استفاده از توابع جاوا اسکریپتی در Blazor از JSRuntime استفاده می‌شود. بنابراین به شکل زیر عمل خواهیم کرد.
protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        int dateFieldCount = 1;
        if (firstRender)
        {
            for (int i = 1; i <= dateFieldCount; i++)
            {
                await JsRuntime.InvokeVoidAsync("CallAmib", "pcal" + i.ToString());
            }
        }
    }
توسط حلقه for تمامی تگ‌های input موجود در کامپوننت را که Id آنها با pcal شروع می‌شود به دیت پیکر تبدیل خواهیم نمود. فقط مقدار متغیر dateFieldCount را باید به تعداد تگ‌های دیت پیکر موجود در کامپوننت تنظیم نمود.
لازم به ذکر است که باید در ابتدای کامپوننت، JSRuntime را به شکل زیر تزریق نمود.
@inject IJSRuntime  JsRuntime
حال فقط کافیست اسکریپت CallAmib را ایجاد کرده و به _layout اضافه نمود.
window.CallAmib = (objCal1) => {
    new AMIB.persianCalendar(objCal1);
}
  بنابراین فایل _layout برنامه الان چیزی شبیه به زیر خواهد بود:
@using Microsoft.AspNetCore.Components.Web
@namespace ShamsiDatePickerBlazor.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <base href="~/" />
    <link rel="stylesheet" href="css/bootstrap/bootstrap.min.css" />
    <link href="css/site.css" rel="stylesheet" />
    <link href="css/js-persian-cal.css" rel="stylesheet" />
    <component type="typeof(HeadOutlet)" render-mode="ServerPrerendered" />
</head>
<body>
    @RenderBody()

    <div id="blazor-error-ui">
        <environment include="Staging,Production">
            An error has occurred. This application may no longer respond until reloaded.
        </environment>
        <environment include="Development">
            An unhandled exception has occurred. See browser dev tools for details.
        </environment>
        <a href="">Reload</a>
        <a>🗙</a>
    </div>
    <script src="js/js-persian-cal.min.js"></script>
    <script src="js/CallAmib.js"></script>
    <script src="_framework/blazor.server.js"></script>
</body>
</html>
تا اینجای کار اگر پروژه را اجرا کنیم، دیت پیکری مانند زیر را خواهیم داشت:

مشکل!!

برای بایند کردن مقدار تاریخ انتخاب شده نمی‌توان از bind-value به طور معمول استفاده کرد؛ زیرا در حقیقت تغییرات input با جاوا اسکریپت انجام می‌گیرد و حالت صفحه تغییری نمی‌کند. برای مرتفع کردن این مشکل نیاز است که در اسکریپت CallAmib متد onchange به شکل زیر صدا زده شده و مقدار تاریخ انتخابی به یک متد داخل کامپوننت ارسال گردیده و در آنجا به یک فیلد منتسب شود.
window.CallCall = (objCal1) => {
    new AMIB.persianCalendar(objCal1,{
        onchange: function(pdate) {
            DotNet.invokeMethodAsync('ShamsiDatePickerBlazor', 'DateChanged', pdate.toString()).then(
                (date) => {
                    console.log(data);
                }
            );
        }
    });
}
توضیحات اسکریپت بالا:
متغیر pdate به صورت توکار مربوط به AMIB.persianCalendar می باشد و مقدار تاریخ انتخابی را در بر دارد.
متد DotNet.invokeMethodAsync یک متد توکار دات نت می‌باشد و برای فراخوانی متدهای سی شارپی از داخل توابع جاوا اسکریپتی به کار می‌رود. آرگومان اول آن در حقیقت نام اسمبلی پروژه می‌باشد. آرگومان دوم آن نام تابع سی شارپی‌است که باید فراخوانی شود و در نهایت آرگومان سوم آن تاریخ انتخاب شده می‌باشد.
در پایان باید متد DateChanged،  به شکل زیر در کامپوننت index نوشته شود:
static string selectedDate;
[JSInvokable]
public static void DateChanged(string pdate)
{
    selectedDate = pdate;
}
این تابع بایستی با صفت [JSInvokable] مزین شود و حتما هم استاتیک باشد.
برای دیدن مقدار جدید selectedDate کافی است روی دکمه ShowNewValue یکبار کلیک نمایید.
نکته: می‌توان به جای input، از InputText مربوط به EditForm هم استفاده نمود. فقط باید یک Id هم به آن انتساب داد. همچنین برای انتساب مقدار دیت پیکر به مدل، باید در متد DateChanged، فیلد مورد نظر از مدل را بجای متغیر selectedDate گذاشت.
شما می‌توانید در اینجا کدهای کامل این مطلب را ملاحظه نمایید.
مطالب
بارگذاری پویای کامپوننت‌های Angular به همراه امکان Lazy loading پویای ماژول‌ها

در نسخه‌های قبل از Angular CLI 6.0، صرفا امکان Bundle کردن جداگانه‌ی ماژول‌هایی که در قسمت  loadChildren مرتبط با تنظیمات مسیریابی  ذکر شده بودند، وجود داشت. بنابراین در برخی از شرایط اگر نیاز به امکان بارگذاری ماژولی به صورت Lazy load بود، باید از سیستم مسیریابی استفاده می‌شد یا اینکه با یکسری ترفند، CLI و Webpack را مجبور به ساخت فایل chunk جداگانه برای ماژول مورد نظر می‌کردید. از زمان انتشار Angular CLI 6.0 امکان Lazy loading پویا نیز مهیا می‌باشد؛ به این ترتیب بدون وابستگی به سیستم مسیریابی، باز هم می‌توان از مزایای Lazy loading بهره برد. در این مطلب روش استفاده از این قابلیت و همچنین نحوه‌ی بارگذاری پویای یک کامپوننت مرتبط با یک ماژول Lazy load شده را بررسی خواهیم کرد. برای این منظور در ادامه با ایجاد یک TabLayout با استفاده از Angular Material Tabs با یکی از موارد پر استفاده‌ی قابلیت مذکور آشنا خواهیم شد.

پیش نیازها

کار را با طراحی و پیاده سازی TabService شروع می‌کنیم. برای این منظور یک سرویس را در فولدر services موجود در کنار CoreModule ایجاد خواهیم کرد؛ به این جهت ابتدا مدل‌های زیر را خواهیم داشت:

import { Type, ValueProvider } from '@angular/core';

export interface OpenNewTabModel {
  label: string;
  componentType: Type<any>;
  iconName: string;
  modulePath?: string;
  data?: ValueProvider[];
}
واسط تعریف شده‌ی در بالا به عنوان قرارداد مدل ورودی متد open مرتبط با سرویس TabService استفاده می‌شود. در اینجا componentType، نوع کامپوننت را مشخص می‌کند که قرار است داخل برگه‌ی جدید نمایش داده شود. modulePath هم به مسیر ماژولی که باید به صورت پویا بارگذاری شود، اشاره می‌کند. دلیل وجود خصوصیت data را نیز در ادامه خواهیم دید.
import { TabItemComponent } from './tab-item-component';

export interface TabItem {
  label: string;
  iconName: string;
  component: TabItemComponent;
}

OpenNewTabModel برای ارسال داده توسط مصرف کننده از این سرویس در نظر گرفته شده است. ولی واسط TabItem دارای خصوصیاتی می‌باشد که ما برای نمایش یک برگه‌ی جدید نیازمندیم. TabItemComponent نیز دارای خصوصیاتی است که مورد نیاز دایرکتیو« NgComponentOutlet» است. 

import { Injector, NgModuleFactory, Type } from '@angular/core';

export interface TabItemComponent {
  componentType: Type<any>;
  moduleFactory?: NgModuleFactory<any>;
  injector: Injector;
}

همانطور که اشاره شد، برای بارگذاری پویای یک کامپوننت از NgComponentOutlet استفاده خواهیم کرد؛ لذا اگر modulePath ای توسط مصرف کننده از TabService، مهیا شده باشد، لازم است ابتدا ماژول مورد نظر به صورت پویا بارگذاری شود و moduleFactory بدست آمده را به عنوان ورودی دایرکتیو مذکور ارسال کنیم. TabService، پیاده سازی به شکل زیر خواهد داشت:
import { BehaviorSubject, Observable } from 'rxjs';
import {
  Injectable,
  Injector,
  NgModuleFactory,
  NgModuleFactoryLoader
} from '@angular/core';

import { OpenNewTabModel } from '../models/open-new-tab-model';
import { TabItem } from '../models/tab-item';

@Injectable({
  providedIn: 'root'
})
export class TabService {
  private tabItemSubject: BehaviorSubject<TabItem[]> = new BehaviorSubject<
    TabItem[]
  >([]);

  constructor(
    private loader: NgModuleFactoryLoader,
    private injector: Injector
  ) {}

  get tabItems$(): Observable<TabItem[]> {
    return this.tabItemSubject.asObservable();
  }

  open(newTab: OpenNewTabModel) {
    if (newTab.modulePath) {
      this.loader
        .load(newTab.modulePath)
        .then((moduleFactory: NgModuleFactory<any>) => {
          this.openInternal(newTab, moduleFactory);
        });
    } else {
      this.openInternal(newTab);
    }
  }

  private openInternal(newTab: OpenNewTabModel, moduleFactory?: NgModuleFactory<any>) {
    const newTabItem: TabItem = {
      label: newTab.label,
      iconName: newTab.iconName,
      component: {
        componentType: newTab.componentType,
        moduleFactory: moduleFactory,
        injector: newTab.data
          ? Injector.create(newTab.data, this.injector)
          : this.injector
      }
    };

    this.tabItemSubject.getValue().push(newTabItem);
    this.tabItemSubject.next(this.tabItemSubject.getValue());
  }

  close(index: number) {
    this.tabItemSubject.getValue().splice(index, 1);
    this.tabItemSubject.next(this.tabItemSubject.getValue());
  }
}
روش کار به این شکل می‌باشد که یک مخزن، برای لیست برگه‌های درخواستی برای نمایش، تحت عنوان tabItemSubject و از نوع BehaviorSubject در نظر گرفته شده تا مصرف کننده از این سرویس که قصد نمایش برگه‌ها را دارد، از تغییرات لیست موجود آگاه شود. عموما TabsComponent، مشترک پراپرتی فقط خواندنی ‎‎‎tabItems‎$ خواهد شد و بقیه بخش‌ها صرفا دستور گشودن برگه‌ی جدید را با متد open صادر خواهند کرد.
یکی از وابستگی‌های این سرویس، وهله‌ای می‌باشد از کلاس  NgModuleFactoryLoader  که در سیستم مسیریابی نیز از همین کلاس برای بارگذاری ماژول‌ها استفاده می‌شود. البته نیاز است که یکی از پیاده سازی‌های این کلاس انتزاعی را به سیستم تزریق وابستگی‌ها نیز معرفی کنید:
{ provide: NgModuleFactoryLoader, useClass: SystemJsNgModuleLoader }
در بدنه متد open، ابتدا بررسی می‌شود که اگر modulePath مشخص شده‌است، ماژول مورد نظر ابتدا توسط متد load مرتبط با وهله NgModuleFactoryLoader به صورت پویا بارگذاری شود و سپس با استفاده از moduleFactory بدست آمده، متد openInternal فراخوانی خواهد شد.
 در بدنه متد openInternal، تنهای نکته‌ای که ذکر آن اهمیت دارد، مرتبط است به مقداردهی خصوصیت injector شیء ایجاد شده. باتوجه به اینکه تا زمان نگارش مطلب جاری امکان کار با Input‌ها و Output‌های کامپوننت مورد نظر که قرار است با استفاده از NgComponentOutlet بارگذاری شود، وجود ندارد، لذا راه حل فعلی، استفاده از سیستم تزریق وابستگی‌ها می‌باشد. برای این منظور، با استفاده از متد استاتیک create کلاس Injector یک child injector ایجاد شده و ValueProvider‌های مشخص شده توسط خصوصیت data، به صورت خودکار رجیستر خواهند شد. در نهایت آگاه سازی مشترکین خصوصیت ‎‎‎tabItems‎با استفاده از فراخوانی متد next مرتبط با tabItemSubject انجام می‌گیرد.

پیاده سازی TabsComponent
import { Component, OnInit } from '@angular/core';

import { TabService } from './../../../services/tab.service';

@Component({
  selector: 'app-tabs',
  templateUrl: './tabs.component.html',
  styleUrls: ['./tabs.component.scss']
})
export class TabsComponent implements OnInit {
  constructor(public service: TabService) {}

  ngOnInit() {}
}

همانطور که عنوان شد، مشترک اصلی خصوصیت tabItems سرویس TabService، کامپوننت تعریف شده‌ی بالا می‌باشد. قالب مرتبط با آن به شکل زیر است:
<mat-tab-group>
  <mat-tab
    *ngFor="let tabItem of (service.tabItems$ | async); let index = index"
  >
    <ng-template mat-tab-label>
      <mat-icon
        class="icon"
        aria-label="icon for tab"
      >{{tabItem.iconName}}</mat-icon>
      <span class="full">{{ tabItem.label }}</span>
    
      <mat-icon
        class="close"
        (click)="service.close(index)"
        aria-label="close tab button"
        >close</mat-icon
      >
      <!-- </button> -->
    </ng-template>

    <ng-container *ngIf="tabItem.component.moduleFactory">
      <ng-container
        *ngComponentOutlet="
          tabItem.component.componentType;
          ngModuleFactory: tabItem.component.moduleFactory;
          injector: tabItem.component.injector
        "
      >
      </ng-container>
    </ng-container>
    <ng-container *ngIf="!tabItem.component.moduleFactory">
      <ng-container
        *ngComponentOutlet="
          tabItem.component.componentType;
          injector: tabItem.component.injector
        "
      >
      </ng-container>
    </ng-container>
  </mat-tab>
</mat-tab-group>

در تکه کد بالا، ابتدا با استفاده از وهله تزریق شده TabService در کامپوننت مذکور، به شکل زیر، مشترک تغییرات لیست برگه‌ها شده‌ایم و با استفاده از دایرکتیو ‎*ngFor به ازای تک تک tabItem‌های درخواست شده برای گشوده شدن، به شکل زیر کار وهله سازی پویا از کامپوننت مشخص شده انجام می‌شود:

<ng-container *ngComponentOutlet="tabItem.component.componentType; ngModuleFactory: tabItem.component.moduleFactory; injector: tabItem.component.injector">
</ng-container>

خوب، با استفاده از آنچه تا اینجای مطلب بررسی شد، می‌توان یک سیستم راهبری مبتنی بر Tab را نیز برپا کرد که مطلب جدایی را می‌طلبد. برای تکمیل مکانیزم بارگذاری پویای ماژول‌ها، نیاز است تا مسیر ماژول مورد نظر را در فایل angular.json و بخش lazyModules به شکل زیر معرفی کنید:

"build": {
          "builder": "@angular-devkit/build-angular:browser",
          "options": {
            "outputPath": "dist/MaterialAngularTabLayout",
            "index": "src/index.html",
            "main": "src/main.ts",
            "polyfills": "src/polyfills.ts",
            "tsConfig": "src/tsconfig.app.json",
            "assets": [
              "src/favicon.ico",
              "src/assets"
            ],
            "styles": [
              "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css",
              "src/styles.scss"
            ],
            "lazyModules": [
              "src/app/lazy/lazy.module"
            ],
            "scripts": []
          },

به عنوان مثال قصد داریم ماژول LazyModule را به صورت پویا بارگذاری کرده و LazyComponent موجود در این ماژول را به صورت پویا در برگه‌ی جدیدی نمایش دهیم. برای این منظور کدهای فایل AppComponent.ts را به شکل زیر تغییر خواهیم داد:

import { Component } from '@angular/core';
import { IdModel } from './core/models/id-model';
import { LazyComponent } from './lazy/lazy.component';
import { OpenNewTabModel } from './core/models/open-new-tab-model';
import { TabService } from './core/services/tab.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent {
  title = 'MaterialAngularTabLayout';
  constructor(private tabService: TabService) {}
  loadLazyComponent() {
    this.tabService.open(<OpenNewTabModel>{
      label: 'Loaded Lazy Component',
      iconName: 'thumb_up',
      componentType: LazyComponent,
      modulePath: 'src/app/lazy/lazy.module#LazyModule',
      data: [{ provide: IdModel, useValue: <IdModel>{ id: 1 } }]
    });
  }
}

در تکه کد بالا با تزریق TabService به سازنده‌ی آن، قصد گشودن برگه‌ی جدیدی را توسط متد open آن، داریم. در بدنه‌ی متد loadLazyComponent یک شیء با قرارداد OpenNewTabModel ایجاد شده و به عنوان آرگومان به متد open ارسال شده است. توجه داشته باشید که modulePath اینجا نیز به مانند خصوصیت loadChildren مرتبط با اشیاء مسیریابی، باید مقدار دهی شود. همچنین قصد داشتیم اطلاعاتی را نیز به کامپوننت مورد نظر ارسال کنیم؛ همانند مکانیزم مسیریابی که با پارامترها این چنین کارهایی صورت می‌پذیرد. در اینجا از یک کلاس به شکل زیر استفاده شده‌است:

export class IdModel {
  constructor(public id: number) {}
}

در این صورت پیاده سازی LazyComponent نیز به شکل زیر خواهد بود:

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

import { IdModel } from './../core/models/id-model';

@Component({
  selector: 'app-lazy',
  templateUrl: './lazy.component.html',
  styleUrls: ['./lazy.component.scss']
})
export class LazyComponent implements OnInit {
  constructor(private model: IdModel) {}

  ngOnInit() {
    console.log(this.model);
  }
}

البته فراموش نکنید کامپوننتی را که نیاز است به صورت پویا بارگذاری شود، در قسمت entryComponents مرتبط با NgModule متناظر به شکل نیز معرفی کنید:

import { CommonModule } from '@angular/common';
import { LazyComponent } from './lazy.component';
import { NgModule } from '@angular/core';

@NgModule({
  imports: [CommonModule],
  declarations: [LazyComponent],
  entryComponents: [LazyComponent]
})
export class LazyModule {}

با خروجی زیر:

و chunk تولید شده برای ماژول مورد نظر:


در صورتیکه در حالت production پروژه را بیلد کنید، هش مرتبط برای chunk تولید شده نیز ایجاد خواهد شد.


کدهای کامل این قسمت را می‌توانید از اینجا دریافت کنید.
مطالب
بخش دوم - بررسی ساختار فایل ها و Syntax برنامه های Svelte
در بخش اول به معرفی SvelteJs پرداختیم و اولین پروژه‌ی خود را ایجاد کردیم. در ادامه به بررسی جزئیات فایل‌های تشکیل شده می‌پردازیم. قبل از هرچیز پیشنهاد میکنم اگر از vs-code استفاده میکنید Extension Svelte را دانلود و نصب نمایید.
پس از ایجاد پروژه، تعدادی فایل توسط Svelte ایجاد می‌شوند که در ادامه آن‌ها را بررسی خواهیم کرد.


rollup.config.js : 
به طور پیش فرض Svelte از rollup برای ساخت برنامه استفاده میکند که جایگزینی برای webpack است. فعلا نیازی به تغییر و دانستن جزئیاتی در مورد این فایل نداریم؛ چراکه به صورت پیش فرض توسط قالب دریافت شده، تمامی کانفیگ‌های مورد نظر ما برای توسعه و ساخت نهایی باندل برنامه انجام شده‌است. فقط به چند نکته‌ی مهم در این فایل اشاره خواهم کرد.
import svelte from 'rollup-plugin-svelte';
import resolve from 'rollup-plugin-node-resolve';
import commonjs from 'rollup-plugin-commonjs';
import livereload from 'rollup-plugin-livereload';
import { terser } from 'rollup-plugin-terser';

const production = !process.env.ROLLUP_WATCH;

export default {
input: 'src/main.js',
output: {
sourcemap: true,
format: 'iife',
name: 'app',
file: 'public/bundle.js'
},
plugins: [
svelte({
// enable run-time checks when not in production
dev: !production,
// we'll extract any component CSS out into
// a separate file — better for performance
css: css => {
css.write('public/bundle.css');
}
}),

// If you have external dependencies installed from
// npm, you'll most likely need these plugins. In
// some cases you'll need additional configuration —
// consult the documentation for details:
// https://github.com/rollup/rollup-plugin-commonjs
resolve(),
commonjs(),

// Watch the `public` directory and refresh the
// browser on changes when not in production
!production && livereload('public'),

// If we're building for production (npm run build
// instead of npm run dev), minify
production && terser()
],
watch: {
clearScreen: false
}
};
در خط 12 این فایل، مقدار input برابر با src/main.js است که به rollup، نقطه شروع برنامه را نشان میدهد و مانند سایر فریم ورک‌ها، اسکریپت آغاز کننده برنامه ما میباشد. همینطور در خطوط 15 و 24، فایل‌های bundle خروجی برنامه که به صورت پیش فرض در فولدر public قرار میگیرند، مسیرشان مشخص شده است.


package.json : 
احتمالا اگر از هر کدام از فریم ورک‌های معروفی مثل vue - react - angualr استفاده کرده باشید میدانید این فایل چیست؛ ولی بد نیست توضیح مختصری بدهم و تفاوت مهم package‌ها در svelte را با سایر فریم ورک‌ها، بیان کنم. این فایل تمام وابستگی‌های پروژه و اسکریپت‌های مورد نیاز برای ساخت و اجرای برنامه را در خود نگه میدارد.
{
  "name": "svelte-app",
  "version": "1.0.0",
  "devDependencies": {
    "npm-run-all": "^4.1.5",
    "rollup": "^1.10.1",
    "rollup-plugin-commonjs": "^9.3.4",
    "rollup-plugin-livereload": "^1.0.0",
    "rollup-plugin-node-resolve": "^4.2.3",
    "rollup-plugin-svelte": "^5.0.3",
    "rollup-plugin-terser": "^4.0.4",
    "sirv-cli": "^0.4.0",
    "svelte": "^3.0.0"
  },
  "scripts": {
    "build": "rollup -c",
    "autobuild": "rollup -c -w",
    "dev": "run-p start:dev autobuild",
    "start": "sirv public",
    "start:dev": "sirv public --dev"
  }
}
نکته مهمی که در اینجا به چشم میخورد وجود نداشتن بخش dependencies در این فایل است. در بخش dependencies عموما وابستگی‌های پروژه در زمان اجرای برنامه قرار میگیرد. همانطور که قبلا اشاره کرده بودم، Svelte یک کامپایلر است. به همین جهت در زمان اجرا، نیاز به هیچ وابستگی اضافه‌تری ندارد؛ برخلاف سایر فریم ورک‌ها که حداقل نیاز دارند خود اسکریپت فریم ورک، زمان اجرا لود شده و توسط کاربر دانلود شود. بجای آن همانطور که مشاهده میکنید در خط 4, devDependecies وجود دارد که تمام وابستگی‌های svelte را دربر میگیرد که فقط قبل از build شدن برنامه مورد نیاز هستند. در خط 15، تگ اسکریپ قرار دارد که برای راحتی ساخت و اجرای برنامه همانطور که در بخش قبل دیدیم میتوانیم از آنها استفاده کنیم (npm run dev ---- npm run build ---- etc)

 build  برای ساخت و ایجاد خروجی‌های برنامه توسط rollup مورد قرار استفاده میگیرد. 
 autobuild  مانند build برای ساخت خروجی‌های نهایی برنامه استفاده میشود. ولی تفاوتی که دارد پس از هر تغییر در سورس کد برنامه به صورت خودکار build جدیدی پس از اجرای آن گرفته میشود. 
 dev   برنامه را درحالت Developer Mode اجرا میکند که برای مشاهده تغییرات به صورت خودکار در browser، بدون نیاز به رفرش صفحه و همینطور عیب یابی  برنامه مناسب است. 
 start  از طریق sirv  که یک وب سرور سبک برای هاست کردن سایت‌های استاتیک است، برنامه را هاست میکند.
 start:dev   مانند start است با این تفاوت که برنامه را در حالت Developer Mode هاست میکند که میتواند برای عیب یابی برنامه از آن استفاده کرد؛ چرا که سورس برنامه از طریق source Map قابل دسترس خواهد بود.

دو پوشه src و public هم برای ما به صورت پیش فرض ایجاد شده‌اند که فولدر public فایل‌های نهایی تولید شده برنامه ما را شامل میشود و src، دربرگیرنده تمام سورس کدهای برنامه ما میباشد.
src/App.svelte :
همه بخش‌های برنامه در Svelte از کامپوننت‌ها تشکیل میشوند و این فایل کامپوننت اصلی برنامه در Svelte است. همانطور که نام این فایل پیداست پسوند تمام کامپوننت‌های Svelte نام این کامپایلر است svelte. 
<script>
export let name;
</script>

<style>
h1 {
color: purple;
}
</style>

<h1>Hello {name}!</h1>

اگر قبلا با vuejs کار کرده باشید، این syntax برای شما آشنا خواهد بود؛ هرچند بسیار شبیه کدنویسی در صفحه html است. در کامپوننت‌های svelte شما دو تگ Script و Style دارید و خارج از این دو تگ میتوانید html خود را قرار دهید؛ مانند مثال بالا. در تگ اسکریپت، کدهای جاوا اسکریپتی مرتبط با کامپوننت قرار میگیرد و در تگ Style هم Css‌های مرتبط با کامپوننت. در مثال بالا، در خط 11 ما یک تگ h1 داریم که مقدار hello و یک {name} را نمایش خواهد داد. با استفاده از علامت {} میتوانید کدهای جاوااسکریپتی خود رابه html جاری اضافه کنید که در مثال بالا متغیر name در خط 2 تعریف شده است. در تگ اسکریپت شما امکان ساخت هرگونه متغیر و فانکشنی را که در جاوا اسکریپت معتبر است، خواهید داشت که همینطور میتوان از آنها در صفحه html استفاده کرد. ولی svelte با استفاده از کلمات کلیدی جاوا اسکریپت، چند امکان به این بخش اضافه کرده است. اگر به خط 2 مجددا دقت کنیم، شاید برای شما سؤال ایجاد شود که کلمه World از کجا می‌آید و چطور به متغیر name نسبت داده شده‌است. نکته‌ای که در کد بالا وجود دارد، کلمه export قبل از متغیر است. به این معنا که استفاده کننده از این کامپوننت میتواند name را مقدار دهی کند. در svelte به این نوع متغیر‌ها props گفته میشود. در این مثال name توسط اسکریپت آغاز کننده برنامه با به اصطلاح entry point برنامه ما مقدار دهی خواهد شد که در ادامه این فایل را بررسی میکنیم.

src/main.js : 
import App from './App.svelte';

const app = new App({
target: document.body,
props: {
name: 'world'
}
});

export default app;
فایل main.js فایل آغاز کننده برنامه و entry-point ما است. در خط اول، کامپوننت اصلی برنامه را که قبلا بررسی کردیم، به این فایل import میکنیم. در خط سوم یک object از این کامپوننت گرفته و آن را مقداردهی خواهیم کرد. در خط 4 مقدار target را برابر با محتوای صفحه html نهایی برنامه قرار میدهیم که در مسیر public/index.html تولید خواهد شد و در نهایت خصیصه‌های کامپوننت خود (props) را که قبلا تعریف کردیم، مقدار دهی میکنیم؛ خطوط 5-7 .
اگر به خاطر داشته باشید، ما در کامپوننت App.svelte یک متغیر به نام name را به عنوان یک props (خصیصه) export کرده بودیم و در اینجا مقدار این متغیر را برابر با world قرار دادیم. 
در خط آخر  (10) هم مانند تمام فایل‌ها و ماژول‌های جاوا اسکریپت، این object را برای استفاده export میکنیم.


نکته: پیش نیاز استفاده از svelte، درک نسبی روی مباحث مرتبط با JavaScript و Html و Css است. لذا در این آموزش من به جزئیات مرتبط با این سه مورد وارد نمیشوم و سعی میکنم تمرکز بیشتر بر روی مباحث مرتبط با خود svelte باشد. 
در بخش بعدی با ایجاد یک پروژه جدید، با سایر امکانات svelte و همینطور syntax آن بیشتر آشنا خواهیم شد.
مطالب
آپلود فایل‌ها در یک برنامه‌ی Angular به کمک کامپوننت ng2-file-upload
در مطلب «بررسی روش آپلود فایل‌ها از طریق یک برنامه‌ی Angular به یک برنامه‌ی ASP.NET Core» روش عمومی آپلود فایل‌ها را بررسی کردیم. آن مطلب وابستگی به کامپوننت خاصی ندارد و عمومی است. در مطلب جاری می‌خواهیم روش دیگری را مبتنی بر کامپوننت ng2-file-upload بررسی کنیم که به همراه نمایش درصد پیشرفت ارسال فایل‌ها، امکان انتخاب بهتر نوع فایل‌های آپلودی و همچنین امکان مشاهده‌ی لیست کامل فایل‌های انتخاب شده و امکان حذف مواردی از آن، پیش از ارسال نهایی است.



پیشنیازهای کار با کامپوننت ng2-file-upload

برای شروع به کار با کامپوننت ng2-file-upload، ابتدا نیاز است بسته‌ی npm آن‌را نصب کرد:
 >npm install ng2-file-upload --save

همچنین یک کامپوننت آزمایشی را هم به برنامه (دقیقا همان مثال مطلب قبلی) جهت اعمال آن اضافه می‌کنیم:
 >ng g c UploadFile/ng2-file-upload-test

پس از آن نیاز است به ماژولی که این کامپوننت جدید در آن قرار دارد، مدخل FileUploadModule کامپوننت ng2-file-upload را افزود:
import { FileUploadModule } from "ng2-file-upload";

@NgModule({
  imports: [
    FileUploadModule
  ]
در غیراینصورت خطای شناخته نشدن خاصیت uploader را در حین اعمال این کامپوننت مشاهده خواهید کرد.


تکمیل Ng2FileUploadTestComponent جهت اعمال ng2-file-upload

اکنون به کلاس کامپوننت جدیدی که ایجاد کردیم، مراجعه کرده و تغییرات ذیل را اعمال می‌کنیم:
import { FileUploader, FileUploaderOptions } from "ng2-file-upload";
import { Ticket } from "./../ticket";

export class Ng2FileUploadTestComponent implements OnInit {
  fileUploader: FileUploader;
  model = new Ticket();
در اینجا یک خاصیت عمومی از نوع FileUploader تعریف شده‌است که در اختیار قالب این کامپوننت قرار خواهد گرفت. همچنین شیء مدل فرم نیز همانند مطلب «بررسی روش آپلود فایل‌ها از طریق یک برنامه‌ی Angular به یک برنامه‌ی ASP.NET Core» تهیه شده‌است. هدف این است که بررسی کنیم علاوه بر ارسال فایل‌ها، چگونه می‌توان اطلاعات یک فرم را نیز به سمت سرور ارسال کرد.


وهله سازی از کامپوننت ng2-file-upload و انجام تنظیمات اولیه‌ی آن

پس از تعریف خاصیت عمومی fileUploader، اکنون نوبت به وهله سازی آن است:
    this.fileUploader = new FileUploader(
      <FileUploaderOptions>{
        url: "api/SimpleUpload/SaveTicket",
        headers: [
          { name: "X-XSRF-TOKEN", value: this.getCookie("XSRF-TOKEN") },
          { name: "Accept", value: "application/json" }
        ],
        isHTML5: true,
        // allowedMimeType: ["image/jpeg", "image/png", "application/pdf", "application/msword", "application/zip"]
        allowedFileType: [
          "application",
          "image",
          "video",
          "audio",
          "pdf",
          "compress",
          "doc",
          "xls",
          "ppt"
        ],
        removeAfterUpload: true,
        autoUpload: false,
        maxFileSize: 10 * 1024 * 1024
      }
    );
- در اینجا url، مسیر اکشن متدی را در سمت سرور مشخص می‌کند که قرار است فایل‌های ارسالی را دریافت و ذخیره کند.
- اگر برنامه از نکات anti-forgery token استفاده می‌کند، این کامپوننت برخلاف روش مطرح شده‌ی در مطلب مشابه قبلی، هیچ هدری را به سمت سرور ارسال نمی‌کند. بنابراین نیاز است کوکی مرتبط را خودمان یافته و سپس به لیست هدرها اضافه کنیم. در اینجا روش استخراج یک کوکی را توسط کدهای جاوا اسکریپتی مشاهده می‌کنید:
  getCookie(name: string): string {
    const value = "; " + document.cookie;
    const parts = value.split("; " + name + "=");
    if (parts.length === 2) {
      return decodeURIComponent(parts.pop().split(";").shift());
    }
  }

- برای محدود سازی فایل‌های ارسالی توسط این کامپوننت، دو روش وجود دارد:
الف) مشخص سازی مقدار خاصیت allowedMimeType
همانطور که مشاهده می‌کنید، در اینجا باید mime type فایل‌های مجاز را مشخص کرد.
ب) مشخص سازی مقدار خاصیت allowedFileType
برخلاف تصور، در اینجا از پسوند فایل‌ها استفاده نمی‌کند و از یک لیست از پیش مشخص که نمونه‌ای از آن‌را در اینجا مشاهده می‌کنید، کمک گرفته می‌شود. بنابراین اگر برای مثال تنها نیاز به ارسال تصاویر بود، مقدار image را نگه داشته و مابقی را از لیست حذف کنید.

- removeAfterUpload به این معنا است که آیا لیست نهایی که نمایش داده می‌شود، پس از آپلود باقی بماند یا خیر؟
- توسط خاصیت maxFileSize می‌توان حداکثر اندازه‌ی قابل قبول فایل‌های ارسالی را مشخص کرد.


مدیریت رخ‌دادهای کامپوننت ng2-file-upload

اکنون که وهله‌ای از این کامپوننت ساخته شده‌است، می‌توان رخ‌دادهای آن‌را نیز مدیریت کرد. برای مثال:
الف) نحوه‌ی ارسال اطلاعات اضافی به همراه یک فایل به سمت سرور
    this.fileUploader.onBuildItemForm = (fileItem, form) => {
      for (const key in this.model) {
        if (this.model.hasOwnProperty(key)) {
          form.append(key, this.model[key]);
        }
      }
    };
در اینجا شبیه به مطلب مشابه قبلی، مقادیر خواص شیء مدل، به صورت خودکار استخراج شده و به خاصیت form این کامپوننت که درحقیقت همان FormData ارسالی به سمت سرور است، اضافه می‌شوند.

ب) اطلاع یافتن از رخ‌داد خاتمه‌ی کار
رخ‌داد onCompleteAll پس از ارسال تمام فایل‌ها به سمت سرور فراخوانی می‌شود:
    this.fileUploader.onCompleteAll = () => {
      // clear the form
      // this.model = new Ticket();
    };

ج) در حین وهله سازی fileUploader، تعدادی محدودیت نیز قابل اعمال هستند. این محدودیت‌ها سبب نمایش هیچگونه پیام خطایی نمی‌شوند. فقط زمانیکه کاربر فایلی را انتخاب می‌کند، این فایل در لیست ظاهر نمی‌شود. اگر علاقمند به مدیریت این وضعیت باشید، می‌توان از رخ‌داد onWhenAddingFileFailed استفاده کرد:
    this.fileUploader.onWhenAddingFileFailed = (item, filter, options) => {
      // msg: `You can't select ${item.name} file because of the ${filter.name} filter.`
    };

د) اگر ارسال فایلی به سمت سرور با شکست مواجه شود، در ر‌خ‌دادگردان onErrorItem می‌توان به نام این فایل و اطلاعات بیشتری که از سمت سرور دریافت شده‌است، دسترسی یافت:
    this.fileUploader.onErrorItem = (fileItem, response, status, headers) => {
       //
    };

ه) اگر از سمت سرور اطلاعات JSON مانندی یا هر اطلاعات دیگری به سمت کلاینت پس از آپلود ارسال می‌شود، این اطلاعات را می‌توان در رخ‌دادگردان onSuccessItem دریافت کرد:
    this.fileUploader.onSuccessItem = (item, response, status, headers) => {
      if (response) {
        const ticket = JSON.parse(response);
        console.log(`ticket:`, ticket);
      }
    };


ارسال نهایی فرم و  فایل‌ها به سمت سرور

در پایان، با فراخوانی متد uploadAll شیء fileUploader جاری، می‌توان اطلاعات فرم و تمام فایل‌های آن‌را به سمت سرور ارسال کرد:
  submitForm(form: NgForm) {
    this.fileUploader.uploadAll();

    // NOTE: Upload multiple files in one request -> https://github.com/valor-software/ng2-file-upload/issues/671
  }
فقط باید دقت داشت که این کامپوننت هر فایل را جداگانه به سمت سرور ارسال می‌کند و برخلاف روش مطلب قبلی، همه را یکجا و در طی یک درخواست به سمت سرور ارسال نمی‌کند. اما کدهای سمت سرور آن با مطلب مشابه قبلی دقیقا یکی است و تفاوتی نمی‌کند (همان نکات قسمت «دریافت فرم درخواست پشتیبانی در سمت سرور و ذخیره‌ی فایل‌های آن‌» مطلب قبلی نیز در اینجا صادق است).


کدهای کامل کامپوننت ng2-file-upload-test.component.ts را در اینجا می‌توانید مشاهده کنید.


تکمیل قالب کامپوننت Ng2FileUploadTestComponent

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

افزودن فیلد اضافی توضیحات به فرم

<div class="container">
  <h3>Support Form(ng2-file-upload)</h3>
  <form #form="ngForm" (submit)="submitForm(form)" novalidate>
    <div class="form-group" [class.has-error]="description.invalid && description.touched">
      <label class="control-label">Description</label>
      <input #description="ngModel" required type="text" class="form-control"
        name="description" [(ngModel)]="model.description">
      <div *ngIf="description.invalid && description.touched">
        <div class="alert alert-danger"  *ngIf="description.errors.required">
          description is required.
        </div>
      </div>
    </div>
هدف از این فیلد این است که شیء Ticket را وهله سازی و مقدار دهی کند. از مقدار آن در رخ‌دادگردان onBuildItemForm که پیشتر توضیح داده شد، استفاده می‌شود.

تعریف ویژه‌ی فیلد ارسال فایل‌ها به سمت سرور

    <div class="form-group">
      <label class="control-label">Screenshot(s)</label>
      <input required type="file" multiple ng2FileSelect [uploader]="fileUploader"
        class="form-control" name="screenshot">
    </div>
در اینجا ابتدا دایرکتیو ng2FileSelect ذکر می‌شود تا کامپوننت مرتبط فعالسازی شود. سپس خاصیت uploader این دایرکتیو توسط خاصیت fileUploader که پیشتر در کامپوننت، وهله سازی و تنظیم شد، مقدار دهی می‌شود.
ذکر ویژگی استاندارد multiple را نیز در اینجا مشاهده می‌کنید. وجود آن سبب خواهد شد تا کاربر بتواند چندین فایل را با هم انتخاب کند. اگر نیازی به ارسال چندین فایل نیست، این ویژگی را حذف کنید.

نمایش لیست فایل‌ها و نمایش درصد پیشرفت آپلود آن‌ها

جدولی را که در تصویر ابتدای بحث مشاهده کردید، به صورت ذیل شکل می‌گیرد (کدهای آن در همان صفحه‌ی توضیحات کامپوننت نیز موجود هستند):
    <div style="margin-bottom: 10px" *ngIf="fileUploader.queue.length">
      <h3>Upload queue</h3>
      <p>Queue length: {{ fileUploader?.queue?.length }}</p>
      <table class="table">
        <thead>
          <tr>
            <th width="50%">Name</th>
            <th>Size</th>
            <th>Progress</th>
            <th>Status</th>
            <th>Actions</th>
          </tr>
        </thead>
        <tbody>
          <tr *ngFor="let item of fileUploader.queue">
            <td><strong>{{ item?.file?.name }}</strong></td>
            <td nowrap>{{ item?.file?.size/1024/1024 | number:'.2' }} MB</td>
            <td>
              <div class="progress" style="margin-bottom: 0;">
                <div class="progress-bar" role="progressbar" [ngStyle]="{ 'width': item.progress + '%' }"></div>
              </div>
            </td>
            <td class="text-center">
              <span *ngIf="item.isError"><i class="glyphicon glyphicon-remove"></i></span>
            </td>
            <td nowrap>
              <button type="button" class="btn btn-danger btn-xs" (click)="item.remove()">
                <span class="glyphicon glyphicon-trash"></span> Remove
              </button>
            </td>
          </tr>
        </tbody>
      </table>

      <div>
        <div>
          Queue progress:
          <div class="progress">
            <div class="progress-bar" role="progressbar" [ngStyle]="{ 'width': fileUploader.progress + '%' }"></div>
          </div>
        </div>
        <button type="button" class="btn btn-danger btn-s" (click)="fileUploader.clearQueue()"
          [disabled]="!fileUploader.queue.length">
          <span class="glyphicon glyphicon-trash"></span> Remove all
        </button>
      </div>
    </div>
شیء fileUploader وهله سازی شده‌ی در کامپوننت این قالب، دارای خاصیت queue است. در این خاصیت، لیست فایل‌های انتخابی توسط کاربر درج می‌شوند. برای مثال مقدار fileUploader?.queue?.length مساوی تعداد فایل‌های انتخابی توسط کاربر است. بنابراین می‌توان حلقه‌ای را بر روی آن تشکیل داد و مشخصات این فایل‌ها را در صفحه نمایش داد. همچنین هر آیتم آن دارای متد remove نیز هست. کار این متد، حذف این آیتم از لیست queue است و یا اگر متد fileUploader.clearQueue فراخوانی شود، تمام آیتم‌های این لیست را حذف می‌کند.
در اینجا از progress-bar بوت استرپ برای نمایش درصد آپلود فایل‌ها استفاده شده‌است:
              <div class="progress" style="margin-bottom: 0;">
                <div class="progress-bar" role="progressbar" [ngStyle]="{ 'width': item.progress + '%' }"></div>
              </div>
این کامپوننت ارسال فایل، خاصیت item.progress هر فایل موجود در queue را مدام به روز رسانی می‌کند. به همین جهت می‌توان از آن جهت تغییر عرض پیشرفت progress-bar بوت استرپ استفاده کرد.

غیرفعال کردن دکمه‌ی ارسال، در صورت عدم انتخاب یک فایل

    <button class="btn btn-primary" [disabled]="form.invalid || !fileUploader.queue.length"
      type="submit">Submit</button>
  </form>
اگر بخواهیم انتخاب حداقل یک فایل را توسط کاربر اجباری کنیم، می‌توان خاصیت disabled دکمه‌ی ارسال را به طول صف یا fileUploader.queue.length نیز متصل کرد.


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید.
نظرات مطالب
بررسی چند نکته در مورد ارث بری کلاس‌ها در #C
این مورد هم می‌توان به جمع بندی اضافه نمود:

اگر یک نمونه از کلاس فرزند با نوع والد (()Parent pc = new Child) بسازیم و قبل از آن هیج نمونه ای از فرزند و والد نساخته باشیم در اینصورت بترتیب سازنده استاتیک فرزند؛ سازنده استاتیک والد؛ سازنده والد و سازنده فرزند اجرا می‌شوند. یعنی سازنده استاتیک فرزند قبل از سازنده استاتیک والد اجرا می‌شود در حالی که سازنده  غیراستاتیک والد قبل از سازنده غیراستاتیک فرزند اجرا می‌شود.