<TextBlock Text="نمایش مخلوطی از متن فارسی و متن English با هم" Margin="7" FontFamily="Fonts/irsans.ttf#Iranian Sans, Comic Sans Ms" FontSize="25" FlowDirection="RightToLeft" VerticalAlignment="Top" HorizontalAlignment="Center" />
در این مثال به کمک MVC5، یک کپچای ساده و قابل فهم را تولید و استفاده خواهیم کرد. این نوشته بر اساس این مقاله ایجاد شده و جزئیات زیادی برای درک افراد مبتدی به آن افزوده شده است که امیدوارم راهنمای مفیدی برای علاقمندان باشد.
با کلیک راست بر روی پوشه کنترلر، یک کنترلر به منظور ایجاد کپچا بسازید و اکشن متد زیر را در آن کنترلر ایجاد کنید:
public class CaptchaController : Controller { public ActionResult CaptchaImage(string prefix, bool noisy = true) { var rand = new Random((int)DateTime.Now.Ticks); //generate new question int a = rand.Next(10, 99); int b = rand.Next(0, 9); var captcha = string.Format("{0} + {1} = ?", a, b); //store answer Session["Captcha" + prefix] = a + b; //image stream FileContentResult img = null; using (var mem = new MemoryStream()) using (var bmp = new Bitmap(130, 30)) using (var gfx = Graphics.FromImage((Image)bmp)) { gfx.TextRenderingHint = TextRenderingHint.ClearTypeGridFit; gfx.SmoothingMode = SmoothingMode.AntiAlias; gfx.FillRectangle(Brushes.White, new Rectangle(0, 0, bmp.Width, bmp.Height)); //add noise if (noisy) { int i, r, x, y; var pen = new Pen(Color.Yellow); for (i = 1; i < 10; i++) { pen.Color = Color.FromArgb( (rand.Next(0, 255)), (rand.Next(0, 255)), (rand.Next(0, 255))); r = rand.Next(0, (130 / 3)); x = rand.Next(0, 130); y = rand.Next(0, 30); gfx.DrawEllipse(pen, x - r, y - r, r, r); } } //add question gfx.DrawString(captcha, new Font("Tahoma", 15), Brushes.Gray, 2, 3); //render as Jpeg bmp.Save(mem, System.Drawing.Imaging.ImageFormat.Jpeg); img = this.File(mem.GetBuffer(), "image/Jpeg"); } return img; }
همانطور که از کد فوق پیداست، دو مقدار a و b، به شکل اتفاقی ایجاد میشوند و حاصل جمع آنها در یک Session نگهداری خواهد شد. سپس تصویری بر اساس تصویر a+b ایجاد میشود (مثل 3+4). این تصویر خروجی این اکشن متد است. به سادگی میتوانید این اکشن را بر اساس خواسته خود اصلاح کنید؛ مثلا به جای حاصل جمع دو عدد، از کاربرد چند حرف یا عدد که بصورت اتفاقی تولید کردهاید، استفاده نمائید.
فرض کنید میخواهیم کپچا را هنگام ثبت نام استفاده کنیم.
در فایل AccountViewModels.cs در پوشه مدلها در کلاس RegisterViewModel خاصیت زیر را اضافه کنید:
[Required(ErrorMessage = "لطفا {0} را وارد کنید")] [Display(Name = "حاصل جمع")] public string Captcha { get; set; }
حالا در پوشه View/Account به فایل Register.Cshtml خاصیت فوق را اضافه کنید:
<div class="form-group"> <input type="button" value="" id="refresh" /> @Html.LabelFor(model => model.Captcha) <img alt="Captcha" id="imgcpatcha" src="@Url.Action("CaptchaImage","Captcha")" style="" /> </div>
وظیفه این بخش، نمایش کپچاست. تگ img دارای آدرسی است که توسط اکشن متدی که در ابتدای این مقاله ایجاد نمودهایم تولید میشود. این آدرس تصویر کپچاست. یک دکمه هم با شناسه refresh برای به روز رسانی مجدد تصویر در نظر گرفتهایم.
حالا کد ایجکسی برای آپدیت کپچا توسط دکمه refresh را به شکل زیر بنویسید (من در پایین ویوی Register، اسکریپت زیر را قرار دادم):
<script type="text/javascript"> $(function () { $('#refresh').click(function () { $.ajax({ url: '@Url.Action("CaptchaImage","Captcha")', type: "GET", data: null }) .done(function (functionResult) { $("#imgcpatcha").attr("src", "/Captcha/CaptchaImage?" + functionResult); }); }); }); </script>
آنچه در url نوشته شده است، شاید اصولیترین شکل فراخوانی یک اکشن متد باشد. این اکشن در ابتدای مقاله تحت کنترلری به نام Captcha معرفی شده بود و خروجی آن آدرس یک فایل تصویری است. نوع ارتباط، Get است و هیچ اطلاعاتی به اکشن متد فرستاده نمیشود، اما اکشن متد ما آدرسی را به ما برمیگرداند که تحت نام FunctionResult آن را دریافت کرده و به کمک کد جی کوئری، مقدارش را در ویژگی src تصویر موجود در صفحه جاری جایگزین میکنیم. دقت کنید که برای دسترسی به تصویر، لازم است جایگزینی آدرس، در ویژگی src به شکل فوق صورت پذیرد.*
تنها کار باقیمانده اضافه کردن کد زیر به ابتدای اکشن متد Register درون کنترلر Account است.
if (Session["Captcha"] == null || Session["Captcha"].ToString() != model.Captcha) { ModelState.AddModelError("Captcha", "مجموع اشتباه است"); }
واضح است که اینکار پیش از شرط if(ModelState.IsValidate) صورت میگیرد و وظیفه شرط فوق، بررسی ِ برابریِ مقدار Session تولید شده در اکشن CaptchaImage (ابتدای این مقاله) با مقدار ورودی کاربر است. (مقداری که از طریق خاصیت تولیدی خودمان به آن دسترسی داریم) . بدیهیاست اگر این دو مقدار نابرابر باشند، یک خطا به ModelState اضافه میشود و شرط ModelState.IsValid که در اولین خط بعد از کد فوق وجود دارد، برقرار نخواهد بود و پیغام خطا در صفحه ثبت نام نمایش داده خواهد شد.
تصویر زیر نمونهی نتیجهای است که حاصل خواهد شد :
* اصلاح : دقت کنید بدون استفاده از ایجکس هم میتوانید تصویر فوق را آپدیت کنید:
$('#refresh').click(function () { var d = new Date(); $("#imgcpatcha").attr("src", "Captcha/CaptchaImage?" + d.getTime()); });
رویداد کلیک را با کد فوق جایگزین کنید؛ دو نکته در اینجا وجود دارد :
یک. استفاده از زمان در انتهای آدرس به خاطر مشکلاتیست که فایرفاکس یا IE با اینگونه آپدیتهای تصویری دارند. این دو مرورگر (بر خلاف کروم) تصاویر را نگهداری میکنند و آپدیت به روش فوق به مشکل برخورد میکند مگر آنکه آدرس را به کمک اضافه کردن زمان آپدیت کنید تا مرورگر متوجه داستان شود
دو. همانطور که میبینید آدرس تصویر در حقیقت خروجی یک اکشن است. پس نیازی نیست هر بار این اکشن را به کمک ایجکس صدا بزنیم و روش فوق در مرورگرهای مختلف جواب خواهد داد.
ایا معمار نرم افزار هستید؟
....
Becoming a software architect isn't something that simply happens overnight or with a promotion. It's a role , not a rank . It's an evolutionary process where you'll gradually gain the experience and confidence that you need to undertake the role.
@page "/test" <nav> <!-- یک روش --> <a href="#section2">Section2</a> <!-- روش دیگر --> <NavLink href="#section2">Section2</NavLink> </nav> @* ... *@ <h2 id="section2">It's Section2.</h2> @* ... *@
روش جاوااسکریپتی
@inject IJSRuntime JSRuntime @inject NavigationManager NavigationManager @implements IDisposable @code { protected override void OnInitialized() { NavigationManager.LocationChanged += OnLocationChanged; } protected override async Task OnAfterRenderAsync(bool firstRender) { await ScrollToFragment(); } public void Dispose() { NavigationManager.LocationChanged -= OnLocationChanged; } private async void OnLocationChanged(object sender, LocationChangedEventArgs e) { await ScrollToFragment(); } private async Task ScrollToFragment() { var uri = new Uri(NavigationManager.Uri, UriKind.Absolute); var fragment = uri.Fragment; if (fragment.StartsWith('#')) { // Handle text fragment (https://example.org/#test:~:text=foo) // https://github.com/WICG/scroll-to-text-fragment/ var elementId = fragment.Substring(1); var index = elementId.IndexOf(":~:", StringComparison.Ordinal); if (index > 0) { elementId = elementId.Substring(0, index); } if (!string.IsNullOrEmpty(elementId)) { await JSRuntime.InvokeVoidAsync("BlazorScrollToId", elementId); } } } }
function BlazorScrollToId(id) { const element = document.getElementById(id); if (element instanceof HTMLElement) { element.scrollIntoView({ behavior: "smooth", block: "start", inline: "nearest" }); } }
@page "/" <PageTitle>Index</PageTitle> <a href="#section2"> <h1>Section2</h1> </a> <SurveyPrompt Title="How is Blazor working for you?" /> <div style="height: 2000px"> </div> <div id="section2"> <h2>It's Section2. </h2> </div> <AnchorNavigation />
روش استفاده از توابع داخلی Blazor JS
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Rendering; using Microsoft.AspNetCore.Components.Routing; using System.Diagnostics.CodeAnalysis; namespace TestAnchorNavigation; public class AnchorNavigation: ComponentBase, IDisposable { private bool _setFocus; [Inject] private NavigationManager NavManager { get; set; } = default!; [Parameter] public RenderFragment? ChildContent { get; set; } [Parameter] public string? BookmarkName { get; set; } [DisallowNull] public ElementReference? Element { get; private set; } protected override void BuildRenderTree(RenderTreeBuilder builder) { builder.OpenElement(0, "span"); builder.AddAttribute(2, "tabindex", "-1"); builder.AddContent(3, this.ChildContent); builder.AddElementReferenceCapture(4, this.SetReference); builder.CloseElement(); } protected override void OnInitialized() => NavManager.LocationChanged += this.OnLocationChanged; protected override void OnParametersSet() => _setFocus = this.IsMe(); private void SetReference(ElementReference reference) => this.Element = reference; private void OnLocationChanged(object? sender, LocationChangedEventArgs e) { if (this.IsMe()) { _setFocus = true; this.StateHasChanged(); } } protected async override Task OnAfterRenderAsync(bool firstRender) { if (_setFocus) await this.Element!.Value.FocusAsync(false); _setFocus = false; } private bool IsMe() { string? elementId = null; var uri = new Uri(this.NavManager.Uri, UriKind.Absolute); if (uri.Fragment.StartsWith('#')) { elementId = uri.Fragment.Substring(1); return elementId == BookmarkName; } return false; } public void Dispose() => NavManager.LocationChanged -= this.OnLocationChanged; }
@page "/" <PageTitle>Index</PageTitle> <NavLink href="#section2"> <h1>Section2</h1> </NavLink> <SurveyPrompt Title="How is Blazor working for you?" /> <div style="height: 2000px"> </div> <AnchorNavigation BookmarkName="section2"> <h2>It's Section2. </h2> </AnchorNavigation>
extra small, small, medium, large, extra large
در این نگارش، break-point از نوع extra small، نسبت به نگارش سوم بوت استرپ، جدید است.
به این ترتیب این گرید به شدت انعطاف پذیر شده و میتوان برای اندازههای مختلف صفحه، طرحبندیهای متفاوتی را ارائه داد.
دربرگیرندهها و ردیفهای گرید بوت استرپ
گرید طرحبندی بوت استرپ 4، 12 ستونه است و از فناوری خاصی به نام Flexbox استفاده میکند. برای کار با آن نیاز است با دو عنصر اصلی زیر آشنا بود:
- Containers: هدف آنها تنظیم طرحبندی، مطابق با break-point (های) خاصی میباشد.
- ردیفها و ستونها: طرحبندی اصلی را تشکیل میدهند و ردیفها، تعریف ستونها را میسر میکنند.
بررسی Grid Containers
در بوت استرپ 4، دو نوع Container با کلاسهای زیر وجود دارند:
- container: یک دربرگیرندهی متداول است و طرحبندی را در میانهی صفحه و بر اساس break-point (های) خاصی نمایش میدهد. این دربرگیرنده، در اطرف آن یک padding با اندازهی 15px را نیز به همراه دارد و با break-pointهای زیر خودش را تطبیق میدهد:
extra small: کمتر از 576px
small: بیشتر از 576px
medium: بیشتر از 768px
large: بیشتر از 992px
extra-large: بیشتر از 1200px
- container-fluid: این دربرگیرنده، کل عرض نمایشی صفحه را پوشش میدهد.
یک مثال: تعریف container، row و columns
<head> <style> img { width: 100px; display: block; } </style> </head> <body> <header class="clearfix" style="height: 50vh; background: url(images/background.jpg) no-repeat center center; background-size: cover; margin-bottom: 20px;"> <div class="container"> <img src="images/wisdompetlogo.svg" alt="Wisdom Pet Logo"> </div> </header> <div class="container"> <section id="services"> <div class="row"> <article class="col"> <img class="mx-auto" src="images/icon-exoticpets.svg" alt="Icon"> <h3>Exotic Pets</h3> <p>We offer specialized care for reptiles, rodents, birds, and other exotic pets.</p> </article> <article class="col"> <img class="mx-auto" src="images/icon-grooming.svg" alt="Icon"> <h3>Grooming</h3> <p>Our therapeutic grooming treatments help battle fleas, allergic dermatitis, and other challenging skin conditions.</p> </article> <article class="col"> <img class="mx-auto" src="images/icon-health.svg" alt="Icon"> <h3>General Health</h3> <p>Wellness and senior exams, ultrasound, x-ray, and dental cleanings are just a few of our general health services.</p> </article> <article class="col"> <img class="mx-auto" src="images/icon-nutrition.svg" alt="Icon"> <h3>Nutrition</h3> <p>Let our nutrition experts review your pet's diet and prescribe a custom nutrition plan for optimum health and disease prevention.</p> </article> <article class="col"> <img class="mx-auto" src="images/icon-pestcontrol.svg" alt="Icon"> <h3>Pest Control</h3> <p>We offer the latest advances in safe and effective prevention and treatment of fleas, ticks, worms, heart worm, and other parasites.</p> </article> <article class="col"> <img class="mx-auto" src="images/icon-vaccinations.svg" alt="Icon"> <h3>Vaccinations</h3> <p>Our veterinarians are experienced in modern vaccination protocols that prevent many of the deadliest diseases in pets.</p> </article> </div> </section> </div> </body>
توضیحات:
- برای آزمایش این مثال، ابتدا کلاس container آنرا حذف کنید:
<div class="container">
اما چون میخواهیم محل قرارگیری لوگوی داخل این هدر، با حاشیهی سمت چپ container ذیل آن یکی باشد، این logo را داخل container قرار دادهایم.
- از این جهت که تصاویر استفاده شده از نوع svg هستند و بسیار بزرگ میباشند، با style تعریف شدهی در ابتدای head، اندازهی آنها را کاهش دادهایم و همچنین نوع نمایشی آنها را نیز به block تنظیم کردهایم. در این حالت برای اینکه تصاویر در میانهی این block قرار گیرند، از کلاس mx-auto استفاده شدهاست.
- در مرحلهی بعد، کار تعریف سطرها و ستونها انجام شدهاست:
<div class="container"> <section id="services"> <div class="row"> <article class="col">
برای مثال اگر عرض صفحه را کاهش دهیم، فقط به اندازهی سه آیتم، فضا برای نمایش وجود خواهد داشت و اگر عرض صفحه را بیشتر کنیم، امکان نمایش هر 6 آیتم تعریف شده را مانند تصویر قبلی، میسر میکند.
انواع ستونهای طرحبندی در بوت استرپ 4
همانطور که عنوان شد، گرید بوت استرپ 4، دارای 12 ستون است. البته در اینجا یک ستون میتواند عرض چندین ستون را نیز به خود اختصاص دهد.
در این تصویر، فرمول تعریف کلاسهای ستونها را در بوت استرپ 4 مشاهده میکنید. هر قسمتی از این فرمول که داخل پرانتز قرار گرفتهاست، یعنی ذکر آن اختیاری میباشد؛ به همین جهت در مثال قبلی، ذکر صرفا col نیز کار کرد. در این حالت اگر دو ستون را تعریف کنید، هر کدام 50 درصد عرض container را به خود اختصاص میدهند و اگر سه ستون تعریف شود، هر کدام 33 درصد عرض را اشغال میکنند.
در این فرمول BP به معنای break-point است و یکی از مقادیر ذکر شدهی مقابل آنرا میتواند داشته باشد. برای مثال اگر col-sm را تعریف کردیم، یعنی این ستون تا زمانیکه اندازهی صفحه تا 576px باشد، کل عرض صفحه را پر میکند.
COL در اینجا به معنای تعداد ستونی است که این محتوا قرار است به خود اختصاص دهد. برای مثال col-md-6 یعنی این ستون تا رسیدن به break-point از نوع md، کل عرض صفحه را به خود اختصاص میدهد؛ پس از آن (اندازهی صفحهی بیشتر از 768px) این ستون، 6 واحد از 12 واحد ممکن را به خود اختصاص خواهد داد.
ذکر BP نیز اختیاری است. یعنی اگر تنها col-6 را تعریف کردیم، این ستون در تمام break-pointها و در تمام اندازههای ممکن صفحه، همواره 6 واحد از 12 واحد موجود را به خود اختصاص میدهد.
مثال: بررسی فرمول تعریف ستونها در بوت استرپ 4
<head> <style> img { width: 100%; height: 200px; max-height: 200px; } </style> </head> <body> <div class="container"> <div id="services"> <div class="row"> <section class="col"> <img src="images/image.png" alt="sample image"> <h4>Exotic Pets</h4> <p>We offer <strong>specialized</strong> care for <em>reptiles, rodents, birds,</em> and other exotic pets.</p> </section> <section class="col"> <img src="images/image.png" alt="sample image"> <h4>Grooming</h4> <p>Our therapeutic <span class="font-weight-bold">grooming</span> treatments help battle fleas, allergic dermatitis, and other challenging skin conditions.</p> </section> <section class="col"> <img src="images/image.png" alt="sample image"> <h4>General Health</h4> <p>Wellness and senior exams, ultrasound, x-ray, and dental cleanings are just a few of our general health services.</p> </section> <section class="col"> <img class="img-fluid" src="images/image.png" alt="sample image"> <h4>Nutrition</h4> <p>Let our nutrition experts review your pet's diet and prescribe a custom nutrition plan for optimum health and disease prevention.</p> </section> <section class="col"> <img src="images/image.png" alt="sample image"> <h4>Pest Control</h4> <p>We offer the latest advances in safe and effective prevention and treatment of fleas, ticks, worms, heart worm, and other parasites.</p> </section> <section class="col"> <img src="images/image.png" alt="sample image"> <h4>Vaccinations</h4> <p>Our veterinarians are experienced in modern vaccination protocols that prevent many of the deadliest diseases in pets.</p> </section> </div> </div> </div> </body>
- سپس کل محتوا را داخل یک ردیف تعریف کردهایم.
- در ادامه هر المان section تعریف شده را به صورت یک col در آوردهایم. به این ترتیب بوت استرپ سعی میکند تا کل عرض را با ستونهای تعریف شده تا جائیکه ممکن است پر کند:
و اگر عرض صفحه را کوچکتر کنیم، باز هم تاجائیکه ممکن است تعدادی را در سطر اول و مابقی را در سطرهای بعدی جا خواهد داد:
در ادامه میخواهیم مشخص کنیم، ستونهای خاصی، همیشه عرض مشخصی را به خود اختصاص دهند:
<div class="container"> <div id="services"> <div class="row"> <section class="col-6">
در این حالت، اولین ستون تعریف شده، تحت هر حالتی و با هر اندازهی صفحهای، همیشه نصف عرض (6 واحد از 12 واحد) را به خود اختصاص خواهد داد.
مرحلهی بعد این است که مشخص کنیم یک ستون در چه اندازهای از صفحه شروع کند به اختصاص دادن عرضی خاص به خود. برای این منظور هر 6 عنصر section تعریف شده را به صورت زیر ویرایش میکنیم:
<section class="col-sm">
و اگر صفحه را بزرگتر کنیم، مجددا همان حالت تعریف col خالی تکرار میشود و بوت استرپ سعی میکند در هر سطر، تا جائیکه میتواند، تعداد آیتم بیشتری را جا دهد. اگر بخواهیم این تعداد جا دادنها را نیز کنترل کنیم، میتوان به صورت زیر عمل کرد:
<section class="col-sm-6">
امکان تعریف بیش از یک break-point برای هر ستون در بوت استرپ 4
در این جدول انواع break-pointهای قابل تعریف توسط بوت استرپ 4 را ملاحظه میکنید. تا اینجا تاثیر اعمال تنها یکی از اینها را بر روی یک ستون بررسی کردیم؛ اما میتوان چندین break-point را بر روی یک ستون نیز اعمال کرد. برای مثال اگر به تمام sectionهای مثال این قسمت ترکیب زیر را اضافه کنیم:
<section class="col-sm-6 col-md-4">
امکان تغییر موقعیت شروع ستونها در بوت استرپ 4
در تصویر فوق، فرمول تغییر موقعیت شروع ستونها در بوت استرپ 4 را مشاهده میکنید. برای مثال اگر offset-sm-1 را به اولین section اضافه کنیم:
<section class="col-sm-6 col-md-4 offset-sm-1">
برای نمونه این قابلیت، پیشتر یکی از روشهای در مرکز صفحه قرار دادن ستونها بود؛ اما چون بوت استرپ 4 از Flexbox استفاده میکند، روشهای بهتری نیز برای آن وجود دارند که آنها را در قسمتهای بعد بررسی میکنیم.
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: Bootstrap4_04.zip
CoffeeScript #14
قسمتهای اصلاح نشده
CoffeeScript در حال رفع برخی از معایب طراحی جاوااسکریپت است و این راه، بس طولانی است. همانطور که قبلا گفته شد، CoffeeScript به شدت به تجزیه و تحلیل استاتیک در زمان طراحی محدود شده است و هیچ بررسی در زمان اجرایی را برای بهبود کارآیی آن انجام نمیدهد.CoffeeScript از یک کامپایلر مستقیم منبع به منبع استفاده میکند. با این دیدگاه که هر دستور در CoffeeScript در نتیجه به یک دستور معادل در جاوااسکریپت تبدیل میشود.
CoffeeScript برای همهی کلمات کلیدی جاوااسکریپت، کلمهی معادلی ایجاد نمیکند، مانند typeof؛ و همچنین برخی از معایب طراحی جاوااسکریپت، به CoffeeScript نیز اعمال میشود.
در دو قسمت قبل + و + بر روی معایب طراحی در جاوااسکریپت که توسط CoffeeScript اصلاح شده بود، توضیح دادیم. حال میخواهیم درباره برخی از معایب جاوااسکریپت که CoffeeScript تا به حال نتوانسته است آنها را اصلاح کند صحبت کنیم.
استفاده از eval
در حالیکه CoffeeScript برخی از نقاط ضعف جاوااسکریپت را اصلاح کرده است، اما همچنان معایب دیگری نیز وجود دارند، که شما تنها باید از این نقاط ضعف آگاه باشید. یکی از این موارد، تابع eval است. برای استفاده از آن، باید با اشکالاتی که در حین کار با آن مواجه میشوید، آگاهی کامل داشته باشید و در صورت امکان از استفاده از آن اجتناب کنید.
تابع eval یک رشته از کد جاوااسکریپت را در حوزهی محلی اجرا میکند و توابعی مانند setTimeout و setInterval نیز میتوانند در آرگومان اولشان یک رشته از کد جاوااسکریپت را دریافت و ارزیابی کنند.
با این حال، مانند eval ،with نیز ردیابی کامپایلر را از کار میاندازد و این امر تاثیر بسیار زیادی بر روی کارآیی آن دارد. کامپایلر هیچ ایده ای درباره کدی که درون eval قرار داده شده است، ندارد تا زمانی که آن را اجرا کند. به همین دلیل نمیتواند هیچ عمل بهینه سازی را بر روی انجام دهد. یکی دیگر از نگرانیهای استفادهی از eval، امنیت است. در صورتیکه شما ورودی را به eval ارسال کنید، eval باعث میشود که کد شما به راحتی در معرض حملات تزریق کد قرار میگیرد. در 99% از مواقع، وقتی شما میخواهید از eval استفاده کنید، راههای بهتر و امنتری وجود دارند ( مانند استفاده از براکت ).
# Don't do this model = eval(modelName) # Use square brackets instead model = window[modelName]
استفاده از typeof
اپراتور typeof احتمالا بزرگترین نقص طراحی جاوااسکریپت است؛ تنها به این دلیل که اساسا به طور کامل شکست خورده است. در واقع از آن فقط یک استفاده میشود تا تشخیص داده شود که یک مقدار undefined است یا نه.
typeof undefinedVar is "undefined"
در اینجا لیستی از مشکلات، هنگام استفاده از typeof را مشاهده میکنید:
Value Class Type ---------------------------------------------- "foo" String string new String("foo") String object 1.2 Number number new Number(1.2) Number object true Boolean boolean new Boolean(true) Boolean object new Date() Date object new Error() Error object [1,2,3] Array object new Array(1, 2, 3) Array object new Function("") Function function /abc/g RegExp object new RegExp("meow") RegExp object {} Object object new Object() Object object
سوالی که اینجا مطرح میشود این است که ما چطور میتوانیم یک نوع را در جاوااسکریپت چک کنیم؟
خوب، خوشبختانه ()Object.prototype.toString ما را نجات داده است. اگر ما این تابع را بر روی یک شیء خاص فراخوانی کنیم، مقدار صحیح را بر میگرداند.
در اینجا مثالی از نحوهی پیاده سازی jQuery.type را مشاهده میکنید:
type = do -> classToType = {} for name in "Boolean Number String Function Array Date RegExp Undefined Null".split(" ") classToType["[object " + name + "]"] = name.toLowerCase() (obj) -> strType = Object::toString.call(obj) classToType[strType] or "object" # Returns the sort of types we'd expect: type("") # "string" type(new String) # "string" type([]) # "array" type(/\d/) # "regexp" type(new Date) # "date" type(true) # "boolean" type(null) # "null" type({}) # "object"
if typeof aVar isnt "undefined" objectType = type(aVar)
objectType = type(aVar?)
anArray?.push? aValue
استفاده از instanceof
کلمهی کلیدی instanceof نیز تقریبا همانند typeof شکست خورده است. در حالت ایده آل، instanceof، سازندهی دو شیء را با هم مقایسه میکند، در صورتیکه یک شیء نمونهای از شیء دیگر باشد، یک مقدار boolean را باز میگرداند. در واقع instanceof موقعی کار مقایسه را انجام میدهد که اشیاء، سفارشی سازی شده باشند. وقتی عمل مقایسه میخواهد بر روی این نوع اشیاء سفارشی سازی شده، انجام شود، استفاده از typeof بیفایده است.
new String("foo") instanceof String # true "foo" instanceof String # false
class Parent class Child extends Parent child = new Child child instanceof Child # true child instanceof Parent # true
استفاده از delete
از کلمه کلیدی delete برای حذف خصوصیات موجود در اشیاء به صورت کاملا مطمئن، میتوان استفاده کرد.anObject = {one: 1, two: 2} delete anObject.one anObject.hasOwnProperty("one") # false
aVar = 1 delete aVar typeof Var # "integer"
aVar = 1 aVar = null
فرض کنید میخواهیم وضعیت یک سایت را از لحاظ قابلیت دسترسی مونیتور کنیم، آیا Up است، Down است و امثال آن. یک سری از وب سرورها ping را بستهاند (ICMP Replies). بنابراین الزاما با استفاده از این روش ساده نمیتوان به مقصود رسید.
خوشبختانه انجام اینکار با استفاده از فضای نام استاندارد System.Net و کلاس HttpWebRequest ، بدون نیاز به هیچگونه کلاس یا کامپوننت خارجی، به سادگی قابل انجام است. کلاس زیر به همین منظور تهیه شده است:
using System;
using System.Net;
public class CSiteMonitor
{
public struct UrlHeaderInfo
{
public DateTime _lastModified;
public string _statusCode;
public string _errorMessage;
}
/// <summary>
/// آیا آدرس اینترنتی وارد شده معتبر است؟
/// </summary>
/// <param name="url">آدرس مورد نظر جهت بررسی</param>
/// <returns></returns>
public static bool IsValidURL(string url)
{
try
{
Uri uri = new Uri(url);
return (uri.Scheme == Uri.UriSchemeHttp) || (uri.Scheme == Uri.UriSchemeHttps);
}
catch { return false; }
}
/// <summary>
/// آدرس اینترنتی جهت بررسی
/// </summary>
/// <param name="url"></param>
/// <returns></returns>
/// <exception cref="ArgumentException">آدرس اینترنتی وارد شده معتبر نیست</exception>
public static UrlHeaderInfo GetSiteHeaderInfo(string url)
{
if (!IsValidURL(url))
throw new ArgumentException("آدرس اینترنتی وارد شده معتبر نیست", "url");
UrlHeaderInfo hhi = new UrlHeaderInfo { _lastModified = DateTime.Now, _statusCode = "NOK!", _errorMessage = string.Empty };
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
//request.Proxy
request.Method = "HEAD";
request.AllowAutoRedirect = true;
request.UserAgent = "Mozilla/5.0 (Windows; U; Windows NT 5.0; ; rv:1.8.0.7) Gecko/20060917 Firefox/1.9.0.1";
request.Timeout = 1000 * 300;
request.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
try
{
using (HttpWebResponse response = (HttpWebResponse) request.GetResponse())
{
hhi._statusCode = response.StatusCode.ToString();
hhi._lastModified = response.LastModified;
}
}
catch (Exception ex)
{
hhi._errorMessage = ex.Message;
}
return hhi;
}
}
- در متد GetSiteHeaderInfo نیاز بود تا از یک تابع بیش از یک خروجی داشته باشیم. راههای زیاد برای انجام اینکار هست.برای مثال:
الف)ارائه خروجیها به صورت یک آرایه. زیاد جالب نیست، چون اگر شخصی دقیقا مستندات متد شما را مطالعه نکند نمیداند که ترتیب خروجیها چگونه است و هر کدام چه معنایی دارند.
ب)ارائه خروجیها با استفاده از آرگومانهایی از نوع out یا ref . در دنیای شیء گرایی این نوع روشها را باید منسوخ شده در نظر گرفت و صرف سازگاری با زبانهایی مانند C که این روش در آنها رواج دارد (استفاده از آرگومانهایی از نوع اشارهگر) باید به آن نگاه کرد و نه بیشتر.
ج)خروجیها را به صورت یک کلاس یا struct درنظر گرفت تا استفاده کننده دقیقا بداند که فیلد خروجی چه معنایی دارد و هم چنین دقیقا چه تعداد خروجی مد نظر است.
- حتما باید از try/finally جهت اطمینان حاصل نمودن از بسته شدن response استفاده شود، در غیر اینصورت پس از دو خطای متوالی حاصل شده عملا دیگر نمیتوان از شیء response استفاده کرد. البته همانطور که پیش تر نیز ذکر شد، عبارت using توسط کامپایلر به try/finally بست داده میشود، بنابراین جهت خوانایی بیشتر کد بهتر است از این روش استفاده شود.
- جهت بلاک نشدن درخواست بهتر است از یک UserAgent کمک گرفته شود.
- جهت بررسی اعتبار یک آدرس اینترنتی یا میتوان از Regular expressions استفاده کرد یا از شیء Uri که روش آنرا ملاحظه میکنید.
- اگر در شبکه داخلی خود از پروکسی استفاده میشود، میتوان قسمت request.Proxy را با شیء پروکسی تنظیم شده مطابق مشخصات پروکسی سرور خود، بکار برد.
- در این مثال بیشتر هدف پیاده سازی کلاس دریافت اطلاعات هدر سایت بود و از ارائه کدهای مربوط به تایمر یا یک ترد جهت بررسی متوالی وضعیت سایت صرفنظر شد.
مثالی در مورد نحوهی استفاده از کلاس فوق:
CSiteMonitor.UrlHeaderInfo info = CSiteMonitor.GetSiteHeaderInfo("http://www.google.com");
MessageBox.Show(info._statusCode);
@using (Ajax.BeginForm("Upload", "Attachment", FormMethod.Post, new AjaxOptions { HttpMethod = "POST", }, new { encType = "multipart/form-data", id = "attach-form" })) { @Html.AntiForgeryToken() @Html.TextBoxFor(m => m.FirstName }) <input type="file" name="Files" data-buttonText="انتخاب تصویر"> <button type="submit">ارسال</button> }
var formData = new FormData(); $('form').submit(function() { var action = $(this).attr('action'); var formData = new FormData($(this).get(0)); $.ajax({ type: "POST", dataType: "json", url: action, data: formData, processData: false, contentType: false, success: function(data) { //... } success: function(data) { //... } }); return false; });
public class MyModel
{
public string FirstName{ get; set; }
public IEnumerable<HttpPostedFileBase> Files { get; set; }
}
[AjaxOnly] [HttpPost] [ValidateAntiForgeryToken] public ActionResult Upload(MyModel model) { if (!ModelState.IsValid) return //.... if(model.Files != null) foreach (var file in model.Files) if (file != null && file.ContentLength > 0) { // .... } }
زمانی که سیستم عامل های GUI مثل ویندوز به بازار آمدند، یکی از قسمتهای گرافیکی آنها AddressBar نام داشت که مسیر حرکت آنها را در فایل سیستم نشان میداد و در سیستم عاملهای متنی CLI با دستور cd یا pwd انجام میشد. بعدها در وب هم همین حرکت با نام BreadCrumb صورت گرفت که به عنوان مثال مسیر رسیدن به صفحهی یک محصول یا یک مقاله را نشان میداد. در یک پروژهی اندرویدی نیاز بود تا یک ساختار درختی را پیاده سازی کنم، ولی در برنامههای اندروید ایجاد یک درخت، کار هوشمندانه و مطلوبی نیست و روش کار به این صورت است که یک لیست از گروههای والد را نمایش داده و با انتخاب هر آیتم لیست به آیتمهای فرزند تغییر میکند. حالا مسئله این بود که کاربر باید مسیر حرکت خودش را بشناسد. به همین علت مجبور شدم یک BreadCrumb را برای آن طراحی کنم که در زیر تصویر آن را مشاهده میکنید.
از نکات جالب توجه در مورد این ماژول میتوان گفت که قابلیت این را دارد تا تصمیمات خود را بر اساس اندازههای مختلف صفحه نمایش بگیرد. به عنوان مثال اگر آیتمهای بالا بیشتر از سه عدد باشد و در صفحه جا نشود از یک مسیر جعلی استفاده میکند و همهی آیتمها با اندیس شماره 1 تا index-3 را درون یک آیتم با عنوان (...) قرار میدهد که من به آن میگویم مسیر جعلی. به عنوان نمونه مسیر تصویر بالا در صفحه جا شده است و نیازی به این کار دیده نشده است. ولی تصویر زیر از آن جا که مسیر، طول width صفحه نمایش رد کرده است، نیاز است تا چنین کاری انجام شود. موقعیکه کاربر آیتم ... را کلیک کند، مسیر باز شده و به محل index-3 حرکت میکند. یعنی دو مرحله به عقب باز میگردد.
نگاهی به کارکرد ماژول
قبل از توضیح در مورد سورس، اجازه دهید نحوهی استفاده از آن را ببینیم.
این سورس شامل دو کلاس است که سادهترین کلاس آن AndBreadCrumbItem میباشد که مشابه کلاس ListItem در بخش وب دات نت است و دو مقدار، یکی متن و دیگری Id را میگیرد:
سورس:
public class AndBreadCrumbItem { private int Id; private String diplayText; public AndBreadCrumbItem(int Id, String displayText) { this.Id=Id; this.diplayText=displayText; } public String getDiplayText() { return diplayText; } public void setDiplayText(String diplayText) { this.diplayText = diplayText; } public int getId() { return Id; } public void setId(int id) { Id = id; } }
به عنوان مثال میخواهیم یک breadcrumb را با مشخصات زیر بسازیم:
AndBreadCrumbItem itemhome=new AndBreadCrumbItem(0,"Home"); AndBreadCrumbItem itemproducts=new AndBreadCrumbItem(12,"Products"); AndBreadCrumbItem itemdigital=new AndBreadCrumbItem(15,"Digital"); AndBreadCrumbItem itemhdd=new AndBreadCrumbItem(56,"Hard Disk Drive");
AndBreadCrumb breadCrumb=new AndBreadCrumb(this); breadCrumb.AddNewItem(itemhome); breadCrumb.AddNewItem(itemproducts); breadCrumb.AddNewItem(itemdigital); breadCrumb.AddNewItem(itemhdd);
breadCumb.setContext(this);
پس از افزودن آیتم ها، تنظیمات زیر را اعمال نمایید:
LinearLayout layout=(LinearLayout)getActivity().findViewById(R.id.breadcumblayout); layout.setPadding(8, 8, 8, 8); breadCrumb.setLayout(layout); breadCrumb.SetTinyNextNodeImage(R.drawable.arrow); breadCrumb.setTextSize(25); breadCrumb.SetViewStyleId(R.drawable.list_item_style);
حال برای رسم آن متد UpdatePath را صدا میزنیم:
breadCrumb.UpdatePath();
الان اگر برنامه اجرا شود باید breadcrumb از چپ به راست رسم گردد. برای استفادههای فارسی، راست به چپ میتوانید از متد زیر استفاده کنید:
breadCrumb.setRTL(true);
در صورتیکه قصد دارید تنظیمات بیشتری چون رنگ متن، فونت متن و ... را روی هر المان اعمال کنید، از رویداد زیر استفاده کنید:
breadCrumb.setOnTextViewUpdate(new ITextViewUpdate() { @Override public TextView UpdateTextView(Context context, TextView tv) { tv.setTextColor(...); tv.setTypeface(...); return tv; } });
همچنین در صورتیکه میخواهید بدانید کاربر بر روی چه عنصری کلیک کرده است، از رویداد زیر استفاده کنید:
breadCumb.setOnClickListener(new IClickListener() { @Override public void onClick(int position, int Id) { //... } });
آخرین متد موجود که کمترین استفاده را دارد، متد SetNoResize است. در صورتیکه این متد با True مقداردهی گردد، عملیات تنظیم بر اساس صفحهی نمایش لغو میشود. این متد برای زمانی مناسب است که به عنوان مثال شما از یک HorozinalScrollView استفاده کرده باشید. در این حالت layout شما هیچ گاه به پایان نمیرسد و بهتر هست عملیات اضافه را لغو کنید.
نگاهی به سورس
کلاس زیر شامل بخشهای زیر است:
فیلدهای خصوصی
//=-=--=-=-=-=-=-=-=-=-=-=-=- Private Properties -=-=-=-=-=-=-=--=-=-= private List<AndBreadCrumbItem> items=null; private List<TextView> textViews; private int tinyNextNodeImage; private int viewStyleId; private Context context; private boolean RTL; private float textSize=20; private boolean noResize=false; LinearLayout layout; IClickListener clickListener; ITextViewUpdate textViewUpdate; LinearLayout.LayoutParams params ;
اینترفیسها هم با حرف I شروع و برای تعریف رویدادها ایجاد شدهاند. در ادامه از تعدادی متد get و Set برای مقدار دهی بعضی از فیلدهای خصوصی بالا استفاده شده است:
//=-=---=-=-=-=-- Constructor =--=-=-=-=-=--=-=- public AndBreadCrumb(Context context) { this.context=context; params = new LinearLayout.LayoutParams (LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT); } //=-=-=--=--=-=-=-=-=-=-=-=- Public Properties --=-=-=-=-=-=--=-=-=-=-=-=- //each category would be added to create path public void AddNewItem(AndBreadCrumbItem item) { if(items==null) items=new ArrayList<>(); items.add(item); } // if you want a pointer or next node between categories or textviews public void SetTinyNextNodeImage(int resId) {this.tinyNextNodeImage=resId;} public void SetViewStyleId(int resId) {this.viewStyleId=resId;} public void setTextSize(float textSize) {this.textSize = textSize;} public boolean isRTL() { return RTL; } public void setRTL(boolean RTL) { this.RTL = RTL; } public void setLayout(LinearLayout layout) { this.layout = layout; } public void setContext(Context context) { this.context = context; } public boolean isNoResize() { return noResize; } public void setNoResize(boolean noResize) { this.noResize = noResize; }
بعد از آن به متدهای خصوصی میرسیم که متد زیر، متد اصلی ما برای ساخت breadcrumb است:
//primary method for render objects on layout private void DrawPath() { //stop here if essentail elements aren't present if (items == null) return ; if (layout == null) return; if (items.size() == 0) return; //we need to get size of layout,so we use the post method to run this thread when ui is ready layout.post(new Runnable() { @Override public void run() { //textviews created here one by one int position = 0; textViews = new ArrayList<>(); for (AndBreadCrumbItem item : items) { TextView tv = MakeTextView(position, item.getId()); tv.setText(item.getDiplayText()); textViews.add(tv); position++; } //add textviews on layout AddTextViewsOnLayout(); //we dont manage resizing anymore if(isNoResize()) return; //run this code after textviews Added to get widths of them TextView last_tv=textViews.get(textViews.size()-1); last_tv.post(new Runnable() { @Override public void run() { //define width of each textview depend on screen width BatchSizeOperation(); } }); } }); }
TextView tv=new TextView(this); tv.getWidth(); //return 0 layout.add(tv); tv.getWidth(); //return 0
TextView tv=new TextView(this); tv.post(new Runnable() { @Override public void run() { tv.getWidth(); //return x } });
باز میگردیم به متد DrawPath و داخل متد post
در اولین خط این پروسه به ازای هر آیتم، یک TextView توسط متد MakeTextView ساخته میشود که شامل کد زیر است:
private TextView MakeTextView(final int position, final int Id) { //settings for cumbs TextView tv=new TextView(this.context); tv.setEllipsize(TextUtils.TruncateAt.END); tv.setSingleLine(true); tv.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize); tv.setBackgroundResource(viewStyleId); /*call custom event - this event will be fired when user click on one of textviews and returns position of textview and value that user sat as id */ tv.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { SetPosition(position); clickListener.onClick(position, Id); } }); //if user wants to update each textviews if(textViewUpdate!=null) tv=textViewUpdate.UpdateTextView(context,tv); if(isRTL()) tv.setRotationY(180); return tv; }
در خطوط اولیه، یک Textview ساخته و متد Ellipsize را با Truncate.END مقداردهی مینماید. این مقدار دهی باعث میشود اگر متن، در Textview جا نشد، ادامهی آن با ... مشخص شود. در خط بعدی Textview را تک خطه معرفی میکنیم. در خط بعدی اندازهی قلم را بر اساس آنچه کاربر مشخص کرده است، تغییر میدهیم و بعد هم استایل را برای آن مقداردهی میکنیم. بعد از آن رویداد کلیک را برای آن مشخص میکنیم تا اگر کاربر بر روی آن کلیک کرد، رویداد اختصاصی خودمان را فراخوانی کنیم.
در خط بعدی اگر rtl با true مقدار دهی شده باشد، textview را حول محور Y چرخش میدهد تا برای زبانهای راست به چپ چون فارسی آماده گردد و در نهایت Textview ساخته شده و به سمت متد DrawPath باز میگرداند.
بعد از ساخته شدن TextViewها، وقت آن است که به Layout اضافه شوند که وظیفهی اینکار بر عهدهی متد AddTextViewOnLayout است:
//this method calling by everywhere to needs add textviews on the layout like master method :drawpath private void AddTextViewsOnLayout() { //prepare layout //remove everything on layout for recreate it layout.removeAllViews(); layout.setOrientation(LinearLayout.HORIZONTAL); layout.setVerticalGravity(Gravity.CENTER_VERTICAL); if(isRTL()) layout.setRotationY(180); //add textviews one by one int position=0; for (TextView tv:textViews) { layout.addView(tv,params); //add next node image between textviews if user defined a next node image if(tinyNextNodeImage>0) if(position<(textViews.size()-1)) { layout.addView(GetNodeImage(), params); position++; } } }
تا به اینجا کار چیدمان به ترتیب انجام شده است ولی از آنجا که اندازهی Layout در هر گوشی و در دو حالت حالت افقی یا عمودی نگه داشتن گوشی متفاوت است، نمیتوان به این چینش اعتماد کرد که به چه نحوی عناصر نمایش داده خواهند شد و این مشکل توسط متد BatchSizeOperation (تغییر اندازه دسته جمعی) حل میگردد. در اینجا هم باز متد post به آخرین textview اضافه شده است. به این علت که موقعیکه همهی textviewها در ui جا خوش کردند، بتوانیم به خاصیتهای ui آنها دستیابی داشته باشیم. حالا بعد از ترسیم باید اندازه آنها را اصلاح کنیم. قدم به قدم متد BatchSizeOperation را بررسی میکنیم:
//set textview width depend on screen width private void BatchSizeOperation() { //get width of next node between cumbs Bitmap tinyBmap = BitmapFactory.decodeResource(context.getResources(), tinyNextNodeImage); int tinysize=tinyBmap.getWidth(); //get sum of nodes tinysize*=(textViews.size()-1); ... }
//get width size of screen(layout is screen here) int screenWidth=GetLayoutWidthSize(); //get sum of arrows and cumbs width int sumtvs=tinysize; for (TextView tv : textViews) { int width=tv.getWidth(); sumtvs += width; }
private int GetLayoutWidthSize() { int width=layout.getWidth(); int padding=layout.getPaddingLeft()+layout.getPaddingRight(); width-=padding; return width; }
private void BatchSizeOperation() { .... //if sum of cumbs is less than screen size the state is good so return same old textviews if(sumtvs<screenWidth) return ; if(textViews.size()>3) { //make fake path MakeFakePath(); //clear layout and add textviews again AddTextViewsOnLayout(); } //get free space without next nodes -> and spilt rest of space to textviews count to get space for each textview int freespace =screenWidth-tinysize; int each_width=freespace/textViews.size(); //some elements have less than each_width,so we should leave size them and calculate more space again int view_count=0; for (TextView tv:textViews) { if (tv.getWidth()<=each_width) freespace=freespace-tv.getWidth(); else view_count++; } if (view_count==0) return; each_width=freespace/view_count; for (TextView tv:textViews) { if (tv.getWidth()>each_width) tv.setWidth(each_width); } }
اگر آیتمها بیشتر از سه عدد باشند، میتوانیم از حالت مسیر جعلی استفاده کنیم که توسط متد MakeFakePath انجام میشود. البته بعد از آن هم باید دوباره viewها را چینش کنیم تا مسیر جدید ترسیم گردد، چون ممکن است بعد از آن باز هم جا نباشد یا آیتمها بیشتر از سه عدد نیستند. در این حالت، حداقل کاری که میتوانیم انجام دهیم این است که فضای موجود را بین آنها تقسیم کنیم تا همهی کاسه، کوزهها سر آیتم آخر نشکند و متنش به ... تغییر یابد و حداقل از هر آیتم، مقداری از متن اصلی نمایش داده شود. پس میانگین فضای موجود را گرفته و بر تعداد المانها تقسیم میکنیم. البته این را هم باید در نظر گرفت که در تقسیم بندی، بعضی آیتمها آن مقدار پهنا را نیاز ندارند و با پهنای کمتر هم میشود کل متنشان را نشان داد. پس یک کار اضافهتر این است که مقدار پهنای اضافی آنها را هم حساب کنیم و فقط آیتمهایی را پهنا دهیم که به مقدار بیشتری از این میانگین احتیاج دارند. در اینجا کار به پایان میرسد و مسیر نمایش داده میشود.
نحوهی کارکرد متد MakeFakePath بدین صورت است که 4 عدد TextView را ایجاد کرده که المانهای با اندیس 0 و 2 و 3 به صورت نرمال و عادی ایجاد شده و همان کارکرد سابق را دارند. ولی المان شماره دو با اندیس 1 با متن ... نمایندهی آیتمهای میانی است و رویدادکلیک آن به شکل زیر تحریف یافته است:
//if elements are so much(mor than 3),we make a fake path to decrease elements private void MakeFakePath() { //we make 4 new elements that index 1 is fake element and has a rest of real path in its heart //when user click on it,path would be opened textViews=new ArrayList<>(4); TextView[] tvs=new TextView[4]; int[] positions= {0,items.size()-3,items.size()-2,items.size()-1}; for (int i=0;i<4;i++) { //request for new textviews tvs[i]=MakeTextView(positions[i],items.get(positions[i]).getId()); if(i!=1) tvs[i].setText(items.get(positions[i]).getDiplayText()); else { tvs[i].setText("..."); //override click event and change it to part of code to open real path by call setposition method and redraw path tvs[i].setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { int pos = items.size() - 3; int id = items.get(pos).getId(); SetPosition(items.size() - 3); clickListener.onClick(pos, id); } }); } textViews.add(tvs[i]); } }
$("#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'] } );