قابلیتی که در ادامه از آن برای «قالب پذیر ساختن گرید» استفاده خواهیم کرد، همان نکتهی «امکان تعویض پویای قالبهای یک دربرگیرنده» است که در مطلب «امکان تعریف قالبها در Angular با دایرکتیو ng-template» به آن پرداختیم.
تعریف قالبهای نمایش و ویرایش اطلاعات یک ردیف در گرید طراحی شده
پس از آشنایی با دایرکتیوهای تعریف و کار با قالبها در Angular، اکنون تبدیل بدنهی ثابت جدول، به دو قالب نمایش و ویرایش، سادهاست.
در قسمت دوم این سری، کار رندر بدنهی اصلی گرید توسط همین چند سطر، در قالب آن مدیریت میشود:
<tbody> <tr *ngFor="let item of queryResult.items; let i = index"> <td class="text-center">{{ itemsPerPage * (currentPage - 1) + i + 1 }}</td> <td class="text-center">{{ item.productId }}</td> <td class="text-center">{{ item.productName }}</td> <td class="text-center">{{ item.price | number:'.0' }}</td> <td class="text-center"> <input id="item-{{ item.productId }}" type="checkbox" [checked]="item.isAvailable" disabled="disabled" /> </td> </tr> </tbody> </table>
در ادامه قسمت داخلی ngFor را تبدیل به یک ng-container میکنیم تا قالب پذیر شود:
<tbody> <tr *ngFor="let item of queryResult.items; let i = index"> <ng-container [ngTemplateOutlet]="loadTemplate(item)" [ngOutletContext]="{ $implicit: item, idx: i }"></ng-container> </tr> </tbody>
در اینجا ngTemplateOutlet این امکان را میدهد تا بتوان توسط کدهای برنامه، قالب هر ردیف را مشخص کرد. متد loadTemplate در کدهای کامپوننت متناظر فراخوانی شده و بر اساس وضعیت هر ردیف، یکی از دو قالب ذیل را بازگشت میدهد:
الف) قالب نمایش معمولی و فقط خواندنی رکوردها
<!--The Html Template for Read-Only Rows--> <ng-template #readOnlyTemplate let-item let-i="idx"> <td class="text-center">{{ itemsPerPage * (currentPage - 1) + i + 1 }}</td> <td class="text-center">{{ item.productId }}</td> <td class="text-center">{{ item.productName }}</td> <td class="text-center">{{ item.price | number:'.0' }}</td> <td class="text-center"> <input id="item-{{ item.productId }}" type="checkbox" [checked]="item.isAvailable" disabled="disabled" /> </td> <td> <input type="button" value="Edit" class="btn btn-default btn-xs" (click)="editItem(item)" /> </td> <td> <input type="button" value="Delete" (click)="deleteItem(item)" class="btn btn-danger btn-xs" /> </td> </ng-template>
این قالب در کدهای کامپوننت آن به صورت ذیل قابل دسترسی و انتخاب شدهاست:
@ViewChild("readOnlyTemplate") readOnlyTemplate: TemplateRef<any>;
ب) قالب ویرایش اطلاعات هر ردیف که از آن برای افزودن یک ردیف جدید هم میتوان استفاده کرد
شبیه به همان کاری را که برای نمایش ردیفهای فقط خواندنی انجام دادیم، در مورد قالب ویرایش هر ردیف نیز تکرار میکنیم. در اینجا فقط امکان ویرایش نام محصول، قیمت آن و موجود بودن آنرا توسط یکسری input box مهیا کردهایم:
<!--The Html Template for Editable Rows--> <ng-template #editTemplate let-item let-i="idx"> <td class="text-center">{{ itemsPerPage * (currentPage - 1) + i + 1 }}</td> <td class="text-center">{{ item.productId }}</td> <td class="text-center"> <input type="text" [(ngModel)]="selectedItem.productName" class="form-control" /> </td> <td class="text-center"> <input type="text" [(ngModel)]="selectedItem.price" class="form-control" /> </td> <td class="text-center"> <input id="item-{{ item.productId }}" type="checkbox" [checked]="item.isAvailable" [(ngModel)]="selectedItem.isAvailable" /> </td> <td> <input type="button" value="Save" (click)="saveItem()" class="btn btn-success btn-xs" /> </td> <td> <input type="button" value="Cancel" (click)="cancel()" class="btn btn-warning btn-xs" /> </td> </ng-template>
@ViewChild("editTemplate") editTemplate: TemplateRef<any>;
تا اینجا کار تعریف قالبهای این گرید به پایان میرسد. در ادامه کدهای افزودن، ثبت، ویرایش، حذف و لغو را پیاده سازی خواهیم کرد:
خواص عمومی مورد نیاز جهت کار با قالبها و ویرایشهای درون ردیفی
@ViewChild("readOnlyTemplate") readOnlyTemplate: TemplateRef<any>; @ViewChild("editTemplate") editTemplate: TemplateRef<any>; selectedItem: AppProduct; isNewRecord: boolean;
به علاوه اگر به قالب editTemplate دقت کنید، مقدار ویرایش شده به [(ngModel)]="selectedItem.productName" انتساب داده میشود. به همین جهت شیء selectedItem نیز تعریف شدهاست.
همچنین نیاز است بدانیم اکنون در حال ویرایش یک ردیف هستیم یا این ردیف، کاملا ردیف جدیدی است. به همین جهت پرچم isNewRecord نیز تعریف شدهاست.
فعالسازی قالب ویرایش هر ردیف
در انتهای هر ردیف، دکمهی ویرایش نیز قرار دارد که به (click) آن، رخداد editItem متصل است:
editItem(item: AppProduct) { this.selectedItem = item; }
loadTemplate(item: AppProduct) { if (this.selectedItem && this.selectedItem.productId === item.productId) { return this.editTemplate; } else { return this.readOnlyTemplate; } }
مدیریت افزودن یک ردیف جدید
دکمهی افزودن یک ردیف جدید به صورت ذیل به قالب اضافه شدهاست:
<div class="panel"> <input type="button" value="Add new product" class="btn btn-primary" (click)="addItem()" /> </div>
addItem() { this.selectedItem = new AppProduct(0, "", 0, false); this.isNewRecord = true; this.queryResult.items.push(this.selectedItem); this.queryResult.totalItems++; }
مدیریت لغو ویرایش هر ردیف
برای اینکه ویرایش هر ردیف را لغو کنیم و قالب آنرا به حالت فقط خواندنی بازگشت دهیم، فقط کافی است selectedItem را به نال تنظیم کنیم:
cancel() { this.selectedItem = null; }
مدیریت حذف هر ردیف
در اینجا با پیاده سازی متد رخدادگردان deleteItem و ارسال id هر ردیف به سرور، کار حذف هر ردیف را انجام خواهیم داد:
deleteItem(item: AppProduct) { this.productsService .deleteAppProduct(item.productId) .subscribe((resp: Response) => { this.getPagedProductsList(); }); }
مدیریت ثبت و یا به روز رسانی هر ردیف
آخرین عملیاتی که باید مدیریت شود، بررسی پرچم isNewRecord است. اگر true بود، کار افزودن یک ردیف جدید صورت گرفته و سپس این پرچم false میشود. اگر false بود، به معنای درخواست به روز رسانی ردیفی مشخص است. در پایان هر دو عملیات selectedItem را نیز true میکنیم و این پایان عملیات باید داخل قسمت دریافت پاسخ از سرور مدیریت شود و نه پس از فراخوانی این متدها؛ چون متدهای subscribe غیرهمزمان بوده و ردیفهای پس از آنها بلافاصله اجرا میشوند.
saveItem() { if (this.isNewRecord) { this.productsService .addAppProduct(this.selectedItem) .subscribe((resp: AppProduct) => { this.selectedItem.productId = resp.productId; this.isNewRecord = false; this.selectedItem = null; }); } else { this.productsService .updateAppProduct(this.selectedItem.productId, this.selectedItem) .subscribe((resp: AppProduct) => { this.selectedItem = null; }); } }
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید.