<add key="VisualStudioDesignTime:Enabled" value="true" />
<form action="/rest/login" method="POST"> <input name="username" required> <input type="password" name="password" required> </form>
محتوا یا content به معنای المانهایی هستند که درون یک المان قرار میگیرند. برای مثال در اینجا المان <form> دارای محتوایی متشکل از دو المان <input> است و دون المان <input> دارای محتوایی نیستند.
از ویژگیها و یا attributes، برای ارائهی اطلاعات بیشتر و یا تعیین حالت عناصر استفاده میشود. برای نمونه در اینجا المان <form> دارای دو ویژگی action و method است که برای ارائهی اطلاعاتی بیشتر جهت تعیین روش و محل ارسال اطلاعات به سرور از آنها استفاده میشود. همچنین میتوان ویژگیهای سفارشی را نیز توسط ویژگیهای شروع شدهی با -data نیز به المانها اضافه کرد که جزئی از استاندارد HTML 5 است. هرچند میتوان ویژگی کاملا غیرمتعارفی مانند myproject-uuid را نیز به یک المان اضافه کرد. این مورد مشکل خاصی را ایجاد نکرده و صفحه بدون مشکل رندر میشود؛ اما یک چنین صفحهای دیگر به عنوان یک صفحهی استاندارد HTML ارزیابی نخواهد شد؛ از این جهت که از ویژگی استفاده کردهاست که در هیچکدام از استانداردهای HTML ذکر نشدهاست.
آخرین نگارش HTML5 به همراه 4 ویژگی جدید دیگر نیز هست:
- Boolean attribute : مانند ویژگی required که به المانهای <input> قابل انتساب است. حضور این نوع ویژگیها بدون مقداری در یک المان
<input name=username required>
نمونهی دیگری از این نوع ویژگیها، ویژگی hidden است که به HTML 5.1 اضافه شدهاست و اگر به عنصری اضافه شود، آن المان رندر نخواهد شد.
- unquoted attribute: به این معنا که میتوان "" را از اطراف مقدار یک ویژگی حذف کرد:
<input name=username required>
- single-quoted and double-quoted attributes: که به معنای استفاده از "" و یا '' جهت تعیین مقدار یک ویژگی است.
تفاوت attributes با خواص المانها چیست؟
Attributes در تعریف تگ HTML یک عنصر ظاهر میشوند اما خواص، جزئی از تعریف شیء یک عنصر هستند.
<div class="bold">I'm a bold element</div>
<a href="http://www.site.com/blog/">Read the blog</a>
<script> document.querySelector('A').href = 'https://www.dntips.ir'; </script>
- برای مثال واژهی class در زبان جاوا اسکریپت یک واژهی کلیدی است. به همین جهت در اینجا اگر بخواهیم مقدار ویژگی class را تغییر دهیم باید از خاصیت className آن المان استفاده کنیم.
- مورد دوم ویژگی checked المانهای radio و checkbox است. این ویژگی فقط در ابتدای کار این المانها به آنها متصل میشود:
<input type="checkbox" checked> <script> // this does not remove the checked attribute document.querySelector('INPUT').checked = false; </script>
- تمام ویژگیهای غیراستانداردی که تعریف شوند، دارای خاصیت متناظری در آن المان نخواهند بود، اما به آنها expando properties گفته میشود.
یافتن المانها بر اساس ویژگیهای آنها
از آنجائیکه attribute selectors در استاندارد W3C CSS 2 معرفی شدهاند، امکان کار با آنها از زمان IE 8.0 میسر بودهاست و برای کار با آنها الزاما نیازی به استفاده از jQuery نیست!
یافتن المانها بر اساس نام ویژگیها
<form action="/submitHandler" method="POST"> <input name="first-name"> <input name="last-name" required> <input type="email" name="email" required> <button disabled>submit</button> </form>
var $result = $('[required], [disabled]');
var result = document.querySelectorAll('[required], [disabled]');
در اینجا باید دقت داشت که این جستجو صرفا بر اساس نام ویژگیها انجام میشود؛ حتی اگر این ویژگی دارای مقداری نباشد:
<div class="bold">I'm bold</div> <span class>I'm not</span>
var result = document.querySelectorAll('[class]');
یافتن المانها بر اساس نام و مقدار ویژگیها
<section> <h2>Sites</h2> <ul> <li> <a href="https://www.dntips.ir/">dotnettips</a> </li> <li> <a href="https://google.com/">Google</a> </li> </ul> </section>
var $result = $('A[href="https://www.dntips.ir/"]');
var result = document.querySelectorAll('A[href="https://www.dntips.ir/"]');
و یا اگر اینبار بخواهیم تمام ویژگیهای کلاسی که دارای مقدار هستند را انتخاب کنیم، روش آن با استفاده از exclusion selector به صورت زیر است:
var result = document.querySelectorAll('[class]:not([class=""]');
یافتن المانها بر اساس نام و قسمتی از مقدار ویژگیها
<a href="https://www.dntips.ir/">home page</a> <a href="https://www.dntips.ir/postsarchive">articles</a> <a href="https://www.dntips.ir/newsarchive">news</a>
var result = document.querySelectorAll('A[href*="www.dotnettips.info"]');
یافتن المانها بر اساس نام و کلمهای مشخص در مقدار ویژگیها
<div class="one two three">1 2 3</div> <div class="onetwothree">123</div>
var result = document.querySelectorAll('[class∼=two]');
در اینجا نوع دیگری از کوئری را هم میتوان مطرح کرد: آیا المانی مشخص، دارای کلاس two است؟
روش انجام آن در jQuery به صورت زیر است:
var hasTwoClass = $divEl.hasClass('two');
var hasTwoClass = divEl.classList.contains('two');
همچنین خاصیت classList به همراه متدهای استاندارد add و remove نیز هست که معادل متدهای addClass و removeClass جیکوئری هستند.
divEl.classList.remove('red'); divEl.classList.add('blue');
// removes "hide" class divEl.classList.toggle('hide'); // re-adds "hide" class divEl.classList.toggle('hide');
یافتن المانها بر اساس نام و شروع یا خاتمهی عبارتی مشخص در مقدار ویژگیها
<img id="cat" src="/images/cat.gif"> <img id="zebra" src="zebra.gif"> <a href="#zebra">watch the zebra</a> <a href="/logout">logout</a>
var result = document.querySelectorAll('A[href^="#"], [src$=".gif"]');
خواندن مقادیر ویژگیها
<input type="password" name="user-password" required>
// returns "password" $inputEl.attr('type'); // returns "true" $inputEl.is('[required]');
// returns "password" inputEl.getAttribute('type'); // returns "true" inputEl.hasAttribute('required');
تغییر مقدار ویژگیها
<input name="temp" required>
روش انجام اینکار در جیکوئری:
$inputEl .attr('type', 'email') // #1 .removeAttr('required') // #2 .attr('name', 'userEmail'); // #3
inputEl.setAttribute('type', 'email'); // #1 inputEl.removeAttribute('required'); // #2 inputEl.setAttribute('name', 'userEmail'); // #3
بسیاری از مواقع پیش میآید که در سایت خود بخواهیم کادری داشته باشیم که با کلیک بروی آن ظاهر و با کلیک دوباره بروی آن محو شود. مانند تصویر زیر
سپس با کلیک بروی قسمت مشخص شده از تصویر بالا تصویر مانند زیر ظاهر شود.
در این نوشته قصد داریم کادری به این صورت حالا به هر منظوری طراحی نماییم.
برای کار سه قسمت کد داریم:
- کدهای طراحی قسمت مورد نظر در صفحه وب
- نوشتن کدهای CSS مربوطه
- نوشتن کدهای jQuery
در مرحله اول ابتدا صفحه وب خود را به نحو زیر ایجاد مینماییم.
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>کادر لغزان با jQuery</title> <script src="Scripts/jquery-1.7.1.min.js"></script> <link href="CSS/site.css" rel="stylesheet" /> </head> <body> <div id="loginPanel"> <div style="height: auto;" id="login"> <div> <div> <br /> محتویات دلخواه خود را در این قسمت قرار دهید </div> </div> <div><a href="#" id="closeLogin"></a></div> </div> <div id="container"> <div id="top"> <!-- login --> <ul> <li> </li> <li><a id="toggleLogin" href="#">پانل باز شو</a></li> </ul> <!-- / login --> </div> <!-- / top --> </div> </div> <div id="main"> محتویات سایت در این قسمت قرار میگیرد </div> </body> </html>
body { margin:0; padding:0; width:100%; background: #e9e9e9 url(images/header_bg.gif) top repeat-x; direction: rtl; } html { padding:0; margin:0; } #main {margin-top: 100px;} #loginPanel { margin: 0px; position: absolute; overflow: hidden; height: auto; z-index: 3000; width: 100%; top: 0px; color: #fff; } #top { background: url(images/login_top.jpg) repeat-x 0 0; height: 38px; position: relative; } #top ul.login { display: block; position: relative; float: right; clear: right; height: 38px; width: auto; margin: 0; right: 150px; color: white; text-align: center; background: url(images/login_r.png) no-repeat right 0; padding-right: 45px; } #top ul.login li.left { background: url(images/login_l.png) no-repeat left 0; height: 38px; width: 45px; padding: 0; margin: 0; display: block; float: left; } #top ul.login li { text-align: left; padding: 0 6px; display: block; float: left; height: 38px; background: url(images/login_m.jpg) repeat-x 0 0; } #top ul.login li a { color: #fff; text-decoration: none; } #top ul.login li a:hover { color: #ff0000; text-decoration: none; } #login { width: 100%; color: white; background: #1E1E1E; overflow: hidden; position: relative; z-index: 3; height: 0px; } #login a { text-decoration: none; color: #fff; } #login a:hover { color: white; text-decoration: none; } #login .loginContent { width: 900px; height: 80px; margin: 0 auto; padding-top: 25px; text-align: right; } #login .loginClose { display: block; position: absolute; right: 15px; top: 10px; width: 70px; text-align: left; } #login .loginClose a { display: block; width: 100%; height: 20px; background: url(images/button_close.jpg) no-repeat right 0; padding-right: 10px; border: none; color: white; } #login .loginClose a:hover { background: url(images/button_close.jpg) no-repeat right -20px; } .cen { text-align: center;} .w_100p{ width: 100%;}
<script type="text/javascript"> $(document).ready(function () { $("#login").hide(0); $("#toggleLogin").click(function () { $("#login").slideToggle("slow"); }); $("#closeLogin").click(function () { $("#login").slideUp("slow"); }); }); </script>
روش دیگری برای تمیزسازی HTML و مقابله با XSS
برپایی تنظیمات اولیهی سیستم مسیریابی در AngularJS 2.0
برای کار با سیستم مسیریابی AngularJS 2.0، ابتدا باید اسکریپتهای آن به صفحه اضافه شوند. در ادامه المان پایهای تعریف شده و سپس باید سرویس پروایدر مسیریابی را رجیستر کرد. جزئیات این موارد را در ادامه بررسی میکنیم:
الف) سرویس مسیریابی، جزئی از angular2/core نیست. به همین جهت مدخل اسکریپت متناظر با آن باید به صفحهی اصلی سایت اضافه شود که این مورد، در قسمت اول بررسی پیشنیازهای نصب AngularJS 2.0 صورت گرفتهاست:
<!-- Required for routing --> <script src="~/node_modules/angular2/bundles/router.dev.js"></script>
ب) افزودن المان base به ابتدای صفحه:
<!DOCTYPE html> <html> <head> <base href="/">
از آنجائیکه فایل index.html در ریشهی سایت قرار گرفتهاست، مقدار آغازین href آن به / تنظیم شدهاست.
ج) شبیه به حالت ثبت پروایدر HTTP در قسمت قبل، برای ثبت پروایدر مسیریابی نیز به فایل App\app.component.ts مراجعه میکنیم:
//same as before ... import { ROUTER_PROVIDERS } from 'angular2/router'; //same as before ... @Component({ //same as before … providers: [ ProductService, HTTP_PROVIDERS, ROUTER_PROVIDERS ] }) //same as before ...
علت ختم شدن نام این سرویسها به PROVIDERS این است که این تعاریف، امکان استفادهی از چندین سرویس زیر مجموعهی آنها را فراهم میکنند و صرفا یک سرویس نیستند.
ساخت کامپوننت نمایش جزئیات محصولات
در ادامه میخواهیم جزئیات هر محصول را با کلیک بر روی نام آن در لیست محصولات، در آدرسی دیگر به صورتی مجزا مشاهده کنیم. به همین منظور به پوشهی products برنامه مراجعه کرده و دو فایل جدید product-detail.component.ts و product-detail.component.html را ایجاد کنید؛ با این محتوا:
الف) محتوای فایل product-detail.component.html
<div class='panel panel-primary'> <div class='panel-heading'> {{pageTitle}} </div> </div>
import { Component } from 'angular2/core'; @Component({ templateUrl: 'app/products/product-detail.component.html' }) export class ProductDetailComponent { pageTitle: string = 'Product Detail'; }
اگر دقت کنید، این کامپوننت ویژه دارای خاصیت selector نیست. ذکر selector تنها زمانی اجباری است که بخواهیم این کامپوننت را داخل کامپوننتی دیگر قرار دهیم. در اینجا قصد داریم این کامپوننت را به صورت یک View جدید، توسط سیستم مسیریابی نمایش دهیم و نه به صورت جزئی از یک کامپوننت دیگر.
افزودن تنظیمات مسیریابی به برنامه
مسیریابی در AngularJS 2.0، مبتنی بر کامپوننتها است. به همین جهت، ابتدای کار مسیریابی، مشخص سازی تعدادی از کامپوننتها هستند که قرار است به عنوان مقصد سیستم راهبری (navigation) مورد استفاده قرار گیرند و به ازای هر کدام، یک مسیریابی و Route جدید را تعریف میکنیم. تعریف هر Route جدید شامل انتساب نامی به آن، تعیین مسیر مدنظر و مشخص سازی کامپوننت مرتبط است:
{ path: '/products', name: 'Products', component: ProductListComponent },
این تنظیمات به عنوان یک متادیتای جدید دیگر به کلاس AppComponent، در فایل app.component.ts اضافه میشوند:
//same as before … import { ROUTER_PROVIDERS, RouteConfig } from 'angular2/router'; //same as before … @Component({ //same as before … }) @RouteConfig([ { path: '/welcome', name: 'Welcome', component: WelcomeComponent, useAsDefault: true }, { path: '/products', name: 'Products', component: ProductListComponent } ]) export class AppComponent { pageTitle: string = "DNT AngularJS 2.0 APP"; }
همانطور که ملاحظه میکنید، یک کلاس میتواند بیش از یک decorator داشته باشد.
()RouteConfig@ را به کامپوننتی الصاق میکنیم که قصد میزبانی مسیریابی را دارد (Host component). این مزین کننده، آرایهای از اشیاء را قبول میکند و هر شیء آن دارای خواصی مانند مسیر، نام و کامپوننت است. باید دقت داشت که نام هر مسیریابی تعریف شده باید pascal case باشد. در غیراینصورت مسیریاب ممکن است این نام را با path اشتباه کند.
همچنین امکان تعریف خاصیت دیگری به نام useAsDefault نیز در اینجا میسر است. از آن جهت تعریف مسیریابی پیش فرض سیستم، در اولین بار نمایش آن، استفاده میشود.
در اینجا نام کامپوننت، رشتهای ذکر نمیشود و دقیقا اشاره دارد به نام کلاس متناظر. بنابراین هر نام کلاسی که در اینجا اضافه میشود، باید به همراه import ماژول آن نیز در ابتدای فایل جاری باشد. به همین جهت اگر تنظیمات فوق را اضافه کنید، ذیل کلمهی WelcomeComponent یک خط قرمز مبتنی بر عدم تعریف آن کشیده میشود. برای تعریف آن، پوشهی جدیدی را به ریشهی سایت به نام home اضافه کنید و به آن دو فایل ذیل را اضافه نمائید:
الف) محتوای فایل welcome.component.ts
import { Component } from 'angular2/core'; @Component({ templateUrl: 'app/home/welcome.component.html' }) export class WelcomeComponent { public pageTitle: string = "Welcome"; }
<div class="panel panel-primary"> <div class="panel-heading"> {{pageTitle}} </div> <div class="panel-body"> <h3 class="text-center">Default page</h3> </div> </div>
پس از تعریف این کامپوننت، اکنون باید import ماژول آنرا به ابتدای فایل app.component.ts اضافه کنیم، تا مشکل عدم شناسایی نام کلاس WelcomeComponent برطرف شود:
import { WelcomeComponent } from './home/welcome.component';
فعال سازی مسیریابیهای تعریف شده
روشهای مختلفی برای دسترسی به اجزای یک برنامه وجود دارند؛ برای مثال کلیک بر روی یک لینک، دکمه و یا تصویر و سپس فعال سازی مسیریابی متناظر با آن. همچنین کاربر میتواند آدرس صفحهای را مستقیما در نوار آدرسهای مرورگر وارد کند. به علاوه امکان کلیک بر روی دکمههای back و forward مرورگر نیز همواره وجود دارند. تنظیمات مسیریابیهای انجام شده، دو مورد آخر را به صورت خودکار مدیریت میکنند. در اینجا تنها باید مدیریت اولین حالت ذکر شده را با اتصال مسیریابیها به اعمال کاربران، انجام داد.
به همین جهت منویی را به بالای صفحهی برنامه اضافه میکنیم. برای این منظور، فایل app.component.ts را گشوده و خاصیت template کامپوننت AppComponent را به نحو ذیل تغییر میدهیم:
@Component({ //same as before … template: ` <div> <nav class='navbar navbar-default'> <div class='container-fluid'> <a class='navbar-brand'>{{pageTitle}}</a> <ul class='nav navbar-nav'> <li><a [routerLink]="['Welcome']">Home</a></li> <li><a [routerLink]="['Products']">Product List</a></li> </ul> </div> </nav> <div class='container'> <router-outlet></router-outlet> </div> </div> `, //same as before … })
سپس جهت تعریف لینکهای هر آیتم، از یک دایرکتیو توکار AngularJS 2.0 به نام routerLink استفاده میکنیم. هر routerLink به یکی از آیتمهای تنظیم شدهی در RouteConfig بایند میشود. بنابراین نامهایی که در اینجا قید شدهاند، دقیقا نامهایی هستند که در خاصیت name هر کدام از اشیاء تشکیل دهندهی RouteConfig، تعریف و مقدار دهی گردیدهاند.
اکنون اگر کاربر بر روی یکی از لینکهای Home و یا Product List کلیک کند، مسیریابی متناظر با آن فعال میشود (بر اساس این نام، در لیست عناصر RouteConfig جستجویی صورت گرفته و عنصر معادلی بازگشت داده میشود) و سپس View آن کامپوننت نمایش داده خواهد شد.
تا اینجا دایرکتیو جدید routerLink به قالب کامپوننت اضافه شدهاست؛ اما AngularJS 2.0 نمیداند که باید آنرا از کجا دریافت کند. به همین جهت ابتدا import آنرا (ROUTER_DIRECTIVES) به ابتدای ماژول جاری اضافه خواهیم کرد:
import { ROUTER_PROVIDERS, RouteConfig, ROUTER_DIRECTIVES } from 'angular2/router';
directives: [ROUTER_DIRECTIVES],
تا اینجا اگر دقت کرده باشید، کامپوننت نمایش لیست محصولات را از کامپوننت ریشهی سایت حذف کردهایم و بجای آن منوی بالای سایت را نمایش میدهیم که توسط آن میتوان به صفحهی آغازین و یا صفحهی نمایش لیست محصولات، رسید. به همین جهت خاصیت directives دیگر شامل ذکر کلاس کامپوننت لیست محصولات نیست.
در انتهای قالب کامپوننت ریشهی سایت، یک دایرکتیو جدید به نام router-outlet نیز تعریف شدهاست. وقتی یک کامپوننت فعال میشود، نیاز است View مرتبط با آن نیز نمایش داده شود. دایرکتیو router-outlet محل نمایش این View را مشخص میکند.
اکنون اگر برنامه را اجرا کنیم، به این شکل خواهیم رسید:
اگر دقت کنید، آدرس بالای صفحه، در اولین بار نمایش آن به http://localhost:2222/welcome تنظیم شده و این مقدار دهی بر اساس خاصیت useAsDefault تنظیمات مسیریابی سایت انجام شدهاست (نمایش welcome به عنوان صفحهی اصلی و پیش فرض).
همچنین با کلیک بر روی لینک لیست محصولات، کامپوننت آن فعال شده و نمایش داده میشود. محل قرارگیری این کامپوننتها، دقیقا در محل قرارگیری دایرکتیو router-outlet است.
ارسال پارامترها به سیستم مسیریابی
در ابتدا بحث، مقدمات کامپوننت نمایش جزئیات یک محصول انتخابی را تهیه کردیم. برای فعال سازی این کامپوننت و مسیریابی آن، نیاز است بتوان پارامتری را به سیستم مسیریابی ارسال کرد. برای مثال با انتخاب آدرس product/5، جزئیات محصول با ID مساوی 5 نمایش داده شود.
برای این منظور:
الف) اولین قدم، تعریف مسیریابی آن است. به همین جهت به فایل app.component.ts مراجعه و دو تغییر ذیل را به آن اعمال کنید:
//same as before … import { ProductDetailComponent } from './products/product-detail.component'; @Component({ //same as before … }) @RouteConfig([ //same as before … { path: '/product/:id', name: 'ProductDetail', component: ProductDetailComponent } ]) //same as before …
تفاوت این مسیریابی با نمونههای قبلی در تعریف id:/ است. پس از ذکر :/، نام یک متغیر عنوان میشود و اگر نیاز به چندین متغیر بود، همین الگو را تکرار خواهیم کرد.
ب) سپس نحوهی فعال سازی این مسیریابی را توسط تعریف لینکی جدید، معرفی میکنیم. بنابراین فایل قالب product-list.component.html را گشوده و سپس بجای نمایش عنوان محصول:
<td>{{ product.productName }}</td>
<td> <a [routerLink]="['ProductDetail', {id: product.productId}]"> {{product.productName}} </a> </td>
اکنون که از دایرکتیو جدید routerLink در این قالب استفاده شدهاست، نیاز است تعریف دایرکتیو آنرا به متادیتای کلاس کامپوننت لیست محصولات نیز اضافه کنیم تا AngularJS 2.0 بداند آنرا از کجا باید تامین کند:
import { Component, OnInit } from 'angular2/core'; import { ROUTER_DIRECTIVES } from 'angular2/router'; //same as before … @Component({ //same as before … directives: [StarComponent, ROUTER_DIRECTIVES] })
در ادامه اگر برنامه را اجرا کنید، عنوانهای محصولات، به آدرس نمایش جزئیات آنها لینک شدهاند:
ج) در آخر زمانیکه View نمایش جزئیات محصول فعال میشود، نیاز است این id را از url جاری دریافت کند. به همین جهت فایل product-detail.component.ts را گشوده و تغییرات ذیل را به آن اعمال کنید:
import { Component } from 'angular2/core'; import { RouteParams } from 'angular2/router'; @Component({ templateUrl: 'app/products/product-detail.component.html' }) export class ProductDetailComponent { pageTitle: string = 'Product Detail'; constructor(private _routeParams: RouteParams) { let id = +this._routeParams.get('id'); this.pageTitle += `: ${id}`; } }
در این حالت، id دریافتی، به متغیر pageTitle اضافه شده و در قالب مربوطه به صورت خودکار نمایش داده میشود.
تا اینجا اگر برنامه را اجرا کنید، صفحهی نمایش جزئیات یک محصول، با کلیک بر روی عناوین آنها به صورت زیر نمایش داده میشود:
افزودن دکمهی back با کدنویسی
اکنون برای بازگشت مجدد به لیست محصولات، میتوان از دکمهی back مرورگر استفاده کرد، اما امکان طراحی این دکمه در قالبها نیز پیش بینی شدهاست.
برای این منظور قالب product-detail.component.html را به نحو ذیل بازنویسی میکنیم:
<div class='panel panel-primary'> <div class='panel-heading'> {{pageTitle}} </div> <div class='panel-footer'> <a class='btn btn-default' (click)='onBack()' style='width:80px'> <i class='glyphicon glyphicon-chevron-left'></i> Back </a> </div> </div>
سپس کدهای product-detail.component.ts را به صورت ذیل تکمیل خواهیم کرد:
import { Component } from 'angular2/core'; import { RouteParams, Router } from 'angular2/router'; @Component({ templateUrl: 'app/products/product-detail.component.html' }) export class ProductDetailComponent { pageTitle: string = 'Product Detail'; constructor(private _routeParams: RouteParams, private _router: Router) { let id = +this._routeParams.get('id'); this.pageTitle += `: ${id}`; } onBack(): void { this._router.navigate(['Products']); } }
رفع تداخل مسیریابیهای ASP.NET MVC با مسیریابیهای AngularJS 2.0
در طی بحث جاری عنوان شد که اگر کاربر مسیر http://localhost:2222/product/2 را جایی ثبت کرده یا bookmark کند، پس از فراخوانی مستقیم آن در نوار آدرسهای مرورگر، بلافاصله به این آدرس هدایت خواهد شد. این مورد صحیح است اگر از index.html بجای بکارگیری ASP.NET MVC، جهت هاست برنامه استفاده شود. اگر چنین آدرسی را در یک برنامهی ASP.NET MVC فراخوانی کنیم، ابتدا به دنبال کنترلری به نام product میگردد (ابتدا وارد موتور ASP.NET MVC میشود) و چون این کنترلر در سمت سرور تعریف نشدهاست، پیام 404 و یا یافت نشد را مشاهده خواهید کرد و فرصت به اجرای برنامهی AngularJS نخواهد رسید.
برای حل این مشکل نیاز است یک route جدید را به نام catch all، در انتهای مسیریابیهای فعلی اضافه کنید؛ تا سایر درخواستهای رسیده را به صفحهی نمایش برنامهی AngularJS هدایت کند:
public class RouteConfig { public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapRoute( name: "Default", url: "{controller}/{action}/{id}", defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }, constraints: new { controller = "Home" } // for catch all to work, Home|About|SomeName ); // Route override to work with Angularjs and HTML5 routing routes.MapRoute( name: "NotFound", url: "{*catchall}", defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } ); } }
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: MVC5Angular2.part9.zip
خلاصهی بحث
حین ایجاد کامپوننتها باید به نحوهی نمایش آنها نیز فکر کرد. اگر کامپوننتی قرار است داخل یک کامپوننت دیگر نمایش یابد، باید دارای selector باشد. یک چنین کامپوننتی نیاز به تعریف مسیریابی ندارد. برای کامپوننتهایی که به عنوان یک View مستقل طراحی میشوند و قرار است در یک صفحهی مجزا نمایش داده شوند، نیازی به تعریف selector نیست؛ اما باید برای آنها مسیریابیهای ویژهای را تعریف کرد. همچنین نیاز است مدیریت اعمال کاربران را جهت فعال سازی آنها نیز مدنظر داشت. برای استفاده از امکانات مسیریابی توکار AngularJS 2.0 نیاز است اسکریپت آنرا به صفحهی اصلی اضافه کرد. سپس باید المان base را جهت نحوهی تشکیل آدرسهای مسیریابی، به صفحه اضافه کرد. در ادامه کار ثبت ROUTER_PROVIDERS در بالاترین سطح سلسه مراتب کامپوننتهای سایت انجام میشود. با استفاده از RouteConfig کار تنظیمات ابتدایی مسیریابی صورت خواهد گرفت. این decorator به کامپوننتی که قرار است کار میزبانی مسیریابی را انجام دهد، متصل میشود. پس از تعریف مسیریابیها با ذکر یک نام منحصربفرد، مسیری که باید توسط کاربر وارد شود و نام کامپوننت مدنظر، با استفاده از دایرکتیو routerLink کار تعریف این آدرسها، در رابط کاربری برنامه انجام میشود. این دایرکتیو جدید، جزئی از مجموعهی ROUTER_DIRECTIVES است که باید به لیست دایرکتیوهای کامپوننت ریشههای سایت و هر کامپوننتی که از routeLink استفاده میکند، اضافه شود. برای بایند این دایرکتیو به مسیریابیهای تعریف شده، سمت راست این اتصال باید به آرایهای از مقادیر مقدار دهی شود که اولین عنصر آن، نام یکی از عناصر مسیریابی تعریف شدهی در RouteConfig است. از دومین عنصر آن برای مقدار دهی پارامترهای ارسالی به سیستم مسیریابی استفاده میشود. کار دایرکتیو router-outlet، مشخص سازی محل نمایش یک View است که عموما در قالب میزبان مسیریابی قرار میگیرد. برای تعیین پارامترهای مسیریابی، از الگوی paramName:/ استفاده میشود. برای دسترسی به این مقادیر در یک کامپوننت، میتوان از سرویس RouteParams استفاده کرد. برای فعال سازی یک مسیریابی با کدنویسی، از سرویس Router و متد navigate آن کمک میگیریم.
C:\Windows\System32\inetsrv\config\applicationHost.config
جدول خصوصیت ها
خصوصیت | توضیح |
customLogPluginClsid | یک پارامتر رشتهای اختیاری که در آن، آی دی کلاس یا کلاسهایی نوشته میشود که برای custom logging نوشته شدهاند و این گزینه ترتیب اجرای آنها را تعیین میکند. |
directory | اختیاری است. محل ذخیرهی لاگ فایلها را مشخص میکند و در صورتیکه ذکر نشود، همان مسیر پیش فرض است. |
enabled | اختیاری است. فعال بودن سیستم لاگ برای آن سایت را مشخص میکند. مقدار پیش فرض آن true است. |
flushByEntryCountW3CLog | این مقدار مشخص میکند چند رخداد باید اتفاق بیفتد تا عمل ذخیره سازی لاگ صورت گیرد. اگر بعد از هر رخداد عمل ثبت لاگ انجام شود، سرعت ثبت لاگها بالا میرود؛ ولی باعث استفادهی مداوم از منابع و همچنین درخواست ثبت اطلاعات را روی دیسک خواهد داد و تاوان آن با زیاد شدن عملیات روی دیسک، پرداخته خواهد شد. ولی در حالتیکه چند رخداد را نگهداری سپس دستهای ثبت کند، باعث افزایش کارآیی و راندمان سرور خواهد شد. در صورتیکه سرور به مشکلات لحظهای برخورد میکند مقدار آن را کاهش دهید. مقدار پیش فرض 0 است. یعنی اینکه ثبت، بعد از 64000 لاگ خواهد بود. |
localTimeRollover | نحوهی نامگذاری فایلهای لاگ را مشخص میکند که مقدار بولین گرفته و اختیاری است. به طور پیش فرض مقدار false دارد. |
logExtFileFlags | این گزینه در حالتی به کارتان میآید که فرمت W3C را برای ثبت لاگها انتخاب کرده باشید و در اینجا مشخص میکنید که چه فیلدهایی باید در لاگ باشند و اگر بیش از یکی بود میتوان با ، (کاما) از هم جدایشان کرد. |
logFormat | نوع فرمت ذخیره سازی لاگها |
logSiteId | اختیاری است و مقدار پیش فرض آن true است. بدین معنا که کد یا شمارهی سایت هم در لاگ خواهد بود و این در حالتی است که گزارش در سطح سرور باشد. در غیر این صورت اگر هر سایت، جداگانه لاگی برای خود داشته باشد، ذکر نمیگردد. |
logTargetW3C | اختیاری است و و مقدار file و *ETW را میگیرد که به طور پیش فرض روی File تنظیم است. در این حالت فایل لاگها در یک فایل متنی توسط http.sys ذخیره میشود. ولی موقعیکه از ETW استفاده میشود، http.sys با استفاده از iislogprovider دادهها را به سمت ETW ارسال میکند که منجر به اجرای سرویس Logsvc شده که از دادهها کوئری گرفته و آنها را مستقیما از پروسههای کارگر جمع آوری و به سمت فایل لاگ ارسال میکند. همچنین انتخاب این دو گزینه نیز ممکن است. |
maxLogLineLength | حداکثر تعداد خطی که یک لاگ میتواند داشته باشد تا اینکه بتوانید در مصرف دیسک سخت صرفه جویی کنید و بیشتر کاربرد آن برای لاگهای کاستوم است. این عدد باید از نوع Uint باشد و اختیاری است و از 2 تا 65536 مقدار میپذیرد که مقدار پیش فرض آن 65536 میباشد. |
period | همان مبحث زمان بندی در مورد ایجاد فایلهای لاگ است که در مقالهی پیشین برسی کردیم و مقادیر Dialy,Hourly,monthlyو weekly را میپذیرد. همچنین maxsize هم هست؛ موقعی که لاگ به نهایت حجمی که برای آن تعیین کردیم میرسد. |
truncateSize | اختیاری است و مقدار آن از نوع int64 است. حداکثر حجم یک فایل لاگ را مشخص میکند تا اگر period روی maxsize تنظیم شده بود، حداکثر حجم را میتوان از اینجا تعیین نمود. در مقاله پیشین در این باره صحبت کردیم؛ حداقل عدد برای آن 1,048,576 است و اگر کمتر از آن بنویسید، سیستم همین عدد 1,048,576 را در نظر خواهد گرفت. مقدار پیش فرض آن 20971520 می باشد. |
<system.applicationHost> <sites> <siteDefaults> <logFile logFormat="W3C" directory="%SystemDrive%\inetpub\logs\LogFiles" enabled="true"> <customFields> <clear/> <add logFieldName="ContosoField" sourceName="ContosoSource" sourceType="ServerVariable" /> </customFields> </logFile> </siteDefaults> </sites> </system.applicationHost>
تغییر تنظمیات لاگ با Appcmd
appcmd.exe set config -section:system.applicationHost/sites /siteDefaults.logFile.enabled:"True" /commit:apphost appcmd.exe set config -section:system.applicationHost/sites /siteDefaults.logFile.logFormat:"W3C" /commit:apphost appcmd.exe set config -section:system.applicationHost/sites /siteDefaults.logFile.directory:"%SystemDrive%\inetpub\logs\LogFiles" /commit:apphost
تنظمیات تگ لاگ با برنامه نویسی و اسکریپت نویسی
using System; using System.Text; using Microsoft.Web.Administration; internal static class Sample { private static void Main() { using (ServerManager serverManager = new ServerManager()) { Configuration config = serverManager.GetApplicationHostConfiguration(); ConfigurationSection sitesSection = config.GetSection("system.applicationHost/sites"); ConfigurationElement siteDefaultsElement = sitesSection.GetChildElement("siteDefaults"); ConfigurationElement logFileElement = siteDefaultsElement.GetChildElement("logFile"); logFileElement["logFormat"] = @"W3C"; logFileElement["directory"] = @"%SystemDrive%\inetpub\logs\LogFiles"; logFileElement["enabled"] = true; serverManager.CommitChanges(); } } }
با استفاده از اسکریپت نویسی توسط جاوااسکریپت و وی بی اسکریپت هم نیز این امکان مهیاست:
var adminManager = new ActiveXObject('Microsoft.ApplicationHost.WritableAdminManager'); adminManager.CommitPath = "MACHINE/WEBROOT/APPHOST"; var sitesSection = adminManager.GetAdminSection("system.applicationHost/sites", "MACHINE/WEBROOT/APPHOST"); var siteDefaultsElement = sitesSection.ChildElements.Item("siteDefaults"); var logFileElement = siteDefaultsElement.ChildElements.Item("logFile"); logFileElement.Properties.Item("logFormat").Value = "W3C"; logFileElement.Properties.Item("directory").Value = "%SystemDrive%\\inetpub\\logs\\LogFiles"; logFileElement.Properties.Item("enabled").Value = true; adminManager.CommitChanges();
FTP Logging
IIS را باز کنید و در لیست درختی، سرور را انتخاب کنید. در قسمت FTP میتوانید گزینهی Ftp logging را ببینید. تنظیمات این قسمت هم دقیقا همانند قسمت logging میباشد و همان موارد برای آن هم صدق میکند.
بررسی تگ آن در applicationhost
تگ این نوع لاگ در فایل applicationhost در زیر مجموعهی تگ <site> به شکل زیر نوشته میشود:
<system.ftpServer> <log centralLogFileMode="Central"> <centralLogFile enabled="true" /> </log> </system.ftpServer>
گزینه centralLogFileMode دو مقدار central و site را میپذیرد. اگر گزینهی central انتخاب شود، یعنی همهی لاگها را داخل یک فایل در سطح سرور ثبت کن ولی اگر گزینهی site انتخاب شده باشد، لاگ هر سایت در یک فایل ثبت خواهد شد.
گزینهی logInUTF8 یک خصوصیت اختیاری است که مقدار پیش فرض آن true میباشد. در این حالت باید تمامی رشتهها به انکدینگ UTF-8 تبدیل شوند.
همانطور که میبینید تگ log در بالا یک تگ فرزند هم به اسم centralLogFile دارد که همان خصوصیات جدول بالا در آن مهیاست.
دسترسی به تنظیمات این قسمت توسط دستور Appcmd:
appcmd.exe set config -section:system.ftpServer/log /centralLogFileMode:"Central" /commit:apphost appcmd.exe set config -section:system.ftpServer/log /centralLogFile.enabled:"True" /commit:apphost
دسترسی به تنظیمات این قسمت توسط دات نت:
using System; using System.Text; using Microsoft.Web.Administration; internal static class Sample { private static void Main() { using (ServerManager serverManager = new ServerManager()) { Configuration config = serverManager.GetApplicationHostConfiguration(); ConfigurationSection logSection = config.GetSection("system.ftpServer/log"); logSection["centralLogFileMode"] = @"Central"; ConfigurationElement centralLogFileElement = logSection.GetChildElement("centralLogFile"); centralLogFileElement["enabled"] = true; serverManager.CommitChanges(); } } }
دسترسی به تنظیمات این قسمت توسط Javascript:
var adminManager = new ActiveXObject('Microsoft.ApplicationHost.WritableAdminManager'); adminManager.CommitPath = "MACHINE/WEBROOT/APPHOST"; var logSection = adminManager.GetAdminSection("system.ftpServer/log", "MACHINE/WEBROOT/APPHOST"); logSection.Properties.Item("centralLogFileMode").Value = "Central"; var centralLogFileElement = logSection.ChildElements.Item("centralLogFile"); centralLogFileElement.Properties.Item("enabled").Value = true; adminManager.CommitChanges();
همانطور که در قسمت اول گفته شد، اجزای رابط کاربری (تگهای HTML) در کتابخانهی React به عنوان کامپوننتها (مؤلفههای جزء) شناخته میشوند. React تگها را به عنوان اجزایی مستقل و با وضعیتی مشخص در حافظه میشناسد. دلایل ارزشمند بودن این روش در ادامه بررسی میشود.
خوانایی بهتر (Readability)
React میتواند تگهای یگانه یا مخلوطی از تگهای به هم مرتبط را در پس زمینه ساخته و با یک نام واحد (کامپوننت) به HTML DOM ارسال کند. یعنی اگر جایی یک کامپوننت صدا زده شود، تگ یا تگهای مرتبط به آن کامپوننت را به عنوان خروجی خواهیم داشت. همانطور که میشود تگهای مختلف را به صورت تو در تو استفاده کرد، کامپوننتها را هم میشود به همین روش فراخوانی کرد. در مثال زیر روش صدا زدن چند کامپوننت و تگهایی را که ارائه میدهد، داریم.
// Components in a JavaScript file. <clickableImage href="http://google.com" src="google.png" /> <LinksContainer> <LinksList> <clickableImage href="http://yahoo.com" src="yahoo.png" /> </LinksList> </LinksContainer> <!--Output in HTML DOM--> <a href="http://google.com"> <img src="google.png" /> </a> <div> <div> <ul> <li> <a href="http://google.com"> <img src="google.png" /> </a> </li> </ul> </div> </div>
در قسمت کامپوننتها میبینیم که چطور کامپوننتها یکبار به صورت تکی و یک بار به صورت تو در تو اجرا میشوند. خروجی در قسمت Output واضح است که با نام کامپوننتها هماهنگی دارد. با این مثال چند مورد مشخص میشود.
- به هر کامپوننت قبلا گفته شده چه تگهایی را باید ایجاد کند. در نتیجه با هر بار فراخوانی در هر مکان، تگ یا تگهایی که به آن معرفی شده را میسازد.
- هر کامپوننت میتواند مقادیری را به عنوان ورودی دریافت کند و آنها را به تگها در خروجی اعمال کند. در مثال بالا href و src در فراخوانیهای مختلف، مقادیر متفاوتی را به خروجی میفرستند.
- با انتخاب نام مناسب برای کامپوننتها، بدون آنکه بدانیم چطور ساخته شدهاند میتوانیم حدس بزنیم چه تگهایی را خواهند ساخت و این دلیلی است که خوانایی برنامه افزایش میابد.
- دلیل دیگر که باعث خوانایی برنامه میشود، این است که هر یک از این کامپوننتها میتوانند تگهای زیادی را یک جا بسازند که این کار منجر به کم شدن مقدار کد برنامه میشود. برنامه هر چه کم کدتر، با خوانایی بیشتر!
قابلیت استفاده مجدد
در ادامه وقتی با روش ساخت کامپوننتها آشنا شدیم، متوجه میشویم که کامپوننتها چیزی بیشتر از یک تابع نیستند. وقتی نام یک کامپوننت را فراخوانی کنیم در واقع یک تابع را اجرا میکنیم، به آن پارامتر ورودی را میدهیم و از آن خروجی میگیریم. میدانیم که توابع را میشود یکبار ساخت و چندبار استفاده کرد. بخصوص اگر این توابع به متغیرهای سراسری و سایر توابع وابسته نباشند و به صورت مستقل عمل کنند، میشود آنها را به برنامههای دیگر هم انتقال داد.
نحوه ساخت یک کامپوننت در React
در React به سه روش میشود کامپوننتها را ایجاد کرد. در روش اول توضیحات زیاد خواهند بود، اما در دو روش بعدی فقط نکات کلیدی گفته خواهد شد.
Stateless function components
میخواهیم یک منو از نوشیدنیها را با استفاده از کامپوننتها نمایش دهیم. در یک فایل جاوااسکریپت کدهای زیر را وارد کنید. در ادامه هر بخش توضیح داده خواهد شد.
var hotDrinks = [ { item: "Tea", price: "7000" }, { item: "Espresso", price: "10000" }, { item: "Hot Chocolate", price: "12000" } ]; var MenuItem = function (props) { return ( <li className="list-group-item"> <span className="badge">{props.price}</span> <p>{props.item}</p> </li> ) }; var Menu = function (props) { return ( <div className="row"> <div className="col-md-4"> <ul className="list-group"> {props.data.map(item => <MenuItem {...item} />)} </ul> </div> </div> ) }; ReactDOM.render( <Menu data={hotDrinks} />, document.getElementById("reactTestContainer") )
- فرض میکنیم که لیست نوشیدنیها و قیمت آنها را به فرمتی که میبینید از سرور دریافت کردهایم. (hotDrinks)
- شیء MenuItem یک تابع بدون نام را اجرا میکند. از دیدگاه React این تابع یک کامپوننت است. کامپوننت با هر بار فراخوانی مقادیری را برای یک نوشیدنی و قیمت آن، دریافت میکند.کامپوننت به عنوان خروجی یک تگ <li>، پر شده با مقادیر ورودی را بازگشت میدهد.
- شیء Menu یک تابع بدون نام را اجرا میکند. از دید React این تابع یک کامپوننت است. کامپوننت با هر بار فراخوانی، مجموعهای از نوشیدنیها و قیمت آنها را دریافت میکند. متد map به کمک یک Arrow Function آرایهای از کامپوننت MenuItem ایجاد میکند که به ازای هر عضو ایجاد شده، یکبار MenuItem اجرا میشود. هر عضو (item) دارای یک نام نوشیدنی و قیمت آن است. سه نقطه در {…item} برای پر کردن جای خالی نیست! این عبارت یعنی اینکه مقادیر نام و قیمت را به صورت جداگانه (یعنی دو پارامتر مجزا) به کامپوننت MenuItem ارسال میکند. کامپوننت، به عنوان خروجی یک تگ <ul>، پر شده با آرایهای از کامپوننت MenuItem را بازگشت میدهد.
- متد render از شیء ReactDOM وظیفه ساخت تگهای JSX واقع در کامپوننتها را در HTML DOM به عهده دارد. پارامتر اول render، کامپوننت Menu است با ورودی دادههای گرفته شده از سرور. همانطور که شرح داده شد، کامپوننت Menu با فراخوانی و به کمک دادههای ورودی، کامپوننت MenuItem را پیادهسازی خواهد کرد. پارامتر دوم render، محلی است که تگها باید در آن ساخته شوند. مثلا یک تگ <div>
- در هر کدام از کامپوننتها و در قسمت ReactDOM.render میشود از کامپوننتهای دیگر به صورت تو در تو استفاده کرد.
React.createClass
React یک API درونی برای ایجاد کامپوننتها، به نام createClass دارد. این تابع باید یک شیء پیکربندی درون خود داشته باشد که در آن و بین دو آکولاد {} خواص و متدها تعریف میشوند. تابع createClass برای کار حداقل باید یک متد به نام render داشته باشد که در آن تگهای JSX را قرار میدهیم. کامپوننت MenuItem را که به صورت Stateless ساختیم، دوباره با createClass ایجاد میکنیم.
var MenuItem = React.createClass({ render: function () { return ( <li className="list-group-item"> <span className="badge">{this.props.price}</span> <p>{this.props.item}</p> </li> ) } });
برای خواندن مقادیر ورودی در این روش باید از this استفاده کنیم. بر اساس قواعد شیء گراییِ، MenuItem و Menu کلاس هستند و هر بار در ReactDOM.render کامپوننت Menu را به HTML DOM ارسال میکنیم. یک نمونه از این کلاس ساخته میشود و کلاس Menu، نمونههایی از کلاس MenuItem را میسازد. this به نمونهی ساخته شده از یک کلاس اشاره دارد.
React.Component
در روش آخر با استفاده از extend، از کلاس React.Component ارث بری میکنیم و کامپوننت را میسازیم. مفاهیم کلاس و ارث بری در جاوااسکریپ را میشود از اینجا یاد گرفت. مجددا MenuItem را با این روش ایجاد میکنیم.
class MenuItem extends React.Component { render() { return ( <li className="list-group-item"> <span className="badge">{this.props.price}</span> <p>{this.props.item}</p> </li> ); } }
همانطور که میبینید بین دو روش React.Component و React.createClass تفاوتی جز در syntax آنها نیست. در اینجا از سایر امکانات کلاس در جاوااسکریپت مثل سازنده کلاس میشود استفاده کرد. کامپوننتها در React میتوانند کاری بیشتر از ساخت تگها در HTML DOM را انجام دهند. در قسمت بعد به قابلیت مهم حفظ و دنبال کردن تغییرات در وضعیت کامپوننتها میپردازیم.
<Import Project="$(MSBuildExtensionsPath)\Microsoft\MicrosoftAjax\ajaxmin.tasks" /> <Target Name="AfterBuild"> <ItemGroup> <JS Include="**\*.js" Exclude="**\*.min.js;Scripts\*.js" /> </ItemGroup> <ItemGroup> <CSS Include="**\*.css" Exclude="**\*.min.css" /> </ItemGroup> <AjaxMin JsSourceFiles="@(JS)" JsSourceExtensionPattern="\.js$" JsTargetExtension=".min.js" CssSourceFiles="@(CSS)" CssSourceExtensionPattern="\.css$" CssTargetExtension=".min.css" /> </Target>
موقعی که از کاربر یک عکس رو دریافت میکنیم نیازه که بررسی شه که آیا این فایل واقعا عکسه یا نه.
برای کنترل این موضوع Attributeهای اعتبارسنجی بهترین انتخاب هستن و با یکبار افزودن میتوان از آنها در هر جایی استفاده کرد.
سمت سرور اینکار به این صورت هست ^
اما سمت کلاینت حتی اگه بیایم فرمت فایل رو هم بررسی کنیم بازهم جوابگو نیست و میشه به راحتی فرمت یک فایل ZIP رو به PNG تغییر داد و ارسالش کرد و عملا اعتبارسنجی سمت کلاینت کار خاصی رو انجام نمیده.
در این پروژه Attribute اعتبارسنجی برای بررسی فایل که آیا واقعا عکس هست یا نه نوشته شده، هم سمت کلاینت و هم سمت سرور.
نحوه انجام کار: برای سمت کلاینت عکسی که کاربر وارد میکنه رو در داخل تگ IMG نمایش میده و اگه عکس به درستی لود بشه رخداد OnLoad تگ IMG فراخوانی میشه و اعتبارسنجی پاس میشه.
تگ IMG به کاربر نمایش داده نمیشه و به صورت display: none هستش.
از این Attribute اعتبارسنجی در کنار Attributeهای کنترل حجم فایل هم میشه استفاده کرد که قبل از اینکه بخوایم بررسی کنیم که آیا فایل واقعا عکسه یا نه ابتدا حجم فایل مورد بررسی قرار بگیره.
آموزش Knockout.Js #2
»Observable(قابل مشاهده کردن تغییرات)
KO از Observable برای ردیابی و مشاهده تغییرات خواص ViewModel استفاده میکند. در واقع Observable دقیقا شبیه به متغیرها در JavaScript عمل میکنند با این تفاوت که به KO اجازه میدهند که تغییرات این خواص را پیگیری کند و این تغییرات را به بخشهای مرتبط View اعمال نماید. اما سوال این است که KO چگونه متوجه میشود که این تغییرات بر کدام قسمت در View تاثیر خواهند داشت؟ جواب این سوال در مفهوم Binding است.
»Binding
برای اتصال بخشهای مختلف View به Observableها باید از binding(مقید سازی) استفاده کنیم. بدون عملیات binding، امکان اعمال تغییرات Observableها بر روی عناصر HTML امکان پذیر نیست.
برای مثال در شکل زیر یکی از خواص ViewModel را به View متناظر مقید شده است.
با کمی دقت در شکل بالا این نکته به دست میآید که میتوان در یک ViewModel، فقط خواص مورد نظر را به عناصر Html مقید کرد.
دانلود فایلهای مورد نیاز
فایلهای مورد نیاز برای KO رو میتوانید از اینجا
دانلود نمایید و به پروژه اضافه کنید. به صورت پیش فرض فایلهای مورد
نیاز KO، در پروژههای MVC 4 وجود دارد و نیاز به دانلود آنها نیست و شما
باید فقط مراحل BundleConfig را انجام دهید.
تعریف ViewModel
برای تعریف ViewModel و پیاده سازی مراحل Observable و binding باید به صورت زیر عمل نمایید:
<html lang='en'> <head> <title>Hello, Knockout.js</title> <meta charset='utf-8' /> <link rel='stylesheet' href='style.css' /> </head> <body> <h1>Hello, Knockout.js</h1> <script type='text/javascript' src='knockout-2.1.0.js'> <script type='text/javascript'> var personViewModel = { firstName: "Masoud", lastName: "Pakdel" }; ko.applyBindings(personViewModel); </script> </script> </body> </html>
مقید سازی عناصر HTML
برای مقید سازی عناصر HTML به ViewModelها باید از data-bind attribute استفاده نماییم. برای مثال:
<p><span data-bind='text: firstName'></span>'s Shopping Cart</p>
چگونه خواص را Observable کنیم
در پروژههای WPF، فقط در صورتی تغییرات خواص یک کلاس ردیابی میشوند که اولا کلاس اینترفیس INotifyPropertyChanged را پیاده سازی کرده باشد ثانیا، در متد set این خواص، متد OnPropertyChanged(البته این متد میتواند هر نام دیگری نیز داشته باشد) صدا زده شده باشد. نکته مهم و اساسی در KO نیز همین است که برای اینکه KO بتواند تغییرات هر خاصیت را مشاهده کند حتما خواص مورد نظر باید Observable شوند. برای این کار کافیست به صورت عمل کنید:
var personViewModel = { firstName: ko.observable("Masoud"), lastName: ko.observable("Pakdel") };
پیاده سازی متدهای get و set
personViewModel.firstName() // Get personViewModel.firstName("Masoud") // Set
function PersonViewModel() { this.firstName = ko.observable("Masoud"); this.lastName = ko.observable("Pakdel"); this.clickMe= function() { alert("this is test!"); }; };
<button data-bind='click: clickMe'>Click Me...</button>
پس به صورت خلاصه:
- ابتدا ViewModel مورد نظر را ایجاد نمایید؛
- سپس با استفاده از data-bind عملیات مقید سازی بین View و ViewModel را انجام دهید
- در نهایت با استفاده از Obsevable تغییرات خواص مورد نظر را ردیابی نمایید.
ادامه دارد...