مطالب
طریقه بررسی صحت کدملی به کمک متدهای الحاقی
برای بررسی صحت کدملی باید کمی با ساختار این کد آشنا شویم. کد ملی 6-761161-007 را در نظر بگیرید. همانطور که مشاهده می‌کنید این کد به 3 قسمت تقسیم شده است. قسمت اول که یک عدد 3 رقمی است نشان دهنده محل تولد فرد است. 6 رقم وسط شماره شناسایی فرد و رقم آخر، رقم کنترل است. در حقیقت ساختار 9 رقم اول طبق الگوریتمی قابل بررسی است که خروجی آن الگوریتم همان رقم آخر است. اگر خروجی الگوریتم با رقم کنترل برابر باشد می‌توان گفت کدملی وارد شده معتبر است و در غیر اینصورت کدملی معتبر نخواهد بود.
برای این منظور دو متدالحاقی زیر به پروژه DNT.Extensions اضافه شده است که بدنه آنها به شرح زیر است:
        /// <summary>
        /// Validate IR National Code
        /// </summary>
        /// <param name="nationalcode">National Code</param>
        /// <returns></returns>
        public static bool IsValidNationalCode(this string nationalcode)
        {
            int last;
            return nationalcode.IsValidNationalCode(out last);
        }

        /// <summary>
        /// Validate IR National Code
        /// </summary>
        /// <param name="nationalcode">National Code</param>
        /// <param name="lastNumber">Last Number Of National Code</param>
        /// <returns></returns>
        public static bool IsValidNationalCode(this string nationalcode, out int lastNumber)
        {
            lastNumber = -1;
            if (!nationalcode.IsItNumber()) return false; 
            var array = nationalcode.ToCharArray();
            if (array.Length != 10) return false;
            var j = 10;
            var sum = 0;
            for (var i = 0; i < array.Length - 1; i++)
            {
                sum += Int32.Parse(array[i].ToString(CultureInfo.InvariantCulture)) * j;
                j--;
            }
            var div = sum / 11;
            var r = div * 11;
            var diff = Math.Abs(sum - r);

            if (diff <= 2)
            {
                lastNumber = diff;
                return diff == Int32.Parse(array[9].ToString(CultureInfo.InvariantCulture));
            }
            var temp = Math.Abs(diff - 11);
            lastNumber = temp;
            return temp == Int32.Parse(array[9].ToString(CultureInfo.InvariantCulture));
        }
طریقه استفاده از این متدها نیز به شرح ذیل است:
bool b = "0077611616".IsValidNationalCode();
پی نوشت:
لطفاً بخش نظرات خوانندگان را نیز دنبال کنید.
نظرات مطالب
ساخت یک Form Generator ساده در MVC
به دو روش می‌تونید اینکار رو انجام بدید: 1- از یک جدول دیگر برای اعمال اعتبارسنجی استفاده کنید که کاربر خودش بتونه rule اعمال کنه، رابطه این جدول با جدول فیلد هم به صورت یک به چند هست یعنی یک فیلد می‌تونه چند تا validation rule داشته باشه:
public class FieldValidation
{
        public int Id { get; set; }
        public string Rule { get; set; }
        public virtual Field Field { get; set; }
}
روش دوم:
می‌تونید از یک فیلد اضافی تحت عنوان "متن خطا" در جدول فیلد استفاده کنید و در ویوی مربوطه به این صورت از اون استفاده کنید:
<div class="col-md-4">
                                    <input type="text" name="[@i].TitleEn" data-val="true" data-val-required="عنوان را وارد نمائید" id="[@i].TitleEn" value="" />
                                    <span class="field-validation-valid text-danger" data-valmsg-for="[@i].TitleEn" data-valmsg-replace="true"></span>
                                </div>

نظرات مطالب
فعال سازی و پردازش صفحات پویای افزودن، ویرایش و حذف رکوردهای jqGrid در ASP.NET MVC
سلام. من کد‌های زیر رو نوشتم اما دیلیت خطی ( ردیفی) برام کار نمی‌کنه و اررو 500 میده:
$("#JQGrid1").jqGrid({
                url: "/manager/Products/OnProductDataRequested",
                editurl: '/manager/Products/EditProductData',
                mtype: "GET",
                datatype: "json",
                page: 1,
                sortname: 'Priority',
                sortorder: "desc",
                viewrecords: true,
                jsonReader: { id: "Id" },
                prmNames: { id: "Id" },
                colNames: ["Id","خلاصه","توضیح خلاصه","قیمت","قیمت با تخفیف","درصد کارمزد سایت","آستانه هشدار موجودی","محتوا", "عنوان","فروشگاه","گروه","تعداد موجودی", "اولویت","محصولات مکمل","تصاویر","مقادیر مشخصه محصول","قیمت","نظرات", "فعال","ویژه","موجود","برند","عملیات درجا","عملیات کامل"],
                colModel: [
                    { key: true, width: 50, name: "Id", hidden: true, search: false },
                    {
                        editable: true, width: 10, name: "Abstract", search: true, stype: "text", editable: true,
                        hidden: true,
                        editrules: { edithidden: true }
                    },
                    {
                        editable: true, width: 10, name: "AbstractDescription", search: true, stype: "text", editable: true,
                        hidden: true,
                        editrules: { edithidden: true }
                    },
                    {
                        editable: true, width: 10, name: "Value", search: true, stype: "text", editable: true,
                        hidden: true,
                        editrules: { edithidden: true }
                    },
                    {
                        editable: true, width: 10, name: "Discount", search: true, stype: "text", editable: true,
                        hidden: true,
                        editrules: { edithidden: true }
                    },
                    {
                        editable: true, width: 10, name: "SiteWagePercentage", search: true, stype: "text", editable: true,
                        hidden: true,
                        editrules: { edithidden: true }
                    },
                    {
                        editable: true, width: 10, name: "InventoryAlertLimit", search: true, stype: "text", editable: true,
                        hidden: true,
                        editrules: { edithidden: true }
                    },
                    {
                        editable: true, width: 10, name: "Context", search: true, stype: "text", editable: true,
                        hidden: true,
                        editrules: { edithidden: true }
                    },
                    {
                        editable: true, width: 150, name: "Title", search: true, stype: "text",
                        searchoptions: { "sopt": ["bw", "eq"] }
                    },
                   {
                       name: "shopTitle", align: 'center', viewable: true, editrules: { edithidden: true },
                       search: true,
                       editable: true, stype: 'select',
                       edittype: 'select',
                       searchoptions: {
                           sopt: ["eq", "ne"],
                           dataUrl: "/manager/Products/Getshop/", buildSelect: function (data) {
                               var response, s = '<select>', i;
                               response = jQuery.parseJSON(data);
                               if (response && response.length) {
                                   $.each(response, function (i) {
                                       s += '<option value="' + this.shId + '">' + this.shTitle + '</option>';
                                   });
                               }
                               return s + '</select>';
                           }, 
                       },
                       editoptions: {
                           dataUrl: "/manager/Products/Getshop",
                           buildSelect: function (data) {
                               var response, s = '<select>', i;
                               response = jQuery.parseJSON(data);
                               if (response && response.length) {
                                   $.each(response, function (i) {
                                       s += '<option value="' + this.shId + '">' + this.shTitle + '</option>';
                                   });
                               }
                               return s + '</select>';
                           },
                       }
                   },
                    {
                        editable: true, name: "groupTitle", search: true, stype: "select"
                        , editrules: { edithidden: true },
                        search: true,
                        edittype: 'select',
                        editoptions: {
                        dataUrl:  "/manager/Products/GetGroups",
                          buildSelect: function (data) {
                              var response, s = '<select>', i;
                              response = jQuery.parseJSON(data);
                              if (response && response.length) {
                                  $.each(response, function (i) {
                                      s += '<option value="' + this.grpId + '">' + this.grpTitle + '</option>';
                                  });
                              }
                              return s + '</select>';
                          },
                          }
                    },
                    {
                        editable: true, width: 70, name: "AvailableCount", search: true, stype: "number",
                        searchoptions: { "sopt": ["bw", "eq"] }
                    },
                   {
                       editable: true, width: 50, name: "Priority", search: true, stype: "number",
                       searchoptions: { "sopt": ["bw", "eq"] }
                   },
                   {
                       editable: false, width: 80, name: "ComplementProducts", search: true, stype: "text",
                       searchoptions: { "sopt": ["bw", "eq"] }
                   },
                   {
                       editable: false, width: 70, name: "Images", search: true, stype: "text",
                       searchoptions: { "sopt": ["bw", "eq"] }
                   },
                   {
                       editable: false, width: 100, name: "ProductProperty", search: true, stype: "text",
                       searchoptions: { "sopt": ["bw", "eq"] }
                   },
                   {
                       editable: false, width: 80, name: "Price", search: true, stype: "text",
                       searchoptions: { "sopt": ["bw", "eq"] }
                   },
                   {
                       editable: false, width: 80, name: "Comments", search: true, stype: "text",
                       searchoptions: { "sopt": ["bw", "eq"] }
                   },
                   {
                       editable: true, width: 50, name: "Active", search: true, formatter: 'checkbox', edittype: 'checkbox', editoptions: { value: "True:False" }
                       , formatoptions: { disabled: false}
                   },
                    {
                        editable: true, width: 80, name: "AmazingOffer", search: true, formatter: 'checkbox', edittype: 'checkbox', editoptions: { value: "True:False" }
                        , formatoptions: { disabled: false}
                    },
                    {
                        editable: true, width: 80, name: "Available", search: true, formatter: 'checkbox', edittype: 'checkbox', editoptions: { value: "True:False" },
                        searchoptions: { "sopt": ["bw", "eq"] }, formatoptions: { disabled: false }
                    },
                   
                       {
                           name: 'BrandId', align: 'center', hidden: true, viewable: true, editrules: { edithidden: true },
                           editable: true, stype: 'select',
                           edittype: 'select',
                           editoptions: {
                               dataUrl: "/manager/Products/GetBrands",
                               buildSelect: function (data) {
                                   var response, s = '<select>', i;
                                   response = jQuery.parseJSON(data);
                                   if (response && response.length) {
                                       $.each(response, function (i) {
                                           s += '<option value="' + this.brandId + '">' + this.brandTitle + '</option>';
                                       });
                                   }
                                   return s + '</select>';
                               },
                           }

                       },
                    {
                        name: "myac", width: 80, fixed: true, sortable: false, resize: false, formatter: 'actions',
                        formatoptions: { keys: true }
                       },
                    {
                        editable: false, width: 70, name: "FullEdit", search: true, stype: "text",
                        searchoptions: { "sopt": ["bw", "eq"] }
                    },
                    // BLAH, BLAH, BLAH
                ],
                gridComplete: function () {
                    var ids = jQuery("#JQGrid1").jqGrid('getDataIDs');
                    for (var i = 0; i < ids.length; i++) {
                        var cl = ids[i];
                        ComplementProducts = "<div class=\"btn-group\"><a class='btn btn-primary' href=/manager/ComplementProducts/Index/" + cl + " }) + '><span class=\"fa fa-shopping-cart\" style='color: white;'></span></a></div>";
                        Images = "<div class=\"btn-group\"><a class='btn btn-primary' href=/manager/Images/Index/" + cl + " }) + '><span class=\"fa fa-picture-o\" style='color: white;'></span></a></div>";
                        ProductProperty = "<div class=\"btn-group\"><a class='btn btn-primary' href=/manager/ProductPropertyItems/Index/" + cl + " }) + '><span class=\"fa fa-braille\" style='color: white;'></span></a></div>";
                        Price = "<div class=\"btn-group\"><a class='btn btn-primary' href=/manager/pricesppitems/Index/" + cl + " }) + '><span class=\"fa fa-line-chart\" style='color: white;'></span></a></div>";
                        Comments = "<div class=\"btn-group\"><a class='btn btn-primary' href=/manager/Products/Comments/" + cl + " }) + '><span class=\"fa fa-comments\" style='color: white;'></span></a></div>";
                        FullEdit = "<div class=\"btn-group\"><a class='btn btn-primary' href=/manager/Products/Edit/" + cl + " }) + '><span class=\"fa fa-edit\" style='color: white;'></span></a><a class='btn btn-primary' href=/manager/Products/Details/" + cl + " }) + '><span class=\"fa fa-exclamation-circle\" style='color: white;'></span></a></div>";

                        jQuery("#JQGrid1").jqGrid('setRowData', ids[i], { ComplementProducts: ComplementProducts, Images: Images, ProductProperty: ProductProperty, Price: Price, Comments: Comments, FullEdit: FullEdit});
                    }
                }, loadComplete: function () {

                    var activeButton = getColumnIndexByName('Active');
                    var ids = jQuery("#JQGrid1").jqGrid('getDataIDs'); //id's
                    $("tbody > tr.jqgrow > td:nth-child(" + (activeButton + 1) + ") > input",
                      this).click(function (e) {
                          var rowId = $(e.target).closest("tr").attr("id");
                          // alert("active clicked " + rowId);
                          $.ajax({
                              type: "POST",
                              url: "/manager/Products/Activate",
                              data: {
                                  id: rowId
                              },
                              dataType: "json"
                          });

                      });



                    var amazingOfferButton = getColumnIndexByName('AmazingOffer');
                    var ids = jQuery("#JQGrid1").jqGrid('getDataIDs'); //id's
                    $("tbody > tr.jqgrow > td:nth-child(" + (amazingOfferButton + 1) + ") > input",
                      this).click(function (e) {
                          var rowId = $(e.target).closest("tr").attr("id");
                          $.ajax({
                              type: "POST",
                              url: "/manager/Products/ShowInAmazingOffer",
                              data: {
                                  id: rowId
                              },
                              dataType: "json"
                          });
                          $('#JQGrid1').trigger('reloadGrid');
                      });

                    var availableButton = getColumnIndexByName('Available');
                    var ids = jQuery("#JQGrid1").jqGrid('getDataIDs'); //id's
                    $("tbody > tr.jqgrow > td:nth-child(" + (availableButton + 1) + ") > input",
                      this).click(function (e) {
                          var rowId = $(e.target).closest("tr").attr("id");
                          $.ajax({
                              type: "POST",
                              url: "/manager/Products/Available",
                              data: {
                                  id: rowId
                              },
                              dataType: "json"
                          });
                          $('#JQGrid1').trigger('reloadGrid');
                      });
                },
                height: "auto",
                caption: "",
                viewrecords: true,
                rowNum: 20,
                direction: "rtl",
                pager: jQuery('#JQGrid1_pager'),
                rowList: [10, 20, 30, 40],
                toppager: true,
                jsonReader:
                {
                    root: "rows",
                    page: "page",
                    total: "total",
                    records: "records",
                    repeatitems: false,
                    Id: "0"
                },

            }).jqGrid('navGrid', '#JQGrid1_pager',
                // the buttons to appear on the toolbar of the grid
                { edit: true, add: true, del: true, search: true, refresh: true, view: false, position: "left", cloneToTop: true,searchtext:"جستجو" },
                   // options for the Edit Dialog
                   {
                       width: 450,
                       editCaption: "ویرایش محصول",
                       recreateForm: true,
                       closeAfterEdit: true,
                       viewPagerButtons: false,
                       //afterShowForm: populateGroups,
                       errorTextFormat: function (data) {
                           return 'Error: ' + data.responseText
                       }
                },
                   // Add options
                   { url: '/TabMaster/Create', closeAfterAdd: true },
                   // Delete options
                   { url: '/manager/Products/Remove' },
                   {
                       zIndex: 100,
                       caption: "جستجوی محصول",
                       sopt: ['cn']
                   }
                   );

 
مطالب
نکات استفاده از افزونه‌ی Web Essentials جهت کار با تصاویر
در این مطلب نکات کار با تصاویر را توسط افزونه‌ی Web Esstentials بررسی می‌کنیم. این افزونه قابلیت‌های زیر را در کار با تصاویر در اختیار شما قرار می‌دهد:
بهینه‌سازی تصاویر
یکی از موارد مهمی که باید مورد توجه قرار بگیرد، استفاده از تصاویر کم حجم در وب‌سایت می‌باشد. روش‌های مختلفی جهت بهینه‌سازی تصاویر مورد استفاده در سایت وجود دارند، به طور مثال جهت بهینه‌سازی تصاویر PNG می‌توانید از ابزار PNGGauntlet استفاده کنید. همچنین اینجا نیز یک ابزار آنلاین موجود می‌باشد. افزونه‌ی Web Essentails این قابلیت را به آسانی در اختیار شما قرار می‌دهد؛ اینکار را می‌توانید توسط این افزونه به روش‌های زیر انجام دهید:
  • کلیک راست بر روی تصویر
برای اینکار بر روی فایلی که می‌خواهید optimize کنید، کلیک راست کرده و از منوی ظاهر شده گزینه Web Essentials و سپس Optimize Image را انتخاب کنید:

در قسمت status bar نیز می‌توانید نتیجه را مشاهده کنید:

  • انتخاب چندین تصویر
روال قبلی را می‌توانید برای چندین فایل انتخاب شده و یا یک پوشه تکرار کنید:

  • بهینه‌سازی تصاویر موجود در فایل‌های CSS
همچنین امکان بهینه‌سازی تصاویر داخل فایل‌های CSS نیز توسط این افزونه امکان پذیر است:

  • بهینه سازی تصاویر Base64 Encode
توسط این افزونه می‌توانیم تصاویر  Data Uri  را نیز بهینه سازی کنیم:

همانطور که در تصویر فوق مشاهده می‌کنید می‌توانیم تصاویری که به صورت Data Uri درون کد پیوست شده اند را با کلیک بر روی Save to file به صورت یک فایل ذخیره کنیم.

ایجاد تصاویر Sprite
یکی دیگر ار قابلیت‌های افزونه Web Essentials امکان تهیه تصاویر به صورت Sprite می باشد. برای اینکار کافی است به این صورت عمل کنید:

بعد از کلیک بر روی Create image sprite باید یک نام برای آن تعیین کنید و سپس بر روی کلید Save کلیک کنید. با اینکار یک فایل از نوع XML با پسوند sprite برای شما ساخته خواهد شد:
<?xml version="1.0" encoding="utf-8"?>
<sprite xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://vswebessentials.com/schemas/v1/sprite.xsd">
  <settings>
    <!--Determines if the sprite image should be automatically optimized after creation/update.-->
    <optimize>true</optimize>
    <!--Determines the orientation of images to form this sprite. The value must be vertical or horizontal.-->
    <orientation>vertical</orientation>
    <!--File extension of sprite image.-->
    <outputType>png</outputType>
    <!--Determin whether to generate/re-generate this sprite on building the solution.-->
    <runOnBuild>false</runOnBuild>
    <!--Use full path to generate unique class or mixin name in CSS, LESS and SASS files. Consider disabling this if you want class names to be filename only.-->
    <fullPathForIdentifierName>true</fullPathForIdentifierName>
    <!--Use absolute path in the generated CSS-like files. By default, the URLs are relative to sprite image file (and the location of CSS, LESS and SCSS).-->
    <useAbsoluteUrl>false</useAbsoluteUrl>
    <!--Specifies a custom subfolder to save CSS files to. By default, compiled output will be placed in the same folder and nested under the original file.-->
    <outputDirectoryForCss />
    <!--Specifies a custom subfolder to save LESS files to. By default, compiled output will be placed in the same folder and nested under the original file.-->
    <outputDirectoryForLess />
    <!--Specifies a custom subfolder to save SCSS files to. By default, compiled output will be placed in the same folder and nested under the original file.-->
    <outputDirectoryForScss />
  </settings>
  <!--The order of the <file> elements determines the order of the images in the sprite.-->
  <files>
    <file>/Content/Images/01.png</file>
    <file>/Content/Images/02.png</file>
    <file>/Content/Images/03.png</file>
    <file>/Content/Images/04.png</file>
  </files>
</sprite>
یکی از زیر مجموعه‌های این فایل، تصویر نهایی می‌باشد، همچنین فایل‌های css, less, map و scss آن نیز تولید می‌شود:

به عنوان مثال فایل CSS تصویر فوق به صورت زیر می‌باشد:
/*
This is an example of how to use the image sprite in your own CSS files
*/
.Content-Images-01 {
/* You may have to set 'display: block' */
width: 32px;
height: 32px;
background: url('icons.png') 0 0;
}
.Content-Images-02 {
/* You may have to set 'display: block' */
width: 32px;
height: 32px;
background: url('icons.png') 0 -32px;
}
.Content-Images-03 {
/* You may have to set 'display: block' */
width: 32px;
height: 32px;
background: url('icons.png') 0 -64px;
}
.Content-Images-04 {
/* You may have to set 'display: block' */
width: 32px;
height: 32px;
background: url('icons.png') 0 -96px;
}
هر کدام از کلاس‌های فوق به یک تصویر در فایل مربوطه توسط image position اشاره می‌کند. شما می‌توانید با انتساب هر کدام از کلاس‌های فوق به یک المنت از آن تصویر استفاده نمائید:
<div class="Content-Images-01"></div>
<div class="Content-Images-02"></div>
<div class="Content-Images-03"></div>
<div class="Content-Images-04"></div>

استفاده از تصاویر Data URIs
یکی دیگر از روش‌های کاهش درخواست‌های HTTP در یک سایت استفاده از Data URIs  می باشد، توسط این روش می‌توانید فایل هایتان را درون HTML و یا CSS قرار دهید یا به اصطلاح embed کنید. به طور مثال جهت استفاده از یک تصویر می‌توانید به راحتی با آدرس دهی تصویر درون تگ img، تصویر را درون صفحه نمایش دهید:
<img src="https://www.dntips.ir/images/logo.png" />

همین کار را می‌توانیم توسط Data URIs انجام دهیم:
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAIAAAA7ljmRAAAAGElEQVQIW2P4DwcMDAxAfBvMAhEQMYgcACEHG8ELxtbPAAAAAElFTkSuQmCC" />
در کد فوق تصویر موردنظر را درون HTML به صورت embed شده قرار داده ایم، در این حالت دیگری نیازی به رفت و برگشت به سرور جهت نمایش تصویر نیست.
سینتکس Data URIs
به طور مثال تگ زیر را در نظر داشته باشید:
<img src="data:image/png;base64,iVBOR..." />
مقدار ویژگی src شامل موارد زیر است:
data: نام schema
image/png: نوع محتوا(content type)
base64: نوع encoding استفاده شده برای encode کردن اطلاعات
iVBOR...: اطلاعات encode شده.
توسط افزونه Web Essentials به راحتی می‌توانید تصویر موردنظرتان را به صورت Data URI تهیه کنید:

مطالب
ایجاد Drop Down List های آبشاری توسط Kendo UI
پیشتر مطلبی را در مورد ایجاد Drop Down List‌های به هم پیوسته توسط jQuery Ajax در این سایت مطالعه کرده بودید. شبیه به همان مطلب را اینبار قصد داریم توسط Kendo UI پیاده سازی کنیم.


مدل‌های برنامه

در اینجا قصد داریم لیست گروه‌ها را به همراه محصولات مرتبط با آن‌ها، توسط دو drop down list نمایش دهیم:
public class Category
{
    public int CategoryId { set; get; }
    public string CategoryName { set; get; }
 
    [JsonIgnore]
    public IList<Product> Products { set; get; }
}


public class Product
{
    public int ProductId { set; get; }
    public string ProductName { set; get; }
}
از ویژگی JsonIgnore جهت عدم درج لیست محصولات، در خروجی JSON نهایی تولیدی گروه‌ها، استفاده شده‌است.


منبع داده JSON سمت سرور

پس از مشخص شدن مدل‌های برنامه، اکنون توسط دو اکشن متد، لیست گروه‌ها و همچنین لیست محصولات یک گروه خاص را با فرمت JSON بازگشت می‌دهیم:
using System.Linq;
using System.Text;
using System.Web.Mvc;
using KendoUI12.Models;
using Newtonsoft.Json;
 
namespace KendoUI12.Controllers
{
 
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            return View(); // shows the page.
        }
 
        [HttpGet]
        public ActionResult GetCategories()
        {
            return new ContentResult
            {
                Content = JsonConvert.SerializeObject(CategoriesDataSource.Items),
                ContentType = "application/json",
                ContentEncoding = Encoding.UTF8
            };
        }
 
        [HttpGet]
        public ActionResult GetProducts(int categoryId)
        {
 
            var products = CategoriesDataSource.Items
                            .Where(category => category.CategoryId == categoryId)
                            .SelectMany(category => category.Products)
                            .ToList();
 
            return new ContentResult
            {
                Content = JsonConvert.SerializeObject(products),
                ContentType = "application/json",
                ContentEncoding = Encoding.UTF8
            };
        }
    }
}
بار اولی که صفحه بارگذاری می‌شود، توسط یک درخواست Ajax ایی، لیست گروه‌ها دریافت خواهد شد. سپس با انتخاب یک گروه، اکشن متد GetProducts جهت بازگرداندن لیست محصولات آن گروه، فراخوانی می‌گردد.
در اینجا به عمد از JsonConvert.SerializeObject استفاده شده‌است تا ویژگی JsonIgnore کلاس گروه‌ها، توسط کتابخانه‌ی JSON.NET مورد استفاده قرار گیرد (ASP.NET MVC برخلاف ASP.NET Web API به صورت پیش فرض از JSON.NET استفاده نمی‌کند).


کدهای سمت کاربر برنامه

کدهای جاوا اسکریپتی Kendo UI را جهت تعریف دو drop down list به هم مرتبط و آبشاری، در ادامه ملاحظه می‌کنید:
<!--نحوه‌ی راست به چپ سازی -->
<div class="k-rtl k-header demo-section">
    <label for="categories">گروه‌ها: </label><input id="categories" style="width: 270px" />
    <label for="products">محصولات: </label><input id="products" disabled="disabled" style="width: 270px" />
</div>
 
@section JavaScript
{
    <script type="text/javascript">
        $(function () {
            $("#categories").kendoDropDownList({
                optionLabel: "انتخاب گروه...",
                dataTextField: "CategoryName",
                dataValueField: "CategoryId",
                dataSource: {
                    transport: {
                        read: {
                            url: "@Url.Action("GetCategories", "Home")",
                            dataType: "json",
                            contentType: 'application/json; charset=utf-8',
                            type: 'GET'
                        }
                    }
                }
            });
 
            $("#products").kendoDropDownList({
                autoBind: false, // won’t try and read from the DataSource when it first loads
                cascadeFrom: "categories", // the id of the DropDown you want to cascade from
                optionLabel: "انتخاب محصول...",
                dataTextField: "ProductName",
                dataValueField: "ProductId",
                dataSource: {
                    // When the serverFiltering is disabled, then the combobox will not make any additional requests to the server.
                    serverFiltering: true, // the DataSource will send filter values to the server
                    transport: {
                        read: {
                            url: "@Url.Action("GetProducts", "Home")",
                            dataType: "json",
                            contentType: 'application/json; charset=utf-8',
                            type: 'GET',
                            data: function () {
                                return { categoryId: $("#categories").val() };
                            }
                        }
                    }
                }
            });
        });
    </script>
 
    <style scoped>
        .demo-section {
            width: 100%;
            height: 100px;
        }
    </style>
}
دراپ داون اول، به صورت متداولی تعریف شده‌است. ابتدا فیلدهای Text و Value هر ردیف آن مشخص و سپس منبع داده آن به اکشن متد GetCategories تنظیم گردیده‌است. به این ترتیب با اولین بار مشاهده‌ی صفحه، این دراپ داون پر خواهد شد.
سپس دراپ دوم که وابسته‌است به دراپ داون اول، با این نکات طراحی شده‌است:
الف) خاصیت autoBind آن به false تنظیم شده‌است. به این ترتیب این دراپ داون در اولین بار نمایش صفحه، به سرور جهت دریافت اطلاعات مراجعه نخواهد کرد.
ب) خاصیت cascadeFrom آن به id دراپ داون اول تنظیم شده‌است.
ج) در منبع داده‌ی آن دو تغییر مهم وجود دارند:
- خاصیت serverFiltering به true تنظیم شده‌است. این مورد سبب خواهد شد تا آیتم گروه انتخاب شده، به سرور ارسال شود.
- خاصیت data نیز تنظیم شده‌است. این مورد پارامتر categoryId اکشن متد GetProducts را تامین می‌کند و مقدار آن از مقدار انتخاب شده‌ی دراپ داون اول دریافت می‌گردد.

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


سپس با انتخاب یک گروه، لیست محصولات مرتبط با آن در دراپ داون دوم ظاهر می‌گردند:



کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید.
مطالب
تبدیل زیرنویس‌های یوتیوب به فایل srt

تعدادی از ویدیوهای یوتیوب، خصوصا مواردی که از Google videos به یوتیوب منتقل شده‌اند، دارای زیر نویس هستند. این زیر نویس هم فرمت جالبی دارد:
<?xml version="1.0" encoding="UTF-8"?>
<transcript>
<text start="23.49" dur="5.97">
&amp;gt;&amp;gt;Commentator: We have here today, Steve with
us whom I met not too long ago at something
</text>
...
</transcript>
و از هر لحاظ بهتر است از فرمت srt متداول (خصوصا از لحاظ قابلیت بهتر parse آن) ولی ... این فرمت xml ایی، متداول نیست و بهتر است جهت استفاده از آن در برنامه‌های پخش کننده فایل‌های ویدیویی، به srt تبدیل شود.
ابتدا شاید بپرسید که این‌ها را چگونه می‌توان دریافت کرد. روش کار به صورت زیر است:
http://video.google.com/timedtext?hl=en&v=Id&lang=en

در قسمت‌های hl و lang ، زبان مورد نظر قرار می‌گیرد و قسمت v همان id معروف ویدیوی یوتیوب مورد نظر است. برای مثال اگر لینک ویدیوی ما http://www.youtube.com/watch?v=-PA-buwI3q4 باشد، لینک زیرنویس انگلیسی هماهنگ با آن http://video.google.com/timedtext?hl=en&v=-PA-buwI3q4&lang=en خواهد بود.

البته زمان‌های این فایل xml یک نکته‌ی خاص گوگلی دارد!
اگر عدد 3.4 را مشاهده کردید یعنی سه ثانیه و 400 میلی ثانیه
اگر عدد 3.04 را مشاهده کردید یعنی سه ثانیه و 40 میلی ثانیه
به عبارتی با قسمت اعشاری آن باید به صورت رشته برخورد کنید. اگر طول آن یک بود، دو صفر و اگر طول آن 2 بود، 1 صفر در جلوی آن باید قرار گیرد تا قابل استفاده شود.

کلاس تهیه شده زیر کار تبدیل Xml گوگل را به Srt انجام می‌دهد:
using System;
using System.Linq;
using System.Text;
using System.Web;
using System.Xml.Linq;

namespace YtSubs
{
public class GoogleXmlToSrt
{
public string ConvertXmlToSrt(string url)
{
var transcript = loadTranscript(url);

var srtBuf = new StringBuilder();
var lineNumber = 0;

foreach (var item in transcript.Root.Elements("text"))
{
lineNumber++;
srtBuf.Append(lineNumber.ToString());

var timeLine = getTimeLine(item);
srtBuf.AppendFormat("{0}{1} --> {2}{3}", Environment.NewLine, timeLine.Item1, timeLine.Item2, Environment.NewLine);

var msg = decodeText(item);
srtBuf.AppendLine(msg + Environment.NewLine);
}
return srtBuf.ToString();
}

private static XDocument loadTranscript(string url)
{
var transcript = XDocument.Load(url);

if (transcript.Root == null)
throw new InvalidOperationException("couldn't load received data.");
return transcript;
}

private static string decodeText(XElement text)
{
var textValue = text.Value.Split('\n');
var msg = textValue.Aggregate(
string.Empty, (current, line) =>
current + (HttpUtility.HtmlDecode(line.Trim()) + Environment.NewLine)
);
return msg.Trim();
}

private static int fractionalPart(string data)
{
var idx = data.IndexOf(".");
if (idx == -1) return 0;
var fractionalPart = data.Substring(idx + 1);
if (fractionalPart.Length == 1) //3.4 --> 3.400
return int.Parse(fractionalPart + "00");

if (fractionalPart.Length == 2) //3.04 --> 3.040
return int.Parse(fractionalPart + "0");

return int.Parse(fractionalPart.Substring(0, 3));
}

private Tuple<string, string> getTimeLine(XElement text)
{
var startTs = getStartTs(text);
TimeSpan endTs = getEndTs(text, startTs);
return new Tuple<string, string>(timeSpanToString(startTs), timeSpanToString(endTs));
}

private static TimeSpan getStartTs(XElement text)
{
var startData = text.Attribute("start");
if (startData == null)
throw new InvalidOperationException("This is not a valid subtitle file.");

var start = startData.Value;
var startTs = new TimeSpan(0, 0, 0, (int)Math.Truncate(double.Parse(start)), fractionalPart(start));
return startTs;
}

private static TimeSpan getEndTs(XElement text, TimeSpan startTs)
{
TimeSpan endTs;
var durData = text.Attribute("dur");
if (durData == null)
{
endTs = startTs + new TimeSpan(0, 0, 0, 3, 0);
}
else
{
var dur = durData.Value;
var durTs = new TimeSpan(0, 0, 0, (int)Math.Truncate(double.Parse(dur)), fractionalPart(dur));
endTs = startTs + durTs;
}
return endTs;
}

private static string timeSpanToString(TimeSpan lineTs)
{
return string.Format("{0}:{1}:{2},{3}",
lineTs.Hours.ToString("D2"),
lineTs.Minutes.ToString("D2"),
lineTs.Seconds.ToString("D2"),
lineTs.Milliseconds.ToString("D3"));
}
}
}
و یک نمونه از استفاده آن می‌تواند به شکل زیر باشد:
using System.IO;

namespace YtSubs
{
class Program
{
static void Main(string[] args)
{
var url = "http://video.google.com/timedtext?hl=en&v=-PA-buwI3q4&lang=en";
//var url = "subtitle.xml";
var srtBuf = new GoogleXmlToSrt().ConvertXmlToSrt(url);
File.WriteAllText("-PA-buwI3q4.srt", srtBuf);
}
}
}


مطالب تکمیلی:
همه چیز درباره زیرنویس ویدئوهای گوگل

نظرات مطالب
اعتبار سنجی سمت کاربر wysiwyg-editor ها در ASP.NET MVC
مرسی بابت این پست، راه حل جدید و کارآمدی یاد گرفتم.
پیش از این برای ادیتور‌های Froala به نحو زیر عمل می‌کردم:

var editor = $("#content"); // textArea id
var text = editor.editable("getText"); // gets plain text
if(text.length < 1) {
    alert('Post content cannot be empty');
    editor.focus();
    return false;
}

یک سوال: با استفاده از راه حلی که مطرح کردید، چطور میشه ادیتور رو در صورت وجود خطا Focus کرد و یا کلاس CSS خاصی بدان اضافه کرد؟
نظرات مطالب
اجرای وظایف زمان بندی شده با Quartz.NET - قسمت اول
فعلاً با کد زیر مشکل موقتاً حل شد، یعنی کاربر باید با کلیک روی دکمه مطمئن بشه که حالا می‌تونه اقدام به ارسال بعدی کنه یا نمی‌تونه.
جالبه متوجه شدم اون کدهای متوقف کردن جاب هم در جا عمل نمی‌کنن، یعنی معلوم نیست کی متوقف بشه. پس اینجا به درد کار ما نمی‌خوره.
اگه راه بهتری به نظرتون رسید ممنون می‌شم در میون بزارید.
 protected void Button6_Click(object sender, EventArgs e)
    {
        var scheduler = new StdSchedulerFactory().GetScheduler();
        var jobs = scheduler.GetCurrentlyExecutingJobs();
        //foreach (var j in jobs)
        //{
        //    Console.WriteLine("Progress of {0} is {1}",
        //        j.JobDetail.Key,
        //        j.JobDetail.JobDataMap["progress"]);
        //    Label620.Text += j.JobDetail.Key.ToString();
           
        //}
        if (jobs.Count ==0)
        {



            
            Label620.Text = "آماده برای ارسال ایمیل!";

        }
        else
        {
            
            Label620.Text = "لطفاً بعد از چند دقیقه مجدداً تلاش نمایید!";
        }
        
    }
اشتراک‌ها
کتابخانه mresize

Event based jQuery element resize

The plugin does not use any kind of timer(s) to detect size changes. It uses the resize event on (invincible) iframe(s) which makes it perform much better than other solutions which use timers to poll element size. The script detects size changes made from JS, CSS, animations etc. and it works on any element able to contain other elements (e.g. div, p, li etc.). To use it on other inline elements (e.g. images) you need to add a wrapper and call the event on it.  Demo

کتابخانه mresize