مطالب
بررسی angular.bootstrap
در پست‌های قبلی با مفهوم ng-app آشنا شدید. دایرکتیو ng-app برای استفاده از راه انداز خودکار فریم ورک Angular (معروف به auto-bootstrap) استفاده می‌شود. در حالت پیش فرض، به ازای هر سند Html فقط می‌توان یک ماژول در Angular تعریف کرد. در سند مربوطه اولین المانی که دارای دایرکتیو ng-app باشد به عنوان عنصر ریشه در نظر گرفته می‌شود و تمام عناصر تعریف شده در محدوده این دایرکتیو قایل استفاده برای ماژول مورد نظر خواهد بود. سایر عناصر حتی اگر ng-app یکسان داشته باشند نادیده گرفته می‌شوند.
ابتدا یک مثال زیر را به روش auto-bootstrap بررسی  می‌کنیم:
<div ng-app="myApp">
    <div ng-controller="myController as ctrl">
       <span>ng-app #1</span> {{ctrl.firstName}} {{ctrl.lastName}}
    </div>
</div>

<div ng-app="myApp">
    <div ng-controller="myController as ctrl">
        <span>ng-app #2</span> {{ctrl.firstName}} {{ctrl.lastName}}
    </div>
</div>

@section scripts
{
    <script type="text/javascript" src="~/scripts/Modules/module8.js"></script>
}
در کنترلر مورد نظر نیز تعاریف به صورت زیر خواهد بود:
var app = angular.module('myApp', []);

app.controller('myController', function ()
{
    this.firstName = "Masoud";
    this.lastName = "Pakdel";
});
در مثال بالا دو تگ div وجود دارد که به صورت مشترک با استفاده از دایرکتیو ng-app به یک ماژول اشاره می‌کنند. طبق گفته‌ها بالا در روش auto-bootstrap اولین عنصری که دارای دایرکتیو ng-app باشد به عنوان محدوده ماژول مورد استفاده قرار خواهد گرفت در نتیجه سایر المان‌ها (در اینجا منظور تگ div دوم است)نادیده گرفته خواهند شد.  پس خروجی به صورت زیر می‌شود:

اما اگر قصد داشته باشیم که در یک سند html دو نقطه شروع تعریف کنیم در حالی که هر کدام از یک منبع داده استفاده نمایند باید bootstrap برنامه را به صورت دستی تعیین کرد. برای این کار کافیست از دستور angular.bootstrap به صورت زیر استفاده نماییم:
پیاده سازی مثال بالا
<div id="myAppContainer1">
    <div ng-controller="myController as ctrl">
        <span>ng-app #1</span> {{ctrl.firstName}} {{ctrl.lastName}}
    </div>
</div>

<div id="myAppContainer2">
    <div ng-controller="myController as ctrl">
        <span>ng-app #2</span> {{ctrl.firstName}} {{ctrl.lastName}}
    </div>
</div>

@section scripts
{
    <script type="text/javascript" src="~/scripts/Modules/module8.js"></script>
}
اولین تغییر مورد نظر این است که، دایرکتیو ng-app  حذف شد و به جای آن id برای تگ div تعیین کردیم. در فایل کنترلر مورد نظر نیز تغییر زیر را اعمال می‌کنیم:
var app = angular.module('myApp', []);

app.controller('myController', function ()
{
    this.firstName = "Masoud";
    this.lastName = "Pakdel";
});

angular.bootstrap(document.getElementById("myAppContainer1"), ["myApp"]);

angular.bootstrap(document.getElementById("myAppContainer2"), ["myApp"]);
با استفاده از دستور angular.bootstrap می‌توان بر اساس id تعیین شده تگ مورد نظر در سند را به دست آورد و ماژول مورد نظر را به آن نسبت داد.

خروجی مثال بالا:


برخلاف حالت قبل هر دو نقطه شروع به یک منبع داده اشاره می‌کنند و محدودیت حالت قبل برطرف می‌شود.
مطالب
بازنویسی رویدادها در jQuery
فرض کنید می‌خواهید برای تمام دکمه‌های حذف، در کل پروژه، قبل از انجام عمل اصلی، یک confirm را به کاربر نشان دهید تا اگر کاربر بر روی کلید تایید، کلیک نمود، عمل مورد نظر انجام شود. برای چنین کاری در یک layout اصلی (و یا یک فایل js ) کدی شبیه به قطعه کد زیر را نوشته‌ایم:
$(document).ready(function () {
    $(document).on('click', '.confirm', function () {
        alert("Clicked Me !");
    });
});

حال فرض کنید در یکی از صفحات  قصد داریم اگر بر دکمه‌ای که قبلا برای آن رویدادی نوشته‌ایم  (منظور کد‌های بالا می‌باشد)، کلیک شد، یکسری عملیات دیگر، جدای از آن عملیات انجام شود. برای اینکار در صفحه مربوطه، کدی شبیه زیر را نوشته‌ایم :
$(document).ready(function () {
    $(document).on('click', '.confirm', function () {
        alert("Clicked Me On nested Page !");
    });
});

اگر پروژه را اجرا نمایید و بر روی دکمه مربوطه کلیک نماییم، هر دو Event نوشته شده، اجرا خواهند شد. در چنین حالتی تکلیف چیست و چکار باید کرد؟ جواب: selector را تغییر دهیم :خیر.
برای این کار می‌توان رویداد کلیک را برای تگ مورد نظر با استفاده از متد off بازنویسی کنیم:
$(document).off('click', '.confirm');

- روش‌هایی دیگر برای انجام این کار:
$(".confirm").removeAttr("onclick");
$( ".confim").unbind( "click" );
مطالب
بارگذاری یک یوزرکنترل با استفاده از جی‌کوئری

مزیت استفاده از یوزر کنترل‌ها، ماژولار کردن برنامه است. برای مثال اگر صفحه جاری شما قرار است از چهار قسمت اخبار، منوی پویا ، سخن روز و آمار کاربران تشکیل شود، می‌توان هر کدام را توسط یک یوزر کنترل پیاده سازی کرده و سپس صفحه اصلی را از کنار هم قرار دادن این یوزر کنترل‌ها تهیه نمود.
با این توضیحات اکنون می‌خواهیم یک یوزکنترل 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());
}
}
}
تا این‌جا متد وب سرویسی را داریم که می‌تواند مسیر یک یوزر کنترل را به همراه خواص عمومی آن‌را دریافت کرده و سپس یوزر کنترل را رندر نموده و حاصل را به صورت HTML به شما تحویل دهد. با استفاده از reflection خواص عمومی یوزر کنترل یافت شده و مقادیر لازم به آن‌ها پاس می‌شوند.

چند نکته:
الف) وب کانفیگ برنامه 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("امکان اتصال به سرور در این لحظه مقدور نیست. لطفا مجددا سعی کنید.");
}
});
});
};
برای اینکه با کلیات این روش آشنا شوید می‌توان به مقاله "بررسی وجود نام کاربر با استفاده از jQuery Ajax در ASP.Net" مراجعه نمود که از ذکر مجدد آن‌ها خودداری می‌شود. همچنین در مورد نوشتن یک پلاگین جی‌کوئری در مقاله "افزونه جملات قصار jQuery" توضیحاتی داده شده است.
عمده کاری که در این پلاگین صورت می‌گیرد فراخوانی متد 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 استفاده شد.

ابزار دیباگ:
بهترین ابزار برای دیباگ این نوع اسکریپت‌ها استفاده از افزونه فایرباگ فایرفاکس است. برای مثال مطابق تصویر زیر، یوزر کنترلی فراخوانی شده است که در سرور وجود ندارد:


دریافت مثال فوق


مطالب
تبدیل تاریخ میلادی به شمسی در SSIS به کمک سی شارپ
برای تبدیل تاریخ میلادی به تاریخ شمسی در package‌های SSIS می‌توان از زبان سی شارپ استفاده کرد . بدین طریق می‌توان در طی عملیات ETL و هنگام transform کردن داده‌ها ، عملیات تبدیل از میلادی به شمسی را انجام داد . عملیات تبدیل داده در این مثال به کمک Script Component انجام می‌شود. 

برای این کار از داده‌های موجود در پایگاه داده [AdventureWorksLT2008R2].[SalesLT].[Address] استفاده می‌کنم . 
برای این کار یک package تعریف می‌کنم و برای استخراج داده‌ها یک OLEDB تعریف می‌کنم . برای تبدیل داده‌ها از Script Component و برای گرفتن خروجی آزمایشی از union استفاده می‌کنم : 

دایره‌های قرمز رنگ مشخص شده در تصویر ، بیانگر data viewerهای تعریف شده است. 

در تنظیمات dataSource مانند زیر عمل می‌کنیم :
دستور زیر فقط برای نمایش یک نمونه از کارکرد عملیات مورد نظر در این مثال می‌باشد 


نمونه خروجی مانند زیر می‌باشد : 




سپس برای تنظیمات Script Component به ترتیب زیر عمل می‌کنیم :

در قسمت Input column ستون هایی را که به عنوان پارامتر می‌توانیم با آنها کار کنیم ، تعریف می‌کنیم : (دقت شود که تغییر نام متغیر‌ها ، در کد‌ها اعمال می‌شوند .) 




حال می‌خواهم ستون‌های تاریخ میلادی و شمسی را برای خروجی تعریف کنم :

 


همانطور که مشاهده می‌کنید ، نوع داده ای برای خروجی را رشته تعریف کردم.

سپس به قسمت script بر می‌گردیم (سمت چپ پنجره ) و روی Edit Script کلیک می‌کنیم : (در صورتی که تمایل به کد نویسی با VB را دارید در همین قسمت می‌توانید آن را تنظیم کنید) 



پنجره ای مانند زیر برای ویرایش کد‌ها ، باز می‌شود 


همانطور که مشاهده می‌کنید درون این کلاس 4 متد تبدیل تاریخ را پیاده کردم و از آنها در متد input0_processInputRow استفاده کردم . این کار نیازی به پیاده سازی حلقه ندارد و به راحتی می‌توان آنها را روی سطر‌های دلخواه تعریف کرد . خروجی نمایش داده شده در data viewer‌ها مانند زیر می‌باشند : 


قبل از اعمال تبدیلات : 



بعد از اعمال تبدیلات : 


موفق باشید 

مطالب
تبدیل یک View به رشته و بازگشت آن به همراه نتایج JSON حاصل از یک عملیات Ajax ایی در ASP.NET MVC

ممکن است بخواهیم در پاسخ یک تقاضای Ajax ایی، اگر عملیات در سمت سرور با موفقیت انجام شد، خروجی یک Controller action را به کاربر نهایی نشان دهیم. در چنین سناریویی لازم است که بتوانیم خروجی یک action را بصورت رشته برگردانیم. در این مقاله به این مسئله خواهیم پرداخت .
فرض کنید در یک سیستم وبلاگ ساده قصد داریم امکان کامنت گذاشتن بصورت
Ajax را پیاده سازی کنیم. یک ایده عملی و کارآ این است: بعد از اینکه کاربر متن کامنت را وارد کرد و دکمه‌ی ارسال کامنت را زد، تقاضا به سمت سرور ارسال شود و اگر سرور پیغام موفقیت را صادر کرد، متن نوشته شده توسط کاربر را به کمک کدهای JavaScript و در همان سمت کلاینت بصورت یک کادر کامنت جدید به محتوای صفحه اضافه کنیم. بنده در اینجا برای اینکه بتوانم اصل موضوع مورد بحث را توضیح دهم، از یک سناریوی جایگزین استفاده می‌کنم؛ کاربر موقعیکه دکمه ارسال را زد، تقاضا به سرور ارسال میشود. سرور بعد از انجام عملیات، تحت یک شی  JSON هم نتیجه‌ی انجام عملیات و هم محتوای HTML نمایش کامنت جدید در صفحه را به سمت کلاینت ارسال خواهد کرد و کلاینت در صورت موفقیت آمیز بودن عملیات، آن محتوا را به صفحه اضافه می‌کند.

با توجه به توضیحات داده شده، ابتدا یک شیء نیاز داریم تا بتوانیم توسط آن نتیجه‌ی عملیات Ajax ایی را بصورت  JSON به سمت کلاینت ارسال کنیم:

public class MyJsonResult
{
  public bool success { set; get; }
  public bool HasWarning { set; get; }
  public string WarningMessage { set; get; }
  public int errorcode { set; get; }
public string message {set; get; }   public object data { set; get; }  }

سپس به متدی نیاز داریم که کار تبدیل نتیجه‌ی action را به رشته، انجام دهد:

public static string RenderViewToString(ControllerContext context,
    string viewPath,
    object model = null,
    bool partial = false) 
{
    ViewEngineResult viewEngineResult = null;
    if (partial) viewEngineResult = ViewEngines.Engines.FindPartialView(context, viewPath);
    else viewEngineResult = ViewEngines.Engines.FindView(context, viewPath, null);
    if (viewEngineResult == null) throw new FileNotFoundException("View cannot be found.");
    var view = viewEngineResult.View;
    context.Controller.ViewData.Model = model;
    string result = null;
    using(var sw = new StringWriter()) {
        var ctx = new ViewContext(context, view, context.Controller.ViewData, context.Controller.TempData, sw);
        view.Render(ctx, sw);
        result = sw.ToString();
    }
    return result;
}
در اینجا موتور View را بر اساس اطلاعات یک View، مدل و سایر اطلاعات Context جاری کنترلر، وادار به تولید معادل رشته‌ای آن می‌کنیم.

فرض کنیم در سمت Controller هم از کدی شبیه به این استفاده میکنیم:
public JsonResult AddComment(CommentViewModel model) {
    MyJsonResult result = new MyJsonResult() {
        success = false;
    };
    if (!ModelState.IsValid) {
        result.success = false;
        result.message = "لطفاً اطلاعات فرم را کامل وارد کنید";
        return Json(result);
    }
    try {
        Comment theComment = model.toCommentModel();
        //EF service factory
        Factory.CommentService.Create(theComment);
        Factory.SaveChanges();
        result.data = Tools.RenderViewToString(this.ControllerContext, "/views/posts/_AComment", model, true);
        result.success = true;
    } catch (Exception ex) {
        result.success = false;
        result.message = "اشکال زمان اجرا";
    }
    return Json(result);
}

و در سمت کلاینت برای ارسال Form به صورت Ajax ایی خواهیم داشت:

@using (Ajax.BeginForm("AddComment", "posts", 
new AjaxOptions()
{
   HttpMethod = "Post", 
   OnSuccess = "AddCommentSuccess", 
   LoadingElementId = "AddCommentLoading"
}, new { id = "frmAddComment", @class = "form-horizontal" }))
{ 
    @Html.HiddenFor(m => m.PostId)
    <label for="fname">@Texts.ContactName</label> 
    <input type="text" id="fname" name="FullName" class="form-control" placeholder="@Texts.ContactName ">
    <label for="email">@Texts.Email</label> 
    <input type="email" id="InputEmail" name="email" class="form-control" placeholder="@Texts.Email">
    <br><textarea name="C_Content" cols="60" rows="10" class="form-control"></textarea><br>
    <input type="submit" value="@Texts.SubmitComments" name="" class="btn btn-primary">
    <div class="loading-mask" style="display:none">@Texts.LoadingMessage</div>
}
در اینجا در صورت موفقیت آمیز بودن عملیات، متد جاوا اسکریپتی AddCommentSuccess فراخوانی خواهد شد.
باید توجه شود Texts در اینجا یک Resource هست که به منظور نگهداری کلمات استفاده شده در سایت، برای زبانهای مختلف استفاده می‌شود (رجوع شود به مفهوم بومی سازی در Asp.net) .

و در قسمت script ‌ها داریم:

<script type="text/javascript">
  function AddCommentSuccess(jsData) {
   if (jsData && jsData.message)
    alert(jsData.message);
   if (jsData && jsData.success) {
    document.getElementById("frmAddComment").reset();
      //افزودن کامنت جدید ساخته شده توسط کاربر به لیست کامنتهای صفحه
    $("#divAllComments").html(jsData.data + $("#divAllComments").html());    
   }
  }
</script>
متد AddCommentSuccess اطلاعات شیء JSON بازگشتی از کنترلر را دریافت و سپس پیام آن‌را در صورت موفقیت آمیز بودن عملیات، به DIV ایی با id مساوی divAllComments اضافه می‌کند.

مطالب
Blazor 5x - قسمت چهارم - مبانی Blazor - بخش 1 - Data Binding
عنوان می‌شود که HTML over Web socket آینده‌ی توسعه‌ی برنامه‌های وب است و این آینده هم اکنون توسط Blazor Server در دسترس است. در این مدل توسعه، ابتدا یک اتصال SignalR برقرار شده و سپس تمام تعاملات بین سرور و کلاینت، از طریق همین اتصال که عموما web socket است، مدیریت می‌شود. به همین جهت در ادامه قصد داریم یک پروژه‌ی Blazor Server را تکمیل کنیم. پس از آن یک پروژه‌ی Blazor WASM را نیز بررسی خواهیم کرد. بنابراین هر دو مدل توسعه‌ی برنامه‌های Blazor را پوشش خواهیم داد. برای این منظور در ابتدا مبانی Blazor را بررسی می‌کنیم که در هر دو مدل یکی است.


تعریف مدل برنامه

در همان پروژه‌ی خالی Blazor Server که در قسمت دوم با دستور dotnet new blazorserver ایجاد کردیم، پوشه‌ی Models را افزوده و کلاس BlazorRoom را در آن تعریف می‌کنیم:
namespace BlazorServerSample.Models
{
    public class BlazorRoom
    {
        public int Id { set; get; }

        public string Name { set; get; }

        public decimal Price { set; get; }

        public bool IsActive { set; get; }
    }
}
سپس برای اینکه مدام نیاز به تعریف فضای نام آن در فایل‌های مختلف razor. برنامه نباشد، به فایل Imports.razor_ مراجعه کرده و سطر زیر را به انتهای آن اضافه می‌کنیم:
@using BlazorServerSample.Models
برنامه را نیز توسط دستور dotnet watch run اجرا می‌کنیم.


Data binding یک طرفه

در ادامه به فایل Pages\Index.razor مراجعه کرده و منهای سطر اول مسیریابی آن، مابقی محتوای آن‌را حذف می‌کنیم. در اینجا می‌خواهیم مقادیر نمونه‌ای از شیء BlazorRoom را نمایش دهیم. به همین جهت این شیء را در قسمت code@ فایل razor جاری (همانند نکات قسمت قبل)، ایجاد می‌کنیم:
@page "/"

<h2 class="bg-light border p-2">
    First Room
</h2>
Room: @Room.Name
<br/>
Price: @Room.Price

@code
{
    BlazorRoom Room = new BlazorRoom
    {
        Id = 1,
        Name = "Room 1",
        IsActive = true,
        Price = 499
    };
}
در اینجا در ابتدا شیء Room را در قسمت قطعه کد فایل razor جاری ایجاد کرده و سپس اطلاعات آن‌را با استفاده از زبان Razor نمایش داده‌ایم.


 به این روش نمایش اطلاعات، one-way data-binding نیز گفته می‌شود. اما چطور می‌توان یک طرفه بودن آن‌را متوجه شد؟ برای این منظور یک text-box را نیز در ذیل تعاریف فوق، به صورت زیر اضافه می‌کنیم که مقدارش را از Room.Price دریافت می‌کند:
<input type="number" value="@Room.Price" />
اکنون اگر این مقدار را تغییر دهیم، عدد جدید قیمت اتاق، به خاصیت Room.Price منعکس نمی‌شود و تغییری نمی‌کند:



Data binding دو طرفه

اکنون می‌خواهیم اگر مقدار ورودی Room.Price توسط text-box فوق تغییر کرد، نتیجه‌ی نهایی، به خاصیت متناظر با آن نیز اعمال شود و تغییر کند. برای این منظور فقط کافی است ویژگی value را به bind-value@ تغییر دهیم:
<input type="number" @bind-value="@Room.Price" />
ویژگی bind-value@ سبب برقراری data-binding دو طرفه می‌شود. یعنی در ابتدا مقدار اولیه‌ی خاصیت Room.Price را نمایش می‌دهد. در ادامه‌ی اگر کاربر، مقدار این text-box را تغییر داد، نتیجه‌ی نهایی را به خاصیت Room.Price نیز اعمال می‌کند و همچنین این تغییر، سبب به روز رسانی UI نیز می‌شود؛ یعنی در جائیکه پیشتر مقدار اولیه‌ی Room.Price را نمایش داده بودیم، اکنون مقدار جدید آن نمایش داده خواهد شد:


البته اگر برنامه را اجرا کنیم، با تغییر مقدار text-box، بلافاصله تغییری را مشاهده نخواهیم کرد. برای اعمال تغییرات نیاز خواهد بود تا در جائی خارج از text-box کلیک و focus را به المانی دیگر منتقل کنیم. اگر می‌خواهیم همراه با تایپ اطلاعات درون text-box، رابط کاربری نیز به روز شود، می‌توان bind-value را به یک رخداد خاص، مانند oninput متصل کرد. حالت پیش‌فرض آن onchange است:
<input type="number" @bind-value="@Room.Price" @bind-value:event="oninput" />
اکنون اگر برنامه را اجرا کرده و درون text-box اطلاعاتی را وارد کنیم، بلافاصله UI نیز به روز رسانی خواهد شد.
لیست کامل رخ‌دادها را در اینجا می‌توانید مشاهده کنید. برای مثال برای یک المان input، دو رخداد onchange و oninput قابل تعریف هستند.

یک نکته: در حین کار با bind-value@، نیازی نیست مقدار آن با @ شروع شود. یعنی ذکر "bind-value="Room.Price@ نیز کافی است.


تمرین 1 - خاصیت IsActive یک اتاق را به یک checkbox متصل کرده و همچنین وضعیت جاری آن‌را نیز در یک برچسب نمایش دهید.

در اینجا می‌خواهیم مقدار خاصیت Room.IsActive را توسط یک اتصال دو طرفه، به یک checkbox متصل کنیم:
<input type="checkbox" @bind-value="Room.IsActive"  />
<br/>
This room is @(Room.IsActive? "Active" : "Inactive").
با استفاده از bind-value@، وضعیت جاری خاصیت Room.IsActive را به یک checkbox متصل کرده‌ایم. همچنین در ادامه توسط یک عبارت شرطی، این وضعیت را نمایش داده‌ایم.


بار اولی که برنامه نمایش داده می‌شود، هر چند مقدار IsActive بر اساس مقدار دهی آن در شیء Room، مساوی true است، اما chekbox، علامت نخورده باقی می‌ماند. برای رفع این مشکل نیاز است ویژگی checked این المان را نیز به صورت زیر مقدار دهی کرد:
<input type="checkbox" @bind-value="Room.IsActive"
   checked="@(Room.IsActive? "cheked" : null)" />
در این حالت اگر اتاقی فعال باشد، مقدار ویژگی checked، به checked و در غیراینصورت به null تنظیم می‌شود. به این ترتیب مشکل عدم نمایش checkbox انتخاب شده در بار اول نمایش کامپوننت جاری، برطرف می‌شود.


اتصال خواص مدل‌ها به dropdown‌ها

اکنون می‌خواهیم مدل این مثال را کمی توسعه داده و خواص تو در تویی را به آن اضافه کنیم:
using System.Collections.Generic;

namespace BlazorServerSample.Models
{
    public class BlazorRoom
    {
        // ...

        public List<BlazorRoomProp> RoomProps { set; get; }
    }

    public class BlazorRoomProp
    {
        public int Id { set; get; }

        public string Name { set; get; }

        public string Value { set; get; }
    }
}
برای مثال یک اتاق می‌تواند ویژگی‌هایی مانند مساحت، تعداد نفرات مجاز و غیره را داشته باشد. هدف از ویژگی جدید RoomProps، تعیین لیست این نوع موارد است.
پس از این تعاریف، فیلد Room را به صورت زیر به روز رسانی می‌کنیم تا تعدادی از خواص اتاق را به همراه داشته باشد:
@code
{
    BlazorRoom Room = new BlazorRoom
    {
        Id = 1,
        Name = "Room 1",
        IsActive = true,
        Price = 499,
        RoomProps = new List<BlazorRoomProp>
        {
            new BlazorRoomProp
            {
                Id = 1, Name = "Sq Ft", Value = "100"
            },
            new BlazorRoomProp
            {
                Id = 2, Name = "Occupancy", Value = "3"
            }
        }
    };
}
در ادامه می‌خواهیم این خواص را در یک dropdown نمایش دهیم. همچنین با انتخاب یک خاصیت از دراپ‌داون، مقدار خاصیت انتخابی را در یک برچسب نیز به صورت پویا نمایش خواهیم داد:
<select @bind="SelectedRoomPropValue">
    @foreach (var prop in Room.RoomProps)
    {
        <option value="@prop.Value">@prop.Name</option>
    }
</select>
<span>The value of the selected room prop is: @SelectedRoomPropValue</span>

@code
{
    string SelectedRoomPropValue = "";
    // ...
همانطور که مشاهده می‌کنید، انجام یک چنین کاری با Blazor بسیار ساده‌است و نیازی به استفاده از جاوا اسکریپت و یا جی‌کوئری ندارد.
در اینجا یک فیلد را در قطعه کد برنامه تعریف کرده و به المان select متصل کرده‌ایم. هرگاه آیتمی در این دراپ داون انتخاب شود، این فیلد، مقدار آن آیتم انتخابی را خواهد داشت. در ادامه توسط یک حلقه‌ی foreach، تمام خواص یک اتاق را دریافت کرده و به صورت options‌های یک select استاندارد، نمایش می‌دهیم. در آخر نیز مقدار SelectedRoomPropValue را نمایش داده‌ایم که این مقدار به صورت پویا تغییر می‌کند:



تعریف لیستی از اتاق‌ها

عموما در یک برنامه‌ی واقعی، با یک تک اتاق کار نمی‌کنیم. به همین جهت در ادامه لیستی از اتاق‌ها را تعریف و مقدار دهی اولیه خواهیم کرد:
@code
{
    string SelectedRoomPropValue = "";

    List<BlazorRoom> Rooms = new List<BlazorRoom>();

    protected override void OnInitialized()
    {
        base.OnInitialized();

        Rooms.Add(new BlazorRoom
        {
            Id = 1,
            Name = "Room 1",
            IsActive = true,
            Price = 499,
            RoomProps = new List<BlazorRoomProp>
            {
                new BlazorRoomProp
                {
                    Id = 1, Name = "Sq Ft", Value = "100"
                },
                new BlazorRoomProp
                {
                    Id = 2, Name = "Occupancy", Value = "3"
                }
            }
        });

        Rooms.Add(new BlazorRoom
        {
            Id = 2,
            Name = "Room 2",
            IsActive = true,
            Price = 399,
            RoomProps = new List<BlazorRoomProp>
            {
                new BlazorRoomProp
                {
                    Id = 1, Name = "Sq Ft", Value = "250"
                },
                new BlazorRoomProp
                {
                    Id = 2, Name = "Occupancy", Value = "4"
                }
            }
        });
    }
}
در ابتدا فیلد Rooms تعریف شده که لیستی از BlazorRoomها است. در ادامه بجای مقدار دهی مستقیم آن در همان سطح قطعه کد، آن‌را در یک متد life-cycle کامپوننت جاری به نام OnInitialized که مخصوص این نوع مقدار دهی‌های اولیه است، مقدار دهی کرده‌ایم.


نمایش لیست قابل ویرایش اتاق‌ها

اکنون می‌خواهیم به عنوان تمرین 2، لیست جزئیات اتاق‌های تعریف شده را نمایش دهیم؛ با این شرط که نام و قیمت هر اتاق، قابل ویرایش باشد. همچنین خواص تعریف شده نیز به صورت ستون‌هایی مجزا، نمایش داده شوند. برای مثال اگر دو خاصیت در اینجا تعریف شده، 2 ستون اضافه‌تر نیز برای نمایش آن‌ها وجود داشته باشد. به علاوه از آنجائیکه می‌خواهیم اتصال دوطرفه را نیز آزمایش کنیم، نام و قیمت هر اتاق را نیز در پایین جدول، مجددا به صورت برچسب‌هایی نمایش خواهیم داد.


برای رسیدن به تصویر فوق می‌توان به صورت زیر عمل کرد:
<div class="border p-2 mt-3">
    <h2 class="text-info">Rooms List</h2>
    <table class="table table-dark">
        @foreach(var room in Rooms)
        {
            <tr>
                <td>
                    <input type="text" @bind-value="room.Name" @bind-value:event="oninput"/>
                </td>
                <td>
                    <input type="text" @bind-value="room.Price" @bind-value:event="oninput"/>
                </td>
                @foreach (var roomProp in room.RoomProps)
                {
                    <td>
                        @roomProp.Name, @roomProp.Value
                    </td>
                }
            </tr>
        }
    </table>

    @foreach(var room in Rooms)
    {
        <p>@room.Name's price is @room.Price.</p>
    }
</div>
در اینجا یک حلقه‌ی تو در تو را مشاهده می‌کنید. حلقه‌ی بیرونی، ردیف‌های جدول را که شامل نام و قیمت هر اتاق است، به صورت input-boxهای متصل به خواص متناظر با آن‌ها نمایش می‌دهد. سپس برای اینکه بتوانیم خواص هر ردیف را نیز نمایش دهیم، حلقه‌ی دومی را بر روی room.RoomProps تشکیل داده‌ایم.
هدف از foreach پس از جدول، نمایش تغییرات انجام شده‌ی در input-boxها است. برای مثال اگر نام یک ردیف را تغییر دادیم، چون یک اتصال دو طرفه برقرار است، خاصیت متناظر با آن به روز رسانی شده و بلافاصله در برچسب‌های ذیل جدول، منعکس می‌شود.


کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید: Blazor-5x-Part-04.zip
مطالب
پیاده سازی PWA در Angular
امروزه طراحی اپلیکیشن‌های موبایل بخش زیادی از جامعه را در برگرفته است و روز به روز در حال توسعه میباشند. موازی با رشد روز افزون و نیاز بیشتر به این اپلیکیشن‌ها فریمورک‌های زیادی نیز  ابداع شده اند. از جمله این فریم ورک‌ها می‌توان به موارد زیر اشاره کرد:

Ionic  , react native , flutter , xamarin ….

دیگر لازم نیست برای طراحی اپلیکیشن خود حتما از زبان‌های native  استفاده کنید. بیشتر فریم ورک‌های معرفی شده جاوا اسکریپتی میباشند.

 
مزایای نوشتن یک اپلیکیشن با  فریم ورک‌ها:

1-  نوشتن کد به مراتب آسانتر است.
۲- چون اکثر فریم ورک‌ها، فریم ورک‌های جاوا اسکریپتی هستند، سرعت بالایی هنگام run کردن برنامه دارند ولی در build آخر و خروجی نهایی بر روی پلتفرم، این سرعت کندتر می‌شود.
۳- cossplatform بودن. با طراحی یک اپلیکیشن برای یک پلتفرم می‌توان همزمان برای پلتفرم‌های دیگر خروجی گرفت.


معایب:

۱- برنامه نویس را محدود  میکند.
۲- سرعت اجرای پایینی دارد.
۳- حجم برنامه به مراتب بالاتر میباشد.
۴- از منابع سخت افزاری بیشتری استفاده می‌کند.

اگر شما هم میخواهید از فریم ورک‌ها برای طراحی اپلیکیشن استفاده کنید در اینجا  می‌توانید اطلاعات بیشتری را درباره هر کدام از فریم ورک‌ها ببینید. هر کدام از این فریم ورک‌ها دارای مزایا معایب و همچنین رزومه کاری خوب می‌باشند. بیشتر فریم ورک‌های بالا در رندر آخر، همان کد native را تولید می‌کنند؛ مثلا اگر برنامه‌ای را با react native بنویسید، میتوانید همان برنامه را بر روی اندروید استودیو و کد native بالا بیارید. توضیحات  بالا مقایسه فریم ورک‌های Cross Platform با زبان‌های native بود. اما در این مقاله قصد آشنایی با تکنولوژی جدیدی را داریم که شما را قادر می‌سازد یک وب اپلیکیشن را با آن طراحی کنید.


 pwa چیست؟

pwa مخفف Progressive Web Apps است و یک تکنولوژی برای طراحی وب اپلیکیشن‌های تحت مرورگر میباشد. شما با pwa میتوانید اپلیکیشن خود را بر روی پلتفرم‌های مختلفی اجرا کنید، طوری که کاربران متوجه نشوند با یک صفحه‌ی وبی دارند کار میکنند. شاید شما هم فکر کنید این کار را هم میتوان با html ,css و responsive کردن صفحه انجام داد! ولی اگر بخواهید کاربر متوجه استفاده‌ی از یک صفحه‌ی وبی نشود، باید حتما از pwa استفاده کنید! برای مثال یک صفحه‌ی وبی معمولی حتما باید در بستر اینترنت اجرا شود، ولی با pwa با یکبار وصل شدن به اینترنت و کش کردن داده‌ها، برای بار دوم دیگر نیازی به اینترنت ندارد و میتواند به صورت offline  کار کند. شما حتی با pwa می‌توانید اپلیکیشن را در background اجرا کنید و notification ارسال کنید.


مزیت pwa

۱- یکی از مزیت‌های مهم pwa، حالت offline آن میباشد که حتی با قطع اینترنت، شما میتوانید همچنان با اپلیکیشن کار کنید.
۲- با توجه به اینکه شما در حقیقت با یک صفحه‌ی وبی کار میکنید، دیگر نیازی به دانلود و نصب ندارید.
۳- امکان به‌روز رسانی کردن، بدون اعلام کردن نسخه جدید.
۴- از سرعت بسیار زیادی برخوردار است.
۵- چون pwa از پروتکل https استفاده می‌کند، دارای امنیت بالایی میباشد.


معایب

۱- محدود به مرورگر می‌باشد.
۲- هرچند امروزه اکثر مرورگر‌ها pwa را پشتیبانی میکنند، ولی در بعضی از مرورگر‌ها و مرورگر‌های با ورژن پایین، pwa پشتیبانی نمیشود.
3- نمیتوان یک برنامه‌ی مبتنی بر os را نوشت و محدود به مرورگر میباشد


چند نکته درباره pwa

1- برای pwa  لزومی ندارد حتما از فریم ورک‌های spa استفاده کنید. شما از هر فریم ورک Client Side ای می‌توانید استفاده کنید.
۲- چون صفحات شما در پلتفرم‌های مختلف و با صفحه نمایش‌های مختلفی اجرا می‌شود، باید صفحات به صورت کاملا responsive شده طراحی شوند.
۳- باید از پروتکل https استفاده کنید.

ما در این مقاله از فریم ورک angular  استفاده  خواهیم کرد.

قبل از شروع، با شیوه کار pwa آشنا خواهیم شد. یکی از قسمت‌های مهم  Service Worker ،pwa میباشد که از جمله کش کردن، notification فرستادن و اجرای پردازش‌ها در پس زمینه را بر عهده دارد.


با توجه به شکل بالا، Service Worker مانند یک لایه‌ی نرم افزاری مابین کلاینت و سرور قرار دارد که درخواست‌های داده شده از کلاینت را در صورت اتصال به اینترنت، ارسال کرده و مجددا response را برگشت میدهد‌. اگر به هر دلیلی اینترنت قطع باشد، درخواست به صورت آفلاین به کش مرورگر که توسط proxy ساخته شده است، فرستاده میشود و response را برگشت میدهد.


چند نکته  در رابطه با Service Worker

- نباید برای  نگهداری داده global از Service Worker استفاد کرد. برای استفاده از داده‌های Global میتوان از Local Storage یا IndexedDB استفاده کرد.
- service worker  به dom دسترسی ندارند.


 سناریو

 قبل از شروع، سناریوی پروژه را تشریح خواهیم کرد. رکن اصلی یک برنامه‌ی وب، UI آن میباشد و قصد داریم کاربران را متوجه کار با یک صفحه‌ی وبی نکنیم. قالبی که ما در این مثال در نظر گرفتیم خیلی شبیه به یک اپلیکیشن پلتفرم اندرویدی میباشد. یک کاربر با کشیدن منوی کشویی میتواند گزینه‌های خود را انتخاب نمایند. اولین گزینه‌ای که قصد پیاده سازی آن را داریم، ثبت کاربران میباشد. بعد از ثبت کاربران در یک Component جدا، کاربران را در یک جدول نمایش خواهیم داد.


 قبل از شروع لازم است به چند نکته زیر توجه کرد

1-  سرویس crud را به صورت کامل در پروژه قرار خواهیم داد، ولی چون از حوصله‌ی مقاله خارج است، فقط ثبت کاربران و نمایش کاربران را پیاده سازی خواهیم کرد.
2 - هر اپلیکیشنی قطعا نیاز به یک وب سرویس دارد که واسط بین دیتابیس و اپلیکیشن باشد. ولی ما چون در این مثال به عنوان front end کار قصد طراحی  اپلیکیشن را داریم، برای حذف back end از firebase استفاده خواهیم کرد و مستقیما از انگولار به دیتابیس firebase کوئری خواهیم زد.


 شروع به کار

پیش نیاز‌های یک پروژه‌ی انگیولاری را بر روی سیستم خود فراهم کنید. ما در این مثال از یک template آماده انگیولاری استفاده خواهیم کرد. پس برای اینکه با جزئیات و طراحی ui درگیر نشویم، از لینک github پروژه را دریافت کنید.
سپس وارد root پروژه شوید و با دستور زیر پکیج‌های پروژه را نصب کنید:
 npm install
پس از نصب پکیج‌ها، با دستور ng serve، پروژه را اجرا کنید.

پس از اجرا باید خروجی بالا را داشته باشید. می‌خواهیم یکی از قابلیت‌های pwa را بررسی کنیم. بر روی صفحه کلیک راست کرده و وارد inspect شوید. وارد تب Application  شوید و Service Worker را انتخاب کنید.
 


قبل از اینکه کاری را انجام دهید، چند بار صفحه را refresh کنید! صفحه بدون هیچ تغییری refresh میشود. اینبار گزینه‌ی offline را فعال کنید و مجددا صفحه را refresh کنید.
این بار صفحه No internet به معنی قطع بودن اینترنت نمایش داده میشود و شما دیگر نمی‌توانید بر روی اپلیکیشن خود فعالیتی داشته باشید! 


نصب pwa  بر روی پروژه

برای اضافه کردن pwa به پروژه وارد ریشه‌ی پروژه شوید و دستور زیر را وارد کنید:
 ng add @angular/PWA
پس از  اجرای دستور بالا، تغییراتی در پروژه لحاظ میشود از جمله در فایل‌هایindex.html,app-module,angular.json، همچنین یک پوشه‌ی جدید به نام icons در assets و دو فایل جیسونی زیر به روت پروژه اضافه شده‌اند:
 Manifest.json : اگر محتویات فایل را مشاهده کرده باشید، شامل تنظیمات فنی وب اپلیکیشن می‌باشد؛ از جمله Home Screen Icon و نام وب اپلیکیشن و سایر تنظیمات دیگر.
 Nsgw-config.json : این فایل نسبت به فایل manifest فنی‌تر میباشد و بیشتر به کانفیگ مد آفلاین و کش کردن مرتبط میشود. در ادامه با این فایل بیشتر کار داریم.

بعد از نصب pwa  بر روی پروژه، همه تنظیمات به صورت خودکار انجام می‌شود. البته می‌توانید تنظیمات مربوط به کش کردن داده‌ها را به صورت پیشرفته‌تر کانفیگ کنید.


  اجرا کردن وب اپلیکیشن

 برای اجرا کردن و نمایش خروجی از وب اپلیکیشن، ابتدا باید از پروژه build گرفت. با استفاده از دستور زیر از پروژه خود build بگیرید:
 ng build -- prod
سپس  با دستور زیر وارد پوشه‌ای که فایل‌های Build  شده در آن قرار دارند، شوید:
 cd dist/Web Application  Pwa
بعد با دستور زیر برنامه را اجرا کنید:
 Http-server -o
اگر چنانچه با دستور بالا به خطا برخوردید، با دستور زیر پکیج npm آن را نصب کنید:
 npm i http-server
اگر تا به اینجای کار درست پیش رفته باشید، پروژه را بدون هیچ تغییری مشاهده خواهیم کرد. inspect گرفته و وارد تب Application شوید. اینبار گزینه‌ی offline را فعال کنید و مجددا صفحه را refresh کنید! اینبار برنامه بدون هیچ مشکلی کار میکند. حتی شما میتوانید در کنسول، برنامه را به صورت کامل stop کنید.


برسی فایل Nsgw-config.json 

وارد فایل Nsgw-config.json شوید: 

"$schema": "./node_modules/@angular/service-worker/config/schema.json",
  "index": "/index.html",
  "assetGroups": [
    {
      "name": "app",
      "installMode": "prefetch",
      "resources": {
        "files": [
          "/favicon.ico",
          "/index.html",
          "/*.css",
          "/*.js"
        ]
      }
    }, {
      "name": "assets",
      "installMode": "lazy",
      "updateMode": "prefetch",
      "resources": {
        "files": [
          "/assets/**",
          "/*.(eot|svg|cur|jpg|png|webp|gif|otf|ttf|woff|woff2|ani)"
        ]
      }

    }
  ],
  "dataGroups": [
    {
      "name": "api-performance",
      "urls": [
        "https://api/**"
      ],
      "cacheConfig": {
        "strategy": "performance",
        "maxSize": 100,
        "maxAge": "3d"
      }
    }
  ]
}
 این فایل مربوط به کش کردن داده‌ها میباشند و میتواند شامل چند object زیر باشد. مهمترین آنها را بررسی خواهیم کرد:
  ۱- assetGroups : کش کردن اطلاعات مربوط به اپلیکیشن
  ۲- Index : کش کردن  فایل مربوط به index.html
  ۳-  assetGroups : کش کردن فایل‌های مربوط به asset، شامل فایل‌های js، css  و  غیره
  ۴- dataGroups : این object مربوط به وقتی است که برنامه در حال اجرا است. میتوان داده‌های در حال اجرای اپلیکیشن را کش کرد. داده‌ی در حال اجرا میتواند شامل فراخوانی apiها باشد. برای مثال فرض کنید شما در حالت کار کردن online با اپلیکیشن، لیستی از اسامی کاربران را از api گرفته و نمایش میدهید. وقتی دفعه‌ی بعد در حالت offline  اپلیکیشن را باز کنیم، اگر api را کش کرده باشیم، اپلیکیشن داده‌ها را از کش فراخوانی میکند. این عمل درباره post کردن داده‌ها هم صدق میکند.

خود dataGroups شامل چند object زیر میباشد:
  ۱- name :  یک نام انتخابی برای Groups  میباشد.
 ۲- urls  : شامل آرایه‌ای از آدرس‌ها میباشد. میتوان آدرس یک دومین را همراه با کل apiها به صورت زیر کش کرد:
"https://api/**"
۳- cacheConfig  : که شامل
  ۱- maxSize  : حداکثر تعداد کش‌های مربوط به Groups .
  ۲- maxAge  : حداکثر  lifetime مربوط به کش.
  ۳- strategy  : که میتواند یکی از مقادیر freshness به معنی Network-First یا performance  به معنی Cache-First باشد.


 پیاده سازی پیغام نمایش به‌روزرسانی جدید وب اپلیکیشن

در اپلیکیشن‌های native  وقتی به‌روزرسانی جدیدی برای app اعلام میشود، در فروشگاهای اینترنتی پیغامی مبنی بر به‌روزرسانی جدید app برای کاربران ارسال میشود که کاربران میتوانند app خود را به‌روزرسانی کنند. ولی در pwa تنها با یک رفرش صفحه میتوان اپلیکیشن را به جدیدترین امکانات به‌روزرسانی کرد! برای اینکه بتوانیم با هر تغییر، پیغامی را جهت به‌روزرسانی نسخه یا هر  پیغامی دیگری را نمایش دهیم، از کد زیر استفاده میکنم:
if(this.swUpdate.isEnabled)
    {
      this.swUpdate.available.subscribe(()=> {

        if(confirm("New Version available.Load New Version?")){
          window.location.reload();
        }
      })
    }


وصل شدن به دیتابیس

برای اینکه بتوانیم یک وب اپلیکیشن کامل را طراحی کنیم، قطعا نیاز به دیتابیس داریم. برای دیتابیس از firebase استفاده میکنیم. قبل از شروع، وارد سایت firebase  شوید. پس از لاگین، بر روی Add Project کلیک کنید. بعد از انتخاب نام مناسب، create Project را انتخاب کنید. ساختار دیتابیس‌های firebase  شبیه nosqlها می‌باشد و بر پایه‌ی json کار میکنند. پس از ساخت پروژه و دریافت کد جاواسکریپتی زیر شروع به طراحی فیلد‌های دیتابیس خواهیم کرد.


در منوی سمت چپ، بر روی database کلیک کنید و یک دیتابیس را در حالت test mode  ایجاد نماید. سپس یک collection را به نام user ایجاد کرده و فیلد‌های زیر را به آن اضافه کنید:
Age :number
Fullname :string
Mobile : string
مرحله‌ی بعد، config پروژه‌ی انگیولاری میباشد. وارد  vscode شوید و با دستور زیر پکیج firebase را اضافه کنید:
  npm install --save firebase @angular/fire
سپس وارد appmodule شوید و آن‌را به صورت زیر کانفیگ کنید:

@NgModule({
  declarations: [AppComponent, RegisterComponent,AboutComponent, UserListComponent],
  imports: [
    BrowserModule,
    FormsModule,
    HttpClientModule,
    HttpClientModule,
    SharedModule,
    AppRoutingModule,
    AngularFireModule.initializeApp(environmentFirebase.firebase),
    AngularFireDatabaseModule,
    ServiceWorkerModule.register('ngsw-worker.js', { enabled: environment.production }),
  ],
  providers: [FirebaseService],
  bootstrap: [AppComponent]
})
export class AppModule {}
خط ۱۰ مربوط به pwa است که هنگام نصب pwa اضافه شده‌است و خط ۸ و مربوط به apiKey فایربیس میباشد. می‌شود گفت environmentFirebase.firebase شبیه  connection string می‌باشد که به صورت زیر کانفیگ شده است:
export const environment ={
   production: true,
   firebase: {
     apiKey: "AIz×××××××××××××××××××××××××××××××××8",
     authDomain: "pwaangular-6c041.firebaseapp.com",
     databaseURL: "https://pwaangular-6c041.firebaseio.com",
     projectId: "pwaangular-6c041",
     storageBucket: "pwaangular-6c041.appspot.com",
     messagingSenderId: "545522081966"
   }

}

FirebaseService در قسمت providers مربوط به سرویس crud میباشد. اگر وارد فایل مربوطه شوید، چند عمل اصلی به صورت زیر در آن پیاده سازی شده است:

import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import * as firebase from 'firebase';
import { AngularFireDatabase } from '@angular/fire/database';
import { HttpClient } from '@angular/common/http';
import { map } from 'rxjs/operators';
import { ThrowStmt } from '@angular/compiler';
@Injectable({
  providedIn: 'root'
})
export class FirebaseService {

  ref = firebase.firestore().collection('users');
  constructor(public db: AngularFireDatabase,public _http:HttpClient) { 
 
  }

  getUsers(): Observable<any> {
    return new Observable((observer) => {
      this.ref.onSnapshot((querySnapshot) => {
        let User = [];
        querySnapshot.forEach((doc) => {
          let data = doc.data();
          User.push({
            key: doc.id,
            fullname: data.fullname,
            age: data.age,
            mobile: data.mobile
          });
        });
        observer.next(User);
      });
    });
  }

  getUser(id: string): Observable<any> {
    return new Observable((observer) => {
      this.ref.doc(id).get().then((doc) => {
        let data = doc.data();
        observer.next({
          key: doc.id,
          title: data.title,
          description: data.description,
          author: data.author
        });
      });
    });
  }

  postUser(user): Observable<any> {
    return new Observable((observer) => {
      
      this.ref.add(user).then((doc) => {
        observer.next({
          key: doc.id,
        });
      });
    });
  }

  updateUser(id: string, data): Observable<any> {
    return new Observable((observer) => {
      this.ref.doc(id).set(data).then(() => {
        observer.next();
      });
    });
  }

  deleteUser(id: string): Observable<{}> {
    return new Observable((observer) => {
      this.ref.doc(id).delete().then(() => {
        observer.next();
      });
    });
  }

getDataOnApi(){
  return this._http.get('https://site.com/api/General/Getprovince')
  .pipe(
      map((res: Response) => {
return res;

      })
  );
}

getOnApi(){
  return  this._http.get("https://site.com/api/General/Getprovince",).pipe(
    map((response:any) => {
       
return  response
    } )
    );
}
}
اگر دقت کرده باشید، خیلی شبیه به کد نویسی سمت سرور میباشد.

با دستورات زیر میتوانید مجددا پروژه را اجرا کنید:
ng build --prod
cd dist/Pwa-WepApp
Http-server -o


تست وب اپلیکیشن بر روی پلتفرم‌های مختلف

برای اینکه بتوانیم خروجی وب اپلیکیشن را بر روی پلتفرم‌های مختلفی تست کنیم، میتوانیم آن را آپلود کرده و مثل یک سایت اینترنتی، با وارد کردن دومین، وارده پروژه شد. ولی کم هزینه‌ترین راه، استفاده از ابزار ngrok میباشد. میتوانید توسط این مقاله پروژه خودتان در لوکال بر روی https سوار کنید.

نکته! توجه کنید apiهای مربوط به firebase را نمیتوان کش کرد.


کد‌های مربوط به این قسمت را میتوانید از این repository دریافت کنید .
مطالب
توسعه برنامه های Cross Platform با Xamarin Forms & Bit Framework - قسمت سوم
در قسمت قبل، محیط توسعه نرم افزار مد نظرمان را ایجاد کردیم و توانستیم پروژه پیش فرض Xamarin forms را بیلد کنیم. حالا قصد داریم تا یک مثال ساده را با هم بررسی کنیم و آن را بر روی ویندوز تست کنیم. در قسمت بعدی نیز همین مثال ساده را بر روی Android و در قسمت بعدتر نیز بر روی iOS تست می‌کنیم. پس از اطمینان از اینکه امکان تست برنامه را بر روی هر سه پلتفرم یافته‌اید، بر روی آموزش موارد بیشتری از Xamarin Forms تمرکز می‌کنیم.
برای شروع، Xaml Live را از Visual Studio Marketplace دانلود کنید.
سپس در صورتیکه ابزار git را ندارید، آن را نیز دانلود و نصب کنید و بعد دستور git clone https://github.com/ysmoradi/XamApp را در Command line وارد کنید. دقت کنید پروژه را در جایی Clone نکنید که مسیر فولدر آن طولانی شود. این پروژه، یک پروژه آماده برای تست و تغییر است و ما برای بررسی نحوه اجرا آن در UWP - Android - iOS‌ از آن استفاده می‌کنیم. خود کدها در جلسات بعدتر بررسی خواهند شد. تنها چیزی که الآن اهمیت دارد اطمینان از راه اندازی شدن محیط توسعه نرم افزار شما به بهترین شکل ممکن است.
   
با ساختار پروژه و جزئیات آن در گذر زمان بیشتر آشنا می‌شویم، ولی به صورت کلی این Solution دارای 4 پروژه است:
XamApp - XamApp.Android - XamApp.iOS - XamApp.UWP
- در XamApp، تقریبا تمامی پروژه نوشته می‌شود. این مورد شامل منطق برنامه است که با CSharp نوشته می‌شود و ظاهر برنامه که با XAML نوشته می‌شود. اگر چه که می‌توان ظاهر برنامه را نیز با CSharp زد، انجام این کار به هیچ وجه توصیه نمی‌شود. در مورد Xaml نیز بعد از راه اندازی این مثال در Windows-Android-iOS صحبت خواهیم کرد.
- XamApp.Android پروژه‌ای است که اگر Set as start up project شود، به شما اجازه تست کدهای داخل XamApp را بر روی گوشی یا Emulator‌های اندرویدی می‌دهد. همچنین از طریق این پروژه می‌توانید پابلیش apk را بگیرید و به امکانات پایه Android مانند Activity - Fragment - Android XML - Intent و ... در همان زبان CSharp دسترسی داشته باشید. استفاده از این پروژه، مطلب قسمت بعدی این دوره آموزشی است.
- XamApp.iOS پروژه‌ای است که اگر Set as start up project شود، به شما اجازه تست کدهای داخل XamApp را بر روی گوشی یا Emulator‌های iOS ای می‌دهد. همچنین از طریق این پروژه می‌توانید پابلیش ipa را بگیرید و به امکانات پایه iOS مانند Delegate - Storyboard و ... در همان زبان CSharp دسترسی داشته باشید. در آموزش بعد از آموزش Android، به iOS نیز خواهیم پرداخت.
- XamApp.UWP پروژه‌ای است که اگر Set as start up project شود، به شما اجازه تست کدهای داخل XamApp را بر روی ویندوز خودتان می‌دهد. همچنین از طریق این پروژه می‌توانید پابلیش appxbundle یا msi را بگیرید و به امکانات پایه Windows در همان زبان CSharp دسترسی داشته باشید. UWP مخفف Universal windows platform است.

برای شروع پروژه‌های XamApp.Android و XamApp.iOS را Unload کنید، زیرا در این قسمت با آنها کاری نداریم. همچنین پروژه XamApp.UWP را Set as start up project کنید. 
فقط برای یکبار از منوی Tools در ویژوال استدیو، Options را باز کنید و در قسمت جستجوی آن، عبارت Intellitrace را بنویسید و اگر چیزی پیدا شد، تیک Enable Intellitrace را بردارید تا غیر فعال شود. همچنین مجدد Suppress JIT optimization را جستجو کنید و تیک آن را بزنید تا فعال شود.
دکمه F5 را بزنید و برنامه را اجرا کنید. یک دکمه می‌بینید که Text آن عبارت + است. اگر بر روی آن کلیک کنید، می‌بینید که متن Label بالای آن می‌شود Button tapped 1 time
فایل HelloWorldView.xaml را در فولدر Views در پروژه XamApp، پیدا کنید و نگاهی به کد Label و Button بیاندازید که درون StackLayout هستند و StackLayout خود داخل ContentPage است که در نتیجه صفحه اول ما کل فضایی را که دارد، به StackLayout اختصاص یافته‌است.
    <StackLayout HorizontalOptions="Center" VerticalOptions="Center">
        <Label Text="{Binding StepsCount, StringFormat='{}Button tapped {0} times!'}" />
        <Button Command="{Binding IncreaseStepsCountCommand}" Text="+" />
    </StackLayout>
StackLayout نوعی Layout ساده‌است که بسته به تنظیم Orientation اش، می‌تواند Vertical یا Horizontal باشد. آیتم‌های داخل‌اش را که در این مثال Label و Button هستند، یا عمودی یا افقی می‌چیند که پیش فرض‌اش عمودی است. Horizontal Options و Vertical Options اش هم که هر دو Center هستند باعث می‌شود آیتم‌ها دقیقا در وسط StackLayout بنشینند. چون تمامی فضای ContentPage به StackLayout اختصاص یافته‌است، عملا Label و Button در وسط برنامه ظاهر می‌شوند.
Label ما دارای Text ای است که به StepsCount متصل یا Bind شده است ( به کمک Binding StepsCount).
StepsCount عددی است که در ابتدا صفر است و با کلیک دکمه، مقدار آن یکی یکی افزایش می‌یابد. این کد در HelloWorldViewModel.cs و به زبان CSharp نوشته شده‌است. StringFormat نیز در اینجا عملکردی معادل StringFormat در CSharp را دارد.
‌Button دارای Command ای است که به متدی در CSharp به نام IncreaseStepsCount متصل شده‌است.
حال نگاهی به کد CSharp بیندازیم:
    public class HelloWorldViewModel : BitViewModelBase
    {
        public int StepsCount { get; set; }

        public BitDelegateCommand IncreaseStepsCountCommand { get; set; }

        public HelloWorldViewModel()
        {
            IncreaseStepsCountCommand = new BitDelegateCommand(IncreaseStepsCount);
        }

        async Task IncreaseStepsCount()
        {
            StepsCount += 1;
        }
    }
ظاهر برنامه در فایل Xaml ای نوشته شده به نام HelloWorldView.xaml و منطق برنامه در کلاسی است به نام HelloWorldViewModel که این View و ViewModel انتهای نام این دو، از معماری MVVM یا Model - View - View Model می‌آید که در سال 2006~2007 و با معرفی WPF کم کم معروف شد و ما نیز از آن در این مثال داریم استفاده می‌کنیم.
StepsCount که در View به Text آن Label وصل شده بود، در CSharp یک Property از جنس int است. Command ما با نام IncreaseStepsCountCommand به متدی وصل شده‌است که کارش اضافه کردن یکی یکی StepsCount است.

در حالت عادی اگر بخواهید این برنامه را تغییر دهید که مثلا به جای یکی یکی بالا بردن StepsCount، آن را یکی یکی کم کند، ابتدا برنامه را Stop می‌کنید و سپس در Xaml برای Button مربوطه، Text را از + به - تغییر می‌دهید. همچنین کد CSharp را نیز عوض می‌کنید که می‌شود:
StepsCount -= 1
و مجددا F5 را می‌زنید. این روش قطعا خیلی Productive نیست و زمان زیادی را از شما می‌گیرد. شما می‌توانید با Break کردن اجرای برنامه به تغییر کدهای CSharp بپردازید. همچنین بدون Break کردن می‌توانید کدهای Xaml را تغییر دهید و به این روی، خیلی سریع‌تر پروژه را پیش ببرید.
با مشاهده این ویدئو می توانید درک بهتری از عملکرد Edit & continue داشته باشید. دقت کنید که در زمان تغییر ظاهر و منطق، اگر مثلا عدد، تا 17 افزایش داده شده بود برای تست، روی 17 می‌ماند و به صفر بر نمی‌گردد. در واقع کل برنامه Reload نمی‌شود و این تفاوت Edit & continue با Hot reload موجود در سایر ابزارهاست.

همچنین با کوچک و بزرگ کردن برنامه اجرا شده به سایز گوشی‌ها و تبلت‌های مختلف عملا می‌توانید برنامه را در سایزهای مختلف تست کنید. توجه داشته باشید که در Xamarin Forms مقدار دهی به طول و عرض و ... در تمامی پلتفرم‌ها و Device‌ها فارغ از Resolution یکسان است و در همه جا Width=64 عملا به یک سانتی متر اشاره دارد. علاوه بر این بدون اینکه صفحه مانیتور شما Touch باشد، می‌تواند حتی Touch را نیز تست کنید که برای این کار می‌توانید از Simulator استفاده کنید. به این صورت که به جای Local Machine گزینه Simulator را انتخاب می‌کنید.
 

 

برای پابلیش پروژه نیز می‌توانید از آموزش‌های بر روی وب استفاده کنید که شامل ارائه برنامه به استفاده کنندگان با یا بدون Microsoft Store است که از فرمت نه چندان جالب appxbundle استفاده می‌کند و ما از این آموزش عبور می‌کنیم و به ذکر این نکته بسنده می‌کنیم که نسخه بعدی Visual Studio 2017 یعنی 15.9 قابلیت ساختن msix یا Windows installer را نیز دارد که از هر چیزی بهتر است و برای پابلیش بهتر است تا ارائه نسخه Stable بعدی ویژوال استودیو که احتمالا در طی کمتر از یک ماه دیگر ارائه می‌شود، صبر کنید. دقت کنید علاوه بر کامپیوتر، لپ تاپ و تبلت‌های ویندوزی، برنامه‌ی شما بر روی XBox نیز می‌تواند کار کند.

در قسمت بعدی، همین پروژه را بر روی Android نیز اجرا می‌کنیم.

نظرات مطالب
ایجاد قسمت‌های Toggle در سایت با jQuery
سلام آقای فتح الله ممنون از پستی که قرار دادید بنده کد شما رو تویه سایتم قرار دادم ولی یک مشکلی که وجود داره وقتی رویه دکمه ارسال که درون یک آپدیت پنل هستش کلیک میکنم قسمت toggle بسته میشه چطوری میشه این رو موقع post back باز نگه داشت .با تشکر
مطالب
آموزش QUnit #2
فریم ورک تست جاوا اسکریپت QUnit:
 انتخاب و استفاده از یک فریم ورک برای تست کد‌های جاوا اسکریپت، قطعا نتیجه بهتری را به همراه خواهد داشت. من در این جا از QUnit که یکی از بهترین‌های تست واحد است، استفاده می‌کنم. برای این کار فایل‌های qunit.js و qunit.css را دانلود و مانند زیر برای تست واحد آماده کنید:
<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <title>Refactored date examples</title>

  <link rel="stylesheet" href="../qunit.css">
  <script src="../qunit.js"></script>
  <script src="prettydate.js"></script>
 
  <script>
  test("prettydate basics", function() {
    var now = "2013/01/28 22:25:00";
    equal(prettyDate(now, "2013/01/28 22:24:30"), "just now");
    equal(prettyDate(now, "201308/01/28 22:23:30"), "1 minute ago");
    equal(prettyDate(now, "2013/01/28 21:23:30"), "1 hour ago");
    equal(prettyDate(now, "2013/01/27 22:23:30"), "Yesterday");
    equal(prettyDate(now, "2013/01/26 22:23:30"), "2 days ago");
    equal(prettyDate(now, "2012/01/26 22:23:30"), undefined);
  });
  </script>
</head>
<body>
 
<div id="qunit"></div>
 
</body>
</html>
در کد بالا ابتدا فایل‌های فریم ورک و فایل prettydate.js را اضافه کردیم. برای نمایش نتیجه تست، یک تگ div با نام qunit در بین تگ body اضافه می‌کنیم. 
تابع test:
این تابع برای تست توابع نوشته شده، استفاده می‌شود. ورودی‌های این تابع، یکی عنوان تست و دومی یک متود دیگر، به عنوان ورودی دریافت می‌کند که در آن بدنه تست نوشته می‌شود.
تابع equal:
اولین تابع برای سنجش تست واحد equal است و در آن، تابعی که می‌خواهیم تست کنیم با مقدار خروجی آن مقایسه می‌شود.
فایل را با نام test.htm ذخیره و آن را در مرورگر خود باز نمایید. خروجی در شکل آورده شده است:

همین طور که در تصویر بزرگ می‌بینید اطلاعات مرورگر، زمان تکمیل تست و تعداد تست، تعداد تست پاس شده و تعداد تست شکست خورده، نشان داده شده است.

اگر یکی از تست‌ها با شکست روبرو شود رنگ پس زمینه قرمز و جزئیات شکست نمایش داده می‌شوند.
بهینه سازی، مرحله اول:
در حال حاضر تست ما کامل نیست زیرا امکان تست n weeks ago یا تعداد هفته پیش میسر نیست. قبل از آنکه این را به آزمون اضافه کنیم، تغییراتی در تست می‌دهیم
test("prettydate basics", function() {
  function date(then, expected) {
    equal(prettyDate("2013/01/28 22:25:00", then), expected);
  }
  date("2013/01/28 22:24:30", "just now");
  date("2013/01/28 22:23:30", "1 minute ago");
  date("2013/01/28 21:23:30", "1 hour ago");
  date("2013/01/27 22:23:30", "Yesterday");
  date("2013/01/26 22:23:30", "2 days ago");
  date("2012/01/26 22:23:30", undefined);
});
تابع prettyDate را در تابع دیگری به نام date قرار می‌دهیم. این تغییر سبب می‌شود تا امکان مقایسه زمان ورودی تست جاری با تست قبلی فراهم شود.
تست دستکاری عناصر DOM:
تا اینجا با تست توایع آشنا شدید، حالا می‌خواهیم تغییراتی در prettyDate دهیم تا امکان انتخاب عناصر DOM و به روزرسانی آن نیز وجود داشته باشد. فایل prettyDate2.js در زیر آورده شده است:
var prettyDate = {
  format: function(now, time){
    var date = new Date(time || ""),
      diff = (((new Date(now)).getTime() - date.getTime()) / 1000),
      day_diff = Math.floor(diff / 86400);
 
    if ( isNaN(day_diff) || day_diff < 0 || day_diff >= 31 )
      return;
 
    return day_diff === 0 && (
        diff < 60 && "just now" ||
        diff < 120 && "1 minute ago" ||
        diff < 3600 && Math.floor( diff / 60 ) +
          " minutes ago" ||
        diff < 7200 && "1 hour ago" ||
        diff < 86400 && Math.floor( diff / 3600 ) +
          " hours ago") ||
      day_diff === 1 && "Yesterday" ||
      day_diff < 7 && day_diff + " days ago" ||
      day_diff < 31 && Math.ceil( day_diff / 7 ) +
        " weeks ago";
  },
 
  update: function(now) {
    var links = document.getElementsByTagName("a");
    for ( var i = 0; i < links.length; i++ ) {
      if ( links[i].title ) {
        var date = prettyDate.format(now, links[i].title);
        if ( date ) {
          links[i].innerHTML = date;
        }
      }
    }
  }
};
prettyDate شامل دو تابع، یکی format که weeks ago به آن اضافه گردیده و تابع update که با انتخاب تگ‌ها، مقدار title را به تابع فرمت و خروجی آن را در Html هر عنصر قرار می‌دهد. حال یک تست واحد می‌نویسیم:
<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <title>Refactored date examples</title>
  <link rel="stylesheet" href="../qunit.css">
  <script src="../qunit.js"></script>
  <script src="prettydate2.js"></script>
  <script>
  test("prettydate.format", function() {
    function date(then, expected) {
      equal(prettyDate.format("2013/01/28 22:25:00", then),
        expected);
    }
    date("2013/01/28 22:24:30", "just now");
    date("2013/01/28 22:23:30", "1 minute ago");
    date("2013/01/28 21:23:30", "1 hour ago");
    date("2013/01/27 22:23:30", "Yesterday");
    date("2013/01/26 22:23:30", "2 days ago");
    date("2012/01/26 22:23:30", undefined);
  });
 
  function domtest(name, now, first, second) {
    test(name, function() {
      var links = document.getElementById("qunit-fixture")
        .getElementsByTagName("a");
      equal(links[0].innerHTML, "January 28th, 2013");
      equal(links[2].innerHTML, "January 27th, 2013");
      prettyDate.update(now);
      equal(links[0].innerHTML, first);
      equal(links[2].innerHTML, second);
    });
  }
  domtest("prettyDate.update", "2013-01-28T22:25:00Z",
    "2 hours ago", "Yesterday");
  domtest("prettyDate.update, one day later", "2013/01/29 22:25:00",
    "Yesterday", "2 days ago");
  </script>
</head>
<body>
 
<div id="qunit"></div>
<div id="qunit-fixture">
 
<ul>
  <li id="post57">
    <p>blah blah blah...</p>
    <small>
      Posted <span>
        <a href="/2013/01/blah/57/" title="2013-01-28T20:24:17Z"
          >January 28th, 2013</a>
      </span>
      by <span><a href=""></a></span>
    </small>
  </li>
  <li id="post57">
    <p>blah blah blah...</p>
    <small>
      Posted <span>
        <a href="/2013/01/blah/57/" title="2013-01-27T22:24:17Z"
          >January 27th, 2013</a>
      </span>
      by <span><a href=""></a></span>
    </small>
  </li>
</ul>
 
</div>
 
</body>
</html>
همین طور که مشاهد می‌کنید در تست واحد اول خود تابع prettyDate.format را تست نموده ایم. در تست بعدی عناصر DOM نیز دستکاری و تست شده است. تابع domtest با جستجوی تگ qunit-fixture و تگ‌های a درون آن، مقدار نهایی html آن با مقدار داده شده، مقایسه شده است.

در شکل بالا نتیجه تست واحد نشان داده شده است.