نکاتی درباره برنامه نویسی دستوری(امری)
mutable Keyword
در فصل دوم(شناسه ها) گفته شد که برای یک شناسه امکان تغییر مقدار وجود ندارد. اما در #F راهی وجود دارد که در صورت نیاز بتوانیم مقدار یک شناسه را تغییر دهیم.در #F هرگاه بخواهیم شناسه ای تعریف کنیم که بتوان در هر زمان مقدار شناسه رو به دلخواه تغییر داد از کلمه کلیدی mutable کمک میگیریم و برای تغییر مقادیر شناسهها کافیست از علامت (->) استفاده کنیم. به یک مثال در این زمینه دقت کنید:
#1 let mutable phrase = "Can it change? " #2 printfn "%s" phrase #3 phrase <- "yes, it can." #4 printfn "%s" phrase
در خط اول یک شناسه را به صورت mutable(تغییر پذیر) تعریف کردیم و در خط سوم با استفاده از (->) مقدار شناسه رو update کردیم. خروجی مثال بالا به صورت زیر است:
Can it change? yes, it can.
نکته اول: در این روش هنگام update کردن مقدار شناسه حتما باید مقدار جدید از نوع مقدار قبلی باشد در غیر این صورت با خطای کامپایلری متوقف خواهید شد.
#1 let mutable phrase = "Can it change? " #3 phrase <- 1
اجرای کد بالا خطای زیر را به همراه خواهد داشت.(خطا کاملا واضح است و نیاز به توضیح دیده نمیشود)
Prog.fs(9,10): error: FS0001: This expression has type int but is here used with type string
let redefineX() = let x = "One" printfn "Redefining:\r\nx = %s" x if true then let x = "Two" printfn "x = %s" x printfn "x = %s" x
در مثال بالا در تابع redefineX یک شناسه به نام x تعریف کردم با مقدار "One". یک بار مقدار شناسه x رو چاپ میکنیم و بعد دوباره بعد از شرط true یک شناسه دیگر با همون نام یعنی x تعریف شده است و در انتها هم دو دستور چاپ. ابتدا خروجی مثال بالا رو با هم مشاهده میکنیم.
Redefining: x = One x = Two x = One
let mutableX() = let mutable x = "One" printfn "Mutating:\r\nx = %s" x if true then x <- "Two" printfn "x = %s" x printfn "x = %s" x
Mutating: x = One x = Two x = Two
Reference Cells
روشی برای استفاده از شناسهها به صورت mutable است. با این روش میتونید شناسه هایی تعریف کنید که امکان تغییر مقدار برای اونها وجود دارد. زمانی که از این روش برای مقدار دهی به شناسهها استفاده کنیم یک کپی از مقدار مورد نظر به شناسه اختصاص داده میشود نه آدرس مقدار در حافظه.
به جدول زیر توجه کنید:
Member Or Field | Description | Definition |
(derefence operator)! | مقدار مشخص شده را برگشت میدهد | let (!) r = r.contents |
(Assignment operator)=: | مقدار مشخص شده را تغییر میدهد | let (:=) r x = r.contents <- x |
ref operator | یک مقدار را در یک reference cell جدید کپسوله میکند | let ref x = { contents = x } |
Value Property | برای عملیات get یا set مقدار مشخص شده | member x.Value = x.contents |
contents record field | برای عملیات get یا set مقدار مشخص شده | let ref x = { contents = x } |
let refVar = ref 6 refVar := 50
printfn "%d" !refVar
let xRef : int ref = ref 10 printfn "%d" (xRef.Value) printfn "%d" (xRef.contents) xRef.Value <- 11 printfn "%d" (xRef.Value) xRef.contents <- 12 printfn "%d" (xRef.contents)
10 10 11 12
خصیصه اختیاری در #F
در #F زمانی از خصیصه اختیاری استفاده میکنیم که برای یک متغیر مقدار وجود نداشته باشد. option در #F نوعی است که میتواند هم مقدار داشته باشد و هم نداشته باشد.
let keepIfPositive (a : int) = if a > 0 then Some(a) else None
let exists (x : int option) = match x with | Some(x) -> true | None -> false
چگونگی استفاده از option
مثال
let rec tryFindMatch pred list = match list with | head :: tail -> if pred(head) then Some(head) else tryFindMatch pred tail | [] -> None let result1 = tryFindMatch (fun elem -> elem = 100) [ 200; 100; 50; 25 ] //برابر با 100 است let result2 = tryFindMatch (fun elem -> elem = 26) [ 200; 100; 50; 25 ]// برابر با None است
یک مثال کاربردی تر
open System.IO let openFile filename = try let file = File.Open (filename, FileMode.Create) Some(file) with | ex -> eprintf "An exception occurred with message %s" ex.Message None
Enumeration
تقریبا همه با نوع داده شمارشی یا enums آشنایی دارند. در اینجا فقط به نحوه پیاده سازی آن در #F میپردازیم. ساختار کلی تعریف آن به صورت زیر است:
type enum-name = | value1 = integer-literal1 | value2 = integer-literal2 ...
type Color = | Red = 0 | Green = 1 | Blue = 2
let col1 : Color = Color.Red
type uColor = | Red = 0u | Green = 1u | Blue = 2u let col3 = Microsoft.FSharp.Core.LanguagePrimitives.EnumOfValue<uint32, uColor>(2u)
توضیح درباره use
در دات نت خیلی از اشیا هستند که اینترفیس IDisposable رو پیاده سازی کرده اند. این بدین معنی است که حتما یک متد به نام dispose برای این اشیا وجود دارد که فراخوانی آن به طور قطع باعث بازگرداندن حافظه ای که در اختیار این کلاسها بود میشود. برای راحتی کار در #C یک عبارت به نام using وجود دارد که در انتها بلاک متد dispose شی مربوطه را فراخوانی میکند.
using(var writer = new StreamWriter(filePath)) { }
let writeToFile fileName = use sw = new System.IO.StreamWriter(fileName : string) sw.Write("Hello ")
در #F اعداد دارای علامت و اعداد شناور دارای وابستگی با واحدهای اندازه گیری هستند که به نوعی معرف اندازه و حجم و مقدار و ... هستند. در #F شما مجاز به تعریف واحدهای اندازه گیری خاص خود هستید و در این تعاریف نوع عملیات اندازه گیری را مشخص میکنید. مزیت اصلی استفاده از این روش جلوگیری از رخ دادن خطاهای کامپایلر در پروژه است. ساختار کلی تعریف:
[<Measure>] type unit-name [ = measure ]
[<Measure>] type cm
[<Measure>] type ml = cm^3
let value = 1.0<cm>
قدرت اصلی واحدهای اندازه گیری #F در توابع تبدیل است. تعریف توابع تبدیل به صورت زیر میباشد:
[<Measure>] type g تعریف واحد گرم [<Measure>] type kg تعریف واحد کیلوگرم let gramsPerKilogram : float<g kg^-1> = 1000.0<g/kg> تعریف تابع تبدیل
[<Measure>] type degC // دما بر حسب سلسیوس [<Measure>] type degF // دما بر حسب فارنهایت let convertCtoF ( temp : float<degC> ) = 9.0<degF> / 5.0<degC> * temp + 32.0<degF> // تابع تبدیل سلسیوس به فارنهایت let convertFtoC ( temp: float<degF> ) = 5.0<degC> / 9.0<degF> * ( temp - 32.0<degF>) // تابع تبدیل فارنهایت به سلسیوس let degreesFahrenheit temp = temp * 1.0<degF> // درجه به فارنهایت let degreesCelsius temp = temp * 1.0<degC> // درجه به سلسیوس printfn "Enter a temperature in degrees Fahrenheit." let input = System.Console.ReadLine() let mutable floatValue = 0. if System.Double.TryParse(input, &floatValue)// اگر ورودی عدد بود then printfn "That temperature in Celsius is %8.2f degrees C." ((convertFtoC (degreesFahrenheit floatValue))/(1.0<degC>)) else printfn "Error parsing input."
خروجی مثال بالا :
Enter a temperature in degrees Fahrenheit. 90 That temperature in degrees Celsius is 32.22.
- خواندن متن متدهای بالادستی را آسانتر میکند. در واقع نام متد عملا نقش کامنت را ایفا میکند. مراجعه کنید به Self documenting code
- احتمال اینکه متدها دوباره استفاده شوند را بالا میبرد
public void PrintInfo(dynamic person) { Console.WriteLine(person.Name); Console.WriteLine(person.LastName); Console.WriteLine(person.Job.Title); Console.WriteLine(person.Job.Salary); }
public void PrintInfo(dynamic person) { Console.WriteLine(person.Name); Console.WriteLine(person.LastName); PrintJob(person.Job); } public void PrintJob(dynamic job) { Console.WriteLine(job.Title); Console.WriteLine(job.Salary); }
مراحل انجام این بازسازی کد
- متد جدیدی ایجاد کنید و نام آن را بر اساس کاری که قرار است تکه کد انتخاب شده انجام دهد، تعیین کنید (نام را بر اساس کاری که قرار است انجام دهد انتخاب نمایید؛ نه نحوه انجام دادن آن کار).
- تکه کد نیازمند استخراج را از بدنه متد قبلی به بدنه متد جدید کپی کنید.
- کد استخراج شده را برای پیدا کردن متغیرهای محلی بهجا مانده از متد اصلی بررسی کنید. این متغیرها احتمالا متغیرهای محلی متد جدید و پارامترهای آن را تشکیل خواهند داد.
- اگر متغیرهای محلی موجود در تکه کد استخراج شده فقط در آن استفاده شدهاند، آنها را به صورت متغیرهای محلی موقتی در متد جدید ایجاد کنید.
- متغیرهای محلی تکه کد استخراج شده را بررسی کنید که آیا در جایی از آن ویرایش شدهاند یا خیر؟ در صورتیکه ویرایش شده باشند، بررسی کنید که آیا میتوان آن را به عنوان خروجی متد جدید در نظر گرفت و متغیرها را مقداردهی کرد یا خیر؟ (توضیح بیشتر در مثال)
- پارامترهای متناظری را با متغیرهای محلی موقتی ایجاد شده، در متد جدید ایجاد کنید.
- بعد از این که تکلیف تمامی متغیرهای محلی مشخص شد، کد را کامپایل کنید.
- در متد مبدا تکه کد استخراج شده را به فراخوانی متد جدید تغییر دهید.
- کد را کامپایل و تست کنید .
- متغیر ویرایش شده فقط در تکه کد استخراج شده ویرایش و استفاده شدهاست.
- متغیر ویرایش شده بعد از تکه کد استخراج شده نیز در بدنه متد اصلی مورد استفاده قرار گرفتهاست.
public void SetPayableAmount(dynamic orderItem) { var price = orderItem.Price; var discount = 0; var discountPlan = GetDiscountPlan(); var discountPercentage = discountPlan.Percentage; discount = price * discountPercentage; orderItem.PayableAmount = orderItem.Price - discount } private dynamic GetDiscountPlan() { throw new NotImplementedException(); }
public void SetPayableAmount(dynamic orderItem) { var price = orderItem.Price; var discount = GetDiscount(price); orderItem.PayableAmount = orderItem.Price - discount; } public dynamic GetDiscount(decimal price) { var discountPlan = GetDiscountPlan(); var discountPercentage = discountPlan.Percentage; return price * discountPercentage; } private dynamic GetDiscountPlan() { throw new NotImplementedException(); }
ابزارهای کمکی برای این بازسازی کد
stiReportMain.Dictionary.Report.BusinessObjectsStore.Clear(); reportViewer.Report.BusinessObjectsStore.Clear(); stiReportMain.RegBusinessObject("MyCategory", "documents", list); stiReportMain.Design();
stiReportMain.Dictionary.Report.BusinessObjectsStore.Clear(); reportViewer.Report.BusinessObjectsStore.Clear(); stiReportMain.RegBusinessObject("MyCategory", "documents", list); stiReportMain.Dictionary.SynchronizeBusinessObjects(); stiReportMain.Compile(); stiReportMain.RenderWithWpf(true); //Or Render();
تغییرات مورد نیاز سمت کلاینت جهت فعال سازی جستجو در jqGrid
در سمت کلاینت، در حین تعریف ستونها، ابتدا باید توسط مقدار دهی خاصیت search، ستونهای مشارکت کنندهی در حین جستجو را مشخص کرد:
colModel: [ { name: 'Name', index: 'Name', align: 'right', width: 200, search: true, stype: 'text', searchoptions: { sopt: searchOptions } }, { name: 'Supplier.Id', index: 'Supplier.Id', align: 'right', width: 200, search: true, stype: 'select', edittype: 'select', searchOperators: true, searchoptions: { sopt: searchOptions, dataUrl: '@Url.Action("SuppliersSelect","Home")' } } ],
- stype، روش مقایسهی مقادیر را مشخص میکند. مقدار پیش فرض آن text است و مقادیری مانند int، integer، float، number، numeric، date و datetime را میپذیرد.
- searchoptions برای تنظیم جزئیات نحوهی جستجوی بر روی فیلدها بکار میرود. توسط sopt میتوان آرایهای با مقادیر ذیل را مقدار دهی کرد:
var searchOptions = ['eq', 'ne', 'lt', 'le', 'gt', 'ge', 'bw', 'bn', 'in', 'ni', 'ew', 'en', 'cn', 'nc'];
البته باید دقت داشت که آرایه فوق کاملترین حالت ممکن است و ضرورتی ندارد تمام حالات را برای یک فیلد تعریف کرد. چون برای مثال جستجو cn یا contains برای مقادیر رشتهای معنا دارد و نه سایر حالات.
- searchoptions گزینههای دیگری را نیز میتواند شامل شود. برای مثال در حین نمایش جستجوی داخل ردیفی یا صفحهی دیالوگ مخصوص آن، قصد داریم فیلد نام تولید کننده را توسط یک drop down نمایش دهیم و نه یک text box پیش فرض. برای این منظور dataUrl مقدار دهی شدهاست.
SuppliersSelect آن به اکشن متد ذیل اشاره میکند که لیست تولید کنندگان را با فرمت لیستی از SelectListItemها به یک partial view تولید کنندهی دراپ داون ارسال میکند:
public ActionResult SuppliersSelect() { var list = ProductDataSource.LatestProducts; var suppliers = list.Select(x => new SelectListItem { Text = x.Supplier.CompanyName, Value = x.Supplier.Id.ToString(CultureInfo.InvariantCulture) }).ToList(); return PartialView("_SelectPartial", suppliers); }
@model IList<SelectListItem> @Html.DropDownList("srch", Model)
- اگر دقت کرده باشید، نام فیلد تولید کننده با Supplier.Id مقدار دهی شدهاست. علت اینجا است که در زمان استفاده از drop down، مقدار Id آیتم انتخابی، به سرور ارسال میشود. به همین جهت کار کردن پویا با Supplier.Id سادهتر خواهد بود.
- برای نمایش دیالوگ و یا نوار ابزار توکار جستجو، میتوان دکمههایی را به فوتر گرید اضافه کرد:
$(document).ready(function () { $('#list').jqGrid({ // ... مانند قبل و توضیحات فوق }) .jqGrid('navGrid', '#pager', { add: false, edit: false, del: false }, {}, // default settings for edit {}, // default settings for add {}, // delete instead that del:false we need this { // search options multipleSearch: true, closeOnEscape: true, closeAfterSearch: true, ignoreCase: true }) .jqGrid('navButtonAdd', "#pager", { caption: "نوار ابزار جستجو", title: "Search Toolbar", buttonicon: 'ui-icon-search', onClickButton: function () { toolbarSearching(); } }); }); function toolbarSearching() { $('#list').filterToolbar({ groupOp: 'OR', defaultSearch: "cn", autosearch: true, searchOnEnter: true, searchOperators: true, // فعال سازی منوی اپراتورها stringResult : true // وجود این سطر سبب میشود تا اپراتورها به سرور ارسال شوند }); }; function singleSearching() { $('#list').searchGrid({ closeAfterSearch: true }); }; function advancedSearching() { $('#list').searchGrid({ multipleSearch: true, closeAfterSearch: true }); };
- با استفاده از متد jqGrid و پارامتر navGrid، در ناحیهی pager گرید، تنظیمات جستجو را فعال کردهایم.
multipleSearch به معنای امکان جستجوی همزمان بر روی بیش از یک فیلد است. closeOnEscape سبب بسته شدن صفحهی دیالوگ جستجو با فشردن دکمهی ESC میشود. اگر closeAfterSearch به true تنظیم نشود، صفحهی دیالوگ جستجو پس از جستجو، در صفحه باقی مانده و بسته نخواهد شد.
- این دکمهی جستجو، جزو موارد توکار jqGrid است. اگر قصد داشته باشیم یک دکمهی سفارشی دیگر را نیز اضافه کنیم، مجددا از متد jqGrid با پارامتر navButtonAdd در ناحیهی pager استفاده خواهیم کرد. کلیک بر روی آن سبب اجرای متد toolbarSearching میشود.
در اینجا حداقل سه نوع جستجو را میتوان فعال کرد:
- filterToolbar که سبب نمایش نوار ابزار جستجو، دقیقا بالای ستونهای جدول میشود.
- searchGrid که صفحهی دیالوگ مستقلی را جهت جستجو به صورت پویا تولید میکند. اگر خاصیت multipleSearch آن به true تنظیم نشود، این جستجو هربار تنها بر روی یک فیلد قابل انجام خواهد بود و برعکس.
- در حالت جستجوی نوار ابزاری، اگر خواص searchOperators و stringResult به true تنظیم شوند، مانند تصویر ذیل، به ازای هر ستون میتوان از عملگرهای مختلفی استفاده کرد. در غیراینصورت جستجوی انجام شده بر اساس groupOp و defaultSearch پیش فرض انجام میشود. یعنی And یا Or تمام موارد تنها در حالت مثلا contains یا تساوی و امثال آن و نه حالت پیشرفتهی انتخاب عملگرها توسط کاربر.
یک نکته
اگر میخواهید صفحهی جستجو در وسط صفحه ظاهر شود، میتوانید از تنظیمات CSS ذیل استفاده کنید:
/* align center search popup in jqgrid */ .ui-jqdialog { display: none; width: 300px; position: absolute; padding: .2em; font-size: 11px; overflow: visible; left: 30% !important; top: 40% !important; }
پردازش سمت سرور جستجوی پویای jqGrid
کدهای سمت سرور، با کدهای استفاده از dynamic LINQ مایکروسافت یکی است. با این تفاوت که اینبار قسمت where این کوئری نیز پویا میباشد. پیشتر قسمت order by را پویا پردازش کرده بودیم. برای ساخت where پویا که در dynamic LINQ به خوبی پشتیبانی میشود، باید ابتدا ساختار اطلاعات ارسالی به سرور را آنالیز کنیم:
//single field search //_search=true&nd=1403935889318&rows=10&page=1&sidx=Id&sord=asc&searchField=Id&searchString=4444&searchOper=eq&filters= //multi-field search //_search=true&nd=1403935941367&rows=10&page=1&sidx=Id&sord=asc&filters=%7B%22groupOp%22%3A%22AND%22%2C%22rules%22%3A%5B%7B%22field%22%3A%22Id%22%2C%22op%22%3A%22eq%22%2C%22data%22%3A%2244%22%7D%2C%7B%22field%22%3A%22SupplierID%22%2C%22op%22%3A%22eq%22%2C%22data%22%3A%221%22%7D%5D%7D&searchField=&searchString=&searchOper= // filters -> {"groupOp":"AND","rules":[{"field":"All","op":"cn","data":"fffff"},{"field":"Price","op":"bn","data":"ffff"}]} //toolbar search //_search=true&nd=1403935593036&rows=10&page=1&sidx=Id&sord=asc&Id=2&Name=333&SupplierID=1&CategoryID=1&Price=44
- در تمام این حالات پارامتر _search مساوی true است (تفاوت آن با درخواست اطلاعات معمولی).
- در حالت جستجوی نوار ابزاری، اگر گزینههای searchOperators و stringResult به true تنظیم نشوند، حالت toolbar search فوق را شاهد خواهیم بود. در غیراینصورت به حالت multi-field search سوئیچ میشود.
- در حالت جستجوی تک فیلدی توسط صفحه دیالوگ جستجوی jqGrid، فیلد در حال جستجو توسط searchField و مقدار آن توسط searchString به سرور ارسال شدهاند. مابقی پارامترها نال هستند.
- در حالت جستجوی چند فیلدی توسط صفحه دیالوگ جستجوی jqGrid، اینبار filters مقدار دهی شدهاست و سایر پارامترها نال هستند. مقدار filters ارسالی، در حقیقت یک شیء JSON است با ساختار کلی ذیل:
{ "groupOp": "AND", "groups" : [ { "groupOp": "OR", "rules": [ { "field": "name", "op": "eq", "data": "England" }, { "field": "id", "op": "le", "data": "5"} ] } ], "rules": [ { "field": "name", "op": "eq", "data": "Romania" }, { "field": "id", "op": "le", "data": "1"} ] }
public class SearchFilter { public string groupOp { set; get; } public List<SearchGroup> groups { set; get; } public List<SearchRule> rules { set; get; } } public class SearchRule { public string field { set; get; } public string op { set; get; } public string data { set; get; } public override string ToString() { return string.Format("'{0}' {1} '{2}'", field, op, data); } } public class SearchGroup { public string groupOp { set; get; } public List<SearchRule> rules { set; get; } }
اگر این موارد را کنار هم قرار دهیم، به متدی عمومی ApplyFilter با امضای ذیل خواهیم رسید.
public IQueryable<T> ApplyFilter<T>(IQueryable<T> query, bool _search, string searchField, string searchString, string searchOper, string filters, NameValueCollection form)
پس از آن، تغییراتی که در کدهای متد GetProducts باید اعمال شوند به صورت ذیل است:
[HttpPost] public ActionResult GetProducts(string sidx, string sord, int page, int rows, bool _search, string searchField, string searchString, string searchOper, string filters) { var list = ProductDataSource.LatestProducts; var pageIndex = page - 1; var pageSize = rows; var totalRecords = list.Count; var totalPages = (int)Math.Ceiling(totalRecords / (float)pageSize); var productsQuery = list.AsQueryable(); productsQuery = new JqGridSearch().ApplyFilter(productsQuery, _search, searchField, searchString, searchOper, filters, this.Request.Form); var productsList = productsQuery.OrderBy(sidx + " " + sord) .Skip(pageIndex * pageSize) .Take(pageSize) .ToList(); var productsData = new JqGridData { Total = totalPages, Page = page, Records = totalRecords, Rows = (productsList.Select(product => new JqGridRowData { Id = product.Id, RowCells = new List<string> { product.Id.ToString(CultureInfo.InvariantCulture), product.Name, product.Supplier.CompanyName, product.Category.Name, product.Price.ToString(CultureInfo.InvariantCulture) } })).ToArray() }; return Json(productsData, JsonRequestBehavior.AllowGet); }
- نوع متد به HttpPost تغییر کردهاست. این مورد برای ارسال اطلاعات حجیم جستجوها به سرور ضروری است و بهتر است از حالت Get استفاده نشود.
این حالت در سمت کلاینت نیز باید تنظیم شود:
$('#list').jqGrid({ //url access method type mtype: 'POST',
productsQuery = new JqGridSearch().ApplyFilter(productsQuery, _search, searchField, searchString, searchOper, filters, this.Request.Form);
برای مطالعه بیشتر
جستجوی تک فیلدی
جستجوی نوار ابزاری
جستجوی چند فیلدی
کدهای کامل این مثال را از اینجا میتوانید دریافت کنید
jqGrid03.zip
الف) تهیه فایل nuspec
NuGet قابلیت پذیرش متغیرهای خود تکمیل شوندهای را نیز دارد. فایل nuspec یا جزئیات بستهی تولیدی نیوگت، در این حالت یک چنین شکلی را پیدا میکند:
<?xml version="1.0"?> <package > <metadata> <id>$id$</id> <version>$version$</version> <title>$title$</title> <authors>$author$</authors> <owners>$author$</owners> <licenseUrl>https://site.com/prj/LICENSE</licenseUrl> <projectUrl>https://site.com/prj</projectUrl> <requireLicenseAcceptance>false</requireLicenseAcceptance> <description>$description$</description> <copyright>Copyright 2015 My Name</copyright> </metadata> </package>
بنابراین تکمیل اطلاعات فایل AssemblyInfo.cs را فراموش نکنید. برای مثال اطلاعات AssemblyCompany آن، در قسمت authors فایل فوق جایگزین میشود.
ب) تهیه فایل NuGet.exe
فایل NuGet.exe را جهت کامپایل فایل فرضی TestNuget.nuspec نیاز داریم. میتوان آنرا از سایت کدپلکس تهیه کرد و یا تنها کافی است بر روی Solution موجود در VS.NET کلیک راست کرده و گزینهی Enable NuGet Package Restrore را انتخاب کنیم. با اینکار به صورت خودکار پوشهی ویژهای به نام .nuget به همراه فایل NuGet.exe ایجاد میشود.
ج) کامپایل فایل nuspec
در همان پوشهی جدید .nuget، یک فایل bat را با محتوای ذیل تهیه کنید:
"%~dp0NuGet.exe" pack "..\TestNuGet\TestNuGet.csproj" -Prop Configuration=Release copy "%~dp0*.nupkg" "%localappdata%\NuGet\Cache" pause
در ادامه برای اینکه امکان آزمایش محلی این بسته را نیز داشته باشیم، فایل بستهی تولیدی، به کش محلی نیوگت، در سیستم جاری کپی میشود.
نکتهی جالب این روش، تهیهی قسمت dependencies پروژه، به صورت خودکار است:
<?xml version="1.0"?> <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd"> <metadata> <id>TestNuget</id> <version>1.0.0.0</version> <title>TestNuget</title> <authors>Vahid N.</authors> <owners>Vahid N.</owners> <licenseUrl>https://site.com/prj/LICENSE</licenseUrl> <projectUrl>https://site.com/prj</projectUrl> <requireLicenseAcceptance>false</requireLicenseAcceptance> <description>This is a test.</description> <copyright>Copyright 2015 My Name</copyright> <dependencies> <dependency id="EntityFramework" version="6.1.2" /> </dependencies> </metadata> </package>
د) آزمایش محلی بستهی جدید
اکنون که فایل TestNuget.1.0.0.0.nupkg تولیدی را در آدرس localappdata%\NuGet\Cache% نیز کپی کردهایم، به منوی Tools/Options مراجعه و در قسمت NuGet Package Manager آن، به قسمت Package sources، این آدرس کش محلی را نیز معرفی کنید:
به این ترتیب، بدون ارسال یک بسته به سایت اصلی نیوگت، میتوان بسته را از کش محلی نیوگت نصب و آزمایش کرد:
فایلهای ذکر شده در مطلب فوق را از اینجا میتوانید دریافت کنید:
TestNuget.zip
PdfRpt-1.7.zip
- Added WingdingsSymbolField. - Improved detection of nested properties. - Added new overload for ProgressBarField template. Now it's possible to select its color based on the current row's values. - Added WingdingsSymbols/WingdingsSymbolsPdfReport.cs sample. - Added new NestedProperties/NestedPropertiesPdfReport.cs sample. - Added another WPF Sample (WpfAppAcroReader). - Enabled full compression. - Improved QRCode (Barcodes/BarcodesPdfReport.cs) sample to use a transparent background. - Improved the ASP.MVC sample to show how to Flush or Browse the produced PDF file. - Improved the DynamicCrosstab/DynamicCrosstabPdfReport.cs sample to show how to change the captions of the header row.
ایجاد ساختار ابتدایی پروژه
برای ساخت پروژه، به خط فرمان مراجعه کرده و با دستور زیر، یک پروژهی react از نوع typescript را ایجاد میکنیم.
npx create-react-app todo-mobx --template typescript cd todo-mobx
برای توسعهی این مثال، از محیط توسعهی VSCode استفاده میکنیم. اگر VSCode بر روی سیستم شما نصب باشد، در همان مسیری که خط فرمان باز است، دستور زیر را اجرا کنید؛ پروژهی شما در VSCode باز میشود:
code
سپس در محیط VSCode، دکمههای ctrl+` را فشرده (ctrl+back-tick) و دستورات زیر را در ترمینال ظاهر شده وارد کنید:
npm install --save-dev typescript @types/node @types/react @types/react-dom @types/jest npm install mobx mobx-react-lite --save
در ادامه برای استایل بندی بهتر برنامه از کتابخانههای bootstrap و font-awesome استفاده میکنیم:
npm install bootstrap --save npm install font-awesome --save
سپس فایل index.tsx را باز کرده و دو خط زیر را به آن اضافه میکنیم:
import "bootstrap/dist/css/bootstrap.css"; import "font-awesome/css/font-awesome.css";
کتابخانهی MobX، از تزئین کنندهها یا decorators استفاده میکند. بنابراین نیاز است به tsconfig پروژه مراجعه کرده و خط زیر را به آن اضافه کنیم:
"compilerOptions": { .... , "experimentalDecorators": true }
ایجاد مخازن حالت MobX
در ادامه نیاز است storeهای MobX را ایجاد کنیم و بعد آنها را به react اتصال دهیم. بدین منظور یک پوشهی جدید را در مسیر src، به نام stores ایجاد میکنیم و سپس فایل جدیدی را به نام todo-item.ts در آن با محتوای زیر ایجاد میکنیم:
import { observable, action } from "mobx"; export default class TodoItem { id = Date.now(); @observable text: string = ''; @observable isDone: boolean = false; constructor(text: string) { this.text = text; } @action toggleIsDone = () => { this.isDone = !this.isDone } @action updateText = (text: string) => { this.text = text; } }
در همان مسیر stores، فایل دیگری را نیز به نام todo-list.ts، با محتوای زیر ایجاد میکنیم:
import { observable, computed, action } from "mobx"; import TodoItem from "./todo-item"; export class TodoList { @observable.shallow list: TodoItem[] = []; constructor(todos: string[]) { todos.forEach(this.addTodo); } @action addTodo = (text: string) => { this.list.push(new TodoItem(text)); } @action removeTodo = (todo: TodoItem) => { this.list.splice(this.list.indexOf(todo), 1); }; @computed get finishedTodos(): TodoItem[] { return this.list.filter(todo => todo.isDone); } @computed get openTodos(): TodoItem[] { return this.list.filter(todo => !todo.isDone); } }
توضیحات:
مفهوم observable@: کل شیء state را به صورت یک شیء قابل ردیابی JavaScript ای ارائه میکند.
مفهوم computed@: این نوع خواص، مقدار خود را زمانیکه observableهای وابستهی به آنها تغییر کنند، به روز رسانی میکنند.
مفهوم action@: جهت به روز رسانی state و سپس نمایش تغییرات یا نمایش نمونهی دیگری در DOM میباشند.
import { createContext, useContext } from "react"; import { TodoList } from "../stores/todo-list"; export const StoreContext = createContext<TodoList>({} as TodoList); export const StoreProvider = StoreContext.Provider; export const useStore = (): TodoList => useContext(StoreContext);
import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; import App from './App'; import * as serviceWorker from './serviceWorker'; import "bootstrap/dist/css/bootstrap.css"; import "font-awesome/css/font-awesome.css"; import { TodoList } from './stores/todo-list'; import { StoreProvider } from './providers/store-provider'; const todoList = new TodoList([ 'Read Book', 'Do exercise', 'Watch Walking dead series' ]); ReactDOM.render( <StoreProvider value={todoList}> <App /> </StoreProvider> , document.getElementById('root')); // If you want your app to work offline and load faster, you can change // unregister() to register() below. Note this comes with some pitfalls. // Learn more about service workers: https://bit.ly/CRA-PWA serviceWorker.unregister();
import React, { useState } from 'react'; import { useStore } from '../providers/store-provider'; export const TodoNew = () => { const [newTodo, setTodo] = useState(''); const todoList = useStore(); const addTodo = () => { todoList.addTodo(newTodo); setTodo(''); }; return ( <div className="input-group mb-3"> <input type="text" className="form-control" placeholder="Add To do" value={newTodo} onChange={(e) => setTodo(e.target.value)} /> <div className="input-group-append"> <button className="btn btn-success" type="submit" onClick={addTodo}>Add Todo</button> </div> </div> ) };
import React from 'react'; import { TodoItem } from "./TodoItem"; import { useObserver } from "mobx-react-lite"; import { useStore } from '../providers/store-provider'; export const TodoList = () => { const todoList = useStore(); return useObserver(() => ( <div> <h1>Open Todos</h1> <table className="table"> <thead className="thead-dark"> <tr> <th>Name</th> <th className="text-left">Do It?</th> <th>Actions</th> </tr> </thead> <tbody> { todoList.openTodos.map(todo => <tr key={`${todo.id}-${todo.text}`}> <TodoItem todo={todo} /> </tr>) } </tbody> </table> <h1>Finished Todos</h1> <table className="table"> <thead className="thead-light"> <tr> <th>Name</th> <th className="text-left">Do It?</th> <th>Actions</th> </tr> </thead> <tbody> { todoList.finishedTodos.map(todo => <tr key={`${todo.id}-${todo.text}`}> <TodoItem todo={todo} /> </tr>) } </tbody> </table> </div> )); };
import React, { useState } from 'react'; import TodoItemClass from "../stores/todo-item"; import { useStore } from '../providers/store-provider'; interface Props { todo: TodoItemClass; } export const TodoItem = ({ todo }: Props) => { const todoList = useStore(); const [newText, setText] = useState(''); const [isEditing, setEdit] = useState(false); const saveText = () => { todo.updateText(newText); setEdit(false); setText(''); }; return ( <React.Fragment> { isEditing ? <React.Fragment> <td> <input className="form-control" placeholder={todo.text} type="text" onChange={(e) => setText(e.target.value)} /> </td> <td></td> <td> <button className="btn btn-xs btn-success " onClick={saveText}>Save</button> </td> </React.Fragment> : <React.Fragment> <td> {todo.text} </td> <td className="text-left"> <input className="form-check-input" type="checkbox" onChange={todo.toggleIsDone} defaultChecked={todo.isDone}></input> </td> <td> <button className="btn btn-xs btn-warning " onClick={() => setEdit(true)}> <i className="fa fa-edit"></i> </button> <button className="btn btn-xs btn-danger ml-2" onClick={() => todoList.removeTodo(todo)}> <i className="fa fa-remove"></i> </button> </td> </React.Fragment> } </React.Fragment> ) };
آیا AngularJS میتواند سلطهی خود را حفظ کند؟
گریس مانکی یکی از افزونههای فایرفاکس است که توسط آن میتوان اسکریپتهایی را بر روی صفحات وب در حال مشاهده، جهت تغییر آنها اجرا کرد. این نوع تغییرات بیشتر در جهت بالا بردن کارآیی یا خوانایی سایتها صورت میگیرد. مثلا بررسی وجود لینکهای ارائه شده در یک صفحه وب (قبل از اینکه به آنها رجوع کنیم، مشخص شود که آیا وجود دارند یا خیر) و هزاران مثال شبیه به این که در سایت اسکریپتهای آن قابل دریافت هستند.
گریس مانکی به خودی خود کار خاصی را انجام نمیدهد و فقط میزبان اجرایی اسکریپتهایی است که برای آن تهیه شده اند. به این اسکریپتها user scripts گفته میشود و جهت تهیه آنها از زبان جاوا اسکریپت استفاده میگردد.
مطابق اصول نامگذاری آن، فایل این اسکریپتها حتما باید به .user.js ختم شود تا توسط افزونه گریس مانکی قابل شناسایی باشد.
اسکریپت سلام دنیای گریس مانکی!
// ==UserScript==
// @name Hello World
// @namespace http://diveintogreasemonkey.org/download/
// @description example script to alert "Hello world!" on every page
// @include *
// @exclude http://diveintogreasemonkey.org/*
// @exclude http://www.diveintogreasemonkey.org/*
// ==/UserScript==
alert('Hello world!');
در پایان مثلا نام آنرا hello.user.js خواهیم گذاشت (همانطور که ذکر شد قسمت user.js آن باید رعایت شود). برای نصب آن فقط کافی است این فایل را به درون پنجره فایر فاکس کشیده و رها کنیم (drag & drop).
استفاده از jQuery در اسکریپتهای گریس مانکی
عمده کاربردهای اسکریپتهای گریس مانکی در جهت اعمال تغییرات بر روی document object model صفحه (DOM) هستند. کتابخانه jQuery اساسا برای این منظور تهیه و بهینه سازی شده است.
مثال:
فرض کنید قصد داریم به نتایج خروجی جستجوی گوگل، fav icon سایتهای یافت شده را اضافه کنیم (در کنار هر لینک، آیکون سایت مربوطه را نمایش دهیم). گوگل اینکار را انجام نداده است. اما ما علاقمند هستیم که این قابلیت را اضافه کنیم!
// ==UserScript==
// @name Google FavIcon
// @namespace http://userscripts.org
// @description Shows favicon for Google searches results
// @include http://*.google.*/search?*
// ==/UserScript==
loadJquery();
$(document).ready(function(){
$("h3.r > a.l").each(function(){
var $a = $(this);
var href = $a.attr("href");
var domain = href.replace(/<\S[^><]*>/g, "").split('/')[2];
var image = '<img src="http://' + domain + '/favicon.ico" style="border:0;padding-right:4px;" />';
//GM_log(">image:> " + image);
$(this).prepend(image);
});
});
دریافت سورس این مثال
در اینجا ابتدا لینکهای حاصل از جستجو پیدا میشوند. سپس نام دومین مربوطه استخراج میگردد (با استفاده از regular expressions) و در ادامه از این نام دومین یک آدرس استاندارد http://domain/favico.ico ساخته شده و از آن یک تگ img درست میشود. در آخر این تگ به قبل از لینکهای گوگل اضافه میشود.
شاید سؤال بپرسید از کجا مشخص شد که باید به دنبال h3.r > a.l گشت؟ به تصویر زیر دقت نمائید (نمایی است از یکی از تواناییهای افزونه fireBug فایرفاکس).
هنگامیکه صفحه بارگذاری شد، بر روی آیکون سوسک موجود در status bar فایرفاکس کلیک کنید تا FireBug ظاهر شود (البته من اینجا سوسک دیدم شاید موجود دیگری باشد :) )
سپس بر روی دکمه inspect در نوار ابزار آن کلیک کنید. در همین حال اشارهگر ماوس را به یکی از لینکهای نتیجه جستجوی گوگل نزدیک کنید. بلافاصله تگها و کلاسهای مورد استفاده آن به شکل زیبایی ظاهر خواهند شد. به این صورت صرفه جویی قابل ملاحظهای در وقت صورت خواهد گرفت.
نتیجه اجرای اسکریپت فوق (پس از نصب) به صورت زیر است:
نکته: نحوه دیباگ کردن اسکریپتهای گریس مانکی
اگر نیاز به مشاهده مقدار متغیرها در لحظه اجرای اسکریپت داشتید، یکی از راهحلهای موجود استفاده از تابع GM_log مربوط به API گریس مانکی است که خروجی آنرا در قسمت messages مربوط به error console فایرفاکس میتوان دید.