jQuery Tips #2
در این پست قصد دارم نحوهی کاربا Cookie را با استفاده از jQuery برسی کنم و در پست بعدی یک مثال عملی را برسی میکنیم.
همانطور که میدانید کوکی یکی از اشیاء بسیار مهم برای نگه داری دادهها در بحث وب میباشد که یک فایل متنی است که سمت Client ذخیره میشود. و ما زمانی که از کتابخانه jQuery استفاده میکنیم خیلی مهم است که بدانیم چگونه باید با Cookieها کار کرد.
برای کار با کوکیها در jQuery باید از Plugin های موجود استفاده کرد . برای ایجاد یک Cookie ابتدا فایل jQuery و سپس این کتابخانه را به صفحه مورد نظر اضافه نموده و کد زیر را برای ایجاد یک کوکی مینویسیم
<script src="jquery-1.7.1.min.js" type="text/javascript"></script> <script src="jquery.cookie.js" type="text/javascript"></script> <script type="text/javascript"> $(function () { $.cookie("TestCookie", "Test Cookie By Mohsen Bahrzadeh "); }); </script>
حال پروژه را اجرا میکنیم. و در تصویر زیر مشاهده میکنید که کوکی ما ایجاد شده است
یکی از آیتمهای بسیار مهم در کوکیها تعریف زمان انقضاء کوکی است برای ست کردن تاریخ از کد زیر استفاده میکنیم
$(function () { $.cookie("TestCookie", "Test Cookie By Mohsen Bahrzadeh ", { expires: 7 }); });
$(function () { alert($.cookie("TestCookie")); });
$(function () { $.cookie("TestCookie", null); });
Iterators در ES 6
هر Iterator شیءایی است که دارای متد next میباشد. هر بار که این متد فراخوانی میشود، عضو بعدی مجموعه، بازگشت داده خواهد شد. خروجی هر مرحله، درون یک شیء با دو خاصیت value و done قرار داده میشود. value، مقدار مرحلهی بعد است و done مشخص میکند که آیا به پایان مجموعه رسیدهایم یا خیر (بنابراین در اینجا تعداد اعضای Iterator مشخص نیست).
مثالی از پیمایش یک آرایه با چندین روش مختلف
در مثال زیر، آرایهای از اعداد را داریم که نیاز است جمع اعضای آن محاسبه شود:
let sum = 0; let numbers = [1,2,3,4];
// for loop sum = 0; for(let i =0; i < numbers.length; i++){ sum += numbers[i]; } //sum = 10
// for in sum = 0; for(let i in numbers) { sum += numbers[i]; } //sum = 10
// iterator sum = 0; let iterator = number.values(); let next = iterator.next(); while(!next.done){ sum += next.value; next = iterator.next(); } //sum = 10
مرحلهی بعدی، فراخوانی متد next این Iterator است. این عملیات باید در طی یک حلقه، تا پایان کار Iterator انجام شود. همانطور که در ابتدای بحث نیز عنوان شد، خروجی متد next یک شیء است که دارای خواص value و done میباشد. اگر done مساوی true شد، یعنی به پایان کار پیمایش رسیدهایم.
البته هدف از این مثال، صرفا نمایش سطح پایین کار با Iterators بود. در عمل از حلقهی جدیدی به نام for of برای انجام این پیمایش استفاده میشود.
معرفی حلقهی for of
جاوا اسکریپت سالها است که دارای حلقهی for in میباشد و نمونهای از کاربرد آنرا در مثال قبل مشاهده کردید. اگر این حلقه بر روی آرایهها فراخوانی شود، هربار ایندکس پیمایش شده را بازگشت میدهد و اگر بر روی یک شیء فراخوانی شود، خواص آن شیء را بازگشت میدهد:
var person = { first: "Vahid", last "N" }; for(let i in person) { console.log(person[i]); }
چون این حلقه صرفا ایندکسها و کلیدها را بازگشت میدهد، جهت کار با Iterators که نیاز است به مقادیر اعضاء دسترسی پیدا کنیم، مناسب نیست. به همین جهت در ES 6، حلقهی جدیدی به نام for of برای کار با Iterators معرفی شدهاست:
let numbers = [1,2,3,4]; for(let i of numbers) { console.log(i); }
let sum = 0; let numbers = [1,2,3,4]; for(let n of numbers){ sum += n; }
for of یکی از روشهای پیمایش Iterators است. پارامترهای rest و همچنین Array.from نیز چنین قابلیتی را فراهم میکنند.
امکان پیاده سازی Iterators سفارشی نیز وجود دارد که پیشنیاز آن، درک مبحث جدید Symbols است که به صورت جداگانهای بررسی خواهد شد.
مقایسهای بین امکانات Rider و Visual Studio
راه کار اولیه که به ذهنم رسید قرار دادن کدها در یک تابع جدید مانند کد زیر است :
function myfunc(){ $('#test')....; }
window.onload = myfunc;
و خلاصه اینکه در لینک برای این مسئله هم راه کار وجود دارد و آن :
window.addEventListener('load', myfunc1, false); window.addEventListener('load', myfunc2, false); ...
چرا بعد از عملیات ajax ایی دیگه کار نمیده؟
از کد زیر برای نمایش استفاده شده:
<script type="text/javascript"> $(document).ready(function () { $("[rel='tooltip']").tooltip({ placement: 'top', trigger: 'hover' }); }); </script>
ولی بعد از انجام این دستور دیگه کار نمیده:
@Ajax.ActionLink(" ", MVC.Admin.ContactUs.ActionNames.List, MVC.Admin.ContactUs.Name, new { bywriter = ViewBag.bywriter, bydate = ViewBag.bydate, byisread = ViewBag.byisread, byisshow = ViewBag.byisshow, page = max, count = ViewBag.COUNT }, new AjaxOptions { HttpMethod = "Post", InsertionMode = InsertionMode.Replace, OnBegin = "showLoading", UpdateTargetId = "listdiv", OnComplete = "hideLoading" }, new { @class = "glyphicon glyphicon-backward nodecoration", @rel = "tooltip", @title = "صفحه آخر" })
var image = document.createElement("img"); image.setAttribute("src", "logo.png"); React.createElement("img", { src : "logo.png" });
Virtual DOM
تفاوت در ساخت تگهای HTML به صورت مجازی بین JavaScript و React این است که React وضعیت تگهایی را که میسازد دنبال میکند. برای مثال فرض کنید نام سه محصول را در یک تگ < ul > نشان دادهایم. React وضعیت اصلی این تگ را که به مرورگر فرستاده، در حافظه دارد و همچنین در اثر تغییر منبع دادهای که برای < ul > مشخص کردهایم (که میتواند ورود اطلاعات به صورت Ajax باشد (مثلا اضافه شدن یک محصول جدید)) وضعیت جدیدی را برای تگ < ul > در حافظه ایجاد میکند. با وجود دو وضعیت برای یک تگ در حافظه، React میتواند تفاوت بین آنها را تشخیص داده و تگ را به روز کند. به این حالت عملکرد React ، اصطلاحا Virtual DOM میگویند.
React رابط کاربری را به صورت یک مدل میبیند و این مدل را با توجه به وضعیت اصلی آن در حافظه دوباره میسازد. برای React مهم نیست که ماهیت تغییر چیست. فقط وضعیتها را مثل دو عکس میبیند و میفهمد که آیا چیزی عوض شدهاست یا نه. دیالوگ React با مرورگر اینطور است: ای تگ < ul > این لیست را نشان بده (لیستی با سه محصول)، و بعد میگوید: ای تگ < ul > این لیست را نشان بده (لیستی با چهار محصول)!
کامپوننتهای React
<a href = “http://google.com”> <img src=”google.png”/> </a> // Components <clickableimage/> <linkimage/>
در کد بالا، بخش اول واضح است. عکسی که قابلیت کلیک شدن را دارد. حال فرض کنید یکی از کامپوننتهای <clickableimage/> یا <linkimage/>، همان تصویر قابل کلیک را ایجاد کنند. با نام گذاری واضح کامپوننتها، خوانایی برنامه بهتر میشود. یعنی میدانیم هر کامپوننت چه کاری را برای ما انجام میدهد. با این تصور که اگر تگهای زیاد و طولانی را در بخش رابط کاربری داریم، ارزش استفاده از کامپوننتهای React مشخص میشود.
قابلیت استفاده مجدد
در React کامپوننتها برای اساس توابع ساخته میشوند. یعنی وقتی یک کامپوننت را صدا بزنیم، در واقع یک تابع را اجرا میکنیم. در نتیجه کامپوننتها رفتار توابع را دارند؛ ورودی میگیرند و خروجی که یک DOM مجازی است را تحویل میدهند. اگر تابعی که مسئول ساخت کامپوننت است وابستگی به توابع یا متغیرهای بیرونی نداشته باشد، میتواند در جای دیگری از برنامه یا برنامهای دیگر مجددا استفاده شود. کد زیر نشان میدهد که چطور کامپوننتهای React ساخته میشوند.var ClickableImage = function(props) { return ( <a href={props.href}> <img src={props.src} /> </a> ); }; ReactDOM.render( <ClickableImage href="http://google.com" src="logo.png" />, document.getElementById("targetDivId"));
استفاده از Tag Helpers ویژهی ASP.NET Core برای مدیریت محیطهای توسعه و تولید
فایلهای برنامهی تک صفحهای تولید شدهی توسط Angular CLI، در نهایت یک چنین شکلی را خواهند داشت:
این فایلها نیز در حالت توسعه تهیه شدهاند. در یک برنامهی واقعی، صفحهی سادهی index.html تولیدی آن، تنها میتواند یک قالب شروع به کار باشد و نه فایل نهایی که قرار است ارائه شود. نیاز است به این فایل تگهای بیشتری را اضافه کرد و سفارشی سازیهای خاصی را به آن اعمال نمود. در این حالت با توجه به بازنویسی و تولید مجدد این فایل در هر بار ساخت برنامه، میتوان از فایل Layout پروژهی ASP.NET Core جاری استفاده کرد. به این ترتیب از مزایای Razor و تمام زیرساختی که در اختیار داریم نیز محروم نخواهیم شد.
بنابراین تنها کاری را که باید انجام دهیم، کپی ساختار فایل index.html تولیدی به فایل Layout برنامه است.
مشکل! در حالت توسعه، نام فایلهای تولید شده به همین سادگی است که ملاحظه میکنید. اما در حالت ارائهی نهایی، این فایلها به همراه یک هش نیز تولید میشوند (پیاده سازی مفهوم cache busting و اجبار به بهروز رسانی کش مرورگر، باتوجه به تغییر آدرس فایلها)؛ مانند vendor.ea3f8329096dbf5632af.bundle.js
راه حل اول: تولید فایلهای نهایی بدون هش
ng build -prod --output-hashing=none
درکل بهتر است از این روش استفاده نشود، چون با وجود پروکسیهای کش کردن اطلاعات در بین راه، احتمال اینکه کاربران نگارشهای قدیمی برنامه را مشاهده کنند، بسیار زیاد است.
راه حل دوم: تگ Script در ASP.NET Core اجازهی ذکر تمام فایلهای اسکریپت یک پوشه را نیز میدهد
<script type="text/javascript" asp-src-include="*.js"></script>
راه حل واقعی
در اینجا کدهای کامل فایل Views\Shared\_Layout.cshtml را که میتواند جایگزین فایل index.html تولیدی توسط Angular CLI باشد، ملاحظه میکنید:
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <link rel="icon" type="image/x-icon" href="favicon.ico"> <title>ng2-lab</title> <base href="/"> <environment names="Development"> </environment> <environment names="Staging,Production"> <link rel="stylesheet" asp-href-include="~/styles*.css" /> </environment> </head> <body> @RenderBody() <app-root></app-root> <environment names="Development"> <script type="text/javascript" src="/inline.bundle.js"></script> <script type="text/javascript" src="/polyfills.bundle.js"></script> <script type="text/javascript" src="/scripts.bundle.js"></script> <script type="text/javascript" src="/styles.bundle.js"></script> <script type="text/javascript" src="/vendor.bundle.js"></script> <script type="text/javascript" src="/main.bundle.js"></script> </environment> <environment names="Production,Staging"> <script type="text/javascript" asp-src-include="~/inline*.js"></script> <script type="text/javascript" asp-src-include="~/polyfills*.js"></script> <script type="text/javascript" asp-src-include="~/scripts*.js"></script> <script type="text/javascript" asp-src-include="~/vendor*.js"></script> <script type="text/javascript" asp-src-include="~/main*.js"></script> </environment> </body> </html>
[name].[hash].bundle.js
همچنین باید دقت داشت که در حالت توسعه، تمام شیوه نامههای برنامه در فایل styles.bundle.js قرار میگیرند. اما در حالت ارائهی نهایی، این فایل وجود نداشته و با نام کلی styles*.css تولید میشود که باید در head صفحه قرار گیرد (مانند تنظیمات حالت تولید در Layout فوق).
اصلاح قسمت URL Rewrite برنامه
در حالت کار با برنامههای تک صفحهای وب، در اولین درخواست رسیدهی به برنامه ممکن است آدرسی درخواست شود که معادل کنترلر و اکشن متدی را در برنامهی سمت سرور نداشته باشد. در این حالت کاربر را به همان صفحهی index.html هدایت میکنیم تا سیستم مسیریابی سمت کلاینت، کار نمایش آن صفحه را انجام دهد:
app.Use(async (context, next) => { await next(); var path = context.Request.Path.Value; if (path != null && context.Response.StatusCode == 404 && !Path.HasExtension(path) && !path.StartsWith("/api/", StringComparison.OrdinalIgnoreCase)) { context.Request.Path = "/index.html"; await next(); } });
//context.Request.Path = "/index.html"; context.Request.Path = "/"; // since we are using views/shared/_layout.cshtml now.
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید.
<script src="~/Scripts/jquery.validate.min.js" type="text/javascript"></script> <script src="~/Scripts/jquery.validate.unobtrusive.min.js" type="text/javascript"></script>
عدم سازگاری پیش فرض jquery.validate با بعضی از ویجتهای Kendo UI
در حالت استفاده از Kendo UI، این سیستم هنوز هم کار میکند؛ اما با یک مشکل. اگر برای مثال از kendoComboBox استفاده کنید، اعتبارسنجیهای تعریف شده در برنامه، توسط jquery.validate دیده نخواهند شد. برای مثال فرض کنید یک چنین مدلی در اختیار View برنامه قرار گرفته است:
public class OrderDetailViewModel { [StringLength(15)] [Required] public string Destination { get; set; } }
@model Mvc4TestViewModel.Models.OrderDetailViewModel @using (Ajax.BeginForm(actionName: "Index", controllerName: "Home", ajaxOptions:new AjaxOptions(), htmlAttributes: new { id = "Form1", name ="Form1" }, routeValues: new { } )) { @Html.AntiForgeryToken() @Html.ValidationSummary(true) <fieldset> <legend>OrderDetail</legend> <div class="editor-label"> @Html.LabelFor(model => model.Destination) </div> <div class="editor-field"> @Html.TextBoxFor(model => model.Destination, new { @class = "k-textbox" }) @Html.ValidationMessageFor(model => model.Destination) </div> <p> <button class="k-button" type="submit" title="Sumbit"> Sumbit </button> </p> </fieldset> } @section JavaScript { <script type="text/javascript"> $(function () { $("#Destination").kendoComboBox({ dataSource: [ "loc 1", "loc 2" ] }); </script> }
همانطور که در تصویر مشاهده میکنید، با اتصال kendoComboBox به یک فیلد، این فیلد در حالت مخفی قرار میگیرد و ویجت کندو یو آی بجای آن نمایش داده خواهد شد. در این حالت چون در فایل jquery.validate.js چنین تنظیمی وجود دارد:
$.extend( $.validator, { defaults: { //… ignore: ":hidden",
راه حل آن نیز سادهاست. تنها باید خاصیت ignore را بازنویسی کرد و تغییر داد:
<script type="text/javascript"> $(function () { var form = $('#Form1'); form.data('validator').settings.ignore = ''; // default is ":hidden". }); </script>
<script type="text/javascript"> $.validator.setDefaults({ ignore: "" }); </script>
یکپارچه کردن سیستم اعتبارسنجی Kendo UI با سیستم اعتبارسنجی ASP.NET MVC
در مطلب «اعتبار سنجی ورودیهای کاربر در Kendo UI» با زیرساخت اعتبارسنجی Kendo UI آشنا شدید. برای اینکه بتوان این سیستم را با ASP.NET MVC یکپارچه کرد، نیاز است دو کار صورت گیرد:
الف) تعریف فایل kendo.aspnetmvc.js به صفحه اضافه شود:
<script src="~/Scripts/kendo.aspnetmvc.js" type="text/javascript"></script>
<script type="text/javascript"> $(function () { $("form").kendoValidator(); }); </script>
فایل kendo.aspnetmvc.js که در بستهی مخصوص Kendo UI تهیه شده برای ASP.NET MVC موجود است (در پوشهی js آن)، عملکردی مشابه فایل jquery.validate.unobtrusive مایکروسافت دارد. کار آن وفق دادن و ترجمهی اعتبارسنجی unobtrusive به روش Kendo UI است.
این فایل را از اینجا میتوانید دریافت کنید:
kendo.mvc.zip
البته باید دقت داشت که در حال حاضر فقط ویژگیهای ذیل از ASP.NET MVC توسط kendo.aspnetmvc.js پشتیبانی میشوند:
Required StringLength Range RegularExpression
public static List<T> CreateGenericListFromAnonymous<T>(object obj, T example) { var newquery = new List<T>(); var constructor = typeof(T).GetConstructors( System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance ).OrderBy(c => c.GetParameters().Length).First(); foreach (var item in ((IEnumerable<object>)obj)) { var mapobj = new object[example.GetType().GetProperties().Count()]; int counter = 0; foreach (var itemmap in example.GetType().GetProperties()) { object value = item.GetType().GetProperty(itemmap.Name).GetValue(item, null); Type t = itemmap.PropertyType; mapobj[counter] = Convert.ChangeType(value, t); counter++; } newquery.Add((T)constructor.Invoke(mapobj)); } return newquery; } var context = new Models.EntitiesConnection(); var query = context.Posts.Select(pst => new { id = pst.id, Title = pst.Title, Likes = pst.Likes, Unlikes = pst.Unlikes }).OrderByDescending(c => c.id); object test_custom_casting = CreateGenericListFromAnonymous(query.ToList(), new { id = 0, Title = string.Empty });