- نسخهی اصلی: http://www.telerik.com/support/whats-new/kendo-ui/release-history
- محصور کنندهی ASP.NET MVC آن: http://www.telerik.com/support/whats-new/aspnet-mvc/release-history
var rpt = new IListPdfReport().CreatePdfReport();
> npm install bootstrap --save > npm install ngx-bootstrap --save
"apps": [ { "styles": [ "../node_modules/bootstrap/dist/css/bootstrap.min.css", "styles.css" ],
import { PaginationModule } from "ngx-bootstrap"; @NgModule({ imports: [ PaginationModule.forRoot() ]
>ng g m SimpleGrid -m app.module --routing
>ng g c SimpleGrid/products-list
>ng g cl SimpleGrid/app-product
export class AppProduct { constructor( public productId: number, public productName: string, public price: number, public isAvailable: boolean ) {} }
>ng g cl SimpleGrid/PagedQueryModel
export class PagedQueryModel { constructor( public sortBy: string, public isAscending: boolean, public page: number, public pageSize: number ) {} }
>ng g cl SimpleGrid/PagedQueryResult
export class PagedQueryResult<T> { constructor(public totalItems: number, public items: T[]) {} }
>ng g cl SimpleGrid/GridColumn
export class GridColumn { constructor( public title: string, public propertyName: string, public isSortable: boolean ) {} }
>ng g s SimpleGrid/products-list -m simple-grid.module
toQueryString(obj: any): string { const parts = []; for (const key in obj) { if (obj.hasOwnProperty(key)) { const value = obj[key]; if (value !== null && value !== undefined) { parts.push(encodeURIComponent(key) + "=" + encodeURIComponent(value)); } } } return parts.join("&"); }
[HttpGet("[action]")] public PagedQueryResult<Product> GetPagedProducts(ProductQueryViewModel queryModel)
http://localhost:5000/api/Product/GetPagedProducts?sortBy=productId&isAscending=true&page=2&pageSize=7
@Injectable() export class ProductsListService { private baseUrl = "api/Product"; constructor(private http: Http) {} getPagedProductsList( queryModel: PagedQueryModel ): Observable<PagedQueryResult<AppProduct>> { return this.http .get(`${this.baseUrl}/GetPagedProducts?${this.toQueryString(queryModel)}`) .map(res => { const result = res.json(); return new PagedQueryResult<AppProduct>( result.totalItems, result.items ); }); }
<table class="table table-striped table-hover table-bordered table-condensed"> <thead> <tr> <th class="text-center" style="width:3%">#</th> <th *ngFor="let column of columns" class="text-center"> <div *ngIf="column.isSortable" (click)="sortBy(column.propertyName)" style="cursor: pointer"> {{ column.title }} <i *ngIf="queryModel.sortBy === column.propertyName" class="glyphicon" [class.glyphicon-sort-by-order]="queryModel.isAscending" [class.glyphicon-sort-by-order-alt]="!queryModel.isAscending"></i> </div> <div *ngIf="!column.isSortable" style="cursor: pointer"> {{ column.title }} </div> </th> </tr> </thead>
export class ProductsListComponent implements OnInit { columns: GridColumn[] = [ new GridColumn("Id", "productId", true), new GridColumn("Name", "productName", true), new GridColumn("Price", "price", true), new GridColumn("Available", "isAvailable", true) ];
export class ProductsListComponent implements OnInit { itemsPerPage = 7; queryModel = new PagedQueryModel("productId", true, 1, this.itemsPerPage); sortBy(columnName) { if (this.queryModel.sortBy === columnName) { this.queryModel.isAscending = !this.queryModel.isAscending; } else { this.queryModel.sortBy = columnName; this.queryModel.isAscending = true; } this.getPagedProductsList(); } }
<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>
export class ProductsListComponent implements OnInit { itemsPerPage = 7; currentPage: number; numberOfPages: number; isLoading = false; queryModel = new PagedQueryModel("productId", true, 1, this.itemsPerPage); queryResult = new PagedQueryResult<AppProduct>(0, []); constructor(private productsListService: ProductsListService) {} ngOnInit() { this.getPagedProductsList(); } private getPagedProductsList() { this.isLoading = true; this.productsListService .getPagedProductsList(this.queryModel) .subscribe(result => { this.queryResult = result; this.isLoading = false; }); } }
<div align="center"> <pagination [maxSize]="8" [boundaryLinks]="true" [totalItems]="queryResult.totalItems" [rotate]="false" previousText="‹" nextText="›" firstText="«" lastText="»" (numPages)="numberOfPages = $event" [(ngModel)]="currentPage" (pageChanged)="onPageChange($event)"></pagination> </div> <pre class="card card-block card-header">Page: {{currentPage}} / {{numberOfPages}}</pre>
export class ProductsListComponent implements OnInit { itemsPerPage = 7; currentPage: number; numberOfPages: number; onPageChange(event: any) { this.queryModel.page = event.page; this.getPagedProductsList(); } }
public void ConfigureServices(IServiceCollection services) { services.Configure<BearerTokensOptions>(options => Configuration.GetSection("BearerTokens").Bind(options)); services .AddAuthentication(options => { options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultSignInScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; }) .AddJwtBearer(cfg => { cfg.RequireHttpsMetadata = false; cfg.SaveToken = true; cfg.TokenValidationParameters = new TokenValidationParameters { ValidIssuer = Configuration["BearerTokens:Issuer"], ValidAudience = Configuration["BearerTokens:Audience"], IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["BearerTokens:Key"])), ValidateIssuerSigningKey = true, ValidateLifetime = true, ClockSkew = TimeSpan.Zero }; cfg.Events = new JwtBearerEvents { OnAuthenticationFailed = context => { var logger = context.HttpContext.RequestServices.GetRequiredService<ILoggerFactory>().CreateLogger(nameof(JwtBearerEvents)); logger.LogError("Authentication failed.", context.Exception); return Task.CompletedTask; }, OnTokenValidated = context => { var tokenValidatorService = context.HttpContext.RequestServices.GetRequiredService<ITokenValidatorService>(); return tokenValidatorService.ValidateAsync(context); }, OnMessageReceived = context => { return Task.CompletedTask; }, OnChallenge = context => { var logger = context.HttpContext.RequestServices.GetRequiredService<ILoggerFactory>().CreateLogger(nameof(JwtBearerEvents)); logger.LogError("OnChallenge error", context.Error, context.ErrorDescription); return Task.CompletedTask; } }; });
{ "BearerTokens": { "Key": "This is my shared key, not so secret, secret!", "Issuer": "http://localhost/", "Audience": "Any", "AccessTokenExpirationMinutes": 2, "RefreshTokenExpirationMinutes": 60 } }
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
$.ajax({ headers: { 'Authorization': 'Bearer ' + jwtToken },
const string tokenKey = "my.custom.jwt.token.key"; if (context.HttpContext.Items.ContainsKey(tokenKey)) { context.Token = (string)context.HttpContext.Items[tokenKey]; }
OnTokenValidated = context => { var tokenValidatorService = context.HttpContext.RequestServices.GetRequiredService<ITokenValidatorService>(); return tokenValidatorService.ValidateAsync(context); },
public class TokenValidatorService : ITokenValidatorService { private readonly IUsersService _usersService; private readonly ITokenStoreService _tokenStoreService; public TokenValidatorService(IUsersService usersService, ITokenStoreService tokenStoreService) { _usersService = usersService; _usersService.CheckArgumentIsNull(nameof(usersService)); _tokenStoreService = tokenStoreService; _tokenStoreService.CheckArgumentIsNull(nameof(_tokenStoreService)); } public async Task ValidateAsync(TokenValidatedContext context) { var userPrincipal = context.Principal; var claimsIdentity = context.Principal.Identity as ClaimsIdentity; if (claimsIdentity?.Claims == null || !claimsIdentity.Claims.Any()) { context.Fail("This is not our issued token. It has no claims."); return; } var serialNumberClaim = claimsIdentity.FindFirst(ClaimTypes.SerialNumber); if (serialNumberClaim == null) { context.Fail("This is not our issued token. It has no serial."); return; } var userIdString = claimsIdentity.FindFirst(ClaimTypes.UserData).Value; if (!int.TryParse(userIdString, out int userId)) { context.Fail("This is not our issued token. It has no user-id."); return; } var user = await _usersService.FindUserAsync(userId).ConfigureAwait(false); if (user == null || user.SerialNumber != serialNumberClaim.Value || !user.IsActive) { // user has changed his/her password/roles/stat/IsActive context.Fail("This token is expired. Please login again."); } var accessToken = context.SecurityToken as JwtSecurityToken; if (accessToken == null || string.IsNullOrWhiteSpace(accessToken.RawData) || !await _tokenStoreService.IsValidTokenAsync(accessToken.RawData, userId).ConfigureAwait(false)) { context.Fail("This token is not in our database."); return; } await _usersService.UpdateUserLastActivityDateAsync(userId).ConfigureAwait(false); } }
public class UserToken { public int Id { get; set; } public string AccessTokenHash { get; set; } public DateTimeOffset AccessTokenExpiresDateTime { get; set; } public string RefreshTokenIdHash { get; set; } public DateTimeOffset RefreshTokenExpiresDateTime { get; set; } public int UserId { get; set; } // one-to-one association public virtual User User { get; set; } }
public interface ITokenStoreService { Task AddUserTokenAsync(UserToken userToken); Task AddUserTokenAsync( User user, string refreshToken, string accessToken, DateTimeOffset refreshTokenExpiresDateTime, DateTimeOffset accessTokenExpiresDateTime); Task<bool> IsValidTokenAsync(string accessToken, int userId); Task DeleteExpiredTokensAsync(); Task<UserToken> FindTokenAsync(string refreshToken); Task DeleteTokenAsync(string refreshToken); Task InvalidateUserTokensAsync(int userId); Task<(string accessToken, string refreshToken)> CreateJwtTokens(User user); }
private async Task<string> createAccessTokenAsync(User user, DateTime expires) { var claims = new List<Claim> { // Unique Id for all Jwt tokes new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), // Issuer new Claim(JwtRegisteredClaimNames.Iss, _configuration.Value.Issuer), // Issued at new Claim(JwtRegisteredClaimNames.Iat, DateTime.UtcNow.ToUnixEpochDate().ToString(), ClaimValueTypes.Integer64), new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()), new Claim(ClaimTypes.Name, user.Username), new Claim("DisplayName", user.DisplayName), // to invalidate the cookie new Claim(ClaimTypes.SerialNumber, user.SerialNumber), // custom data new Claim(ClaimTypes.UserData, user.Id.ToString()) }; // add roles var roles = await _rolesService.FindUserRolesAsync(user.Id).ConfigureAwait(false); foreach (var role in roles) { claims.Add(new Claim(ClaimTypes.Role, role.Name)); } var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration.Value.Key)); var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); var token = new JwtSecurityToken( issuer: _configuration.Value.Issuer, audience: _configuration.Value.Audience, claims: claims, notBefore: DateTime.UtcNow, expires: expires, signingCredentials: creds); return new JwtSecurityTokenHandler().WriteToken(token); }
<ItemGroup> <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="2.0.0" /> </ItemGroup>
public async Task<(string accessToken, string refreshToken)> CreateJwtTokens(User user) { var now = DateTimeOffset.UtcNow; var accessTokenExpiresDateTime = now.AddMinutes(_configuration.Value.AccessTokenExpirationMinutes); var refreshTokenExpiresDateTime = now.AddMinutes(_configuration.Value.RefreshTokenExpirationMinutes); var accessToken = await createAccessTokenAsync(user, accessTokenExpiresDateTime.UtcDateTime).ConfigureAwait(false); var refreshToken = Guid.NewGuid().ToString().Replace("-", ""); await AddUserTokenAsync(user, refreshToken, accessToken, refreshTokenExpiresDateTime, accessTokenExpiresDateTime).ConfigureAwait(false); await _uow.SaveChangesAsync().ConfigureAwait(false); return (accessToken, refreshToken); }
[AllowAnonymous] [HttpPost("[action]")] public async Task<IActionResult> Login([FromBody] User loginUser) { if (loginUser == null) { return BadRequest("user is not set."); } var user = await _usersService.FindUserAsync(loginUser.Username, loginUser.Password).ConfigureAwait(false); if (user == null || !user.IsActive) { return Unauthorized(); } var (accessToken, refreshToken) = await _tokenStoreService.CreateJwtTokens(user).ConfigureAwait(false); return Ok(new { access_token = accessToken, refresh_token = refreshToken }); }
[AllowAnonymous] [HttpPost("[action]")] public async Task<IActionResult> RefreshToken([FromBody]JToken jsonBody) { var refreshToken = jsonBody.Value<string>("refreshToken"); if (string.IsNullOrWhiteSpace(refreshToken)) { return BadRequest("refreshToken is not set."); } var token = await _tokenStoreService.FindTokenAsync(refreshToken); if (token == null) { return Unauthorized(); } var (accessToken, newRefreshToken) = await _tokenStoreService.CreateJwtTokens(token.User).ConfigureAwait(false); return Ok(new { access_token = accessToken, refresh_token = newRefreshToken }); }
[AllowAnonymous] [HttpGet("[action]"), HttpPost("[action]")] public async Task<bool> Logout() { var claimsIdentity = this.User.Identity as ClaimsIdentity; var userIdValue = claimsIdentity.FindFirst(ClaimTypes.UserData)?.Value; // The Jwt implementation does not support "revoke OAuth token" (logout) by design. // Delete the user's tokens from the database (revoke its bearer token) if (!string.IsNullOrWhiteSpace(userIdValue) && int.TryParse(userIdValue, out int userId)) { await _tokenStoreService.InvalidateUserTokensAsync(userId).ConfigureAwait(false); } await _tokenStoreService.DeleteExpiredTokensAsync().ConfigureAwait(false); await _uow.SaveChangesAsync().ConfigureAwait(false); return true; }
[HttpGet] public ActionResult Index() { return View(); }
using System; using System.Linq; using System.Web.Mvc; using System.Security.Cryptography; using System.Text; using System.Web.Caching; namespace Parsnet.Core { public class StopSpamAttribute : ActionFilterAttribute { // حداقل زمان مجاز بین درخواستها برحسب ثانیه public int DelayRequest = 10; // پیام خطایی که در صورت رسیدن درخواست غیرمجاز باید صادر کنیم public string ErrorMessage = "درخواستهای شما در مدت زمان معقولی صورت نگرفته است."; //خصوصیتی برای تعیین اینکه آدرس درخواست هم به شناسه یکتا افزوده شود یا خیر public bool AddAddress = true; public override void OnActionExecuting(ActionExecutingContext filterContext) { // درسترسی به شئی درخواست var request = filterContext.HttpContext.Request; // دسترسی به شیئ کش var cache = filterContext.HttpContext.Cache; // کاربر IP بدست آوردن var IP = request.ServerVariables["HTTP_X_FORWARDED_FOR"] ?? request.UserHostAddress; // مشخصات مرورگر var browser = request.UserAgent; // در اینجا آدرس درخواست جاری را تعیین میکنیم var targetInfo = (this.AddAddress) ? (request.RawUrl + request.QueryString) : ""; // شناسه یکتای درخواست var Uniquely = String.Concat(IP, browser, targetInfo); //در اینجا با کمک هش یک امضا از شناسهی درخواست ایجاد میکنیم var hashValue = string.Join("", MD5.Create().ComputeHash(Encoding.ASCII.GetBytes(Uniquely)).Select(s => s.ToString("x2"))); // ابتدا چک میکنیم که آیا شناسهی یکتای درخواست در کش موجود نباشد if (cache[hashValue] != null) { // یک خطا اضافه میکنیم ModelState اگر موجود بود یعنی کمتر از زمان موردنظر درخواست مجددی صورت گرفته و به filterContext.Controller.ViewData.ModelState.AddModelError("ExcessiveRequests", ErrorMessage); } else { // اگر موجود نبود یعنی درخواست با زمانی بیشتر از مقداری که تعیین کردهایم انجام شده // پس شناسه درخواست جدید را با پارامتر زمانی که تعیین کرده بودیم به شیئ کش اضافه میکنیم cache.Add(hashValue, true, null, DateTime.Now.AddSeconds(DelayRequest), Cache.NoSlidingExpiration, CacheItemPriority.Default, null); } base.OnActionExecuting(filterContext); } } }
[HttpPost] [StopSpam(DelayRequest = 5)] [ValidateAntiForgeryToken] public virtual async Task<ActionResult> SendFile(HttpPostedFileBase file, int userid = 0) { } [HttpPost] [StopSpam(DelayRequest = 30, ErrorMessage = "زمان لازم بین ارسال هر مطلب 30 ثانیه است")] [ValidateAntiForgeryToken] public virtual async Task<ActionResult> InsertPost(NewPostModel model) { }
[HttpPost] [StopSpam(AddAddress = true, DelayRequest = 20)] [ValidateAntiForgeryToken] public Task<ActionResult> InsertPost(NewPostModel model) { if (ModelState.IsValid) { var newPost = dbContext.InsertPost(model); if (newPost != null) { ViewBag.ExecuteResult = true; } } if (ModelState.IsValidField("ExcessiveRequests") == true)
{
ViewBag.ExecuteResult = false;
}
return View(); }
در این آموزش هدف ما ایجاد برنامهای بر اساس TodoMVC است که میتواند خودش را با یک دیتابیس آنلاین همگام سازی کند.
مطمئن باشید بیشتر از 10 دقیقه وقت شمارا نخواهد گرفت !
فایل index.html را باز کنید و فایلهای PouchDB را به آن اضافه کنید :
<script src="//cdn.jsdelivr.net/pouchdb/2.2.0/pouchdb.min.js"></script> <script src="js/base.js"></script> <script src="js/app.js"></script>
حالا PouchDB نصب شده و آماده به کار است . ( در نسخه نهایی بهتر است از فایلهای local استفاده کنید )
بقیه کارها باید در فایل app.js انجام شود . برای شروع باید بانک اطلاعاتی جدیدی را برای درج اطلاعات خودمان ایجاد کنیم . برای ایجاد بانک اطلاعاتی خیلی ساده یک instance جدید از آبجکت PouchDB میسازیم .
var db = new PouchDB('todos'); var remoteCouch = false;
نیازی نیست که برای بانک خود، نما (Schema) ایجاد کنید! بعد از اینکه اسم را مشخص کنید، بانک آماده به کار است.
اولین کاری که باید انجام دهیم این است که یک متد را ایجاد کنیم و توسط آن اطلاعات خودمان را در بانک اطلاعاتی ذخیره کنیم. نام متد را addTodo انتخاب میکنیم و کارش این است که وقتی کاربر کلید Enter را فشار داد، اطلاعات را داخل بانک اطلاعاتی ذخیره کند. متد مورد نظر به این صورت هست:
function addTodo(text) { var todo = { _id: new Date().toISOString(), title: text, completed: false }; db.put(todo, function callback(err, result) { if (!err) { console.log('Successfully posted a todo!'); } }); }
در PouchDB هر پرونده نیاز دارد تا یک فیلد unique با نام _id داشته باشد. اگر دادهای بخواهد در بانک اطلاعاتی ثبت شود و دارای _id مشابه باشد، با آن به صورت یک update رفتار خواهد شد. در اینجا ما از تاریخ با عنوان id استفاده کردیم که در این مورد خاص میباشد. شما میتواند از db.post() نیز استفاده کنید؛ اگر یک id را به صورت random میخواهید. تنها چیزی که اجباری است در هنگام ساختن یک پرونده، همین _id است و بقیه موارد کاملا اختیاری هستند.
در اینجا ما از یک function کمکی به نام redrawTodosUI استفاده خواهیم کرد که وظیفهاش این است تا یک array را دریافت کرده و آن را هر طور که مشخص کردید نمایش دهد. البته آن را به سلیقه خودتان میتوانید آماده کنید.
تنها کاری که باید انجام دهیم این است که اطلاعات را از بانک اطلاعاتی استخراج کرده و به function مورد نظر پاس دهیم.
در اینجا میتوانیم به سادگی از db.allDocs برای خواندن اطلاعات از بانک اطلاعاتی استفاده کنیم.
خاصیت include_docs به PouchDB این دستور را میدهد که ما درخواست دریافت همه اطلاعات داخل پروندهها را داریم و descending هم نحوه مرتب سازی را که بر اساس _id هست، مشخص میکند تا بتوانیم جدیدترین اطلاعات را اول دریافت کنیم .
function showTodos() { db.allDocs({include_docs: true, descending: true}, function(err, doc) { redrawTodosUI(doc.rows); }); }
هر بار که ما اطلاعات جدیدی را در بانک اطلاعاتی وارد میکنیم، نیازمند این هستیم تا اطلاعات جدید را به صورت خودکار دریافت و نمایش دهیم. برای این منظور میتوانیم به روش زیر عمل کنیم :
var remoteCouch = false; db.info(function(err, info) { db.changes({ since: info.update_seq, live: true }).on('change', showTodos); });
حالا هر بار که اطلاعات جدیدی در بانک اطلاعات ثبت شود، به صورت خودکار نمایش داده خواهد شد. خاصیت live مشخص میکند که این کار میتواند بی نهایت بار انجام شود.
وقتی که کاربر یک آیتم Todo را کامل میکند، یک چک باکس را علامت میزند و اعلام میکند که این کار انجام شده است.
function checkboxChanged(todo, event) { todo.completed = event.target.checked; db.put(todo); }
این بخش شبیه به قسمت ثبت اطلاعات است، با این تفاوت که باید شامل یک فیلد _rev ( اضافه بر _id ) نیز باشد. در غیر اینصورت تغییرات ثبت نخواهد شد. این کار برای این است که اشتباهی، اطلاعاتی در بانک ثبت نشود.
برای حذف اطلاعات باید از متد db.remove به همراه آبجکت مورد نظر استفاده کنید .
function deleteButtonPressed(todo) { db.remove(todo); }
مانند بخش ویرایش نیز باید در اینجا هم _id و هم _rev مشخص شود.
باید توجه داشته باشید در اینجا همان آبجکتی را که از بانک فراخوانی کردهایم، به این متد پاس میدهیم.
البته شما میتونید آبجکت خودتان را نیز ایجاد کنید {_id: todo._id, _rev: todo._rev} اما خوب همان روش قبلی عاقلانهتر است و احتمال خطای کمتری دارد .
حالا میخواهیم همگام سازی را اجرا کنیم و برای این کار نیاز هست یا CouchDB را به صورت Local نصب کنیم یا از سرویسهای آنلاین مثل IrisCouch استفاده کنید .
برای اینکه به صورت مستقیم با CouchDB در ارتباط باشید باید مطمئن شوید که CORS فعال هست.
در اینجا فقط نام کاربری و رمز ورود را مشخص کنید. به صورت پیش فرض CouchDB به صورت Admin Party نصب میشود و نیازی به User و password ندارد. مگر اینکه برایش مشخص کنید.
همچنین شما باید myname.iriscouch.com را با سرور خودتان جایگزین کنید که اگر به صورت local کار میکنید 127.0.0.1:5984 است.
$ export HOST=http://username:password@myname.iriscouch.com $ curl -X PUT $HOST/_config/httpd/enable_cors -d '"true"' $ curl -X PUT $HOST/_config/cors/origins -d '"*"' $ curl -X PUT $HOST/_config/cors/credentials -d '"true"' $ curl -X PUT $HOST/_config/cors/methods -d '"GET, PUT, POST, HEAD, DELETE"' $ curl -X PUT $HOST/_config/cors/headers -d \ '"accept, authorization, content-type, origin"'
به فایل app.js برگردید . در اینجا باید آدرس بانک اطلاعاتی آنلاین را مشخص کنیم.
var db = new PouchDB('todos'); var remoteCouch = 'http://user:pass@mname.iriscouch.com/todos';
فراموش نکنید که نام کاربری و رمز ورود را بسته به نیاز خود تغییر دهید.
حالا میتونیم function که وظیفه همگام سازی اطلاعات را به عهده دارد بنویسیم :
function sync() { syncDom.setAttribute('data-sync-state', 'syncing'); var opts = {live: true}; db.replicate.to(remoteCouch, opts, syncError); db.replicate.from(remoteCouch, opts, syncError); }
db.replicate() به PouchDB میگوید که که همه اطلاعات را "به" یا "از" remoteCouch انتقال دهد.
ما دوبار این درخواست را دادیم. در بار اول اطلاعات به داخل سرور ریموت منتقل میشود و در بار دوم اطلاعات local جایگزین میشوند.
یک callback هم وقتی که این کار به پایان برسد اجرا خواهد شد .
میتوانید برنامه خود را در دو مرورگر مختلف اجرا کنید تا نتیجه کار را ببینید.
شما توانستید اولین برنامه خود را توسط PouchDB ایجاد کنید. البته این یک برنامه ساده بود و در دنیای واقعی نیاز هست تا خیلی کارهای بیشتری را انجام دهید. اما باز هم اصول اولیه کار را یاد گرفتید و ادامه راه را میتوانید تنهایی ادامه دهید .