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
بدین منظور فریم ورک ASP.NET Web API کتابخانه ای برای تولید خودکار صفحات راهنما در زمان اجرا (run-time) فراهم کرده است.
ایجاد صفحات راهنمای API
برای شروع ابتدا ابزار ASP.NET and Web Tools 2012.2 Update را نصب کنید. اگر از ویژوال استودیو 2013 استفاده میکنید این ابزار بصورت خودکار نصب شده است. این ابزار صفحات راهنما را به قالب پروژههای ASP.NET Web API اضافه میکند.
یک پروژه جدید از نوع ASP.NET MVC Application بسازید و قالب Web API را برای آن انتخاب کنید. این قالب پروژه کنترلری بنام ValuesController را بصورت خودکار برای شما ایجاد میکند. همچنین صفحات راهنمای API هم برای شما ساخته میشوند. تمام کد مربوط به صفحات راهنما در قسمت Areas قرار دارند.
اگر اپلیکیشن را اجرا کنید خواهید دید که صفحه اصلی لینکی به صفحه راهنمای API دارد. از صفحه اصلی، مسیر تقریبی Help/ خواهد بود.
این لینک شما را به یک صفحه خلاصه (summary) هدایت میکند.
نمای این صفحه در مسیر Areas/HelpPage/Views/Help/Index.cshtml قرار دارد. میتوانید این نما را ویرایش کنید و مثلا قالب، عنوان، استایلها و دیگر موارد را تغییر دهید.
بخش اصلی این صفحه متشکل از جدولی است که APIها را بر اساس کنترلر طبقه بندی میکند. مقادیر این جدول بصورت خودکار و توسط اینترفیس IApiExplorer تولید میشوند. در ادامه مقاله بیشتر درباره این اینترفیس صحبت خواهیم کرد. اگر کنترلر جدیدی به API خود اضافه کنید، این جدول بصورت خودکار در زمان اجرا بروز رسانی خواهد شد.
ستون "API" متد HTTP و آدرس نسبی را لیست میکند. ستون "Documentation" مستندات هر API را نمایش میدهد. مقادیر این ستون در ابتدا تنها placeholder-text است. در ادامه مقاله خواهید دید چگونه میتوان از توضیحات XML برای تولید مستندات استفاده کرد.
هر API لینکی به یک صفحه جزئیات دارد، که در آن اطلاعات بیشتری درباره آن قابل مشاهده است. معمولا مثالی از بدنههای درخواست و پاسخ هم ارائه میشود.
افزودن صفحات راهنما به پروژه ای قدیمی
می توانید با استفاده از NuGet Package Manager صفحات راهنمای خود را به پروژههای قدیمی هم اضافه کنید. این گزینه مخصوصا هنگامی مفید است که با پروژه ای کار میکنید که قالب آن Web API نیست.
از منوی Tools گزینههای Library Package Manager, Package Manager Console را انتخاب کنید. در پنجره Package Manager Console فرمان زیر را وارد کنید.
Install-Package Microsoft.AspNet.WebApi.HelpPage
@Html.ActionLink("API", "Index", "Help", new { area = "" }, null)
همانطور که مشاهده میکنید مسیر نسبی صفحات راهنما "Help/" میباشد. همچنین اطمینان حاصل کنید که ناحیهها (Areas) بدرستی رجیستر میشوند. فایل Global.asax را باز کنید و کد زیر را در صورتی که وجود ندارد اضافه کنید.
protected void Application_Start() { // Add this code, if not present. AreaRegistration.RegisterAllAreas(); // ... }
افزودن مستندات API
بصورت پیش فرض صفحات راهنما از placeholder-text برای مستندات استفاده میکنند. میتوانید برای ساختن مستندات از توضیحات XML استفاده کنید. برای فعال سازی این قابلیت فایل Areas/HelpPage/App_Start/HelpPageConfig.cs را باز کنید و خط زیر را از حالت کامنت درآورید:
config.SetDocumentationProvider(new XmlDocumentationProvider( HttpContext.Current.Server.MapPath("~/App_Data/XmlDocument.xml")));
زیر قسمت Output گزینه XML documentation file را تیک بزنید و در فیلد روبروی آن مقدار "App_Data/XmlDocument.xml" را وارد کنید.
حال کنترلر ValuesController را از مسیر Controllers/ValuesController.cs/ باز کنید و یک سری توضیحات XML به متدهای آن اضافه کنید. بعنوان مثال:
/// <summary> /// Gets some very important data from the server. /// </summary> public IEnumerable<string> Get() { return new string[] { "value1", "value2" }; } /// <summary> /// Looks up some data by ID. /// </summary> /// <param name="id">The ID of the data.</param> public string Get(int id) { return "value"; }
اپلیکیشن را مجددا اجرا کنید و به صفحات راهنما بروید. حالا مستندات API شما باید تولید شده و نمایش داده شوند.
صفحات راهنما مستندات شما را در زمان اجرا از توضیحات XML استخراج میکنند. دقت کنید که هنگام توزیع اپلیکیشن، فایل XML را هم منتشر کنید.
توضیحات تکمیلی
صفحات راهنما توسط کلاس ApiExplorer تولید میشوند، که جزئی از فریم ورک ASP.NET Web API است. به ازای هر API این کلاس یک ApiDescription دارد که توضیحات لازم را در بر میگیرد. در اینجا منظور از "API" ترکیبی از متدهای HTTP و مسیرهای نسبی است. بعنوان مثال لیست زیر تعدادی API را نمایش میدهد:
- GET /api/products
- {GET /api/products/{id
- POST /api/products
اگر اکشنهای کنترلر از متدهای متعددی پشتیبانی کنند، ApiExplorer هر متد را بعنوان یک API مجزا در نظر خواهد گرفت. برای مخفی کردن یک API از ApiExplorer کافی است خاصیت ApiExplorerSettings را به اکشن مورد نظر اضافه کنید و مقدار خاصیت IgnoreApi آن را به true تنظیم نمایید.
[ApiExplorerSettings(IgnoreApi=true)] public HttpResponseMessage Get(int id) { }
همچنین میتوانید این خاصیت را به کنترلرها اضافه کنید تا تمام کنترلر از ApiExplorer مخفی شود.
کلاس ApiExplorer متن مستندات را توسط اینترفیس IDocumentationProvider دریافت میکند. کد مربوطه در مسیر Areas/HelpPage/XmlDocumentation.cs/ قرار دارد. همانطور که گفته شد مقادیر مورد نظر از توضیحات XML استخراج میشوند. نکته جالب آنکه میتوانید با پیاده سازی این اینترفیس مستندات خود را از منبع دیگری استخراج کنید. برای اینکار باید متد الحاقی SetDocumentationProvider را هم فراخوانی کنید، که در HelpPageConfigurationExtensions تعریف شده است.
کلاس ApiExplorer بصورت خودکار اینترفیس IDocumentationProvider را فراخوانی میکند تا مستندات APIها را دریافت کند. سپس مقادیر دریافت شده را در خاصیت Documentation ذخیره میکند. این خاصیت روی آبجکتهای ApiDescription و ApiParameterDescription تعریف شده است.
مطالعه بیشتر
افزونهی زیر کار مشخص کردن کلماتی که قابل تبدیل از فارسی به پارسی هستند را انجام میدهد (به صورت خودکار و در زمان تایپ) و همچنین امکان تبدیل خودکار آنها را نیز فراهم میکند.
برای نصب آن باید به ترتیب زیر عمل کنید (مهم)
- نصب دات نت فریم ورک 3 و نیم (نزدیک به 231 مگابایت)
اگر VS.Net 2008 SP1 را دارید نیازی به این بسته نخواهید داشت.
- نصب بسته به روز رسانی سیستم (حدودا 14 مگ)
- نصب افزونه (حدودا 550 کیلوبایت)
لطفا این سه مرحله را به ترتیب انجام دهید در غیر اینصورت نتیجه نخواهید گرفت.
همچنین بدیهی است که برنامه باید با دسترسی admin نصب شود.
و همانطور که در عنوان این موضوع نیز ذکر گردید، این افزونه تنها برای MS-Word 2007 طراحی شده است.
سورس کامل و جزئیات نحوهی برنامه نویسی آنرا در طی روزهای آینده در این بلاگ مشاهده خواهید کرد.
پس از نصب به گزینهی word options مراجعه کنید: (جهت اطمینان از نصب آن)
این افزونه را باید در لیست مربوطه مشاهده نمائید:
همان طور که میدونید مکانیزم عملکرد اکثر پلاگینهای lightbox به صورت زیر است:
<a href="orginal size of image directory"><img src="thumb(smaller size) image directory" /></a>
خب معلوم میشه که آدرس عکسی که به صورت کوچیک)اصطلاحا بند انگشتی) به نمایش در میاد باید داخل تگ img ، وآدرس عکس با سایز اصلی ، داخل تگ a قرار بگیره.
پس تقریبا معلوم شد که چه باید بکنیم.
ابتدا باید عکسی که آپلود شده را به صورت خودکار به اندازه کوچکتر تغییر ابعاد داده و سپس آدرس عکس تغییر ابعاد داده شده ، را به عنوان آدرس عکس آپلود شده به ckeditor باز گردانیم که در شکل زیر آن را نشان داده ام:
ولی ما فقط آدرس عکس تغییر ابعاد داده شده متعلق به تگ img را ، به ckeditor داده ایم و برای اینکه با lightbox به خوبی کار کند، احتیاج داریم که آدرس عکس با سایز اصلی را داخل تگ a قرار دهیم، که در ckeditor میتوانیم از قسمت پیوندها این کار را انجام دهیم، و همهی این عملیات باید به صورت خودکار انجام شود.خب تا اینجا خلاصه ای از آنچه باید انجام شود را گفتم.
در کل منطقی که باید پیاده شود بدین گونه است:
1- پیاده سازی ckeditor
2- فعال کردن قسمت آپلود عکس ckeditor
3- آپلود کردن عکس و تغییر اندازه آن به کمک کلاس WebImage
4- برگرداندن آدرس عکسهای آپلود شده به ckeditor و روشهای پیاده سازی آن
نکات:
الف) من در اینجا از ASP.NET MVC استفاده میکنم .شما نیز میتوانید منطق پیاده سازی شده را به راحتی و با کمی تغییرات به پروژه خود اعمال کنید.
ب) کدهایی که من نوشتم 100% بهینه نیستند و کاملا هم بدون اشکال نیستند ولی نیازهای شما را برآورده میکند.
ج)آدرس دادن فایلهای من کاملا صحیح نیستند و بهتر است شما از T4MVC استفاده کنید.
مرحله اول: دانلود و پیاده سازی ckeditor در صفحه
ابتدا به سایت ckeditor.com مراجعه و از قسمت دانلود، آن را دانلود کرده و سپس از حالت فشرده درآورده، در فولدری مثلا به نام Scripts در پروژهی خود بریزید.
همان طور که میدانید ckeditor با یک textarea یکپارچه میشود ، که من سادهترین آن را برای شما شرح میدهم.
اول از همه شما باید یک فایل جاوا اسکریپت متعلق به ckeditor را در صفحه ای که از آن میخواهید استفاده کنید به صفحه ضمیمه کنید. نام این فایل ckeditor.js هست که در فایل دانلودی در داخل پوشهی ckeditor دیده میشود.
<script src="/Scripts/ckeditor/ckeditor.js" type="text/javascript"></script>
خب فرض کنید که یک textarea به شکل زیر داریم. برای اینکه این textarea با ckeditor یکپارچه شود کافیست که class آن را بر روی ckeditor قرار دهید.
<textarea id="content" name="content" class="ckeditor" ></textarea>
تا به اینجای کار توانستیم ckeditor را اجرا کنیم.
در مرحله بعد سراغ فعال سازی آپلود عکس در آن میرویم.
مرحله دوم: فعال سازی آپلود عکس در ckeditor
از شواهد این طور به نظر میاد که به صورت پیشفرض این امکان غیر فعال است و باید به صورت دستی آن را فعال کنیم.
برای این کار کافیست که از مستندات ckeditor کمک بگیریم.
خب یکی از راحتترین این روشها این است که قبل از بسته شدن تگ بادی فایل جاوا اسکریپت زیر را بنویسیم:
<script type="text/javascript"> CKEDITOR.replace( 'content' , { filebrowserImageUploadUrl: '/Admin/UploadImage' } ); </script>
توضیحات:
آرگومان اول CKEDITOR.replace که در اینجا content است ، در واقع id همان textarea ای هست که میخواهیم ckeditor روی آن اعمال شود.
آرگومان دوم نام کنترلر و اکشن را برای آپلود فایل مشخص میکنه.(از منطق ASP.Net MVC استفاده کردم)
خب تا اینجا اگر تست بگیرید میبینید که قسمت زیر برایتان فعال شده.
مرحله سوم: آپلود کردن عکس و تغییر اندازه آن به کمک کلاس WebImage
برای این که ببینم که این فرم آپلود ckeditor ، چه پارامترهایی را به اکشن متد من ارسال میکنه ، از فایرباگ کمک گرفتم:
همان طور که مییبینید به خوبی آدرس post شدن اطلاعات به اکشن متدی که من برایش مشخص کردم را فهمیده.
اما سه پارامتر دیگر نیز به اکشن متد ما ارسال میکنه: CKEditor و CKeditorFuncNum و langCod
برای اینکه با این پارامترهای ارسالی بیشتر آشنا شوید ، توصیه میکنم این صفحه را ببینید.
آنچه که از این پارامترها برای ما مهم هست ، من در اکشن متد تعریفی خود لحاظ کرده ام.
خب همون طور که یپش از این گفته بودم ما نیاز داریم که عکس آپلود شده را به ابعاد کوچکتر تغییر اندازه داده و اصطلاحا از آن به عنوان تصویر بند انگشتی استفاده کنیم. آدرس این عکس کوچک شده ، همانیست که در آدرس تگ img قرار میگیره و در ابتدا به کاربر نمایش داده میشه.
برای انجام این عمل من کلاسی را برای کار کردن با عکس برایتان معرفی میکنم، به نام WebImage که در داخل فضای نامی System.Web.Helpers قرار گرفته است.
از طریق این کلاس میتوان کلیه عملیات دریافت فایل آپلود شده، ویرایش ، تغییر اندازه ، چرخاندن ، بریدن و حتی watermark کردن و در نهایت ذخیره عکس را به آسانی انجام داد.
من کدهای متد UploadImage را برایتان قرار میدهم که زیاد هم بهینه نیست و سپس برایتان توضیح میدهم.
public ActionResult UploadImage(string CKEditorFuncNum, string CKEditor, string langCode) { string message; // message to display when file upload successfully(optional) string thumbPath = ""; // the directory for thumb file that should resize var db = new MyDbContext(); // make new instance from my context // here logic to upload image // and get file path of the image var file = WebImage.GetImageFromRequest(); // get the uploaded file from request var ext = Path.GetExtension(file.FileName); // get the path of file //get the file name without extension var fileName = Path.GetFileNameWithoutExtension(file.FileName); //add time to file name to avoid same name file overwrite, and then add extension to it fileName += DateTime.Now.ToFileTime() + ext; //choose the path for the original size of image var path = Path.Combine(Server.MapPath("~/Content/UploadedImages/Default"), fileName); file.Save(path); //save the original size of the image db.Images.Add(new Image { RealName = file.FileName, FileName = fileName, UploadDate = DateTime.Now }); // save image info to db db.SaveChanges(); // submit changes to db string defaultPath = "/Content/UploadedImages/Default/" + fileName; //path for original size of //images if (file.Width > 400) // if width of image bigger than 400 px do resize { file.Resize(400, 400, true); //resize the image , third argument is aspect ratio string thumbName = "Thumb-" + fileName; // resized image name //path for resized image file string path2 = Server.MapPath("~/Content/UploadedImages/Thumbs/" + thumbName); thumbPath = "/Content/UploadedImages/Thumbs/" + thumbName; //save resized image file file.Save(path2); } else { thumbPath = defaultPath; // if the size not bigger than 400px the thumb, path = default path } // passing message success/failure message = "Image was saved correctly"; // since it is an ajax request it requires this string //java script that return files path to ckeditor string output = @"<script>window.parent.CKEDITOR.tools.callFunction(" + CKEditorFuncNum + ", \"" + thumbPath + "\", \"" + message + "\");window.parent.document.getElementById('cke_145_textInput').value='" + defaultPath + "';window.parent.document.getElementById('cke_125_textInput').value=0;</script>"; return Content(output); }
توضیحات:
ابتدا یه رشته به نام message در نظر گرفتم ، برای هنگامی که آپلود شد، ckeditor به کاربر نشان بده.
سپس منطق من به این صورت بوده که مسیری برای ذخیره سازی فایلهای تغییر اندازه داده شده ، و نیز مسیری برای فایلهای با اندازه اصلی در نظر گرفتم.
همچنین من در اینجا من از بانک اطلاعاتی برای ذخیره سازی اطلاعاتی از عکس استفاده کردم، که در اینجا بحث اصلی ما نیست.
سپس به کمک WebImage.GetImageFromRequest فایل آپلود شده را دریافت کردم.این متد به اندازه کافی باهوش هست که بفهمد ، چه فایلی آپلود شده.
سپس پسوند فایل را از نام فایل جدا کردم، و تاریخ کنونی را به شکل رشته در آورده و به انتهای نام عکس اضافه کرده تا از تکراری نبودن نام عکسها مطمئن باشم.
سپس پسوند فایل را نیز دوباره به نام فایل اضافه کردم و به کمک متد Save کلاس WebImage عکس را ذخیره کردم.
سپس چک کردم که اگر عرض عکس بیشتر از 400 پیکسل هست ، آن را تغییر اندازه بده و ذخیره کنه، و در غیر این صورت آدرس عکسی که قرار بود تغییر اندازه داده بشه با آدرس عکس اصلی یکی میشه.
قسمت مهم:
نکته مهم اینه که ما آدرسهای عکسهای آپلود شده را چگونه به ckeditor برگردانیم.
همان طوری که در قسمت آخر هم مشاهده میکنید ، ما سه دستور جاوا اسکریپت به مرورگر برگردوندیمم:
اولیش:
window.parent.CKEDITOR.tools.callFunction(" + CKEditorFuncNum + ", \"" + thumbPath + "\", \"" + message + "\");
در حقیقت ما در اینجا ما از apiهای ckeditor و همچنین پارامترهای ارسالی از طرف ckeditor استفاده کردیم ، تا قسمت آدرس عکس را با آدرس عکس تغییر اندازه داده شده و کوچک شده پر میکنیم.
سوال؟
حالا چگونه قسمت پیوند را پر کنیم؟ این را دیگر من پیدا نکردم ، تا دست به دامن دوست و یا شایدم دشمن قدیمیمون جاوا اسکریپت شدم.
اول رفتم به کمک فایرباگ دیدم که id فیلد پیوندها چیه؟
همان طور که معلومه id این فیلد cke_145_textInput هست و به کمک یه خط js میتوان این فیلد را با آدرس عکس آپلود شده با سایز اصلی پر کرد.
اولش من این را نوشتم:
document.getElementsById("cke_145_textInput").value = defaultPath;
اما بازم js شروع کرد به بدقلق شدن. هرچی توی کنسول دیباگش کردم خطای null بودن را میداد. بعد از یه ساعت سرو کله زدن و تقریبا ناامید شدن ، چشمم به قسمت اول کد api خود ادیتور که اولش را با window.parent شروع کرده افتاد ، و من هم کد خودم را به شکل زیر تغییر دادم:
window.parent.document.getElementsById("cke_145_textInput").value = defaultPath;
موفقیت در این قسمت از کد باعث شد که من دست به کدتر شوم و مشکل border عکس ها، که به صورت دیفالت در IE یا همون دشمن همیشگی وجود داره ، را حل کنم ومقدار border را به صورت پیش فرض صفر کنم.
window.parent.document.getElementsById('cke_125_textInput').value=0;
خب همهی این دردسرها را ما تحمل کردیم تا به سادهترین شکل ممکن هر عکسی را که آپلود شد ، برای مکانیزم lightbox آماده کنیم و به راحتی یه گالری عکس خوب داشته باشیم.
خب حتما میپرسید که از چه پلاگینی برای ایجاد lightbox استفاده کنیم:
من به شخصه پلاگین colorbox را پیشنهاد میدم.
با انجام یک سرچ ساده سایتش را پیدا کنید و با مستندات و ویژگیهای آن آشنا شوید.
یک پیشنهاد:
برای انجام سلکت زدن برای عناصری که باید پلاگین colorbox روی آنها اعمال شوند من سلکت زدن به شیوهی زیر را پیشنهاد میکنم:
$(".container [href$='.jpg']").colorbox({ maxWidth: 800, opacity: 0.5, rel: 'gal' }); $(".container [href$='.png']").colorbox({ maxWidth: 800, opacity: 0.5, rel: 'gal' }); $(".container [href$='.gif']").colorbox({ maxWidth: 800, opacity: 0.5, rel: 'gal' });
فرض کنید div ی که متن و عکسهای ما را شامل میشه ، کلاسش container باشه . با کمک [href$='.jpg'] میتوان گفت هر لینکی که، پسوند فایلی که به آن اشاره میکند، .jpg هست، ویژگی colorbox را به خود بگیرید.
یک پیشنهاد برای تشکیل گالری عکس:
همان طور که من در بالا اشاره کردم ، rel را بر روی gal قرار دادم، تا هر تگی که ویژگی rel را داشته باشد، تشکیل یک گروه برای گالری عکس را بدهد.
برای اینکه بتوانیم این ویژگی را به عناصر مورد نظر خود اعمال کنیم ، بازم دست به دامان jQuery میشویم:
$(".container [href$='.jpg']").attr("rel", "gal");
خب مثل اینکه دیگر کار تمام شده و امیدوارم برای شما مفید بوده باشه.
موفق باشید...
قالب ASP.NET Core و Angular 2 برای Visual Studio
{ "projects": [ "src", "test" ], "sdk": { "version": "1.0.0-preview2-003121" } }
@{ ViewData["Title"] = "Home Page"; } <html> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>@ViewData["Title"] - WebApplicationBasic</title> <link rel="stylesheet" href="ClientApp/dist/vendor.css" /> </head> <body> <app asp-prerender-module="ClientApp/dist/main-server">Loading...</app> @section scripts { <script src="~/ClientApp/dist/vendor.js"></script> <script src="~/ClientApp/dist/main-client.js"></script> } </body> </html>
الف) مسیر تصاویر موجود در فایل سیستم، در بانک اطلاعاتی ذخیره شدهاند و قرار است گزارش نهایی در ستونی مشخص شامل این تصاویر باشد.
ب) محتوای بایناری تصاویر در خود بانک اطلاعاتی ذخیره شدهاند و نیاز است آنها را در گزارشات نمایش دهیم.
هر دو حالت فوق در PdfReport پشتیبانی میشوند و در ادامه نحوه انجام این موارد را بررسی خواهیم کرد.
الف) بارگذاری تصاویر از فایل سیستم
ابتدا مدل زیر را در نظر بگیرید:
namespace PdfReportSamples.Models { public class ImageRecord { public int Id { set; get; } public string ImagePath { set; get; } public string Name { set; get; } } }
using System; using System.Collections.Generic; using iTextSharp.text.pdf; using PdfReportSamples.Models; using PdfRpt.Core.Contracts; using PdfRpt.FluentInterface; namespace PdfReportSamples.ImageFilePath { public class ImageFilePathPdfReport { public IPdfReportData CreatePdfReport() { return new PdfReport().DocumentPreferences(doc => { doc.RunDirection(PdfRunDirection.RightToLeft); doc.Orientation(PageOrientation.Portrait); doc.PageSize(PdfPageSize.A4); doc.DocumentMetadata(new DocumentMetadata { Author = "Vahid", Application = "PdfRpt", Keywords = "Test", Subject = "Test Rpt", Title = "Test" }); }) .DefaultFonts(fonts => { fonts.Path(AppPath.ApplicationPath + "\\fonts\\irsans.ttf", Environment.GetEnvironmentVariable("SystemRoot") + "\\fonts\\verdana.ttf"); }) .PagesFooter(footer => { footer.DefaultFooter(DateTime.Now.ToString("MM/dd/yyyy")); }) .PagesHeader(header => { header.DefaultHeader(defaultHeader => { defaultHeader.ImagePath(AppPath.ApplicationPath + "\\Images\\01.png"); defaultHeader.Message("گزارش جدید ما"); }); }) .MainTableTemplate(template => { template.BasicTemplate(BasicTemplate.SnowyPineTemplate); }) .MainTablePreferences(table => { table.ColumnsWidthsType(TableColumnWidthType.Relative); }) .MainTableDataSource(dataSource => { var listOfRows = new List<ImageRecord> { new ImageRecord { Id=1, ImagePath = AppPath.ApplicationPath + "\\Images\\01.png", Name = "Rnd" }, new ImageRecord { Id=2, ImagePath = AppPath.ApplicationPath + "\\Images\\02.png", Name = "Bug" }, new ImageRecord { Id=3, ImagePath = AppPath.ApplicationPath + "\\Images\\03.png", Name = "Stuff" }, new ImageRecord { Id=4, ImagePath = AppPath.ApplicationPath + "\\Images\\04.png", Name = "Sun" } }; dataSource.StronglyTypedList(listOfRows); }) .MainTableColumns(columns => { columns.AddColumn(column => { column.PropertyName("rowNo"); column.IsRowNumber(true); column.CellsHorizontalAlignment(HorizontalAlignment.Center); column.IsVisible(true); column.Order(0); column.Width(1); column.HeaderCell("ردیف"); }); columns.AddColumn(column => { column.PropertyName<ImageRecord>(x => x.Id); column.CellsHorizontalAlignment(HorizontalAlignment.Center); column.IsVisible(true); column.Order(1); column.Width(3); column.HeaderCell("شماره"); column.ColumnItemsTemplate(t => t.Barcode(new Barcode39())); }); columns.AddColumn(column => { column.PropertyName<ImageRecord>(x => x.ImagePath); column.CellsHorizontalAlignment(HorizontalAlignment.Center); column.IsVisible(true); column.Order(2); column.Width(3); column.HeaderCell("تصویر"); column.ColumnItemsTemplate(t => t.ImageFilePath(defaultImageFilePath: string.Empty, fitImages: false)); }); columns.AddColumn(column => { column.PropertyName<ImageRecord>(x => x.Name); column.CellsHorizontalAlignment(HorizontalAlignment.Center); column.IsVisible(true); column.Order(3); column.Width(2); column.HeaderCell("نام"); }); }) .MainTableEvents(events => { events.DataSourceIsEmpty(message: "There is no data available to display."); }) .Export(export => { export.ToExcel(); export.ToXml(); }) .Generate(data => data.AsPdfFile(AppPath.ApplicationPath + "\\Pdf\\RptImageFilePathSample.pdf")); } } }
توضیحات:
- در متد MainTableDataSource، یک سری رکورد دلخواه را بر اساس تعدادی تصویر مشخص ایجاد کردهایم. این تصاویر در پوشه bin\images مثالهای PdfReport قرار دارند. سپس آنها را توسط متد dataSource.StronglyTypedList، در اختیار PdfReport قرار دادهایم.
- در مرحله بعد، توسط متد MainTableColumns، ستونهای دلخواه گزارش را ایجاد کردهایم.
دو نکته در اینجا مهم هستند:
الف) برای نمایش یک تصویر از فایل سیستم، فقط کافی است از ColumnItemsTemplate متناظر با آن استفاده کرد (حالت پیش فرض، نمایش متنی اطلاعات است). برای نمونه قالب پیش فرض ImageFilePath به نحو زیر قابل استفاده است:
column.ColumnItemsTemplate(t => t.ImageFilePath(defaultImageFilePath: string.Empty, fitImages: false));
ب) در کتابخانه iTextSharp که پایه PdfReport است، امکان تهیه بارکد هم وجود دارد. برای مثال این بارکدها توسط قالب زیر، قابل استفاده خواهند شد:
column.ColumnItemsTemplate(t => t.Barcode(new Barcode39()));
ب) بارگذاری محتوای تصاویر از بانک اطلاعاتی
ابتدا کدهای کامل این مثال را در نظر بگیرید:
using System; using PdfRpt.Core.Contracts; using PdfRpt.FluentInterface; namespace PdfReportSamples.DbImage { public class DbImagePdfReport { public IPdfReportData CreatePdfReport() { return new PdfReport().DocumentPreferences(doc => { doc.RunDirection(PdfRunDirection.RightToLeft); doc.Orientation(PageOrientation.Portrait); doc.PageSize(PdfPageSize.A4); doc.DocumentMetadata(new DocumentMetadata { Author = "Vahid", Application = "PdfRpt", Keywords = "Test", Subject = "Test Rpt", Title = "Test" }); }) .DefaultFonts(fonts => { fonts.Path(AppPath.ApplicationPath + "\\fonts\\irsans.ttf", Environment.GetEnvironmentVariable("SystemRoot") + "\\fonts\\verdana.ttf"); }) .PagesFooter(footer => { footer.DefaultFooter(DateTime.Now.ToString("MM/dd/yyyy")); }) .PagesHeader(header => { header.DefaultHeader(defaultHeader => { defaultHeader.ImagePath(AppPath.ApplicationPath + "\\Images\\01.png"); defaultHeader.Message("گزارش جدید ما"); }); }) .MainTableTemplate(template => { template.BasicTemplate(BasicTemplate.AutumnTemplate); }) .MainTablePreferences(table => { table.ColumnsWidthsType(TableColumnWidthType.FitToContent); }) .MainTableDataSource(dataSource => { dataSource.GenericDataReader( providerName: "System.Data.SQLite", connectionString: "Data Source=" + AppPath.ApplicationPath + "\\data\\blogs.sqlite", sql: @"SELECT [url], [name], [NumberOfPosts], [AddDate], [thumbnail] FROM [tblBlogs] WHERE [NumberOfPosts]>=@p1", parametersValues: new object[] { 10 } ); }) .MainTableEvents(events => { events.DataSourceIsEmpty(message: "There is no data available to display."); }) .MainTableSummarySettings(summary => { summary.OverallSummarySettings("جمع کل"); summary.PreviousPageSummarySettings("نقل از صفحه قبل"); }) .MainTableColumns(columns => { columns.AddColumn(column => { column.PropertyName("rowNo"); column.IsRowNumber(true); column.CellsHorizontalAlignment(HorizontalAlignment.Center); column.IsVisible(true); column.Order(0); column.HeaderCell("ردیف"); }); columns.AddColumn(column => { column.PropertyName("url"); column.CellsHorizontalAlignment(HorizontalAlignment.Center); column.IsVisible(true); column.Order(1); column.HeaderCell("آدرس"); }); columns.AddColumn(column => { column.PropertyName("name"); column.CellsHorizontalAlignment(HorizontalAlignment.Center); column.IsVisible(true); column.Order(2); column.HeaderCell("نام"); }); columns.AddColumn(column => { column.PropertyName("NumberOfPosts"); column.CellsHorizontalAlignment(HorizontalAlignment.Center); column.IsVisible(true); column.Order(3); column.HeaderCell("تعداد پست"); column.ColumnItemsTemplate(template => { template.TextBlock(); template.DisplayFormatFormula(obj => obj == null ? string.Empty : string.Format("{0:n0}", obj)); }); column.AggregateFunction(aggregateFunction => { aggregateFunction.NumericAggregateFunction(AggregateFunction.Sum); aggregateFunction.DisplayFormatFormula(obj => obj == null ? string.Empty : string.Format("{0:n0}", obj)); }); }); columns.AddColumn(column => { column.PropertyName("AddDate"); column.CellsHorizontalAlignment(HorizontalAlignment.Center); column.IsVisible(true); column.Order(4); column.HeaderCell("تاریخ ثبت"); column.ColumnItemsTemplate(template => { template.TextBlock(); template.DisplayFormatFormula(obj => obj == null ? string.Empty : ((DateTime)obj).ToString("dd/MM/yyyy HH:mm")); }); }); columns.AddColumn(column => { column.PropertyName("thumbnail"); column.CellsHorizontalAlignment(HorizontalAlignment.Center); column.IsVisible(true); column.Order(5); column.HeaderCell("تصویر"); column.ColumnItemsTemplate(t => t.ByteArrayImage(defaultImageFilePath: string.Empty, fitImages: false)); }); }) .Export(export => { export.ToXml(); export.ToExcel(); }) .Generate(data => data.AsPdfFile(AppPath.ApplicationPath + "\\Pdf\\RptDbImageSample.pdf")); } } }
توضیحات:
بانک اطلاعاتی SQLite موجود در پوشه bin\data سورس PdfReport، حاوی فیلدی است به نام thumbnail که در آن محتوای یک سری تصویر ذخیره شدهاند.
در اینجا توسط کوئری زیر قصد داریم این اطلاعات را خوانده و نمایش دهیم:
SELECT [url], [name], [NumberOfPosts], [AddDate], [thumbnail] FROM [tblBlogs] WHERE [NumberOfPosts]>=@p1
column.ColumnItemsTemplate(t => t.ByteArrayImage(defaultImageFilePath: string.Empty, fitImages: false));
به همین جهت افزونه farsiInput، که کدهای آنرا در ادامه مشاهده میکنید، تهیه گردید. این افزونه تا این تاریخ با IE، فایرفاکس، کروم و اپرا سازگار است و توسط آن کاربر بدون نیاز به داشتن یک صفحه کلید فارسی میتواند فارسی تایپ کند. برای سوئیچ به حالت انگلیسی، دکمه Scroll lock باید روشن شود و این مورد توسط پارامتر changeLanguageKey قابل تغییر است.
// <![CDATA[ (function ($) { $.fn.farsiInput = function (options) { var defaults = { changeLanguageKey: 145 /* Scroll lock */ }; var options = $.extend(defaults, options); var lang = 'fa'; var keys = new Array(1711, 0, 0, 0, 0, 1608, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1705, 1572, 0, 1548, 1567, 0, 1616, 1571, 8250, 0, 1615, 0, 0, 1570, 1577, 0, 0, 0, 1569, 1573, 0, 0, 1614, 1612, 1613, 0, 0, 8249, 1611, 171, 0, 187, 1580, 1688, 1670, 0, 1600, 1662, 1588, 1584, 1586, 1740, 1579, 1576, 1604, 1575, 1607, 1578, 1606, 1605, 1574, 1583, 1582, 1581, 1590, 1602, 1587, 1601, 1593, 1585, 1589, 1591, 1594, 1592); var substituteChar = function (charCode, e) { if (navigator.appName == "Microsoft Internet Explorer") { window.event.keyCode = charCode; } else { insertAtCaret(String.fromCharCode(charCode), e); } }; var insertAtCaret = function (str, e) { var obj = e.target; var startPos = obj.selectionStart; var endPos = obj.selectionEnd; var scrollTop = obj.scrollTop; obj.value = obj.value.substring(0, startPos) + str + obj.value.substring(endPos, obj.value.length); obj.focus(); obj.selectionStart = startPos + str.length; obj.selectionEnd = startPos + str.length; obj.scrollTop = scrollTop; e.preventDefault(); }; var keyDown = function (e) { var evt = e || window.event; var key = evt.keyCode ? evt.keyCode : evt.which; if (key == options.changeLanguageKey) { lang = (lang == 'en') ? 'fa' : 'en'; return true; } }; var fixYeKeHalfSpace = function (key, evt) { var originalKey = key; var arabicYeCharCode = 1610; var persianYeCharCode = 1740; var arabicKeCharCode = 1603; var persianKeCharCode = 1705; var halfSpace = 8204; switch (key) { case arabicYeCharCode: key = persianYeCharCode; break; case arabicKeCharCode: key = persianKeCharCode; break; } if (evt.shiftKey && key == 32) { key = halfSpace; } if (originalKey != key) { substituteChar(key, evt); } }; var keyPress = function (e) { if (lang != 'fa') return; var evt = e || window.event; var key = evt.keyCode ? evt.keyCode : evt.which; fixYeKeHalfSpace(key, evt); var isNotArrowKey = (evt.charCode != 0) && (evt.which != 0); if (isNotArrowKey && (key > 38) && (key < 123)) { var pCode = (keys[key - 39]) ? (keys[key - 39]) : key; substituteChar(pCode, evt); } } return this.each(function () { var input = $(this); input.keypress(function (e) { keyPress(e); }); input.keydown(function (e) { keyDown(e); }); }); }; })(jQuery); // ]]>
<html> <head> <title>تکست باکس فارسی</title> <script type="text/javascript" src="jquery-1.9.1.min.js"></script> <script type="text/javascript" src="jquery.farsiInput.js"></script> <style type="text/css"> input, textarea { font-family: tahoma; font-size: 9pt; } </style> </head> <body> <input dir="rtl" id='text1' /> <br /> <textarea dir="rtl" id='text2' rows="15" cols="84"></textarea> <script type="text/javascript"> $(function () { $("#text1, #text2").farsiInput(); }); </script> </body> </html>
دریافت کدهای کامل افزونه farsiInput
farsi_input.zip
امروز اولین دستورات MDX را خواهیم نوشت. قبل از شروع کار فراموش نکنید موارد زیر را حتما انجام داده باشید :
- نصب پایگاه داده ی Adventure Work DW 2008 و همچنین نصب پایگاه دادهی چند بعدی Adventure Work DW 2008 روی SSAS
- مطاله قسمتهای قبلی برای آشنایی با مفاهیم پایه .
در صورتیکه پیش شرایط فوق را نداشته باشید، احتمالا در ادامه با مشکلاتی مواجه خواهید شد؛ زیرا برای آموزش MDX Query ها از پایگاه دادهی Adventure Work DW 2008 استفاده شده است.
دقت داشته باشید که MDX Query ها تا حدودی شبیه T/SQL میباشند؛ اما مطلقا از نظر مفهومی با هم شباهت ندارند. به عبارت دیگر ما در T/SQL با یک مدل رابطهای سرو کار داریم در حالیکه در MDX ها با یک پایگاه داده چند بعدی کار میکنیم. به بیان دیگر در پایگاه دادههای رابطهای صحبت از جداول، ردیفها، ستونها و ضرب دکارتی مجموعهها میباشد، اما در پایگاه دادههای چند بعدی در خصوص Dimension,Fact,Cube,Tuple و ... صحبت میکنیم. البته ماکروسافت تلاش کردهاست تا حد زیادی Syntax ها شبیه به یکدیگر باشند.
نحوهی نوشتن یک Select در MDX ها به صورت زیر میباشد :
Select {} On Columns , {} On Rows From <Cube_Name> Where <Condition>
در ادامه با اجرای هر کوئری، توضیحات لازم در خصوص آن ارایه میگردد و با پیگیری این آموزشها میتوانید مفاهیم، توابع و ... را در MDX Query ها بیاموزید.
برای اجرای دستورات زیر باید Microsoft SQL Server Management Studio را باز نمایید و به سرویس SSAS متصل شوید. سپس پایگاه دادهی Adventure Works DW 2008R2 را انتخاب نمایید و از Cubes Adventure Works را انتخاب نمایید.
حال دکمهی New Query را در بالای صفحه بزنید ( Ctrl + N )
سپس در صفحهی باز شده میتوانید Cube یا SubCube های آن Cube را انتخاب کرده و کمی پایینتر Measure Group را خواهیم داشت و در انتها Measure ها و Dimension ها قرار گرفتهاند. (در هنگام نوشتن Select میتوان از عمل Drag&Drop برای آسانتر شدن نوشتن MDX Query ها نیز استفاده کنید)
متاسفانه هنوز در IDE مربوط به SQL Server کلیدی برای مرتب سازی دستورات MDX وجود ندارد و البته در نرم افزار هایی مانند SQL Toll Belt هم چنین چیزی قرار داده نشده است . بنابر این توصیه میشود در نوشتن دستورات MDX تمام تلاش خود را بکنید تا دستوراتی مرتب و خوانا را تولید کنید.
با اجرای دستور زیر اولین کوئری خود را در پایگاه دادهی چند بعدی بنویسید (برای اجرا کلید F5 مانند T/SQL کار خواهد کرد.)
Select From [Adventure Works]
شاید تعجب کنید. کوئری فاقد قسمت Projection میباشد! در MDX ها میتوان هیچ سطر یا ستونی را انتخاب نکرد. اما چگونه؟ و خروجی نمایش داده شده چیست؟
برای توضیح مطلب فوق باید در خصوص Default Measure کمی اطلاعات داشته باشید. در هنگام Deploy کردن پروژه در SSAS برای هر Cube یک Measure به عنوان Measure پیش فرض انتخاب شده. بنابر این در صورتیکه هیچ گونه Projection یا Where ایی اعمال نشده باشد، SQL Server به صورت پیش فرض مقدار Mesaure پیش فرض را بدون اعمال هیچ بعدی نمایش میدهد.
خروجی دستور بالا مشابه تصویر زیر میباشد.
حال دستور زیر را اجرا میکنیم :
Select From [Adventure Works] Where [Measures].[Reseller Sales Amount]
تصویر خروجی به صورت زیر میباشد :
شاید باز هم تعجب کنید. نوشتن نام یک شاخص به جای عبارت شرط؟! آیا خروجی عبارات شرطی نباید Boolean باشند؟
خیر. اگر چنین پرسش هایی در ذهن شما ایجاد شده باشد، به دلیل مقایسهی MDX با T/SQL میباشد. در اینجا شرط Where بر روی ردیفهای جدول مدل رابطه ای اعمال نمیشود و عملا بیانگر واکشی اطلاعات از مدل چند بعدی میباشد. با اعمال شرط فوق به SSAS اعلام کرده ایم که خروجی بر اساس شاخص [Measures].[Reseller Sales Amount] باشد. با توجه به این که شاخص انتخاب شده با شاخص پیش فرض یکی میباشد خروجی با حالت قبل تفاوتی نخواهد کرد.
برای درک بهتر، کوئری زیر را اجرا کنید :
Select From [Adventure Works] where [Measures].[Internet Sales Amount]
استفاده از این شرط سبب استفاده نشدن از شاخص پیش فرض می شود . به عبارت دیگر این کوئری دارای سرجمع مبلغ فروش اینترنتی می باشد.
دستور زیر را اجرا کنید :
Select [Measures].[Reseller Sales Amount] on columns From [Adventure Works]
با اعمال یک شاخص خاص در ستون ، عملا فیلترینگ انجام می شود
استفاده از یک دایمنشن در ستون :
دستور زیر را اجرا کنید
Select [Date].[Calendar].[Calendar Year] on columns From [Adventure Works]
خروجی به شکل زیر خواهد بود
همان طور که مشاهده میکنید خروجی دارای چندین ستون میباشد و دارای مقادیری در هر ستون. اما این مقادیر از کجا آمده اند؟
همواره این نکته را به خاطر بسپارید که در صورت عدم ذکر نام یک Measure در کوئری ، SSAS از Measure پیش فرض استفاده میکند. حال کوئری فوق میزان فروش نمایندگان ( Reseller Sales Amount ) را در هر سال نمایش میدهد.
سوال بعدی این میباشد که این سالها از کجا آمده اند؟ خوب برای درک بهتر این مورد میتوانیم مانند تصویر زیر به دایمنشن Date رفته و در ساختار سلسله مراتبی ، اعضای سطح [Date].[Calendar].[Calendar Year] را مشاهده کنیم.
ایجاد سرجمع ستونها :
کوئری زیر را اجرا نمایید
Select {[Date].[Calendar].[Calendar Year],[Date].[Calendar]} on columns From [Adventure Works]
بعد از اجرا
تصویر زیر را خواهید دید :
سوال اول این میباشد که کاربرد {} در انتخاب دایمنشنها چیست؟ در پاسخ میتوان گفت که اگر شاخص ها یا بعد ها ، مرتبط به یک سلسله مراتب باشند آنها را در یک {} قرار می دهیم ولی اگر سلسله مراتب متفاوت باشد، یا بعد و شاخص باشند باید در () قرار بگیرند .
خوب همان طور که مشخص است در ساختار سلسله مراتبی ابتدا سال و بعد یک سطح بالاتر را انتخاب کرده ایم این به معنی نمایش سرجمع در سطح بالاتر از سال میباشد(سرجمع تمامی سال ها).
استفاده از دایمنشن و Measure در سطر و ستون مجرا :
کوئری زیر را اجرا نمایید
Select {[Date].[Calendar].[Calendar Year],[Date].[Calendar]} on columns, [Product].[Product Categories].[Category] on rows From [Adventure Works]
خروجی مشابه شکل زیر میباشد
در مثال فوق از بعدها در ستون و همزمان، نمایش نوع دسته بندی محصولات در ردیفها استفاده شده است. به عبارت دیگر نتیجه عبارت است از فروش نماینگان فروش ( Reseller Sales Amount ) براساس هر سال به تفکیک نوع دسته بندی محصول فروخته شده.
(کسانی که چنین گزارشی را با استفاده از T/SQL نوشته اند، احتمالا از آسانی نوشتن این گزارش توسط MDX ها شگفت زده شده اند.)
قراردادن فیلد سرجمع در ردیف :
برای این منظور کوئری زیر را اجرا نمایید
Select {[Date].[Calendar].[Calendar Year],[Date].[Calendar]} on columns, {[Product].[Product Categories].[Category],[Product].[Product Categories]}on rows From [Adventure Works]
خروجی به صورت زیر میباشد
نحوهی نمایش سرجمع در ردیف، مشابه نمایش سرجمع در ستون میباشد.
استفاده از تابع non empty :
برای حذف ستون هایی که کاملا دارای مقدار null میباشند به صورت زیر عمل میکنیم :
Select non empty {[Date].[Calendar].[Calendar Year],[Date].[Calendar]} on columns , {[Product].[Product Categories].[Category],[Product].[Product Categories]} on rows From [Adventure Works]
خروجی به صورت زیر میباشد:
انتخاب دو دایمنشن در سطر و ستون و مشخص نمودن یک Measure خاص برای کوئری :
برای این کار به صورت زیر عمل خواهیم کرد:
Select {[Date].[Calendar].[Calendar Year],[Date].[Calendar]} on columns, {[Product].[Product Categories].[Category],[Product].[Product Categories]} on rows From [Adventure Works] Where [Measures].[Internet Sales Amount]
در اینجا با اعمال شرط Where عملا از SSAS خواستهایم خروجی برای شاخص مشخص شده واکشی شود.
در بالا میزان فروش اینترنتی برای دسته بندی محصولات و در سالهای مختلف ارائه و همچنین سرجمع ستون و سطر نیز نمایش داده شده است.
در صورتیکه بخواهیم ستون و سطرهایی را که دارای مقدار null در تمامی آن سطر یا ستون میباشند، حذف کنیم به صورت زیر عمل میکنیم:
Select non empty {[Date].[Calendar].[Calendar Year],[Date].[Calendar]} on columns, non empty {[Product].[Product Categories].[Category],[Product].[Product Categories]} on rows From [Adventure Works] Where [Measures].[Internet Sales Amount]
اگر در یک دایمنشن فقط یک سلسله مراتب باشد یا اصلا سلسله مراتبی وجود نداشته باشد، می توان از نام خود دایمنشن استفاده کرد
Select [Sales Channel] on columns From [Adventure Works]
و دقت داشته باشید دایمنشنی که دارای بیش از یک سلسله مراتب باشد، حتما باید در Select مشخص شود که از کدام سلسله مراتب می خواهیم استفاده کنیم .در غیر این صورت با خطا مواجه خواهیم شد.
Select [Product] on columns From [Adventure Works]
استفاده از فیلدهای یک دایمنشن که دارای سلسه مراتب می باشد نیز جایز می باشد
Select [Product].[Category] on columns From [Adventure Works]
Select [Product].[Category].[all] on columns From [Adventure Works] -- Select [Product].[Category].[All] on columns From [Adventure Works] -- Select [Product].[Category].[(all)] on columns From [Adventure Works] -- Select [Product].[Category].[all products] on columns From [Adventure Works]
برای به دست آوردن سرجمع کل روی یک صفت از دایمنشن، باید از سه حالت آخر استفاده کرد. حالت اول خطا دارد و خروجی خالی نمایش داده می شود .
در صورتی که بخواهیم از یک دایمنشن تمامی Member های آن را واکشی کنیم به صورت زیر عمل خواهیم کرد
Select {[Product].[Category].members} on columns From [Adventure Works]
استفاده از Members روی یک خصوصیت در دایمنشن به معنی دریافت سرجمع آن صفت و سپس تک تک اجزای آن صفت میباشد.
اگر از یک صفت واکشی اطلاعات انجام شود در سطح اعضای آن، در آن صورت دیگر سرجمع نمایش داده نمی شود و فقط جمع هر عضو در آن صفت نمایش داده می شود .
Select [Product].[Category].[Category].members -- dimension.hierarchy.level.members on columns From [Adventure Works]
اگر بخواهیم دو ستون را داشته باشیم که هر دو برای یک دایمنشن میباشند باید از {} استفاده کرد . دستور اول خطا خواهد داشت.
Select [Product].[Category].[Category].members,[Product].[Category].[All Products] on columns From [Adventure Works]
در دستور دوم با استفاده از {} خروجی نمایش داده میشود که عبارت است از تمامی اعضای سطح [Product].[Category].[Category]. به همراه سرجمع تمامی محصولات.
Select {[Product].[Category].[Category].members,[Product].[Category].[All Products]} on columns From [Adventure Works]
یک راه کوتاهتر برای انتخاب تمامی اعضا و سرجمع آنها
Select {[Product].[Category].[Category],[Product].[Category]} on columns From [Adventure Works]
می توان از کلمات Members, All X استفاده نکرد.
انتخاب اولین دسته بندی محصول البته این ترتیب بر اساس Key Columns در SSAS می باشد .
Select [Product].[Category].&[1] on columns From [Adventure Works]
انتخاب دقیق یک عضو در خروجی
Select [Product].[Category].[Bikes] on columns From [Adventure Works]
انتخاب دو عضو از یک دایمنشن
Select {[Product].[Category].[Bikes],[Product].[Category].[Clothing]} on columns From [Adventure Works]
واکشی تمامی دسته بندی محصولات بر اساس Measure پیش فرض :
Select [Product].[Product Categories].members on columns From [Adventure Works]
در صورتیکه بخواهیم دو Dimension مختلف را در یک ستون یا سطر بیاوریم باید از Join استفاده کنیم. بنابر این دو دستور زیر با خطا روبرو میشوند
Select [Product].[Product Categories],[Product].[Category] on columns From [Adventure Works] Go Select {[Product].[Product Categories],[Product].[Category]} on columns From [Adventure Works]
تعریف Axis : به هر کدام از ستون یا سطر یک محور یا Axis گفته میشود.
با بررسی مثال فوق به نتایج زیر خواهیم رسید.
1. امکان استفاده از دو سلسله مراتب مختلف از یک دایمنشن در یک Axis وجود ندارد . مگر اینکه آنها را باهمدیگر CrossJoin کنیم .
2. امکان استفاده از دو سلسله مراتب مختلف از یک دایمنشن در دو Axis مختلف وجود دارد .
ترتیب انتخاب Axis ها به صورت زیر میباشد:
1. Columns
2. Rows
برای مشخص شدن موضوع کوئری زیر را اجرا کنید
Select [Product].[Product Categories].members on rows From [Adventure Works]
نمیتوانیم ردیفی را واکشی کنیم بدون اینکه ستونی برای کوئری مشخص کرده باشیم.
البته میتوان ستون خالی ایجاد نماییم مانند مثال زیر :
Select {} on columns, [Product].[Product Categories].members on rows From [Adventure Works]
البته در این صورت خروجی فقط نام دسته بندی محصولات خواهد بود زیرا هیچ ستونی مشخص نشده .
در مقالات بعدی به ادامهی مطالب MDX Query خواهیم پرداخت.
پیشنیازها
در اینجا فرض بر این است که موارد ذیل را نصب کردهاید:
- آخرین نگارش مرورگر Chrome
- افزونهی Debugger for Chrome که از آن برای دیباگ کدهای جاوا اسکریپتی و تایپاسکریپتی یکپارچهی با VSCode میتوان استفاده کرد.
تنظیمات VSCode جهت دیباگ برنامههای مبتنی بر Angular CLI
کدهای مطلب «فرمهای مبتنی بر قالبها در Angular - قسمت پنجم - ارسال اطلاعات به سرور» از انتهای بحث آن قابل دریافت هستند. این کدها را دریافت کرده و سپس پوشهی اصلی آنرا در VSCode باز کنید. اگر در ویندوز هستید با کلیک راست بر روی پوشه، گزینهی Open With Code ظاهر میشود و یا حتی میتوان از طریق خط فرمان به پوشهی اصلی برنامه مراجعه کرده و سپس دستور . code را صادر نمود.
در ادامه نیاز است دیباگر VSCode را تنظیم کنیم. اگر پیشتر هیچ تنظیمی را نداشته باشید، پس از مراجعهی به برگهی debug آن، بر روی دکمهی سبز رنگ Start Debugging آن کلیک کنید. در ادامه در منوی باز شده، گزینهی Chrome را انتخاب کنید:
اینکار سبب میشود تا فایل جدید vscode\launch.json. به پوشهی جاری اضافه شده و سپس تنظیمات دیباگر کروم در آن قرار گیرند.
حالت دوم شبیه به حالتی است که برنامهی مورد بحث جاری دارد: پیشتر دیباگری مانند NET Core. در آن تنظیم شدهاست. در این حالت، منوی drop down مربوط به دیباگرهای تنظیم شده را گشوده و سپس گزینهی آخر آن یعنی Add configuration را انتخاب کنید:
در اینجا نیز منوی Intellisense آن ظاهر شده و امکان انتخاب گزینهی Chrome را میدهد:
در نهایت هر دو حالت به ایجاد فایل جدید vscode\launch.json. و یا ویرایش آن منتهی میشوند. در اینجا تنها کاری را که باید انجام داد، تغییر پورت پیش فرض آن است:
- اگر از دستور ng serve استفاده میکنید، این پورت را به 4200 تغییر دهید (پورت پیش فرض این دستور است؛ برای تغییر آن، از پارامتر port-- در دستور ng serve استفاده کنید):
{ "version": "0.2.0", "configurations": [ { "type": "chrome", "request": "launch", "name": "Launch Chrome with ng serve", "url": "http://localhost:4200/#", "webRoot": "${workspaceRoot}" } ] }
- اگر از دستورات dotnet watch run و سپس ng build -watch استفاده میکنید (اولی وب سرور آزمایشی NET Core. را راه اندازی میکند و دومی کار ساخت پیوستهی برنامهی Angular را انجام میدهد)، این پورت را بر روی 5000 تنظیم کنید:
{ "version": "0.2.0", "configurations": [ { "type": "chrome", "request": "launch", "name": "Launch Chrome for dotnet build & ng build", "url": "http://localhost:5000/#", "webRoot": "${workspaceRoot}" } ] }
آزمایش یک break point و بررسی مقادیر دریافتی از سرور
تا اینجا کاری را که انجام دادیم، به افزودن یک قطعه تنظیم جدید به فایل vscode\launch.json. خلاصه میشود.
در ادامه به کامپوننت employee-register.component.ts مراجعه کرده و سطر this.languages = data را تبدیل به یک سطر مستقل درون {} میکنیم تا بتوانیم بر روی آن break point قرار دهیم:
ngOnInit() { this.formPoster.getLanguages().subscribe( data => { this.languages = data; }, err => console.log("get error: ", err) ); }
پس از آن از طریق خط فرمان به ریشهی پروژه وارد شده و دو پنجرهی کنسول مجزا را باز کنید. در اولی دستورات
>npm install >ng build --watch
>dotnet restore >dotnet watch run
سپس به برگهی دیباگ مراجعه کرده و گزینهی جدید Launch Chrome for dotnet build & ng build را انتخاب و سپس بر روی دکمهی سبز رنگ اجرای آن کلیک کنید:
اکنون اگر صفحهی مشاهدهی فرم را در مرورگر کروم باز شده درخواست کنیم، به این break point خواهیم رسید؛ اما ... حاوی اطلاعات data نیست. برای رفع این مشکل نیاز است تنظیمات پیشفرض را به صورت ذیل بهبود بخشید:
{ "version": "0.2.0", "configurations": [ { "type": "chrome", "request": "launch", "name": "Launch Chrome for dotnet build & ng build", "url": "http://localhost:5000", "runtimeArgs": [ "--user-data-dir", "--remote-debugging-port=9222", "--disable-session-crashed-bubble" ], "sourceMaps": true, "trace": true, "webRoot": "${workspaceRoot}/wwwroot/", "userDataDir": "${workspaceRoot}/.vscode/chrome" } ] }
- تنظیم sourceMaps و همچنین مشخص سازی محل دقیق پوشهی قرارگیری فایلهای نهایی ng build که همان پوشهی wwwroot است در webRoot سبب خواهند شد تا اینبار break point ما حاوی اطلاعات واقعی data دریافتی از سرور باشند (با نزدیک کردن اشارهگر ماوس به data، اطلاعات تکمیلی آن ظاهر میشود):
- اگر از دستور ng serve استفاده میکنید، در این تنظیمات پورت را به 4200 و محل پوشهی wwwroot را به dist تغییر دهید (مطابق تنظیمات فایل angular-cli.json.).