مطالب
CoffeeScript #5

Classes

کلاس نه تنها در جاوااسکریپت بلکه در سایر زبان‌ها از جمله CoffeeScript نیز، بسیار مفید است.

در پشت صحنه، CoffeeScript برای ایجاد کلاس از prototype استفاده می‌کند. برای ساختن کلاس در CoffeeScript از کلمه کلیدی class باید استفاده کنید.

class Animal
نتیجه‌ی کامپایل مثال بالا می‌شود:
var Animal;
Animal = (function() {
  function Animal() {}
  return Animal;
})();
در مثال بالا، Animal نام کلاس و همچنین نامی است که برای ایجاد یک نمونه از آن می‌توانید استفاده کنید. در پشت صحنه CoffeeScript با استفاده از سازنده توابع این کار را انجام می‌دهد. یعنی شما می‌توانید با استفاده از کلمه کلیدی new  یک نمونه از کلاس نوشته شده را بسازید.
animal = new Animal
تعریف سازنده برای کلاس بسیار ساده است. فقط کافی است از کلمه کلیدی constructor به عنوان یک تابع در کلاس تعریف شده استفاده کنید. این تابع شبیه به initialize در Ruby و __init__ در Python است.
class Animal
  constructor: (name) ->
    @name = name
نتیجه کامپایل کد بالا می‌شود:
var Animal;
Animal = (function() {
  function Animal(name) {
    this.name = name;
  }
  return Animal;
})();
همچنین CoffeeScript امکان خلاصه نویسی را در سازنده کلاس نیز ایجاد کرده است. برای اینکار با اضافه کردن @ به آرگومان‌های تابع سازنده به صورت پیشوند این کار انجام می‌شود. مثال زیر معادل مثال قبل است که به صورت دستی مقدار دهی انجام شده است.
class Animal
  constructor: (@name) ->
و برای استفاده از این کلاس
animal = new Animal "Cat"
alert "Animal is a #{animal.name}"

Instance properties

اضافه کردن property به یک کلاس بسیار ساده و راحت است، syntax این کار دقیقا مانند اضافه کردن property به یک object است. فقط نکته ای که باید رعایت شود میزان تو رفتگی property نوشته شده است که به طور صحیح در داخل بدنه کلاس قرار بگیرد.
class Animal
  price: 5

  sell: (customer) ->

animal = new Animal
animal.sell(new Customer)
نتیجه کامپایل کد بالا می‌شود:
var Animal, animal;

Animal = (function() {
  function Animal() {}

  Animal.prototype.price = 5;

  Animal.prototype.sell = function(customer) {};

  return Animal;

})();

animal = new Animal;
animal.sell(new Customer);

Static properties

برای تعریف property به صورت static باید کلمه کلیدی this را به ابتدای آن اضافه کنید.
class Animal
  this.find = (name) ->

Animal.find("Dog")
در قسمت‌های قبل گفته شد، که به جای this می‌توان از @ استفاده کرد، در اینجا نیز می‌توان چنین کاری را انجام داد.
class Animal
  @find: (name) ->

Animal.find("Dog")
نتیجه‌ی کامپایل آن می‌شود:
var Animal;

Animal = (function() {
  function Animal() {}

  Animal.find = function(name) {};

  return Animal;

})();

Animal.find("Dog");
اشتراک‌ها
بازی Arrow hero
A minimalist game where your goal is to match your inputs with an unstoppable continuous overwelming flow of arrows.  Play
بازی Arrow hero
مطالب
انتخاب layoutهای متفاوت در برنامه‌های Angular
شاید برای شما هم پیش آمده باشد که در یک برنامه‌ی Angular بخواهید layoutهای مختلفی داشته باشید؛ مثلا هنگام لاگین، طبق عرف کار باید هدر و فوتر صفحه از بین بروند و فقط فرم لاگین نمایش داده شود و یا بخواهید هنگام لاگین، یک layout مخصوص پنل مدیریتی داشته باشید و یا …

قبل از شروع، فرض را بر آن می‌گیریم که حداقل نیاز‌های یک پروژه‌ی Angular را آماده کرده اید. سپس یک پوشه‌ی جدید را به نام layout می‌سازیم و layout‌های مربوطه را در آن ایجاد میکنیم. با دستور زیر یک کامپوننت جدید را که layout ما خواهد شد، با نام دلخواهی ایجاد می‌کنیم:
ng g c Loginlayout
 و همچنین یک کامپوننت دیگر را برای صفحه‌ی اصلی به نام homelayout می‌سازیم:
ng g c homelayout

در ادامه Loginlayout را باز کرده و تنظیمات زیر را لحاظ کنید:
<div style="width: 100%;height: 250px;background-color: aquamarine">
   <h1>Header</h1>
</div>

<router-outlet></router-outlet>

<div style="width: 100%;height: 250px;background-color: brown">
  <h1>Foother</h1>
</div>
در اینجا یک هدر و یک فوتر را ساخته و <router-outlet></router-outlet> را در آن قرار می‌دهیم که قسمت پویای ما خواهد شد.

اکنون وارد کامپوننت home layout شوید و دقیقا مانند قبل، تنظیمات دلخواه خود را انجام داده و همچنین <router-outlet></router-outlet> راهم درون جائیکه می‌خواهید به صورت پویا باشد بگذارید.
تا اینجا ما فقط layoutها را طراحی کردیم. در ادامه در ریشه‌ی پروژه، سه کامپوننت را به نام‌های Home , Login, About میسازیم. Home و About دارای یک قالب و Login هم داری قالب مخصوص به خود میباشد.

سپس وارد کامپوننت آغازین برنامه (app.component.html) شوید و در آن <router-outlet></router-outlet> را وارد کنید. در اینجا دیگر نیازی به نوشتن تگ‌های خاص دیگری نیست.

در ادامه به اصلی‌ترین قسمت، یعنی مسیریابی می‌رسیم. وارد app.module.ts شوید و آن را به صورت زیر تنظیم کنید:
export const routes: Routes = [    
           { 
                path: 'Loginlayout', 
                component: LoginlayoutComponent ,
                children: [
                  { path: 'Login', component: LoginComponent, pathMatch: 'full'}                 
                ]
            },
            { 
                path: 'Homelayout', 
                component: HomelayoutComponent,
                children: [
                  { path: 'About', component: AbouComponent, pathMatch: 'full'},
                  { path: 'Home', component: HomeComponent, pathMatch: 'full'}
                ]
            }          
];
همانطورکه ملاحظه می‌کنید، مسیریابی بالا شامل مسیریابی‌های تو در تویی است. در اینجا کامپوننت‌های Home و About درون HomelayoutComponent بارگذاری می‌شوند و خود HomelayoutComponent  نیز درون app.component.
همچنین برای اینکه مشخص شود کدام کامپوننت به عنوان کامپوننت پیشفرض نمایش داده شود، به صورت زیر عمل میکنیم:
path: '', 
component: HomelayoutComponent,
children: [
  { path: '', component:HomeComponent, pathMatch: 'full'}         
]
به  این روش میتوانید هر تعداد layout ایی را که میخواهید، ایجاد کنید.

کدهای کامل این مطلب را می‌توانید از اینجا دریافت و یا به صورت آنی آزمایش کنید.
مطالب
React 16x - قسمت 8 - ترکیب کامپوننت‌ها - بخش 2 - مدیریت state
در ادامه‌ی بحث ترکیب کامپوننت‌ها، پس از نمایش لیستی از کامپوننت‌های شمارشگر و مقدار دهی عدد آغازین آن‌ها، به همراه مدیریت حذف هر ردیف در قسمت قبل، اکنون می‌خواهیم دکمه‌ای را اضافه کنیم تا تمام شمارشگرها را به حالت اول خودشان بازگرداند. برای این منظور دکمه‌ی Reset را به ابتدای المان‌های کامپوننت Counters اضافه می‌کنیم:
<button
  onClick={this.handleReset}
  className="btn btn-primary btn-sm m-2"
>
  Reset
</button>
سپس متد رویدادگردان handleReset آن‌را به صورت زیر با تنظیم مقدار value هر counter به صفر و بازگشت آن و در نهایت به روز رسانی state کامپوننت با این آرایه‌ی جدید، پیاده سازی می‌کنیم:
  handleReset = () => {
    const counters = this.state.counters.map(counter => {
      counter.value = 0;
      return counter;
    });
    this.setState({ counters }); // = this.setState({ counters: counters });
  };


اکنون پس از ذخیره سازی فایل counters.jsx و بارگذاری مجدد برنامه در مرورگر، هرچقدر بر روی دکمه‌ی Reset کلیک کنیم ... اتفاقی رخ نمی‌دهد! حتی اگر به افزونه‌ی React developer tools نیز مراجعه کنیم، مشاهده خواهیم کرد که عمل تنظیم value به صفر، در تک تک کامپوننت‌های شمارشگر، به درستی صورت گرفته‌است؛ اما تغییرات به DOM اصلی منعکس نشده‌اند:


البته اگر به همین تصویر دقت کنید، هنوز مقدار count، در state آن 4 است. علت اینجا است که هر کدام از Counterها دارای local state خاص خودشان هستند و در آن‌ها، مقدار count به صورت زیر مقدار دهی شده‌است که در آن تغییرات بعدی این this.props.value، متصل به count نیست و count، فقط یکبار مقدار دهی می‌شود:
class Counter extends Component {
  state = {
    count: this.props.counter.value
  };
این قطعه‌ی از کد، تنها زمانی اجرا می‌شود که یک وهله از کلاس کامپوننت Counter، در حال ایجاد است. به همین جهت زمانیکه صفحه برای بار اول بارگذاری می‌شود، مقدار آغازین count به درستی دریافت می‌شود. اما با کلیک بر روی دکمه‌ی Reset، هرچند مقدار value هر شیء counter تعریف شده‌ی در کامپوننت والد تغییر می‌کند، اما local state کامپوننت‌های فرزند به روز رسانی نمی‌شوند و مقدار جدید value را دریافت نمی‌کنند. برای رفع یک چنین مشکلی نیاز است یک مرجع مشخص را برای مقدار دهی stateهای کامپوننت‌های فرزند ایجاد کنیم.


حذف Local state

اکنون می‌خواهیم در کامپوننت Counter، قسمت local state آن‌را به طور کامل حذف کرده و تنها از this.props جهت دریافت اطلاعاتی که نیاز دارد، استفاده کنیم. به این نوع کامپوننت‌ها، «‍Controlled component» نیز می‌گویند. یک کامپوننت کنترل شده دارای local state خاص خودش نیست و تمام داده‌های دریافتی را از طریق this.props دریافت می‌کند و هر زمانیکه قرار است داده‌ای تغییر کند، رخ‌دادی را به والد خود صادر می‌کند. بنابراین این کامپوننت به طور کامل توسط والد آن کنترل می‌شود.
برای پیاده سازی این مفهوم، ابتدا خاصیت state کامپوننت Counter را حذف می‌کنیم. سپس تمام ارجاعات به this.state را در این کامپوننت یافته و آن‌ها را تغییر می‌دهیم. اولین ارجاع، در متد handleIncrement به صورت this.state.count تعریف شده‌است:
  handleIncrement = () => {
    this.setState({ count: this.state.count + 1 });
  };
 از این جهت که دیگر دارای local state نیستیم، داشتن متد this.setState در اینجا بی‌مفهوم است. در یک کامپوننت کنترل شده، هر زمانیکه قرار است داده‌ای ویرایش شود، این کامپوننت باید رخ‌دادی را صادر کرده و از والد خود درخواست تغییر اطلاعات را ارائه دهد؛ شبیه به this.props.onDelete ای که در قسمت قبل کامل کردیم. بنابراین کل متد handleIncrement را نیز حذف می‌کنیم. اینبار رخ‌داد onClick، سبب بروز رخداد onIncrement در والد خود خواهد شد:
<button
  onClick={() => this.props.onIncrement(this.props.counter)}
  className="btn btn-secondary btn-sm"
>
  Increment
</button>
همچنین دو متد دیگری که ارجاعی را به this.state داشتند، به صورت زیر جهت استفاده‌ی از this.props.counter.value، به روز رسانی می‌شوند:
  getBadgeClasses() {
    let classes = "badge m-2 badge-";
    classes += this.props.counter.value === 0 ? "warning" : "primary";
    return classes;
  }

  formatCount() {
    const { value } = this.props.counter; // Object Destructuring
    return value === 0 ? "Zero" : value;
  }
تا اینجا به صورت کامل local state این کامپوننت حذف و با this.props جایگزین شده و در نتیجه تحت کنترل کامپوننت والد آن قرار می‌گیرد.

در ادامه به کامپوننت Counters مراجعه کرده و متد رویدادگردانی را جهت پاسخگویی به رخ‌داد onIncrement رسیده‌ی از کامپوننت‌های فرزند، تعریف می‌کنیم:
  handleIncrement = counter => {
    console.log("handleIncrement", counter);
  };
سپس ارجاعی از این متد را به ویژگی onIncrement تعریف شده‌ی در المان Counter، متصل می‌کنیم:
  <Counter
    key={counter.id}
    counter={counter}
    onDelete={this.handleDelete}
    onIncrement={this.handleIncrement}
  />
اکنون هر زمانیکه بر روی دکمه‌ی Increment کلیک شود، this.props.onIncrement آن، سبب فراخوانی متد handleIncrement والد خود خواهد شد.

پیاده سازی کامل متد handleIncrement اینبار به صورت زیر است:
  handleIncrement = counter => {
    console.log("handleIncrement", counter);
    const counters = [...this.state.counters]; // cloning an array
    const index = counters.indexOf(counter);
    counters[index] = { ...counter }; // cloning an object
    counters[index].value++;
    console.log("this.state.counters", this.state.counters[index]);
    this.setState({ counters });
  };
همانطور که در قسمت‌های قبل نیز عنوان شد، در React نباید مقدار state را به صورت مستقیم ویرایش کرد؛ مانند مراجعه‌ی مستقیم به this.state.counters[index] و سپس تغییر خاصیت value آن‌. بنابراین باید یک clone از آرایه‌ی counters و سپس یک clone از شیء counter رسیده‌ی از کامپوننت فرزند را ایجاد کنیم تا این cloneها دیگر ارجاعی را به اشیاء اصلی ساخته شده‌ی از روی آن‌ها نداشته باشند (مهم‌ترین خاصیت یک clone) تا اگر خاصیت و مقداری را در آن‌ها تغییر دادیم، دیگر به شیء اصلی که از روی آن‌ها clone شده‌اند، منعکس نشوند. در اینجا از spread operator برای ایجاد این cloneها استفاده شده‌است. اکنون مقادیر خواص این cloneها را تغییر می‌دهیم و درنهایت این counters جدید را که خودش نیز یک clone است، به متد this.setState جهت به روز رسانی UI و همچنین state کامپوننت، ارسال می‌کنیم.

تا اینجا اگر برنامه را ذخیره کرده و منتظر به روز رسانی آن در مرورگر شویم، با کلیک بر روی Reset، تمام کامپوننت‌ها با هر وضعیتی که پیشتر داشته باشند، به حالت اول خود باز می‌گردند:



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


در ادامه می‌خواهیم یک منوی راهبری (یا همان NavBar در بوت استرپ) را به بالای صفحه اضافه کنیم و در آن جمع کل تعداد Counterهای رندر شده را نمایش دهیم؛ مانند نمایش تعداد آیتم‌های انتخاب شده‌ی توسط یک کاربر، در یک سبد خرید. برای پیاده سازی آن، درخت کامپوننت‌های React را مطابق شکل فوق تغییر می‌دهیم. یعنی مجددا کامپوننت App را در به عنوان کامپوننت ریشه‌ای انتخاب کرده که سایر کامپوننت‌ها از آن مشتق می‌شوند و همچنین کامپوننت مجزای NavBar را نیز اضافه خواهیم کرد.
برای این منظور به index.js مراجعه کرده و مجددا کامپوننت App را که غیرفعال کرده بودیم و بجای آن Counters را نمایش می‌دادیم، اضافه می‌کنیم:
import App from "./App";

ReactDOM.render(<App />, document.getElementById("root"));

سپس کامپوننت جدید NavBar را توسط فایل جدید src\components\navbar.jsx اضافه می‌کنیم تا منوی راهبری سایت را نمایش دهد:
import React, { Component } from "react";

class NavBar extends Component {
  render() {
    return (
      <nav className="navbar navbar-light bg-light">
        <a className="navbar-brand" href="#">
          Navbar
        </a>
      </nav>
    );
  }
}

export default NavBar;

اکنون به App.js مراجعه کرده و متد render آن‌را جهت نمایش درخت کامپوننت‌هایی که مشاهده کردید، تکمیل می‌کنیم:
import "./App.css";

import React from "react";

import Counters from "./components/counters";
import NavBar from "./components/navbar";

function App() {
  return (
    <React.Fragment>
      <NavBar />
      <main className="container">
        <Counters />
      </main>
    </React.Fragment>
  );
}

export default App;
ابتدا کامپوننت NavBar در بالای صفحه رندر می‌شود و سپس کامپوننت Counters در میانه‌ی صفحه. چون در اینجا چندین المان قرار است رندر شوند، از React.Fragment برای محصور کردن آن‌ها استفاده کرده‌ایم.
تا اینجا اگر برنامه را ذخیره کنیم تا در مرورگر بارگذاری مجدد شود، چنین شکلی حاصل شده‌است:


اکنون می‌خواهیم تعداد کامپوننت‌های شمارشگر را در navbar نمایش دهیم. پیشتر state کامپوننت Counters را توسط props، به کامپوننت‌های Counter رندر شده‌ی توسط آن انتقال دادیم. استفاده‌ی از این ویژگی به دلیل وجود رابطه‌ی والد و فرزندی بین این کامپوننت‌ها میسر شد. اما همانطور که در تصویر درخت کامپوننت‌های جدید تشکیل شده مشاهده می‌کنید، رابطه‌ی والد و فرزندی بین دو کامپوننت Counters و NavBar وجود ندارد. بنابراین اکنون این سؤال مطرح می‌شود که چگونه باید تعداد کل شمارشگرهای کامپوننت Counters را به کامپوننت NavBar، برای نمایش آن‌ها انتقال داد؟ در یک چنین حالت‌هایی که رابطه‌ی والد و فرزندی بین کامپوننت‌ها وجود ندارد و می‌خواهیم آن‌ها را همگام سازی کنیم و داده‌هایی را بین آن‌ها به اشتراک بگذاریم، باید state را به یک سطح بالاتر انتقال داد. یعنی در این مثال باید state کامپوننت Counters را به والد آن که اکنون کامپوننت App است، منتقل کرد. پس از آن چون هر دو کامپوننت NavBar و Counters، از کامپوننت App مشتق می‌شوند، اکنون می‌توان این state را به تمام فرزندان App توسط props منتقل کرد و به اشتراک گذاشت.


انتقال state به یک سطح بالاتر

برای انتقال state به یک سطح بالاتر، به کامپوننت Counters مراجعه کرده و خاصیت state آن‌را به همراه تمامی متدهایی که آن‌را تغییر می‌دهند و از آن استفاده می‌کنند، انتخاب و cut می‌کنیم. سپس به کامپوننت App مراجعه کرده و آن‌ها را در اینجا paste می‌کنیم. یعنی خاصیت state و متدهای handleDelete، handleReset و handleIncrement را از کامپوننت Counters به کامپوننت App منتقل می‌کنیم. این مرحله‌ی اول است. سپس نیاز است به کامپوننت Counters مراجعه کرده و ارجاعات به state و متدهای یاد شده را توسط props اصلاح می‌کنیم. برای این منظور ابتدا باید این props را در کامپوننت App مقدار دهی کنیم تا بتوانیم آن‌ها را در کامپوننت Counters بخوانیم؛ یعنی متد render کامپوننت App، تمام این خواص و متدها را باید به صورت ویژگی‌هایی به تعریف المان Counters اضافه کند تا خاصیت props آن بتواند به آن‌ها دسترسی داشته باشد:
  render() {
    return (
      <React.Fragment>
        <NavBar />
        <main className="container">
          <Counters
            counters={this.state.counters}
            onReset={this.handleReset}
            onIncrement={this.handleIncrement}
            onDelete={this.handleDelete}
          />
        </main>
      </React.Fragment>
    );
  }

پس از این تعاریف می‌توانیم به کامپوننت Counters بازگشته و ارجاعات فوق را توسط خاصیت props، در متد render آن اصلاح کنیم:
  render() {
    return (
      <div>
        <button
          onClick={this.props.onReset}
          className="btn btn-primary btn-sm m-2"
        >
          Reset
        </button>
        {this.props.counters.map(counter => (
          <Counter
            key={counter.id}
            counter={counter}
            onDelete={this.props.onDelete}
            onIncrement={this.props.onIncrement}
          />
        ))}
      </div>
    );
  }
در اینجا سه رویدادگردان و یک خاصیت counters، از طریق خاصیت props والد کامپوننت Counter که اکنون کامپوننت App است، خوانده می‌شوند.

پس از این نقل و انتقالات، اکنون می‌توانیم تعداد counters را در NavBar نمایش دهیم. برای این منظور ابتدا در کامپوننت App، به همان روشی که ویژگی counters={this.state.counters} را به تعریف المان Counters اضافه کردیم، شبیه به همین کار را برای کامپوننت NavBar نیز می‌توانیم انجام دهیم تا از طریق خاصیت props آن قابل دسترسی شود و یا حتی می‌توان به صورت زیر، تنها جمع کل را به آن کامپوننت ارسال کرد:
<NavBar
    totalCounters={this.state.counters.filter(c => c.value > 0).length}
/>

سپس در کامپوننت NavBar، عدد totalCounters فوق را که به تعداد کامپوننت‌هایی که مقدار value آن‌ها بیشتر از صفر است، اشاره می‌کند، از طریق خاصیت props خوانده و نمایش می‌دهیم:
class NavBar extends Component {
  render() {
    return (
      <nav className="navbar navbar-light bg-light">
        <a className="navbar-brand" href="#">
          Navbar{" "}
          <span className="badge badge-pill badge-secondary">
            {this.props.totalCounters}
          </span>
        </a>
      </nav>
    );
  }
}
که با ذخیره کردن این فایل و بارگذاری مجدد برنامه در مرورگر، به خروجی زیر خواهیم رسید:



کامپوننت‌های بدون حالت تابعی

اگر به کدهای کامپوننت NavBar دقت کنیم، تنها یک تک متد render در آن ذکر شده‌است و تمام اطلاعات مورد نیاز آن نیز از طریق props تامین می‌شود و دارای state و یا هیچ رویدادگردانی نیست. یک چنین کامپوننتی را می‌توان به یک «Stateless Functional Component» تبدیل کرد؛ کامپوننت‌های بدون حالت تابعی. در اینجا بجای اینکه از یک کلاس برای تعریف کامپوننت استفاده شود، می‌توان از یک function استفاده کرد (به همین جهت به آن functional می‌گویند). احتمالا نمونه‌ی آن‌را با کامپوننت App پیش‌فرض قالب create-react-app نیز مشاهده کرده‌اید که در آن فقط یک ()function App وجود دارد. البته در کدهای فوق چون نیاز به ذکر state، در کامپوننت App وجود داشت، آن‌را از حالت تابعی، به حالت کلاس استاندارد کامپوننت، تبدیل کردیم.
اگر بخواهیم کامپوننت بدون حالت NavBar را نیز تابعی کنیم، می‌توان به صورت زیر عمل کرد:
import React from "react";

// Stateless Functional Component
const NavBar = props => {
  return (
    <nav className="navbar navbar-light bg-light">
      <a className="navbar-brand" href="#">
        Navbar{" "}
        <span className="badge badge-pill badge-secondary">
          {props.totalCounters}
        </span>
      </a>
    </nav>
  );
};

export default NavBar;
برای اینکار قسمت return متد render کامپوننت را cut کرده و به داخل تابع NavBar منتقل می‌کنیم. بدنه‌ی این تابع را هم می‌توان توسط میان‌بر sfc که مخفف Stateless Functional Component است، در VSCode تولید کرد.
پیشتر در کامپوننت NavBar از شیء this استفاده شده بود. این روش تنها با کلاس‌های استاندارد کامپوننت کار می‌کند. در اینجا باید props را به عنوان پارامتر متد دریافت (همانند مثال فوق) و سپس از آن استفاده کرد.

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

یک نکته: امکان Destructuring Arguments نیز در اینجا وجود دارد. یعنی بجای اینکه یکبار props را به عنوان پارامتر دریافت کرد و سپس توسط آن به خاصیت totalCounters دسترسی یافت، می‌توان نوشت:
const NavBar = ({ totalCounters }) => {
در این حالت شیء props دریافت شده توسط ویژگی Objects Destructuring، به totalCounters تجزیه می‌شود و سپس می‌توان تنها از همین متغیر دریافتی، به صورت {totalCounters} در کدها استفاده کرد.



کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: sample-08.zip
مطالب
استفاده از چندین بانک اطلاعاتی به صورت همزمان در EF Code First
یکی از روش‌های تهیه‌ی برنامه‌های چند مستاجری، ایجاد بانک‌های اطلاعاتی مستقلی به ازای هر مشتری است؛ یا نمونه‌ی دیگر آن، برنامه‌هایی هستند که اطلاعات هر سال را در یک بانک اطلاعاتی جداگانه نگه‌داری می‌کنند. در ادامه قصد داریم، نحوه‌ی کار با این بانک‌های اطلاعاتی را به صورت همزمان، توسط EF Code first و در حالت استفاده از الگوی واحد کار و تزریق وابستگی‌ها، به همراه فعال سازی خودکار مباحث migrations و به روز رسانی ساختار تمام بانک‌های اطلاعاتی مورد استفاده، بررسی کنیم.


مشخص سازی رشته‌های متفاوت اتصالی

فرض کنید برنامه‌ی جاری شما قرار است از دو بانک اطلاعاتی مشخص استفاده کند که تعاریف رشته‌های اتصالی آن‌ها در وب کانفیگ به صورت ذیل مشخص شده‌اند:
  <connectionStrings>
    <clear />
    <add name="Sample07Context" connectionString="Data Source=(local);Initial Catalog=TestDbIoC;Integrated Security = true" providerName="System.Data.SqlClient" />
    <add name="Database2012" connectionString="Data Source=(local);Initial Catalog=testdb2012;Integrated Security = true" providerName="System.Data.SqlClient" />
  </connectionStrings>
البته، ذکر این مورد کاملا اختیاری است و می‌توان رشته‌های اتصالی را به صورت پویا نیز در زمان اجرا مشخص و مقدار دهی کرد.


تغییر Context برنامه جهت پذیرش رشته‌های اتصالی پویای قابل تغییر در زمان اجرا

اکنون که قرار است کاربران در حین ورود به برنامه، بانک اطلاعاتی مدنظر خود را انتخاب کنند و یا سیستم قرار است به ازای کاربری خاص، رشته‌ی اتصالی خاص او را به Context ارسال کند، نیاز است Context برنامه را به صورت ذیل تغییر دهیم:
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using EF_Sample07.DomainClasses;
 
namespace EF_Sample07.DataLayer.Context
{
    public class Sample07Context : DbContext, IUnitOfWork
    {
        public DbSet<Category> Categories { set; get; }
        public DbSet<Product> Products { set; get; }
 
        /// <summary>
        /// It looks for a connection string named Sample07Context in the web.config file.
        /// </summary>
        public Sample07Context()
            : base("Sample07Context")
        {
        }
 
        /// <summary>
        /// To change the connection string at runtime. See the SmObjectFactory class for more info.
        /// </summary>
        public Sample07Context(string connectionString)
            : base(connectionString)
        {
            //Note: defaultConnectionFactory in the web.config file should be set.
        }
 
 
        public void SetConnectionString(string connectionString)
        {
            this.Database.Connection.ConnectionString = connectionString;
        }
    }
}
در اینجا دو متد سازنده را مشاهده می‌کنید. سازنده‌ی پیش فرض، از رشته‌ای اتصالی با نامی مساوی Sample07Context استفاده می‌کند و سازنده‌ی دوم، امکان پذیرش یک رشته‌ی اتصالی پویا را دارد. مقدار پارامتر ورودی آن می‌تواند نام رشته‌ی اتصالی و یا حتی مقدار کامل رشته‌ی اتصالی باشد. حالت پذیرش نام رشته‌ی اتصالی زمانی مفید است که همانند مثال ابتدای بحث، این نام‌ها را پیشتر در فایل کانفیگ برنامه ثبت کرده باشید و حالت پذیرش مقدار کامل رشته‌ی اتصالی، جهت مقدار دهی پویای آن بدون نیاز به ثبت اطلاعاتی در فایل کانفیگ برنامه مفید است.

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


مدیریت سشن‌های رشته‌ی اتصالی جاری

پس از اینکه کاربر، در حین ورود مشخص کرد که از چه بانک اطلاعاتی قرار است استفاده کند و یا اینکه برنامه بر اساس اطلاعات ثبت شده‌ی او تصمیم‌گیری کرد که باید از کدام رشته‌ی اتصالی استفاده کند، نگهداری این رشته‌ی اتصالی نیاز به سشن دارد تا به ازای هر کاربر متصل به سایت منحصربفرد باشد. در مورد مدیریت سشن‌ها در برنامه‌های وب، از نکات مطرح شده‌ی در مطلب «مدیریت سشن‌ها در برنامه‌های وب به کمک تزریق وابستگی‌ها» استفاده خواهیم کرد:
using System;
using System.Threading;
using System.Web;
using EF_Sample07.DataLayer.Context;
using EF_Sample07.ServiceLayer;
using StructureMap;
using StructureMap.Web;
using StructureMap.Web.Pipeline;
 
namespace EF_Sample07.IoCConfig
{
    public static class SmObjectFactory
    {
        private static readonly Lazy<Container> _containerBuilder =
            new Lazy<Container>(defaultContainer, LazyThreadSafetyMode.ExecutionAndPublication);
 
        public static IContainer Container
        {
            get { return _containerBuilder.Value; }
        }
 
        public static void HttpContextDisposeAndClearAll()
        {
            HttpContextLifecycle.DisposeAndClearAll();
        }
 
        private static Container defaultContainer()
        {
            return new Container(ioc =>
            {
                // session manager setup
                ioc.For<ISessionProvider>().Use<DefaultWebSessionProvider>();
                ioc.For<HttpSessionStateBase>()
                   .Use(ctx => new HttpSessionStateWrapper(HttpContext.Current.Session));
 
                ioc.For<IUnitOfWork>()
                   .HybridHttpOrThreadLocalScoped()
                   .Use<Sample07Context>()
                    // Remove these 2 lines if you want to use a connection string named Sample07Context, defined in the web.config file.
                   .Ctor<string>("connectionString")
                   .Is(ctx => getCurrentConnectionString(ctx));
 
                ioc.For<ICategoryService>().Use<EfCategoryService>();
                ioc.For<IProductService>().Use<EfProductService>();
 
                ioc.For<ICategoryService>().Use<EfCategoryService>();
                ioc.For<IProductService>().Use<EfProductService>();
 
                ioc.Policies.SetAllProperties(properties =>
                {
                    properties.OfType<IUnitOfWork>();
                    properties.OfType<ICategoryService>();
                    properties.OfType<IProductService>();
                    properties.OfType<ISessionProvider>();
                });
            });
        }
 
        private static string getCurrentConnectionString(IContext ctx)
        {
            if (HttpContext.Current != null)
            {
                // this is a web application
                var sessionProvider = ctx.GetInstance<ISessionProvider>();
                var connectionString = sessionProvider.Get<string>("CurrentConnectionString");
                if (string.IsNullOrWhiteSpace(connectionString))
                {
                    // It's a default connectionString.
                    connectionString = "Database2012";
                    // this session value should be set during the login phase
                    sessionProvider.Store("CurrentConnectionStringName", connectionString);
                }
 
                return connectionString;
            }
            else
            {
                // this is a desktop application, so you can store this value in a global static variable.
                return "Database2012";
            }
        }
    }
}
در اینجا نحوه‌ی پویا سازی تامین رشته‌ی اتصالی را مشاهده می‌کنید. در مورد اینترفیس ISessionProvider و کلاس پایه HttpSessionStateBase پیشتر در مطلب «مدیریت سشن‌ها در برنامه‌های وب به کمک تزریق وابستگی‌ها» بحث شد.
نکته‌ی مهم این تنظیمات، قسمت مقدار دهی سازنده‌ی کلاس Context برنامه به صورت پویا توسط IoC Container جاری است. در اینجا هر زمانیکه قرار است وهله‌ای از Sample07Context ساخته شود، از سازنده‌ی دوم آن که دارای پارامتری به نام connectionString است، استفاده خواهد شد. همچنین مقدار آن به صورت پویا از متد getCurrentConnectionString که در انتهای کلاس تعریف شده‌است، دریافت می‌گردد.
در این متد ابتدا مقدار HttpContext.Current بررسی شده‌است. این مقدار اگر نال باشد، یعنی برنامه‌ی جاری یک برنامه‌ی دسکتاپ است و مدیریت رشته‌ی اتصالی جاری آن‌را توسط یک خاصیت Static یا Singleton تعریف شده‌ی در برنامه نیز می‌توان تامین کرد. از این جهت که در هر زمان، تنها یک کاربر در App Domain جاری برنامه‌ی دسکتاپ می‌تواند وجود داشته باشد و Singleton یا Static تعریف شدن اطلاعات رشته‌ی اتصالی، مشکلی را ایجاد نمی‌کند. اما در برنامه‌های وب، چندین کاربر در یک App Domain به سیستم وارد می‌شوند. به همین جهت است که مشاهده می‌کنید در اینجا از تامین کننده‌ی سشن، برای نگهداری اطلاعات رشته‌ی اتصالی جاری کمک گرفته شده‌است.

کلید این سشن نیز در این مثال مساوی CurrentConnectionStringName تعریف شده‌است. بنابراین در حین لاگین موفقیت آمیز کاربر، دو مرحله‌ی زیر باید طی شوند:
 sessionProvider.Store("CurrentConnectionString", "Sample07Context");
uow.SetConnectionString(WebConfigurationManager.ConnectionStrings[_sessionProvider.Get<string>("CurrentConnectionString")].ConnectionString);
ابتدا باید سشن CurrentConnectionStringName به بانک اطلاعاتی انتخابی کاربر تنظیم شود. برای نمونه در این مثال خاص، از نام رشته‌ی اتصالی مشخص شده‌ی در وب کانفیگ برنامه (مثال ابتدای بحث) به نام Sample07Context استفاده شده‌است.
سپس از متد SetConnectionString برای خواندن مقدار نام مشخص شده در سشن CurrentConnectionStringName کمک گرفته‌ایم. هرچند سازنده‌ی کلاس Context برنامه، هر دو حالت استفاده از نام رشته‌ی اتصالی و یا مقدار کامل رشته‌ی اتصالی را پشتیبانی می‌کند، اما خاصیت this.Database.Connection.ConnectionString تنها رشته‌ی کامل اتصالی را می‌پذیرد (بکار رفته در متد SetConnectionString).

تا اینجا کار پویا سازی انتخاب و استفاده از رشته‌ی اتصالی برنامه به پایان می‌رسد. هر زمانیکه قرار است Context برنامه توسط IoC Container نمونه سازی شود، به متد getCurrentConnectionString رجوع کرده و مقدار رشته‌ی اتصالی را از سشن تنظیم شده‌‌ای به نام CurrentConnectionStringName دریافت می‌کند. سپس از مقدار آن جهت مقدار دهی سازنده‌ی دوم کلاس Context استفاده خواهد کرد.


مدیریت migrations خودکار برنامه در حالت استفاده از چندین بانک اطلاعاتی

یکی از مشکلات کار با برنامه‌های چند دیتابیسی، به روز رسانی ساختار تمام بانک‌های اطلاعاتی مورد استفاده، پس از تغییری در ساختار مدل‌های برنامه است. از این جهت که اگر تمام بانک‌های اطلاعاتی به روز نشوند، کوئری‌های جدید برنامه که از خواص و فیلدهای جدید استفاده می‌کنند، دیگر کار نخواهند کرد. پویا سازی اعمال این تغییرات را می‌توان به صورت ذیل انجام داد:
using System;
using System.Data.Entity;
using System.Web;
using EF_Sample07.DataLayer.Context;
using EF_Sample07.IoCConfig;
 
namespace EF_Sample07.WebFormsAppSample
{
    public class Global : HttpApplication
    {
        void Application_Start(object sender, EventArgs e)
        {
            initDatabases();
        }
 
        private static void initDatabases()
        {
            // defined in web.config
            string[] connectionStringNames =
            {
                "Sample07Context",
                "Database2012"
            };
 
            foreach (var connectionStringName in connectionStringNames)
            {
                Database.SetInitializer(
                    new MigrateDatabaseToLatestVersion<Sample07Context, Configuration>(connectionStringName));
 
                using (var ctx = new Sample07Context(connectionStringName))
                {
                    ctx.Database.Initialize(force: true);
                }
            }
        }
 
        void Application_EndRequest(object sender, EventArgs e)
        {
            SmObjectFactory.HttpContextDisposeAndClearAll();
        } 
    }
}
نکته‌ی مهمی که در اینجا بکار گرفته شده‌است، مشخص سازی صریح سازنده‌ی شیء MigrateDatabaseToLatestVersion است. به صورت معمول در اکثر برنامه‌های تک دیتابیسی، نیازی به مشخص سازی پارامتر سازنده‌ی این کلاس نیست و در این حالت از سازنده‌ی بدون پارامتر کلاس Context برنامه استفاده خواهد شد. اما اگر سازنده‌ی آن‌را مشخص کنیم، به صورت خودکار از متد سازنده‌ای در کلاس Context استفاده می‌کند که پارامتر رشته‌ی اتصالی را به صورت پویا می‌پذیرد.
در این مثال خاص، متد initDatabases در حین آغاز برنامه فراخوانی شده‌است. منظور این است که اینکار در طول عمر برنامه تنها کافی است یکبار انجام شود و پس از آن است که EF Code first می‌تواند از رشته‌های اتصالی متفاوتی که به آن ارسال می‌شود، بدون مشکل استفاده کند. زیرا اطلاعات نگاشت کلاس‌های مدل برنامه به جداول بانک اطلاعاتی به این ترتیب است که کش می‌شوند و یا بر اساس کلاس Configuration به صورت خودکار به بانک اطلاعاتی اعمال می‌گردند.


کدهای کامل این مثال را که در حقیقت نمونه‌ی بهبود یافته‌ی مطلب «EF Code First #12» است، از اینجا می‌توانید دریافت کنید:
UoW-Sample
مطالب
React 16x - قسمت 14 - طراحی یک گرید - بخش 4 - پویاسازی تعاریف ستون‌ها
در گریدی که تا به اینجا طراحی کردیم، اگر قرار باشد بجای جدول فیلم‌ها، جدول مشتری‌ها نمایش داده شود، چکار باید کرد؟ با پیاده سازی فعلی، باید کل تعاریف MoviesTable را در کامپوننت دیگری مانند CustomersTable تکرار کنیم. به همین جهت برای پویاسازی تعاریف ستون‌ها نیاز است این قسمت را از جدول اصلی جدا کرده و به کامپوننت مستقلی مانند tableHeader منتقل کنیم.


ایجاد کامپوننت جدید tableHeader

برای پویاسازی تعاریف ستون‌ها و همچنین کم کردن مسئولیت‌های کامپوننت MoviesTable، فایل جدید src\components\common\tableHeader.jsx را ایجاد می‌کنیم تا در برگیرنده‌ی کامپوننت جدید TableHeader شود. پس از ایجاد این فایل، با استفاده از میانبرهای imrc و cc، ساختار ابتدایی کامپوننت TableHeader را تشکیل می‌دهیم. سپس به کامپوننت MoviesTable بازگشته و متد raiseSort آن‌را cut و به اینجا منتقل می‌کنیم. همچنین نیاز است کل thead جدول فیلم‌ها را نیز به اینجا منتقل کنیم. اما چون می‌خواهیم این تعاریف پویا باشند، باید امکان تعریف پویای ستون‌ها را نیز به آن اضافه کنیم. بنابراین اینترفیس این کامپوننت به صورت زیر است:
- ورودی‌های آن: آرایه‌ی ستون‌های جدول و همچنین شیء sortColumn و رخ‌داد onSort که در متد raiseSort استفاده می‌شوند.

با این توضیحات، کامپوننت TableHeader چنین شکلی را پیدا می‌کند:
import React, { Component } from "react";

class TableHeader extends Component {
  raiseSort = path => {
    console.log("raiseSort", path);
    const sortColumn = { ...this.props.sortColumn };
    if (sortColumn.path === path) {
      sortColumn.order = sortColumn.order === "asc" ? "desc" : "asc";
    } else {
      sortColumn.path = path;
      sortColumn.order = "asc";
    }
    this.props.onSort(sortColumn);
  };

  render() {
    return (
      <thead>
        <tr>
          {this.props.columns.map(column => (
            <th onClick={() => this.raiseSort(column.path)}>{column.label}</th>
          ))}
        </tr>
      </thead>
    );
  }
}

export default TableHeader;
در ابتدای آن، متد raiseSort را از کامپوننت MoviesTable به اینجا منتقل کرده‌ایم.
سپس در متد رندر آن، بر اساس آرایه‌ی columns که از props این کامپوننت دریافت خواهد شد، لیست thهای هدر را به صورت پویا رندر می‌کنیم. در اینجا ساختار مورد نیاز شیء column را نیز مشاهده می‌کنید. نیاز است یک برچسب نمایش داده شود و همچنین برای اینکه this.raiseSort نیز بتواند مجددا کار کند، نیاز است نام خاصیتی که قرار است مرتب سازی بر اساس آن انجام شود نیز مشخص باشد. بنابراین تا اینجا شیء column باید دارای دو خاصیت label و path باشد.

پس از تعریف ابتدایی کامپوننت TableHeader، به کامپوننت MoviesTable بازگشته و شروع به استفاده‌ی از آن می‌کنیم:
import TableHeader from "./common/tableHeader";

در ادامه باید آرایه‌ی columns را که به صورت props به کامپوننت TableHeader ارسال می‌شود، تعریف و مقدار دهی کنیم که تشکیل شده‌است از اشیایی با خواص path و label:
  columns = [
    { path: "title", label: "Title" },
    { path: "genre.name", label: "Genre" },
    { path: "numberInStock", label: "Stock" },
    { path: "dailyRentalRate", label: "Rate" },
    {},
    {}
  ];
در اینجا دو شیء خالی را نیز در انتهای لیست مشاهده می‌کنید که به thهای خالی مانند نمایش Like و دکمه‌ی Delete اشاره می‌کنند.
اکنون می‌توان کل تعریف thead موجود در این کامپوننت را به طور کامل با کامپوننت TableHeader ای که import کردیم، جایگزین کنیم:
  render() {
    const { movies, onDelete, onLike, onSort, sortColumn } = this.props;

    return (
      <table className="table">
        <TableHeader
          columns={this.columns}
          sortColumn={sortColumn}
          onSort={onSort}
        />
        <tbody>
در اینجا ویژگی‌های مورد نیاز جهت تامین props کامپوننت TableHeader نیز ذکر شده‌اند. this.columns را که در همین کامپوننت تعریف کردیم، sortColumn و onSort هم جزو props ارسالی به کامپوننت جاری هستند.

در این حالت اگر برنامه را اجرا کنید، بدون مشکل خروجی نهایی را رندر می‌کند؛ اما در کنسول توسعه دهندگان مرورگر یک چنین خطایی را نیز لاگ خواهد کرد:
index.js:1375 Warning: Each child in a list should have a unique "key" prop.
Check the render method of `TableHeader`. See https://fb.me/react-warning-keys for more information.
در حین تعریف رندر لیست thها در کامپوننت TableHeader، ذکر ویژگی key را فراموش کرده‌ایم. البته در اینجا می‌توان از column.path به‌عنوان key استفاده کرد، اما چون در آرایه‌ی ستون‌ها دو شیء خالی را نیز در انتهای لیست داریم، بهتر است برای این‌ها یک id را نیز تعریف کردیم تا بتوان آن‌ها را به صورت منحصربفردی شناسایی کرد:
class MoviesTable extends Component {
  columns = [
    { path: "title", label: "Title" },
    { path: "genre.name", label: "Genre" },
    { path: "numberInStock", label: "Stock" },
    { path: "dailyRentalRate", label: "Rate" },
    { key: "like" },
    { key: "delete" }
  ];
سپس متد رندر کامپوننت TableHeader را جهت درج key به روز رسانی می‌کنیم:
  render() {
    return (
      <thead>
        <tr>
          {this.props.columns.map(column => (
            <th
              key={column.path || column.key}
              style={{ cursor: "pointer" }}
              onClick={() => this.raiseSort(column.path)}
            >
              {column.label}
            </th>
          ))}
        </tr>
      </thead>
    );
دراینجا اگر column.path مقدار دهی شده بود، از آن استفاده می‌شود، در غیراینصورت از مقدار column.key، به عنوان مقدار ویژگی خاصیت key هر المان th، استفاده خواهد شد.


استخراج TableBody از جدول کامپوننت MoviesTable

اکنون با استخراج TableHeader از کامپوننت MoviesTable، به همان مشکل مخلوط بودن درجه‌ی abstractions رسیده‌ایم. از یک طرف با یک abstraction سطح بالا مانند TableHeader در این کامپوننت سر و کار داریم و از طرف دیگر، نمایش تمام جزئیات درونی رندر جدول نیز پیش روی ما است. همچنین رندر ستون‌های آن نیز پویا نیست و هنوز بر اساس خاصیت this.columns تعریف شده، واکنش نشان نمی‌دهد. به همین جهت tbody این جدول را نیز به یک کامپوننت مستقل تبدیل می‌کنیم. برای این منظور فایل جدید src\components\common\tableBody.jsx را اضافه می‌کنیم. سپس با استفاده از میانبرهای imrc و cc، ساختار ابتدایی کامپوننت TableBody را تشکیل می‌دهیم.
این کامپوننت قرار است آرایه‌ای از اشیاء را دریافت و ردیف‌هایی را بر اساس آن‌ها رندر کند. به همین جهت این آرایه را از props و با نام data دریافت می‌کنیم. نام data به عمد انتخاب شده‌است، تا بیانگر عمومی بودن آن باشد؛ بجای استفاده از نام ویژه‌ی آرایه‌ی movies، در این مثال خاص.
import React, { Component } from "react";

class TableBody extends Component {
  render() {
    const { data, columns } = this.props;

    return (
      <tbody>
        {data.map(item => (
          <tr>
            {columns.map(column => (
              <td></td>
            ))}
          </tr>
        ))}
      </tbody>
    );
  }
}

export default TableBody;
تا اینجا ساختار ابتدایی کامپوننت TableBody را مشاهده می‌کنید که هدف آن، رندر پویای قسمت tbody جدول است. این کامپوننت ابتدا نیاز دارد تا data را از props دریافت کند و بر اساس آن، لیست tr‌ها را رندر کند. سپس هر tr نیز از چندین td تشکیل می‌شود. به همین جهت به لیست دومی به نام columns، برای رندر پویای tdها نیاز است.


رندر محتویات هر سلول جدول به صورت پویا

در این مرحله می‌خواهیم محتویات tdها را رندر کنیم و حالت فعلی آن‌ها یک چنین شکلی را داشته و در آن ارجاع مستقیمی به شیء movie و خواص آن وجود دارد:
{movies.map(movie => (
  <tr key={movie._id}>
    <td>{movie.title}</td>
به علاوه این tdها به رندر دکمه‌ی Like و Delete که المان‌های سفارشی نیز محسوب می‌شوند، ختم شده‌اند.
برای رندر خواص اشیاء آرایه‌ی ارسالی به کامپوننت TableBody، می‌توان از روش [] برای دسترسی به مقادیر خواص استفاده کرد که سبب رندر پویای این مقادیر می‌شود:
<td>{item[column.path]}</td>
مشکل! روش item[column.path] با خاصیتی مانند "genre.name" که یک خاصیت تو در تو است، کار نمی‌کند. به همین جهت نیاز به متد زیر، برای انجام اینکار است:
  getPropValue(obj, path) {
    if (!path) {
      return obj;
    }

    const properties = path.split(".");
    return this.getPropValue(obj[properties.shift()], properties.join("."));
  }
بنابراین تا اینجا روش رندر مقدار هر خاصیت به صورت زیر تغییر می‌کند:
<td>{getPropValue(item, column.path)}</td>
 این تغییر می‌تواند 4 ستون اول را بدون مشکل رندر کند. اما برای مثال در ستون پنجم، کامپوننت Like قرار گرفته‌است. برای نمایش آن باید چکار کرد؟
همانطور که در ابتدای این سری نیز بررسی کردیم، عبارات JSX در نهایت به اشیاء خالص جاوا اسکریپتی ترجمه می‌شوند. این ویژگی در حین تعریف المان‌های سفارشی مانند کامپوننت Like نیز صادق است. به همین جهت در آرایه‌ی columns که تعاریف ستون‌های جدول را به همراه دارد، می‌توان یک خاصیت جدید را تعریف و به آن عبارات JSX را انتساب داد. بنابراین تعاریف tdهای Like و Delete را به طور کامل cut کرده و به خاصیت جدید content این دو شیء خالی انتهای لیست آرایه‌ی columns انتساب می‌دهیم:
class MoviesTable extends Component {
  columns = [
    { path: "title", label: "Title" },
    { path: "genre.name", label: "Genre" },
    { path: "numberInStock", label: "Stock" },
    { path: "dailyRentalRate", label: "Rate" },
    {
      key: "like",
      content: movie => (
        <Like liked={movie.liked} onClick={() => this.props.onLike(movie)} />
      )
    },
    {
      key: "delete",
      content: movie => (
        <button
          onClick={() => this.props.onDelete(movie)}
          className="btn btn-danger btn-sm"
        >
          Delete
        </button>
      )
    }
  ];
البته در اینجا جهت مقدار دهی اشیایی مانند movie، بجای استفاده‌ی مستقیم از یک React element، از یک arrow function استفاده کرده‌ایم تا movie را دریافت کند و یک المان React را بازگشت دهد. همچنین پیشتر از متغیرهای onLike و onDelete در کدهای tdها استفاده کرده بودیم که در ابتدای متد رندر تعریف شده بودند؛ اما زمانیکه این قطعات کد را به خاصیت content منتقل می‌کنیم، دیگر شناسایی نمی‌شوند. بنابراین در اینجا برای دسترسی به آن‌ها، مستقیما از props استفاده می‌شود.

مرحله‌ی بعد، مراجعه به کامپوننت tableBody و استفاده از خاصیت جدید content، جهت رندر محتوای آن است. در اینجا در متد renderCell بررسی می‌کنیم اگر ستونی دارای خاصیت content باشد، آن content را رندر می‌کنیم. در غیراینصورت از همان getPropValue متداول استفاده خواهد شد:
  renderCell = (item, column) => {
    if (column.content) {
      return column.content(item);
    }

    return this.getPropValue(item, column.path);
  };

  createKey = (item, column) => {
    return item._id + (column.path || column.key);
  };

  render() {
    const { data, columns } = this.props;

    return (
      <tbody>
        {data.map(item => (
          <tr key={item._id}>
            {columns.map(column => (
              <td key={this.createKey(item, column)}>
                {this.renderCell(item, column)}
              </td>
            ))}
          </tr>
        ))}
      </tbody>
    );
  }
- در متد renderCell، فراخوانی column.content(item) با توجه به function بودن content تعریف شده‌ی در آرایه‌ی columns، در حقیقیت یک عبارت JSX را بازگشت می‌دهد که در خروجی‌های متدهای React مجاز است و در نهایت تبدیل به المان‌های خالص جاوا اسکریپتی در DOM مجازی React و در نهایت DOM اصلی مرورگر می‌شوند.
- همچنین در اینجا یک createKey را نیز مشاهده می‌کنید. المان‌های هر Array.map نوشته شده، نیاز به یک ویژگی key مقدار دهی شده دارند که در دو قسمت trها و همچنین tdها تعریف شده‌است. در فرمول آن جائیکه از || استفاده شده، اگر ستونی دارای path بود، مقدار آن درج می‌شود، اما اگر مانند دو ستون آخر صرفا key تعریف شده بود، وجود || سبب می‌شود تا column.key درنظر گرفته شود و مشکلی رخ ندهد.
- علت تعریف دو متد مجزای renderCell و createKey هم کم شدن بار if/elseها، در بین کدهای درج شده‌ی در ردیف‌های جدول است.

اکنون به کامپوننت MoviesTable مراجعه کرده و کل tbody آن‌را حذف و با المان کامپوننت TableBody، جایگزین می‌کنیم:
//...
import TableBody from "./common/tableBody";
//...

class MoviesTable extends Component {
  // ...

  render() {
    const { movies, onSort, sortColumn } = this.props;

    return (
      <table className="table">
        <TableHeader
          columns={this.columns}
          sortColumn={sortColumn}
          onSort={onSort}
        />
        <TableBody columns={this.columns} data={movies} />
      </table>
    );
  }
}
تا اینجا اگر این تغییرات را ذخیره کرده و برنامه را مجددا در مرورگر بارگذاری کنیم، باید به همان خروجی قبلی برسیم؛ که اینبار تعاریف ستون‌های آن پویا شده‌است.


اضافه کردن آیکن مرتب سازی اطلاعات به سر ستون‌های جدول

در کامپوننت tableHeader، کار رندر thها انجام می‌شود. در اینجا پس از نام سرستون، می‌خواهیم آیکن نمایش صعودی و یا نزولی بودن روش مرتب سازی جاری را نمایش دهیم. برای این منظور، ابتدا متد renderSortIcon را به این کامپوننت اضافه می‌کنیم:
  renderSortIcon = column => {
    const { sortColumn } = this.props;

    if (column.path !== sortColumn.path) {
      return null;
    }

    if (sortColumn.order === "asc") {
      return <i className="fa fa-sort-asc" />;
    }

    return <i className="fa fa-sort-desc" />;
  };
این متد، شیء column در حال رندر را دریافت کرده و بر اساس sortColumn دریافتی از props و همچنین صعودی و یا نزولی بودن روش مرتب سازی، یکی از آیکن‌های font-awesome را به صورت یک المان جدید رندر می‌کند. اگر این column در حال رندر، با sortColumn تعیین شده یکی نبود، آیکنی رندر نمی‌شود (با بازگشت نال، هیچ چیزی رندر نخواهد شد).
و سپس در متد رندر کامپوننت tableHeader، این متد را در کنار label آن ستون درج خواهیم کرد:
{column.label} {this.renderSortIcon(column)}
پس از ذخیره سازی تغییرات و بارگذاری مجدد برنامه در مرورگر، خروجی آن‌را برای نمونه به صورت یک آیکن مثلثی شکل، در کنار عنوان Title می‌توان مشاهده کرد:



استخراج کل Table از جدول کامپوننت MoviesTable

در حال حاضر اگر به پیاده سازی کامپوننت MoviesTable دقت کنیم، یک تگ table به همراه دو کامپوننت TableHeader و TableBody در آن درج شده‌اند. با این طراحی، اگر قصد استفاده‌ی از این امکانات را در جای دیگری داشته باشیم، باید دقیقا همین قطعه کد را تکرار کنیم. به همین جهت کل تگ table این کامپوننت را استخراج کرده و به کامپوننت جدیدی منتقل می‌کنیم. به همین جهت فایل جدید src\components\common\table.jsx را ایجاد کرده و با استفاده از میانبرهای imrc و cc، ساختار ابتدایی کامپوننت Table را تشکیل می‌دهیم. سپس کل تگ table کامپوننت MoviesTable را cut کرده و به متد رندر کامپوننت جدید Table منتقل می‌کنیم. سپس اولین قدم برای سازگار کردن این محتوا با یک کامپوننت جدید، افزودن importهای زیر است:
import TableBody from "./tableBody";
import TableHeader from "./tableHeader";
سپس باید تمام ویژگی‌های استفاده شده‌ی در این المان منتقل شده را از طریق props دریافت کرد که انجام اینکار را در سطر اول متد رندر مشاهده می‌کنید:
import TableBody from "./tableBody";
import TableHeader from "./tableHeader";

class Table extends Component {
  render() {
    const { columns, sortColumn, onSort, data } = this.props;
    return (
      <table className="table">
        <TableHeader
          columns={columns}
          sortColumn={sortColumn}
          onSort={onSort}
        />
        <TableBody columns={columns} data={data} />
      </table>
    );
  }
}

export default Table;
با این تغییرات به یک کامپوننت ساده‌ی با قابلیت استفاده‌ی مجدد رسیده‌ایم. اکنون المان آن‌را در کامپوننت MoviesTable، در جای تگ قبلی table قرار می‌دهیم:
//...

import Table from "./common/table";

class MoviesTable extends Component {
  //... 

  render() {
    const { movies, onSort, sortColumn } = this.props;

    return (
      <Table
        columns={this.columns}
        sortColumn={sortColumn}
        onSort={onSort}
        data={movies}
      />
    );
  }
}


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید:  sample-14.zip
مطالب
ایجاد سرویس چندلایه‎ی WCF با Entity Framework در قالب پروژه - 5
پس از ایجاد متدها، نوبت به تغییرات App.Config می‎رسد. هرچند خود Visual Studio برای کلاس پیش‌گزیده‌ی خود تنظیماتی را در App.Config افزوده است ولی چنان‎چه در در خاطر دارید ما آن فایل‎ها را حذف کردیم و فایل‎های جدیدی به جای آن افزودیم. از این رو مراحل زیر را انجام دهید:
1- فایل App.Config را از Solution Explorer باز کنید.
2- به جای عبارت MyNewsWCFLibrary.Service1 در قسمت Service Name این عبارت را بنویسید: MyNewsWCFLibrary.MyNewsService
3- در قسمت BaseAddress عبارت Design_Time_Addresses را حذف کنید.
4- در قسمت BaseAddress شماره پورت را به 8080 تغییر دهید.
5- در قسمت BaseAddress به جای Service1 بنویسید: MyNewsService
6- در قسمت endpoint به جای عبارت MyNewsWCFLibrary.IService1 بنویسید: MyNewsWCFLibrary.IMyNewsService 
در پایان تگ Service در App.Config باید همانند کد زیر باشد:
   <services>
      <service name="MyNewsWCFLibrary.MyNewsService">
        <host>
          <baseAddresses>
            <add baseAddress="http://localhost:8080/MyNewsWCFLibrary/MyNewsService/" />
          </baseAddresses>
        </host>
        <!-- Service Endpoints -->
        <!-- Unless fully qualified, address is relative to base address supplied above -->
        <endpoint address="" binding="basicHttpBinding" contract="MyNewsWCFLibrary.IMyNewsService">
          <!-- 
              Upon deployment, the following identity element should be removed or replaced to reflect the 
              identity under which the deployed service runs.  If removed, WCF will infer an appropriate identity 
              automatically.
          -->
          <identity>
            <dns value="localhost" />
          </identity>
        </endpoint>
        <!-- Metadata Endpoints -->
        <!-- The Metadata Exchange endpoint is used by the service to describe itself to clients. -->
        <!-- This endpoint does not use a secure binding and should be secured or removed before deployment -->
        <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
      </service>
    </services>
تغییرات را ذخیره کنید و پروژه را اجرا کنید. باید پنجره‌ای شبیه به پنجره‌ی زیر نشان داده شود:

در صورت مشاهده پیام خطا، ویژوال استودیو را ببندید و این‌بار به صورت Run as administrator باز کنید.

برای نمونه روی متد AddCategory کلیک کنید. در پنجره نشان داده شده همانند شکل در برابر فیلد CatName مقداری وارد کنید و روی دکمه Invoke کلیک کنید. متد مورد نظر اجرا شده و مقداری که وارد کرده ایم در پایگاه داده‌ها ذخیره می‌شود. مقداری که در قسمت پایین دیده می‌شود خروجی متد است که در اینجا شناسه رکورد درج‌شده است.

بار دیگر برای مشاهده رکورد درج‌شده روی متد GetAllCategory کلیک کنید. به علت این‌که این متد ورودی ندارد در قسمت بالا چیزی نشان داده نمی‌شود. روی دکمه Invoke کلیک کنید. با پیغام خطای زیر روبه‌رو خواهید شد:

افزودن ویژگی Virtual به tblNews و tblCategory در بخش دوم  خواندید؛ باعث می‌شود که Entity Framework در هنگام اجرا کلاس‌هایی با عنوان "پروکسی‌های پویا" به کلاس‌های Address و Customer بیفزاید و بنابراین قابلیت Lazy Loading برای این کلاس‌ها در زمان اجرای برنامه فراهم می‌گردد. 

ولی با افزودن پروکسی‌های پویا به کلاس‌های ما، این کلاس‌ها قابلیت انتقال خود از طریق سرویس‌های WCF را از دست می‌دهند زیرا پروکسی‌های پویا به طور پیش‌گزیده قابلیت سریالایز و دیسریالایز شدن را ندارند!

خوشبختانه می‌توانیم این ویژگی را در کلاس DBContext غیرفعال کنیم. برای این منظور قالب سازنده‌ی آن یا MyNewsModel.Context.tt را از Solution Explorer باز کنید و کد زیر را در آن پیدا کنید:

<#=Accessibility.ForType(container)#> partial class <#=code.Escape(container)#> : DbContext
{
    public <#=code.Escape(container)#>()
        : base("name=<#=container.Name#>")
    {

سپس در ادامه‌ی آن کدغیرفعال‌کردن پروکسی پویا را به این شکل بنویسید:

<#=Accessibility.ForType(container)#> partial class <#=code.Escape(container)#> : DbContext
{
    public <#=code.Escape(container)#>()
        : base("name=<#=container.Name#>")
    {
      Configuration.ProxyCreationEnabled = false;

اکنون اگر فایل را ذخیره کنیم سپس فایل MyNewsModel.Context.cs را از Solution Explorer باز کنید؛ خواهید دید که این خط کد در جای خود قرارگرفته است.

بار دیگر پروژه را اجرا کنید روی متد GetAllCategory کلیک کنید. این بار اگر دکمه Invoke را بفشارید با همانند شکل زیر را خواهید دید:

در بخش ششم پیرامون ارتباط جدول‌های tblNews و tblCategory و نمایش محتویات وابسته جدول خبر به دسته و تنظیمات آن در t4 و کلاس Service

در بخش هفتم پیرامون میزبانی WCFLibrary در یک Web Application

و در بخش هشتم پیرامون ایجاد یک برنامه‌ی ویندوزی جهت استفاده از سرویس‌های WCF خواهم نوشت. 

مطالب
آموزش فایرباگ - #5 - HTML Panel
در این سری از مقالات آموزش FireBug ، به صورت ترتیبی پیش رفتیم و ابتدا توضیحات تقریبا مفصلی در مورد پنل Console دادیم.
اکنون به پرکاربردترین بخش آن ، یعنی پنل HTML می‌رسیم.

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

نگاهی به کدهای ارسال شده به مرورگر در آدرس Google.com و نحوه‌ی نمایش آن در فایرباگ را ملاحظه بفرمایید.
( کدهای ارسال شده سمت چپ - نمایش در فایرباگ سمت راست )


این پنل به سه بخش اصلی تقسیم می‌شود :
  • بخش اصلی یا NodeView که محتوای صفحه را بصورت درختی و مرتب و رنگی نمایش می‌دهد.
  •  Panel Toolbar که در بالای پنل قرار دارد.
  •  Side Panels که شامل پنل‌های Style , Computed , Layout , DOM می‌شود.
    که به ترتیب برای نمایش و ویرایش استایل‌ها ، مشاهده استایل‌های محاسبه شده ، مشاهده Layout یا آرایش و نمایش اطلاعات DOM تگ انتخاب شده در NodeView است.

در این مقاله با دو بخش NodeView و Panel Toolbar ، و در مقاله‌ی بعد با پنل‌های سمت راست یعنی Side Panels آشنا می‌شویم.

ویرایش HTML

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


برای اضافه کردن یک Attribute جدید به تگ هم بروی تگ مورد نظر راست کلیک می‌کنید و سپس گزینه‌ی New Attribute را انتخاب می‌کنید. ابتدا نام ویژگی ، یک Enter ، سپس مقدار را وارد می‌کنید. با زدن کلیدهای Enter متوالی ، می‌توانید به وارد کردن ویژگی‌ها ادامه دهید.
برای ویرایش کردن یک تگ و تگ‌های فرزندش ، بروی تگ موردنظر کلیک کنید تا به حالت انتخاب دربیاید ، سپس بروی دکمه‌ی Edit در بالای پنل کلیک کنید. در نهایت بعد از انجام ویرایش ، مجددا بروی دکمه‌ی Edit کلیک کنید.


 Node View
NodeView نام بخش اصلی پنل HTML است که محتویات صفحه را بصورت مرتب شده و درختی نمایش می‌دهد.
تگ (Node) هایی که دارای فرزند می‌باشند ، یک علامت + یا - در کنارشان وجود دارد که با کلیک بروی آن می‌توانید آن تگ را باز/بسته کنید. همچنین این کار با کلید‌های + و - یا Right Arrow و Left Arrow از روی کیبورد هم قابل انجام است.
برای باز کردن یک لینک در این قسمت هم از ترکیب Ctrl و کلیک موس کمک بگیرید.
در نهایت زمانی که موس را بروی یک تگ قرار می‌دهید ، محیطی که توسط آن تگ در صفحه اشغال شده است بصورت رنگی مشخص می‌شود.
که رنگ زرد به معنی محیط margin ، رنگ آبی تیره به معنی محیط padding و رنگ آبی روشن هم به معنی محیط محتوا است.

 Panel Toolbar
این قسمت در بالای پنل HTML قرار دارد که گزینه‌های زیر را دارا می‌باشد:



  •  Break On Mutate
    این دکمه امکان توقف کد JavaScript ای که سعی در ویرایش محتوای صفحه را دارد ، می‌دهد.
    زمانی که FireBug تشخیص دهد که کدی سعی در ویرایش محتوا دارد ، شما را به خط مورد نظر از کد ، در پنل Script منتقل می‌کند.

  • Edit
    این دکمه برای ویرایش مستقیم محتوای یک تگ بکار می‌رود
    نکته‌ی جالب در ویرایش محتوا در فایرباگ این است که تغییرات در لحظه اعمال می‌شوند و نیاز به عمل بروزرسانیِ جداگانه نیست. برای مثال در همین قسمت Edit ، با هر ویرایش محتوا ، تغییرات در لحظه اعمال می‌شوند.

  •  Element Path
    زمانی که یک تگ را در FireBug انتخاب می‌کنید ، لیستی از تگ‌ها که از تگ جاری شروع و به تگ ریشه ختم می‌شود ، نمایش داده می‌شود که با کلیک بروی هرکدام ، همان به عنوان تگ فعلی یا انتخاب شده تعیین می‌شود.
    نتیجه‌ی عملیاتی که بروی این تگ‌های انجام می‌دهید (حرکت موس و راست کلیک کردن) معادل همان عملیات بروی تگ‌های نمایش داده شده در قسمت اصلی (NodeView) است.



Options Menu

هر تب یا پنل در فایرباگ دارای یک سری تنظیمات است که Options Menu نام دارد. تب HTML هم دارای یک سری تنظیمات است که دانشتن آنها بسیار به شما کمک خواهد کرد.
این منو با کلیک کردن بروی فلش تب () یا راست کلیک کردن بروی تب ظاهر می‌شود.


  • Show Full Text
    در صورت فعال بودن ، متون بصورت کامل نمایش داده می‌شوند ، در غیراینصورت بصورت خلاصه شده نمایش داده می‌شوند.

  • Show White Space
    در صورت فعال بودن ، فضاهای خالی ، کرکترهای خط جدید و ... را نمایش می‌دهد.


  • Show Comments
    در صورت فعال بودن ، کامنت‌ها را هم نمایش می‌دهد


  • سه گزینه ی Show Entities As Symbols ، Show Entities As Names و Show Entities As Unicode ، نوع نمایش کرکترهای ویژه را تعیین می‌کنند.

  • Highlight Changes
    در صورت فعال بودن ، تگ تغییر یافته توسط JavaScript (یا تگ والدی که قابل مشاهده باشد) Highlight می‌شود

  • Expand Changes
    در صورت فعال بودن ، زمان تغییر دادن یک تگ توسط JavaScript ، والدهای آن تگ باز (Expand) می‌شوند

  • Scroll Changes Into View
    در صورت فعال بودن این گزینه ، هنگام تغییر یک تگ در صفحه توسط JavaScript ، قسمت NodeView به قسمت تغییر بافته حرکت می‌کند.
    (فعال بودن این گزینه هنگام بررسی سایت هایی که اسلایدر یا سیستم هایی مشابه دارند ، باعث میشه که نتوانید بروی قسمت‌های سایت تمرکز کنید و مدام اسکرول به قسمت تغییرات منتقل می‌شود.)

  • Shade Box Model
    در صورت فعال بودن ، فضای margin , padding و content را به شکلی که در بالا گفته شد نمایش می‌دهد ، در غیر اینصورت فقط یک خط دور تگ نشان می‌دهد.

  • Show Quick Info Box
    در صورت فعال بودن ، یک پاپ‌آپ به همراه اطلاعات مختصری از تگ در صفحه نمایش می‌دهد.




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

  • Copy HTML
    خود تگ و محتوایش را بصورت HTML در حافظه کپی می‌کند.

  • Copy innerHTML
    محتوای تگ را در حافظه کپی می‌کند.

  • Copy XPath
    آدرس XPath تگ را در حافظه کپی می‌کند.

  • Copy CSS Path
    CSS Selector تگ را در حافظه کپی می‌کند.

  • Log Events
    رویدادهای تگ را در پنل Console ثبت می‌کند. (کلیک موس ، فشردن کلید ، ...)
    برای لغو لاگ کردن ، مجددا بروی تگ راست کلیک کرده و این گزینه را از حالت انتخاب خارج کنید.

  • Scroll Into View
    صفحه را به جایی که تگ قابل نمایش است منتقل می‌کند.

  • New Attribute...
    یک attribute جدید به تگ اضافه می‌کند.
    برای لغو عملیات ، دکمه‌ی Esc را بزنید.

  • Edit Attribute "<attribute name>"...
    اگر بروی یک attribute راست کلیک کرده باشید ، این گزینه و گزینه‌ی بعدی را مشاهده خواهید کرد.
    معادل کلیک بروی نام attribute است ، نام را به حالت ویرایش درمی آورد.

  • Delete Attribute "<attribute name>"
    attribute را حذف می‌کند.

  • Edit HTML...
    تگ را به حالت ویرایش می‌برد.
    معادل انتخاب تگ ، زدن کلید Edit است.

  • Delete Element
    تگ را حذف می‌کند.
    راه دیگر حذف یک تگ ، انتخاب تگ و فشردن کلید Del از کیبورد است.

  • Expand/Contract All
    تگ و Childهایش را باز/بسته می‌کند. (بجز تگ های <script> , <style> , <link>)
    می‌توان با ترکیب کلیدShift + * هم این کار را انجام داد که در این حالت تگ‌های فوق هم شامل باز/بسته شدن می‌شوند.

  • Break On Attribute Change
    مانع اجرای کد JavaScript ای که attribute تگ را تغییر می‌دهد می‌شود و فایرباگ به خطی که باعث این عمل شده است در پنل Script منتقل می‌شود.
    به عبارتی دیگر یک Break Point در خط JavaScript ای که باعث ویرایش attribute می‌شود قرار می‌دهد.

  • Break On Child Addition or Removal
    مشابه توضیح قبل ، Break Point را در خطی که باعث اضافه/حذف شدن تگِ Child شده است قرار می‌دهد.

  • Break On Element Removal
    مشابه توضیح قبل ، Break Point را در خطی که باعث حذف شدن تگ شده است قرار می‌دهد.

  • Inspect in DOM Tab
    تگ فعلی را در پنل DOM ، برای بررسی باز می‌کند.


در قسمت بعدی با پنل‌های سمت راست (Side Panels) آشنا می‌شویم.
مطالب
کامپوننت‌ها در AngularJS 1.5 - قسمت دوم - مسیریابی
در این قسمت به معرفی سیستم مسیریاب در Angular 1.5 خواهیم پرداخت. قبل از معرفی این سیستم ابتدا سیستم مسیریاب اصلی در Angular را بررسی خواهیم کرد.

مروری بر مسیریابی در AngularJS
برای استفاده از مسیریاب اصلی Angular کافی است از دایرکتیو ویژه‌ایی با نام ng-view به همراه یکسری تنظیمات پیکربندی استفاده کنیم. به عنوان مثال اگر آدرس صفحه با home/ مطابقت داشته باشد، تمپلیت home.html توسط دایرکتیو ng-view بارگذاری خواهد شد. برای فعال‌سازی این سیستم ابتدا باید پکیج angular-route را به پروژه مثال قسمت قبل اضافه کنید:
bower install angular-route --save
در ادامه لازم است وابستگی فوق را به صفحه‌ی index.html اضافه نمائید:
<script src="bower_components/angular-route/angular-route.min.js" type="text/javascript"></script>
اکنون درون صفحه به جای نمایش مستقیم کامپوننت، از دایرکتیو ng-view استفاده خواهیم کرد:
<div class="col-md-9">
                <ng-view></ng-view>
</div>

 همچنین مقدار فیلد url کامپوننت dntWidget را به صورت زیر تغییر دهید:
      model.panel = {
          title: "Panel Title",
          items: [
              {
                  title: "Home", url: "#/home"
              },
              {
                  title: "Articles", url: "#/articles"
              },
              {
                  title: "Authors", url: "#/authors"
              }
          ]
      };

در ادامه باید سیستم مسیریاب را به عنوان یک وابستگی به اپلیکیشن معرفی کنیم:
var module = angular.module("dntModule", ["ngRoute"]);
اکنون می‌توانیم پیکربندی موردنظر جهت هدایت آدرس‌ها به تمپلیت‌های مربوطه را بنویسیم:
module.config(function ($routeProvider) {
        $routeProvider
            .when("/home", { template: "<app-home></app-home>" })
            .when("/articles", { template: "<app-articles></app-articles>" })
            .when("/authors", { template: "<app-authors></app-authors>" })
            .otherwise({ redirectTo: "/home" });
    });

همانطور که مشاهده می‌کنید به route provider اعلام کرده‌ایم که در صورت مطابقت داشتن آدرس URL با هر کدام از حالت‌های فوق، تمپلیت متناسب با آن را نمایش بدهد. در نهایت توسط otherwise اگر آدرس، با هیچکدام از حالت‌های تعریف شده مطابقت نداشت، کاربر به آدرس home/ هدایت خواهد شد. 
نکته‌ایی که در کد فوق وجود دارد این است که سیستم مسیریاب اصلی Angular تا اینجا هیچ اطلاعی از وجود کامپوننت‌ها ندارد، اما می‌داند یک تمپلیت چیست. بنابراین از تمپلیت، جهت نمایش یک کامپوننت استفاده خواهد کرد.
برای ایجاد کامپوننت‌های فوق نیز می‌توانید آن را به صورت زیر ایجاد کنید:
module.component("appHome", {
        template: `
        <hr><div>
            <div>Panel heading = HomePage</div>
            <div>
                HomePage
            </div>
        </div>`
    });
    module.component("appArticles", {
        template: `
        <hr><div>
            <div>Panel heading = Articles</div>
            <div>
                Articles
            </div>
        </div>`
    });
    module.component("appAuthors", {
        template: `
        <hr><div>
            <div>Panel heading = Authors</div>
            <div>
                Authors
            </div>
        </div>`
    });

اکنون اگر برنامه را اجرا کنید، خواهید دید که به صورت پیش‌فرض به آدرس home/# هدایت خواهیم شد. زیرا آدرسی برای root، درون route configuration تعریف نکرده‌ایم:

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


معرفی Component Router

مزیت این روتر این است که به صورت اختصاصی برای کار با کامپوننت‌ها طراحی شده است. بنابراین دیگر نیازی به استفاده از template درون route configuration نیست. برای استفاده از این روتر ابتدا باید پکیج آن را نصب کنیم:

bower install angular-component-router --save

سپس وابستگی فوق را با روتر پیش‌فرضی که در مثال قبل بررسی کردیم، جایگزین خواهیم کرد:

<script src="bower_components/angular-component-router/angular_1_router.js"></script>

همچنین درون فایل module.js به جای وابستگی ngRoute از ngComponentrouter استفاده خواهیم کرد:

var module = angular.module("dntModule", ["ngComponentRouter"]);

در ادامه به جای تمامی route configurations قبلی، اینبار یک کامپوننت جدید را به صورت زیر ایجاد خواهیم کرد:

module.component("appHome", {
        template: `
        <hr>
        <div>
            <div>Panel heading = HomePage</div>
            <div>
                HomePage
            </div>
        </div>`
    });

همانطور که مشاهده می‌کنید برای پاسخ‌گویی به تغییرات URL، مقدار routeConfig$ را مقداردهی کرده‌ایم. در اینجا به جای بارگذاری تمپلیت، خود کامپوننت، در هر یک از ruleهای فوق بارگذاری خواهد شد. برای حالت otherwise نیز از سینتکس **/ استفاده کرده‌ایم.

تمپلیت کامپوننت فوق نیز به صورت زیر است:

<div class="container">
    <div class="row">
        <div class="col-md-3">
            <hr>
            <dnt-widget></dnt-widget>
        </div>
        <div class="col-md-9">
            <ng-outlet></ng-outlet>
        </div>
    </div>
</div>

لازم به ذکر است دیگر نباید از دایرکتیو  ng-view استفاده کنیم؛ زیرا این دایرکتیو برای استفاده از روتر اصلی طراحی شده است. به جای آن از دایرکتیو ng-outlet استفاده شده است. این کامپوننت به عنوان یک کامپوننت top level عمل خواهد کرد. بنابراین درون صفحه‌ی index.html از کامپوننت فوق استفاده خواهیم کرد:

<html ng-app="dntModule">
<head>
    <meta charset="UTF-8">
    <title>Using Angular 1.5 Component Router</title>
    <link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.css">
    <link rel="stylesheet" href="bower_components/font-awesome/css/font-awesome.min.css">
</head>
<body>
    
    <dnt-app></dnt-app>

    <script src="bower_components/angular/angular.js" type="text/javascript"></script>
    <script src="bower_components/angular-component-router/angular_1_router.js"></script>
    <script src="scripts/module.js" type="text/javascript"></script>
    <script src="scripts/dnt-app.component.js"></script>
    <script src="scripts/dnt-widget.component.js"></script>
</body>
</html>

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

module.value("$routerRootComponent", "dntApp");

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

کدهای این قسمت را نیز از اینجا می‌توانید دریافت کنید.