$(function () { $(window).scroll(function () { var aTop = $(document).height() - $(window).scrollTop() - $(window).height(); if (aTop == 0) { alert("ok"); } }); });
ECMAScript 5
در حالیکه ECMAScript 5 قابلیتهای فوق العادهای را برای کنترل کردن خصوصیات موجود در اشیاء، در اختیار شما قرار میدهد، اما هیچ راه کاری را برای خصوصیاتی که موجود نیستند، ندارد. شما میتوانید برای خواص موجود، از رونویسی (تنظیم writable برابر false) و یا حذف شدن (تنظیم configurable برابر false) جلوگیری کنید. شما میتوانید از اختصاص خصوصیات جدید به اشیاء با استفاده از ()Object.preventExtensions و یا تنظیم تمام خصوصیات به صورت فقط خواندنی و یا غیرقابل حذف ()Object.freeze جلوگیری کنید.
اگر شما نمیخواهید تمام خصوصیات را فقط خواندنی کنید میتوانید از ()Object.seal استفاده کنید. اینها مانع از اضافه کردن خصوصیات و یا حذف کردن خصوصیات موجود میشوند. اگر به یک شیء مهر و موم شده (sealed)، زمانی که از strict mode استفاده میکنید، یک خصوصیت جدید اضافه کنید باعث ایجاد خطا میشود:
"use strict"; var person = { name: "Vahid Mohammad Taheri" }; Object.seal(person); person.age = 27; // Error!
نجات با Proxyها
پروکسیها، دارای سابقه طولانی و پیچیده ای در ECMAScript 6 است. طرح اولیه آن توسط Firefox و Chrome قبل از تصمیم TC-39 به تغییر پروکسیها، اجرا شده است. این تغییرات، برای بهتر و روانتر شدن پروکسیها از طرح اولیه پروکسیها انجام گرفت.
بزرگترین تغییر صورت گرفته، معرفی شیء هدف که میخواست با پروکسی در تعامل باشد، بود. به جای تعریف دام برای نوعهای مختلفی از عملیات، با ایجاد پروکسی، مستقیما برای عملیاتی از قبل تعیین شده بر روی اشیاء هدف، این کار را انجام میدهیم.
این کار از طریق یک سری از روشهایی که به مخفی کردن عملیات در ECMAScript مطابقت دارند، انجام میشود. به عنوان مثال زمانیکه بر روی یک ویژگی از یک شیء، عمل خواندن انجام میشود، عملیات [[Get]]
در موتور جاوااسکریپت انجام میگیرد. نحوهی رفتار [[Get]]
را نمیتوان تغییر داد؛ با این حال، با استفاده از پروکسیها میتوان دامی برای زمان فراخوانی [[Get]]
قرار داد و عملیات خاص مورد نظر خود را اعمال کرد. به مثال زیر توجه کنید:
var proxy = new Proxy({ name: "Vahid" }, { get: function(target, property) { if (property in target) { return target[property]; } else { return 13; } } }); console.log(proxy.time); // 13 console.log(proxy.name); // "Vahid" console.log(proxy.title); // 13
[[Get]]
به دام افتاده و تابع تعریف شدهی ما اجرا میشود (باقی عملیات به صورت عادی اجرا میشوند). دامی که برای شیء مورد نظر تعریف کردهایم دو پارامتر دریافت میکند (اول شی هدف، دوم ویژگی مورد نظر). با استفاده از کد نوشته شده در آن ابتدا بررسی میشود که شیء مورد نظر دارای ویژگی ارسال شده است یا خیر؟ در صورتی که وجود داشته باشد، مقدار آن بازگشت داده میشود و در غیر اینصورت به صورت ثابت مقدار 13 برگشت داده میشود.برای ایجاد اشیاء دفاعی لازم است چگونگی رهگیری عملیات
[[Get]]
را درک کنید و هدف از این کار صدور خطا در زمان دستیابی به ویژگی ای از شیءایی که وجود ندارد است.function createDefensiveObject(target) { return new Proxy(target, { get: function(target, property) { if (property in target) { return target[property]; } else { throw new ReferenceError("Property \"" + property + "\" does not exist."); } } }); }
تابع
()createDefensiveObject
یک شیء را به عنوان هدف میپذیرد و یک شیء دفاعی برای آن ایجاد میکند. پروکسی یک دام به نام get دارد؛ برای زمانی که عمل خواندن انجام میشود. اگر ویژگی خوانده شده در شیء وجود داشت، مقدار آن برگشت داده میشود و از سوی دیگر، وقتی ویژگی خوانده شده در شیء وجود نداشته باشد، سبب بروز خطا میشود. به مثال زیر توجه کنید:var person = { name: "Vahid" }; var defensivePerson = createDefensiveObject(person); console.log(defensivePerson.name); // "Vahid" console.log(defensivePerson.age); // Error!
اشیاء دفاعی باعث میشوند تا بر روی ویژگیهایی که در شیء وجود دارند، بتوان عمل خواندن را انجام داد و در ویژگیهایی که موجود نیستند در هنگام خواندن، باعث صدور پیام خطا میشوند. با این حال هنوز هم شما میتوانید ویژگیهای جدید را بدون خطا اضافه کنید:
var person = { name: "Vahid" }; var defensivePerson = createDefensiveObject(person); console.log(defensivePerson.name); // "Vahid" defensivePerson.age = 13; console.log(defensivePerson.age); // 13
روشهای تشخیص ویژگیهای استاندارد هنوز هم به طور معمول و بدون خطا کار میکنند.
var person = { name: "Vahid" }; var defensivePerson = createDefensiveObject(person); console.log("name" in defensivePerson); // true console.log(defensivePerson.hasOwnProperty("name")); // true console.log("age" in defensivePerson); // false console.log(defensivePerson.hasOwnProperty("age")); // false
var person = { name: "Vahid" }; Object.preventExtensions(person); var defensivePerson = createDefensiveObject(person); defensivePerson.age = 27; // Error! console.log(defensivePerson.age); // Error!
defensivePerson
برای هر دو حالت خواندن و نوشتن ویژگیهایی که وجود ندارند، خطا صادر میکند.شاید مفیدترین زمان برای استفاده از اشیاء دفاعی، در هنگام تعریف یک سازنده باشد و شما میتوانید این کار را به عنوان یک قرارداد در نوشتن اشیاء حفظ کنید.
برای مثال:
function Person(name) { this.name = name; return createDefensiveObject(this); } var person = new Person("Vahid"); console.log(person.age); // Error!
()createDefensiveObject
درون سازنده، میتوانید اطمینان کامل داشته باشید که همهی نمونههای ساخته شدهی از شیء Person، دارای حالت دفاعی میباشند.- می خواهیم کد هایی با قابلیت استفادهی مجدد بنویسیم ، استفاده از عملکردهای مشابه در سطح صفحات یک Web application یا چند Web Application.
- می خواهیم کد هایی با قابلیت نگهداری بنویسیم ، هر چه قدر در فاز توسعه کدهای با کیفیت بنویسیم در فاز نگهداری از آن بهره میبریم. باید کد هایی بنویسیم که قابل Debug و خواندن توسط دیگر افراد تیم باشند.
- کدهای ما نباید با توابع و متغیرهای دیگر پلاگینها تداخل نامگزاری داشته باشند. در برنامههای امروزی بسیار مرسوم است که از پلاگینهای Third party استفاده شود. میخواهیم با رعایت Encapsulation and modularization در کدهایمان از این تداخل جلوگیری کنیم.
function getDate() { var now = new Date(); var utc = now.getTime() + (now.getTimezoneOffset() * 60000); var est; est = new Date(utc + (3600000 * -4)); return dateFormat(est, "dddd, mmmm dS, yyyy, h:MM:ss TT") + " EST"; } function initiate_geolocationToTextbox() { navigator.geolocation.getCurrentPosition(handle_geolocation_queryToTextBox); } function handle_geolocation_queryToTextBox(position) { var longitude = position.coords.longitude; var latitude = position.coords.latitude; $("#IncidentLocation").val(latitude + " " + longitude); }
- توابع و متغیرها به Global scope برنامه افزوده میشوند.
- کد Modular نیست.
- احتمال رخ دادن Conflict در اسامی متغیرها و توابع بالا میرود.
- نگهداری کد به مرور زمان سخت میشود.
// file1.js function saveState(obj) { // write code here to saveState of some object alert('file1 saveState'); } // file2.js (remote team or some third party scripts) function saveState(obj, obj2) { // further code... alert('file2 saveState"); }
<script src="file1.js" type="text/javascript"></script> <script src="file2.js" type="text/javascript"></script>
تولید اعداد تصادفی در جاوا اسکریپت
شما نیز با دانستن قواعد ساده کد نویسی برای Text templateها میتوانید از این امکان برای تولید اتوماتیک کلاسها، HTML ، XML و بطور کلی هر نوع فایل متنی استفاده نمایید. کاربردهای عملی Text template بیشتر برای تولید کد از روی دیتابیس و مثلا بر اساس فیلدهای هر جدول و امثال آن میباشد. اما با کمی خلاقیت استفادههای بسیار زیاد و جالبی را میتوانید از آن مشاهده نمایید.
کاربردهایی مانند : تولید خود کار فایل Config، تولید خودکار Unit Test، تولید خودکار کلاسهای CRUD برای هر موجودیت در لایه Data، تولید خودکار SQL برای StoredProcedure ها، تولید خودکار Html و Js، تولید خودکار فرمهای ASPX و ... برای فرمهای ثابت و تکراری با فرآیند مشخص، تولید خودکار مستندات پروژه در قالب Word Document , … ، تولید خودکار فایلهای Excel از اطلاعات خاص و تولید خودکار فرمهای xaml و .....
در این آموزش با قواعد اصلی نوشتن کد برای Text templateها آشنا میشویم و چند نمونه از کاربردهای آن را به عنوان مثال کاربردی مورد بررسی قرار خواهیم داد.
برای شروع قبل از توضیح در مورد قواعد کد نویسی Text template، یک فایل Text template ایجاد نمایید. برای ایجاد، از پنجره Add New Item یک فایل Text template به پروژه اضافه نمایید:
<#@ template debug="false" hostspecific="false" language="C#" #> <#@ output extension=".txt" #>
Extension نیز پسوند فایل خروجی تولید شده را مشخص مینماید .
اگر در ادامه متن Hello dotnettips را بنویسید و فایل را Save کنید، کنار فایل tt مذکور، یک فایل با همان نام و پسوند txt ایجاد خواهد شد که داخل آن نوشته است:
Hello dotnettips
برای ایجاد فایل خروجی همچنین میتوانید روی فایل tt کلیک راست نموده و از منوی باز شده Run Custom Tool را انتخاب نمایید. توجه داشته باشید که در تنظیمات Custom Tool باید مقدار TextTemplatingFileGenerator وجود داشته باشد:
خوب؛ برای ادامه، باید با قواعد کد نویسی در Text template آشنا شوید. جلسه بعدی قواعد کد نویسی T4 را بررسی خواهیم کرد.
استفاده از Ember Data با Local Storage
برای کار با HTML 5 local storage نیاز به Ember Data Local Storage Adapter نیز هست که در قسمت اول این سری، آدرس دریافت آن معرفی شد. این فایلها نیز در پوشهی Scripts\Libs برنامه کپی خواهند شد.
در ادامه به فایل Scripts\App\store.js که در قسمت قبل جهت تعریف دو آرایه ثابت مطالب و نظرات اضافه شد، مراجعه کرده و محتوای فعلی آنرا با کدهای زیر جایگزین کنید:
Blogger.ApplicationSerializer = DS.LSSerializer.extend(); Blogger.ApplicationAdapter = DS.LSAdapter.extend();
در ادامه با توجه به حذف دو آرایهی posts و comments که پیشتر در فایل store.js تعریف شده بودند، نیاز است مدلهای متناظری را جهت تعریف خواص آنها، به برنامه اضافه کنیم. اینکار را با افزودن دو فایل جدید comment.js و post.js به پوشهی Scripts\Models انجام خواهیم داد.
محتوای فایل Scripts\Models\post.js :
Blogger.Post = DS.Model.extend({ title: DS.attr(), body: DS.attr() });
Blogger.Comment = DS.Model.extend({ text: DS.attr() });
<script src="Scripts/Models/post.js" type="text/javascript"></script> <script src="Scripts/Models/comment.js" type="text/javascript"></script>
برای تعاریف مدلها در Ember data مرسوم است که نام مدلها، اسامی جمع نباشند. سپس با ایجاد وهلهای از DS.Model.extend یک مدل ember data را تعریف خواهیم کرد. در این مدل، خواص هر شیء را مشخص کرده و مقدار آنها همیشه ()DS.attr خواهد بود. این نکته را در دو مدل Post و Comment مشاهده میکنید. اگر دقت کنید به هر دو مدل، خاصیت id اضافه نشدهاست. این خاصیت به صورت خودکار توسط Ember data تنظیم میشود.
اکنون نیاز است برنامه را جهت استفاده از این مدلهای جدید به روز کرد. برای این منظور فایل Scripts\Routes\posts.js را گشوده و مدل آنرا به نحو ذیل ویرایش کنید:
Blogger.PostsRoute = Ember.Route.extend({ //controllerName: 'posts', // مقدار پیش فرض است و نیازی به ذکر آن نیست //renderTemplare: function () { // this.render('posts'); // مقدار پیش فرض است و نیازی به ذکر آن نیست //}, model: function () { return this.store.find('post'); } });
به همین ترتیب فایل Scripts\Routes\recent-comments.js را نیز جهت استفاده از data store ویرایش خواهیم کرد:
Blogger.RecentCommentsRoute = Ember.Route.extend({ model: function () { return this.store.find('comment'); } });
Blogger.PostRoute = Ember.Route.extend({ model: function (params) { return this.store.find('post', params.post_id); } });
افزودن امکان ثبت یک مطلب جدید
تا اینجا اگر برنامه را اجرا کنید، برنامه بدون خطا بارگذاری خواهد شد اما فعلا رکوردی را برای نمایش ندارد. در ادامه، برنامه را جهت افزودن مطالب جدید توسعه خواهیم داد. برای اینکار ابتدا به فایل Scripts\App\router.js مراجعه کرده و سپس مسیریابی جدید new-post را تعریف خواهیم کرد:
Blogger.Router.map(function () { this.resource('posts', { path: '/' }); this.resource('about'); this.resource('contact', function () { this.resource('email'); this.resource('phone'); }); this.resource('recent-comments'); this.resource('post', { path: 'posts/:post_id' }); this.resource('new-post'); });
<h2>Ember.js blog</h2> <ul> {{#each post in arrangedContent}} <li>{{#link-to 'post' post.id}}{{post.title}}{{/link-to}}</li> {{/each}} </ul> <a href="#" class="btn btn-primary" {{action 'sortByTitle' }}>Sort by title</a> {{#link-to 'new-post' classNames="btn btn-success"}}New Post{{/link-to}}
برای تعریف عناصر نمایشی این مسیریابی، فایل جدید قالب Scripts\Templates\new-post.hbs را با محتوای زیر اضافه کنید:
<h1>New post</h1> <form> <div class="form-group"> <label for="title">Title</label> {{input value=title id="title" class="form-control"}} </div> <div class="form-group"> <label for="body">Body</label> {{textarea value=body id="body" class="form-control" rows="5"}} </div> <button class="btn btn-primary" {{action 'save'}}>Save</button> </form>
پس از آن نیاز است نام فایل قالب new-post را به template loader برنامه در فایل index.html اضافه کرد:
<script type="text/javascript"> EmberHandlebarsLoader.loadTemplates([ 'posts', 'about', 'application', 'contact', 'email', 'phone', 'recent-comments', 'post', 'new-post' ]); </script>
Blogger.NewPostController = Ember.Controller.extend({ actions: { save: function () { var newPost = this.store.createRecord('post', { title: this.get('title'), body: this.get('body') }); newPost.save(); this.transitionToRoute('posts'); } } });
<script src="Scripts/Controllers/new-post.js" type="text/javascript"></script>
در اینجا کنترلر جدید NewPostController را مشاهده میکنید. از این جهت که برای دسترسی به خواص مدل تغییر کرده، از متد this.get استفاده شدهاست، نیازی نیست حتما از یک ObjectController مانند قسمت قبل استفاده کرد و Controller معمولی نیز برای اینکار کافی است.
آرگومان اول this.store.createRecord نام مدل است و آرگومان دوم آن، وهلهای که قرار است به آن اضافه شود. همچنین باید دقت داشت که برای تنظیم یک خاصیت، از متد this.set و برای دریافت مقدار یک خاصیت تغییر کرده از this.get به همراه نام خاصیت مورد نظر استفاده میشود و نباید مستقیما برای مثال از this.title استفاده کرد.
this.store.createRecord صرفا یک شیء جدید (ember data object) را ایجاد میکند. برای ذخیره سازی نهایی آن باید متد save آنرا فراخوانی کرد (پیاده سازی الگوی active record است). به این ترتیب این شیء در local storage ذخیره خواهد شد.
پس از ذخیرهی مطلب جدید، از متد this.transitionToRoute استفاده شدهاست. این متد، برنامه را به صورت خودکار به صفحهی متناظر با مسیریابی posts هدایت میکند.
اکنون برنامه را اجرا کنید. بر روی دکمهی سبز رنگ new post در صفحهی اول کلیک کرده و یک مطلب جدید را تعریف کنید. بلافاصله عنوان و لینک متناظر با این مطلب را در صفحهی اول سایت مشاهده خواهید کرد.
همچنین اگر برنامه را مجددا بارگذاری کنید، این مطالب هنوز قابل مشاهده هستند؛ زیرا در local storage مرورگر ذخیره شدهاند.
در اینجا اگر به لینکهای تولید شده دقت کنید، id آنها عددی نیست. این روشی است که local storage با آن کار میکند.
افزودن امکان حذف یک مطلب به سایت
برای حذف یک مطلب، دکمهی حذف را به انتهای قالب Scripts\Templates\post.hbs اضافه خواهیم کرد:
<h2>{{title}}</h2> {{#if isEditing}} <form> <div class="form-group"> <label for="title">Title</label> {{input value=title id="title" class="form-control"}} </div> <div class="form-group"> <label for="body">Body</label> {{textarea value=body id="body" class="form-control" rows="5"}} </div> <button class="btn btn-primary" {{action 'save' }}>Save</button> </form> {{else}} <p>{{body}}</p> <button class="btn btn-primary" {{action 'edit' }}>Edit</button> <button class="btn btn-danger" {{action 'delete' }}>Delete</button> {{/if}}
سپس کنترلر Scripts\Controllers\post.js را جهت مدیریت اکشن جدید delete به نحو ذیل تکمیل میکنیم:
Blogger.PostController = Ember.ObjectController.extend({ isEditing: false, actions: { edit: function () { this.set('isEditing', true); }, save: function () { var post = this.get('model'); post.save(); this.set('isEditing', false); }, delete: function () { if (confirm('Do you want to delete this post?')) { this.get('model').destroyRecord(); this.transitionToRoute('posts'); } } } });
متد save نیز در اینجا بهبود یافتهاست. ابتدا مدل جاری دریافت شده و سپس متد save بر روی آن فراخوانی میشود. به این ترتیب اطلاعات از حافظه به local storage نیز منتقل خواهند شد.
ثبت و نمایش نظرات به همراه تنظیمات روابط اشیاء در Ember Data
در ادامه قصد داریم امکان افزودن نظرات را به مطالب، به همراه نمایش آنها در ذیل هر مطلب، پیاده سازی کنیم. برای اینکار نیاز است رابطهی بین یک مطلب و نظرات مرتبط با آنرا در مدل ember data مشخص کنیم. به همین جهت فایل Scripts\Models\post.js را گشوده و تغییرات ذیل را به آن اعمال کنید:
Blogger.Post = DS.Model.extend({ title: DS.attr(), body: DS.attr(), comments: DS.hasMany('comment', { async: true }) });
همچنین نیاز است یک سر دیگر رابطه را نیز مشخص کرد. برای این منظور فایل Scripts\Models\comment.js را گشوده و به نحو ذیل تکمیل کنید:
Blogger.Comment = DS.Model.extend({ text: DS.attr(), post: DS.belongsTo('post', { async: true }) });
در ادامه نیاز است بتوان تعدادی نظر را ثبت کرد. به همین جهت با تعریف مسیریابی آن شروع میکنیم. این مسیریابی تعریف شده در فایل Scripts\App\router.js نیز باید تو در تو باشد؛ زیرا قسمت ثبت نظر (new-comment) دقیقا داخل همان صفحهی نمایش یک مطلب ظاهر میشود:
Blogger.Router.map(function () { this.resource('posts', { path: '/' }); this.resource('about'); this.resource('contact', function () { this.resource('email'); this.resource('phone'); }); this.resource('recent-comments'); this.resource('post', { path: 'posts/:post_id' }, function () { this.resource('new-comment'); }); this.resource('new-post'); });
<h2>{{title}}</h2> {{#if isEditing}} <form> <div class="form-group"> <label for="title">Title</label> {{input value=title id="title" class="form-control"}} </div> <div class="form-group"> <label for="body">Body</label> {{textarea value=body id="body" class="form-control" rows="5"}} </div> <button class="btn btn-primary" {{action 'save' }}>Save</button> </form> {{else}} <p>{{body}}</p> <button class="btn btn-primary" {{action 'edit' }}>Edit</button> <button class="btn btn-danger" {{action 'delete' }}>Delete</button> {{/if}} <h2>Comments</h2> {{#each comment in comments}} <p> {{comment.text}} </p> {{/each}} <p>{{#link-to 'new-comment' this class="btn btn-success"}}New comment{{/link-to}}</p> {{outlet}}
در انتهای قالب نیز یک {{outlet}} اضافه شدهاست. کار آن نمایش قالب ارسال یک نظر جدید، پس از کلیک بر روی لینک New Comment میباشد. این قالب را با افزودن فایل Scripts\Templates\new-comment.hbs با محتوای ذیل ایجاد خواهیم کرد:
<h2>New comment</h2> <form> <div class="form-group"> <label for="text">Your thoughts:</label> {{textarea value=text id="text" class="form-control" rows="5"}} </div> <button class="btn btn-primary" {{action "save"}}>Add your comment</button> </form>
<script type="text/javascript"> EmberHandlebarsLoader.loadTemplates([ 'posts', 'about', 'application', 'contact', 'email', 'phone', 'recent-comments', 'post', 'new-post', 'new-comment' ]); </script>
Blogger.NewCommentController = Ember.ObjectController.extend({ needs: ['post'], actions: { save: function () { var comment = this.store.createRecord('comment', { text: this.get('text') }); comment.save(); var post = this.get('controllers.post.model'); post.get('comments').pushObject(comment); post.save(); this.transitionToRoute('post', post.id); } } });
<script src="Scripts/Controllers/new-comment.js" type="text/javascript"></script>
قسمت ذخیره سازی comment جدید با ذخیره سازی یک post جدید که پیشتر بررسی کردیم، تفاوتی ندارد. از متد this.store.createRecord جهت معرفی وهلهای جدید از comment استفاده و سپس متد save آن، برای ثبت نهایی فراخوانی شدهاست.
در ادامه باید این نظر جدید را به post متناظر با آن مرتبط کنیم. برای اینکار نیاز است تا به مدل کنترلر post دسترسی داشته باشیم. به همین جهت خاصیت needs را به تعاریف کنترلر جاری به همراه نام کنترلر مورد نیاز، اضافه کردهایم. به این ترتیب میتوان توسط متد this.get و پارامتر controllers.post.model در کنترلر NewComment به اطلاعات کنترلر post دسترسی یافت. سپس خاصیت comments شیء post جاری را یافته و مقدار آنرا به comment جدیدی که ثبت کردیم، تنظیم میکنیم. در ادامه با فراخوانی متد save، کار تنظیم ارتباطات یک مطلب و نظرهای جدید آن به پایان میرسد.
در آخر با فراخوانی متد transitionToRoute به مطلبی که نظر جدیدی برای آن ارسال شدهاست باز میگردیم.
همانطور که در این تصویر نیز مشاهده میکنید، اطلاعات ذخیره شده در local storage را توسط افزونهی Ember Inspector نیز میتوان مشاهده کرد.
افزودن دکمهی حذف به لیست نظرات ارسالی
برای افزودن دکمهی حذف، به قالب Scripts\Templates\post.hbs مراجعه کرده و قسمتی را که لیست نظرات را نمایش میدهد، به نحو ذیل تکمیل میکنیم:
{{#each comment in comments}} <p> {{comment.text}} <button class="btn btn-xs btn-danger" {{action 'delete' }}>delete</button> </p> {{/each}}
Blogger.CommentController = Ember.ObjectController.extend({ needs: ['post'], actions: { delete: function () { if (confirm('Do you want to delete this comment?')) { var comment = this.get('model'); comment.deleteRecord(); comment.save(); var post = this.get('controllers.post.model'); post.get('comments').removeObject(comment); post.save(); } } } });
<script src="Scripts/Controllers/comment.js" type="text/javascript"></script>
در این حالت اگر برنامه را اجرا کنید، پیام «Do you want to delete this post» را مشاهده خواهید کرد بجای پیام «Do you want to delete this comment». علت اینجا است که قالب post به صورت پیش فرض به کنترلر post متصل است و نه کنترلر comment. برای رفع این مشکل تنها کافی است از itemController به نحو ذیل استفاده کنیم:
{{#each comment in comments itemController="comment"}} <p> {{comment.text}} <button class="btn btn-xs btn-danger" {{action 'delete' }}>delete</button> </p> {{/each}}
در کنترلر Comment روش دیگری را برای حذف یک رکورد مشاهده میکنید. میتوان ابتدا متد deleteRecord را بر روی مدل فراخوانی کرد و سپس آنرا save نمود تا نهایی شود. همچنین در اینجا نیاز است نظر حذف شده را از سر دیگر رابطه نیز حذف کرد. روش دسترسی به post جاری در این حالت، همانند توضیحات NewCommentController است که پیشتر بحث شد.
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید:
EmberJS03_04.zip
پیشنیازها
برای این مطلب از دو کتابخانهی moment-jalaali، برای تبدیل تاریخ، از میلادی به شمسی و برعکس، استفاده خواهیم کرد. همچنین کنترل انتخاب تاریخ نیز از کتابخانهی MD.BootstrapPersianDateTimePicker تامین میشود.
moment-jalaali را میتوانید به صورت ذیل از طریق npm نصب کنید:
npm install moment-jalaali --save
node_modules/moment/min/moment.min.js node_modules/moment-jalaali/build/moment-jalaali.js
MD.BootstrapPersianDateTimePicker را یا از طریق نیوگت نصب کنید و یا مخزن کد آنرا به صورت کامل دریافت کنید:
Install-Package MD.BootstrapPersianDateTimePicker
همچنین فایلهای jalaali.js و jquery.Bootstrap-PersianDateTimePicker.js آنرا نیز به مداخل اسکریپتهای صفحهی خود اضافه نمائید.
در کل اگر از ASP.NET Core استفاده میکنید، فایل bundleconfig.json یک چنین شکلی را پیدا خواهد کرد. در این حالت فایل layout برنامه تنها این دو مدخل نهایی را نیاز خواهد داشت:
<link href="~/css/site.min.css" rel="stylesheet" asp-append-version="true" /> <script src="~/js/site.min.js" type="text/javascript" asp-append-version="true"></script>
فعالسازی کنترل انتخاب تاریخ شمسی بجای کامپوننت پیش فرض انتخاب تاریخ Kendo UI
پس از افزودن ارجاعات مورد نیاز، اکنون فرض ما بر این است که ستون تاریخ، دقیقا با فرمت میلادی از سمت سرور دریافت میشود و addDate نام دارد.
پس از آن، مرحلهی اول کار، نمایش این تاریخ میلادی به صورت شمسی است:
{ field: "addDate", title: "تاریخ ثبت", template: "#=moment(addDate).format('jYYYY/jMM/jDD')#",
در ادامهی تکمیل خواص ستون جاری، خاصیت editor را اضافه خواهیم کرد:
editor: function(container, options) { }
در ادامه نیاز است یک input field سفارشی را در اینجا درج کنیم تا کار نمایش کنترل انتخاب تاریخ شمسی را انجام دهد:
// دریافت تاریخ میلادی و تبدیل آن به شمسی جهت نمایش در تکست باکس var persianAddDate = moment(options.model.addDate).format('jYYYY/jMM/jDD'); // ایجاد کنترل انتخاب تاریخ سفارشی با مقدار تاریخ شمسی دریف جاری var input = $('<div dir="ltr" class="input-group">'+ '<div class="input-group-addon" data-name="datepicker1" data-mddatetimepicker="true" data-trigger="click" data-targetselector="#' + options.field + '" data-fromdate="false" data-enabletimepicker="false" data-englishnumber="true" data-placement="left">'+ '<span class="glyphicon glyphicon-calendar"></span>'+ '</div>'+ '<input type="text" value="'+ persianAddDate +'" class="form-control" id="' + options.field + '" placeholder="از تاریخ" data-mddatetimepicker="true" data-trigger="click" data-targetselector="#' + options.field + '" data-englishnumber="true" data-fromdate="true" data-enabletimepicker="false" data-placement="right" />'+ '</div>'); // افزودن کنترل جدید به صفحه input.appendTo(container);
متد input.appendTo، کار افزودن این input جدید را به container یا همان محل نمایش ستون جاری، انجام میدهد.
در این حالت اگر برنامه را اجرا کنید، هرچند ظاهر Input جدید تغییر کردهاست، اما سبب نمایش کنترل انتخاب تاریخ نمیشود؛ چون این فیلد ویرایشی، پس از رندر صفحه، به صفحه اضافه شدهاست. به همین جهت نیاز است متد EnableMdDateTimePickers نیز فراخوانی شود. این متد کار فعالسازی input جدید را انجام خواهد داد:
// با خبر سازی کتابخانه انتخاب تاریخ از تکست باکس جدید EnableMdDateTimePickers();
تا اینجا موفق شدیم بجای کنترل انتخاب تاریخ میلادی، کنترل انتخاب تاریخ شمسی را نمایش دهیم. اما تاریخی که انتخاب میشود نیز شمسی است و تاریخی که به سمت سرور ارسال خواهد شد، میلادی میباشد. به همین جهت تغییرات این کنترل را تحت نظر قرار داده و هر زمانیکه کاربر تاریخی را انتخاب کرد، آنرا به میلادی تبدیل کرده و بجای فیلد addDate اصلی تنظیم میکنیم:
// هر زمانیکه کاربر تاریخ جدیدی را وارد کرد، آنرا به میلادی تبدیل کرده و در مدل ردیف جاری ثبت میکنیم // در نهایت این مقدار میلادی است که به سمت سرور ارسال خواهد شد $('#' + options.field).change(function(){ var selectedPersianDate = $(this)[0].value; var gregorianAddDate = moment(selectedPersianDate, 'jYYYY/jMM/jDD').format('YYYY-MM-DD'); options.model.set('addDate', gregorianAddDate); });
تنها مرحلهی باقیمانده، مخفی کردن این کنترل نمایش تاریخ، با از دست رفتن فوکوس است:
// با از دست رفتن فوکوس نیاز است این کنترل مخفی شود $('#' + options.field).blur(function(){ $('[data-name="datepicker1"]').MdPersianDateTimePicker('hide'); });
کدهای کامل این مثال را از اینجا میتوانید دریافت کنید: با این کنترلر و این View
مدل و کنترلر برنامه
namespace jQueryMvcSample07.Models { public class BlogPost { public int Id { set; get; } public string Title { set; get; } public string Body { set; get; } } }
using System.Web.Mvc; using System.Web.UI; using jQueryMvcSample07.Models; using jQueryMvcSample07.Security; namespace jQueryMvcSample07.Controllers { public class HomeController : Controller { [HttpGet] public ActionResult Index() { return View(); //نمایش یک منوی ساده در ابتدای کار } [HttpGet] public ActionResult ShowSynchronous() { var model = getModel(); return View(model); //نمایش همزمان } private static BlogPost getModel() { //شبیه سازی یک عملیات طولانی System.Threading.Thread.Sleep(3000); var model = new BlogPost { Title = "عنوان ... ", Body = "مطلب... " }; return model; } [HttpGet] public ActionResult ShowAsynchronous() { return View(); //نمایش ابتدایی صفحه } [HttpPost] [AjaxOnly] [OutputCache(Location = OutputCacheLocation.None, NoStore = true)] public ActionResult RenderAsynchronous() { //دریافت اطلاعات به صورت غیرهمزمان var model = getModel(); return PartialView(viewName: "_Post", model: model); } } }
در کنترلر Home، ابتدا اکشن متد Index آن فراخوانی شده و در این حالت دو لینک زیر نمایش داده میشوند:
@{ ViewBag.Title = "Index"; } <h2> نمایش اطلاعات به صورت همزمان و غیرهمزمان</h2> <ul> <li> @Html.ActionLink(linkText: "نمایش همزمان", actionName: "ShowSynchronous", controllerName: "Home") </li> <li> @Html.ActionLink(linkText: "نمایش غیر همزمان", actionName: "ShowAsynchronous", controllerName: "Home") </li> </ul>
در هر دو صفحه نهایتا از یک Partial View به نام _Post.cshtml برای نمایش اطلاعات استفاده خواهد شد:
@model jQueryMvcSample07.Models.BlogPost <fieldset> <legend>@Model.Title</legend> @Model.Body </fieldset>
@model jQueryMvcSample07.Models.BlogPost @{ ViewBag.Title = "ShowSynchronous"; } <h2>نمایش همزمان</h2> @{ Html.RenderPartial("_Post", Model); }
در حالتیکه کاربر بر روی لینک نمایش غیرهمزمان کلیک میکند، صفحه زیر را مشاهده خواهد کرد:
@{ ViewBag.Title = "ShowAsynchronous"; var loadInfoUrl = Url.Action(actionName: "RenderAsynchronous", controllerName: "Home"); } <h2> نمایش غیر همزمان</h2> <div id="info" align="center"> </div> <div id="progress" align="center" style="display: none"> <br /> <img src="@Url.Content("~/Content/images/loadingAnimation.gif")" alt="loading..." /> </div> @section JavaScript { <script type="text/javascript"> $(function () { $("#progress").css("display", "block"); $.ajax({ type: "POST", url: '@loadInfoUrl', complete: function (xhr, status) { var data = xhr.responseText; if (xhr.status == 403) { window.location = "/login"; } else if (status === 'error' || !data || data == "nok") { alert('خطایی رخ داده است'); } else { $("#progress").css("display", "none"); $("#info").html(data); } } }); }); </script> }
به این ترتیب میتوان حس سریع بودن سایت را زمانیکه قسمتی از صفحه نیاز به زمان بیشتری برای نمایش اطلاعات دارد، به کاربر منتقل کرد.
دریافت پروژه کامل این قسمت
jQueryMvcSample07.zip
public class CustomLogger : iTextSharp.text.log.ILogger { public iTextSharp.text.log.ILogger GetLogger(Type klass) { return this; } public iTextSharp.text.log.ILogger GetLogger(string name) { return this; } public bool IsLogging(iTextSharp.text.log.Level level) { return true; } public void Warn(string message) { System.Diagnostics.Trace.TraceWarning(message); } public void Trace(string message) { System.Diagnostics.Trace.TraceInformation(message); } public void Debug(string message) { System.Diagnostics.Trace.TraceInformation(message); } public void Info(string message) { System.Diagnostics.Trace.TraceInformation(message); } public void Error(string message) { System.Diagnostics.Trace.TraceError(message); } public void Error(string message, Exception e) { System.Diagnostics.Trace.TraceError(message + System.Environment.NewLine + e); } }
iTextSharp.text.log.LoggerFactory.GetInstance().SetLogger(new CustomLogger());
بله، حدس شما درست است استفاده از صفحه کلیدهای مجازی میشه گفت یکی از بهترین راههای ممکن هست، چون در این روشها کلید به صورت سخت افزاری فشرده نمیشود (کلید فشرده شده به صف پیامهای ویندوز نمیرود) در نتیجه نرم افزارها یا سخت افزارهای جاسوس نمیتوانند این اطلاعات را ثبت کنند. و کاربر با خیال راحت میتواند دادههای خود را وارد نمایند (تاکید میکنم این روش فقط جلو این نرم افزارها یا سخت افزارها را میگیرد و تضمینی برای اینکه در زمان ارسال دادههای شما لو نرود ندارد).
خوب حال چه باید کرد؟
یک راه میتواند پیادهسازی صفحه کلید مجازی با کدهای طرف کلاینت مانند جاوا اسکریپت و ویبی اسکریپت است، اما گروهی پلاگینی را توسعه دادهاند که با چند خط کدنویسی ساده به راحتی میتوانید یک صفحه کلید مجازی چندزبانه (با هر زبانی که دلتون میخواد) داشته باشید و از اون در برنامههای خودتون استفاده کنید.
نحوهی نصب:
ایتدا فایلهای مورد نیاز را از سایت سازنده که شامل فایل جاوا اسکریپت ، فایل استایل و یک تصویر (آخرین نسخه) یا از این آدرس به صورت کامل (در حال حاضر نسخه 1.49) دریافت کرده، پس از دریافت فایلها آنها را در هاست خود بارگزاری (آپلود) نمائید. سپس کدهای زیر را در صفحهای که میخواهید صفحه کلید نمایش یابد در بین تگ <head> ... و <head/> قرار دهید.
<script type="text/javascript" src="keyboard.js" charset="UTF-8"></script> <link rel="stylesheet" type="text/css" href="keyboard.css">
مثال:
<input type="text" value="" class="keyboardInput">
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title></title> <script type="text/javascript" src="keyboard.js" charset="UTF-8"></script> <link rel="stylesheet" type="text/css" href="keyboard.css"/> </head> <body> <input type="text" value="" class="keyboardInput"/> </body> </html>
با این کار پس از اجرای صفحه مورد نظر خروجی شما مانند تصویر زیر خواهد بود، جهت محدود کردن کلیدها و عملیات دلخواه و سفارشی سازی با پارامترهای دلخواه میتواند از دموهای موجود در سایت سازنده بهره بگیرید.
موفق وموید باشید.