<script type="text/javascript"> function LaodWordInfo(id) { showProgress(); $.ajax({ type: "Post", url: "test/Info", data: JSON.stringify({ ID: id }), contentType: "application/json; charset=utf-8", dataType: "json", complete: function (xhr, status) { var data = xhr.responseText; if (status === 'error' || !data) { } else { var dialog = $("#dialog"); dialog.html(data); dialog.dialog("open"); } hideProgress(); return false; } } ); } function showProgress() { $('#Progress').css("display", "block"); } function hideProgress() { $('#Progress').css("display", "none"); } $(function () { $("#dialog").dialog({ autoOpen: false, show: "fade", hide: "fade", width: 550, title: "WordInfo", resizable: false }); }); </script>
فقط مساله ای که برای من اتفاق افتاد این بود که زمانی که دکمه مربوط به بازسازی کپچا رو میزدم تصویر از کش خونده میشد و تصویر جدید نمایش داده نمیشد که برای حل این موضوع کافیست کد ایجکس برای آپدیت کپچا توسط دکمه refresh را به صورت زیر بنویسید:
jQuery('#refresh').on({ 'click': function () { var random = new Date(); jQuery.ajax({ url: '@Url.Action("CaptchaImage","Captcha")', type: "GET", data: null, success: function (result) { jQuery("#imgcpatcha").attr("src", "/Captcha/CaptchaImage?" + random + result); } }); } });
مزیت استفاده از یوزر کنترلها، ماژولار کردن برنامه است. برای مثال اگر صفحه جاری شما قرار است از چهار قسمت اخبار، منوی پویا ، سخن روز و آمار کاربران تشکیل شود، میتوان هر کدام را توسط یک یوزر کنترل پیاده سازی کرده و سپس صفحه اصلی را از کنار هم قرار دادن این یوزر کنترلها تهیه نمود.
با این توضیحات اکنون میخواهیم یک یوزکنترل ASP.Net را توسط jQuery Ajax بارگذاری کرده و نمایش دهیم. حداقل دو مورد کاربرد را میتوان برای آن متصور شد:
الف) در اولین باری که یک صفحه در حال بارگذاری است، قسمتهای مختلف آنرا بتوان از یوزر کنترلهای مختلف خواند و تا زمان بارگذاری کامل هر کدام، یک عبارت لطفا منتظر بمانید را نمایش داد. نمونهی آنرا شاید در بعضی از CMS های جدید دیده باشید. صفحه به سرعت بارگذاری میشود. در حالیکه مشغول مرور صفحه جاری هستید، قسمتهای مختلف صفحه پدیدار میشوند.
ب) بارگذاری یک قسمت دلخواه صفحه بر اساس درخواست کاربر. مثلا کلیک بر روی یک دکمه و امثال آن.
روش کلی کار:
1) تهیه یک متد وب سرویس که یوزر کنترل را بر روی سرور اجرا کرده و حاصل را تبدیل به یک رشته کند.
2) استفاده از متد Ajax جیکوئری برای فراخوانی این متد وب سرویس و افزودن رشته دریافت شده به صفحه.
بدیهی است زمانیکه متد Ajax فراخوانی میشود میتوان عبارت یا تصویر منتظر بمانید را نمایش داد و پس از پایان کار این متد، عبارت (یا تصویر) را مخفی نمود.
پیاده سازی:
قسمت تبدیل یک یوزر کنترل به رشته را قبلا در مقاله "تهیه قالب برای ایمیلهای ارسالی یک برنامه ASP.Net" مشاهده کردهاید. در اینجا برای استفاده از این متد در یک وب سرویس نیاز به کمی تغییر وجود داشت (KeyValuePair ها درست سریالایز نمیشوند) که نتیجه نهایی به صورت زیر است. یک فایل Ajax.asmx را به برنامه اضافه کرده و سپس در صفحه Ajax.asmx.cs کد آن به صورت زیر میتواند باشد:
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Web;
using System.Web.Script.Services;
using System.Web.Services;
using System.Web.UI;
using System.Web.UI.HtmlControls;
namespace AjaxTest
{
public class KeyVal
{
public string Key { set; get; }
public object Value { set; get; }
}
/// <summary>
/// Summary description for Ajax
/// </summary>
[ScriptService]
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[System.ComponentModel.ToolboxItem(false)]
public class Ajax : WebService
{
/// <summary>
/// Removes Form tags using Regular Expression
/// </summary>
private static string cleanHtml(string html)
{
return Regex.Replace(html, @"<[/]?(form)[^>]*?>", string.Empty, RegexOptions.IgnoreCase);
}
/// <summary>
/// تبدیل یک یوزر کنترل به معادل اچ تی ام ال آن
/// </summary>
/// <param name="path">مسیر یوزر کنترل</param>
/// <param name="properties">لیست خواص به همراه مقادیر مورد نظر</param>
/// <returns></returns>
/// <exception cref="NotImplementedException"><c>NotImplementedException</c>.</exception>
[WebMethod(EnableSession = true)]
[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
public string RenderUserControl(string path,
List<KeyVal> properties)
{
Page pageHolder = new Page();
UserControl viewControl =
(UserControl)pageHolder.LoadControl(path);
viewControl.EnableViewState = false;
Type viewControlType = viewControl.GetType();
if (properties != null)
foreach (var pair in properties)
{
if (pair.Key != null)
{
PropertyInfo property =
viewControlType.GetProperty(pair.Key);
if (property != null)
{
if (pair.Value != null) property.SetValue(viewControl, pair.Value, null);
}
else
{
throw new NotImplementedException(string.Format(
"UserControl: {0} does not have a public {1} property.",
path, pair.Key));
}
}
}
//Form control is mandatory on page control to process User Controls
HtmlForm form = new HtmlForm();
//Add user control to the form
form.Controls.Add(viewControl);
//Add form to the page
pageHolder.Controls.Add(form);
//Write the control Html to text writer
StringWriter textWriter = new StringWriter();
//execute page on server
HttpContext.Current.Server.Execute(pageHolder, textWriter, false);
// Clean up code and return html
return cleanHtml(textWriter.ToString());
}
}
}
چند نکته:
الف) وب کانفیگ برنامه ASP.Net شما اگر با VS 2008 ایجاد شده باشد مداخل لازم را برای استفاده از این وب سرویس توسط jQuery Ajax دارد در غیر اینصورت موفق به استفاده از آن نخواهید شد.
ب) هنگام بازگرداندن این اطلاعات با فرمت json = ResponseFormat.Json جهت استفاده در jQuery Ajax ، گاهی از اوقات بسته به حجم بازگردانده شده ممکن است خطایی حاصل شده و عملیات متوقف شد. این طول پیش فرض را (maxJsonLength) در وب کانفیگ به صورت زیر تنظیم کنید تا مشکل حل شود:
<system.web.extensions>
<scripting>
<webServices>
<jsonSerialization maxJsonLength="10000000"></jsonSerialization>
</webServices>
</scripting>
</system.web.extensions>
برای پیاده سازی قسمت Ajax آن برای اینکه کار کمی تمیزتر و با قابلیت استفاده مجدد شود یک پلاگین تهیه شده (فایلی با نام jquery.advloaduc.js) که سورس آن به صورت زیر است:
$.fn.advloaduc = function(options) {
var defaults = {
webServiceName: 'Ajax.asmx', //نام فایل وب سرویس ما
renderUCMethod: 'RenderUserControl', //متد وب سرویس
ucMethodJsonParams: '{path:\'\'}',//پارامترهایی که قرار است پاس شوند
completeHandler: null //پس از پایان کار وب سرویس این متد جاوا اسکریپتی فراخوانی میشود
};
var options = $.extend(defaults, options);
return this.each(function() {
var obj = $(this);
obj.prepend("<div align='center'> لطفا اندکی تامل بفرمائید... <img src=\"images/loading.gif\"/></div>");
$.ajax({
type: "POST",
url: options.webServiceName + "/" + options.renderUCMethod,
data: options.ucMethodJsonParams,
contentType: "application/json; charset=utf-8",
dataType: "json",
success:
function(msg) {
obj.html(msg.d);
// if specified make callback and pass element
if (options.completeHandler)
options.completeHandler(this);
},
error:
function(XMLHttpRequest, textStatus, errorThrown) {
obj.html("امکان اتصال به سرور در این لحظه مقدور نیست. لطفا مجددا سعی کنید.");
}
});
});
};
عمده کاری که در این پلاگین صورت میگیرد فراخوانی متد Ajax جیکوئری است. سپس به متد وب سرویس ما (که در اینجا نام آن به صورت پارامتر نیز قابل دریافت است)، پارامترهای لازم پاس شده و سپس نتیجه حاصل به یک شیء در صفحه اضافه میشود.
completeHandler آن اختیاری است و پس از پایان کار متد اجکس فراخوانی میشود. در صورتیکه به آن نیازی نداشتید یا مقدار آن را null قرار دهید یا اصلا آنرا ذکر نکنید.
مثالی در مورد استفاده از این وب سرویس و همچنین پلاگین جیکوئری نوشته شده:
الف) یوزر کنترل ساده زیر را به پروژه اضافه کنید:
<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="part1.ascx.cs" Inherits="TestJQueryAjax.part1" %>
<asp:Label runat="server" ID="lblData" ></asp:Label>
سپس کد آنرا به صورت زیر تغییر دهید:
using System;
using System.Threading;
namespace TestJQueryAjax
{
public partial class part1 : System.Web.UI.UserControl
{
public string Text1 { set; get; }
public string Text2 { set; get; }
protected void Page_Load(object sender, EventArgs e)
{
Thread.Sleep(3000);
if (!string.IsNullOrEmpty(Text1) && !string.IsNullOrEmpty(Text2))
lblData.Text = Text1 + "<br/>" + Text2;
}
}
}
عمدا یک sleep سه ثانیهای در اینجا در نظر گرفته شده تا اثر آنرا بهتر بتوان مشاهده کرد.
ب) اکنون کد مربوط به صفحهای که قرار است این یوزر کنترل را به صورت غیرهمزمان بارگذاری کند به صورت زیر خواهد بود (مهمترین قسمت آن نحوه تشکیل پارامترها و مقدار دهی خواص یوزر کنترل است):
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="TestJQueryAjax._Default" %>
<!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 runat="server">
<title></title>
<script src="js/jquery.js" type="text/javascript"></script>
<script src="js/jquery.advloaduc.js" type="text/javascript"></script>
<script src="js/json2.js" type="text/javascript"></script>
<script type="text/javascript">
function showAlert() {
alert('finished!');
}
//تشکیل پارامترهای متد وب سرویس جهت ارسال به آن
var fileName = 'part1.ascx';
var props = [{ 'Key': 'Text1', 'Value': 'سطر یک' }, { 'Key': 'Text2', 'Value': 'سطر 2'}];
var jsonText = JSON.stringify({ path: fileName, properties: props });
$(document).ready(function() {
$("#loadMyUc").advloaduc({
webServiceName: 'Ajax.asmx',
renderUCMethod: 'RenderUserControl',
ucMethodJsonParams: jsonText,
completeHandler: showAlert
});
});
</script>
</head>
<body>
<form id="form1" runat="server">
<div id="loadMyUc">
</div>
</form>
</body>
</html>
برای ارسال صحیح و امن اطلاعات json به سرور، از اسکریپت استاندارد json2.js استفاده شد.
ابزار دیباگ:
بهترین ابزار برای دیباگ این نوع اسکریپتها استفاده از افزونه فایرباگ فایرفاکس است. برای مثال مطابق تصویر زیر، یوزر کنترلی فراخوانی شده است که در سرور وجود ندارد:
دریافت مثال فوق
تعریف مدل سمت کاربر برنامه
فایل جدید Scripts\App\store.js را اضافه کرده و محتوای آنرا به نحو ذیل تغییر دهید:
var posts = [ { id: '1', title: "Getting Started with Ember.js", body: "Bla bla bla 1." }, { id: '2', title: "Routes and Templates", body: "Bla bla bla 2." }, { id: '3', title: "Controllers", body: "Bla bla bla 3." } ]; var comments = [ { id: '1', postId: '3', text: 'Thanks!' }, { id: '2', postId: '3', text: 'Good to know that!' }, { id: '3', postId: '1', text: 'Great!' } ];
سپس جهت استفاده از آن، تعریف مدخل آنرا به فایل index.html، پیش از تعاریف کنترلرها اضافه خواهیم کرد:
<script src="Scripts/App/store.js" type="text/javascript"></script>
ویرایش قالب مطالب برای نمایش لیستی از عناوین ارسالی
قالب فعلی Scripts\Templates\posts.hbs صرفا دارای یک سری عنوان درج شده به صورت مستقیم در صفحه است. اکنون قصد داریم آنرا جهت نمایش لیستی از آرایه مطالب تغییر دهیم.
همانطور که در تصویر ملاحظه میکنید، با درخواست آدرس صفحهی مطالب، router آن مسیریابی متناظری را یافته و سپس بر این اساس، template، کنترلر و مدلی را انتخاب میکند. به صورت پیش فرض، قالب و کنترلر انتخاب شده، مواردی هستند همنام با مسیریابی جاری. اما مقدار پیش فرضی برای model وجود ندارد و باید آنرا به صورت دستی مشخص کرد.
برای این منظور فایل Scripts\Routes\posts.js را به پوشهی routes با محتوای ذیل اضافه کنید:
Blogger.PostsRoute = Ember.Route.extend({ controllerName: 'posts', renderTemplare: function () { this.render('posts'); }, model: function () { return posts; } });
همچنین اگر به خاطر داشته باشید، در پوشهی کنترلرها فایل posts.js تعریف نشدهاست. اگر اینکار صورت نگیرد، ember.js به صورت خودکار کنترلر پیش فرضی را ایجاد خواهد کرد. در کل، یک قالب هیچگاه به صورت مستقیم با مدل کار نمیکند. این کنترلر است که مدل را در اختیار یک قالب قرار میدهد.
سپس مدخل تعریف این فایل را به فایل index.html، پس از تعاریف کنترلرها اضافه نمائید:
<script src="Scripts/Routes/posts.js" type="text/javascript"></script>
اکنون فایل Scripts\Templates\posts.hbs را گشوده و به نحو ذیل، جهت نمایش عناوین مطالب، ویرایش کنید:
<h2>Emeber.js blog</h2> <ul> {{#each post in model}} <li>{{post.title}}</li> {{/each}} </ul>
نمایش لیست آخرین نظرات ارسالی
در ادامه قصد داریم تا آرایه comments ابتدای بحث را در صفحهای جدید نمایش دهیم. بنابراین نیاز است تا ابتدا مسیریابی آن تعریف شود. بنابراین فایل Scripts\App\router.js را گشوده و مسیریابی جدید recent-comments را به آن اضافه کنید:
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'); });
Blogger.RecentCommentsRoute = Ember.Route.extend({ model: function () { return comments; } });
همچنین نیاز است تا تعریف مدخل این فایل جدید را نیز به انتهای تعاریف مداخل فایل index.html اضافه کنیم:
<script src="Scripts/Routes/recent-comments.js" type="text/javascript"></script>
اکنون قالب application واقع در فایل Scripts\Templates\application.hbs را جهت افزودن منوی مرتبط با این مسیریابی جدید، به نحو ذیل ویرایش خواهیم کرد:
<div class='container'> <nav class='navbar navbar-default' role='navigation'> <ul class='nav navbar-nav'> <li>{{#link-to 'posts'}}Posts{{/link-to}}</li> <li>{{#link-to 'recent-comments'}}Recent comments{{/link-to}}</li> <li>{{#link-to 'about'}}About{{/link-to}}</li> <li>{{#link-to 'contact'}}Contact{{/link-to}}</li> </ul> </nav> {{outlet}} </div>
<h1>Recent comments</h1> <ul> {{#each comment in model}} <li>{{comment.text}}</li> {{/each}} </ul>
<script type="text/javascript"> EmberHandlebarsLoader.loadTemplates([ 'posts', 'about', 'application', 'contact', 'email', 'phone', 'recent-comments' ]); </script>
نمایش مجزای هر مطلب در یک صفحهی جدید
تا اینجا در صفحهی اول سایت، لیست عناوین مطالب را نمایش دادیم. در ادامه نیاز است تا بتوان هر عنوان را به صفحهی متناظر و اختصاصی آن لینک کرد؛ برای مثال لینکی مانند http://localhost:25918/#/posts/3 به سومین مطلب ارسالی اشاره میکند. Ember.js به عدد 3 در اینجا، یک dynamic segment میگوید. از این جهت که مقدار آن بر اساس شماره مطلب درخواستی، متفاوت خواهد بود. برای پردازش این نوع آدرسها نیاز است مسیریابی ویژهای را تعریف کرد. فایل Scripts\App\router.js را گشوده و سپس مسیریابی 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' }); });
با توجه به اینکه این مسیریابی جدید post نام گرفت (جهت نمایش یک مطلب)، به صورت خودکار، کنترلر و قالبی به همین نام را بارگذاری میکند. همچنین مدل خود را نیز باید از مسیریابی خاص خود دریافت کند. بنابراین فایل جدید Scripts\Routes\post.js را در پوشهی routes با محتوای ذیل اضافه کنید:
Blogger.PostRoute = Ember.Route.extend({ model: function (params) { return posts.findBy('id', params.post_id); } });
برای مثال، جهت آدرس http://localhost:25918/#/posts/3، مقدار post_id به صورت خودکار به عدد 3 تنظیم میشود.
پس از آن نیاز است مدخل این فایل جدید را در صفحهی index.html نیز اضافه کنیم:
<script src="Scripts/Routes/post.js" type="text/javascript"></script>
در ادامه برای نمایش اطلاعات مدل نیاز است قالب جدید Scripts\Templates\post.hbs را با محتوای زیر اضافه کنیم:
<h1>{{title}}</h1> <p>{{body}}</p>
<script type="text/javascript"> EmberHandlebarsLoader.loadTemplates([ 'posts', 'about', 'application', 'contact', 'email', 'phone', 'recent-comments', 'post' ]); </script>
اکنون به قالب Scripts\Templates\posts.hbs مراجعه کرده و هر عنوان را به مطلب متناظر با آن لینک میکنیم:
<h2>Emeber.js blog</h2> <ul> {{#each post in model}} <li>{{#link-to 'post' post.id}}{{post.title}}{{/link-to}}</li> {{/each}} </ul>
همچنین با کلیک بر روی هر عنوان نیز مطلب مرتبط نمایش داده خواهد شد:
افزودن امکان ویرایش مطالب
میخواهیم در صفحهی نمایش جزئیات یک مطلب، امکان ویرایش آنرا نیز فراهم کنیم. بنابراین فایل 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> {{/if}}
در فرم تعریف شده، المانهای ورودی اطلاعات از handlebar helperهای ویژهی input و textarea استفاده میکنند؛ بجای المانهای متداول HTML. همچنین value یکی به title و دیگری به body تنظیم شدهاست (خواص مدل ارائه شده توسط کنترلر متصل به قالب). این مقادیر نیز داخل '' قرار ندارند؛ به عبارتی در یک handlebar helper به عنوان متغیر در نظر گرفته میشوند. به این ترتیب اطلاعات کنترلر جاری، به این المانهای ورودی اطلاعات به صورت خودکار bind میشوند و برعکس. اگر کاربر مقادیر آنها را تغییر دهد، تغییرات نهایی به صورت خودکار به خواص متناظری در کنترلر جاری منعکس خواهند شد (two-way data binding).
دو دکمه نیز تعریف شدهاند که به اکشنهای save و edit متصل هستند.
بنابراین نیاز به یک کنترلر جدید، به نام post داریم تا بتوان رفتار قالب post را کنترل کرد. برای این منظور فایل جدید Scripts\Controllers\post.js را با محتوای ذیل ایجاد کنید:
Blogger.PostController = Ember.ObjectController.extend({ isEditing: false, actions: { edit: function () { this.set('isEditing', true); }, save: function () { this.set('isEditing', false); } } });
<script src="Scripts/Controllers/post.js" type="text/javascript"></script>
اگر به کدهای این کنترلر دقت کرده باشید، اینبار زیرکلاسی از ObjectController ایجاد شدهاست و نه Controller، مانند مثالهای قبل. ObjectController تغییرات رخ داده بر روی خواص مدل را که توسط کنترلر در معرض دید قالب قرار دادهاست، به صورت خودکار به مدل مرتبط نیز منعکس میکند (Ember.ObjectController.extend)؛ اما Controller خیر (Ember.Controller.extend). در اینجا مدل کنترلر، تنها «یک» شیء است که بر اساس id آن انتخاب شدهاست. به همین جهت از ObjectController برای ارائه two-way data binding کمک گرفته شد.
در ember.js، یک قالب تنها با کنترلر خودش دارای تبادل اطلاعات است. اگر این کنترلر از نوع ObjectController باشد، تغییرات خاصیتی در یک قالب، ابتدا به کنترلر آن منعکس میشود و سپس این کنترلر، در صورت یافتن معادلی از این خاصیت در مدل، آنرا به روز خواهد کرد. در حالت استفاده از Controller معمولی، صرفا تبادل اطلاعات بین قالب و کنترلر را شاهد خواهیم بود و نه بیشتر.
در ابتدای کار مقدار خاصیت isEditing مساوی false است. این مورد سبب میشود تا در بار اول بارگذاری اطلاعات یک مطلب انتخابی، صرفا عنوان و محتوای مطلب نمایش داده شوند؛ به همراه یک دکمهی edit. با کلیک بر روی دکمهی edit، مطابق کدهای کنترلر فوق، تنها خاصیت isEditing به true تنظیم میشود و در این حالت، بدنهی اصلی شرط if isEditing در قالب post، رندر خواهد شد.
برای مثال در ابتدا مطلب شماره یک را انتخاب میکنیم:
با کلیک بر روی دکمهی edit، فرم ویرایش ظاهر خواهد شد:
نکتهی جالب آن، مقدار دهی خودکار المانهای ویرایش اطلاعات است. در این حالت سعی کنید، عنوان مطلب جاری را اندکی ویرایش کنید:
با ویرایش عنوان، میتوان بلافاصله مقدار تغییر یافته را در برچسب عنوان مطلب نیز مشاهده کرد. این مورد دقیقا مفهوم two-way data binding و اتصال مقادیر value هر کدام از handlebar helperهای ویژهی input و textarea را به عناصر مدل ارائه شده توسط کنترلر post، بیان میکند.
در این حالت در کدهای متد save، تنها کافی است که خاصیت isEditing را به false تنظیم کنیم. زیرا کلیه مقادیر ویرایش شده توسط کاربر، در همان لحظه در برنامه منتشر شدهاند و نیاز به کار بیشتری برای اعمال تغییرات نیست.
اضافه کردن دکمهی مرتب سازی بر اساس عناوین، در صفحهی اول سایت
Ember.ObjectController.extend برای data bindg یک شیء کاربرد دارد. اگر قصد داشته باشیم با آرایهای از اشیاء کار کنیم میتوان از ArrayController استفاده کرد. فرض کنید در صفحهی اول سایت میخواهیم امکان مرتب سازی مطالب را بر اساس عنوان آنها اضافه کنیم. فایل Scripts\Templates\posts.hbs را گشوده و لینک Sort by title را به انتهای آن اضافه کنید:
<h2>Emeber.js blog</h2> <ul> {{#each post in model}} <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>
Blogger.PostsController = Ember.ArrayController.extend({ sortProperties: ['id'],// مقادیر پیش فرض مرتب سازی sortAscending: false, actions: { sortByTitle: function () { this.set('sortProperties', ['title']); this.set('sortAscending', !this.get('sortAscending')); } } });
در ادامه، تعریف مدخل این کنترلر جدید را نیز باید به فایل index.html، اضافه کرد:
<script src="Scripts/Controllers/posts.js" type="text/javascript"></script>
اگر برنامه را در این حالت اجرا کرده و بر روی دکمهی Sort by title کلیک کنید، اتفاقی رخ نمیدهد. علت اینجا است که ArrayController خروجی تغییر یافته خودش را توسط خاصیتی به نام arrangedContent در اختیار قالب خود قرار میدهد. بنابراین نیاز است فایل قالب Scripts\Templates\posts.hbs را به نحو ذیل ویرایش کرد:
<h2>Emeber.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>
یک نکته: حلقهی ویژهای به نام each
اگر قالب Scripts\Templates\posts.hbs را به نحو ذیل، با یک حلقهی each ساده بازنویسی کنید:
<h2>Ember.js blog</h2> <ul> {{#each}} <li>{{#link-to 'post' id}}{{title}}{{/link-to}}</li> {{/each}} </ul> <a href="#" class="btn btn-primary" {{action 'sortByTitle'}}>Sort by title</a>
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید:
EmberJS03_03.zip
* نکته: پروژه انگیولار متریال ۲ در زمان نوشتن این مقاله به تازگی نسخه بتا ۵ را ارائه داده و همچنان در حال توسعه است. این بدان معنی است که ممکن است همه چیز به سرعت تغییر یابد.
مقدمه
انگیولار متریال ۲ همانند انگیولار متریال یک، تمامی المانهای مورد نیاز برای طراحی یک برنامه تک صفحهای را به راحتی فراهم میکند (هرچند تمامی المانهای آن در نسخه بتا پیاده سازی نشدهاند). خبر خوب اینکه، اکثر کامپوننتهای ارائه شده در انگیولار متریال ۲ از قالب راست به چپ پشتیبانی میکنند و اعمال این قالب به سادگی اضافه کردن خصوصیت dir یک المان به rtl است.
در صفحه گیتهاب انگیولار متریال ۲ آمدهاست که انگیولار متریال ۲، واسطهای کاربری با کیفیت بالا را ارائه میدهد و در ادامه منظورش را از «کیفیت بالا»، اینگونه بیان میکند:
- بینالمللی
و قابل دسترس برای همه به نحوی که تمامی کاربران میتوانند از آنها استفاده کنند
(عدم مشکل در چند زبانه بودن و پشتیبانی از قالب راست به چپ و چپ به راست) .
- دارای APIهای ساده برای توسعه دهندگان.
- رفتار مورد انتظار و بدون خطا در تمامی موردهای کاری
-
تست تمامی رفتارها توسط تست یکپارچگی (unit test ) و تست واحد ( integration test )
-
قابلیت سفارشی سازی در چارچوب طراحی متریال
-
بهرهوری بالا
-
کد تمیز و مستندات خوب
شروع کار با انگیولار متریال ۲
قدم اول: نصب angular-material و hammerjs
برای شروع بایستی Angular Material و angular animations و hammer.js را توسط npm به صورت زیر نصب کنید.
npm install --save @angular/material @angular/animations
npm install --save hammerjs
angular/material@: بسته مربوط به انگیولار متریال دو را نصب خواهد کرد.
angular/animations@: این بسته امکاناتی جهت ساخت افکتهای ویژه هنگام تغییر صفحات، یا بارگذاری المنتها را از طریق کدهای css نوشته شده، به راحتی امکانپذیر میکند.
Hammerjs: برخی از کامپوننتهای موجود در انگیولار متریال ۲ وابسته به کتابخانه Hammerjs هستند. (از جمله md-slide-toggle و md-slider, mdTooltip)
قدم دوم: معرفی کتابخانههای خارجی به angular-cli.json
"scripts": [ "../node_modules/hammerjs/hammer.min.js" ],
قدم سوم: افزودن Angular Material به ماوژل اصلی برنامه انگیولار
import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { HttpModule } from '@angular/http'; import { MaterialModule } from '@angular/material'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { AppComponent } from './app.component'; @NgModule({ declarations: [ AppComponent ], imports: [ BrowserModule, FormsModule, HttpModule, MaterialModule, BrowserAnimationsModule ], providers: [], bootstrap: [AppComponent] }) export class AppModule { }
قدم چهارم: افزودن تم و آیکون
همراه با نصب Angular Material تعدادی تم از قبل ساخته شده نیز نصب خواهند شد که شامل یکسری استایل با رنگهای مشخصی هستند. از جمله این تمها عبارتند از:
- indigo-pink
- deeppurple-amber
- purple-green
- pink-bluegrey
همچنین با استفاده از Material Design icons نیز با استفاده از تگ <md-icon> به آیکونهای متریال نیز میتوان دسترسی داشت.
برای افزودن آیکونهای متریال و همچنین انتخاب یک تم از قبل ساخته شده دو خط زیر را به فایل style.css اصلی برنامه اضافه کنید.
@import '~https://fonts.googleapis.com/icon?family=Material+Icons'; @import '~@angular/material/prebuilt-themes/deeppurple-amber.css';
نکتهای که در تگ <md-icon> وجود دارد این است که این تگ انواع فونتها و آیکونهای svg را نیز پشتیبانی میکند. استفاده از آیکونهای متریال یکی از قابلیتهای این تگ محسوب میشود.
برای اطلاعات بیشتر از نحوه ساخت تم سفارشی میتوانید این لینک را دنبال کنید.
قدم آخر: انگیولار متریال آماده است!
با انجام مراحل بالا اکنون میتوانید به راحتی از کامپوننتهای متریال استفاده کنید. کافی است کدهای زیر را به فایل app.component.html اضافه کنید و یک قالب ساده برای برنامه خود بسازید.
<md-sidenav-container> <md-sidenav #end align="end" opened="true" mode="side"> <md-toolbar color="accent"> <div> <md-toolbar-row> <img src="https://material.angular.io/favicon.ico" style="height:50px;margin-top: 2px; margin-bottom: 2px;"> <span> برنامه من </span> </md-toolbar-row> </div> </md-toolbar> <md-nav-list> <md-list-item [routerLink]="['/']"> <div> <div></div> <md-icon role="img" aria-label="home">home</md-icon> <span>خانه</span> </div> </md-list-item> </md-nav-list> <md-nav-list> <md-list-item [routerLink]="['/registries']"> <div> <div></div> <md-icon role="img" aria-label="forms">content_paste</md-icon> <span>فرم</span> </div> </md-list-item> </md-nav-list> <md-nav-list> <md-list-item href="/charts"> <div> <div></div> <md-icon role="img" aria-label="charts">show_chart</md-icon> <span>نمودارها</span> </div> </md-list-item> </md-nav-list> </md-sidenav> <header> <md-toolbar color="primary"> <button md-icon-button (click)="end.toggle()"> <md-icon>menu</md-icon> </button> <span>داشبورد</span> <button md-icon-button [md-menu-trigger-for]="menu"> <md-icon>person</md-icon> </button> </md-toolbar> <md-menu x-position="before" #menu="mdMenu"> <button md-menu-item>تنظیمات</button> <button md-menu-item>خروج</button> </md-menu> </header> <main> <router-outlet></router-outlet> </main> </md-sidenav-container> <span> <button md-fab> <md-icon>check circle</md-icon> </button> </span>
همچنین کدهای css زیر را به فایل اصلی style.css اضافه کنید.
html, body, material-app, md-sidenav-container, .my-content { margin: 0; direction: rtl; width: 100%; height: 100%; } .mat-button-toggle, .mat-button-base, .mat-button, .mat-raised-button, .mat-fab, .mat-icon-button, .mat-mini-fab, .mat-card, .mat-checkbox, .mat-input-container, .mat-list, .mat-menu-item, .mat-radio-button, .mat-select, .mat-list .mat-list-item .mat-list-item-content, .mat-nav-list .mat-list-item .mat-list-item-content, .mat-simple-snackbar, .mat-tab-label, .mat-slide-toggle-content, .mat-toolbar, .mat-tooltip { font-family: 'Iranian Sans', Tahoma !important; } md-sidenav { width: 225px; max-width: 70%; } md-sidenav md-nav-list { display: block; } md-sidenav md-nav-list :hover { background-color: rgb(250, 250, 250); } md-sidenav md-nav-list .md-list-item { cursor: pointer; } .side-navigation { padding-top: 0; } md-nav-list.side-navigation a[md-list-item] > .md-list-item > span.title { margin-right: 10px; } md-nav-list.side-navigation a[md-list-item] > .md-list-item { -webkit-font-smoothing: antialiased; letter-spacing: .14px; } md-list a[md-list-item] .md-list-item, md-list md-list-item .md-list-item, md-nav-list a[md-list-item] .md-list-item, md-nav-list md-list-item .md-list-item { display: flex; flex-direction: row; align-items: center; box-sizing: border-box; height: 48px; padding: 0 16px; } button.my-fab { position: absolute; right: 20px; bottom: 10px; } md-card { margin: 1em; } md-toolbar-row { justify-content: space-between; } .done { position: fixed; bottom: 20px; left: 20px; color: white; } md-nav-list.side-navigation a[md-list-item] { position: relative; } md-list a[md-list-item], md-list md-list-item, md-nav-list a[md-list-item], md-nav-list md-list-item { display: block; } md-list a[md-list-item], md-list md-list-item, md-nav-list a[md-list-item], md-nav-list md-list-item { color: #000; } md-nav-list a { text-decoration: none; color: inherit; } a { color: #039be5; text-decoration: none; -webkit-tap-highlight-color: transparent; } .no-padding { padding: 0 !important; }
به همین راحتی برنامه نمونه با طراحی متریال آماده است.
صفحات مودال در بوت استرپ 3
برای نمایش اطلاعات به صورت فقط خواندنی میشود از این روش استفاده کرد:
<a id="@item.Id" class="detail" data-toggle="modal" data-target=".bs-example-modal-lg" href="#">جزئیات</a> <div class="modal fade bs-example-modal-lg" tabindex="-1" role="dialog" aria-labelledby="myLargeModalLabel" aria-hidden="true"> <div class="modal-dialog"> <div class="modal-content"> // نتیجه اطلاعات </div><!-- /.modal-content --> </div><!-- /.modal-dialog --> </div><!-- /.modal --> <script type="text/javascript"> $(function() { $('.detail').click(function () { var selectedId = $(this).attr('id'); // نتیجه اکشن متد زیر یک پارشال ویو است $.post('@Url.Action("GetRequestById")' + "/" + selectedId, function (data) { $('.modal-content').html(data); }); }); }); </script>
.jqGrid('navButtonAdd', "#pager", { caption: "نوار ابزار جستجو", title: "Search Toolbar", buttonicon: 'ui-icon-search', onClickButton: function () { toolbarSearching(); $('#list')[0].toggleToolbar(); } });
دریافت و نصب افزونهی SQL Server مخصوص VSCode
برای افزودن این افزونه، ابتدا در برگهی Extensions، عبارت mssql را جستجو کرده و سپس آنرا نصب کنید:
پس از نصب آن، مرحلهی بعد، ایجاد یک فایل خالی با پسوند sql است.
انجام اینکار ضروری است و شبیه به حالت نصب افزونهی #C میباشد. به این ترتیب وابستگیهای اصلی آن دریافت، نصب و فعال خواهند شد. این ابزارها نیز سورس باز بوده و موتور SQL Formatter، اجرای SQL و Intellisense آنرا فراهم میکند و چون مبتنی بر NET Core. تهیه شدهاست، چندسکویی است.
تا اینجا مزیتی را که به دست خواهیم آورد Syntax highlighting و Intellisense جهت درج واژههای کلیدی عبارات SQL است:
و یا اگر بر روی فایل sql جاری کلیک راست کنیم، گزینهی Format Document آن سبب میشود تا کدهای SQL نوشته شده، با فرمتی استاندارد، مرتب و یکدست شوند:
بنابراین اگر علاقمندید تا فایلها و عبارات SQL خود را فرمت کنید، این افزونهی سبک وزن چندسکویی، یک چنین قابلیت توکاری را به همراه دارد.
همچنین اگر علاقمندید به یک کتابخانهی سورس باز چندسکویی SQL Formatter و SQL Parser دات نتی دسترسی داشته باشید، کدهای Microsoft/sqltoolsservice در دسترس هستند.
اتصال به SQL Server و کار با آن
پس از نصب مقدماتی افزونهی mssql، دکمههای ctrl+shift+p (و یا F1) را فشرده و عبارت sql را جستجو کنید:
در اینجا سایر قابلیتهای این افزونهی نصب شده را میتوان مشاهده کرد. در لیست ظاهر شده، گزینهی Connect را انتخاب کنید. بلافاصله گزینهی انتخاب پروفایل ظاهر میشود. چون هنوز پروفایلی را تعریف نکردهایم، گزینهی Create connection profile را انتخاب خواهیم کرد:
در ادامه باید نام سرور را وارد کرد. یا میتوانید نام سرور کامل SQL خود را وارد کنید و یا اگر با LocalDB کار میکنید نیز امکان اتصال به آن با تایپlocaldb\MSSQLLocalDB وجود دارد:
سپس نام بانک اطلاعاتی را که میخواهیم به آن متصل شویم ذکر میکنیم:
در مرحلهی بعد، باید نوع اعتبارسنجی اتصال مشخص شود:
چون در ویندوز هستیم، میتوان گزینهی Integrated را نیز انتخاب کرد (یا همان Windows Authentication).
در آخر، جهت تکمیل کار و دخیرهی این اطلاعات وارد شده، میتوان نام پروفایل دلخواهی را وارد کرد:
اکنون کار اتصال به این بانک اطلاعاتی انجام شده و اگر به status bar دقت کنید، نمایش میدهد که در حال به روز رسانی اطلاعات intellisense است.
برای نمونه اینبار دیگر intellisense ظاهر شده منحصر به درج واژههای کلیدی SQL نیست. بلکه شامل تمام اشیاء بانک اطلاعاتی که به آن متصل شدهایم نیز میباشد:
در ادامه برای اجرا این کوئری میتوان دکمههای Ctrl+Shift+E را فشرد و یا ctrl+shift+p (و یا F1) را فشرده و در منوی ظاهر شده، گزینهی execute query را انتخاب کنید (این گزینه بر روی منوی کلیک راست ظاهر شدهی بر روی فایل sql جاری نیز قرار دارد):
نگاهی به محل ذخیره سازی اطلاعات اتصال به بانک اطلاعاتی
پروفایلی را که در قسمت قبل ایجاد کردیم، در منوی File->Preferences->Settings قابل مشاهده است:
// Place your settings in this file to overwrite the default settings { "workbench.colorTheme": "Default Light+", "files.autoSave": "afterDelay", "typescript.check.tscVersion": false, "terminal.integrated.shell.windows": "cmd.exe", "workbench.iconTheme": "material-icon-theme", "vsicons.dontShowNewVersionMessage": true, "mssql.connections": [ { "server": "(localdb)\\MSSQLLocalDB", "database": "TestASPNETCoreIdentityDb", "authenticationType": "Integrated", "profileName": "testLocalDB", "password": "" } ] }
برای مثال پروفایلی را که تعریف کردیم، در دفعات بعدی انتخاب گزینهی Connect، به صورت ذیل ظاهر میشود:
تهیهی خروجی از کوئری اجرا شده
اگر به نوار ابزار سمت راست نتیجهی کوئری اجرا شده دقت کنید، سه دکمهی تهیهی خروجی با فرمتهای csv، json و اکسل نیز در اینجا قرار داده شدهاست:
برای مثال اگر گزینهی json آنرا انتخاب کنید، بلافاصله نام فایلی را پرسیده و سپس این نتیجه را با فرمت JSON نمایش میدهد:
ضمن اینکه حتی میتوان سطرها و سلولهای خاصی را نیز از این خروجی انتخاب کرد و سپس با کلیک بر روی آنها، تنها از این انتخاب، یک خروجی ویژه را تهیه نمود:
مشاهدهی ساختار اشیاء
اگر بر روی هر کدام از اجزای یک کوئری SQL متصل به بانک اطلاعاتی، کلیک راست کنیم، گزینهی Go to definition نیز ظاهر میشود:
با انتخاب آن، بلافاصله عبارت کامل CREATE TABLE [dbo].[AppRoles] ظاهر میشود که در اینجا میتوان ساختار این جدول را به صورت یک عبارت SQL مشاهده کرد.
تغییر تنظیمات افزونهی MSSql
در منوی File->Preferences->Settings با جستجوی mssql میتوان تنظیمات پیش فرض این افزونه را یافت. برای مثال اگر میخواهید تا SQL Formatter آن به صورت خودکار تمام واژههای کلیدی را با حروف بزرگ نمایش دهد، گزینهی mssql.format.keywordCasing را انتخاب کنید. در کنار آن آیکن قلم ویرایش ظاهر میشود. با کلیک بر روی آن، منوی انتخاب uppercase را خواهیم داشت:
پس از این تغییر، اکنون بر روی صفحه کلیک راست کرده و گزینهی Format Document را انتخاب کنید. در این حالت علاوه بر تغییر فرمت سند SQL جاری، تمام واژههای کلیدی آن نیز uppercase خواهند شد.
- Bar and Column
- Line and Vertical Line
- Area and Vertical Area
- Bullet
- Pie and Donut
- Scatter
- Scatter Line
- Bubble
-
Radar and Polar
برای رسم نمودار میتوانیم به صورت زیر عمل کنیم:
1- ابتدا باید استایلهای مربوط به Data Visualization را به صفحه اضافه کنیم:
<link href="Content/kendo.dataviz.min.css" rel="stylesheet" /> <link href="Content/kendo.dataviz.default.min.css" rel="stylesheet" />
<div id="chart"></div>
<div id="chart" style="width: 400px; height: 600px"></div>
$("#chart").kendoChart();
همانطور که مشاهده میکنید هیچ دادهایی را هنوز برای چارت تعیین نکردهایم؛ در نتیجه همانند تصویر فوق یک چارت خالی بر روی صفحه نمایش داده میشود. برای چارت فوق میتوانیم خواصی از قبیل عنوان و ... را تعیین کنیم:
$("#chart").kendoChart({ title: { text: "چارت آزمایشی" } });
نمایش دادهها بر روی چارت:
دادهها را میتوان هم به صورت local و هم به صورت remote دریافت و بر روی چارت نمایش داد. اینکار را میتوانیم توسط قسمت series انجام دهیم:
$("#chart").kendoChart({ title: { text: "عنوان چارت" }, series: [ { name: "دادههای چارت", data: [200, 450, 300, 125] } ] });
$("#chart").kendoChart({ title: { text: "عنوان چارت" }, series: [ { name: "دادههای چارت", data: [200, 450, 300, 125] } ], categoryAxis: { categories: [2000, 2001, 2002, 2003] } });
دریافت اطلاعات از سرور:
کدهای سمت سرور:
public class ProductsController : ApiController { public IEnumerable<ProductViewModel> Get() { var products = _productService.GetAllProducts(); var query = products.GroupBy(p => p.Name).Select(p => new ProductViewModel { Value = p.Key, Count = p.Count() }); return query; } } public class ProductViewModel { public string Value { get; set; } public int Count { get; set; } }
سپس برای دریافت اطلاعات از سمت سرور باید DataSource مربوط به چارت را مقداردهی کنیم:
var productsDataSource = new kendo.data.DataSource({ transport: { read: { url: "api/products", dataType: "json", contentType: 'application/json; charset=utf-8', type: 'GET' } }, error: function (e) { alert(e.errorThrown.stack); }, pageSize: 5, sort: { field: "Id", dir: "desc" } }); $("#chart").kendoChart({ title: { text: "عنوان چارت" }, dataSource: productsDataSource, series: [ { field: "Count", categoryField: "Value", aggregate: "sum" } ] });
موارد فوق را میتوانیم به صورت یک افزونه نیز کپسوله کنیم.
کدهای افزونه jquery.ChartAjax:
(function($) { $.fn.ShowChart = function(options) { var defaults = { url: '/', text: 'نمودار دایره ایی', theme: 'blueOpal', font: '13px bbc-nassim-bold', legendPosition: 'left', seriesField: 'Count', seriesCategoryField: 'Value', titlePosition: 'top', chartWidth: 400, chartHeight: 400 }; var options = $.extend(defaults, options); return this.each(function() { var chartDataSource = new kendo.data.DataSource({ transport: { read: { url: options.url, dataType: "json", contentType: 'application/json; charset=utf-8', type: 'GET' } }, error: function (e) { // handle error } }); $(this).kendoChart({ chartArea: { height: options.chartHeight }, theme: options.theme, title: { text: options.text, font: options.font, position: options.titlePosition }, legend: { position: options.legendPosition, labels: { font: options.font } }, seriesDefaults: { labels: { visible: false, format: "{0}%" } }, dataSource: chartDataSource, series: [ { type: "pie", field: options.seriesField, categoryField: options.seriesCategoryField, aggregate: "sum", } ], tooltip: { visible: true, template: "${category}: ${value}", font: options.font } }); }); }; })(jQuery);
chartArea : جهت تعیین طول و عرض چارت
theme : جهت تعیین قالبهای از پیشتعریف شده:
- Black
- BlueOpal
- Bootstrap
- Default
- Flat
- HighContrast
- Material
- MaterialBlack
- Metro
- MetroBlack
- Moonlight
- Silver
-
Uniform
title : جهت تعیین عنوان چارت
legend : جهت تنظیم ویژگیهای قسمت گروهبندی چارت
tooltip : جهت تنظیم ویژگیهای مربوط به نمایش tooltip در هنگام hover بر روی چارت.
لازم به ذکر است در قسمت series میتوانید نوع چارت را تعیین کنید.
$('#chart').ShowChart({ url: "/Report/ByUnit", legendPosition: "bottom" });
دریافت سورس مثال جاری (KendoChart.zip)