مطالب
شروع به کار با EF Core 1.0 - قسمت 8 - بررسی رابطه‌ی Many-to-Many
در مطلب «بررسی تفصیلی رابطه Many-to-Many در EF Code first» نحوه‌ی مدلسازی رابطه‌ی چند به چند را در EF 6.x بررسی کردیم. یک چنین رابطه‌ای که به همراه مدیریت خودکار join table آن است (جدول BlogPostsJoinTags در شکل زیر)، در EF Core 1.0 RTM پشتیبانی نمی‌شود.


البته همیشه درخواست کنترل این جدول واسط که کاملا از دیدگاه ORMها (تمام آن‌ها) مخفی است، وجود داشته‌است و قرار است این پشتیبانی توسط مفهوم ویژه‌ای به نام shadow properties به نگارش‌های بعدی EF Core اضافه شود.
اما فعلا در نگارش اول آن، توصیه شده‌است که رابطه‌ی many-to-many را به صورت دو رابطه‌ی one-to-many مدلسازی کنید که در ادامه آن‌را بررسی خواهیم کرد. بنابراین پیشنیاز آن مطالعه‌ی مطلب «شروع به کار با EF Core 1.0 - قسمت 7 - بررسی رابطه‌ی One-to-Many» می‌باشد.


مدلسازی موجودیت‌های یک رابطه‌ی چند به چند در EF Core 1.0 RTM توسط Fluent API

در اینجا نحوه‌ی مدلسازی یک رابطه‌ی چند به چند را توسط دو رابطه‌ی one-to-many مشاهده می‌کنید. تنها تفاوت آن با EF 6.x، قید صریح جدول واسط BlogPostsJoinTags است که یک چنین جدولی در EF 6.x به صورت خودکار تشکیل شده و مدیریت می‌شود و کاملا از دید برنامه مخفی است. اما در اینجا (در نگارش اول EF Core) نیاز است این جدول واسط را از حالت مخفی خارج کرد و سپس دو رابطه‌ی یک به چند را به جداول مطالب و تگ‌های آن‌ها تشکیل داد.
مزیت این حالت، دسترسی کامل به طراحی جدول واسط، توسط برنامه است. بنابراین اگر به هر دلیلی نیاز به افزودن خواص بیشتری به این جدول ویژه دارید، اکنون امکان آن میسر است.
    public class Tags
    {
        public Tags()
        {
            BlogPostsJoinTags = new HashSet<BlogPostsJoinTags>();
        }

        public int Id { get; set; }
        public string Name { get; set; }

        public virtual ICollection<BlogPostsJoinTags> BlogPostsJoinTags { get; set; }
    }

    public class BlogPostsJoinTags
    {
        public virtual BlogPosts BlogPost { get; set; }
        public int BlogPostId { get; set; }

        public virtual Tags Tag { get; set; }
        public int TagId { get; set; }
    }

    public class BlogPosts
    {
        public BlogPosts()
        {
            BlogPostsJoinTags = new HashSet<BlogPostsJoinTags>();
        }

        public int Id { get; set; }
        public string Title { get; set; }
        public string Body { get; set; }

        public virtual ICollection<BlogPostsJoinTags> BlogPostsJoinTags { get; set; }
    }
پس از آن باید Context برنامه را نیز جهت ذکر صریح رابطه‌ی یک به چند، ویرایش کرد. با متدهای HasOne و WithMany در مطلب قبل «شروع به کار با EF Core 1.0 - قسمت 7 - بررسی رابطه‌ی One-to-Many» آشنا شدیم و علت نیاز به ذکر صریح آن‌ها، وجود بیش از یک سر رابطه در جدول واسط BlogPostsJoinTags است. در این حالت یافتن InversePropertyها توسط EF Core میسر نیست و حتما باید از یک طرف شروع و سمت دیگر را مشخص کرد.
به علاوه در اینجا تعریف یک composite key را هم بر روی خواص کلید خارجی جدول واسط مشاهده می‌کنید. وجود این کلید ترکیبی سبب خواهد شد که ملزم به ثبت هر دو Id (کلیدهای جداول مطلب و تگ) در حین ثبت در این جدول شویم (یا قید اجباری هر دو طرف رابطه).
    public class MyDBDataContext : DbContext
    {
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseSqlServer(@"Data Source=(local);Initial Catalog=testdb2;Integrated Security = true");
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<BlogPosts>(entity =>
            {
                entity.Property(e => e.Title)
                    .IsRequired()
                    .HasMaxLength(450);
            });

            modelBuilder.Entity<Tags>(entity =>
            {
                entity.Property(e => e.Name)
                    .IsRequired()
                    .HasMaxLength(450);
            });

            modelBuilder.Entity<BlogPostsJoinTags>(entity =>
            {
                entity.HasKey(e => new { e.TagId, e.BlogPostId })
                    .HasName("PK_dbo.BlogPostsJoinTags");

                entity.HasIndex(e => e.BlogPostId)
                    .HasName("IX_BlogPostId");

                entity.HasIndex(e => e.TagId)
                    .HasName("IX_TagId");

                entity.HasOne(d => d.BlogPost)
                    .WithMany(p => p.BlogPostsJoinTags)
                    .HasForeignKey(d => d.BlogPostId)
                    .HasConstraintName("FK_dbo.BlogPostsJoinTags_dbo.BlogPosts_BlogPostId");

                entity.HasOne(d => d.Tag)
                    .WithMany(p => p.BlogPostsJoinTags)
                    .HasForeignKey(d => d.TagId)
                    .HasConstraintName("FK_dbo.BlogPostsJoinTags_dbo.Tags_TagId");
            });
        }

        public virtual DbSet<BlogPosts> BlogPosts { get; set; }
        public virtual DbSet<BlogPostsJoinTags> BlogPostsJoinTags { get; set; }
        public virtual DbSet<Tags> Tags { get; set; }
    }


مدلسازی موجودیت‌های یک رابطه‌ی چند به چند در EF Core 1.0 RTM توسط Data Annotations

در حالت مدلسازی توسط ویژگی‌ها، ذکر InversePropertyها و همچنین ForeignKeyها مقداری واضح‌تر به نظر می‌رسند. به علاوه، یک سری از تنظیمات هم معادل data annotations ایی ندارند؛ مانند composite key تعریف شده‌ی بر روی خواص جدول واسط و همچنین ایندکس‌های تعریف شده، که حتما باید توسط Fluent API تنظیم شوند.
    public class Tags
    {
        public Tags()
        {
            BlogPostsJoinTags = new HashSet<BlogPostsJoinTags>();
        }

        public int Id { get; set; }

        [Required]
        [MaxLength(450)]
        public string Name { get; set; }

        [InverseProperty("Tag")]
        public virtual ICollection<BlogPostsJoinTags> BlogPostsJoinTags { get; set; }
    }

    public class BlogPostsJoinTags
    {
        [ForeignKey("BlogPostId")]
        [InverseProperty("BlogPostsJoinTags")]
        public virtual BlogPosts BlogPost { get; set; }
        public int BlogPostId { get; set; }

        [ForeignKey("TagId")]
        [InverseProperty("BlogPostsJoinTags")]
        public virtual Tags Tag { get; set; }
        public int TagId { get; set; }
    }

    public class BlogPosts
    {
        public BlogPosts()
        {
            BlogPostsJoinTags = new HashSet<BlogPostsJoinTags>();
        }

        public int Id { get; set; }

        [Required]
        [MaxLength(450)]
        public string Title { get; set; }

        public string Body { get; set; }

        [InverseProperty("BlogPost")]
        public virtual ICollection<BlogPostsJoinTags> BlogPostsJoinTags { get; set; }
    }

    public class MyDBDataContext : DbContext
    {
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseSqlServer(@"Data Source=(local);Initial Catalog=testdb2;Integrated Security = true");
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<BlogPostsJoinTags>(entity =>
            {
                entity.HasKey(e => new { e.TagId, e.BlogPostId })
                    .HasName("PK_dbo.BlogPostsJoinTags");

                entity.HasIndex(e => e.BlogPostId)
                    .HasName("IX_BlogPostId");

                entity.HasIndex(e => e.TagId)
                    .HasName("IX_TagId");
            });
        }

        public virtual DbSet<BlogPosts> BlogPosts { get; set; }
        public virtual DbSet<BlogPostsJoinTags> BlogPostsJoinTags { get; set; }
        public virtual DbSet<Tags> Tags { get; set; }
    }
همانطور که در مطلب «شروع به کار با EF Core 1.0 - قسمت 6 - تعیین نوع‌های داده و ویژگی‌های آن‌ها» نیز عنوان شد، علت تنظیم MaxLength به عدد 450 این است که بتوان بر روی این ستون ایندکس ایجاد کرد.


نحوه‌ی ثبت اطلاعات در دو رابطه‌ی یک به چند به همراه جدول واسط

در EF 6.x، کار مقدار دهی Idهای جدول واسط به صورت خودکار انجام می‌شود. در اینجا این مقدار دهی را باید به صورت صریح انجام داد:
 var post = new BlogPosts { ... };
context.BlogPosts.Add(post);

var tag = new Tags { ... };
context.Tags.Add(tag);

var postTag = new BlogPostsJoinTags { Tag = tag, BlogPost = post };
context.PostsTags.Add(postTag);

context.SaveChanges();


نحوه‌ی واکشی اطلاعات به هم مرتبط در دو رابطه‌ی یک به چند به همراه جدول واسط

در مورد متدهای Include و ThenInclude در مطلب «شروع به کار با EF Core 1.0 - قسمت 7 - بررسی رابطه‌ی One-to-Many» پیشتر بحث شد.
            BlogPosts post1 = this.BlogPosts
                  .Include(blogPosts => blogPosts.BlogPostsJoinTags)
                    .ThenInclude(joinTags => joinTags.Tag)
                  .First(blogPosts => blogPosts.Id == 1);
            IEnumerable<Tags> post1Tags = post1.BlogPostsJoinTags.Select(x => x.Tag);
در اینجا اگر می‌خواهیم به لیست تمام برچسب‌های اولین مطلب دسترسی پیدا کنیم، ابتدا باید رابطه‌ی جدول واسط را Include کنیم. سپس چون در همین سطح می‌خواهیم به سطح بعدی تگ‌های مرتبط برسیم، باید از متد الحاقی جدید ThenInclude استفاده کرد. در غیراینصورت در سطر بعدی، post1.BlogPostsJoinTags نال خواهد بود و همچنین حاوی لیست تگ‌ها نیز نخواهد بود.


یک نکته‌ی تکمیلی
وضعیت پشتیبانی از رابطه‌ی many-to-many را همانند EF 6.x در EF Core، در اینجا می‌توانید پیگیری کنید.
مطالب
استفاده از افزونه 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
نظرات مطالب
فعال سازی و پردازش Inline Add در jqGrid
با سلام
آیا امکان آن هست در روش Inline زمانی که میخواهیم مثلا در ستون نام محصول،که از جدول محصول می‌آید را به صورت لیستی نمایش دهیم و کاربر بتواند آن را انتخاب کند؟
نظرات مطالب
Persist ، Load و Bookmark در Workflow
سلام .ممنون از زحمتی که کشیدید.یک سوال کلید WorkFlowID در جدول User کلید خارجی از کدوم یک از جداول WF است؟ توی هیچ کدوم از جدول‌ها نیست!
بازخوردهای پروژه‌ها
ساختار گزارش خاص
سلام ... 
من یک گزارشی دارم که در اون ساختاری خارج از شکل جدول داره و هر چی فکر میکنم و مثال‌ها رو میبینم اون چیزی که من میخوام نیست 
اگه میشه در رابطه با گزارش زیر راهنمایی کنید

مطالب
قابلیت Attribute Routing در ASP.NET MVC 5
در ASP.NET MVC 5 یک قابلیت جدید با نام Attribute Routing افزوده شده است که به ما این اجازه را می‌دهد تا Route‌های سفارشی برای کنترلرها و اکشن متدهایمان با اضافه کردن یک Attribute با نام Route تعریف کنیم.
همچنین می‌توانیم ویژگی RoutePrefix نیز برای کنترلرهایمان تعریف کنیم تا همه‌ی اکشن متدها نیز از آن پیروی کنند. این ویژگی را با ذکر یک مثال معرفی میکنیم :

ابتدا لازم است این ویژگی را در کلاس RouteConfig فعال کنیم :
public static void RegisterRoutes(RouteCollection routes) 
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

    routes.MapMvcAttributeRoutes();

    // ...
}

قدم بعدی تنها افزودن Attribute‌های ذکر شده به کنترلر و اکشن متدهایمان می‌باشد، به طور مثال ما در اینجا یک کنترلر با نام ProductController ایجاد کرده ایم و کنترلر را با ویژگی RoutePrefix مزین کرده ایم که در این حالت به ASP.NET MVC می‌گویم که تمام اکشن متدهای داخل این کنترلر با products شروع شوند :
[RoutePrefix("products")]
public class ProductsController : Controller 
{
    public ProductsController() 
    { 
    }

    [Route]
    public ActionResult Index() 
    {
        return View();
    }
}

همانطور که در کد فوق ملاحظه می‌کنید اکشن متد Index را با افزودن ویژگی Route که آدرس ~/products را تطبیق می‌دهد تعیین کرده ایم.

نحوه تعیین Optional URI Parameter :

کافی است علامت سوال را به آخر پارامتر اضافه کنیم :
[Route("{id?}")]
public ActionResult Index(int id) 
{
   return View();
}
نحوه تعیین Default Route :
[RoutePrefix("products")]
[Route("{action=index}")]
public class ProductsController : Controller 
{
    public ProductsController() 
    { 
    }
    public ActionResult Index() 
    {
        return View();
    }
}
نحوه تعیین Constraint برای Routeها :
[Route("{id:int}")]
public ActionResult Delete(int id) 
{
   return View();
}
در مثال فوق گفته ایم که Id باید از نوع عدد صحیح باشد در غیر اینصورت آن را تطبیق نمی‌دهد.
همچنین می‌توانید از عبارات Regex نیز استفاده کنید به طور مثال در کد زیر پارامتر title باید یک متن و یا عبارت فارسی باشد در غیر اینصورت تطبیقی صورت نمی‌گیرد:
​[Route("{title:regex(\u0600-\u06FF)}")]
public ActionResult Search(string title)
{
   return View();
}​
در لینکی که در بالا معرفی شده لیست کامل Constraint‌ها را می‌توانید مشاهده نمائید،
مطالب
جایگزین کردن jQuery با JavaScript خالص - قسمت ششم - رخدادهای مرورگرها
واکنش نشان دادن به تغییرات صفحات وب، قسمت مهم و عمده‌ای از کار توسعه‌ی برنامه‌های وب را تشکیل می‌دهد. مفاهیم مرتبط با DOM events از زمان IE 4.0 و Netscape Navigator version 2 به مرورگرها اضافه شدند و به مرور تکامل یافتند. پیش از ظهور مرورگرهای مدرن (به IE 9.0 و مرورگرهای پیش از آن، مرورگرهای «باستانی» گفته می‌شود) به علت عدم هماهنگی آن‌ها با استانداردهای وب و تفاوت روش‌های رخدادگردانی، jQuery نقش مهمی را در زمینه‌ی یکدست سازی کدهای مدیریت رخدادها در بین مرورگرهای مختلف ارائه کرد. اما با پیش‌رفت‌های صورت گرفته و هماهنگی بیشتر مرورگرها با استانداردهای وب، دیگر نیازی به jQuery برای ارائه‌ی کدهای یکدست رخدادگردانی نیست و کار مستقیم با API وب مرورگرها برای این منظور کافی است.


انواع رخدادها: بومی و سفارشی

دو رده بندی عمومی رخدادها در مرورگرها وجود دارند: بومی و سفارشی.
بومی‌ها همان‌هایی هستند که در مستندات رسمی استانداردهای وب ذکر شده‌اند؛ مانند click که توسط ماوس و یا صفحه کلید فعال می‌شود و یا load که در زمان بارگذاری کامل صفحه، تصاویر و یا یک iframe رخ می‌دهد.
رخدادهای سفارشی مواردی هستند که توسط یک کتابخانه‌ی خاص و یا جهت یک برنامه‌ی خاص تهیه شده‌اند. مانند یک رخداد سفارشی که زمان شروع آپلود یک فایل را اعلام می‌کند.
رخدادهای سفارشی که بدون jQuery ایجاد و رخ‌می‌دهند، توسط jQuery نیز قابل بررسی و مدیریت هستند و نه برعکس. به عبارتی رخدادهای سفارشی ایجاد شده‌ی توسط jQuery غیراستاندارد بوده و صرفا مختص به API آن هستند.
در این بین، شیء استاندارد Event کار اتصال رخدادهای سفارشی و استاندارد را انجام می‌دهد. هر نوع رخداد DOM (سفارشی و یا بومی)، توسط یک شیء Event بیان می‌شود که آن نیز به همراه تعدادی خاصیت و متد، جهت مدیریت این رخداد است. برای مثال رخداد click دارای خاصیت type ایی به نام click است که در شیء Event متناظر با آن تعریف شده‌است.


انتشار رخدادها در صفحه

در روزهای آغازین وب، Netscape روش event capturing را برای انتشار رخدادها در صفحه ارائه داد و در مقابل آن IE روش event bubbling را معرفی کرد که متضاد یکدیگر بودند. در سال 2000 با ارائه استاندارد DOM Level 2 Events Specification، این وضعیت تغییر کرد و شامل هر دو مورد event capturing و event bubbling است و در حال حاضر تمام مرورگرهای مدرن این استاندارد را پیاده سازی کرده‌اند. بر اساس این استاندارد، زمانیکه رویدادی خلق می‌شود، فاز capturing آغاز می‌گردد که از شیء window شروع، سپس به شیء document منتشر می‌شود و این روند تا رسیدن به المانی که سبب بروز رخداد شده‌است ادامه پیدا می‌کند. پس از پایان فاز capturing، فاز جدید bubbling شروع می‌شود. در این فاز، رخداد از تمام والدین شیء هدف عبور می‌کند تا به شیء window برسد.
برای مثال اگر سند HTML ما چنین تعریفی را داشته باشد و بر روی المان «child of child of one» کلیک شده باشد:
   <!DOCTYPE html> 
   <html>
   <head>
     <title>event propagation demo</title>
   </head>
   <body>
     <section>
       <h1>nested divs</h1>
       <div>one
        <div>child of one
          <div>child of child of one</div>
        </div>
      </div>
    </section>
  </body>
  </html>
این رخداد در فاز capturing از این المان‌ها عبور می‌کند:
1.window
2.document
3.<html>
4.<body>
5.<section>
6.<div>one
7.<div>child of one
8.<div>child of child of one
و در فاز Bubbling از این المان‌ها:
9.<div>child of child of one
10.<div>child of one
11.<div>one
12.<section>
13.<body>
14.<html>
15.document
16.window
هرچند به دلایل تاریخی و همچنین عدم پشتیبانی jQuery از فاز capturing، بیشتر از فاز Bubbling به صورت پیش‌فرض در رخدادگردانی استفاده می‌شود. همچنین صدور رخداد از المانی که آن‌را ایجاد کرده‌است، بیشتر منطقی به نظر می‌رسد تا عکس آن.
البته باید درنظر داشت که jQuery از روش ارائه شده‌ی توسط مرورگر برای فاز Bubbling استفاده نمی‌کند و این مسیر را خودش مجددا محاسبه و رخدادگردان‌های این مسیر را به صورت دستی اجرا می‌کند. به همین جهت کارآیی آن نسبت به روش توکار و بومی مرورگرها کمتر است.


ایجاد رخدادهای DOM و صدور آن‌ها در jQuery

برای نمایش ایجاد و صدور رخدادهای DOM با و بدون jQuery، از قطعه کد HTML زیر استفاده می‌کنیم:
   <div>
     <button type="button">do something</button>
   </div>
 
  <form method="POST" action="/user">
     <label>Enter user name:
       <input name="user">
     </label>
     <button type="submit">submit</button>
  </form>
jQuery به همراه دو متد trigger و triggerHandler برای ایجاد و انتشار رخدادها در طول DOM است. متد trigger سبب ایجاد، صدور و انتشار یک رخداد به تمام والدهای شیء صادر کننده‌ی رخداد می‌شود. نوع این انتشار نیز bubbling است. متد triggerHandler، فقط بر روی المانی که فراخوانی می‌شود، عمل کرده و سبب انتشار این رخداد از طریق bubbling نمی‌شود:
   // submits the form 
   $('FORM').trigger('submit');
 
   // submits the form by clicking the button 
   $('BUTTON[type="submit"]').trigger('click');
 
   // focuses the text input 
   $('INPUT').trigger('focus');
 
  // removes focus from the text input 
  $('INPUT').trigger('blur');
در این مثال‌ها توسط متد trigger، به دو روش سبب submit یک فرم و همچنین در ابتدا سبب focus یک تکست باکس و سپس رفع آن شده‌ایم.
هرچند روش دومی نیز در jQuery API برای انجام همینکارها نیز پیش بینی شده‌است:
   // submits the form 
   $('FORM').submit();
 
   // submits the form by clicking the button 
   $('BUTTON[type="submit"]').click();
 
   // focuses the text input 
   $('INPUT').focus();
 
  // removes focus from the text input 
  $('INPUT').blur();
در اینجا به ازای هر رخداد، یک نام مستعار در jQuery API تدارک دیده شده‌است.

در ادامه فرض کنید یک دکمه داخل یک div قرار گرفته‌است و آن div نیز به همراه یک مدیریت کننده‌ی رخداد کلیک است. در این حالت اگر بخواهیم با کلیک بر روی دکمه سبب اجرای رویدادگردان div والد نشویم، می‌توان از متد triggerHandler استفاده کرد:
  // clicks the first button - the click event does not bubble 
  $('BUTTON[type="button"]').triggerHandler('click');


ایجاد رخدادهای DOM و صدور آن‌ها در جاوا اسکریپت (بدون استفاده از jQuery)

در web API مرورگرها، برای انجام بروز رخدادهای معادل مثالی که با jQuery مطرح شد، می‌توان متدهای بومی متناظر با این رخدادها را بر روی المان‌ها فراخوانی کرد:
   // submits the form 
   document.querySelector('FORM').submit();
 
   // submits the form by clicking the button 
   document.querySelector('BUTTON[type="submit"]').click();
 
   // focuses the text input 
   document.querySelector('INPUT').focus();
 
  // removes focus from the text input 
  document.querySelector('INPUT').blur();
قطعه کد فوق به علت استفاده‌ی از querySelector، با IE 8.0 و تمام مرورگرهای پس از آن سازگار است.
متدهای توکار و بومی click ،focus و blur بر روی تمام عناصر DOM که از اینترفیس HTMLElement مشتق شده باشند، وجود دارند. متد submit فقط بر روی المان‌هایی از نوع <form> وجود دارد و قابل فراخوانی است.
باید دقت داشت که فراخوانی متدهای click و submit از نوع bubbling است؛ اما متدهای focus و blur خیر. از این جهت که این دو رخداد فاز capturing را سبب می‌شوند.

متدهای یاد شده را توسط سازنده‌ی شیء Event و یا متد createEvent شیء document نیز می‌توان ایجاد کرد. یکی از کاربردهای آن، ارائه‌ی رفتاری سفارشی مانند triggerHandler جی‌کوئری است:
   var clickEvent;

   if (typeof Event === 'function') {
     clickEvent = new Event('click', {bubbles: false});
   }
   else {
       clickEvent = document.createEvent('Event');
      clickEvent.initEvent('click', false, true);
  }

  document.querySelector('BUTTON[type="button"]').dispatchEvent(clickEvent);
کار با سازنده‌ی شیء Event در تمام مرورگرهای جدید، منهای IE (تمام نگارش‌های آن) پشتیبانی می‌شود. در اینجا اگر این پشتیبانی وجود داشت، از خاصیت bubbles: false شیء Event استفاده می‌شود و اگر مرورگری قدیمی بود، از متد document.createEvent برای این منظور کمک گرفته می‌شود. در این حالت دومین پارامتر متد initEvent، همان bubbles است.


ایجاد و صدور رخدادهای سفارشی

فرض کنید در حال تهیه‌ی کتابخانه‌ای هستیم که افزودن و حذف آیتم‌ها را به یک گالری عکس ارائه می‌دهد. می‌خواهیم روشی را در اختیار مصرف کننده قرار دهیم تا بتواند به این رخدادهای سفارشی (غیر استانداردی که جزو W3C نیستند) گوش فرا دهد.
در جی‌کوئری برای ایجاد رخدادهای سفارشی به صورت زیر عمل می‌شود:
  // Triggers a custom "image-removed" element, 
  // which bubbles up to ancestor elements.
$libraryElement.trigger('image-removed', {id: 1});
در اینجا نیز صدور رخدادها همانند قبل و توسط همان متد trigger است. اما مشکلی که با آن وجود دارد این است که گوش فرا دهنده‌ی به این رخداد نیز باید توسط جی‌کوئری ارائه شود و خارج از این کتابخانه قابل دریافت و پیگیری نیست.
در خارج از جی‌کوئری و توسط web API استاندارد مرورگرها ایجاد و صدور رخدادهای سفارشی به همراه bubbling آن به صورت زیر است:
  var event = new CustomEvent('image-removed', {
    bubbles: true,
    detail: {id: 1}
  });
  libraryElement.dispatchEvent(event);
البته باید به‌خاطر داشته باشید این روش صرفا با مرورگرهای جدید (منهای تمام نگارش‌های IE) کار می‌کند. در اینجا اگر نیاز به ارائه‌ی راه حلی سازگار با IE نیز وجود داشت می‌توان از document.createEvent استفاده کرد:
  var event = document.createEvent('CustomEvent');
  event.initCustomEvent('image-removed', false, true, {id: 1});
  libraryElement.dispatchEvent(event);
و اگر بخواهیم بررسی وجود IE و یا پشتیبانی از CustomEvent را نیز قید کنیم، به قطعه کد زیر خواهیم رسید که با تمام مرورگرهای موجود کار می‌کند:
  var event;
 
   // If the `CustomEvent` constructor function is not supported, 
   // fall back to `createEvent` method. 
   if (typeof CustomEvent === 'function') {
     event = new CustomEvent('image-removed', {
       bubbles: true,
       detail: {id: 1}
     });
  }
  else {
      event = document.createEvent('CustomEvent');
      event.initCustomEvent('image-removed', false, true, {
        id: 1
      });
  }

  libraryElement.dispatchEvent(event);


گوش فرادادن به رخدادهای صادر شده، توسط jQuery

در جی‌کوئری با استفاده از متد on آن می‌توان به تمام رخدادهای استاندارد و همچنین سفارشی گوش فرا داد:
  $(window).on('resize', function() {
     // react to new window size 
  });
در ادامه برای حذف تمام گوش فرا دهنده‌های به رخداد resize می‌توان از متد off آن استفاده کرد:
  // remove all resize listeners - usually a bad idea 
  $(window).off('resize');
اما مشکلی را که این روش به همراه دارد، از کار انداختن تمام قسمت‌های دیگری است که هم اکنون به صدور این رخداد گوش فرا می‌دهند.
روش بهتر انجام اینکار، ذخیره‌ی ارجاعی به متدی است که قرار است این رویداد گردانی را انجام دهد:
  var resizeHandler = function() {
      // react to new window size 
  };

  $(window).on('resize', resizeHandler);

  // ...later 
  // remove only our resize handler 
  $(window).off('resize', resizeHandler);
و در آخر تنها این گوش فرا دهنده‌ی خاص را در صورت نیاز غیرفعال می‌کنیم و نه تمام گوش فرادهنده‌های سراسر برنامه را.

همچنین اگر یک گوش فراهنده‌ی به رخدادی تنها قرار است یکبار در طول عمر برنامه اجرا شود، می‌توان از متد one استفاده کرد:
$(someElement).one('click', function() {
    // handle click event 
});
پس از یکبار اجرای رخدادگردان کلیک در اینجا، از کلیک‌های بعدی صرفنظر خواهد شد.


گوش فرادادن به رخدادهای صادر شده، توسط جاوا اسکریپت خالص (یا همان web API مرورگرها)

ابتدایی‌ترین روش گوش فرادادن به رخدادها که از زمان آغاز معرفی آن‌ها در دسترس بوده‌است، روش تعریف inline آن‌ها است:
  <button onclick="handleButtonClick()">click me</button>
در اینجا متد رویدادگردان به یکی از ویژگی المان انتساب داده می‌شود. مشکل این روش، نیاز به سراسری تعریف کردن متد handleButtonClick است و دیگر نمی‌توان آن‌را در فضای نامی خاص و یا سفارشی قرار داد.
روش دیگر ثبت رویدادگردان click، انتساب متد آن به خاصیت رخداد متناظری در آن المان ویژه است:
  buttonEl.onclick = function() {
    // handle button click 
  };
مزیت این روش، عدم نیاز به استفاده‌ی از متدهای سراسری است.
البته باید دقت داشت که یکی از دو روش یاد شده را می‌توانید استفاده کنید. در اینجا آخرین رویدادگردان متصل شده‌ی به المان، همواره تمام نمونه‌های موجود دیگر را بازنویسی می‌کند.
اگر نیاز به معرفی رویدادگردان‌های متعددی برای یک المان در ماژول‌های مختلف برنامه وجود داشت، از زمان IE 9.0 به بعد، متد addEventListener برای این منظور تدارک دیده شده‌است و syntax آن بسیار شبیه به متد on جی‌کوئری است:
  buttonEl.addEventListener('click', function() {
    // handle button click 
  });
در این حالت دیگر مشکل نیاز به متدهای سراسری و یا عدم امکان تعریف بیش از یک رویدادگردان خاص برای المانی مشخص، دیگر وجود ندارد.
برای نمونه معادل قطعه کد جی‌کوئری که پیشتر با متد on نوشتیم، با جاوا اسکریپت خالص به صورت زیر است:
  window.addEventListener('resize', function() {
    // react to new window size 
  });
در اینجا برای حذف یک رویدادگردان می‌توان از متد removeEventListener استفاده کرد. تفاوت مهم آن با متد off جی‌کوئری این است که در اینجا حتما باید مشخص باشد کدام رویدادگردان را می‌خواهید حذف کنید:
  var resizeHandler = function() {
      // react to new window size 
  };

  window.addEventListener('resize', resizeHandler);

  // ...later 
  // remove only our resize handler 
  window.removeEventListener('resize', resizeHandler);
یعنی روش حذف رویدادگردان‌ها در اینجا شبیه به مثال دومی است که برای متد off جی‌کوئری ارائه کردیم. ابتدا باید ارجاعی را به متد رویدادگردان تهیه کنیم و سپس بر اساس این ارجاع، امکان حذف آن وجود خواهد داشت.
در اینجا حتی امکان تعریف متد one جی‌کوئری نیز پیش بینی شده‌است (البته جزو استانداردهای جدید وب از سال 2016 است):
  someElement.addEventListener('click', function(event) {
     // handle click event 
  }, { once: true });
اگر بخواهیم متد one سازگار با مرورگرهای قدیمی‌تر را نیز ارائه کنیم، چنین شکلی را پیدا می‌کند:
  var clickHandler = function() {
    // handle click event 
    // ...then unregister handler 
    someElement.removeEventListener('click', clickHandler);
  };
  someElement.addEventListener('click', clickHandler);
در اینجا پس از بروز رخداد، کار removeEventListener آن به صورت خودکار صورت می‌گیرد.


کنترل انتشار رخدادها

فرض کنید می‌خواهیم جلوی انتخاب المان‌های صفحه مانند تصاویر و متن را توسط ماوس بگیریم. روش انجام اینکار با jQuery به صورت زیر است:
$(window).on('mousedown', function(event) {
    event.preventDefault();
});
و یا توسط web API مرورگرها به این صورت:
  window.addEventListener('mousedown', function(event) {
    event.preventDefault();
  });
مطابق «W3C DOM Level 3 Events specification» عملکرد پیش‌فرض رخداد mousedown با انتخاب متون و یا کشیدن و رها کردن المان‌ها آغاز می‌شود. متد preventDefault یکی از متدهای شیء event است که به رویدادگردان‌های تعریف شده ارسال می‌شود و توسط آن عملکرد پیش‌فرض آن رخداد لغو می‌شود.

برای جلوگیری کردن از انتشار رخدادی مانند click جهت رسیدن به سایر رویدادگردان‌های ثبت شده‌ی در بین راه فاز bubbling، می‌توان از متد stopPropagation استفاده کرد. روش انجام اینکار در جی‌کوئری:
  $someElement.on('click', function(event) {
      event.stopPropagation();
  });
البته jQuery صرفا فاز انتشار از نوع bubbling را پشتیبانی می‌کند.
و با web Api جهت جلوگیری از انتشار رخدادها در فاز capturing (این تنها راه مدیریت فاز capturing است):
  // stop propagation during capturing phase 
  someElement.addEventListener('click', function(event) {
      event.stopPropagation();
  }, true);
و یا استفاده از web API برای جلوگیری از انتشار رخدادها در فاز bubbling:
  // stop propagation during bubbling phase 
  someElement.addEventListener('click', function(event) {
      event.stopPropagation();
  });
البته باید درنظر داشت که متد stopPropagation از انتشار رخدادها به سایر گوش فرا دهنده‌های همان المان صادر کننده‌ی رخداد جلوگیری نمی‌کند. برای این منظور باید از متد stopImmediatePropagation استفاده کرد؛ در جی‌کوئری:
  $someElement.on('click', function(event) {
      event.stopImmediatePropagation();
  });
و توسط web API مرورگرها:
  someElement.addEventListener('click', function(event) {
      event.stopImmediatePropagation();
  });

یک نکته: در این حالت اگر متد رویدادگردانی مقدار false را برگرداند، به معنای فراخوانی هر دوی متد preventDefault و stopPropagation است.


ارسال اطلاعات به رویدادگردان‌ها

روش ارسال اطلاعات اضافی به رویداد گردان‌ها در جی‌کوئری به صورت زیر است:
  $uploaderElement.trigger('uploadError', {
    filename: 'picture.jpeg'
  });
و رویدادگردان گوش فرا دهنده‌ی به آن، به این نحو می‌تواند به filename دسترسی پیدا کند:
  $uploaderParent.on('uploadError', function(event, data) {
     showAlert('Failed to upload ' + data.filename);
  });
در اینجا دومین پارامتر تعریف شده، امکان دسترسی به تمام خواص سفارشی ارسالی را میسر می‌کند.

روش انجام اینکار با web API مرورگرها به صورت زیر است:
   // send the failed filename w/ an error event
  var event = new CustomEvent('uploadError', {
     bubbles: true,
     detail: {filename: 'picture.jpeg'}
   });
   uploaderElement.dispatchEvent(event);
 
   // ...and this is a listener for the event 
   uploaderParent.addEventListener('uploadError', function(event) {
      showAlert('Failed to upload ' + event.detail.filename);
  });
این روش با تمام مرورگرهای مدرن (منهای تمام نگارش‌های IE) کار می‌کند و روش دسترسی به خاصیت detail سفارشی تعریف و ارسال شده، از طریق همان خاصیت event ارسالی به رویدادگردان است.
و اگر می‌خواهید از IE هم پشتیبانی کنید، روش جایگزین کردن شیء CustomEvent با createEvent به صورت زیر است:
  // send the failed filename w/ an error event 
   var event = document.createEvent('CustomEvent');
   event.initCustomEvent('uploadError', true, true, {
     filename: 'picture.jpeg'
   });
   uploaderElement.dispatchEvent(event);
 
   // ...and this is a listener for the event 
   uploaderParent.addEventListener('uploadError', function(event) {
    showAlert('Failed to upload ' + event.detail.filename);
  });


متوجه شدن زمان بارگذاری یک شیء در صفحه

در حین توسعه‌ی برنامه‌های وب، با این نوع سؤالات زیاد مواجه خواهید شد: چه زمانی تمام و یا بعضی از المان‌های صفحه کاملا بارگذاری و رندر شده‌اند؟
پاسخ به این نوع سؤالات در W3C UI Events specification توسط رویداد استاندارد load داده شده‌است.

- چه زمانی تمام المان‌های موجود در صفحه کاملا بارگذاری و رندر شده و همچنین شیوه‌نامه‌های تعریف شده نیز به آن‌ها اعمال گردیده‌اند؟
روش انجام اینکار با jQuery:
  $(window).on('load', function() {
    // page is fully rendered 
  });
و توسط web API بومی مرورگرها که بسیار مشابه نمونه‌ی jQuery است:
  window.addEventListener('load', function() {
    // page is fully rendered 
  });

- چه زمانی markup استاتیک صفحه‌ی جاری در جای خود قرار گرفته‌اند؟
اهمیت این موضوع، به دسترسی به زمان مناسب و امن ایجاد تغییرات در DOM بر می‌گردد. برای این منظور رویداد استاندارد DOMContentLoaded پیش‌بینی شده‌است که زودتر از رویداد load، در دسترس برنامه نویس قرار می‌گیرد. در جی‌کوئری توسط یکی از دو روش معروف زیر به رویداد یاد شده دسترسی خواهید داشت:
  $(document).ready(function() {
    // markup is on the page 
  });

  //or
  $(function() {
    // markup is on the page 
  });
و معادل web API آن در تمام مرورگرهای جدید، همان تعریف رویدادگردانی برای DOMContentLoaded استاندارد است:
  document.addEventListener('DOMContentLoaded', function() {
    // markup is on the page 
  });

یک نکته: بهتر است این تعریف web API را پیش از تگ‌های <link> قرار دهید. زیرا بارگذاری آن‌ها، اجرای هر نوع اسکریپتی را تا زمان پایان عملیات، سد می‌کند.

- چه زمانی المانی خاص در صفحه بارگذاری شده‌است و چه زمانی بارگذاری یک المان با شکست مواجه شده‌است؟
در جی‌کوئری توسط بررسی رویدادهای load و error می‌توان به وضعیت نهایی بارگذاری المان‌هایی خاص دسترسی یافت:
   $('IMG').on('load', function() {
     // image has successfully loaded 
   });
 
   $('IMG').on('error', function() {
     // image has failed to load 
   });
روش انجام اینکار با web API مرورگرها نیز یکی است:
  document.querySelector('IMG').addEventListener('load', function() {
    // image has successfully loaded 
  });

  document.querySelector('IMG').addEventListener('error', function() {
    // image has failed to load 
  });

- جلوگیری از ترک اتفاقی صفحه‌ی جاری
گاهی از اوقات نیاز است برای از جلوگیری از تخریب صفحه‌ی جاری و از دست رفتن اطلاعات ذخیره نشده‌ی کاربر، اگر بر روی دکمه‌ی close بالای صفحه کلیک کرد و یا کاربر به اشتباه به صفحه‌ی دیگری هدایت شد، جلوی اینکار را بگیریم. برای این منظور رخداد استاندارد beforeunload درنظر گرفته شده‌است. روش استفاده‌ی از این رویداد در جی‌کوئری:
  $(window).on('beforeunload', function() {
    return 'Are you sure you want to unload the page?';
  });
و در web API مرورگرها:
  window.addEventListener('beforeunload', function(event) {
    var message = 'Are you sure you want to unload the page?';
    event.returnValue = message;
    return message;
  });
در حالت web API بعضی از مرورگرها از نتیجه‌ی متد استفاده می‌کنند و بعضی دیگر از مقدار خاصیت event.returnValue. به همین جهت هر دو مورد در اینجا مقدار دهی شده‌اند.