ng build --prod
در حالیکه importهای مربوط به سرویس به فایل rxjs-operators.ts اضافه شده است.
import 'rxjs/add/operator/filter'; import 'rxjs/add/operator/distinctUntilChanged';
ng build --prod
در حالیکه importهای مربوط به سرویس به فایل rxjs-operators.ts اضافه شده است.
import 'rxjs/add/operator/filter'; import 'rxjs/add/operator/distinctUntilChanged';
<input type="text" [(ngModel)]="myModel" (blur)="onBlurMethod()">
export class AppComponent { myModel: any; constructor(){ this.myModel = '123'; } onBlurMethod(){ alert(this.myModel); } }
export class AppComponent { searchForm: ControlGroup; results: Observable<any[]>; constructor(private http: Http) { let searchField = new Control(); this.searchForm = new ControlGroup({searchField}); this.results = searchField.valueChanges .debounceTime(500) .switchMap((val:string) => { return this.search(val); }); }
Dart کتابخانه ای است که توسط شرکت گوگل ارائه شده است و گفته میشود، قرار است جایگزین جاوا اسکریپت گردد و از آدرس https://www.dartlang.org قابل دسترسی میباشد. این کتابخانه، دارای انعطاف پذیری فوق العاده بالایی است و کد نویسی Java Script را راحتتر میکند. در حال حاضر هیچ مرورگری به غیر از Chromium از این تکنولوژی پشتیبانی نمیکند و جهت تسهیل در کدنویسی، باید از ویرایشگر Dart Editor استفاده کنید. این ویرایشگر کدهای نوشته شده را به دو صورت Native و JavaScript Compiled در اختیار مرورگر قرار میدهد. در ادامه با نحوهی کار و راه اندازی Dart آشنا خواهید شد.
ابتدا Dart و ویرایشگر مربوط به آن را توسط لینکهای زیر دانلود کنید:
دانلود نسخه 64 بیتی دارت + ویرایشگر
دانلود نسخه 32 بیتی دارت + ویرایشگر
بعد از اینکه فایلهای فوق را از حالت فشرده خارج کردید، پوشه ای با نام dart ایجاد مینماید. وارد پوشه dart شده و DartEditor را اجرا کنید.
توجه: جهت اجرای dart به JDK 6.0 یا بالاتر نیاز دارید
در مرحله بعد نمونه کدهای Dart را از لینک زیر دانلود نمایید و از حالت فشرده خارج کنید. پوشه ای با نام one-hour-codelab ایجاد میگردد.
از منوی File > Open Existing Folder… پوشه one-hour-codelab را باز کنید .
توضیحات
- پوشه packages و همچنین فایلهای pubspec.yaml و pubspec.lock شامل پیش نیازها و Package هایی هستند که جهت اجرای برنامههای تحت Dart مورد نیاز هستند. Dart Editor این نیازمندیها را به صورت خودکار نصب و تنظیم میکند.
توجه: اگر پوشه Packages را مشاهده نکردید و یا در سمت چپ فایلها علامت X قرمز رنگ وجود داشت، بدین معنی است که package ها به درستی نصب نشده اند. برای این منظور بر روی pubspec.yaml کلیک راست نموده و گزینه Get Pub را انتخاب کنید. توجه داشته باید که بدلیل تحریم ایران توسط گوگل باید از ابزارهای عبور از تحریم استفاده کنید.
- 6 پوشه را نیز در تصویر فوق مشاهده میکنید که نمونه کد piratebadge را بصورت مرحله به مرحله انجام داده و به پایان میرساند.
- Dart SDK شامل سورس کد مربوط به تمامی توابع، متغیرها و کلاس هایی است که توسط کیت توسعه نرم افزاری Dart ارائه شده است.
- Installed Packages شامل سورس کد مربوط به تمامی توابع، متغیرها و کلاسهای کتابخانههای اضافهتری است که Application به آنها وابسته است.
گام اول: اجرای یک برنامه کوچک
در این مرحله سورس کدهای آماده را مشاهده میکنید و با ساختار کدهای Dart و HTML آشنا میشوید و برنامه کوچکی را اجرا مینمایید.
در Dart Editor پوشه 1-blankbadge را باز کنید و فایلهای piratebadge.html و piratebadge.dart را مشاهده نمایید.
کد موجود در فایل piratebadge.html
<html> <head> <meta charset="utf-8"> <title>Pirate badge</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link rel="stylesheet" href="piratebadge.css"> </head> <body> <h1>Pirate badge</h1> <div> TO DO: Put the UI widgets here. </div> <div> <div> Arrr! Me name is </div> <div> <span id="badgeName"> </span> </div> </div> <script type="application/dart" src="piratebadge.dart"></script> <script src="packages/browser/dart.js"></script> </body> </html>
توضیحات
- در کد HTML ، اولین تگ <script> ، فایل piratebadge.dart را جهت پیاده سازی دستورات dart به صفحه ضمیمه مینماید
- Dart Virtual Machine (Dart VM) کدهای Dart را بصورت Native یا بومی ماشین اجرا میکند. Dart VM کدهای خود را در Dartium که یک ویرایش ویژه از مرورگر Chromium میباشد اجرا میکند که میتواند برنامههای تحت Dart را بصورت Native اجرا کند.
- فایل packages/browser/dart.js پشتیبانی مرورگر از کد Native دارت را بررسی میکند و در صورت پشتیبانی، Dart VM را راه اندازی میکند و در غیر این صورت JavaScript کامپایل شده را بارگزاری مینماید.
کد موجود در piratebadge.dart
void main() { // Your app starts here. }
- این فایل شامل تابع main میباشد که تنها نقطه ورود به application است. تگ <script> موجود در piratebadge.html برنامه را با فراخوانی این تابع راه اندازی میکند.
- تابع main() یک تابع سطح بالا یا top-level میباشد.
- متغیرها و توابع top-level عناصری هستند که خارج از ساختار تعریف کلاس ایجاد میشوند.
جهت اجرای برنامه در Dart Editor بر روی piratebadge.html کلیک راست نمایید و گزینه Run in Dartium را اجرا کنید. این فایل توسط Dartium اجرا میشود و تابع main() را فراخوانی میکند و صفحه ای همانند شکل زیر را نمایش میدهد.
گام دوم: افزودن فیلد input
توجه داشته باشید که در این مرحله یا میتوانید تغییرات مورد نظر خود را در طی آموزش بر روی پوشهی 1-blankbadge اعمال کنید و یا به پوشههای تهیه شده در نمونه کد موجود در همین پروژه مراجعه نمایید.
در این مرحله یک تگ <input> به تگ <div class=”widgets”> اضافه کنید.
... <div> <div> <input type="text" id="inputName" maxlength="15"> </div> </div> ...
سپس کتابخانه dart:html را به ابتدای فایل piratebadge.dart اضافه کنید.
import 'dart:html';
توضیحات
- دستور فوق کلاسها و Resource های موجود در کتابخانه dart:html را اضافه میکند.
- از حجیم شدن کدهای خود نگران نباشید، زیرا فرایند کامپایل کدهای اضافی را حذف خواهد کرد.
- کتابخانه dart:html شامل کلاسهایی جهت کار با عناصر DOM و توابعی جهت دسترسی به این عناصر میباشد.
- در مباحث بعدی یاد میگیرید که با استفاده از کلمه کلیدی show فقط کلاسهایی را import کنید که به آن نیاز دارید.
- اگر کتابخانه ای در هیچ بخش کد استفاده نشود، خود Dart Editor به صورت warning اخطار میدهد و میتوانید آن را حذف کنید.
دستور زیر را در تابع main بنویسید تا رویداد مربوط به ورود اطلاعات در فیلد input را مدیریت نمایید.
void main() { querySelector('#inputName').onInput.listen(updateBadge); }
توضیحات
- تابع querySelector() در کتابخانه dart:html تعریف شده است و یک المنت DOM را جستجو مینماید. پارامتر ورودی آن یک selector میباشد که در اینجا فیلد input را توسط #inputName جستجو نمودیم که یک ID Selector میباشد.
- نوع خروجی این متد یک شی از نوع DOM میباشد.
- تابع onInput.Listen() رویدادی را برای پاسخگویی به ورود اطلاعات در فیلد input تعریف میکند. زمانی که کاربر اطلاعاتی را وارد نماید، تابع updateBadge فراخوانی میگردد.
- رویداد input زمانی رخ میدهد که کاربر کلیدی را از صفحه کلید فشار دهد.
- رشتهها همانند جاوا اسکریپت میتوانند در " یا ' قرار بگیرند.
تابع زیر را به صورت top-level یعنی خارج از تابع main تعریف کنید.
... void updateBadge(Event e) { querySelector('#badgeName').text = e.target.value; }
توضیحات
- این تابع محتوای المنت badgeName را به محتوای وارد شده در فیلد input تغییر میدهد.
- پارامتر ورودی این تابع شی e از نوع Event میباشد و به همین دلیل میتوانیم این تابع را یک Event Handler بنامیم.
- e.target به شی ای اشاره میکند که موجب رخداد رویداد شده است و در اینجا همان فیلد input میباشد
- با نوشتن کد فوق یک warning را مشاهده میکنید که بیان میکند ممکن است خصوصیت value برای e.target وجود نداشته باشد. برای حل این مسئله کد را بصورت زیر تغییر دهید.
... void updateBadge(Event e) { querySelector('#badgeName').text = (e.target as InputElement).value; }
توضیحات
- کلمه کلیدی as به منظور تبدیل نوع استفاده میشود که e.target را به یک InputElement تبدیل میکند.
همانند گام اول برنامه را اجرا کنید و نتیجه را مشاهده نمایید. با تایپ کردن در فیلد input به صورت همزمان در کادر قرمز رنگ نیز نتیجه تایپ را مشاهده مینمایید.
npm install @angular/cdk
import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { DragDropModule } from '@angular/cdk/drag-drop'; import { FormsModule } from '@angular/forms'; @NgModule({ declarations: [ AppComponent ], imports: [ BrowserModule, FormsModule, DragDropModule ], bootstrap: [ AppComponent ] }) export class AppModule {}
<div cdkDrag> I'm Draggable </div>
<div cdkDrag cdkDragLockAxis="x"> I'm Draggable </div>
در ابتدا یک مدل را ایجاد میکنیم:
export interface Todo { title: string; type?: string; }
فایل app.component.ts
export class AppComponent implements OnInit { public title = 'Darg and drop'; public model: Todo; public todo: Todo[]; public done: Todo[]; public cancelled: Todo[]; ngOnInit(): void { this.setDefalutValue(); } addItem(form, $event: Event) { $event.preventDefault(); if (form.valid) { if (this.model.type === 'todo') { this.todo.push({ title: this.model.title }); } else { this.done.push({ title: this.model.title }); } } else { alert('فرم معتبر نمیباشد . عنوان را وارد نمایید'); } } drop(event: CdkDragDrop<Todo[]>) { if (event.previousContainer === event.container) { moveItemInArray(event.container.data, event.previousIndex, event.currentIndex); } else { transferArrayItem(event.previousContainer.data, event.container.data, event.previousIndex, event.currentIndex); } } private setDefalutValue() { this.todo = [ { title: 'خرید مواد غذایی' }, { title: 'رفتن به خانه' }, { title: 'خوابیدن' } ]; this.done = [ { title: 'بیدار شدن' }, { title: 'مسواک زدن' }, { title: 'دوش گرفتن' }, { title: 'چک کردن ایمیل' } ]; this.cancelled = []; this.model = { title: null, type: 'todo' }; } }
در اینجا 3 آرایه یکی برای to-do و یکی برای Done و دیگری برای Cancelled ایجاد میکنیم و به هر کدام تعدادی آیتم را اضافه میکنیم.
addItem : زمانیکه فرم را submit میکنیم اجرا میشود .
drop : زمانی اجرا میشود که یک آیتم را Drag and Drop کنیم. در ایجا شرط event.previousContainer === event.container زمانی درست است که جابجایی در درون یک لیست باشد و هدف در این صورت، مرتب سازی است .
moveItemInArray : ایندکس آیتمها را در همان لیست تغیر میدهد (مرتب سازی).
transferArrayItem: آیتم را از یک لیست حذف و به لیست دیگری اضافه میکند.
فایل app.component.html
<div> <!-- فرم --> <div> <fieldset> <legend> اضافه کردن آیتم جدید </legend> <form #form="ngForm" (submit)="addItem(form,$event)"> <label></label> <input type="text" required name="title" #name="ngModel" [(ngModel)]="this.model.title"> <label></label> <select required name="type" #type="ngModel" [(ngModel)]="this.model.type"> <option value="todo"> انجام دادن </option> <option value="done"> انجام شده </option> </select> <input type="submit" value="ذخیره"> </form> </fieldset> </div> <!-- آیتمها --> <div> <fieldset> <legend> لیست آیتمها </legend> <div> <!-- انجام دادن --> <div> <p> انجام دادن </p> <div cdkDropList #todoList="cdkDropList" [cdkDropListData]="todo" [cdkDropListConnectedTo]="[doneList, cancelledList]" (cdkDropListDropped)="drop($event)"> <div *ngFor="let item of todo" cdkDrag> <p> {{ item.title | titlecase }} </p> </div> </div> </div> <!-- انجام شده --> <div> <p> انجام شده </p> <div cdkDropList #doneList="cdkDropList" [cdkDropListData]="done" [cdkDropListConnectedTo]="[todoList, cancelledList]" (cdkDropListDropped)="drop($event)"> <div *ngFor="let item of done" cdkDrag> <p> {{ item.title | titlecase }} </p> </div> </div> </div> <!-- انجام نشده --> <div> <p> انجام نشده </p> <div cdkDropList #cancelledList="cdkDropList" [cdkDropListData]="cancelled" [cdkDropListConnectedTo]="[todoList, doneList]" (cdkDropListDropped)="drop($event)"> <div *ngFor="let item of cancelled" cdkDrag> <p> {{ item.title | titlecase }} </p> </div> </div> </div> </div> </fieldset> </div> </div>
در ابتدا یک فرم داریم که در اینجا همه چیز مشخص است ( فرمهای مبتنی بر قالبها در Angular )
در ادامه 3 container را ایجاد میکنیم یکی برای to-do و یکی برای Done و در آخر یکی برای Cancelled
container ایجاد شده برای to-do :
<div cdkDropList #todoList="cdkDropList" [cdkDropListData]="todo" [cdkDropListConnectedTo]="[doneList, cancelledList]" (cdkDropListDropped)="drop($event)"> <div *ngFor="let item of todo" cdkDrag> <p> {{ item.title | titlecase }} </p> </div> </div>
توضیحات:
cdkDropList : یک container میباشد، برای آیتمهایی که قرار است Drag and Drop شوند.
todoList #:
id مربوط به container را مشخص میکند.
cdkDropListConnectedTo:
id مربوط به container های دیگری که میتواند آیتم های container جاری را بپذیرد.
cdkDropListData: مشخص کنند منبع داده است.
cdkDropListDropped: این رویداد زمانی اجرا میشود که Drag and Drop برای یک آیتم انجام شود.
cdkDrag: برای اینکه آیتمهای درون یک container قابلیت Drag and Drop را داشته باشند، این دایرکتیو را اضافه میکنیم.
تمام !
<div class="container-fluid"> <div class="row-fluid"> <div class="span12"> <div class="dropdown"> <a class="dropdown-toggle" data-toggle="dropdown" href="#">منوی پایین افتادنی <span class="caret"></span></a> <ul class="dropdown-menu"> <li><a href="http://www.site1.com">site1</a></li> <li><a href="http://www.site2.com">site2</a></li> <li><a href="http://www.site3.com">site3</a></li> <li class="divider"></li> <li class="dropdown-submenu"><a href="#">سایر موارد</a> <ul class="dropdown-menu"> <li><a href="http://www.site4.com">site4</a></li> <li><a href="http://www.site5.com">site5</a></li> </ul> </li> </ul> </div> </div> </div> </div>
<div class="container-fluid"> <div class="row-fluid"> <div class="span12"> <div class="btn-group"> <a class="btn btn-primary" href="#">Split button</a> <a class="btn btn-primary dropdown-toggle" data-toggle="dropdown" href="#"><span class="caret"></span></a> <ul class="dropdown-menu"> <li><a href="http://www.site1.com">site1</a></li> <li><a href="http://www.site2.com">site2</a></li> </ul> </div> </div> </div> </div>
<div class="container"> <div class="row"> <h2> Navigational menus </h2> <div class="span3"> <ul class="nav nav-tabs"> <li class="nav-header">nav nav-tabs</li> <li class="active"><a href="#">Home</a></li> <li><a href="#">Contact</a></li> <li><a href="#">Login</a></li> </ul> </div> <div class="span3"> <ul class="nav nav-tabs nav-stacked"> <li class="nav-header">nav nav-tabs nav-stacked</li> <li class="active"><a href="#">Home</a></li> <li><a href="#">Contact</a></li> <li><a href="#">Login</a></li> </ul> </div> <div class="span3"> <ul class="nav nav-pills"> <li class="nav-header">nav nav-pills</li> <li class="active"><a href="#">Home</a></li> <li><a href="#">Contact</a></li> <li><a href="#">Login</a></li> </ul> </div> <div class="span3"> <ul class="nav nav-list"> <li class="nav-header">nav nav-list</li> <li class="active"><a href="#">Home</a></li> <li><a href="#">Contact</a></li> <li><a href="#">Login</a></li> <li class="dropdown"><a class="dropdown-toggle" data-toggle="dropdown" href="#">Menu ...<span class="caret"></span></a> <ul class="dropdown-menu"> <li><a href="http://www.site1.com">site1</a></li> <li><a href="http://www.site2.com">site2</a></li> </ul> </li> </ul> </div> </div> </div>
<div class="container"> <div class="row"> <div class="span12"> <div class="tabbable"> <ul class="nav nav-tabs"> <li class="active"><a data-toggle="tab" href="#tab1">Home</a></li> <li><a data-toggle="tab" href="#tab2">About</a></li> </ul> <div class="tab-content"> <div class="tab-pane active" id="tab1"> data ... data ... </div> <div class="tab-pane" id="tab2"> data2 ... data2 ... </div> </div> </div> </div> </div> </div>
<div class="container"> <div class="row"> <div class="span12"> <nav class="navbar navbar-inverse"> <div class="navbar-inner"> <a href="/" class="brand">نام سایت در اینجا</a> <a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse"> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </a> <div class="nav-collapse collapse"> <ul class="nav"> <li><a href="/about">درباره</a></li> <li><a href="/contact">تماس</a></li> <li class="dropdown"><a class="dropdown-toggle" data-toggle="dropdown" href="#">Menu ...<span class="caret"></span></a> <ul class="dropdown-menu"> <li><a href="http://www.site1.com">site1</a></li> <li><a href="http://www.site2.com">site2</a></li> </ul> </li> </ul> <form class="navbar-search pull-right input-append" action="/search"> <input class="span2" type="search" /> <button class="btn" type="button"> جستجو</button> </form> </div> </div> </nav> </div> </div> </div>
<div class="container"> <div class="row"> <div class="span12"> <ul class="breadcrumb"> <li><a href="/level1">سطح یک</a> <span class="divider">/</span></li> <li><a href="/level2">سطح دو</a> <span class="divider">/</span></li> <li class="active">سطح سه</li> </ul> </div> </div> </div>
<div class="container"> <div class="row"> <div class="span12"> <div class="pagination pagination-centered pagination-large"> <ul> <li class="disabled"><a href="#">Prev</a></li> <li class="active"><a href="/page/1">1</a></li> <li><a href="/page/2">2</a></li> <li><a href="/page/3">3</a></li> <li><a href="/page/4">4</a></li> <li><a href="#">Next</a></li> </ul> </div> </div> </div> </div>
<div class="container"> <div class="row"> <div class="span12"> <section> <ul class="unstyled"> <li><span class="label">پیش فرض</span></li> <li><span class="label label-success">موفقیت آمیز</span></li> <li><span class="label label-warning">اخطار</span></li> <li><span class="label label-importatnt">مهم است</span></li> <li><span class="label label-info">اطلاعات</span></li> <li><span class="label label-inverse">رنگ معکوس</span></li> </ul> </section> <section> <ul class="unstyled"> <li><span class="badge">پیش فرض</span></li> <li><span class="badge badge-success">موفقیت آمیز</span></li> <li><span class="badge badge-warning">اخطار</span></li> <li><span class="badge badge-importatnt">مهم است</span></li> <li><span class="badge badge-info">اطلاعات</span></li> <li><span class="badge badge-inverse">رنگ معکوس</span></li> </ul> </section> <div> <div class="alert"> <button class="close" data-dismiss="alert">×</button> نمایش اعلانات </div> <div class="alert alert-danger"> <button class="close" data-dismiss="alert">×</button> نمایش اعلانات </div> <div class="alert alert-info"> <button class="close" data-dismiss="alert">×</button> نمایش اعلانات </div> <div class="alert alert-success"> <button class="close" data-dismiss="alert">×</button> نمایش اعلانات </div> <div class="alert alert-error"> <button class="close" data-dismiss="alert">×</button> نمایش اعلانات </div> </div> </div> </div> </div>
<button onClick={this.handleRemove} value={id}>Remove</button>
handleRemove(event) { const id = event.target.value; // ... }
handleChange(param) { //ES-5 return function (event) { }; } handleChange = param => event => {//ES-6 }; <input type="text" onChange={this.handleChange(someParam)} />
class App extends Component { oneParameter = a => e => { alert(a); }; twoParameter = (a, b) => e => { alert(a + b); }; render() { return ( <div> <button onClick={this.oneParameter(32)}> one parameter </button> <br /> <br /> <button onClick={this.twoParameter(10, 54)}> two parameter</button> </div> ); } }
@if (HotelRoomModel.HotelRoomImages.Count > 0) { var serial = 1; foreach (var roomImage in HotelRoomModel.HotelRoomImages) { <div class="col-md-2 mt-3"> <div class="room-image" style="background: url('@roomImage.RoomImageUrl') 50% 50%; "> <span class="room-image-title">@serial</span> </div> <button type="button" @onclick="()=>DeletePhoto(roomImage)" class="btn btn-outline-danger btn-block mt-4">Delete</button> </div> serial++; } }
@code { private const string UploadFolder = "Uploads"; private void DeletePhoto(HotelRoomImageDTO imageDto) { var imageFileName = imageDto.RoomImageUrl.Replace($"{UploadFolder}/", "", StringComparison.OrdinalIgnoreCase); if (HotelRoomModel.Id == 0 && Title == "Create") { FileUploadService.DeleteFile(imageFileName, WebHostEnvironment.WebRootPath, UploadFolder); HotelRoomModel.HotelRoomImages.Remove(imageDto); } } }
@code { private List<string> DeletedImageFileNames = new List<string>(); private void DeletePhoto(HotelRoomImageDTO imageDto) { var imageFileName = imageDto.RoomImageUrl.Replace($"{UploadFolder}/", "", StringComparison.OrdinalIgnoreCase); if (HotelRoomModel.Id == 0 && Title == "Create") { // ... } else { // Edit Mode DeletedImageFileNames.Add(imageFileName); HotelRoomModel.HotelRoomImages.Remove(imageDto); // Update UI } }
private async Task HandleHotelRoomUpsert() { // ... if (HotelRoomModel.Id != 0 && Title == "Update") { // Update Mode var updatedRoomDto = await HotelRoomService.UpdateHotelRoomAsync(HotelRoomModel.Id, HotelRoomModel); foreach(var imageFileName in DeletedImageFileNames) { FileUploadService.DeleteFile(imageFileName, WebHostEnvironment.WebRootPath, UploadFolder); } // await AddHotelRoomImageAsync(updatedRoomDto); await JsRuntime.ToastrSuccess($"The `{HotelRoomModel.Name}` updated successfully."); } else { // ... } } }
.spinner { border: 16px solid silver !important; border-top: 16px solid #337ab7 !important; border-radius: 50% !important; width: 80px !important; height: 80px !important; animation: spin 700ms linear infinite !important; top: 50% !important; left: 50% !important; transform: translate(-50%, -50%); position: absolute !important; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
@code { private bool IsImageUploadProcessStarted; private async Task HandleImageUpload(InputFileChangeEventArgs args) { try { IsImageUploadProcessStarted = true; // ... } finally { IsImageUploadProcessStarted = false; } } }
<InputFile OnChange="HandleImageUpload" multiple></InputFile> <div class="row"> @if (IsImageUploadProcessStarted) { <div class="col-md-12"> <span><i class="spinner"></i> Please wait.. Images are uploading...</span> </div> }
@using BlazorServer.App.Pages.Components
<Confirmation @ref="Confirmation1" OnCancel="OnCancelDeleteImageClicked" OnConfirm="@(()=>OnConfirmDeleteImageClicked(ImageToBeDeleted))"> <div> Do you want to delete @ImageToBeDeleted?.RoomImageUrl image? </div> </Confirmation>
private Confirmation Confirmation1; private HotelRoomImageDTO ImageToBeDeleted; private void OnCancelDeleteImageClicked() { // Confirmation1.Hide(); } private void DeletePhoto(HotelRoomImageDTO imageDto) { ImageToBeDeleted = imageDto; Confirmation1.Show(); } private void OnConfirmDeleteImageClicked(HotelRoomImageDTO imageDto) {
<td> <NavLink href="@($"hotel-room/edit/{room.Id}")" class="btn btn-primary">Edit</NavLink> <button class="btn btn-danger" @onclick="()=>HandleDeleteRoom(room)">Delete</button> </td>
<Confirmation @ref="Confirmation1" OnCancel="OnCancelDeleteRoomClicked" OnConfirm="OnConfirmDeleteRoomClicked"> <div> Do you want to delete @RoomToBeDeleted?.Name? </div> </Confirmation>
@code { private List<HotelRoomDTO> HotelRooms = new List<HotelRoomDTO>(); private HotelRoomDTO RoomToBeDeleted; private Confirmation Confirmation1; private void OnCancelDeleteRoomClicked() { // Confirmation1.Hide(); } private void HandleDeleteRoom(HotelRoomDTO roomDto) { RoomToBeDeleted = roomDto; Confirmation1.Show(); } private async Task OnConfirmDeleteRoomClicked() { if(RoomToBeDeleted is null) { return; } await HotelRoomService.DeleteHotelRoomAsync(RoomToBeDeleted.Id); HotelRooms.Remove(RoomToBeDeleted); // Update UI }
An exception occurred while iterating over the results of a query for context type 'BlazorServer.DataAccess.ApplicationDbContext'. System.InvalidOperationException: A second operation was started on this context before a previous operation completed. This is usually caused by different threads concurrently using the same instance of DbContext. For more information on how to avoid threading issues with DbContext, see https://go.microsoft.com/fwlink/?linkid=2097913.
import Pagination from "./common/pagination";
<Pagination itemsCount={this.state.movies.length} pageSize={this.state.pageSize} onPageChange={this.handlePageChange} />
state = { movies: getMovies(), pageSize: 4 };
handlePageChange = page => { console.log("handlePageChange", page); };
import React, { Component } from "react"; class Pagination extends Component { render() { return ( <nav> <ul className="pagination"> <li className="page-item"> <a className="page-link">1</a> </li> </ul> </nav> ); } } export default Pagination;
class Pagination extends Component { // ... getPageNumbersArray() { const { itemsCount, pageSize } = this.props; const pagesCount = Math.ceil(itemsCount / pageSize); if (pagesCount === 1) { return null; } const pages = new Array(); for (let i = 1; i <= pagesCount; i++) { pages.push(i); } return pages; } }
class Pagination extends Component { render() { const pages = this.getPageNumbersArray(); if (!pages) { return null; } return ( <nav> <ul className="pagination"> {pages.map(page => ( <li key={page} className="page-item"> <a className="page-link">{page}</a> </li> ))} </ul> </nav> ); }
<a onClick={() => this.props.onPageChange(page)} className="page-link" style={{ cursor: "pointer" }} > {page} </a>
<Pagination itemsCount={this.state.movies.length} pageSize={this.state.pageSize} onPageChange={this.handlePageChange} currentPage={this.state.currentPage} />
class Movies extends Component { state = { movies: getMovies(), pageSize: 4, currentPage: 1 };
handlePageChange = page => { console.log("handlePageChange", page); this.setState({currentPage: page}); };
<li key={page} className={ page === this.props.currentPage ? "page-item active" : "page-item" } >
paginate() { const first = (this.state.currentPage - 1) * this.state.pageSize; const last = first + this.state.pageSize; return this.state.movies.slice(first, last); }
render() { const { length: count } = this.state.movies; if (count === 0) return <p>There are no movies in the database.</p>; const movies = this.paginate();
> npm i -g typescript eslint tslint eslint-plugin-react-hooks jshint babel-eslint eslint-plugin-react eslint-plugin-mocha
> npm i prop-types --save
C:/Users/{username}/AppData/Local/Microsoft/TypeScript/3.6/node_modules/@types/prop-types/index
import PropTypes from "prop-types";
Pagination.propTypes = { itemsCount: PropTypes.number.isRequired, pageSize: PropTypes.number.isRequired, currentPage: PropTypes.number.isRequired, onPageChange: PropTypes.func.isRequired }; export default Pagination;
البته این خطا فقط در حالت development مشاهده میشود و در حالت توزیع برنامه، خیر.
export class Book { constructor( public id, public title:string, public pages:Array ){} } return this._http.get('getBook/1') .map(function(res){ var data = res.json(); return new Book(data.id, data.title, data.pages); })
شاید سادهترین تعریف برای Saltarelle این باشد که «کامپایلریست که کدهای C# را به جاوا اسکریپت تبدیل میکند». محاسن زیادی را میتوان برای اینگونه کامپایلرها نام برد؛ مخصوصا در پروژههای سازمانی که نگهداری از کدهای جاوا اسکریپت بسیار سخت و گاهی خارج از توان است و این شاید مهمترین عامل ظهور ابزارهای جدید از قبیل Typescript باشد.
در هر صورت اگر حوصله و وقت کافی برای تجهیز تیم نرم افزاری، به دانش یک زبان جدید مانند Typescript نباشد، استفاده از توان و دانش تیم تولید، از زبان C# سادهترین راه حل است و اگر ابزاری مطمئن برای استفاده از حداکثر قدرت JavaScript همراه با امکانات نگهداری و توسعه کدها وجود داشته باشد، بی شک Saltarelle یکی از بهترینهای آنهاست.
قبلا کامپایلر هایی از این دست مانند Script# وجود داشتند، اما فاقد همه امکانات C# بوده وعملا قدرت کامل C# در کد نویسی وجود نداشت. اما با توجه به ادعای توسعه دهندگان این کامپایلر سورس باز در استفادهی حداکثری از کلیه ویژگیهای C# 5 و با وجود Library های متعدد میتوان Saltarelle را عملا یک کامپایلر موفق در این زمینه دانست.
برای استفاده از Saltarelle در یک برنامه وب ساده باید یک پروژه Console Application به Solution اضافه کرد و پکیج Saltarelle.Compiler را از nuget نصب نمایید. بعد از نصب این پکیج، کلیه Reference ها از پروژه حدف میشوند و هر بار Build توسط کامپایلر Saltarelle انجام میشود. البته با اولین Build، مقداری Error را خواهید دید که برای از بین بردنشان نیاز است پکیج Saltarelle.Runtime را نیز در این پروژه نصب نمایید:
PM> Install-Package Saltarelle.Compiler PM> Install-Package Saltarelle.Runtime
در صورتیکه کماکان Build نهایی با Error همرا بود، یکبار این پروژه را Unload و سپس مجددا Load نمایید
UI یک پروژه وب MVC است و Client یک Console Application که پکیجهای مورد نیاز Saltarelle روی آن نصب شده است.
در صورتیکه پروژه را Build نماییم و نگاهی به پوشهی Debug بیاندازیم، یک فایل JavaScript همنام پروژه وجود دارد:
برای اینکه بعد از هر بار Build ، فایل اسکریپت به پوشهی مربوطه در پروژه UI منتقل شود کافیست کد زیر را در Post Build پروژه Client بنویسیم:
copy "$(TargetDir)$(TargetName).js" "$(SolutionDir)SalratelleSample.UI\Scripts"
اکنون پس از هر بار Build ، فایل اسکریپت مورد نظر در پوشهی Scripts پروژه UI آپدیت میشود:
در ادامه کافیست فایل اسکریپت را به layout اضافه کنیم.
<script src="~/Scripts/SaltarelleSample.Client.js"></script>
در پوشهی Saltarelle.Runtime در پکیجهای نصب شده، یک فایل اسکریپت به نام mscorlib.min.js نیز وجود دارد که حاوی اسکریپتهای مورد نیاز Saltarelle در هنگام اجراست. آن را به پوشه اسکریپتهای پروژه UI کپی نمایید و سپس به Layout اضافه کنید.
<script src="~/Scripts/mscorlib.min.js"></script> <script src="~/Scripts/SaltarelleSample.Client.js"></script>
حال نوبت به اضافه نمودن libraryهای مورد نیازمان است. برای دسترسی به آبجکت هایی از قبیل document, window, element و غیره در جاوااسکریپت میتوان پکیج Saltarelle.Web را در پروژهی Client نصب نمود و برای دسترسی به اشیاء و فرمانهای jQuery، پکیج Salratelle.jQuery را نصب نمایید.
> Install-Package Saltarelle.Web > Install-Package Saltarelle.jQuery
به این libraryها imported library میگویند. در واقع، در زمان کامپایل، برای این libraryها فایل اسکریپتی تولید نمیشود و فقط آبجکتهای #C هستند که که هنگام کامپایل تبدیل به کدهای ساده اسکریپت میشوند که اگر اسکریپت مربوط به آنها به صفحه اضافه نشده باشد، اجرای اسکریپت با خطا مواجه میشود.
به طور سادهتر وقتی از jQuery library استفاده میکنید هیچ فایل اسکریپت اضافهای تولید نمیشود، اما باید اسکریپت jQuery به صفحه شما اضافه شده باشد.
<script src="~/Scripts/jquery-1.10.2.min.js"></script>
مثال ما یک اپلیکیشن ساده برای خواندن فیدهای همین سایت است. ابتدا کدهای سمت سرور را در پروژه UI می نویسیم.
کلاسهای مورد نیاز ما برای این فید ریدر:
public class Feed { public string FeedId { get; set; } public string Title { get; set; } public string Address { get; set; } } public class Item { public string Title { get; set; } public string Link { get; set; } public string Description { get; set; } }
و یک کلاس برای مدیریت منطق برنامه
public class SiteManager { private static List<Feed> _feeds; public static List<Feed> Feeds { get { if (_feeds == null) _feeds = CreateSites(); return _feeds; } } private static List<Feed> CreateSites() { return new List<Feed>() { new Feed(){ FeedId = "1", Title = "آخرین تغییرات سایت", Address = "https://www.dntips.ir/rss.xml" }, new Feed(){ FeedId = "2", Title = "مطالب سایت", Address = "https://www.dntips.ir/feeds/posts" }, new Feed(){ FeedId = "3", Title = "نظرات سایت", Address = "https://www.dntips.ir/feeds/comments" }, new Feed(){ FeedId = "4", Title = "خلاصه اشتراک ها", Address = "https://www.dntips.ir/feed/news" }, }; } public static IEnumerable<Item> GetNews(string id) { XDocument feedXML = XDocument.Load(Feeds.Find(s=> s.FeedId == id).Address); var feeds = from feed in feedXML.Descendants("item") select new Item { Title = feed.Element("title").Value, Link = feed.Element("link").Value, Description = feed.Element("description").Value }; return feeds; } }
کلاس SiteManager فقط یک لیست از فیدها دارد و متدی که با گرفتن شناسهی فید ، یک لیست از آیتمهای موجود در آن فید ایجاد میکند.
حال دو ApiController برای دریافت دادهها ایجاد میکنیم
public class FeedController : ApiController { // GET api/<controller> public IEnumerable<Feed> Get() { return SiteManager.Feeds; } } public class ItemsController : ApiController { // GET api/<controller>/5 public IEnumerable<Item> Get(string id) { return SiteManager.GetNews(id); } }
در View پیشفرض که Index از کنترلر Home است، یک Html ساده برای
فرم صفحه اضافه میکنیم
<div> <div> <h2>Feeds</h2> <ul id="Feeds"> </ul> </div> <div> <h2>Items</h2> <p id="FeedItems"> </p> </div> </div>
در المنت Feeds لیست فیدها را قرار میدهیم و در FeedItems آیتمهای مربوط به هر فید. حال به سراغ کدهای سمت کلاینت میرویم و به جای جاوا اسکریپت از Saltarelle استفاده میکنیم.
کلاس Program را از پروژه Client باز میکنیم و متد Main را به شکل زیر تغییر میدهیم:
static void Main() { jQuery.OnDocumentReady(() => { FillFeeds(); }); }
بعد از کامپایل شدن، کد #C شارپ بالا به صورت زیر در میآید:
$SaltarelleSample_Client_$Program.$main = function() { $(function() { $SaltarelleSample_Client_$Program.$fillFeeds(); }); }; $SaltarelleSample_Client_$Program.$main();
و این همان متد معروف jQuery است که Saltarelle.jQuery برایمان ایجاد کرده است.
متد FillFeeds را به شکل زیر پیاده سازی میکنیم
private static void FillFeeds() { jQuery.Ajax(new jQueryAjaxOptions() { Url = "/api/feed", Type = "GET", Success = (d,t,r) => { // Fill var ul = jQuery.Select("#Feeds"); jQuery.Each((List<Feed>)d, (idx,i) => { var li = jQuery.Select("<li>").Text(i.Title).CSS("cursor", "pointer"); li.Click(eve => { FillData(i.FeedId); }); ul.Append(li); }); } }); }
آبجکت jQuery، متدی به نام Ajax دارد که یک شی از کلاس jQueryAjaxOptions را به عنوان پارامتر میپذیرد. این کلاس کلیه خصوصیات متد Ajax در jQuery را پیاده سازی میکند. نکته شیرین آن توانایی نوشتن lambda برای Delegate هاست.
خاصیت Success یک Delegate است که 3 پارامتر ورودی را میپذیرد.
public delegate void AjaxRequestCallback(object data, string textStatus, jQueryXmlHttpRequest request);
data همان مقداریست که api باز میگرداند که یک لیست از Feed هاست. برای زیبایی کار، من یک کلاس Feed در پروژه Client اضافه میکنم که خصوصیاتی مشترک با کلاس اصلی سمت سرور دارد و مقدار برگشی Ajax را به آن تبدیل میکنم.
کلاس Feed و Item
[PreserveMemberCase()] public class Feed { //[ScriptName("FeedId")] public string FeedId; //[ScriptName("Title")] public string Title; //[ScriptName("Address")] public string Address; } [PreserveMemberCase()] public class Item { // [ScriptName("Title")] public string Title; // [ScriptName("Link")] public string Link; // [ScriptName("Description")] public string Description; }
jQuery.Each((List<Feed>)d, (idx,i) => { var li = jQuery.Select("<li>").Text(i.Title).CSS("cursor", "pointer"); li.Click(eve => { FillData(i.FeedId); }); ul.Append(li); });
به ازای هر آیتمی که در شیء بازگشتی وجود دارد، با استفاد از متد each در jQuery یک li ایجاد میکنیم. همان طور که میبینید کلیه خواص، به شکل Fluent قابل اضافه شدن میباشد. سپس برای li یک رویداد کلیک که در صورت وقوع، متد FillData را با شناسه فید کلیک شده فراخوانی میکند و در آخر li را به المنت ul اضافه میکنیم.
برای هر کلیک هم مانند مثال بالا api را با شناسهی فید مربوطه فراخوانی کرده و به ازای هر آیتم، یک سطر ایجاد میکنیم.
private static void FillData(string p) { jQuery.Ajax(new jQueryAjaxOptions() { Url = "/api/items/" + p, Type = "GET", Success = (d, t, r) => { var content = jQuery.Select("#FeedItems"); content.Html(""); foreach (var item in (List<Item>)d) { var row = jQuery.Select("<div>").AddClass("row").CSS("direction", "rtl"); var link = jQuery.Select("<a>").Attribute("href", item.Link).Text(item.Title); row.Append(link); content.Append(row); } } }); }
در این مثال ما از Saltarelle.jQuery برای استفاده از jQuery.js استفاده نمودیم. libraryهای متعددی برای Saltarelle از قبیل linq,angular,knockout,jQueryUI,nodeJs ایجاد شده و همچنین قابلیتهای زیادی برای نوشتن imported libraryهای سفارشی نیز وجود دارد.
مطمئنا استفاده از چنین کامپایلرهایی راه حلی سریع برای رهایی از مشکلات متعدد کد نویسی با جاوا اسکریپت در نرم افزارهای بزرگ مقیاس است. اما مقایسه آنها با ابزارهایی از قبیل typescript احتیاج به زمان و تجربه کافی در این زمینه دارد.