نظرات مطالب
ارسال ایمیل در ASP.NET Core
نکته تکمیلی:
در صورت استفاده از mailkit در محیط net core. میتوان کلاس SMTPClient را به شکل زیر مورد استفاده قرار داد.
 public class DiskSmtpClient : SmtpClient
    {
        public DiskSmtpClient(IOptionsSnapshot<MailKitOptions> mailOptionsSnapshot)
        {
            if (mailOptionsSnapshot.Value.SpecifiedPickupDirectory)
            {
                SpecifiedPickupDirectory = true;
                PickupDirectoryLocation = mailOptionsSnapshot.Value.PickupDirectoryLocation;
            }
            
        }
        public bool SpecifiedPickupDirectory { get; set; }
        public string PickupDirectoryLocation { get; set; }

        public override Task SendAsync(MimeMessage message, CancellationToken cancellationToken = new CancellationToken(),
            ITransferProgress progress = null)
        {
            if (!SpecifiedPickupDirectory)
                return base.SendAsync(message, cancellationToken, progress);
            return SaveToPickupDirectory(message, PickupDirectoryLocation);

        }

    



        private async Task SaveToPickupDirectory(MimeMessage message, string pickupDirectory)
        {
            using (var stream = new FileStream($@"{pickupDirectory}\email-{Guid.NewGuid().ToString("N")}.eml", FileMode.CreateNew))
            {
                await message.WriteToAsync(stream);
            }
        }

       

        public override Task ConnectAsync(string host, int port = 0, SecureSocketOptions options = SecureSocketOptions.Auto,
            CancellationToken cancellationToken = new CancellationToken())
        {
            if (!SpecifiedPickupDirectory)
                return base.ConnectAsync(host, port, options, cancellationToken);
            return Task.CompletedTask;
        }



        public override Task DisconnectAsync(bool quit, CancellationToken cancellationToken = new CancellationToken())
        {
            if (!SpecifiedPickupDirectory)
                return base.DisconnectAsync(quit, cancellationToken);

            return Task.CompletedTask;
        }
    }
در کد بالا دو خصوصیت SpecifiedPickupDirectory  و PickupDirectoryLocation به آن اضافه شده اند و با رونویسی از متدهای مورد استفاده به جای ارسال ایمیل در صورت مقداردهی  SpecifiedPickupDirectory  ایمیل در آدرس  PickupDirectoryLocation   ذخیره میگردد. سپس به شکل زیر آن را مورد استفاده قرار میدهیم:
 services.AddTransient<DiskSmtpClient>();
  var email = "mail@dotnettips.info";
            var subject = "subject";
            var message = "message";

            var emailMessage = new MimeMessage();

            emailMessage.From.Add(new MailboxAddress("DNT", "do-not-reply@dotnettips.info"));
            emailMessage.To.Add(new MailboxAddress("", email));
            emailMessage.Subject = subject;
            emailMessage.Body = new TextPart(TextFormat.Html)
            {
                Text = message
            };

            
                _client.SpecifiedPickupDirectory = true;
                _client.PickupDirectoryLocation = "c:\\mail";

                _client.LocalDomain = "dotnettips.info";
                await _client.ConnectAsync("smtp.relay.uri", 25, SecureSocketOptions.None).ConfigureAwait(false);
                await _client.SendAsync(emailMessage).ConfigureAwait(false);
                await _client.DisconnectAsync(true).ConfigureAwait(false);
در صورتی که قصد ندارید کد اضافه‌تری را نیز اعمال نمایید میتوانید با اضافه کردن تکه کد زیر به فایل startup و محتوای تنظیمات آن به فایل appsettings.json دو خط بالا را حذف نمایید:
services.Configure<MailKitOptions>(options => Configuration.GetSection("MailKitOptions").Bind(options));
  "MailKitOptions": {
    "SpecifiedPickupDirectory": true,
    "PickupDirectoryLocation": "c:\\mail"
  }
در این صورت میتوان تنظیمات جداگانه ای برای حالت انتشار و توسعه نیز در نظر گرفت.
کلاس متناظر MailKitOptions
    public class MailKitOptions
    {
        public  bool SpecifiedPickupDirectory { get; set; }
        public  string PickupDirectoryLocation { get; set; }
    }

مطالب
بررسی وجود نام کاربر با استفاده از jQuery Ajax در ASP.Net

شاید بعضی از سایت‌ها را دیده باشید که در حین ثبت نام، پس از وارد کردن یک نام کاربری و سپس مشغول شدن به پر کردن فیلد کلمه‌ی عبور، در قسمت نام کاربری شروع به جستجو در مورد آزاد بودن نام کاربری درخواستی می‌کنند یا نمونه‌ای دیگر، فرم پرداخت الکترونیکی بانک سامان. پس از اینکه شماره قبض را وارد کردید، بلافاصله بدون ریفرش صفحه به شما پیغام می‌دهد که این شماره معتبر است یا خیر. امروز قصد داریم این قابلیت را با استفاده از کتابخانه‌ی Ajax مجموعه jQuery در ASP.Net پیاده سازی کنیم (بدون استفاده از ASP.Net Ajax مایکروسافت).
ابتدا سورس کامل را ملاحظه نمائید:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="AjaxTest.aspx.cs" Inherits="testWebForms87.AjaxTest" %>
<!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>jQuery Ajax Text</title>

<script src="jquery.min.js" type="text/javascript"></script>
<script type="text/javascript">
$(document).ready(function() {
$("#<%= TextBox1.ClientID %>").blur(function(event) {
$.ajax({
type: "POST",
url: "AjaxTest.aspx/IsUserAvailable",
data: "{'username': '" + $('#<%= TextBox1.ClientID %>').val() + "'}",
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function(msg) {
$('#valid').html("<img src='ajaxImages/waiting.gif' alt='لطفا کمی تامل کنید'>");
var delay = function() {
AjaxSucceeded(msg);
};

setTimeout(delay, 2000); //remove this
},
error: AjaxFailed
});
});
});
function AjaxSucceeded(result) {
if (result.d == true)
$('#msg').html("<img src='ajaxImages/available.gif' alt='نام کاربری درخواستی موجود است'>");
else
$('#msg').html("<img src='ajaxImages/taken.gif' alt='متاسفانه نام کاربری مورد نظر پیشتر دریافت شده‌است'>");
}
function AjaxFailed(result) {
alert(result.status + ' ' + result.statusText);
}
</script>

</head>
<body>
<form id="form1" runat="server">
<div>
user name:
<asp:TextBox ID="TextBox1" runat="server"></asp:TextBox>
<span id="msg"></span>
<br />
pass:
<asp:TextBox ID="TextBox2" TextMode="Password" runat="server"></asp:TextBox>
</div>
<!-- preload -->
<div style="display: none">
<img src="ajaxImages/available.gif" alt="available" />
<img src="ajaxImages/taken.gif" alt="taken" />
<img src="ajaxImages/waiting.gif" alt="waiting" />
</div>
</form>
</body>
</html>


using System;
using System.Web.Services;

namespace testWebForms87
{
public partial class AjaxTest : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
}

[WebMethod]
public static bool IsUserAvailable(string username)
{
// این مورد را با خواندن اطلاعات از دیتابیس می‌شود تعویض کرد
return username != "test";
}
}
}

توضیحات:
همانطور که ملاحظه می‌کنید صفحه‌ی ASP.Net ما بسیار ساده است و از دو تکست باکس استاندارد تشکیل می‌شود، به همراه تصاویر مربوط به Ajax که یک سری تصاویر ساده چرخان معروف منتظر بمانید ، یافت شد یا موجود نیست می‌باشند. این تصاویر در یک div مخفی (display: none) در صفحه قرار گرفته‌اند و در هنگام بارگذاری صفحه، این‌ها نیز بارگذاری شده و حاضر و آماده خواهند بود. بنابراین هنگام استفاده از آن‌ها، کاربر تاخیری را مشاهده نخواهد کرد. همچنین یک span با id مساوی msg‌ را هم پس از تکست باکس اضافه کرده‌ایم تا تصاویر مربوط به رخدادهای Ajax را با استفاده از توانایی‌های jQuery به آن اضافه کنیم.

اسکریپت Ajax ما با دراختیار گرفتن روال رخداد گردان blur شیء textBox1 شروع می‌شود. همانطور که در مقالات پیشین سایت نیز ذکر شد، روش صحیح دریافت ID یک کنترل ASP.Net در کدهای سمت کلاینت جاوا اسکریپتی، بر اساس خاصیت ClientID آن است که در اولین سطر کدهای ما مشخص است (زیرا در ASP.Net نام و ID یک کنترل در هنگام رندر شدن به همراه ID کنترل‌های دربرگیرنده آن نیز خواهد بود، بنابراین بهتر است این مورد را داینامیک کرد).

کار بررسی موجود بودن نام کاربری (یا مثلا یک شماره قبض و امثال آن) توسط WebMethod ایی به نام IsUserAvailable در code behind صفحه انجام می‌شود که پیاده سازی آن‌را ملاحظه می‌کنید. بدیهی است در این مثال ساده، تنها نام کاربری از پیش رزرو شده، کلمه‌ی test است و در یک کد واقعی این مورد با مقایسه‌ی نام کاربری با اطلاعات موجود در دیتابیس باید صورت گیرد (و حملات تزریق اس کیوال را هم فراموش نکنید. برای رهایی از آن‌ها "حتما" باید از پارامترهای ADO.Net استفاده کرد و گرنه کد شما مستعد به این نوع حملات خواهد بود).

سؤال: چرا از web method استفاده شد و همچنین چرا این متد static است؟
زمانیکه یک متد با کلمه کلیدی static‌ مشخص می‌شود حالت state less پیدا می‌کند یعنی مستقل از وهله‌ی کلاس عمل می‌کند. در این حالت نیازی به ارسال ViewState نبوده (بنابراین در کوئری مورد نظر ما بسیار بهینه و سبک عمل می‌کنند) و همچنین نیازی به ایجاد یک وهله‌ای از کلاس صفحه‌ی ما نیز نخواهد بود. برای توضیحات بیشتر به این مقاله مراجعه نمائید. (به صورت خلاصه، دلیل اصلی، کارآیی بالا و بهینه بودن این روش در این مساله ویژه است و در ASP.Net Ajax مایکروسافت به صورت گسترده‌ای در پشت صحنه مورد استفاده قرار می‌گیرد)
استفاده از ویژگی WebMethod عملکرد صفحه‌ی ما را شبیه به یک وب سرویس خواهد کرد و امکان دسترسی به آن در متدهای استاندارد POST به صورت ارسال دیتا به آدرس WebService.asmx/WebMethodName‌ خواهد بود. یک مثال ساده و عملی

بررسی تابع Ajax بکار رفته:
این تابع هنگام فراخوانی رخداد blur تکست‌باکس ما (مطابق کد فوق) فراخوانی می‌شود. ساختار ساده‌ای دارد که به شرح زیر است:
type: "POST"
نحوه‌ی ارسال داده را به متد وب سرویس ما مشخص می‌کند.

url: "AjaxTest.aspx/IsUserAvailable"
اطلاعات پست شده، به صفحه‌ی AjaxTest.aspx و وب متد IsUserAvailable ارسال خواهد شد

data: "{'username': '" + $('#<%= TextBox1.ClientID %>').val() + "'}",
داده‌ای که به آرگومان username ما ارسال می‌شود، همان مقدار تایپ شده در TextBox1 است (که باز هم دریافت ID آن به صورت داینامیک صورت گرفته تا مشکل زا نشود)

contentType: "application/json; charset=utf-8",
dataType: "json",
در این دو سطر از نوع داده‌ی json استفاده شده است که فرمت بسیار سبک و بهینه‌ای برای تبادل اطلاعات در وب به‌شمار می‌آید و توسط کتابخانه‌های جاوا اسکریپتی به سادگی پردازش شده و تبدیل به اشیاء مورد نظر خواهند شد. برای مثال اگر خروجی یک وب سرویس در حالت xml به صورت زیر باشد:

<xx yy="nn"></xx>
معادل json آن به شرح زیر است:
{ "xx": {"yy":"nn"} }
success: function(msg)
Success ، پس از موفقیت آمیز بودن عملیات ajax در jQuery فراخوانی می‌شود
error: AjaxFailed
و اگر در این بین خطایی رخ داده باشد، قسمت error فراخوانی می‌شود.

در این مثال برای نمایش بهتر عملیات، یک وقفه‌ی 2 ثانیه‌ای توسط setTimeout ایجاد شده و بدیهی است در یک مثال واقعی باید آن‌را حذف نمود.

نکته: با استفاده از افزونه‌ی فایرباگ فایرفاکس، می‌توان جزئیات این عملیات را بهتر مشاهده نمود:





مطالب
استفاده از Fluent Validation در برنامه‌های ASP.NET Core - قسمت سوم - اعتبارسنجی سمت کلاینت
FluentValidation یک کتابخانه‌ی اعتبارسنجی اطلاعات سمت سرور است و راهکاری را برای اعتبارسنجی‌های سمت کلاینت ارائه نمی‌دهد؛ اما می‌تواند متادیتای مورد نیاز unobtrusive java script validation را تولید کند و به این ترتیب بسیاری از قواعد آن دقیقا همانند روش‌های توکار اعتبارسنجی ASP.NET Core در سمت کلاینت نیز کار می‌کنند؛ مانند:
• NotNull/NotEmpty
• Matches (regex)
• InclusiveBetween (range)
• CreditCard
• Email
• EqualTo (cross-property equality comparison)
• MaxLength
• MinLength
• Length

اما باید دقت داشت که سایر قابلیت‌های این کتابخانه، قابلیت ترجمه‌ی به قواعد unobtrusive java script validation ندارند؛ مانند:
- استفاده‌ی از شرط‌ها توسط متدهای When و یا Unless (برای مثال اگر خاصیت A دارای مقدار 5 بود، آنگاه ورود اطلاعات خاصیت B باید اجباری شود)
- تعریف قواعد اعتبارسنجی سفارشی، برای مثال توسط متد Must
- تعریف RuleSetها (برای مثال تعیین کنید که از یک مدل، فقط تعدادی از خواص آن اعتبارسنجی شوند و نه تمام آن‌ها)

در یک چنین حالت‌هایی باید کاربر اطلاعات کامل فرم را به سمت سرور ارسال کند و در post-back صورت گرفته، نتایج اعتبارسنجی را دریافت کند.


روش توسعه‌ی اعتبارسنجی سمت کلاینت کتابخانه‌ی FluentValidation جهت سازگاری آن با  unobtrusive java script validation

باتوجه به اینکه قواعد سفارشی کتابخانه‌ی FluentValidation، معادل unobtrusive java script validation ای ندارند، در ادامه مثالی را جهت تدارک آن‌ها بررسی می‌کنیم. برای این منظور، معادل مطلب «ایجاد ویژگی‌های اعتبارسنجی سفارشی در ASP.NET Core 3.1 به همراه اعتبارسنجی سمت کلاینت آن‌ها» را بر اساس ویژگی‌های کتابخانه‌ی FluentValidation پیاده سازی می‌کنیم.

1) ایجاد اعتبارسنج سمت سرور

می‌خواهیم اگر مقدار عددی خاصیتی بیشتر از مقدار عددی خاصیت دیگری بود، اعتبارسنجی آن با شکست مواجه شود. به همین منظور با پیاده سازی یک PropertyValidator سفارشی، LowerThanValidator را به صورت زیر تعریف می‌کنیم:
using System;
using FluentValidation;
using FluentValidation.Validators;

namespace FluentValidationSample.Models
{
    public class LowerThanValidator : PropertyValidator
    {
        public string DependentProperty { get; set; }

        public LowerThanValidator(string dependentProperty) : base($"باید کمتر از {dependentProperty} باشد")
        {
            DependentProperty = dependentProperty;
        }

        protected override bool IsValid(PropertyValidatorContext context)
        {
            if (context.PropertyValue == null)
            {
                return false;
            }

            var typeInfo = context.Instance.GetType();
            var dependentPropertyValue =
                Convert.ToInt32(typeInfo.GetProperty(DependentProperty).GetValue(context.Instance, null));

            return int.Parse(context.PropertyValue.ToString()) < dependentPropertyValue;
        }
    }

    public static class CustomFluentValidationExtensions
    {
        public static IRuleBuilderOptions<T, int> LowerThan<T>(
            this IRuleBuilder<T, int> ruleBuilder, string dependentProperty)
        {
            return ruleBuilder.SetValidator(new LowerThanValidator(dependentProperty));
        }
    }
}
- در اینجا dependentProperty همان خاصیت دیگری است که باید مقایسه با آن صورت گیرد. در متد بازنویسی شده‌ی IsValid، می‌توان مقدار آن‌را از context.Instance استخراج کرده و سپس با مقدار جاری context.PropertyValue مقایسه کرد.
- همچنین جهت سهولت کار فراخوانی آن، یک متد الحاقی جدید به نام LowerThan نیز در اینجا تعریف شده‌است.
- البته اگر قصد اعتبارسنجی سمت سرور را ندارید، می‌توان در متد IsValid، مقدار true را بازگشت داد.


2) ایجاد متادیتای مورد نیاز جهت unobtrusive java script validation در سمت سرور

در ادامه نیاز است ویژگی‌های data-val خاص unobtrusive java script validation را توسط FluentValidation ایجاد کنیم:
using FluentValidation;
using FluentValidation.AspNetCore;
using FluentValidation.Internal;
using FluentValidation.Resources;
using FluentValidation.Validators;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;

namespace FluentValidationSample.Models
{
    public class LowerThanClientValidator : ClientValidatorBase
    {
        private LowerThanValidator LowerThanValidator
        {
            get { return (LowerThanValidator)Validator; }
        }

        public LowerThanClientValidator(PropertyRule rule, IPropertyValidator validator) :
            base(rule, validator)
        {
        }

        public override void AddValidation(ClientModelValidationContext context)
        {
            MergeAttribute(context.Attributes, "data-val", "true");
            MergeAttribute(context.Attributes, "data-val-LowerThan", GetErrorMessage(context));
            MergeAttribute(context.Attributes, "data-val-LowerThan-dependentproperty", LowerThanValidator.DependentProperty);
        }

        private string GetErrorMessage(ClientModelValidationContext context)
        {
            var formatter = ValidatorOptions.MessageFormatterFactory().AppendPropertyName(Rule.GetDisplayName());
            string messageTemplate;
            try
            {
                messageTemplate = Validator.Options.ErrorMessageSource.GetString(null);
            }
            catch (FluentValidationMessageFormatException)
            {
                messageTemplate = ValidatorOptions.LanguageManager.GetStringForValidator<NotEmptyValidator>();
            }
            return formatter.BuildMessage(messageTemplate);
        }
    }
}
در این کدها، تنها قسمت مهم آن، متد AddValidation است که کار تعریف و افزودن متادیتاهای unobtrusive java script validation را انجام می‌دهد و برای مثال سبب رندر یک چنین تگ HTML ای می‌شود که در آن پیام اعتبارسنجی و خاصیت دیگر مدنظر ذکر شده‌اند:
<input dir="rtl" type="number"
 data-val="true"
 data-val-lowerthan="باید کمتر از Age باشد"
 data-val-lowerthan-dependentproperty="Age"
 data-val-required="'سابقه کار' must not be empty."
 id="Experience"
 name="Experience"
 value="">


3) تعریف مدل کاربران و اعتبارسنجی آن

مدلی که در این مثال از آن استفاده شده، یک چنین تعریفی را دارد و در قسمت اعتبارسنجی خاصیت Experience آن، از متد الحاقی جدید LowerThan استفاده شده‌است:
    public class UserModel
    {
        [Display(Name = "نام کاربری")]
        public string Username { get; set; }

        [Display(Name = "سن")]
        public int Age { get; set; }

        [Display(Name = "سابقه کار")]
        public int Experience { get; set; }
    }

    public class UserValidator : AbstractValidator<UserModel>
    {
        public UserValidator()
        {
            RuleFor(x => x.Username).NotNull();
            RuleFor(x => x.Age).NotNull();
            RuleFor(x => x.Experience).LowerThan(nameof(UserModel.Age)).NotNull();
        }
    }


4) افزودن اعتبارسنج‌های تعریف شده به تنظیمات برنامه

پس از تعریف LowerThanValidator و LowerThanClientValidator، روش افزودن آن‌ها به تنظیمات FluentValidation به صورت زیر است:
namespace FluentValidationSample.Web
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllersWithViews().AddFluentValidation(
                fv =>
                {
                    fv.RegisterValidatorsFromAssembly(Assembly.GetExecutingAssembly());
                    fv.RegisterValidatorsFromAssemblyContaining<RegisterModelValidator>();
                    fv.RunDefaultMvcValidationAfterFluentValidationExecutes = false;

                    fv.ConfigureClientsideValidation(clientSideValidation =>
                    {
                        clientSideValidation.Add(
                            validatorType: typeof(LowerThanValidator),
                            factory: (context, rule, validator) => new LowerThanClientValidator(rule, validator));
                    });
                }
            );
        }


5) تعریف کدهای جاوا اسکریپتی مورد نیاز

پیش از هرکاری، اسکریپت‌های فایل layout برنامه باید چنین تعریفی را داشته باشند:
<script src="~/lib/jquery/dist/jquery.min.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
<script src="~/lib/jquery-validation/dist/jquery.validate.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js"></script>
<script src="~/js/site.js" asp-append-version="true"></script>
در اینجا مداخل jquery، سپس jquery.validate و بعد از آن jquery.validate.unobtrusive را مشاهده می‌کنید. در ادامه فایل js/site.js را به صورت زیر تکمیل خواهیم کرد:
$.validator.unobtrusive.adapters.add('LowerThan', ['dependentproperty'], function (options) {
    options.rules['LowerThan'] = {
        dependentproperty: options.params['dependentproperty']
    };
    options.messages['LowerThan'] = options.message;
});

$.validator.addMethod('LowerThan', function (value, element, parameters) {      
    var dependentProperty = '#' + parameters['dependentproperty'];
    var dependentControl = $(dependentProperty);
    if (dependentControl) {
        var targetvalue = dependentControl.val();
        if (parseInt(targetvalue) > parseInt(value)) {
            return true;
        }
        return false;
    }
    return true;
});
در اینجا یکبار اعتبارسنج LowerThan را به validator.unobtrusive معرفی می‌کنیم. سپس منطق پیاده سازی آن‌را که بر اساس یافتن مقدار خاصیت دیگر و مقایسه‌ی آن با مقدار خاصیت جاری است، به اعتبارسنج‌های jQuery Validator اضافه خواهیم کرد.


6) آزمایش برنامه

پس از این تنظیمات، اگر Viewما چنین تعریفی را داشته باشد:
@using FluentValidationSample.Models
@model UserModel
@{
    ViewData["Title"] = "Home Page";
}
<div dir="rtl">
    <form asp-controller="Home"
          asp-action="RegisterUser"
          method="post">
        <fieldset class="form-group">
        <legend>ثبت نام</legend>
            <div class="form-group row">
                <label asp-for="Username" class="col-md-2 col-form-label text-md-left"></label>
                <div class="col-md-10">
                    <input dir="rtl" asp-for="Username" class="form-control" />
                    <span asp-validation-for="Username" class="text-danger"></span>
                </div>
            </div>
            <div class="form-group row">
                <label asp-for="Age" class="col-md-2 col-form-label text-md-left"></label>
                <div class="col-md-10">
                    <input dir="rtl" asp-for="Age" class="form-control" />
                    <span asp-validation-for="Age" class="text-danger"></span>
                </div>
            </div>
            <div class="form-group row">
                <label asp-for="Experience" class="col-md-2 col-form-label text-md-left"></label>
                <div class="col-md-10">
                    <input dir="rtl" asp-for="Experience" class="form-control" />
                    <span asp-validation-for="Experience" class="text-danger"></span>
                </div>
            </div>

            <div class="form-group row">
                <label class="col-md-2 col-form-label text-md-left"></label>
                <div class="col-md-10 text-md-right">
                    <button type="submit" class="btn btn-info col-md-2">ارسال</button>
                </div>
            </div>
        </fieldset>
    </form>
</div>
در صورتیکه سابقه‌ی کار را بیشتر از سن وارد کنیم، به یک چنین خروجی سمت کلاینتی (بدون نیاز به post-back کامل به سمت سرور) خواهیم رسید:

مطالب
معرفی Selector های CSS - قسمت 2
11- S1,S2
اگر بخواهیم قالب بندی را برای چند Selector به صورت یکجا انجام دهیم، این Selector‌ها را با کاما (,) از هم جدا می‌نماییم.
<style>
    div,.content,table.main {
        color: red;
    }
</style>
<div>Text 1</div>
<p>Text 2</p>
<table class="main" border="1">
    <tr>
        <td>Cell 1</td>
        <td>Cell 2</td>
        <td>Cell 3</td>
    </tr>
    <tr>
        <td>Cell 4</td>
        <td>Cell 5</td>
        <td>Cell 6</td>
    </tr>
</table>
<span class="content">Text 3</span>
<h1>Text 4</h1>
در مثال فوق Text 1 و Text 3 و همچنین محتوای تگ table به رنگ قرمز نمایش می‌یابند.
پشتیبانی در مرورگرها:

 Selector نسخه CSS
 Yes  Yes  Yes Yes   Yes S1,S2  1
توجه:
Selector‌ها می‌توانند به صورت ترکیبی نیز استفاده شوند. به مثال زیر توجه کنید:
<style>
    .content .tag {
        color: red;
    }

    h1#index {
        color: blue;
    }

    ul.list li.even {
        color: green;
    }
</style>
<h1 id="index">Index</h1>
<h1>Header 1</h1>
<div class="content">
    Lorem ipsum dolor sit amet, <span class="tag">consectetuer</span> adipiscing elit.
    Maecenas porttitor congue massa. Fusce posuere, magna sed pulvinar ultricies,
    purus lectus malesuada libero, <span class="tag">sit</span> amet commodo magna 
    eros quis urna. Nunc viverra <span class="tag">imperdiet</span> enim. Fusce est.
</div>
<h1>Header 2</h1>
<div class="content">
    <ul class="list">
        <li>Item 1</li>
        <li class="even">Item 2</li>
        <li>Item 3</li>
        <li class="even">Item 4</li>
        <li>Item 5</li>
        <li class="even">Item 6</li>
    </ul>
</div>
در مثال فوق، تگ h1 که ویژگی id آن برابر index می‌باشد، با توجه به Selector ی که بصورت h1#index تعریف شده است، به رنگ آبی نمایش می‌یابد. تمامی تگ‌های span که عضو کلاس tag می‌باشند و در داخل تگ div ی قرار دارند که عضو کلاس content است، با توجه به Selector ی که بصورت .content .tag تعریف شده است، به رنگ قرمز نمایش می‌یابند. تمامی تگهای li که ویژگی class آنها برابر even می‌باشد، و فرزند تگ ul ی هستند که عضو کلاس list است، با توجه به Selector ی که بصورت ul.list li.even تعریف شده است، به رنگ سبز نمایش می‌یابند.

12- [attribute]

تگ‌هایی را انتخاب می‌نماید که دارای یک attribute یا ویژگی خاص باشند.
<style>
    [readonly] {
        background: green;
    }
</style>
<input type="text" value="Value 1" readonly="readonly"/>
<input type="text"/>
در مثال فوق، رنگ پس زمینه تگ input اول که دارای ویژگی readonly می‌باشد، به رنگ سبز نمایش می‌یابد.
پشتیبانی در مرورگرها:

 Selector نسخه CSS
 3.1  9.6  7.0 2.0  4.0 [attribute]    2

13- 
 [attribute=value] 
تگ هایی را انتخاب می‌نماید که دارای یک attribute یا ویژگی خاص هستند و مقدار آن ویژگی دقیقا برابر با value می‌باشد.
<style>
    [lang=fa] {
        direction:rtl;
    }
</style>
<div lang="fa">متن 1</div>
در مثال فوق، جهت نوشتاری محتوای تگ div که دارای ویژگی lang با مقدار fa می‌باشد، از راست به چپ می‌شود و در سمت راست صفحه نمایش می‌یابد.
پشتیبانی در مرورگرها:

 Selector نسخه CSS
 3.1 9.6 7.0 2.0 4.0 [attribute=value] 
 2

14-
   [attribute=value i]
تگ‌هایی را انتخاب می‌نماید که دارای یک attribute یا ویژگی خاص هستند و مقدار آن ویژگی دقیقا برابر با value می‌باشد. همچنین value، نسبت به حروف کوچک و بزرگ حساس نمی‌باشد. 
<style>
    [lang=fa] {
        direction:rtl;
    }
</style>
<div lang="FA">متن 1</div>
در مثال فوق، جهت نوشتاری محتوای تگ div که دارای ویژگی lang با مقدار FA می‌باشد، از راست به چپ می‌شود و در سمت راست صفحه نمایش می‌یابد. با اینکه در Selector مقدار fa با حروف کوچک ذکر شده است.
پشتیبانی در مرورگرها:

 Selector نسخه CSS
 No   No   No  No 
 No [attribute=value i] 
 4

15- 
 [attribute|=value] 
تگ هایی را انتخاب می‌نماید که دارای یک attribute یا ویژگی خاص هستند و مقدار آن ویژگی با یک value خاص آغاز می‌شود. کلمه آغازین مقدار حتما باید با value برابر باشد یا با dash (-) از کلمه‌ی بعدی جدا شده باشد.
<style>
    [class|=info] {
        color:red
    }
</style>
<div class="info">Text 1</div>
<div class="infobar">Text 2</div>
<div class="info bar">Text 3</div>
<div class="info-bar">Text 4</div>
<div class="btninfo">Text 5</div>
<div class="btn info">Text 6</div>
<div class="btn-info">Text 7</div>
<div class="toolinfoicon">Text 8</div>
<div class="tool info icon">Text 9</div>
<div class="tool-info-icon">Text 10</div>
در مثال فوق Text 1 و Text 4 به رنگ قرمز نمایش می‌یابند.
پشتیبانی در مرورگرها:

 Selector نسخه CSS
 3.1  9.6  7.0 2.0  4.0 [attribute|=value] 
 2

16- 
 [attribute^=value] 
تگ‌هایی را انتخاب می‌نماید که دارای یک attribute یا ویژگی خاص هستند و مقدار آن ویژگی با یک value خاص آغاز می شود.
<style>
    [class^=info] {
        color: red;
    }
</style>
<div class="info">Text 1</div>
<div class="infobar">Text 2</div>
<div class="info bar">Text 3</div>
<div class="info-bar">Text 4</div>
<div class="btninfo">Text 5</div>
<div class="btn info">Text 6</div>
<div class="btn-info">Text 7</div>
<div class="toolinfoicon">Text 8</div>
<div class="tool info icon">Text 9</div>
<div class="tool-info-icon">Text 10</div>
در مثال فوق Text 1 و Text 2 و Text 3 و Text 4 به رنگ قرمز نمایش می‌یابند. 
پشتیبانی در مرورگرها:

 Selector نسخه CSS
 3.2  9.6  7.0 3.5  4.0 [attribute^=value] 
 3

17- 
 [attribute~=value] 
تگ‌هایی را انتخاب می‌نماید که دارای یک attribute یا ویژگی خاص هستند و مقدار آن ویژگی شامل یک value خاص می‌باشد که با Space یا فاصله‌ی خالی از سایر مقادیر جدا شده است. 
<style>
    [class~=info] {
        color: red;
    }
</style>
<div class="info">Text 1</div>
<div class="infobar">Text 2</div>
<div class="info bar">Text 3</div>
<div class="info-bar">Text 4</div>
<div class="btninfo">Text 5</div>
<div class="btn info">Text 6</div>
<div class="btn-info">Text 7</div>
<div class="toolinfoicon">Text 8</div>
<div class="tool info icon">Text 9</div>
<div class="tool-info-icon">Text 10</div>
در مثال فوق Text 1 و Text 3 و Text 6 و Text 9 به رنگ قرمز نمایش می‌یابند.
پشتیبانی در مرورگرها:

 Selector نسخه CSS
 3.1  9.6  7.0 2.0  4.0 [attribute~=value] 
 2

18- 
 [attribute*=value] 
تگ‌هایی را انتخاب می‌نماید که دارای یک attribute یا ویژگی خاص هستند و مقدار آن ویژگی شامل یک value خاص می‌باشد. 
<style>
    [class*=info] {
        color: red;
    }
</style>
<div class="info">Text 1</div>
<div class="infobar">Text 2</div>
<div class="info bar">Text 3</div>
<div class="info-bar">Text 4</div>
<div class="btninfo">Text 5</div>
<div class="btn info">Text 6</div>
<div class="btn-info">Text 7</div>
<div class="toolinfoicon">Text 8</div>
<div class="tool info icon">Text 9</div>
<div class="tool-info-icon">Text 10</div>
در مثال فوق تمامی متون به رنگ قرمز نمایش می‌یابند.
پشتیبانی در مرورگرها:

 Selector نسخه CSS
 3.2  9.6  7.0 3.5  4.0 [attribute*=value] 
 3

19- 
[attribute$=value]
تگ‌هایی را انتخاب می‌نماید که دارای یک attribute یا ویژگی خاص هستند و مقدار آن ویژگی به یک value خاص ختم می‌شود. 
<style>
    [class$=info] {
        color: red;
    }
</style>
<div class="info">Text 1</div>
<div class="infobar">Text 2</div>
<div class="info bar">Text 3</div>
<div class="info-bar">Text 4</div>
<div class="btninfo">Text 5</div>
<div class="btn info">Text 6</div>
<div class="btn-info">Text 7</div>
<div class="toolinfoicon">Text 8</div>
<div class="tool info icon">Text 9</div>
<div class="tool-info-icon">Text 10</div>
در مقال فوق Text 1 و Text 5 و Text 6 و Text 7 به رنگ قرمز نمایش می‌یابند.
پشتیبانی در مرورگرها:

 Selector نسخه CSS
 3.2  9.6  7.0 3.5  4.0 [attribute$=value] 
 3   
مطالب
ایجاد alert,confirm,prompt هایی متفاوت با jQuery Impromptu
alert,confirm,prompt سه متد توکار JavaScript هستند که برای نمایش پیغام ، دریافت تایید و دریافت مقدار از کاربر هستند .
گرافیک این پیغام‌ها هم وابسته به مرورگر هستند و قابل تغییر نیستند . متن عنوان و دکمه‌ها هم با توجه به زبان سیستم عامل تعیین می‌شوند و قابل تغییر توسط برنامه نویس نیستند.



حال مواقعی پیش می‌آید که نیاز داریم پیغام هایی با گرافیک و عبارات متفاوت نمایش دهیم . برای رفع این نیاز می‌توانیم از پلاگین jQuery Impromptu استفاده کنیم . البته این پلاگین قابلیت‌های دیگری هم دارد که در این مقاله با آنها آشنا می‌شویم .




از ویژگی‌های این پلاگین می‌توان حجم کم (حدود 11 کیلوبایت) و قدرت شخصی سازی بالا اشاره کرد .

طرز استفاده به این شکل است :

$.prompt( msg , options )


msg می‌تواند یک string یا یک شئ از states باشد . string ارسال شده می‌تواند شامل کدهای html باشد .

یک شئ states هم شامل مجموعه ای از وضعیت‌های prompt است . برای مثال می‌توان یک prompt ایجاد کرد که مثل یک Wizard شامل چند مرحله باشد .

options هم تنظیماتی است که می‌توان مشخص کرد . تنظیماتی مثل : prefix,classes,persistent,timeout,...


msg :


گفتیم شئ states شامل وضعیت‌های مختلف prompt است . هر وضعیت ( state ) می‌تواند شامل بخش‌های زیر باشد :

  • html : مقدار Html وضعیت

  • buttons : یک شئ شامل متن و مقدار دکمه هایی که کاربر می‌تواند کلیک کند

  • focus : ایندکس دکمه‌ی focus شده در وضعیت

  • submit : تابعی که زمانی که یکی از دکمه‌های وضعیت انتخاب شود فراخوانی می‌شود .

    اگر در این تابع false بازگشت داده شود یا متد preventDefault از event فراخوانی شود ، prompt باز می‌ماند . ( روشی برای جلوگیری از بسته شدن prompt هنگام تغییر state یا اعتبار سنجی فرم )
    همچنین شئ event شامل state ( المنت state ) و stateName ( نام state ) می‌باشد .

    پیشفرض :
    function(event, value, message, formVals){}
    value مقدار دکمه ای است که بروی آن کلیک شده ، message مقدار html تعریف شده برای state است ، formVals هم در صورتی که در html تعریف شده برای state ، المنت‌های فرم وجود داشته باشد ، شامل نام/مقادیر آنها می‌باشد . ( برای دریافت مقادیر فرم ، باید از نام المنت استفاده نمایید . )

  • position : مشخص کننده‌ی موقعیت state که شامل موارد زیر است :

    position: { container: '#container', x: 0, y: 0, width: 0, arrow: 'lm' }

    container : ‫selector المنتی است که state باید در آن مکان قرار بگیرد .
    x/y : موقعیت نسبی prompt نسبت به container
    arrow : جهت نمایش فلش prompt است که می‌تواند یکی از این مقادیر باشد : tl, tc, tr, rt, tm, tb, br, bc, bl, lb, lm, lt.



نحوه تعریف یک wizard ساده با شئ states :
var tourSubmitFunc = function (e, v, m, f) {
    if (v === -1) {
        $.prompt.prevState();
        return false;
    } else if (v === 1) {
        $.prompt.nextState();
        return false;
    }
};

var states =
    {
        state0:
            {
                html: "State1",
                buttons: { Next: 1 },
                //position: { container: '#container', x: 10, y: 0, width: 350, arrow: 'lm' },
                submit: tourSubmitFunc
            },
        state1:
            {
                html: "State2",
                buttons: { Prev: -1, Next: 1 },
                submit: tourSubmitFunc
            },
        state2:
            {
                html: "State3",
                buttons: { Prev: -1, Done: 0 },
                submit: tourSubmitFunc
            }
    };

$.prompt(states);


تا به اینجا با پارامتر اول prompt آشنا شدیم و فهمیدیم که می‌توانیم یک رشته یا یک شئ states به عنوان message به prompt ارسال کنیم .

options :

اکنون با optionهای prompt ( پارامتر دوم ) آشنا خواهیم شد .

توجه کنید که زمانی که یک رشته به prompt ارسال کنید ، مقادیر buttons,focus,submit از این تنظیمات دریافت می‌شود .
به عبارت دیگر ، زمانی که یک شئ states به prompt ارسال کنید ، از مقادیر فوق که در تنظیمات است ، استفاده نمی‌شود .


  • loaded
    یک تابع که زمانی که prompt کامل بارگزاری شده فراخوانی می‌شود .
    $.prompt("Message",
        {
            loaded: function() {
                alert("Prompt Loaded !");
            }
        });
  • submit
    یک تابع که زمانی که یکی از دکمه‌های state کلیک شود ، فراخوانی می‌شود .
    ( زمانی اتفاق میوفتد که یک رشته به عنوان متن به prompt  ارسال کرده باشید و زمانی که یک شئ از states ارسال می‌کنید ، هنگام کلیک دکمه‌های آنها ، این تابع فراخوانی نمی‌شود . )
    پیشفرض :
    function(event){}
  • statechanging
    یک تابع که زمانی که یک state در حال تعویض شدن هست فراخوانی می‌شود .
    پیشفرض :
    function(event, fromStateName, toStateName){}
    برای لغو تغییر state ، مقدار return false کنید یا متد preventDefault از event را فراخوانی کنید .

  • statechanged
    یک تابع که زمانی که یک state در حال تعویض شدن هست فراخوانی می‌شود .
    پیشفرض :
    function(event, toStateName){}
  • callback
    یک تابع که زمانی که ( یکی از دکمه‌های prompt کلیک شود و ) prompt بسته شود ، فراخوانی می‌شود .
    پیشفرض :
    function(event[, value, message, formVals]){}
    سه پارامتر آخر تنها زمانی که یک دکمه‌ی prompt کلیک شده باشد موجود هستند .

  • buttons
    یک شئ شامل مجموعه ای از دکمه‌ها .
    پیشفرض :
    { Ok : true }
    شکل دیگر تعریف دکمه به این شکل است :
    [
        {title: 'Hello World',value:true},
        {title: 'Good Bye',value:false}
    ]
  • prefix
    یک پیشوند برای همه css class‌ها و id‌های المنت‌های html که توسط prompt ایجاد می‌شود .
    پیشفرض : jqi

  • classes
    یک css class که به بالاترین سطح prompt داده می‌شود .
    در حالت پیشفرض مقداری ندارد .

  • focus
    ایندکس دکمه‌ی focus شده
    پیشفرض : 0

  • zIndex
    zIndex اعمال شده بروی prompt .
    پیشفرض : 999

  • useiframe
    استفاده از یک iframe برای overlay در IE6
    پیشفرض : false

  • top
    فاصله‌ی prompt از بالای صفحه
    پیشفرض : ‭15%

  • opacity
    میزان شفافیت لایه‌ی ای که صفحه را پوشانده است .
    پیشفرض : 0.6

  • overlayspeed
    سرعت نمایش افکت fadeIn , fadeOut لایه‌ی پوشاننده .
    مقادیر قابل قبول : ‭"slow", "fast", number(milliseconds)
    پیشفرض : "slow"

  • promptspeed
    سرعت نمایش prompt .
    مقادیر قابل قبول : ‭"slow", "fast", number(milliseconds)
    پیشفرض : "fast"

  • show
    نام یک متد jQuery برای animate کردن نمایش prompt .
    مقادیر قابل قبول : "show","fadeIn","slideDown", ...
    پیشفرض : "promptDropIn"

  • persistent
    بسته شدن prompt زمانی که بروی لایه‌ی fade کلیک شود .
    false : بسته شدن
    پیشفرض : true

  • timeout
    مدت زمانی که پس از آن ، prompt بصورت خودکار بسته می‌شود . ( milliseconds )
    پیشفرض : 0


returns
:

مقدار بازگشتی متد prompt ، یک شئ jQuery ، شامل همه‌ی محتویات تولید شده توسط prompt است .


Events using Bind
:
استفاده از Eventها با بایند کردن

  • promptloaded
    معادل loaded در optionها .

  • promptsubmit
    هنگام submit شدن ( کلیک شدن یکی از دکمه‌های ) state فعال می‌شود .

  • promptclose
    معادل callback در optionها .

  • promptstatechanging
    معادل statechanging در optionها .

  • promptstatechanged
    معادل statechanged در optionها .

ظرز بایند کردن یک event به شئ prompt :
var myPrompt = $.prompt( msg , options );
myPrompt.bind('promptloaded', function(e){});


Helper Functions :

  • ‪jQuery.prompt.setDefaults(options)
    تعیین مقادیر پیشفرض برای همه‌ی prompt‌ها .
    jQuery.prompt.setDefaults({
    prefix: 'myPrompt',
    show: 'slideDown'
    });
  • ‪jQuery.prompt.setStateDefaults(options)
    تعیین مقادیر پیشفرض برای stateها .
    jQuery.prompt.setStateDefaults({
    buttons: { Ok:true, Cancel:false },
    focus: 1
    });
  • ‪jQuery.prompt.getCurrentState()
    یک شئ jQuery از state جاری برمی‌گرداند .

  • ‪jQuery.prompt.getCurrentStateName()
    نام state جاری را برمی‌گرداند .

  • ‪jQuery.prompt.getStateContent(stateName)
    یک شئ jQuery از state مشخص شده برمی‌گرداند .

  • ‪jQuery.prompt.goToState(stateName, callback)
    state مشخص شده را در prompt نمایش می‌دهد .
    callback تابعی است که بعد از تغییر state فراخوانی می‌شود .

  • ‪jQuery.prompt.nextState(callback)
    prompt را به state بعدی منتقل می‌کند .

  • ‪jQuery.prompt.prevState(callback)
    prompt را به state قبلی منتقل می‌کند .

  • ‪jQuery.prompt.close()
    prompt را می‌بندد .


حال که با این پلاگین آشنا شدیم ، سه دستور جاوا اسکریپتی که در ابتدای مقاله ذکر کردیم را با این پلاگین پیاده سازی می‌کنیم .

  • alert
    $.prompt("یک پیام تستی",
        {
            prefix: 'dnt',
            buttons: { 'تایید': true }
        });

    نتیجه :



  • confirm
    $.prompt("درخواست تایید - موافقید ؟",
        {
            prefix: 'dnt',
            buttons: { 'تایید': true, 'انصراف': false }
        });

    نتیجه :



  • prompt
    یک فرم html مخفی برای نمایش در prompt :

    <div class="prompt-content" style="display: none;">
        <span>نام خود را وارد نمایید : </span>
        <span>
            <input type="text" name="name" />
        </span>
    </div>

       
    نمایش prompt :

    $.prompt(
        $(".prompt-content").html(),
        {
            prefix: 'dnt',
            buttons: { 'تایید': true, 'انصراف': false }
        });

    نتیجه :



در این سه مثال آخر ، از یک css متفاوت استفاده کردیم . این پلاگین یک سری کلاس دارد که نام این کلاس‌ها از ترکیب مقدار prefix که در option مشخص کردیم حاصل می‌شود .
برای مثال اگر مقدار prefix را برابر با dnt قرار بدهیم ، برای استایل دهی متن پیام باید از کلاس div.dnt .dntmessage استفاده کنید .
همانطور که در سه مثال آخر مشاهده کردید ، تغییری در استایل prompt داشتیم که با تغییر دادن استایل‌های مورد نظر انجام شد .
برای تهیه‌ی یک قالب سفارشی باید selector المنت هایی که نیاز به تغییر دارند را از قالب پیشفرض پیدا کنید ، سپس قسمت prefix از selector را به نام قالب خودتان تغییر بدید و استایل را ویرایش کنید . سپس هنگام ایجاد prompt ، مقدار prefix را برابر نام قالب قرار دهید .

مثلا می‌خواهید یک قالب به نام dnt داشته باشید . می‌خواهید متن قالب راست به چپ باشد .

div.dnt .dntmessage {
     color: #444444;
     line-height: 20px;
     padding: 10px;
/*       Edited      */
     direction:rtl;
     text-align:right;
}


البته راه بهتری هم هست که نیاز به آشنایی با فایرباگ دارد . در این روش ابتدا کل قالب jqi ( موجود در قالب پیشفرض ) را در برنامه خود کپی می‌کنیم ، مقادیر jqi را با نام قالب جایگزین می‌کنیم ، مقدار prefix را در prompt برابر با نام قالب قرار می‌هیم .
اکنون در FireFox یک prompt ایجاد می‌کنیم و توسط فایرباگ استایل هایی که با نام قالب بروی prompt اعمال شده‌اند را مطابق سلیقه تغییر می‌دهیم . در مرحله آخر به تب CSS در فایرباگ می‌رویم و کل استایل‌های مربوط به قالب را کپی و جایگزین استایل قبلی در برنامه می‌کنیم .

قالبی که بنده برای سه دستور فوق استفاده کردم ( dnt ) ، به این شکل است :
/*    Start : DotNetTips Theme     */

.dntfade {
     background-color: #AAAAAA;
     position: absolute;
}

div.dnt {
     background-color: #FFFFFF;
     border-radius: 10px 10px 10px 10px;
     border: 1px solid #FFFFFF;
     box-shadow: 0px 0px 10px 1px #6D6D6C;
     font-family: tahoma;
     font-size: 11px;
     padding: 7px;
     position: absolute;
     text-align: left;
     width: 400px;
}

div.dnt .dntcontainer {
     font-size: small;
}

div.dnt .dntclose {
     color: #BBBBBB;
     cursor: pointer;
     font-weight: bold;
     position: absolute;
     top: 4px;
     width: 18px;
}

div.dnt .dntmessage {
     color: #444444;
     line-height: 20px;
     padding: 10px;
}

div.dnt .dntbuttons {
     background-color: #F4F4F4;
     border: 1px solid #EEEEEE;
     padding: 5px 0px;
     text-align: right;
}

div.dnt button {
     background-color: #2F6073;
     border: 1px solid #F4F4F4;
     color: #FFFFFF;
     font-size: 12px;
     font-weight: bold;
     margin: 0px 10px;
     padding: 3px 10px;
}

div.dnt button:hover {
     background-color: #728A8C;
}

div.dnt button.dntdefaultbutton {
     background-color: #0099CC;
}

.dnt_state {
     direction: rtl;
     text-align: right;
}

.dnt_state button {
     font-family: tahoma;
}

.dntwarning .dnt .dntbuttons {
     background-color: #CCDDFF;
}

.dnt .dntarrow {
     border: 10px solid transparent;
     font-size: 0px;
     height: 0px;
     line-height: 0;
     position: absolute;
     width: 0px;
}

.dnt .dntarrowtl {
     border-bottom-color: #FFFFFF;
     left: 10px;
     top: -20px;
}

.dnt .dntarrowtc {
     border-bottom-color: #FFFFFF;
     left: 50%;
     margin-left: -10px;
     top: -20px;
}

.dnt .dntarrowtr {
     border-bottom-color: #FFFFFF;
     right: 10px;
     top: -20px;
}

.dnt .dntarrowbl {
     border-top-color: #FFFFFF;
     bottom: -20px;
     left: 10px;
}

.dnt .dntarrowbc {
     border-top-color: #FFFFFF;
     bottom: -20px;
     left: 50%;
     margin-left: -10px;
}

.dnt .dntarrowbr {
     border-top-color: #FFFFFF;
     bottom: -20px;
     right: 10px;
}

.dnt .dntarrowlt {
     border-right-color: #FFFFFF;
     left: -20px;
     top: 10px;
}

.dnt .dntarrowlm {
     border-right-color: #FFFFFF;
     left: -20px;
     margin-top: -10px;
     top: 50%;
}

.dnt .dntarrowlb {
     border-right-color: #FFFFFF;
     bottom: 10px;
     left: -20px;
}

.dnt .dntarrowrt {
     border-left-color: #FFFFFF;
     right: -20px;
     top: 10px;
}

.dnt .dntarrowrm {
     border-left-color: #FFFFFF;
     margin-top: -10px;
     right: -20px;
     top: 50%;
}

.dnt .dntarrowrb {
     border-left-color: #FFFFFF;
     bottom: 10px;
     right: -20px;
}

/*    End : DotNetTips Theme     */


برای مشاهده‌ی مثال‌های بیشتر به صفحه‌ی اصلی jQuery Impromptu مراجعه نمایید .
مطالب
استفاده از افزونه MD.BootstrapPersianDateTimePicker در گریدهای Kendo UI
در این مطلب قصد داریم نحوه‌ی یکپارچه سازی افزونه‌ی انتخاب تاریخ و زمان MD.BootstrapPersianDateTimePicker را با گریدهای Kendo UI، در دو حالت ویرایش به صورت popup و یا inline، بررسی کنیم:



پیشنیازها

برای این مطلب از دو کتابخانه‌ی moment-jalaali، برای تبدیل تاریخ، از میلادی به شمسی و برعکس، استفاده خواهیم کرد. همچنین کنترل انتخاب تاریخ نیز از کتابخانه‌ی MD.BootstrapPersianDateTimePicker تامین می‌شود.
moment-jalaali را می‌توانید به صورت ذیل از طریق npm نصب کنید:
 npm install moment-jalaali --save
سپس دو مدخل ذیل را باید به مجموعه‌ی مداخل اسکریپت‌های صفحه‌ی خود اضافه کنید:
node_modules/moment/min/moment.min.js
node_modules/moment-jalaali/build/moment-jalaali.js

MD.BootstrapPersianDateTimePicker را یا از طریق نیوگت نصب کنید و یا مخزن کد آن‌را به صورت کامل دریافت کنید:
 Install-Package MD.BootstrapPersianDateTimePicker
پس از دریافت این بسته، فایل jquery.Bootstrap-PersianDateTimePicker.css آن‌را به مداخل cssهای خود اضافه کنید.
همچنین فایل‌های jalaali.js و jquery.Bootstrap-PersianDateTimePicker.js آن‌را نیز به مداخل اسکریپت‌های صفحه‌ی خود اضافه نمائید.

در کل اگر از ASP.NET Core استفاده می‌کنید، فایل bundleconfig.json یک چنین شکلی را پیدا خواهد کرد. در این حالت فایل layout برنامه تنها این دو مدخل نهایی را نیاز خواهد داشت:
<link href="~/css/site.min.css" rel="stylesheet" asp-append-version="true" />
<script src="~/js/site.min.js" type="text/javascript" asp-append-version="true"></script>


فعالسازی کنترل انتخاب تاریخ شمسی بجای کامپوننت پیش فرض انتخاب تاریخ Kendo UI

پس از افزودن ارجاعات مورد نیاز، اکنون فرض ما بر این است که ستون تاریخ، دقیقا با فرمت میلادی از سمت سرور دریافت می‌شود و addDate نام دارد.
پس از آن، مرحله‌ی اول کار، نمایش این تاریخ میلادی به صورت شمسی است:
{
   field: "addDate", title: "تاریخ ثبت",
   template: "#=moment(addDate).format('jYYYY/jMM/jDD')#",
این‌کار را با تعریف یک template و سپس ارسال مقدار addDate که همان نام خاصیت ستون جاری است به کتابخانه‌ی moment-jalaali، انجام می‌دهیم. در این حالت همواره تاریخ‌های میلادی تنظیم شده‌ی در این ستون، در حالت نمایش معمولی با فرمت شمسی ظاهر می‌شوند.

در ادامه‌ی تکمیل خواص ستون جاری، خاصیت editor را اضافه خواهیم کرد:
 editor: function(container, options) {
}
توسط پارامتر اول آن می‌توان کار سفارشی سازی کنترل ویرایش این ستون را انجام داد و خاصیت دوم آن، نام فیلد جاری و همچنین مقادیر مدل آن‌را در اختیار ما قرار می‌دهد.
در ادامه نیاز است یک input field سفارشی را در اینجا درج کنیم تا کار نمایش کنترل انتخاب تاریخ شمسی را انجام دهد:
// دریافت تاریخ میلادی و تبدیل آن به شمسی جهت نمایش در تکست باکس
var persianAddDate = moment(options.model.addDate).format('jYYYY/jMM/jDD');
// ایجاد کنترل انتخاب تاریخ سفارشی با مقدار تاریخ شمسی دریف جاری
var input = $('<div dir="ltr" class="input-group">'+
'<div class="input-group-addon" data-name="datepicker1" data-mddatetimepicker="true" data-trigger="click" data-targetselector="#' + options.field + '" data-fromdate="false" data-enabletimepicker="false" data-englishnumber="true" data-placement="left">'+
'<span class="glyphicon glyphicon-calendar"></span>'+
'</div>'+
'<input type="text" value="'+ persianAddDate +'" class="form-control" id="' + options.field + '" placeholder="از تاریخ" data-mddatetimepicker="true" data-trigger="click" data-targetselector="#' + options.field + '" data-englishnumber="true" data-fromdate="true" data-enabletimepicker="false" data-placement="right" />'+
'</div>');
// افزودن کنترل جدید به صفحه
input.appendTo(container);
کاری که در اینجا انجام شده، افزودن یک input مزین شده با ویژگی‌های مخصوص MD.BootstrapPersianDateTimePicker است تا توسط آن شناسایی شود. همچنین چون مقدار options.model.addDate در اصل میلادی است، توسط کتابخانه‌ی moment-jalaali به شمسی تبدیل و بجای value این کنترل ارائه می‌شود تا زمانیکه این ستون در حالت ویرایش قرار گرفت، این تاریخ شمسی توسط کنترل انتخاب تاریخ، به درستی پردازش شده و نمایش داده شود.
متد input.appendTo، کار افزودن این input جدید را به container یا همان محل نمایش ستون جاری، انجام می‌دهد.

در این حالت اگر برنامه را اجرا کنید، هرچند ظاهر Input جدید تغییر کرده‌است، اما سبب نمایش کنترل انتخاب تاریخ نمی‌شود؛ چون این فیلد ویرایشی، پس از رندر صفحه، به صفحه اضافه شده‌است. به همین جهت نیاز است متد EnableMdDateTimePickers نیز فراخوانی شود. این متد کار فعالسازی input جدید را انجام خواهد داد:
 // با خبر سازی کتابخانه انتخاب تاریخ از تکست باکس جدید
EnableMdDateTimePickers();

تا اینجا موفق شدیم بجای کنترل انتخاب تاریخ میلادی، کنترل انتخاب تاریخ شمسی را نمایش دهیم. اما تاریخی که انتخاب می‌شود نیز شمسی است و تاریخی که به سمت سرور ارسال خواهد شد، میلادی می‌باشد. به همین جهت تغییرات این کنترل را تحت نظر قرار داده و هر زمانیکه کاربر تاریخی را انتخاب کرد، آن‌را به میلادی تبدیل کرده و بجای فیلد addDate اصلی تنظیم می‌کنیم:
// هر زمانیکه کاربر تاریخ جدیدی را وارد کرد، آن‌را به میلادی تبدیل کرده و در مدل ردیف جاری ثبت می‌کنیم
// در نهایت این مقدار میلادی است که به سمت سرور ارسال خواهد شد
$('#' + options.field).change(function(){
   var selectedPersianDate = $(this)[0].value;
   var gregorianAddDate = moment(selectedPersianDate, 'jYYYY/jMM/jDD').format('YYYY-MM-DD');
   options.model.set('addDate', gregorianAddDate);
});

تنها مرحله‌ی باقیمانده، مخفی کردن این کنترل نمایش تاریخ، با از دست رفتن فوکوس است:
// با از دست رفتن فوکوس نیاز است این کنترل مخفی شود
$('#' + options.field).blur(function(){
   $('[data-name="datepicker1"]').MdPersianDateTimePicker('hide');
});
اگر این کار را انجام ندهیم، در حالت popup با بسته شدن آن، هنوز کنترل نمایش تاریخ نمایان خواهد بود و بسته نمی‌شود یا در حالت ویرایش inline، با کلیک بر روی دکمه‌ی لغو ویرایش ردیف، باز هم شاهد باقی ماندن این کنترل در صفحه خواهیم بود که تجربه‌ی کاربری مطلوبی به شمار نمی‌رود.


کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید: با این کنترلر و این View
نظرات مطالب
ASP.NET MVC #13
سلام آقای نصیری خسته نباشید
من الان واسه ثبت کاربر توی دیتابیس یه کلاس دارم به اسم CustomProfile که تمامی خصوصیت‌های یک کاربر رو توی یه شیئ از این کلاس میریزم و بعد توی کدهای لایه مدل، این شیئ رو توسط کلاس Membership و ProfileBase خود دات نت توی پایگاه داده میریزیم. الان منظور شما اینه که من باید برای ویرایش یک کلاس CustomProfile دیگه با همون مشخصات بسازم و متد اعتبار سنجی برای Username اون رو یه متد دیگه قرار بدم ؟ ببینید کدهای من اینه:
public class CustomProfile
    {
        [Required(ErrorMessage="*")]
        [StringLength(50)]
        [System.Web.Mvc.Remote(action: "CheckUserName",
                                       controller: "User",
                                       HttpMethod = "POST",
                                       ErrorMessage = "این نام کاربری وجود دارد")]
        public string Username{ get; set; }
        [Required(ErrorMessage = "*")]
        public string Password{ get; set; }
        [Required(ErrorMessage = "*")]
        public string FirstName{ get; set; }
        [Required(ErrorMessage = "*")]
        public string LastName{ get; set; }
        
        [Required(ErrorMessage = "*")]
        [StringLength(10, ErrorMessage = "کدملی باید 10 رقم باشد")]
        public string NationalCode { get; set; }
        [Required(ErrorMessage = "*")]
        public string Address { get; set; }

        [RegularExpression(@"\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*",
                           ErrorMessage = "آدرس ایمیل معتبر نیست")]
        [System.Web.Mvc.Remote(action: "CheckEmail",
                                       controller: "User",
                                       AdditionalFields = "Username",
                                       HttpMethod = "POST",
                                       ErrorMessage = "این ایمیل وجود دارد")]
        public string Email{ get; set; }

        [StringLength(11, ErrorMessage = "تلفن باید 11 رقم باشد")]
        public string PhoneNo{ get; set; }

        [StringLength(11, ErrorMessage = "موبایل باید 11 رقم باشد")]
        public string MobileNo{ get; set; }
        
        
        public string[] Roles{ get; set; }
        
        public string LastActivityDate{ get; set; }
        public string LastLoginDate { get; set; }
        public string CreationDate { get; set; }
        public bool IsLockedOut{ get; set; }
    }
این کد کلاسم بود. من توی View هام اومدم و این کلاس را به عنوان ViewModel خودم قرار دادم. و وقتی فرم Create و یا Edit رو Submit میکنم متد اعتبار سنجی من وارد عمل میشه
اینم کدViewها:
@model Sama.Models.CustomProfile
@{
    ViewBag.Title = "Create";
    Layout = "~/Views/Shared/_Layout.cshtml";
    
}
<script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
@using (Html.BeginForm(actionName: "Create", controllerName: "User"))
    {
        @Html.ValidationSummary(true)
     
        <fieldset>
            <legend>کاربر جدید</legend>
            <div>
                <table>
                    <tr>
                        <td>
                            <div>
                                @Html.LabelFor(model => model.Username, "نام کاربری")
                            </div>
                        </td>
                        <td>
                            <div>
                                @Html.EditorFor(model => model.Username)
                                @Html.ValidationMessageFor(model => model.Username)
                            </div>
                        </td>
                    </tr>
                    <tr>
                        <td>
                            <div>
                                @Html.LabelFor(model => model.Password, "کلمه عبور")
                            </div>
                        </td>
                        <td>
                            <div>
                                @Html.PasswordFor(model => model.Password)
                                @Html.ValidationMessageFor(model => model.Password)
                            </div>
                        </td>
                    </tr>
                    <tr>
                        <td>
                            <div>
                                @Html.LabelFor(model => model.FirstName, "نام")
                            </div>
                        </td>
                        <td>
                            <div>
                                @Html.EditorFor(model => model.FirstName)
                                @Html.ValidationMessageFor(model => model.FirstName)
                            </div>
                        </td>
                    </tr>
                    <tr>
                        <td>
                            <div>
                                @Html.LabelFor(model => model.LastName, "نام خانوادگی")
                            </div>
                        </td>
                        <td>
                            <div>
                                @Html.EditorFor(model => model.LastName)
                                @Html.ValidationMessageFor(model => model.LastName)
                            </div>
                        </td>
                    </tr>
                    <tr>
                        <td>
                            <div>
                                @Html.LabelFor(model => model.NationalCode, "کد ملی")
                            </div>
                        </td>
                        <td>
                            <div>
                                @Html.EditorFor(model => model.NationalCode)
                                @Html.ValidationMessageFor(model => model.NationalCode)
                            </div>
                        </td>
                    </tr>
                    <tr>
                        <td>
                            <div>
                                @Html.LabelFor(model => model.Email, "ایمیل")
                            </div>
                        </td>
                        <td>
                            <div>
                                @Html.EditorFor(model => model.Email)
                                @Html.ValidationMessageFor(model => model.Email)
                            </div>
                        </td>
                    </tr>
                    <tr>
                        <td>
                            <div>
                                @Html.LabelFor(model => model.PhoneNo, "تلفن")
                            </div>
                        </td>
                        <td>
                            <div>
                                @Html.EditorFor(model => model.PhoneNo)
                                @Html.ValidationMessageFor(model => model.PhoneNo)
                            </div>
                        </td>
                    </tr>
                    <tr>
                        <td>
                            <div>
                                @Html.LabelFor(model => model.MobileNo, "موبایل")
                            </div>
                        </td>
                        <td>
                            <div>
                                @Html.EditorFor(model => model.MobileNo)
                                @Html.ValidationMessageFor(model => model.MobileNo)
                            </div>
                        </td>
                    </tr>
                    <tr>
                        <td>
                            <div>
                                @Html.LabelFor(model => model.Address, "آدرس")
                            </div>
                        </td>
                        <td>
                            <div>
                                @Html.TextAreaFor(model => model.Address, 5, 30, null)
                                @Html.ValidationMessageFor(model => model.Address)
                            </div>
                        </td>
                    </tr>
                    <tr>
                        <td>
                            نقش‌ها </td>
                        <td>
                            @Helper.CheckBoxList("Roles", (List<SelectListItem>)ViewBag.Roles)
                        </td>
                    </tr>
                    <tr>
                        <td>
                        </td>
                        <td>
                            <input type="submit" value="ذخیره" class="btn btn-primary" />
                        </td>
                    </tr>
                </table>
            </div>
        </fieldset>
    
    }
این View برای Create بود و برای Edit هم مشابه همینه
حالا اگر من درست متوجه شده باشم باید برای Edit ، از یک ViewModel دیگه مثلا CustomProfileEdit استفاده کنم و اونجا متد اعتبار سنجی دیگه ای تعریف کنم. منظور شما همین بود ؟
نظرات مطالب
سازماندهی برنامه‌های Angular توسط ماژول‌ها
ارتقاء به Angular 6: ساده سازی قسمت providers در CoreModule

ویژگی جدیدی به Angular 6 به نام tree-shakable providers اضافه شده‌است که نمونه‌ای از کاربرد آن به صورت پیش‌فرض در حین «ایجاد پروژه‌ی «کتابخانه» توسط Angular CLI 6.0» ارائه می‌شود. به این معنا که با استفاده‌ی از آن دیگر نیازی نیست تا سرویس‌های سراسری برنامه را در قسمت providers مربوط به CoreModule ثبت کرد. همینقدر که یک سرویس سراسری را به صورت ذیل تعریف کنید:
import { Injectable } from '@angular/core';
@Injectable({
    providedIn: 'root'
})
export class MyCoreService { }
خاصیت providedIn آن کار ثبت این سرویس را به صورت خودکار انجام می‌دهد. تفاوت آن با حالت قبل این است که اگر این سرویس جایی در برنامه استفاده نشده باشد، tree-shakable خواهد بود. یعنی به صورت کد مرده در نظر گرفته شده و به prod bundle اضافه نمی‌شود که سبب کاهش حجم نهایی برنامه می‌گردد.
در این حالت تزریق وابستگی‌هایی که در مطلب «تزریق وابستگی‌ها فراتر از کلاس‌ها در برنامه‌های Angular» نیز بحث شدند، پشتیبانی می‌شود:
@Injectable({
    providedIn: 'root',
    useClass: LazyFlightCancellingService,
    deps: [NgModuleFactoryLoader, Injector] 
})
export class FlightCancellingService { }
فقط مواردی مانند ثبت interceptors و حالت‌هایی که به همراه InjectionToken هستند، هنوز هم باید از طریق قسمت providers صورت گیرد.

به عنوان مثال کدهای مخزن کد «اعتبارسنجی کاربران در برنامه‌های Angular» جهت استفاده‌ی از این قابلیت به روز شد.
مطالب
Vue Lifecycle hooks
هر وهله از Vue از یک‌سری مراحل یا (initialization steps) عبور خواهد کرد به عنوان مثال مراحلی از قبیل کامپایل شدن تمپلیت، mount شدن وهله به DOM و یا بروزرسانی DOM زمانیکه داده‌ها تغییر پیدا می‌کنند و ... در حین طی کردن این مسیر یکسری توابع ویژه با نام lifecycle hooks فراخوانی خواهند شد. بنابراین درون این توابع می‌توانیم در هر مرحله کدهای موردنیازمان را قرار دهیم:

همانطور که مشاهده می‌کنید این چرخه حیات با وهله‌سازی شیء Vue شروع می‌شود. در این مرحله اولین تابع یعنی beforeCreate فراخوانی می‌شود. در این مرحله کار مقداردهی اولیه (initialization) ایونت‌های پاس داده شده به Vue instance انجام خواهد گرفت. در مرحله بعد تابع created فراخوانی و در ادامه تمپلیت (اگر تعیین شده باشد) کامپایل و بعد از آن تابع beforeMount فراخوانی خواهد شد. این تابع دقیقاً قبل از اینکه تمپلیت به DOM اضافه شود فراخوانی می‌شود. در این‌حالت المنت تعیین شده در قسمت el با محتوای تمپلیت مقداردهی می‌شود. البته تا اینجا هنوز خروجی به DOM اضافه نشده است؛ تنها اعمال بایندینگ، string interpolation بر روی تمپلیت صورت خواهند گرفت تا خروجی به صورت یک HTML آماده تحویل به مرحله بعد گردد. در ادامه خروجی تهیه شده به DOM اضافه (mount) خواهد شد. در این مرحله اگر دیتایی تغییر کند، تابع beforeUpdate فراخوانی خواهد شد. بعد از اینکه تغییری توسط Vue مشاهده شد، تابع updated فراخوانی خواهد شد. در نهایت توابع beforeDestroyed و destroyed را داریم. درون این توابع فرصت آزادسازی منابع استفاده شده را خواهیم داشت. 
به عنوان مثال بعد از اینکه یک وهله از Vue ایجاد شد، تابعی با نام created فراخوانی خواهد شد: 
new Vue({
    el: '#app',
    data() {
        return {
            a: 1
        };
    },
    created: function () {
        // `this` points to the vm instance
        console.log('a is: ' + this.a)
    }
});

در حالت کلی می‌توانیم hookها را به چهار دسته‌بندی زیر تقسیم کنیم:
(Creation (Initialization
این نوع hook در واقع اولین توابعی هستند که درون یک کامپوننت فراخوانی خواهند شد. در اینجا می‌توانیم قبل از اینکه کامپوننت به DOM اضافه شود، اکشن مورد نیازمان را قرار دهیم. باید دقت داشته باشید که درون این توابع، به target element (همان المنت‌ی که وهله‌ی Vue به آن متصل خواهد شد) و همچنین DOM دسترسی ندارید و مقادیر آن‌ها در این فاز، undefined خواهند بود: 
new Vue({
    el: '#app',
    data() {
        return {
            name: 'Sirwan'
        };
    },
    beforeCreate: function () {
        console.log('name is: ' + this.name);
        console.log(`We don't have access to target element at this point: ${this.$el}`);
    },
    created: function () {
        // `this` points to the vm instance
        console.log('name is: ' + this.name);
        console.log(`We don't have access to target element at this point: ${this.$el}`);
    }
});

(Mounting (DOM Insertion
در این مرحله می‌توانیم بلافاصله قبل و بعد از اولین رندر، به کامپوننت دسترسی داشته باشیم. می‌توانیم از این hookها برای تغییر کامپوننت، به محض رندر شدن استفاده کنیم. بنابراین در این فاز به target element نیز دسترسی خواهیم داشت: 
beforeMount: function () {
    console.log(`this.$el doesn't exist yet, but it will soon!`);
},
mounted: function () {
    console.log(this.$el);
}

(Updating (Diff & Re-render
این توابع هنگامیکه داده‌های تعریف شده در قسمت data تغییر پیدا کنند، فراخوانی خواهند شد. یعنی دقیقاً قبل از اینکه DOM به‌روزرسانی شود، فراخوانی خواهند شد. در مثال زیر به محض ایجاد شدن وهله‌ی Vue، یک تایمر ایجاد شده است. این تایمر هر ثانیه یکبار مقدار دیتای counter را یک واحد افزایش خواهد داد. در حالت عادی اگر دیتایی درون DOM استفاده نشده باشد، درون کنسول خروجی را دریافت نخواهید کرد. در نتیجه این توابع تنها در صورتیکه از دیتا درون DOM استفاده شده باشد، فراخوانی خواهند شد: 
new Vue({
    el: '#app',
    data() {
        return {
            name: 'Sirwan',
            counter: 0
        };
    },
    created: function () {
        setInterval(() => {
            this.counter++
        }, 1000)
    },
    beforeUpdate: function () {
        console.log(this.counter); // Logs the counter value every second, before the DOM updates.
    },
    updated: function () {
        console.log(`new value: ${this.counter}`);
    }
});

(Destruction (Teardown
این توابع، قبل و بعد از تخریب شدن (حذف شدن از DOM) کامپوننت فراخوانی خواهند شد:
new Vue({
    el: '#app',
    data() {
        return {
            name: 'Sirwan',
            counter: 0
        };
    },
    beforeDestroy: function () {
        
    },
    destroyed() {
        console.log(this) // There's practically nothing here!
    }
});
 
 
Vue.js چگونه تغییرات داده‌ها را متوجه خواهد شد؟
شاید تصور کنید که Vue.js به صورت مداوم تغییرات را مشاهده کرده و در صورت وجود تغییری بر روی دیتا، این تغییرات را مستقیماً به DOM اعمال خواهد کرد. هر پراپرتی دارای یک watcher است؛ یعنی وقتی یک شیء را به سازنده Vue ارسال می‌کنیم، یک watcher برای تمامی پراپرتی‌های تعریف شده، درون پراپرتی data ایجاد خواهد کرد. این watcher در واقع مسئول کنترل تغییرات پراپرتی‌ها می‌باشد و اگر تغییری صورت بگیرد، آن را به DOM اعمال خواهد کرد. اما نکته اینجاست که این تغییرات بلافاصه بر روی DOM اعمال نخواهند شد؛ زیرا از لحاظ کارآیی، اینکار بهینه نیست. فرض کنید یک پراپرتی با نام message درون قسمت data داریم که مقدار آن Hello است. اکنون همین مقدار توسط UI مجدداً به Hello ست خواهد شد. در این‌حالت اعمال تغییرات نباید به DOM منعکس شود، زیرا مقدار message به همان مقدار قبلی تنظیم شده است. در عوض Vue.js از مفهومی با نام Virtual DOM استفاده می‌کند که در واقع یک کپی از DOM اصلی است؛ با این تفاوت که دسترسی به آن خیلی سریعتر از DOM اصلی می‌باشد. بنابراین Vue.js تغییرات صورت گرفته را مستقیماً به Virtual DOM اعمال می‌کند. در نهایت آن را با DOM اصلی مقایسه خواهد کرد و در صورت وجود تغییر، آن را با DOM اصلی ادغام خواهد کرد. 
نظرات مطالب
Blazor 5x - قسمت ششم - مبانی Blazor - بخش 3 - چرخه‌های حیات کامپوننت‌ها
یک نکته‌ی تکمیلی: تنظیم پویای عنوان صفحات در برنامه‌های Blazor

برای تنظیم پویای عنوان یک صفحه‌ی وب، نیاز است با DOM API مرورگر به صورت مستقیم کار کرد. برای مثال فایل wwwroot\main.js را که مدخل آن به کامپوننت Host_ و یا صفحه‌ی index.html اضافه می‌شود، به صورت زیر تکمیل می‌کنیم:
window.JsFunctionHelper = {
  blazorSetTitle: function (title) {
    document.title = title;
  }
};
اکنون می‌خواهیم این متد جاوااسکریپتی را که مستقیما با شیء document کار می‌کند، در کامپوننت جدید Client\Shared\PageTitle.razor استفاده کنیم:
@inject IJSRuntime JSRuntime

@code
{
   [Parameter]
   public string Title { get; set; }

   protected override async Task OnParametersSetAsync()
   {
      await JSRuntime.InvokeVoidAsync("JsFunctionHelper.blazorSetTitle", Title);
   }
}
در اینجا کامپوننت جدیدی تعریف شده‌است که به محض تنظیم مقدار پارامتر عنوان آن، سبب فراخوانی متد جاوا اسکریپتی blazorSetTitle می‌شود. برای نمونه روش استفاده‌ی از آن در کامپوننت Counter، جهت نمایش عنوانی پویا، به محض تغییر مقدار شمارشگر، به صورت زیر می‌تواند باشد:
@page "/counter"

<PageTitle Title="@GetPageTitle()" />

<h1>Counter</h1>

<p>Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    private int currentCount = 0;

    private void IncrementCount()
    {
        currentCount++;
    }

    private string GetPageTitle() => $"Counter ({currentCount})";
}
این روش در برنامه‌های Blazor Server کار نخواهد کرد و در حین فراخوانی متد InvokeVoidAsync یک NullReferenceException مشاهده می‌شود؛ چون این نوع برنامه‌های Blazor Server به همراه یک مرحله‌ی pre-render در سمت سرور هستند که ابتدا، کار تهیه‌ی HTML ای را که باید به سمت مرورگر ارسال کنند، به پایان می‌رسانند. در این مرحله خبری از DOM نیست که بتوان به آن دسترسی یافت و تغییری را در آن ایجاد کرد.
برای رفع این مشکل همانطور که در مطلب جاری نیز عنوان شد، باید از روال رویدادگردان OnAfterRenderAsync استفاده کرد. در این حالت کدهای کامپوننت PageTitle.razor به صورت زیر تغییر می‌کنند:
@inject IJSRuntime JSRuntime

@code
{
   [Parameter]
   public string Title { get; set; }

   protected override async Task OnAfterRenderAsync(bool firstRender)
   {
      await JSRuntime.InvokeVoidAsync("JsFunctionHelper.blazorSetTitle", Title);
   }
}
روال رویدادگردان OnAfterRenderAsync پس از اینکه کار بارگذاری و تشکیل کامل DOM در مرورگر انجام شد، فراخوانی می‌شود. به همین جهت دیگر دسترسی به شیء document.title، سبب بروز یک NullReferenceException نخواهد شد.


یک نکته: قرار است در Blazor 6x، کامپوننت‌های جدید Title، Link و Meta جهت تنظیم اطلاعات تگ head صفحه، به صورت استاندارد اضافه شوند:
<Title Value="@title" />
<Meta name="description" content="Modifying the head from a Blazor component." />
<Link href="main.css" rel="stylesheet" />