اشتراکها
نظرات مطالب
پایان پروژه ASP.NET Ajax Control Toolkit !
مایکروسافت ASP.NET Ajax را با اون عظمت و زحمتی که براش کشیده بودند، در مقابل jQuery بازنده اعلام کرده الان شما صحبت از Anthem.Net میکنید که فقط منحصر است به ASP.NET web forms آن هم نگارشهای قبل از سه آن + پشتیبانی از مرورگرهای مختلف هم در آن ضعیف است.
هدف مایکروسافت از اینکار مدیریت هر دو پروژه MVC و Web forms است آن هم با هزینه کم، کیفیت بالا و سازگار با تمام مرورگرها.
ضمنا این رو در نظر باشید که یکی از توانمندیهای jQuery، Ajax است (از کار با DOM گرفته تا دستکاری CSS نمایش داده شده، تا Animation ، مدیریت سادهتر رخدادها، اعمال قالب به سایت و غیره) و این مورد مزیت مهمی است نسبت به تمام کتابخانههایی که فقط برای یک کار و آن هم سهولت تولید برنامههای مبتنی بر Ajax ایجاد شدهاند و از چند مشکل مهم رنج میبرند:
- تک کارهاند. فقط Ajax .
- مشکل سازگاری با مرورگرهای مختلف را دارند.
- به صورت فعال توسعه داده نمیشوند؛ رفع باگ نمیشوند و غیره. برای مثال فواصل به روز رسانی همان Anthem.Net را بررسی کنید.
- توسعه پذیر نیستند. برای مثال آیا میتوان برای Anthem.Net افزونه نوشت؟
- حجم بالایی دارند.
- سرعت پایینی دارند.
jQuery در مورد تمام موارد عنوان شده حرف برای گفتن دارد از حجم کم تا سرعت بالاتر نسبت به اکثر کتابخانههای جاوا اسکریپتی دیگر تا توسعهی منظم، سازگاری عالی با مرورگرها، توسعه پذیری و صدها و هزاران افزونهی مهیا برای آن و غیره.
و RAD هزینه بر است. یعنی چی؟ یعنی حجم بالای کدهای اسکریپتی که باید به برنامهی شما مثلا توسط ASP.NET Ajax تزریق شود که مبادا شما بخواهید دست خودتان را به نوشتن چند سطر کد جاوا اسکریپتی آلوده کنید. همچنین حجم تبادل اطلاعات ASP.NET Ajax را هم که مبتنی است بر RAD را هم با حجم اطلاعات مبادله شده توسط jQuery مقایسه کنید (با پلاگین فایرباگ مربوط به فایرفاکس). این حجم واقعا زیاد است و قابل مقایسه نیست. (تمام اینها هزینههای RAD است)
ضمنا وجود 100 ها افزونه و پلاگین نوشته شده برای jQuery کار شما را بسیار بسیار ساده میکنند، مانند پاسخ قبلی من در این مطلب.
فقط باید کمی وقت بگذارید و چیزی را بیاموزید که واقعا ارزش دارد. گیرم فردا نخواستید با ASP.NET کار کنید. تمام این اطلاعات در PHP هم به درد شما میخورد، چون قسمت سمت کلاینت آنچنان تفاوتی نمیکند و jQuery یک کتابخانهی سمت کلاینت است.
هدف مایکروسافت از اینکار مدیریت هر دو پروژه MVC و Web forms است آن هم با هزینه کم، کیفیت بالا و سازگار با تمام مرورگرها.
ضمنا این رو در نظر باشید که یکی از توانمندیهای jQuery، Ajax است (از کار با DOM گرفته تا دستکاری CSS نمایش داده شده، تا Animation ، مدیریت سادهتر رخدادها، اعمال قالب به سایت و غیره) و این مورد مزیت مهمی است نسبت به تمام کتابخانههایی که فقط برای یک کار و آن هم سهولت تولید برنامههای مبتنی بر Ajax ایجاد شدهاند و از چند مشکل مهم رنج میبرند:
- تک کارهاند. فقط Ajax .
- مشکل سازگاری با مرورگرهای مختلف را دارند.
- به صورت فعال توسعه داده نمیشوند؛ رفع باگ نمیشوند و غیره. برای مثال فواصل به روز رسانی همان Anthem.Net را بررسی کنید.
- توسعه پذیر نیستند. برای مثال آیا میتوان برای Anthem.Net افزونه نوشت؟
- حجم بالایی دارند.
- سرعت پایینی دارند.
jQuery در مورد تمام موارد عنوان شده حرف برای گفتن دارد از حجم کم تا سرعت بالاتر نسبت به اکثر کتابخانههای جاوا اسکریپتی دیگر تا توسعهی منظم، سازگاری عالی با مرورگرها، توسعه پذیری و صدها و هزاران افزونهی مهیا برای آن و غیره.
و RAD هزینه بر است. یعنی چی؟ یعنی حجم بالای کدهای اسکریپتی که باید به برنامهی شما مثلا توسط ASP.NET Ajax تزریق شود که مبادا شما بخواهید دست خودتان را به نوشتن چند سطر کد جاوا اسکریپتی آلوده کنید. همچنین حجم تبادل اطلاعات ASP.NET Ajax را هم که مبتنی است بر RAD را هم با حجم اطلاعات مبادله شده توسط jQuery مقایسه کنید (با پلاگین فایرباگ مربوط به فایرفاکس). این حجم واقعا زیاد است و قابل مقایسه نیست. (تمام اینها هزینههای RAD است)
ضمنا وجود 100 ها افزونه و پلاگین نوشته شده برای jQuery کار شما را بسیار بسیار ساده میکنند، مانند پاسخ قبلی من در این مطلب.
فقط باید کمی وقت بگذارید و چیزی را بیاموزید که واقعا ارزش دارد. گیرم فردا نخواستید با ASP.NET کار کنید. تمام این اطلاعات در PHP هم به درد شما میخورد، چون قسمت سمت کلاینت آنچنان تفاوتی نمیکند و jQuery یک کتابخانهی سمت کلاینت است.
نرم افزارهای ارائه شده این تیم به صورت رایگان بوده و عموما بدون نیاز به نصب و با حجم بسیار کم میباشد که ابزارهای بسیار کارآمدی در اختیار برنامه نویسان قرار داده است.
توضیحات تکمیلی در مورد نرم افزارها در سایت این تیم قرار دارد ولی جهت آشنایی دوستان به صورت مختصر در مورد چند نرم افزار مفید این تیم توضحاتی خواهم داد.
- Convert.Net
یکی از مفیدترین و بهترین ابزارهای این تیم نرم افزار Convert.Net است که امکانات مفیدی در اختیار شما قرار میدهد از جمله :
- Regular Expression Tester : که جهت تست Regexهای نوشته شده استفاده میشود.
- Encoding & Decoding : جهت تبدیل انواع رشتههای Encoded ویا Decoded به یکدیگر استفاده میشود و از html - url - EScape-js - Base64 - string و ... پشتیبانی میکند.
- Encryption & Decryption : جهت Encrypt و Decrypt انواع رشته استفاده میشود که از انکریپتورهای معروفی همچون AES - Rijndael - DES - SHA - TripleDES پشتیبانی میکند.
- Language Translation : یک دیکشنری Multi Language آنلاین در اختیار شما برای ترجمه متون قرار میدهد.
- C# & VB.Net Convertor : برای تبدیل کدهای C# به Vb و برعکس استفاده میشود و طبق تست هایی که روش به شخصه انجام دادم در اکثر موارد بدون خطا تا حدود 90 درصد تبدیلات رو به صورت موفق انجام میده ولی مانند سایر Convertorها با Lambda Expression کمی مشکل دارد.
- Xml & Json Browser : برای مشاهده و تبدیل Xml به Json و برعکس بسیار مفید است ..
- Linq Tester : برای تست کوئریهای Linq استفاده میشود . (برای استفاده از این امکان باید Roslyne روی سیستم شما نصب باشد)
حجم برنامه 2 مگابایت : دانلود
- Database.Net
نرم افزار کم حجم جهت اتصال و مدیریت انواع دیتابیس میباشد در عین سادگی و حجم کم ابزار مفیدی جهت اجرای Queryها ساخت جداول , مشاهده و ویرایش رکوردها و .... در اختیارتان قرار میدهد.
دیتابیسهای پشتیابنی شده در این نرم افزار :
SQL Server 2000/2005/2008/2012/2014/Express/LocalDB SQL Server Compact 3.1/3.5/4.0 (*.sdf;*.*) SQL Azure 10/11 MS Access 97/2000/2002/2003 (*.mdb;*.mde;*.*), 2007/2010/2013 (*.accdb;*.accde;*.*) MS Excel 97/2000/2002/2003(*.xls;*.*), 2007/2010/2013 (*.xlsx;*.xlsm;*.xlsb;*.*) Firebird SuperServer/Embedded 1.5/2.0/2.1/2.5 (*.gdb;*.fdb;*.*) SQLite 3.x (*.db;*.db3;*.sqlite;*.*) MySQL 5.x, MariaDB 5.x/10.x PostgreSQL 8.x/9.x Oracle 10g/11g/12c IBM DB2 9.x/10.x IBM Informix 11.x/12.x Sybase ASE 15.x dBASE IV (*.dbf) Visual FoxPro (*.dbc) Data Sources (OleDB)(*.udl;*.*) ODBC DSN (Data Source Name)(*.dsn;*.*) OData (Open Data Protocol) v1/v2/v3/v4
حجم برنامه 9 مگابایت : دانلود
- Resource.Net
این نرم افزار جهت ساخت , ویرایش و مدیریت انواع فایل Resource برنامههای دات نت بسیار مفید میباشد که از نسخههای مختلف دات نت پشتیبانی میکند.
سایر نرم افزارهای این تیم هم مانند نرم افزارهای معرفی شده بین کاربران محبوبیت زیادی کسب کرده اند که میتوانید برای کسب اطلاعات بیشتر و دانلود این نرم افزارها به وب سایت این تیم مراجعه فرمائید.
در بار اول اجرای برنامههای Blazor WASM، کار دریافت و کش شدن اسمبلیهای NET Runtime. و برنامه انجام میشوند:
در این بین ... اتفاقی رخ نمیدهد و کاربر از پیشرفت عملیات آگاه نمیشود. در این مطلب قصد داریم این وضعیت را بهبود دهیم.
افزودن یک progress-bar به صفحهی آغازین برنامههای Blazor WASM
Blazor امکان دسترسی به چرخهی حیات ابتدایی آنرا نیز میسر کردهاست. برای اینکار ابتدا باید به آن گفت که دریافت خودکار تمام موارد مورد نیاز را انجام نده و ما اینکار را خودمان انجام خواهیم داد:
برای اینکار باید به تگ اسکریپتی که به blazor.webassembly.js اشاره میکند، ویژگی autostart را با مقدار false، افزود. از این پس باید خودمان کار آغاز Blazor را انجام دهیم که در طی دو مرحله، انجام خواهد شد:
الف) تغییر متن Loading پیشفرض جهت نمایش یک progress-bar
به فایل index.html برنامه مراجعه کرده و بجای loading پیشفرض آن، یک چنین طرحی را قرار میدهیم که به همراه یک label و یک progressbar در وسط صفحه است:
ب) سپس فایل جدید js/blazorLoader.js را با محتوای زیر اضافه میکنیم. در ابتدای این فایل به المانهای progressbar و progressbarLabel طرح فوق اشاره میشود:
این فایل باید پس از تعریف مدخل blazor.webassembly.js به فایل index.html اضافه شود:
توضیحات:
- محتوای blazorLoader.js، به صورت خود اجرا شونده تهیه شدهاست.
- متد Blazor.start، کار آغاز دستی Blazor WASM و دریافت فایلهای مورد نیاز آنرا انجام میدهد.
- خاصیت loadBootResource آن، به تابعی اشاره میکند که پیشنیازهای اجرایی Blazor WASM را دریافت میکند.
- در متد سفارشی loadBootResource که تهیه کردهایم، responsePromiseها را شمارش کرده و بر اساس تعداد کلی آنها و مواردی که دریافت آنها به پایان رسیدهاست، یک progress-bar را تشکیل و نمایش میدهیم.
- تابع isAutostartDisabled، بررسی میکند که آیا ویژگی autostart مساوی false، به تگ اسکریپت blazor.webassembly.js اضافه شدهاست یا خیر؟
کدهای کامل این مطلب را از اینجا میتوانید دریافت کنید: BlazorWasmLoadingBar.zip
در این بین ... اتفاقی رخ نمیدهد و کاربر از پیشرفت عملیات آگاه نمیشود. در این مطلب قصد داریم این وضعیت را بهبود دهیم.
افزودن یک progress-bar به صفحهی آغازین برنامههای Blazor WASM
Blazor امکان دسترسی به چرخهی حیات ابتدایی آنرا نیز میسر کردهاست. برای اینکار ابتدا باید به آن گفت که دریافت خودکار تمام موارد مورد نیاز را انجام نده و ما اینکار را خودمان انجام خواهیم داد:
<script src="_framework/blazor.webassembly.js" autostart="false"></script>
الف) تغییر متن Loading پیشفرض جهت نمایش یک progress-bar
<body> <div id="app"> <div class="d-flex flex-column min-vh-100"> <div class="d-flex vh-100"> <div class="d-flex w-100 justify-content-center align-self-center"> <div class="d-flex flex-column w-25"> <div>Loading <label id="progressbarLabel"></label></div> <div class="progress mt-2" style="height: 2em;"> <div id="progressbar" class="progress-bar progress-bar-striped"></div> </div> </div> </div> </div> </div> </div>
ب) سپس فایل جدید js/blazorLoader.js را با محتوای زیر اضافه میکنیم. در ابتدای این فایل به المانهای progressbar و progressbarLabel طرح فوق اشاره میشود:
(function () { let resourceIndex = 0; const fetchResponsePromises = []; const progressbar = document.getElementById("progressbar"); const progressbarLabel = document.getElementById("progressbarLabel"); const loadStart = new Date().getTime(); if (!isAutostartDisabled()) { console.warn( "`blazor.webassembly.js` script tag doesn`t have the `autostart=false` attribute." ); return; } Blazor.start({ loadBootResource: function (type, filename, defaultUri, integrity) { if (type === "dotnetjs") { progressbarLabel.innerText = filename; return defaultUri; } const responsePromise = fetch(defaultUri, { cache: "no-cache", integrity: integrity, }); fetchResponsePromises.push(responsePromise); responsePromise.then((response) => { if (!progressbar) { console.warn("Couldn't find the progressbar element on the page."); return; } if (!progressbarLabel) { console.warn( "Couldn't find the progressbarLabel element on the page." ); return; } resourceIndex++; const totalResourceCount = fetchResponsePromises.length; const percentLoaded = Math.round( 100 * (resourceIndex / totalResourceCount) ); progressbar.style.width = `${percentLoaded}%`; progressbar.innerText = `${percentLoaded} % [${resourceIndex}/${totalResourceCount}]`; progressbarLabel.innerText = filename; if (percentLoaded >= 100) { var span = new Date().getTime() - loadStart; console.log(`All done in ${span} ms.`); } }); return responsePromise; }, }); function isAutostartDisabled() { var wasmScript = document.querySelector( 'script[src="_framework/blazor.webassembly.js"]' ); if (!wasmScript) { return false; } var autostart = wasmScript.attributes["autostart"]; return autostart && autostart.value === "false"; } })();
<script src="_framework/blazor.webassembly.js" autostart="false"></script> <script src="js/blazorLoader.js"></script> </body>
- محتوای blazorLoader.js، به صورت خود اجرا شونده تهیه شدهاست.
- متد Blazor.start، کار آغاز دستی Blazor WASM و دریافت فایلهای مورد نیاز آنرا انجام میدهد.
- خاصیت loadBootResource آن، به تابعی اشاره میکند که پیشنیازهای اجرایی Blazor WASM را دریافت میکند.
- در متد سفارشی loadBootResource که تهیه کردهایم، responsePromiseها را شمارش کرده و بر اساس تعداد کلی آنها و مواردی که دریافت آنها به پایان رسیدهاست، یک progress-bar را تشکیل و نمایش میدهیم.
- تابع isAutostartDisabled، بررسی میکند که آیا ویژگی autostart مساوی false، به تگ اسکریپت blazor.webassembly.js اضافه شدهاست یا خیر؟
کدهای کامل این مطلب را از اینجا میتوانید دریافت کنید: BlazorWasmLoadingBar.zip
همه ما میدانیم برای اینکه محتوای ما به وسیله Google و سایر موتورهای جستجو index شود باید این محتوا در سمت سرور ایجاد و به کلاینت ارسال شود. مدتی بود با مقالاتی مواجه میشدم که نیازی به این کار نیست و گوگل این قابلیت را دارد تا اطلاعاتی را که سمت کلاینت پردازش و Render میشوند نیز index کند. تا این که خودم تصمیم گرفتم این مورد را تست کنم.
چند روز پیش شروع به بررسی SEO در AngularJS 1.x کردم. صورت مسئلهی من به این صورت بود که نام تعدادی شهر را با AngularJS در صفحه Render کنم، طوریکه در DOM اولیه که از سرور هدایت میشود، نام شهرها موجود نباشند. کد زیر را مشاهده کنید.<html dir="rtl"> <head> <title>وب سایت</title> <script src="angular.min.js"> </script> </head> <body ng-app="app"> <ul ng-controller="ctrl"> <li ng-repeat="item in list">{{item}}</li> </ul> </body> <script> var app=angular.module('app',[]); app.controller('ctrl',function($scope,$timeout){ $scope.list=[ 'اردبیل', 'تهران', 'شیراز', 'قزوین', ] }); </script> </html>
سپس در وب مستر گوگل، مسیر را تعریف کردم و به crawl گوگل اعلام کردم که این مسیر را index کند. بعد از مدتی متوجه شدم این صفحه با تمام نامهای شهرها index شدهاند!
مسئله را سختتر کردم و این بار به صورت مسئله اولیه این مورد را هم اضافه کردم که بعد از اینکه صفحه بارگذاری شد، بعد از 5 ثانیه، نام شهر مشهد هم به لیست DOMها اضافه شود و به کد بالا این کد را هم اضافه کردم (این کار را برای شبیه سازی درخواست AJAX انجام دادم):
$timeout(function(){ $scope.list.push('مشهد') },5000);
البته نیاز به بررسی دقیقتر این مسئله هست و باید در پروژههای واقعی این مورد را بررسی کرد تا safe بودن این قابلیت گوگل مورد تایید قرار بگیرد. در حال حاضر برای SEO در ReactJS و AngualrJS و VueJS از Render سمت Server استفاده میکنم. اگر این قابلیت به طور 100% جوابگوی SEO باشد، دیگر نیازی نیست Developerها سمت سرور و کلاینت، کارهای تکراری برای SEO انجام دهند.
تا جایی که من مطلع هستم از طریق تنظیمات IIS میتوان پاسخ به درخواستها را (شامل فایلهای استاتیک و داینامیک) به کمک gzip فشرده کرد. اما اگر اطلاعاتم صحیح باشد این موضوع کمی بار CPU را افزایش میدهد، هر چند گاهی تا 75% حجم اطلاعات رد و بدل شده را کاهش میدهد.
برای اطلاعات بیشتر در مورد تنظیمات IIS6 میتوانید به + و + مراجعه کنید.
همچنین اگر روی IIS7 به بعد میزبانی میشوید میتوانید درون فایل Web.Config و درون تگ system.webServer تنظیمات زیر را اضافه کنید:
<httpCompression directory="%SystemDrive%\inetpub\temp\IIS Temporary Compressed Files"> <scheme name="gzip" dll="%Windir%\system32\inetsrv\gzip.dll" staticCompressionLevel="9" dynamicCompressionLevel="4" /> <scheme name="deflate" dll="%Windir%\system32\inetsrv\gzip.dll" staticCompressionLevel="9" dynamicCompressionLevel="4" /> <dynamicTypes> <add mimeType="text/*" enabled="true" /> <add mimeType="message/*" enabled="true" /> <add mimeType="application/x-javascript" enabled="true" /> <add mimeType="application/atom+xml; charset=utf-8" enabled="true" /> <add mimeType="*/*" enabled="false" /> </dynamicTypes> <staticTypes> <add mimeType="text/*" enabled="true" /> <add mimeType="message/*" enabled="true" /> <add mimeType="application/javascript" enabled="true" /> <add mimeType="*/*" enabled="false" /> </staticTypes> </httpCompression>
(برای مطالعه بیشتر این + را ببینید.)
در این مقاله قصد داریم تا به بررسی و پیاده سازی کنترلرهای تو در تو در AngularJs بپردازیم. به این صورت که میتوانیم در یک صفحه یک کنترلر اصلی را در نظر بگیریم و کنترلرهای دیگر را در این کنترلر قرار دهیم. نحوهی ارث بری متغیرها، انقیاد دادهها و مقادیر تعریف شده در $scope، از جمله مواردی است که به آن خواهیم پرداخت. تمامی مواردی که ذکر خواهد شد در قالب یک پروژه قرار داده شده است.
در کدهای پیاده سازی شده این کنترلر به صورت زیر میباشد. همانطور که مشاهده میکنید در کنترلر اول تنها پارامتر firstName و در کنترلر دوم و سوم نیز به ترتیب lastName و middleName اضافه شده اند. همچنین دو تابع utility به نام getFullName در کنترلر دوم و سوم تعریف شده است.
فایل html حاوی این کنترلرها در زیر نمایش داده شده است. همانطور که مشاهده میکنید تمامی کنترلر دوم یعنی secondControllerScope و کنترلر سوم درون firstControllerScope تعریف شده اند. همچنین دو تابع utility با نامهای getFullName نیز در زیر مقدار fullName را باز میگردانند.
با توجه به مقادیر تعریف شده درون هر یک از کنترلرها و با اجرای پروژه نمونه مشاهده میکنیم که اگر یک پارامتر مدل (مانند middleName) را درون کنترلر فرزند به وسیله input fieldها تغییر دهیم، این تغییر به والد منتقل نمیشود. یعنی اگر به عنوان مثال ما lastName را در کنترلر سوم تغییر دهیم دیگر lastName که در کنترلر سوم است تغییر نخواهد کرد. حال اگر همان lastName در کنترلر دوم که در یک فیلد input قرار داده شده تغییر کند، مشاهده میکنیم که در کنترلر سوم نیز مقدار lastName تغییر خواهد کرد.
حال به بررسی جزئی کنترلرهای تودرتو یا همان Nested Controllers میپردازیم. در این پروژه قصد داریم تا با تعریف سه متغییر firstName و lastName و middleName نحوه ارث بری پارامترها را در این کنترلرهای تو در تو بررسی نماییم. ساختار یک صفحه با کنترلرهای تو در تو به صورت زیر میباشد. همانطور که مشاهده میکنید کنترلر دوم و سوم به صورت nested درون کنترلر اول تعریف شده اند.
<body ng-app ="app"> <div ng-controller="firstControllerScope"> <div ng-controller="secondControllerScope"> <div ng-controller="thirdControllerScope"> </div> </div> </div> </body>
var firstControllerScope = function ($scope) { // Initialize the model variables $scope.firstName = "John"; }; var secondControllerScope = function ($scope) { // Initialize the model variables $scope.lastName = "Doe"; // Define utility functions $scope.getFullName = function () { return $scope.firstName + " " + $scope.lastName; }; }; var thirdControllerScope = function ($scope) { // Initialize the model variables $scope.middleName = "Al"; $scope.lastName = "Smith"; // Define utility functions $scope.getFullName = function () { return $scope.firstName + " " + $scope.middleName + " " + $scope.lastName; }; };
<div ng-controller="firstControllerScope"> <h3>First controller</h3> <strong>First name:</strong> {{firstName}}<br /> <br /> <label>first name: <input type="text" ng-model="firstName" /></label><br /> <br /> <div ng-controller="secondControllerScope"> <h3>Second controller (inside First)</h3> <strong>First name (from First):</strong> {{firstName}}<br /> <strong>Last name (new variable):</strong> {{lastName}}<br /> <strong>Full name:</strong> {{getFullName()}}<br /> <br /> <label>first name: <input type="text" ng-model="firstName" /></label><br /> <label>last name: <input type="text" ng-model="lastName" /></label><br /> <br /> <div ng-controller="thirdControllerScope"> <h3>Third controller (inside Second and First)</h3> <strong>First name (from First):</strong> {{firstName}}<br /> <strong>Middle name (new variable):</strong> {{middleName}}<br /> <strong>Last name (from Second):</strong> {{$parent.lastName}}<br /> <strong>Last name (redefined in Third):</strong> {{lastName}}<br /> <strong>Full name (redefined in Third):</strong> {{getFullName()}}<br /> <br /> <label>first name: <input type="text" ng-model="firstName" /></label><br /> <label>middle name: <input type="text" ng-model="middleName" /></label><br /> <label>last name: <input type="text" ng-model="lastName" /></label> </div> </div> </div>
در بخش بعدی نوع دیگری از ارث بری پارامترها و تابعها در کنترلرهای AngularJs را شرح خواهیم داد.
یکی از مواردی که در توسعه وب نقش مهمی دارد، بهینه سازی فایلهای js و css است که با فشرده سازی و کش کردن آنها میتوان سرعت بارگذاری را تا حد چشمگیری افزایش داد. برای درک بهتر، به مثال زیر توجه کنید.
یک پروژه ساده را ایجاد میکنیم و فایلهای CSS و js را مانند شکل زیر، به آن اضافه میکنیم:
طبق تصویر فایلها را به صفحهای که ساختیم اضافه میکنیم:
پروژه را اجرا کرده و توسط افزونهی firebug درخواستهایی را که از سرور شدهاست، بررسی میکنیم. مشاهده خواهید کرد که به ازای هر فایل، یک درخواست به سرور ارسال شده و هیچکدام از فایلها توسط وب سرور فشرده سازی نشدهاند و اطلاعاتی در مورد کش، به هدر آنها اضافه نشده است.
برای رفع این موارد، روشهای گوناگونی وجود دارد که امروز قصد داریم این کار را توسط کتابخانه Combress انجام دهیم !
نصب کتابخانه Combres
شما میتوانید با استفاده از nuget این کتابخانه را به پروژه خود اضافه کنید.
ایجاد فایل تنظیمات
پس از نصب کتابخانه، فایلی با نام combres.xml در فولدر app_data ساخته میشود که تمامی فعالیتهای کتابخانه براساس آن انجام میشود و ساختار آن بصورت زیر است:
<?xml version="1.0" encoding="utf-8" ?> <combres xmlns='urn:combres'> <filters> <filter type="Combres.Filters.FixUrlsInCssFilter, Combres" /> </filters> <resourceSets url="~/combres.axd" defaultDuration="30" defaultVersion="auto" defaultDebugEnabled="false" defaultIgnorePipelineWhenDebug="true" localChangeMonitorInterval="30" remoteChangeMonitorInterval="60"> <resourceSet name="siteCss" type="css"> <resource path="~/content/Site.css" /> <resource path="~/content/anotherCss.css" /> <resource path="~/scripts/yetAnotherCss.css" /> </resourceSet> <resourceSet name="siteJs" type="js"> <resource path="~/scripts/jquery-1.4.4.js" /> <resource path="~/scripts/anotherJs.js" /> <resource path="~/scripts/yetAnotherJs.js" /> </resourceSet> </resourceSets> </combres>
ResourceSet: با استفاده از هر ResourceSet میتوانید آن مجموعه فایل را در یک درخواست از سرور دریافت کنید.
پارامتر url : برای تولید لینک فایلها از آن استفاده میکند.
پارامتر defaultDuration : این عدد به تعداد روزهای پیشفرض برای کش کردن فایلها اشاره میکند.
پارامتر defaultVersion :در صورتی که مقدار آن auto باشد به ازای هر تغییر، آدرس جدیدی برای فایل موردنظر ایجاد میشود.
پارامتر defaultDebugEnabled :با استفاده از آن میتوانید debug mode را مشخص کنید. در صورتیکه مقدار آن auto باشد، این مقدار مستقیما از وبکانفیگ خوانده میشود.
مقادیر پیش فرض برای تمامی ResourceSetها استفاده میشود و در صورت نیاز میتوان این مقادیر را برای هر ResourceSet بازنویسی کرد. فیلترها برای اعمال تغییراتی در فایل js و CSS استفاده میشوند که باید به این شکل معرفی شوند. در قسمت بعد با فیلترها بیشتر آشنا میشویم.
فایل cobmres.xml را به منظور استفاده در پروژه به صورت زیر تغییر میدهیم.
<?xml version="1.0" encoding="utf-8" ?> <combres xmlns='urn:combres'> <filters> <filter type="Combres.Filters.FixUrlsInCssFilter, Combres" /> </filters> <resourceSets url="~/combres.axd" defaultDuration="30" defaultVersion="auto" defaultDebugEnabled="false" defaultIgnorePipelineWhenDebug="true" localChangeMonitorInterval="30" remoteChangeMonitorInterval="60"> <resourceSet name="siteCss" type="css"> <resource path="~/Styles/Site.css" /> </resourceSet> <resourceSet name="siteJs" type="js"> <resource path="~/Scripts/jquery-1.10.2.js" /> <resource path="~/Scripts/jquery-1.10.2.min.js" /> </resourceSet> </resourceSets> </combres>
اگر از نیوگت برای نصب کتابخانه استفاده کرده باشید تغییرات مورد نیاز در فایل وب کانفیگ به صورت خودکار اعمال میشود؛ در غیر اینصورت باید قسمتهای زیر را به آن اضافه کنید.
<configuration> <configSections> <section name="combres" type="Combres.ConfigSectionSetting, Combres, Version=2.2, Culture=neutral, PublicKeyToken=1ca6b37997dd7536" /> </configSections> <system.web> <pages> <namespaces> <add namespace="Combres" /> </namespaces> </pages> </system.web> <combres definitionUrl="~/App_Data/combres.xml" /> <appSettings> <add key="CombresSectionName" value="combres" /> </appSettings> <runtime> <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> <dependentAssembly> <assemblyIdentity name="AjaxMin" publicKeyToken="21ef50ce11b5d80f" culture="neutral" /> <bindingRedirect oldVersion="0.0.0.0-4.84.4790.14405" newVersion="4.84.4790.14405" /> </dependentAssembly> </assemblyBinding> </runtime> </configuration>
حال باید Route مربوط به Combres را به RouteTable اضافه کنیم. در صورتیکه از نیوگت استفاده کرده باشید، کلاسی به فولدر app_start اضافه شده است که با استفاده از WebActivator اینکار را در Application_Start انجام میدهد؛ در غیر اینصورت باید به صورت زیر این کار را انجام دهیم.
protected void Application_Start(object sender, EventArgs e) { RouteTable.Routes.AddCombresRoute("Combres Route"); }
بعد از انجام مراحل قبل، نوبت به آن رسیده است که از لینکهای combres در صفحات خود استفاده کنیم. شیوه استفاده از آن در وب فرم به این صورت است:
<%@ Import Namespace="Combres" %> <head runat="server"> <%= WebExtensions.CombresLink("siteCss") %> <%= WebExtensions.CombresLink("siteJs") %> </head>
و در MVC به صورت زیر میباشد:
<%= Url.CombresLink("siteCss") %> <%= Url.CombresLink("siteJs") %>
در هر دو مورد نام ResourceSet برای تولید لینک به متد CombresLink ارسال میشود. پس از اجرای مجدد برنامه و با استفاده از firebug خواهیم دید که به ازای هر ResourceSet، یک درخواست به سرور ارسال شده است و حجم فایلها به صورت چشمگیری کاهش یافته است و اطلاعات مربوط به کش هم به آن اضافه شده است.
در ادامه میتوانید فایل site.css قبلی و فعلی را مقایسه کنید!
در قسمت بعد با سازوکار combres و روش استفاده از فیلترها، بیشتر آشنا میشویم.
در قسمت قبل، ساختار فرم ثبت اطلاعات کارمندان را تکمیل کردیم. در این قسمت قصد داریم این اطلاعات را در کامپوننت آن توسط data binding دریافت کنیم.
نقش ngModel در data binding
ngModel دایرکتیوی است که وجود آن سبب میشود تا Angular آن المان ورودی خاص را تحت نظر قرار دهد:
در حالت تعریفی فوق، هیچگونه عملیات data binding ایی صورت نمیگیرد؛ اما Angular به علت وجود ngModel، از وجود این فیلد مطلع شدهاست. اما کامپوننت برنامه اطلاعات خاصی را دریافت نخواهد کرد.
برای رفع این مشکل میتوان با data binding یک طرفه شروع کرد:
در اینجا از syntax ویژهی property binding استفاده شده و ngModel داخل [] قرار گرفتهاست و به firstName تنظیم شدهاست. در این حالت Angular در کامپوننت متناظر با این قالب HTML ایی، به دنبال یک خاصیت عمومی به نام firstName میگردد و مقدار اولیهی این فیلد را از آن دریافت میکند.
در حالت data binding یک طرفه، اگر کاربر اطلاعات فیلد firstname را در فرم برنامه تغییر دهد، این اطلاعات به خاصیت عمومی firstName منعکس نخواهد شد.
برای رفع این مشکل (در صورت نیاز)، میتوان از data binding دو طرفه استفاده کرد:
این حالت شبیه به حالت data binding یک طرفه است؛ با این تفاوت که رویدادگردانی ngModelChange نیز به آن اضافه شدهاست. در اینجا event$ به مقدار فیلد تغییر یافته اشاره میکند و آنرا به firstName انتساب میدهد.
البته این حالت دو طرفه، syntax ساده شدهی زیر را که به banana in the box نیز معروف شدهاست (موز همان () است و جعبه به [] اشاره میکند)، نیز میتواند داشته باشد که بیشتر مورد استفاده قرار میگیرد:
تعریف مدل فرم ثبت اطلاعات کارمندان
برای نگهداری اطلاعات فرم کارمندان، کلاس Employee را به ماژول Employee اضافه میکنیم:
با این خروجی:
سپس ساختار این کلاس را به نحو ذیل تکمیل خواهیم کرد که هر کدام از خواص آن، معادل یکی از المانهای فرم است:
TypeScript این امکان را میدهد تا بتوان خواص عمومی را مستقیما در سازندهی کلاس تعریف کرد. بنابراین در اینجا برای نمونه firstName هم یکی از آرگومانهای سازندهی کلاس کارمند است و هم یک خاصیت عمومی تعریف شدهی در آن. به علاوه در اینجا میتوان به این خواص، مقادیر پیش فرضی را نیز انتساب داد تا در حین وهله سازی آن بتوان از تعریف اجباری یک سری از پارامترها صرفنظر کرد.
پس از آن، به فایل employee-register.component.ts مراجعه کرده و وهلهای از کلاس را به صورت یک خاصیت عمومی در اختیار قالب HTML ایی آن که فرم جاری را تشکیل میدهد، قرار میدهیم:
ابتدا کلاس کارمند import شده و سپس وهلهای از آن به نام model، به صورت یک خاصیت عمومی در اختیار قالب آن قرار گرفتهاست.
تغییر قالب فرم ثبت اطلاعات کارمندان برای اتصال به model
در ادامه، مرحله به مرحله قالب فرم جاری را جهت اتصال به شیء model فوق تغییر خواهیم داد:
اتصال به Text boxes
همانطور که مشاهده میکنید، اینبار ngModel خالی قسمت قبل را توسط syntax تکمیلی banana in the box به data binding دو طرفه تغییر دادهایم. به این ترتیب در ابتدای نمایش فرم، این دو فیلد، مقادیر اولیه نام و نام خانوادگی را از شیء model دریافت کرده و نمایش میدهند. به علاوه اگر فرم نیز تغییر کند، این اطلاعات به شیء model و خواص آن نیز منعکس میشوند.
برای بررسی این مورد، در پایان فرم جهت دیباگ data binding، اطلاعاتی را که در مدل داریم و همچنین اطلاعاتی را که Angular در حال نظارت بر آنها است، به صورت json در صفحه درج میکنیم:
برای مثال یکبار [()] را به [] تبدیل کنید و سپس سعی در تغییر مقادیر فرم نمائید. مشاهده میکنید هرچند این اطلاعات تحت نظارت Angular هستند، اما چون data binding به حالت یک طرفه تغییر کردهاست، دیگر انعکاس آنها، در Model مشاهده نمیشوند.
اتصال به Check boxes
روش کار در اینجا نیز همانند قبل است. با استفاده از data binding دو طرفه، مقدار checkbox را به یک خاصیت عمومی boolean انتساب دادهایم و برعکس (زمانیکه فرم برای بار اول نمایش داده میشود، مقدار اولیهی خود را از شیء model دریافت میکند).
اتصال به Radio buttons
روش اتصال به radio buttons نیز بر اساس data binding دو طرفهاست. فقط در اینجا دقیقا یک خاصیت مشخص، به چندین radio button متصل شدهاست و در نهایت در این گروه که بر اساس name هایی یکسان تشکیل شدهاست، یک مقدار انتخاب میشود و مقدار آن از ویژگی value المان متناظر دریافت میگردد.
اتصال به Drop downs
در اینجا نیز ابتدا نامی به این المان انتساب داده شدهاست و سپس توسط data binding دو طرفه، خاصیت متناظری از مدل را به این المان متصل کردهایم یا برعکس؛ زمانیکه این فرم برای اولین بار نمایش داده میشود، مقدار اولیهی این فیلد بر اساس مقدار آن در شیء model تعیین میشود:
نحوهی فراخوانی یک متد در حین data binding دو طرفه
همانطور که در ابتدای بحث نیز عنوان شد، data binding دو طرفه را به نحو دیگری نیز میتوان تعریف کرد:
در اینجا بجای استفادهی از syntax معروف banana in the box، از روش اتصال یک طرفه و سپس دریافت تغییرات از طریق یک رخدادگردان استفاده شدهاست. مزیت این روش امکان دسترسی همزمان به مقدار وارد شدهی توسط کاربر، در کامپوننت متناظر میباشد:
برای مثال در اینجا اگر کاربر حرف اول یک نام را با حروف کوچک وارد کند، توسط این متد به حرف بزرگ تبدیل شده و جایگزین میشود. این جایگزینی نیز بلافاصله در فرم منعکس خواهد شد.
در قسمت بعد مباحث اعتبارسنجی فرمهای مبتنی بر قالبها را بررسی میکنیم.
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: angular-template-driven-forms-lab-03.zip
برای اجرای آن فرض بر این است که پیشتر Angular CLI را نصب کردهاید. سپس از طریق خط فرمان به ریشهی پروژه وارد شده و دستور npm install را صادر کنید تا وابستگیهای آن دریافت و نصب شوند. در آخر با اجرای دستور ng serve -o برنامه ساخته شده و در مرورگر پیش فرض سیستم نمایش داده خواهد شد.
نقش ngModel در data binding
ngModel دایرکتیوی است که وجود آن سبب میشود تا Angular آن المان ورودی خاص را تحت نظر قرار دهد:
<!--no binding --> <input name="firstname" ngModel>
برای رفع این مشکل میتوان با data binding یک طرفه شروع کرد:
<!--one way binding --> <input name="firstname" [ngModel]="firstName">
در حالت data binding یک طرفه، اگر کاربر اطلاعات فیلد firstname را در فرم برنامه تغییر دهد، این اطلاعات به خاصیت عمومی firstName منعکس نخواهد شد.
برای رفع این مشکل (در صورت نیاز)، میتوان از data binding دو طرفه استفاده کرد:
<!--two way binding --> <input name="firstname" [ngModel]="firstName" (ngModelChange)="firstName=$event">
البته این حالت دو طرفه، syntax ساده شدهی زیر را که به banana in the box نیز معروف شدهاست (موز همان () است و جعبه به [] اشاره میکند)، نیز میتواند داشته باشد که بیشتر مورد استفاده قرار میگیرد:
<!--two way binding --> <input name="firstname" [(ngModel)]="firstName">
تعریف مدل فرم ثبت اطلاعات کارمندان
برای نگهداری اطلاعات فرم کارمندان، کلاس Employee را به ماژول Employee اضافه میکنیم:
> ng g cl Employee/Employee
installing class create src\app\Employee\employee.ts
export class Employee { constructor( public firstName: string, public lastName: string, public isFullTime: boolean, public paymentType: string, public primaryLanguage: string ) {} }
پس از آن، به فایل employee-register.component.ts مراجعه کرده و وهلهای از کلاس را به صورت یک خاصیت عمومی در اختیار قالب HTML ایی آن که فرم جاری را تشکیل میدهد، قرار میدهیم:
import { Employee } from "app/employee/employee"; export class EmployeeRegisterComponent implements OnInit { languages = ["Persian", "English", "Spanish", "Other"]; model = new Employee("Vahid", "N", true, "FullTime", "Persian");
تغییر قالب فرم ثبت اطلاعات کارمندان برای اتصال به model
در ادامه، مرحله به مرحله قالب فرم جاری را جهت اتصال به شیء model فوق تغییر خواهیم داد:
اتصال به Text boxes
<form #form="ngForm" novalidate> <div class="form-group"> <label>First Name</label> <input type="text" class="form-control" name="firstName" [(ngModel)]="model.firstName"> </div> <div class="form-group"> <label>Last Name</label> <input type="text" class="form-control" name="lastName" [(ngModel)]="model.lastName"> </div>
برای بررسی این مورد، در پایان فرم جهت دیباگ data binding، اطلاعاتی را که در مدل داریم و همچنین اطلاعاتی را که Angular در حال نظارت بر آنها است، به صورت json در صفحه درج میکنیم:
<button class="btn btn-primary" type="submit">Ok</button> </form> Model: {{ model | json }} <br> Angular: {{ form.value | json }} <br> form.pristine: {{ form.pristine }}
اتصال به Check boxes
<div class="checkbox"> <label> <input type="checkbox" name="is-full-time" [(ngModel)]="model.isFullTime"> Full Time Employee </label> </div>
اتصال به Radio buttons
<label>Payment Type</label> <div class="radio"> <label> <input type="radio" name="paymentType" value="FullTime" checked [(ngModel)]="model.paymentType"> Full Time </label> </div> <div class="radio"> <label> <input type="radio" name="paymentType" value="PartTime" [(ngModel)]="model.paymentType"> Part Time </label> </div>
اتصال به Drop downs
<div class="form-group"> <label>Primary Language</label> <select class="form-control" name="primaryLanguage" [(ngModel)]="model.primaryLanguage"> <option *ngFor="let lang of languages"> {{ lang }} </option> </select> </div>
نحوهی فراخوانی یک متد در حین data binding دو طرفه
همانطور که در ابتدای بحث نیز عنوان شد، data binding دو طرفه را به نحو دیگری نیز میتوان تعریف کرد:
<div class="form-group"> <label>First Name</label> <input type="text" class="form-control" name="firstName" [ngModel]="model.firstName" (ngModelChange)="firstNameToUpperCase($event)"> </div>
firstNameToUpperCase(value: string) { if (value.length > 0) this.model.firstName = value.charAt(0).toUpperCase() + value.slice(1); else this.model.firstName = value; }
در قسمت بعد مباحث اعتبارسنجی فرمهای مبتنی بر قالبها را بررسی میکنیم.
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: angular-template-driven-forms-lab-03.zip
برای اجرای آن فرض بر این است که پیشتر Angular CLI را نصب کردهاید. سپس از طریق خط فرمان به ریشهی پروژه وارد شده و دستور npm install را صادر کنید تا وابستگیهای آن دریافت و نصب شوند. در آخر با اجرای دستور ng serve -o برنامه ساخته شده و در مرورگر پیش فرض سیستم نمایش داده خواهد شد.
بازخوردهای دوره
صفحات مودال در بوت استرپ 3
با فرض اینکه شما در فراخوانی مودال اول مشکلی نداشته باشید . فرض میکنیم شما عکس رو با ajax فراخوانی کرده و در مودال دوم نمایش میدهید . فقط کافیست شما در مودال اول یک دکمه قرا دهید و با اون دکمه مودال دوم رو فراخوانی کنید . یک فایل نمونه پیوست شد.
testmodal-(2).html
من خودم اکثر اوقات با json مقادیر مورد نظر رو در مودال نشون میدم ، چیزی شبیه به این :
رویداد کلیک ردیفهای لیست داخل مودال اول :
testmodal-(2).html
من خودم اکثر اوقات با json مقادیر مورد نظر رو در مودال نشون میدم ، چیزی شبیه به این :
function gettData(code) { $.ajax({ url: '@Url.Action(MVC.X.Y())', data: { code: code }, type: "POST", success: function (data) { $("#title").html(data.Title); $("#user-image").html(data.imagePath); }, error: function (response) { } }); }
$(document).on('click', '.item', function () { getData($(this).data("code")); $("#SecondModal").modal('toggle'); });