اشتراکها
Some months ago a feature landed in Xamarin.Forms that seemed to truly polarize the Xamarin.Forms community: support for styling applications using CSS. Some argued that it was an unnecessary introduction to "Web" technology to the native development experience, and others that it simply isn't the right solution to the problem. While I sympathize with the latter opinion and think there's plenty of room for some good debate on the right path forward, I count myself as part of a third camp: I think that CSS is a powerful (and frequently maligned) solution to the problem of styling native mobile applications.
اشتراکها
اضافه کردن HTTPS به وب سایت
در ادامهی سری کار با MobX، میخواهیم نکاتی را که در سه قسمت قبل مرور کردیم، در قالب یک برنامه پیاده سازی کنیم:
این برنامه از چهار کامپوننت تشکیل شدهاست:
- کامپوننت App که در برگیرندهی سه کامپوننت زیر است:
- کامپوننت BasketItemsCounter: جمع تعداد آیتمهای انتخابی توسط کاربر را نمایش میدهد؛ به همراه دکمهای برای خالی کردن لیست انتخابی.
- کامپوننت ShopItemsList: لیست محصولات موجود در فروشگاه را نمایش میدهد. با کلیک بر روی هر آیتم آن، آیتم انتخابی به لیست انتخابهای او اضافه خواهد شد.
- کامپوننت BasketItemsList: لیستی را نمایش میدهد که حاصل انتخابهای کاربر در کامپوننت ShopItemsList است (یا همان سبد خرید). در ذیل این لیست، جمع نهایی قیمت قابل پرداخت نیز درج میشود. همچنین اگر کاربر بر روی دکمهی remove هر ردیف کلیک کند، یک واحد از چند واحد انتخابی، حذف خواهد شد.
بنابراین در اینجا سه کامپوننت مجزا را داریم که با هم تبادل اطلاعات میکنند. یکی جمع تعداد محصولات خریداری شده را، دیگری لیست محصولات موجود را و آخری لیست خرید نهایی را نمایش میدهد. همچنین این سه کامپوننت، فرزند یک دیگر هم محسوب نمیشوند و انتقال اطلاعات بین اینها نیاز به بالا بردن state هر کدام و قرار دادن آنها در کامپوننت App را دارد تا بتوان پس از آن از طریق props آنها را بین سه کامپوننت فوق که اکنون فرزند کامپوننت App محسوب میشوند، به اشتراک گذاشت. روش بهتر اینکار، استفاده از یک مخزن حالت سراسری است تا حالتهای این کامپوننتها را نگهداری کرده و دادهها را بین آنها به اشتراک بگذارد که در اینجا برای حل این مساله از کتابخانههای mobx و mobx-react استفاده خواهیم کرد.
برپایی پیشنیازها
برای پیاده سازی برنامهی فوق، یک پروژهی جدید React را ایجاد میکنیم:
در ادامه کتابخانههای زیر را نیز در آن نصب میکنیم. برای این منظور پس از باز کردن پوشهی اصلی برنامه توسط VSCode، دکمههای ctrl+` را فشرده (ctrl+back-tick) و دستور زیر را در ترمینال ظاهر شده وارد کنید:
توضیحات:
- برای استفاده از شیوهنامههای بوت استرپ، بستهی bootstrap نیز در اینجا نصب میشود.
- اصل کار برنامه توسط دو کتابخانهی mobx و کتابخانهی متصل کنندهی آن به برنامههای react که mobx-react نام دارد، انجام خواهد شد.
- چون میخواهیم از افزونهی mobx-devtools نیز استفاده کنیم، نیاز است دو بستهی mobx-react-devtools و همچنین mobx-state-tree را که جزو وابستگیهای آن است، نصب کنیم.
سپس بستههای زیر را که در قسمت devDependencies فایل package.json درج خواهند شد، باید نصب شوند:
علت آنرا در قسمت قبل بررسی کردیم. این وابستگیها برای فعالسازی react-app-rewired و همچنین eslint غنی سازی شدهی آن مورد استفاده قرار میگیرند. به علاوه سه قسمت زیر را نیز از قسمت قبل، به پروژه اضافه میکنیم:
- افزودن فایل جدید config-overrides.js به ریشهی پروژه، تا پشتیبانی ازlegacy" decorators spec" فعال شود.
- اصلاح فایل package.json و ویرایش قسمت scripts آن برای استفادهی از react-app-rewired، تا امکان تغییر تنظیمات webpack به صورت پویا در زمان اجرای برنامه، میسر شود.
- همچنین فایل غنی شدهی eslintrc.json. را نیز به ریشهی پروژه اضافه میکنیم.
تهیه سرویس لیست محصولات موجود در فروشگاه
این برنامه از یک لیست درون حافظهای، برای تهیهی لیست محصولات موجود در فروشگاه استفاده میکند. به همین جهت پوشهی service را افزوده و سپس فایل جدید src\services\productsService.js را با محتوای زیر، ایجاد میکنیم:
ایجاد کامپوننت نمایش لیست محصولات
پس از مشخص شدن لیست محصولات قابل فروش، کامپوننت جدید src\components\ShopItemsList.jsx را به صورت زیر ایجاد میکنیم:
- این کامپوننت آرایهی products را از طریق سرویس services/productsService دریافت کرده و سپس با استفاده از متد Array.map، حلقهای را بر روی عناصر آن تشکیل داده که در نتیجه، سبب درج trهای متناظر با آن میشود؛ تا هر ردیف این جدول، یک آیتم از محصولات موجود را نیز نمایش دهد.
- در اینجا همچنین هر ردیف، به همراه یک دکمهی Add نیز هست که قرار است با کلیک بر روی آن، متد رویدادگردان onAdd فراخوانی شود. این متد نیز از طریق props این کامپوننت دریافت میشود. کتابخانههای مدیریت حالت، تمام خواص و رویدادگردانهای مورد نیاز یک کامپوننت را از طریق props، تامین میکنند.
- فعلا این کامپوننت به هیچ مخزن دادهای متصل نیست و فقط طراحی ابتدایی آن آماده شدهاست.
ایجاد کامپوننت نمایش لیست خرید کاربر (سبد خرید)
اکنون که میتوان توسط کامپوننت لیست محصولات، تعدادی از آنها را خریداری کرد، کامپوننت جدید src\components\BasketItemsList.jsx را برای نمایش لیست نهایی خرید کاربر، به صورت زیر پیاده سازی میکنیم:
- عملکرد این کامپوننت نیز شبیه به کامپوننت نمایش لیست محصولات است؛ با این تفاوت که لیستی که به آن از طریق props ارسال میشود:
لیست محصولات انتخابی کاربر است.
- همچنین هر ردیف نمایش داده شده، به همراه یک دکمهی Remove آیتم انتخابی نیز هست که به متد رویدادگردان onRemove متصل شدهاست.
- در ردیف انتهایی این لیست، مقدار totalPrice که یک خاصیت محاسباتی است، درج میشود.
- فعلا این کامپوننت نیز به هیچ مخزن دادهای متصل نیست و فقط طراحی ابتدایی آن آماده شدهاست.
ایجاد کامپوننت نمایش تعداد آیتمهای خریداری شده
کاربر اگر آیتمی را از لیست محصولات انتخاب کند و یا محصول انتخاب شده را از لیست خرید حذف کند، تعداد نهایی باقی مانده را میتوان در کامپوننت src\components\BasketItemsCounter.jsx مشاهده کرد:
- این کامپوننت یک خاصیت و یک رویدادگردان را از طریق props خود دریافت میکند. خاصیت count، جمع نهایی موجود در سبد خرید را نمایش میدهد و فراخوانی onRemoveAll، سبب پاک شدن تمام آیتمهای موجود در سبد خرید خواهد شد.
- فعلا این کامپوننت نیز به هیچ مخزن دادهای متصل نیست و فقط طراحی ابتدایی آن آماده شدهاست.
نمایش ابتدایی سه کامپوننت توسط کامپوننت App
اکنون که این سه کامپوننت تکمیل شدهاند، میتوان المانهای آنها را در فایل src\App.js درج کرد تا در صفحه نمایش داده شوند:
طراحی مخزنهای حالت MobX مخصوص برنامه
میتوان همانند Redux کل state برنامه را داخل یک شیء store ذخیره کرد و یا چون در اینجا میتوان طراحی مخزن حالت MobX را به دلخواه انجام داد، میتوان چندین مخزن حالت را تهیه و به هم متصل کرد؛ مانند تصویری که مشاهده میکنید. در اینجا:
- src\stores\counter.js: مخزن دادهی حالت کامپوننت شمارشگر است.
- src\stores\market.js: مخزن دادهی کامپوننتهای لیست محصولات و سبد خرید است.
- src\stores\index.js: کار ترکیب دو مخزن قبل را انجام میدهد.
در ادامه کدهای کامل این مخازن را مشاهده میکنید:
مخزن حالت src\stores\counter.js
- کار این مخزن، تامین عدد جمع آیتمهای انتخابی توسط کاربر است که در کامپوننت شمارشگر نمایش داده میشود.
- در اینجا خاصیت totalNumbersInBasket به صورت observable تعریف شدهاست و با تغییر آن چه به صورت مستقیم، با مقدار دهی آن و یا توسط دو action تعریف شده، سبب به روز رسانی UI خواهد شد.
- میشد این مخزن را با مخزن src\stores\market.js یکی کرد؛ اما جهت ارائهی مثالی در مورد نحوهی تعریف چند مخزن و روش برقراری ارتباط بین آنها، به صورت مجزایی تعریف شد.
مخزن حالت src\stores\market.js
- کار این مخزن تامین مدیریت آرایهی basketItems است که بیانگر اشیاء انتخابی توسط کاربر میباشد.
- توسط متد add آن در کامپوننت نمایش لیست محصولات، میتوان آیتمی را به این آرایه اضافه کرد. در اینجا چون شیء product مورد استفاده دارای خاصیت count نیست، روش افزودن آنرا توسط spread operator برای درج خواص شیء product اصلی و سپس تعریف آنرا مشاهده میکنید. این فراخوانی، سبب افزایش یک واحد به عدد شمارشگر نیز میشود.
- متد remove آن در کامپوننت سبد خرید، مورد استفاده قرار میگیرد تا کاربر بتواند اطلاعاتی را از این لیست حذف کند. این فراخوانی، سبب کاهش یک واحد از عدد شمارشگر نیز میشود.
- متد removeAll آن در کامپوننت شمارشگر بالای صفحه استفاده میشود تا سبب خالی شدن آرایهی آیتمهای انتخابی گردد و همچنین عدد آنرا نیز صفر کند.
- خاصیت محاسباتی totalPrice آن در پایین جدول سبد خرید، جمع کل هزینهی قابل پرداخت را مشخص میکند.
مخزن حالت src\stores\index.js
در اینجا روش یکی کردن دو مخزن حالت یاد شده را به صورت خاصیتهای عمومی یک مخزن کد ریشه، مشاهده میکنید:
هر مخزن مجزایی که تعریف شده، دارای یک پارامتر سازندهاست که با مقدار شیء this کلاس RootStore مقدار دهی میشود. با این روش میتوان بین مخازن کد مختلف ارتباط برقرار کرد. برای نمونه درمخزن حالت MarketStore، این پارامتر سازنده، امکان دسترسی به خاصیت counterStore و سپس تمام خاصیتها و متدهای عمومی آنرا فراهم میکند:
تامین مخازن حالت تمام کامپوننتهای برنامه
پس از ایجاد مخازن حالت، اکنون نیاز است آنها را در اختیار سلسه مراتب کامپوننتهای برنامه قرار دهیم. به همین جهت به فایل src\index.js مراجعه کرده و آنرا به صورت زیر تغییر میدهیم:
- در اینجا ابتدا import فایل css بوت استرپ را مشاهده میکنید که در برنامه استفاده شدهاست.
- سپس یک وهلهی جدید از RootStore را که حاوی خاصیتهای عمومی counterStore و marketStore است، ایجاد میکنیم.
- اگر علاقمند باشید تا حین کار با MobX، جزئیات پشت صحنهی آنرا توسط افزونهی mobx-devtools ردیابی کنید، روش آنرا در اینجا با فراخوانی متد makeInspectable مشاهده میکنید. مقدار process.env.NODE_ENV نیز بر اساس پروسهی جاری node.js اجرا کنندهی برنامهی React تامین میشود. اطلاعات بیشتر
- قسمت آخر این تنظیمات، محصور کردن کامپوننت App که بالاترین کامپوننت در سلسله مراتب کامپوننتهای برنامه است، با شیء Provider میباشد. در این شیء توسط spread operator، سبب درج خواص عمومی rootStore، به عنوان مخازن قابل استفاده شدهایم. تنظیم {rootStore...} معادل عبارت زیر است:
به این ترتیب تمام کامپوننتهای برنامه میتوانند با دو مخزن کد ارسالی به آنها کار کنند. در ادامه مشاهده میکنیم که چگونه این ویژگیها، سبب تامین props کامپوننتها خواهند شد.
اتصال کامپوننت ShopItemsList به مخزن حالت marketStore
پس از ایجاد rootStore و محصور کردن کامپوننت App توسط شیء Provider در فایل src\index.js، اکنون باید قسمت export default کامپوننتهای برنامه را جهت استفادهی از مخازن حالت، یکی یکی ویرایش کرد:
در اینجا فراخوانی متد inject، سبب دسترسی به ویژگی marketStore تامین شدهی توسط شیء Provider میشود. تمام ویژگیهایی که به شیء Provider ارائه میشوند، در اینجا به صورت خواصی که توسط Object Destructuring قابل استخراج هستند، قابل دسترسی میشوند. سپس props این کامپوننت را که متد onAdd را میپذیرد، از طریق marketStore.add تامین میکنیم. در آخر کامپوننت ShopItemsList باید به صورت یک observer بازگشت داده شود تا تغییرات store را تحت نظر قرار داده و به این صورت امکان به روز رسانی UI را پیدا کند.
اتصال کامپوننت BasketItemsList به مخزن حالت marketStore
در اینجا نیز سطر export default را جهت دریافت خاصیت marketStore، از شیء Provider تامین شدهی در فایل src\index.js، ویرایش میکنیم. به این ترتیب سه props مورد انتظار این کامپوننت، توسط خاصیتهای basketItems (آرایهی اشیاء انتخابی توسط کاربر)، totalPrice (خاصیت محاسباتی جمع کل هزینه) و متد رویدادگردان onRemove (برای حذف یک آیتم) تامین میشوند. در آخر کامپوننت را به صورت observer محصور کرده و بازگشت میدهیم تا تغییرات در مخزن حالت آن، سبب به روز رسانی UI آن شوند:
اتصال کامپوننت BasketItemsCounter به دو مخزن حالت counterStore و marketStore
در اینجا روش استفادهی از decorator syntax کتابخانهی mobx-react را بر روی یک کامپوننت کلاسی مشاهده میکنید. تزئین کنندهی inject، امکان دسترسی به مخازن حالت تزریقی به شیء Provider را میسر کرده و سپس توسط آن میتوان props مورد انتظار کامپوننت را از مخازن متناظر استخراج کرده و در اختیار کامپوننت قرار داد. همچنین این کامپوننت توسط تزئین کنندهی observer نیز علامت گذاری شدهاست. در این حالت نیازی به تغییر سطر export default نیست.
کدهای کامل این قسمت را میتوانید از اینجا دریافت کنید: state-management-with-mobx-part4.zip
این برنامه از چهار کامپوننت تشکیل شدهاست:
- کامپوننت App که در برگیرندهی سه کامپوننت زیر است:
- کامپوننت BasketItemsCounter: جمع تعداد آیتمهای انتخابی توسط کاربر را نمایش میدهد؛ به همراه دکمهای برای خالی کردن لیست انتخابی.
- کامپوننت ShopItemsList: لیست محصولات موجود در فروشگاه را نمایش میدهد. با کلیک بر روی هر آیتم آن، آیتم انتخابی به لیست انتخابهای او اضافه خواهد شد.
- کامپوننت BasketItemsList: لیستی را نمایش میدهد که حاصل انتخابهای کاربر در کامپوننت ShopItemsList است (یا همان سبد خرید). در ذیل این لیست، جمع نهایی قیمت قابل پرداخت نیز درج میشود. همچنین اگر کاربر بر روی دکمهی remove هر ردیف کلیک کند، یک واحد از چند واحد انتخابی، حذف خواهد شد.
بنابراین در اینجا سه کامپوننت مجزا را داریم که با هم تبادل اطلاعات میکنند. یکی جمع تعداد محصولات خریداری شده را، دیگری لیست محصولات موجود را و آخری لیست خرید نهایی را نمایش میدهد. همچنین این سه کامپوننت، فرزند یک دیگر هم محسوب نمیشوند و انتقال اطلاعات بین اینها نیاز به بالا بردن state هر کدام و قرار دادن آنها در کامپوننت App را دارد تا بتوان پس از آن از طریق props آنها را بین سه کامپوننت فوق که اکنون فرزند کامپوننت App محسوب میشوند، به اشتراک گذاشت. روش بهتر اینکار، استفاده از یک مخزن حالت سراسری است تا حالتهای این کامپوننتها را نگهداری کرده و دادهها را بین آنها به اشتراک بگذارد که در اینجا برای حل این مساله از کتابخانههای mobx و mobx-react استفاده خواهیم کرد.
برپایی پیشنیازها
برای پیاده سازی برنامهی فوق، یک پروژهی جدید React را ایجاد میکنیم:
> create-react-app state-management-with-mobx-part4 > cd state-management-with-mobx-part4
> npm install --save bootstrap mobx mobx-react mobx-react-devtools mobx-state-tree
- برای استفاده از شیوهنامههای بوت استرپ، بستهی bootstrap نیز در اینجا نصب میشود.
- اصل کار برنامه توسط دو کتابخانهی mobx و کتابخانهی متصل کنندهی آن به برنامههای react که mobx-react نام دارد، انجام خواهد شد.
- چون میخواهیم از افزونهی mobx-devtools نیز استفاده کنیم، نیاز است دو بستهی mobx-react-devtools و همچنین mobx-state-tree را که جزو وابستگیهای آن است، نصب کنیم.
سپس بستههای زیر را که در قسمت devDependencies فایل package.json درج خواهند شد، باید نصب شوند:
> npm install --save-dev babel-eslint customize-cra eslint eslint-config-react-app eslint-loader eslint-plugin-babel eslint-plugin-css-modules eslint-plugin-filenames eslint-plugin-flowtype eslint-plugin-import eslint-plugin-no-async-without-await eslint-plugin-react eslint-plugin-react-hooks eslint-plugin-react-redux eslint-plugin-redux-saga eslint-plugin-simple-import-sort react-app-rewired typescript
- افزودن فایل جدید config-overrides.js به ریشهی پروژه، تا پشتیبانی ازlegacy" decorators spec" فعال شود.
- اصلاح فایل package.json و ویرایش قسمت scripts آن برای استفادهی از react-app-rewired، تا امکان تغییر تنظیمات webpack به صورت پویا در زمان اجرای برنامه، میسر شود.
- همچنین فایل غنی شدهی eslintrc.json. را نیز به ریشهی پروژه اضافه میکنیم.
تهیه سرویس لیست محصولات موجود در فروشگاه
این برنامه از یک لیست درون حافظهای، برای تهیهی لیست محصولات موجود در فروشگاه استفاده میکند. به همین جهت پوشهی service را افزوده و سپس فایل جدید src\services\productsService.js را با محتوای زیر، ایجاد میکنیم:
const products = [ { id: 1, name: "Item 1", price: 850 }, { id: 2, name: "Item 2", price: 900 }, { id: 3, name: "Item 3", price: 1500 }, { id: 4, name: "Item 4", price: 1000 } ]; export default products;
ایجاد کامپوننت نمایش لیست محصولات
پس از مشخص شدن لیست محصولات قابل فروش، کامپوننت جدید src\components\ShopItemsList.jsx را به صورت زیر ایجاد میکنیم:
import React from "react"; import products from "../services/productsService"; const ShopItemsList = ({ onAdd }) => { return ( <table className="table table-hover"> <thead className="thead-light"> <tr> <th>Name</th> <th>Price</th> <th>Action</th> </tr> </thead> <tbody> {products.map(product => ( <tr key={product.id}> <td>{product.name}</td> <td>{product.price}</td> <td> <button className="btn btn-sm btn-info" onClick={() => onAdd(product)} > Add </button> </td> </tr> ))} </tbody> </table> ); }; export default ShopItemsList;
- در اینجا همچنین هر ردیف، به همراه یک دکمهی Add نیز هست که قرار است با کلیک بر روی آن، متد رویدادگردان onAdd فراخوانی شود. این متد نیز از طریق props این کامپوننت دریافت میشود. کتابخانههای مدیریت حالت، تمام خواص و رویدادگردانهای مورد نیاز یک کامپوننت را از طریق props، تامین میکنند.
- فعلا این کامپوننت به هیچ مخزن دادهای متصل نیست و فقط طراحی ابتدایی آن آماده شدهاست.
ایجاد کامپوننت نمایش لیست خرید کاربر (سبد خرید)
اکنون که میتوان توسط کامپوننت لیست محصولات، تعدادی از آنها را خریداری کرد، کامپوننت جدید src\components\BasketItemsList.jsx را برای نمایش لیست نهایی خرید کاربر، به صورت زیر پیاده سازی میکنیم:
import React from "react"; const BasketItemsList = ({ items, totalPrice, onRemove }) => { return ( <> <table className="table table-hover"> <thead className="thead-light"> <tr> <th>Name</th> <th>Price</th> <th>Count</th> <th>Action</th> </tr> </thead> <tbody> {items.map(item => ( <tr key={item.id}> <td>{item.name}</td> <td>{item.price}</td> <td>{item.count}</td> <td> <button className="btn btn-sm btn-danger" onClick={() => onRemove(item.id)} > Remove </button> </td> </tr> ))} <tr> <td align="right"> <strong>Total: </strong> </td> <td> <strong>{totalPrice}</strong> </td> <td></td> <td></td> </tr> </tbody> </table> </> ); }; export default BasketItemsList;
const BasketItemsList = ({ items, totalPrice, onRemove }) => {
- همچنین هر ردیف نمایش داده شده، به همراه یک دکمهی Remove آیتم انتخابی نیز هست که به متد رویدادگردان onRemove متصل شدهاست.
- در ردیف انتهایی این لیست، مقدار totalPrice که یک خاصیت محاسباتی است، درج میشود.
- فعلا این کامپوننت نیز به هیچ مخزن دادهای متصل نیست و فقط طراحی ابتدایی آن آماده شدهاست.
ایجاد کامپوننت نمایش تعداد آیتمهای خریداری شده
کاربر اگر آیتمی را از لیست محصولات انتخاب کند و یا محصول انتخاب شده را از لیست خرید حذف کند، تعداد نهایی باقی مانده را میتوان در کامپوننت src\components\BasketItemsCounter.jsx مشاهده کرد:
import React, { Component } from "react"; class BasketItemsCounter extends Component { render() { const { count, onRemoveAll } = this.props; return ( <div> <h1>Total items: {count}</h1> <button type="button" className="btn btn-sm btn-danger" onClick={() => onRemoveAll()} > Empty Basket </button> </div> ); } } export default BasketItemsCounter;
- فعلا این کامپوننت نیز به هیچ مخزن دادهای متصل نیست و فقط طراحی ابتدایی آن آماده شدهاست.
نمایش ابتدایی سه کامپوننت توسط کامپوننت App
اکنون که این سه کامپوننت تکمیل شدهاند، میتوان المانهای آنها را در فایل src\App.js درج کرد تا در صفحه نمایش داده شوند:
import React, { Component } from "react"; import BasketItemsCounter from "./components/BasketItemsCounter"; import BasketItemsList from "./components/BasketItemsList"; import ShopItemsList from "./components/ShopItemsList"; class App extends Component { render() { return ( <main className="container"> <div className="row"> <BasketItemsCounter /> </div> <hr /> <div className="row"> <h2>Products</h2> <ShopItemsList /> </div> <div className="row"> <h2>Basket</h2> <BasketItemsList /> </div> </main> ); } } export default App;
میتوان همانند Redux کل state برنامه را داخل یک شیء store ذخیره کرد و یا چون در اینجا میتوان طراحی مخزن حالت MobX را به دلخواه انجام داد، میتوان چندین مخزن حالت را تهیه و به هم متصل کرد؛ مانند تصویری که مشاهده میکنید. در اینجا:
- src\stores\counter.js: مخزن دادهی حالت کامپوننت شمارشگر است.
- src\stores\market.js: مخزن دادهی کامپوننتهای لیست محصولات و سبد خرید است.
- src\stores\index.js: کار ترکیب دو مخزن قبل را انجام میدهد.
در ادامه کدهای کامل این مخازن را مشاهده میکنید:
مخزن حالت src\stores\counter.js
import { action, observable } from "mobx"; export default class CounterStore { @observable totalNumbersInBasket = 0; constructor(rootStore) { this.rootStore = rootStore; } @action increase = () => { this.totalNumbersInBasket++; }; @action decrease = () => { this.totalNumbersInBasket--; }; }
- در اینجا خاصیت totalNumbersInBasket به صورت observable تعریف شدهاست و با تغییر آن چه به صورت مستقیم، با مقدار دهی آن و یا توسط دو action تعریف شده، سبب به روز رسانی UI خواهد شد.
- میشد این مخزن را با مخزن src\stores\market.js یکی کرد؛ اما جهت ارائهی مثالی در مورد نحوهی تعریف چند مخزن و روش برقراری ارتباط بین آنها، به صورت مجزایی تعریف شد.
مخزن حالت src\stores\market.js
import { action, computed, observable } from "mobx"; export default class MarketStore { @observable basketItems = []; constructor(rootStore) { this.rootStore = rootStore; } @action add = product => { const selectedItem = this.basketItems.find(item => item.id === product.id); if (selectedItem) { selectedItem.count++; } else { this.basketItems.push({ ...product, count: 1 }); } this.rootStore.counterStore.increase(); }; @action remove = id => { const selectedItem = this.basketItems.find(item => item.id === id); selectedItem.count--; if (selectedItem.count === 0) { this.basketItems.remove(selectedItem); } this.rootStore.counterStore.decrease(); }; @action removeAll = () => { this.basketItems = []; this.rootStore.counterStore.totalNumbersInBasket = 0; }; @computed get totalPrice() { return this.basketItems.reduce((previous, current) => { return previous + current.price * current.count; }, 0); } }
- توسط متد add آن در کامپوننت نمایش لیست محصولات، میتوان آیتمی را به این آرایه اضافه کرد. در اینجا چون شیء product مورد استفاده دارای خاصیت count نیست، روش افزودن آنرا توسط spread operator برای درج خواص شیء product اصلی و سپس تعریف آنرا مشاهده میکنید. این فراخوانی، سبب افزایش یک واحد به عدد شمارشگر نیز میشود.
- متد remove آن در کامپوننت سبد خرید، مورد استفاده قرار میگیرد تا کاربر بتواند اطلاعاتی را از این لیست حذف کند. این فراخوانی، سبب کاهش یک واحد از عدد شمارشگر نیز میشود.
- متد removeAll آن در کامپوننت شمارشگر بالای صفحه استفاده میشود تا سبب خالی شدن آرایهی آیتمهای انتخابی گردد و همچنین عدد آنرا نیز صفر کند.
- خاصیت محاسباتی totalPrice آن در پایین جدول سبد خرید، جمع کل هزینهی قابل پرداخت را مشخص میکند.
مخزن حالت src\stores\index.js
در اینجا روش یکی کردن دو مخزن حالت یاد شده را به صورت خاصیتهای عمومی یک مخزن کد ریشه، مشاهده میکنید:
import CounterStore from "./counter"; import MarketStore from "./market"; class RootStore { counterStore = new CounterStore(this); marketStore = new MarketStore(this); } export default RootStore;
export default class MarketStore { @observable basketItems = []; constructor(rootStore) { this.rootStore = rootStore; } @action removeAll = () => { this.basketItems = []; this.rootStore.counterStore.totalNumbersInBasket = 0; }; }
پس از ایجاد مخازن حالت، اکنون نیاز است آنها را در اختیار سلسه مراتب کامپوننتهای برنامه قرار دهیم. به همین جهت به فایل src\index.js مراجعه کرده و آنرا به صورت زیر تغییر میدهیم:
import "./index.css"; import "bootstrap/dist/css/bootstrap.css"; import makeInspectable from "mobx-devtools-mst"; import { Provider } from "mobx-react"; import React from "react"; import ReactDOM from "react-dom"; import App from "./App"; import * as serviceWorker from "./serviceWorker"; import RootStore from "./stores"; const rootStore = new RootStore(); if (process.env.NODE_ENV === "development") { makeInspectable(rootStore); // https://github.com/mobxjs/mobx-devtools } ReactDOM.render( <Provider {...rootStore}> <App /> </Provider>, document.getElementById("root") ); serviceWorker.unregister();
- سپس یک وهلهی جدید از RootStore را که حاوی خاصیتهای عمومی counterStore و marketStore است، ایجاد میکنیم.
- اگر علاقمند باشید تا حین کار با MobX، جزئیات پشت صحنهی آنرا توسط افزونهی mobx-devtools ردیابی کنید، روش آنرا در اینجا با فراخوانی متد makeInspectable مشاهده میکنید. مقدار process.env.NODE_ENV نیز بر اساس پروسهی جاری node.js اجرا کنندهی برنامهی React تامین میشود. اطلاعات بیشتر
- قسمت آخر این تنظیمات، محصور کردن کامپوننت App که بالاترین کامپوننت در سلسله مراتب کامپوننتهای برنامه است، با شیء Provider میباشد. در این شیء توسط spread operator، سبب درج خواص عمومی rootStore، به عنوان مخازن قابل استفاده شدهایم. تنظیم {rootStore...} معادل عبارت زیر است:
<Provider counterStore={rootStore.counterStore} marketStore={rootStore.marketStore}>
اتصال کامپوننت ShopItemsList به مخزن حالت marketStore
پس از ایجاد rootStore و محصور کردن کامپوننت App توسط شیء Provider در فایل src\index.js، اکنون باید قسمت export default کامپوننتهای برنامه را جهت استفادهی از مخازن حالت، یکی یکی ویرایش کرد:
import { inject, observer } from "mobx-react"; import React from "react"; import products from "../services/productsService"; const ShopItemsList = ({ onAdd }) => { return ( // ... ); }; export default inject(({ marketStore }) => ({ onAdd: marketStore.add }))(observer(ShopItemsList));
اتصال کامپوننت BasketItemsList به مخزن حالت marketStore
در اینجا نیز سطر export default را جهت دریافت خاصیت marketStore، از شیء Provider تامین شدهی در فایل src\index.js، ویرایش میکنیم. به این ترتیب سه props مورد انتظار این کامپوننت، توسط خاصیتهای basketItems (آرایهی اشیاء انتخابی توسط کاربر)، totalPrice (خاصیت محاسباتی جمع کل هزینه) و متد رویدادگردان onRemove (برای حذف یک آیتم) تامین میشوند. در آخر کامپوننت را به صورت observer محصور کرده و بازگشت میدهیم تا تغییرات در مخزن حالت آن، سبب به روز رسانی UI آن شوند:
import { inject, observer } from "mobx-react"; import React from "react"; const BasketItemsList = ({ items, totalPrice, onRemove }) => { return ( // ... ); }; export default inject(({ marketStore }) => ({ items: marketStore.basketItems, totalPrice: marketStore.totalPrice, onRemove: marketStore.remove }))(observer(BasketItemsList));
اتصال کامپوننت BasketItemsCounter به دو مخزن حالت counterStore و marketStore
در اینجا روش استفادهی از decorator syntax کتابخانهی mobx-react را بر روی یک کامپوننت کلاسی مشاهده میکنید. تزئین کنندهی inject، امکان دسترسی به مخازن حالت تزریقی به شیء Provider را میسر کرده و سپس توسط آن میتوان props مورد انتظار کامپوننت را از مخازن متناظر استخراج کرده و در اختیار کامپوننت قرار داد. همچنین این کامپوننت توسط تزئین کنندهی observer نیز علامت گذاری شدهاست. در این حالت نیازی به تغییر سطر export default نیست.
import { inject, observer } from "mobx-react"; import React, { Component } from "react"; @inject(rootStore => ({ count: rootStore.counterStore.totalNumbersInBasket, onRemoveAll: rootStore.marketStore.removeAll })) @observer class BasketItemsCounter extends Component { render() { const { count, onRemoveAll } = this.props; return ( // ... ); } } export default BasketItemsCounter;
کدهای کامل این قسمت را میتوانید از اینجا دریافت کنید: state-management-with-mobx-part4.zip
یکی از مزایای Blazor، استفاده از دانش C# / HTML / CSS (که خیلی از ما اینها را هم اکنون بلد هستیم) برای نوشتن برنامههای وب (SPA / PWA)، برنامههای Android / iOS / Windows و وبسایتهایی با قابلیت Pre Rendering و SEO Friendly است. با یک بار کدنویسی به کمک Blazor، ولی با Configurationهای متفاوت میتوان خروجیهای مختلفی را برای پلتفرمهای مختلف گرفت؛ برای مثال Blazor Hybrid خروجی Android / iOS / Windows و Blazor Web Assembly خروجی PWA / SPA و در نهایت Blazor Static خروجی وب سایت میدهد. به علاوه حالت Blazor Server نیز وجود دارد که امروزه بزرگترین مزیت آن، Development experience فوقالعادهاش هست که در آن با استفاده از Hot Reload، میتوان تغییرات در فایلهای SCSS / C# / Razor را به صورت آنی، بدون نیاز به Build مجدد، رفرش کردن و از دست دادن State مشاهده نمود. امکان استفاده از Nuget Packageهای DotNet ای در Android / iOS / Windows / Web در کنار امکان استفاده از امکانات Native هر پلتفرم نیز از دیگر مزایای این روش است.
اما یکی از موانع استفادهی جدی از Blazor در پروژههای داخلی، نبود تقویم شمسی است که سبک بوده و پیش نیاز خاصی جز خود Blazor نداشته باشد. یک راه حل جدید برای حل این مشکل، استفاده از Bit Components است که اخیرا به صورت Open Source ارائه شده است. شما میتوانید Repository مربوطه را Fork نموده، Clone نمایید، به فولدر src بروید و با ویژال استودیو، Bit.Client.Web.BlazorUI.sln را باز کنید و سورس کامپوننتها را به همراه تستهای خودکار آن ببینید.
در سایت مربوطه نیز میتوانید دمویی از بیش از ۲۷ کامپوننت را شامل File uploader، Drop Down، Date Picker، Color Picker، Tree list و... مشاهده کنید که هر کدام دارای Documentation کامل بوده و آماده به استفاده در پروژههای شما هستند.
برای استفاده از Bit Components در پروژه خود، ابتدا Package مربوطه را نصب نمایید و سپس فایل js و css مربوطه را نیز به index.html یا Host.cshtml یا Layout.cshtml اضافه کنید (بسته به تنظیمات پروژهتان).
در Bit Components جز معدود مواردی که چند خطی با JavaScript توسعه داده شدهاست، کمپوننتها با C# / Razor / CSS توسعه داده شدهاند. این روش نسبت به روشهایی که بر روی کمپوننتهای کاملا JavaScript ای، اصطلاحا Wrapper ایجاد میکنند، دارای دو مزیت سرعت بالاتر و تضمین کار کردن آن در حالتهای مختلف مانند Pre Rendering است.
<link href="_content/Bit.Client.Web.BlazorUI/styles/bit.blazorui.min.css" rel="stylesheet" />
<script src="_content/Bit.Client.Web.BlazorUI/scripts/bit.blazorui.min.js"></script>
@using Bit.Client.Web.BlazorUI
<BitButton>Hello!</BitButton>
<BitDatePicker SelectedDate="@BirthDate"></BitDatePicker>
در C# .NET، کلاس CultureInfo، وظیفه نگهداری مواردی چون چند زبانگی، تقویمهای مختلف (اعم از شمسی و...)، موارد مربوط به ارز (برای مثال علامت $ یا ریال و...) را به عهده دارد. از جمله مزایای BitDatePicker، سازگاری با CultureInfo است، به نحوی که CultureInfo.CurrentUICulture هر چه که باشد، بر اساس آن عمل میکند. بنابراین میتوانید در Program.cs پروژه Blazor خود بنویسید:
CultureInfo.CurrentUICulture = new CultureInfo("fa-IR");
سوال اول: اگر بخواهیم در کل سیستم، تقویم شمسی باشد، ولی در یکی از صفحات میلادی چه؟ خب میتوانیم در آن صفحه، به شکل زیر از BitDatePicker استفاده کنیم:
<BitDatePicker Culture="@(new System.Globalization.CultureInfo("en-US"))" />
سوال دوم: تقویم شمسی نمایش داده شده، اسامی ماهها را به صورت فینگلیش نمایش میدهد و یا اسامی خلاصه شده روزها صحیح نیست!
این به خود BitDatePicker ربطی ندارد، بلکه به CultureInfo فارسی خود dotnet مرتبط است، اما شما چگونه میتوانید این مورد را بهبود بدید؟
شما میتوانید ابتدا با
var cultureInfo = CultureInfo.CreateSpecificCulture("fa-IR")
cultureInfo.DateTimeFormat.MonthNames = new[] { "فروردین", "اردیبهشت", "خرداد", "تیر", "مرداد", "شهریور", "مهر", "آبان", "آذر", "دی", "بهمن", "اسفند", "" };
در ادامه لازم هست چه Culture Info ای را که خودتان سفارشی سازی کردهاید، یا Culture Infoهای سیستمی را در CultureInfo.CurrentUICulture قرار بدهید تا BitDatePicker از آن پیروی کند.
در صورت بروز هر گونه مشکلی یا درخواست اضافه شدن امکانی، در repo مربوطه روی GitHub میتوانید یک issue را ثبت کنید.
ASP.NET is almost 15 years old and it has evolved to become a flexible and extremely powerful platform. But 15 years is a long time, especially in the world of web technology, and the folks at Microsoft knew it was time to change ASP.NET.
In this course, Envato Tuts+ instructor Jeremy McPeak will walk you through the major changes to ASP.NET in version 5. Along the way, you'll see how these changes will affect your project and how the new features can make your life as a developer easier. You'll also get a first look at the new Tag Helpers, inversion of control and dependency injection features, with practical examples.
در قسمت قبل با معرفی نوع props توسط TypeScript، مجبور به تکمیل اجباری تک تک آنها شدیم؛ اما در React میتوان props را به صورت اختیاری و یا با مقادیری پیشفرض نیز تعریف کرد.
روش تعیین props پیشفرض توسط TypeScript
اگر بخواهیم توسط روشهای خود React، مقادیر پیشفرض props را تعیین کنیم، میتوان از defaultProps به صورت زیر با تعریف یک شیء جاوا اسکریپتی از پیش مقدار دهی شده، استفاده کرد:
اما در حالت استفادهی از TypeScript و یا حتی نگارش ES6 آن (React در حالت پیشفرض آن)، میتوان مقادیر پیشفرض props را با مقدار دهی مستقیم متغیرهای حاصل از Object Destructuring آن، تعیین کرد:
در اینجا هر متغیری که با مقداری پیشفرض، مقدار دهی شده باشد، اختیاری در نظر گرفته شده و اگر دارای مقدار پیشفرضی نباشد، باید به صورت اجباری در حین تعریف المان این کامپوننت، ذکر شود.
در این حالت انتظار داریم که در حین استفاده و تعریف المان کامپوننت Head، اگر برای مثال ویژگی isActive را ذکر نکردیم، کامپایلر TypeScript خطایی را گزارش نکند؛ که اینطور نیست:
هنوز هم در اینجا میتوان خطای عدم تعریف خاصیت isActive را مشاهده کرد. برای رفع این مشکل، به صورت زیر عمل میکنیم:
در حین تعریف یک type، اگر خاصیتی با علامت ? ذکر شود، به معنای اختیاری بودن آن است. همچنین در اینجا مقدار پیشفرض title را هم حذف کردهایم تا تعریف آن اجباری شود. بنابراین در typeها، تمام خواص اجباری هستند؛ مگر اینکه توسط ? به صورت اختیاری تعریف شوند. این مورد هم مزیتی است که در ابتدای طراحی props یک کامپوننت، باید در مورد اختیاری و یا اجباری بودن آنها بیشتر فکر کرد. همچنین نیازی به استفاده از روشهای غیراستانداردی مانند Head.defaultProps خود React نیست. ذکر مقدار پیشفرض متغیرهای حاصل از Object Destructuring، جزئی از جاوااسکریپت استاندارد است و یا مشخص سازی خواص اختیاری در TypeScript، فقط مختص به پروژههای React نیست و در همه جا به همین شکل کاربرد دارد.
اکنون با تعریف isActive?: boolean، دیگر شاهد نمایش خطایی در حین تعریف المان Head، بدون ذکر خاصیت isActive، نخواهیم بود.
تعریف انواع و اقسام نوعهای props
تا اینجا نوعهای سادهای مانند string و boolean و همچنین نحوهی تعریف اجباری و اختیاری آنها را بررسی کردیم. در ادامه یک نمونهی کاملتر را مشاهده میکنید:
- در ابتدا نوعهای متداولی مانند number و string ذکر شدهاند.
- سپس نحوهی تعریف آرایهای از رشتهها را مشاهده میکنید.
- یا میتوان مقدار یک خاصیت را تنها به مقادیری خاص محدود کرد؛ مانند خاصیت status در اینجا و اگر در حین مقدار دهی این خاصیت، از مقدار دیگری استفاده شود، تایپاسکریپت، خطایی را صادر میکند.
- در ادامه سه روش تعریف اشیاء تو در تو را مشاهده میکنید؛ خاصیت thing از نوع یک شیء خالی تعریف شدهاست (بجای آن میتوان از نوع object هم استفاده کرد). خاصیت thing2 از نوع یک شیء که دارای خاصیت رشتهای name است، تعریف شده و یا بهتر است این نوع تعاریف را به یک type مستقل دیگر مانند User منتقل کرد و سپس از آن جهت تعیین نوع خاصیتی مانند user استفاده نمود.
- در اینجا حتی میتوان یک خاصیت را که از نوع یک تابع است، تعریف کرد. در این تعریف، void نوع خروجی آن است.
روش تعریف props تابعی در TypeScript
برای بررسی روش تعریف نوع توابع ارسالی از طریق props، ابتدا کامپوننت جدید src\components\Button.tsx را ایجاد میکنیم. سپس آنرا به صورت زیر تکمیل خواهیم کرد:
در این کامپوننت، متغیر onClick حاصل از Object Destructuring شیء props دریافتی، یک تابع است که قرار است با کلیک بر روی دکمهای که در این کامپوننت قرار دارد، پیامی را به کامپوننت والد ارسال کند.
با توجه به تعریف { onClick }، در همان لحظه، خطای any بودن نوع آن از طرف TypeScript گزارش داده میشود. بنابراین نوع جدید Props را ایجاد کرده و برای onClick، نوع متناسبی را تعریف میکنیم. در اینجا 4 روش مختلف تعریف نوع function را در TypeScript مشاهده میکنید؛ دو حالت آن با ذکر پرانتزها و درج امضای متد انجام شده و دو حالت دیگر به کمک arrow functions پیاده سازی شدهاند.
برای نمونه آخرین حالت تعریف شده از روش arrow functions استفاده میکند که متداولترین روش تعریف نوع توابع است (چون عنوان میکند که نوع onClick، یک تابع است و آنرا شبیه به یک متد معمولی نمایش نمیدهد) که در آن در ابتدا امضای پارامترهای این تابع مشخص شدهاند و در ادامه پس از <=، نوع خروجی این تابع تعریف شدهاست که void میباشد (این تابع چیزی را بر نمیگرداند).
در آخر، تعریف المان آنرا به صورت زیر به فایل src\App.tsx اضافه میکنیم که onClick آن یک مقدار را دریافت کرده و سپس آنرا در کنسول نمایش میدهد.
البته خروجی از نوع void، در اینجا بسیار معمول است؛ چون هدف از این نوع توابع بیشتر ارسال مقادیری به کامپوننت در برگیرندهی آنها است (مانند value در اینجا) و اگر برای مثال خروجی رشتهای را داشته باشند، باید در حین درج و تعریف المان آنها، برای نمونه یک "return "value1 را هم در انتهای کار قرار داد که عملا استفادهای ندارد و بیمعنا است:
روش تعیین props پیشفرض توسط TypeScript
اگر بخواهیم توسط روشهای خود React، مقادیر پیشفرض props را تعیین کنیم، میتوان از defaultProps به صورت زیر با تعریف یک شیء جاوا اسکریپتی از پیش مقدار دهی شده، استفاده کرد:
Head.defaultProps = { title: "Hello", isActive: true };
type Props = { title: string; isActive: boolean; }; export const Head = ({ title = "Hello", isActive = true }: Props) => {
در این حالت انتظار داریم که در حین استفاده و تعریف المان کامپوننت Head، اگر برای مثال ویژگی isActive را ذکر نکردیم، کامپایلر TypeScript خطایی را گزارش نکند؛ که اینطور نیست:
هنوز هم در اینجا میتوان خطای عدم تعریف خاصیت isActive را مشاهده کرد. برای رفع این مشکل، به صورت زیر عمل میکنیم:
type Props = { title: string; isActive?: boolean; }; export const Head = ({ title, isActive = true }: Props) => {
اکنون با تعریف isActive?: boolean، دیگر شاهد نمایش خطایی در حین تعریف المان Head، بدون ذکر خاصیت isActive، نخواهیم بود.
تعریف انواع و اقسام نوعهای props
تا اینجا نوعهای سادهای مانند string و boolean و همچنین نحوهی تعریف اجباری و اختیاری آنها را بررسی کردیم. در ادامه یک نمونهی کاملتر را مشاهده میکنید:
type User = { name: string; }; type Props = { title: string; isActive?: boolean; count: number; options: string[]; status: "loading" | "loaded"; thing: {}; thing2: { name: string; }; user: User; func: () => void; };
- سپس نحوهی تعریف آرایهای از رشتهها را مشاهده میکنید.
- یا میتوان مقدار یک خاصیت را تنها به مقادیری خاص محدود کرد؛ مانند خاصیت status در اینجا و اگر در حین مقدار دهی این خاصیت، از مقدار دیگری استفاده شود، تایپاسکریپت، خطایی را صادر میکند.
- در ادامه سه روش تعریف اشیاء تو در تو را مشاهده میکنید؛ خاصیت thing از نوع یک شیء خالی تعریف شدهاست (بجای آن میتوان از نوع object هم استفاده کرد). خاصیت thing2 از نوع یک شیء که دارای خاصیت رشتهای name است، تعریف شده و یا بهتر است این نوع تعاریف را به یک type مستقل دیگر مانند User منتقل کرد و سپس از آن جهت تعیین نوع خاصیتی مانند user استفاده نمود.
- در اینجا حتی میتوان یک خاصیت را که از نوع یک تابع است، تعریف کرد. در این تعریف، void نوع خروجی آن است.
روش تعریف props تابعی در TypeScript
برای بررسی روش تعریف نوع توابع ارسالی از طریق props، ابتدا کامپوننت جدید src\components\Button.tsx را ایجاد میکنیم. سپس آنرا به صورت زیر تکمیل خواهیم کرد:
import React from "react"; type Props = { // onClick(): string; method returns string // onClick(): void method returns nothing; // onClick(text: string): void; method with params // onClick: () => void; function returns nothing onClick: (text: string) => void; // function with params }; export const Button = ({ onClick }: Props) => { return <button onClick={() => onClick("hi")}>Click Me</button>; };
با توجه به تعریف { onClick }، در همان لحظه، خطای any بودن نوع آن از طرف TypeScript گزارش داده میشود. بنابراین نوع جدید Props را ایجاد کرده و برای onClick، نوع متناسبی را تعریف میکنیم. در اینجا 4 روش مختلف تعریف نوع function را در TypeScript مشاهده میکنید؛ دو حالت آن با ذکر پرانتزها و درج امضای متد انجام شده و دو حالت دیگر به کمک arrow functions پیاده سازی شدهاند.
برای نمونه آخرین حالت تعریف شده از روش arrow functions استفاده میکند که متداولترین روش تعریف نوع توابع است (چون عنوان میکند که نوع onClick، یک تابع است و آنرا شبیه به یک متد معمولی نمایش نمیدهد) که در آن در ابتدا امضای پارامترهای این تابع مشخص شدهاند و در ادامه پس از <=، نوع خروجی این تابع تعریف شدهاست که void میباشد (این تابع چیزی را بر نمیگرداند).
در آخر، تعریف المان آنرا به صورت زیر به فایل src\App.tsx اضافه میکنیم که onClick آن یک مقدار را دریافت کرده و سپس آنرا در کنسول نمایش میدهد.
البته خروجی از نوع void، در اینجا بسیار معمول است؛ چون هدف از این نوع توابع بیشتر ارسال مقادیری به کامپوننت در برگیرندهی آنها است (مانند value در اینجا) و اگر برای مثال خروجی رشتهای را داشته باشند، باید در حین درج و تعریف المان آنها، برای نمونه یک "return "value1 را هم در انتهای کار قرار داد که عملا استفادهای ندارد و بیمعنا است:
import { Button } from "./components/Button"; import { Head } from "./components/Head"; // ... function App() { return ( <div className="App"> <Head title="Hello" /> <Button onClick={(value) => { console.log(value); }} /> // ...
These are some key features of the framework itself:
Modular and NLayered architecture
Multi-tenancy
Dependency Injection
Domain Driven Design
Unit of work implementation
Flexible Localization system
Automatic data filters
Audit logging
Setting management
Menu management
Authorization
Exception handling
Validation
Logging
Event bus for domain events
Auto-creating Web API layer for Application Services
Auto-creating Javascript proxy layer to use Web API layer
Javascript helper methods for ajax, notifications, message boxes, making UI busy...
Easily working with embedded resources
Useful extension and helper methods
اشتراکها
کتابخانه JSIL
JSIL is a compiler that transforms .NET applications and libraries
from their native executable format - CIL bytecode - into
standards-compliant, cross-browser JavaScript. You can take this
JavaScript and run it in a web browser or any other modern JavaScript
runtime. Unlike other cross-compiler tools targeting JavaScript, JSIL
produces readable, easy-to-debug JavaScript that resembles the code a
developer might write by hand, while still maintaining the behavior and
structure of the original .NET code. Demo