استفاده از کامپوننت AutoComplete کتابخانهی Angular Material
کتابخانهی Angular Material به همراه یک کامپوننت Auto Complete نیز هست. در اینجا قصد داریم آنرا در یک صفحهی دیالوگ جدید نمایش دهیم و با انتخاب کاربری از لیست توصیههای آن و کلیک بر روی دکمهی نمایش آن کاربر، جزئیات کاربر یافت شده را نمایش دهیم.
به همین جهت ابتدا کامپوننت جدید search-auto-complete را به صورت زیر به مجموعهی کامپوننتهای تعریف شده اضافه میکنیم:
ng g c contact-manager/components/search-auto-complete --no-spec
import { SearchAutoCompleteComponent } from "./components/search-auto-complete/search-auto-complete.component"; @NgModule({ entryComponents: [ SearchAutoCompleteComponent ] }) export class ContactManagerModule { }
در ادامه برای نمایش این کامپوننت به صورت popup، دکمهی جدید جستجو را به toolbar اضافه میکنیم:
برای این منظور به فایل toolbar\toolbar.component.html مراجعه کرده و دکمهی جستجو را پیش از دکمهی نمایش منو، قرار میدهیم:
<span fxFlex="1 1 auto"></span> <button mat-button (click)="openSearchDialog()"> <mat-icon>search</mat-icon> </button> <button mat-button [matMenuTriggerFor]="menu"> <mat-icon>more_vert</mat-icon> </button>
@Component() export class ToolbarComponent { constructor( private dialog: MatDialog, private router: Router) { } openSearchDialog() { const dialogRef = this.dialog.open(SearchAutoCompleteComponent, { width: "650px" }); dialogRef.afterClosed().subscribe((result: User) => { console.log("The SearchAutoComplete dialog was closed", result); if (result) { this.router.navigate(["/contactmanager", result.id]); } }); } }
کنترلر جستجوی سمت سرور و سرویس سمت کلاینت استفاده کنندهی از آن
در اینجا کنترلر و اکشن متدی را جهت جستجوی قسمتی از نام کاربران را مشاهده میکنید:
namespace MaterialAspNetCoreBackend.WebApp.Controllers { [Route("api/[controller]")] public class TypeaheadController : Controller { private readonly IUsersService _usersService; public TypeaheadController(IUsersService usersService) { _usersService = usersService ?? throw new ArgumentNullException(nameof(usersService)); } [HttpGet("[action]")] public async Task<IActionResult> SearchUsers(string term) { return Ok(await _usersService.SearchUsersAsync(term)); } } }
از این کنترلر به نحو ذیل در برنامهی Angular برای ارسال اطلاعات و انجام جستجو استفاده میشود:
import { HttpClient, HttpErrorResponse } from "@angular/common/http"; import { Injectable } from "@angular/core"; import { Observable, throwError } from "rxjs"; import { catchError, map } from "rxjs/operators"; import { User } from "../models/user"; @Injectable({ providedIn: "root" }) export class UserService { constructor(private http: HttpClient) { } searchUsers(term: string): Observable<User[]> { return this.http .get<User[]>(`/api/Typeahead/SearchUsers?term=${encodeURIComponent(term)}`) .pipe( map(response => response || []), catchError((error: HttpErrorResponse) => throwError(error)) ); } }
تکمیل کامپوننت جستجوی کاربران توسط یک AutoComplete
پس از این مقدمات که شامل تکمیل سرویسهای سمت سرور و کلاینت دریافت اطلاعات کاربران جستجو شده و نمایش صفحهی جستجو به صورت یک popup است، اکنون میخواهیم محتوای این popup را تکمیل کنیم. البته در اینجا فرض بر این است که مطلب «کنترل نرخ ورود اطلاعات در برنامههای Angular» را پیشتر مطالعه کردهاید و با جزئیات آن آشنایی دارید.
تکمیل قالب search-auto-complete.component.html
<h2 mat-dialog-title>Search</h2> <mat-dialog-content> <div fxLayout="column"> <mat-form-field class="example-full-width"> <input matInput placeholder="Choose a user" [matAutocomplete]="auto1" (input)="onSearchChange($event.target.value)"> </mat-form-field> <mat-autocomplete #auto1="matAutocomplete" [displayWith]="displayFn" (optionSelected)="onOptionSelected($event)"> <mat-option *ngIf="isLoading" class="is-loading"> <mat-spinner diameter="50"></mat-spinner> </mat-option> <ng-container *ngIf="!isLoading"> <mat-option *ngFor="let user of filteredUsers" [value]="user"> <span>{{ user.name }}</span> <small> | ID: {{user.id}}</small> </mat-option> </ng-container> </mat-autocomplete> </div> </mat-dialog-content> <mat-dialog-actions> <button mat-button color="primary" (click)="showUser()"> <mat-icon>search</mat-icon> Show User </button> <button mat-button color="primary" [mat-dialog-close]="true"> <mat-icon>cancel</mat-icon> Close </button> </mat-dialog-actions>
سپس نحوهی اتصال یک Input box معمولی را به کامپوننت mat-autocomplete مشاهده میکنید که شامل این موارد است:
- جعبه متنی که قرار است به یک mat-autocomplete متصل شود، توسط دایرکتیو matAutocomplete به template reference variable تعریف شدهی در آن autocomplete اشاره میکند. برای مثال در اینجا این متغیر auto1 است.
- برای انتقال دکمههای فشرده شدهی در input box به کامپوننت، از رخداد input استفاده شدهاست. این روش با هر دو نوع حالت مدیریت فرمهای Angular سازگاری دارد و کدهای آن یکی است.
در کامپوننت mat-autocomplete این تنظیمات صورت گرفتهاند:
- در لیست ظاهر شدهی توسط یک autocomplete، هر نوع ظاهری را میتوان طراحی کرد. برای مثال در اینجا نام و id کاربر نمایش داده میشوند. اما برای تعیین اینکه پس از انتخاب یک آیتم از لیست، چه گزینهای در input box ظاهر شود، از خاصیت displayWith که در اینجا به متد displayFn کامپوننت متصل شدهاست، کمک گرفته خواهد شد.
- از رخداد optionSelected برای دریافت آیتم انتخاب شده، در کدهای کامپوننت استفاده میشود.
- در آخر کار نمایش لیستی از کاربران توسط mat-optionها انجام میشود. در اینجا برای اینکه بتوان تاخیر دریافت اطلاعات از سرور را توسط یک mat-spinner نمایش داد، از خاصیت isLoading تعریف شدهی در کامپوننت استفاده خواهد شد.
تکمیل کامپوننت search-auto-complete.component.ts
کدهای کامل این کامپوننت را در ادامه مشاهده میکنید:
import { Component, OnDestroy, OnInit } from "@angular/core"; import { MatAutocompleteSelectedEvent, MatDialogRef } from "@angular/material"; import { Subject, Subscription } from "rxjs"; import { debounceTime, distinctUntilChanged, finalize, switchMap, tap } from "rxjs/operators"; import { User } from "../../models/user"; import { UserService } from "../../services/user.service"; @Component({ selector: "app-search-auto-complete", templateUrl: "./search-auto-complete.component.html", styleUrls: ["./search-auto-complete.component.css"] }) export class SearchAutoCompleteComponent implements OnInit, OnDestroy { private modelChanged: Subject<string> = new Subject<string>(); private dueTime = 300; private modelChangeSubscription: Subscription; private selectedUser: User = null; filteredUsers: User[] = []; isLoading = false; constructor( private userService: UserService, private dialogRef: MatDialogRef<SearchAutoCompleteComponent>) { } ngOnInit() { this.modelChangeSubscription = this.modelChanged .pipe( debounceTime(this.dueTime), distinctUntilChanged(), tap(() => this.isLoading = true), switchMap(inputValue => this.userService.searchUsers(inputValue).pipe( finalize(() => this.isLoading = false) ) ) ) .subscribe(users => { this.filteredUsers = users; }); } ngOnDestroy() { if (this.modelChangeSubscription) { this.modelChangeSubscription.unsubscribe(); } } onSearchChange(value: string) { this.modelChanged.next(value); } displayFn(user: User) { if (user) { return user.name; } } onOptionSelected(event: MatAutocompleteSelectedEvent) { console.log("Selected user", event.option.value); this.selectedUser = event.option.value as User; } showUser() { if (this.selectedUser) { this.dialogRef.close(this.selectedUser); } } }
- توسط متد displayFn، عبارتی که در نهایت پس از انتخاب از لیست نمایش داده شده در input box قرار میگیرد، مشخص خواهد شد.
- در متد onOptionSelected، میتوان به شیء انتخاب شدهی توسط کاربر از لیست mat-autocomplete دسترسی داشت.
- این شیء انتخاب شده را در متد showUser و توسط سرویس MatDialogRef به کامپوننت toolbar که در حال گوش فرادادن به رخداد بسته شدن کامپوننت جاری است، ارسال میکنیم. به این صورت است که کامپوننت toolbar میتواند کار هدایت به جزئیات این کاربر را انجام دهد.
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید.
برای اجرای آن:
الف) ابتدا به پوشهی src\MaterialAngularClient وارد شده و فایلهای restore.bat و ng-build-dev.bat را اجرا کنید.
ب) سپس به پوشهی src\MaterialAspNetCoreBackend\MaterialAspNetCoreBackend.WebApp وارد شده و فایلهای restore.bat و dotnet_run.bat را اجرا کنید.
اکنون برنامه در آدرس https://localhost:5001 قابل دسترسی است.
describe('myApp', function() { var scope; beforeEach(angular.mock.module('myApp')); beforeEach(angular.mock.inject(function($rootScope) { scope = $rootScope.$new(); }); it('...') });
describe('Remote tests', function() { var $httpBackend, $rootScope, myService; beforeEach(inject( function(_$httpBackend_, _$rootScope_, _myService_) { $httpBackend = _$httpBackend_; $rootScope = _$rootScope_; myService = _myService_; })); it('should make a request to the backend', function() { $httpBackend.expect('GET', '/v1/api/current_user') .respond(200, {userId: 123}); myService.getCurrentUser(); $httpBackend.flush(); }); });
»httpBackend$.expectGet
»httpBackend$.expectPost
»httpBackend$.expectDelete
»httpBackend$.expectJson
»httpBackend$.expectHead
»httpBackend$.expectPatch
Flush کردن سرویس httpBackend$ در پایان تست نیز برای همین مبحث async اجرا شدن سرویسهای http$backend است.
"devDependencies": { "angular-cli": "1.0.0-beta.28.3",
node -v npm -v
فایل پیشین angular-cli.json حذف و فایل جدید angular.json بجای آن معرفی شدهاست
یکی از مهمترین تغییرات CLI 6.0 نسبت به نگارشهای قبلی آن، پشتیبانی از چندین پروژه است و به همین منظور ساختار فایل تنظیمات آنرا به طور کامل تغییر دادهاند و اگر دستور ng new project1 را صادر کنید، دیگر از فایل پیشین angular-cli.json خبری نبوده و بجای آن فایل جدید angular.json قابل مشاهدهاست:
{ "$schema": "./node_modules/@angular/cli/lib/config/schema.json", "version": 1, "newProjectRoot": "projects", "projects": { "project1": {
مزیت مهم این قابلیت، امکان ایجاد libraries است که به صورت توکار از این نگارش پشتیبانی میشود و میتوان اجزایی مانند components ،directives ،pipes و services اشتراکی را در یک یا چندین کتابخانه قرار داد و سپس از آنها در پروژهی اصلی و یا چندین پروژهی متصل استفاده کرد.
نگارش 6 در پشت صحنه، پروژهی موفق ng-packagr را به مجموعهی CLI اضافه کردهاست و از این پس توسط خود CLI میتوان کتابخانههای استاندارد Angular را تولید کرد. این مورد، مزیت استاندارد سازی کتابخانههای npm حاصل را نیز به همراه دارد؛ مشکلی که گاهی از اوقات به علت عدم رعایت این ساختار، با بستههای فعلی npm مخصوص Angular وجود دارند. برای مثال بدون استفادهی از این ابزار، نیاز است مستندات 13 صفحهای ساخت کتابخانههای Angular را سطر به سطر پیاده سازی کنید که توسط CLI 6.0، به صورت خودکار ایجاد و مدیریت میشود.
بنابراین اکنون سؤال مهمی که مطرح میشود این است: آیا باید فایل angular-cli.json پیشین را به صورت دستی به این فایل جدید به روز رسانی کرد و چگونه؟
به روز رسانی تمام بستههای سراسری سیستم
در ادامه پیش از هر کاری نیاز است تمام بستههای سراسری npm ایی را که هر از چندگاهی به سیستم خود اضافه کردهاید، به روز رسانی کنید. برای مشاهدهی لیست موارد تاریخ مصرف گذشتهی آنها، دستور زیر را صادر کنید:
npm outdated -g --depth=0
npm update -g
به روز رسانی خودکار ساختار فایل angular-cli.json
این به روز رسانی توسط CLI 6.0 به صورت خودکار پشتیبانی میشود و شامل این مراحل است:
ابتدا نیاز است بستهی سراسری آنرا به روز رسانی کرد:
npm i -g @angular/cli
npm install --save-dev @angular/cli@latest
ng update @angular/cli ng update @angular/core ng update rxjs
DELETE .angular-cli.json CREATE angular.json (4273 bytes) UPDATE karma.conf.js (1008 bytes) UPDATE src/tsconfig.spec.json (322 bytes) UPDATE package.json (2076 bytes) UPDATE tslint.json (3217 bytes)
لیست سایر بستههایی را که میتوان توسط این دستور به روز رسانی کرد، با اجرا دستور ng update میتوانید مشاهده کنید. برای مثال اگر از Angular Material نیز استفاده میکنید، دستور به روز رسانی آن به صورت زیر است:
ng update @angular/material
مشکل! دستور ng update کار نمیکند!
اگر پروژهی شما صرفا مبتنی بر بستههای اصلی Angular باشد، مراحل یاد شدهی فوق را با موفقیت به پایان خواهید رساند. اما اگر از کتابخانههای ثالثی استفاده کرده باشید، منهای دستور «ng update @angular/cli» که کار تولید فایل جدید angular.json را انجام میدهد، مابقی با خطاهایی مانند «incompatible peer dependency» و یا «Invalid range:>=2.3.1 <3.0.0||>=4.0.0» متوقف میشوند.
در یک چنین حالتی نیاز است ابتدا وابستگیهای محلی پروژه را به روز کرد و سپس دستورات ng update را تکرار نمود. برای این منظور ابتدا بستهی npm-check-updates را نصب کنید:
npm install npm-check-updates -g
ncu -u npm install
اکنون تمام وابستگیهای محلی پروژهی شما به صورت خودکار به آخرین نگارش آنها به روز رسانی شدهاند و اینبار اگر دستورات ذیل را اجرا کنید، با خطاهای یاد شده مواجه نخواهید شد:
ng update @angular/core ng update rxjs
البته اگر در این حالت برنامه را کامپایل کنید، کار نخواهد کرد. علت اصلی آن به بهروز رسانی rxjs به نگارش 6 آن مرتبط میشود که در مطلب بعدی پیگیری خواهد شد و این نگارش شامل حذفیات بسیاری است در جهت کاهش حجم آن، یکپارچکی و یک دست شدن syntax آن و همچنین بهبود قابل ملاحظهی کارآیی آن. البته پیشنیاز الزامی آن آشنایی با pipe-able operators است. علت دیگر کامپایل نشدن برنامه هم میتواند عدم استفاده از HttpClient نگارش 4.3 به بعد باشد.
خاموش کردن اخطار «TypeScript version mismatch»
اگر نگارش TypeScript نصب شدهی در سیستم به صورت سراسری، با نگارش محلی پروژهی شما یکی نباشد، اخطار «TypeScript version mismatch» را دریافت میکنید. روش خاموش کردن آن در CLI جدید با اجرای دستور زیر است:
ng config cli.warnings.typescriptMismatch false
{ "devDependencies": { "typescript": "~2.7.2" } }
تغییرات ng build در نگارش 6
در نگارش 6، مفهوم پیشین environments به configurations تغییر یافتهاست و اینبار در فایل جدید angular.json تنظیم میشوند:
{ "$schema": "./node_modules/@angular/cli/lib/config/schema.json", "version": 1, "newProjectRoot": "projects", "projects": { "angular-template-driven-forms-lab": { "configurations": { "production": { "optimization": true, "outputHashing": "all", "sourceMap": false, "extractCss": true, "namedChunks": false, "aot": true, "extractLicenses": true, "vendorChunk": false, "buildOptimizer": true, "fileReplacements": [ { "replace": "src/environments/environment.ts", "with": "src/environments/environment.prod.ts" } ] } } },
ng build --env staging
ng build --configuration staging
تغییرات نامهای نهایی تولیدی
در CLI 6.0، نامهای نهایی تولیدی دیگر به همراه bundle و یا chunk نیستند. برای مثال دستور ng build یک چنین خروجی را تولید میکند:
>ng build --watch Date: 2018-05-05T09:10:50.158Z Hash: a43eab94ff01539b8592 Time: 31733ms chunk {main} main.js, main.js.map (main) 9.38 kB [initial] [rendered] chunk {polyfills} polyfills.js, polyfills.js.map (polyfills) 226 kB [initial] [rendered] chunk {runtime} runtime.js, runtime.js.map (runtime) 5.4 kB [entry] [rendered] chunk {styles} styles.js, styles.js.map (styles) 15.6 kB [initial] [rendered] chunk {vendor} vendor.js, vendor.js.map (vendor) 3.05 MB [initial] [rendered]
و هشهای حالت prod به صورت زیر تولید و به نام فایل اضافه میشوند:
>ng build --prod --watch Date: 2018-05-05T09:17:01.803Z Hash: f25fd6788a4969c52b70 Time: 73279ms chunk {0} runtime.6afe30102d8fe7337431.js (runtime) 1.05 kB [entry] [rendered] chunk {1} styles.34c57ab7888ec1573f9c.css (styles) 0 bytes [initial] [rendered] chunk {2} polyfills.6c08419970f9e4781b69.js (polyfills) 59.4 kB [initial] [rendered]
ساده شدن افزودن وابستگیهای ثالث به پروژههای CLI
برای نصب یک کتابخانهی ثالث، پیشتر میبایستی ابتدا بستهی npm آن جداگانه نصب و سپس فایل config برنامه، جهت معرفی مداخل آن، ویرایش میشد. اکنون دستور جدید ng add تمام این مراحل را به صورت خودکار انجام میدهد:
ng add @angular/material
آیا به یادگیری یا ادامهی استفاده از AngularJS خواهید پرداخت؟
در نگاه اول شاید برای توسعه دهندگان مبتدی یک سری مباحث گیج کننده باشن و خیلی از قابلیتها هم جادویی و جذاب. اما حقیقت امر این است که code base این فریم ورک مشکلات شگفت آوری داره. چند ساعت وقت گذاشتن روی اینترنت کافی هست تا از تمام جنبهها فریم ورکهای مطرح رو بررسی کرد و متوجه شد که Angular چقدر مشکلات اساسی داره. بصورت تیتر وار چند مورد رو لیست میکنم:
- Dynamic Scoping, and scope inheritance
- Two-way data binding with $watchers
- The $digest cycle
- No DOM manipulation capabilities
- Finite Routing, unless you use a 3rd party like ui-router
- app logic and structure expressed in HTML
- No server-side rendering (mostly for speed boost and SEO)
- string-based Dependency Injection
- Ill-Conceived architecture (obsolete constructor functions etc)
- Debugging issues
- Re-defining well established terminology
- Syntactic Sugar
- Execution Contexts
- Unnecessary Complications
- Incompatible with 3rd party libraries, like jQuery etc.
- Sparse documentation with literally no real-world examples
و مواردی از این دست. شاید برای پروژههای کوچک این فریم ورک مناسب باشه اما قطعا برای پروژههای بزرگی که قرار است برای مدتی طولانی توسعه داده بشن و نگهداری بشن اصلا انتخاب درستی نیست. حتی اگر پروژههای بزرگی هم با موفقیت توسط این فریم اجرا شده باشه باز هم ماهیت مساله تغییر نمیکنه.
در حال حاظر بین فریم ورکهای دیگه بهترین انتخاب Ember هست که بسیاری از مشکلات ذکر شده رو نداره و ساختار و معماری قوی و خوبی هم داره. Backbone و Durandal هم فریم ورکهای قوی ای هستند ولی تفاوتهای نسبتا زیادی با Ember دارن.
حائز اهمیت این که، اپلیکیشنهای SPA جوان هستند و فعلا همه جای دنیا در حال آزمایش و بررسی این هستند که چطور میشه چنین پروژه هایی رو اجرا کرد و کدام راه بهترین راه هست، بنابراین تا به استانداردهای ثابتی برسیم راه طولانی ای در پیش داریم. از طرفی بزودی استاندارد جدید جاوا اسکریپت (ECMA6) منتشر میشه، که با انتشارش فریم ورک هایی مثل Ember و Angular رو کاملا به هم خواهد ریخت. مثلا در نسخه جدید آبجکتهای Observable خواهیم داشت. بنابراین متدهای Angular و Ember برای تشخیص تغییرات به کلی بلا استفاده خواهند بود و بازنویسیهای اساسی لازم میشود.
Last week, at ng-conf, the Angular team at Google provided the web developer world with an update on the state of Angular 2. They were joined on stage by a member of the TypeScript team, Jonathan Turner, to also announce that Angular 2 will be built using TypeScript. Jonathan then demoed a preview of the upcoming TypeScript 1.5 release via an Angular 2 sample application.
اسکریپتهای خود را یکی کنید
@Html.AntiForgeryToken()
//Post in Ajax services.AddAntiforgery(o => o.HeaderName = "XSRF-TOKEN");
function initDataTables() { table.destroy(); table = $("#tblJs").DataTable({ processing: true, serverSide: true, filter: true, ajax: { url: '@Url.Page("yourPage","yourHandler")', beforeSend: function (xhr) { xhr.setRequestHeader("XSRF-TOKEN", $('input:hidden[name="__RequestVerificationToken"]').val()); }, type: "POST", datatype: "json" }, language: { url: "/Persian.json" }, responsive: true, select: true, columns: scheme, select: true, }); }
processing: true, serverSide: true, filter: true, ajax: { url: '@Url.Page("yourPage","yourHandler ")', beforeSend: function (xhr) { xhr.setRequestHeader("XSRF-TOKEN", $('input:hidden[name="__RequestVerificationToken"]').val()); }, type: "POST", datatype: "json" },
public class FiltersFromRequestDataTable { public string length { get; set; } public string start { get; set; } public string sortColumn { get; set; } public string sortColumnDirection { get; set; } public string sortColumnIndex { get; set; } public string draw { get; set; } public string searchValue { get; set; } public int pageSize { get; set; } public int skip { get; set; } }
public static void GetDataFromRequest(this HttpRequest Request, out FiltersFromRequestDataTable filtersFromRequest) { //TODO: Make Strings Safe String filtersFromRequest = new(); filtersFromRequest.draw = Request.Form["draw"].FirstOrDefault(); filtersFromRequest.start = Request.Form["start"].FirstOrDefault(); filtersFromRequest.length = Request.Form["length"].FirstOrDefault(); filtersFromRequest.sortColumn = Request.Form["columns[" + Request.Form["order[0][column]"].FirstOrDefault() + "][name]"].FirstOrDefault(); filtersFromRequest.sortColumnDirection = Request.Form["order[0][dir]"].FirstOrDefault(); filtersFromRequest.searchValue = Request.Form["search[value]"].FirstOrDefault(); filtersFromRequest.pageSize = filtersFromRequest.length != null ? Convert.ToInt32(filtersFromRequest.length) : 0; filtersFromRequest.skip = filtersFromRequest.start != null ? Convert.ToInt32(filtersFromRequest.start) : 0; filtersFromRequest.sortColumnIndex = Request.Form["order[0][column]"].FirstOrDefault(); filtersFromRequest.searchValue = filtersFromRequest.searchValue?.ToLower(); }
Request.GetDataFromRequest(out FiltersFromRequestDataTable filtersFromRequest);
public static PaginationDataTableResult<T> ToDataTableJs<T>(this IEnumerable<T> source, FiltersFromRequestDataTable filtersFromRequest) { int recordsTotal = source.Count(); CofingPaging(ref filtersFromRequest, recordsTotal); var result = new PaginationDataTableResult<T>() { draw = filtersFromRequest.draw, recordsFiltered = recordsTotal, recordsTotal = recordsTotal, data = source.OrderByIndex(filtersFromRequest).Skip(filtersFromRequest.skip).Take(filtersFromRequest.pageSize).ToList() }; return result; } private static void CofingPaging(ref FiltersFromRequestDataTable filtersFromRequest, int recordsTotal) { if (filtersFromRequest.pageSize == -1) { filtersFromRequest.pageSize = recordsTotal; filtersFromRequest.skip = 0; } }
private static IEnumerable<T> OrderByIndex<T>(this IEnumerable<T> source, FiltersFromRequestDataTable filtersFromRequest) { var props = typeof(T).GetProperties(); string propertyName = ""; for (int i = 0; i < props.Length; i++) { if (i.ToString() == filtersFromRequest.sortColumnIndex) propertyName = props[i].Name; } System.Reflection.PropertyInfo propByName = typeof(T).GetProperty(propertyName); if (propByName is not null) { if (filtersFromRequest.sortColumnDirection == "desc") source = source.OrderByDescending(x => propByName.GetValue(x, null)); else source = source.OrderBy(x => propByName.GetValue(x, null)); } return source; }
var result = query.ToDataTableJs(filtersFromRequest); return new JsonResult(result);
public static IEnumerable<TSource> WhereSearchValue<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate) { return source.Where(predicate); } public static bool ContainsSearchValue(this string source, string toCheck) { return source != null && toCheck != null && source.IndexOf(toCheck, StringComparison.OrdinalIgnoreCase) >= 0; }
if (!string.IsNullOrEmpty(filtersFromRequest.searchValue)) query = query.WhereSearchValue(x => x.title.ContainsSearchValue(filtersFromRequest.searchValue) || x.id.ToString().ContainsSearchValue(filtersFromRequest.searchValue)).AsQueryable();
public JsonResult OnPostList() { Request.GetDataFromRequest(out FiltersFromRequestDataTable filtersFromRequest); var query = _Repo.GetQueryable().Select(x => new VmAdminList() { title = x.Title, } ); if (!string.IsNullOrEmpty(filtersFromRequest.searchValue)) query = query.WhereSearchValue(x => x.title.ContainsSearchValue(filtersFromRequest.searchValue) || x.id.ToString().ContainsSearchValue(filtersFromRequest.searchValue)).AsQueryable(); var result = query.ToDataTableJs(filtersFromRequest); return new JsonResult(result); }
data: function (d) { d.parentId = parentID; d.StartDateTime= StartDateTime; },
if (!int.TryParse(Request.Form["parentId"].FirstOrDefault(), out int parentId)) throw new NullReferenceException();
dataSrc: function (json) { $("#count").val(json.data.length); var sum = 0; json.data.forEach(function (item) { if (!isNullOrEmpty(item.credit)) sum += parseInt(item.credit); }) $("#sum").val(separate(sum)); return json.data; }
columns: [ { data: 'name' }, { data: 'position' }, { data: 'salary' }, { data: 'office' } ]
public class JsDataTblGeneretaor<T> { public readonly DataTableSchemaResult DataTableSchemaResult = new(); public JsDataTblGeneretaor<T> CreateTableSchema() { var props = typeof(T).GetProperties(); foreach (var prop in props) { DataTableSchemaResult.SchemaResult.Add(new() { data = prop.Name, sortable = (prop.PropertyType == typeof(int)) || (prop.PropertyType == typeof(bool)) || (prop.PropertyType == typeof(DateTime)), width = "", visible = (prop.PropertyType != typeof(DateTime)) }); } return this; } public JsDataTblGeneretaor<T> CreateTableColumns() { var props = typeof(T).GetProperties(); CustomAttributeData displayAttribute; foreach (var prop in props) { string displayName = prop.Name; displayAttribute = prop.CustomAttributes.FirstOrDefault(x => x.AttributeType.Name == "DisplayAttribute"); if (displayAttribute != null) { displayName = displayAttribute.NamedArguments.FirstOrDefault().TypedValue.Value.ToString(); } DataTableSchemaResult.Colums.Add(displayName); } return this; } public JsDataTblGeneretaor<T> AddCustomSchema(string data, bool? sortable = null, bool? visible = null, string width = null, string className = null) { if (DataTableSchemaResult.SchemaResult == null || !DataTableSchemaResult.SchemaResult.Any()) return this; foreach (var item in DataTableSchemaResult.SchemaResult.Where(x => x.data == data)) { if (sortable != null) item.sortable = sortable.Value; if (visible != null) item.visible = visible.Value; if (width != null) item.width = width; if (className != null) item.className = className; } return this; } public JsDataTblGeneretaor<T> SerializeSchema() { if (DataTableSchemaResult.SchemaResult == null || !DataTableSchemaResult.SchemaResult.Any()) return this; DataTableSchemaResult.SerializedSchemaResult = JsonSerializer.Serialize(DataTableSchemaResult.SchemaResult); return this; } } public class DataTableSchema { public string data { get; set; } public bool sortable { get; set; } public string width { get; set; } public bool visible { get; set; } public string className { get; set; } } public class DataTableSchemaResult { public readonly List<DataTableSchema> SchemaResult = new(); public readonly List<string> Colums = new(); public string SerializedSchemaResult = ""; }
public void OnGet() { //Create Data Table Js Schema and Columns Dynamicly JsDataTblGeneretaor<yourVM> tblGeneretaor = new(); DataTableSchemaResult = tblGeneretaor.CreateTableColumns().CreateTableSchema().SerializeSchema().DataTableSchemaResult; }
.AddCustomSchema("yourProperty",visible:false)
var scheme = JSON.parse('@Html.Raw(Model.DataTableSchemaResult.SerializedSchemaResult)')
@foreach (var col in Model.DataTableSchemaResult.Colums) { <th>@col</th> }
ردیس ۷ در راه است
گویا ۳ برابر سریعتر از الستیکسرچ است!
In Redis 7.0, Redis Labs is adding two enhancements to its JSON support. The first is with search. RediSearch 2.0, which itself only became generally available barely a month ago; it now adds JSON as a supported data type. Before this, search was run as a separate feature that sat apart from the nodes housing the data engine. RediSearch 2.0 adds new scale-out capabilities to conduct massively parallel searches across up to billions of JSON documents across multiple nodes, returning results in fractions of a second. As the search engine was optimized for the Redis database, Redis Labs claims it runs up to 3x faster than Elasticsearch.