longer importantIt is quite a possible situation to have a user navigating to the client application’s page that sends an HTTP request to the server. While our app processing the request, a user can navigate away from that page. In such a case, we want to cancel the HTTP request since the response is no to that user.
طراحی میکرو سرویسها در دات نت
DuoCode is an alternative compiler, powered by Microsoft® Roslyn, and integrated in Visual Studio.
It magically cross-compiles your C# 6.0 code into high-quality readable JavaScript code, enabling rapid development of web applications utilizing the extensive features of the C# language, the Visual Studio IDE, and the .NET Framework base class libraries.
Akita is a state management pattern, built on top of RxJS, which takes the idea of multiple data stores from Flux and the immutable updates from Redux, along with the concept of streaming data, to create the Observable Data Stores model.
Pwned Passwords is the password search feature for Have I Been Pwned (HIBP), a free service that aggregates data breaches and helps people find out if they've been impacted by malicious activity on the web.
همانطور که در قسمت قبل گفته شد، در این قسمت با روش کار jQuery Mobile و pluginهای مربوط به Cordova آشنا خواهیم شد.
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Title</title> <meta name="viewport" content="width=device-width, initial-scale=1">
روال کار jQuery Mobile
از آنجایی که مستندات jQuery Mobile به قدر کافی کامل هست، نیازی نیست تا در مورد تک تک آنها مثال بزنیم و از اصل مطلب دور شویم. در هر مثالی که زده خواهد شد، در صورت استفاده از ویجتی خاص، با آن آشنا خواهیم شد.
لیست کامل اتریبیوتهای -data به همراه مقادیری که میپذیرند
شما میتوانید از امکانات Theme Roller برای شخصی سازی تمهای مورد نیاز استفاده کنید.
Cordova Plugins
از این قسمت http://plugins.cordova.io/#/viewAll و این قسمت http://plugreg.com/plugins میتوانید سراغ پلاگینهای مورد نیاز خود بگردید. برای مثال وارد بخش کانفیگ پروژه شده و از قسمت plugins و تب Core یکسری از پلاگینهایی را که در Cordova گنجانده شده است، مشاهده میکنید. با کلیک بر روی دکمهی Add میتوانید آن را دانلود کرده و از APIهای آن استفاده کنید.
برای مثال پلاگین Notification را به پروژه اضافه میکنم. سپس یک فایل js را با نام custom.js به فولدر scripts در ریشه پروژه اضافه کرده و محتوای فایلهای index.html , custome.js را به شکل زیر در نظر میگیرم:
$(function() { $("#alert").on('tap', function(event) { navigator.notification.alert("اطلاعات ذخیره شد",null, "alert", "تایید"); }); $("#prompt").on('tap', function(event) { navigator.notification.prompt("برای تائید نام خود را وارد کنید", onPrompt, "prompt", "تایید", "لغو"],"نام خود"]); }); function onPrompt(results) { navigator.notification.alert(results.buttonIndex + "\n" + results.input1, null); } $("#confirm").on('tap', function(event) { navigator.notification.confirm("حذف انجام شود؟", onConfirm, "confirm", ["بله", "خیر", "نمیدانم"]); }); function onConfirm(buttonIndex) { navigator.notification.alert(buttonIndex , null); } $("#beep").on('tap', function(event) { navigator.notification.beep(1); }); });
رخداد tap زمانی صادر میشود که کاربر، دکمهی مورد نظر را لمس کند و یکی از رخدادهای jQuery Mobile میباشد. بعد از نصب پلاگین Notification، با استفاده از navigator.notification میتوانید به متدهای مورد نظر که در بالا مشخص است، دسترسی پیدا کنید.
برای آشنایی با این پلاگین میتوانید داکیومنت آن را مطالعه کنید.
در کد بالا با استفاده از متدهای callback توانستهایم اطلاعاتی در مورد نوع عملکرد کاربر با notification ما بدست آوریم.
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>CordovaApp01</title> <meta name="viewport" content="width=device-width, initial-scale=1"/> <!-- CordovaApp01 references --> <link href="css/index.css" rel="stylesheet" /> <link href="jquery.mobile.rtl/css/themes/default/rtl.jquery.mobile-1.4.0.css" rel="stylesheet" /> </head> <body> <div data-role="page" id="page1"> <div data-role="header"> <h2> تست پلاگین Notification </h2> </div> <div data-role="content"> <a href="#page2" data-transition="pop" data-rel="dialog" data-role="button" data-inline="true" data-icon="back">page 2</a> <button data-role="button" id="alert" data-inline="true" >alert</button> <button data-role="button" id="confirm" data-inline="true">confirm</button> <button data-role="button" id="beep" data-inline="true" >beep</button> <button data-role="button" id="prompt" data-inline="true" >prompt</button> </div> <div data-role="footer"> <h2>من فوتر هستم</h2> </div> </div> <div data-role="page" id="page2"> <div data-role="header"> <h1>Header</h1> </div> <div data-role="content"> Content </div> <div data-role="footer"> <h1>Footer</h1> </div> </div> <!-- Cordova reference, this is added to your app when it's built. --> <script src="scripts/jquery-2.1.3.min.js"></script> <script src="cordova.js"></script> <script src="scripts/platformOverrides.js"></script> <script src="scripts/index.js"></script> <script src="jquery.mobile.rtl/js/rtl.jquery.mobile-1.4.0.js"></script> <script src="scripts/custom.js"></script> </body> </html>
در کد بالا 4 تا button دیده میشود که ویژگی data-role آنها مقدار button در نظر گرفته شدهاست تا توسط jQuery Mobile به عنوان button شناخته شوند و استایلهای لازم بر روی آنها اعمال گردد. قرار است طبق کد js ایی که نوشتهایم، با لمس کردن هر کدام از دکمهها، notification هایی نمایش داده شوند.
برای اینکار شبیه ساز YouWave را دانلود کرده و نصب کنید. سپس در قسمت toolbar ویژوال، گزینهی Device را به جای شبیه ساز Ripple انتخاب کنید. نرم افزار youwave را اجرا کنید حال اگر برنامه را اجرا کنید با خطای زیر مواجه خواهید شد:
Error447C:\Users\Administrator\Documents\Visual Studio 2013\Projects\CordovaApp-01\CordovaApp-01\bld\Debug\platforms\android\cordova\node_modules\q\q.js:126CordovaApp-01 Error448throw e;CordovaApp-01 Error449^CordovaApp-01 Error450Error : DEP10201 : Failed to deploy to device, no devices found.CordovaApp-01
adb connect localhost:5558
<a href="#page2" data-transition="pop" data-rel="dialog" data-role="button" data-inline="true" data-icon="back">page 2</a>
در مقالهی بعد، به مباحث Database در Cordova خواهیم پرداخت.
ادامه دارد...
LINQ Extensions Library - Pivot Extensions
The pivot feature enables transforming a collection of objects into a new collection of objects flattening the original hierarchy
: In the following example The Pivot extension method is used to transform a collection as follow
کتاب رایگان Power BI Succinctly
Power BI is a cloud-based business analytics service that gives us a single view of the most critical business data. A tool for creating live dashboards and rich, interactive reports, Power BI allows access to data for monitoring the health of a business. Pierstefano Tucci will take readers through the Power BI interface, query creation, publishing your work, and more in Power BI Succinctly.
- What is Power BI?
- Service
- Desktop
- Mobile Apps
- Solution Template
- Devoloper
- Power BI Embedded
- Power BI Gateway: Data Security
Kendo UI MVVM
- «استفاده از Kendo UI templates »
- «اعتبار سنجی ورودیهای کاربر در Kendo UI»
- «فعال سازی عملیات CRUD در Kendo UI Grid» جهت آشنایی با نحوهی تعریف DataSource ایی که میتواند اطلاعات را ثبت، حذف و یا ویرایش کند.
در این مطلب قصد داریم به یک چنین صفحهای برسیم که در آن در ابتدای نمایش، لیست ثبت نامهای موجود، از سرور دریافت و توسط یک Kendo UI template نمایش داده میشود. سپس امکان ویرایش و حذف هر ردیف، وجود خواهد داشت، به همراه امکان افزودن ردیفهای جدید. در این بین مدیریت نمایش لیست ثبت نامها توسط امکانات binding توکار فریم ورک MVVM مخصوص Kendo UI صورت خواهد گرفت. همچنین کلیه اعمال مرتبط با هر ردیف نیز توسط data binding دو طرفه مدیریت خواهد شد.
Kendo UI MVVM
الگوی MVVM یا Model-View-ViewModel که برای اولین بار جهت کاربردهای WPF و Silverlight معرفی شد، برای ساده سازی اتصال تغییرات کنترلهای برنامه به خواص ViewModel یک View کاربرد دارد. برای مثال با تغییر عنصر انتخابی یک DropDownList در یک View، بلافاصله خاصیت متصل به آن که در ViewModel برنامه تعریف شدهاست، مقدار دهی و به روز خواهد شد. هدف نهایی آن نیز جدا سازی منطق کدهای UI، از کدهای جاوا اسکریپتی سمت کاربر است. برای این منظور کتابخانههایی مانند Knockout.js به صورت اختصاصی برای این کار تهیه شدهاند؛ اما Kendo UI نیز جهت یکپارچگی هرچه تمامتر اجزای آن، دارای یک فریم ورک MVVM توکار نیز میباشد. طراحی آن نیز بسیار شبیه به Knockout.js است؛ اما با سازگاری 100 درصد با کل مجموعه.
پیاده سازی الگوی MVVM از 4 قسمت تشکیل میشود:
- Model که بیانگر خواص متناظر با اشیاء رابط کاربری است.
- View همان رابط کاربری است که به کاربر نمایش داده میشود.
- ViewModel واسطی است بین Model و View. کار آن انتقال دادهها و رویدادها از View به مدل است و در حالت binding دوطرفه، عکس آن نیز صحیح میباشد.
- Declarative data binding جهت رهایی برنامه نویسها از نوشتن کدهای هماهنگ سازی اطلاعات المانهای View و خواص ViewModel کاربرد دارد.
در ادامه این اجزا را با پیاده سازی مثالی که در ابتدای بحث مطرح شد، دنبال میکنیم.
تعریف Model و ViewModel
در سمت سرور، مدل ثبت نام برنامه چنین شکلی را دارد:
namespace KendoUI07.Models { public class Registration { public int Id { set; get; } public string UserName { set; get; } public string CourseName { set; get; } public int Credit { set; get; } public string Email { set; get; } public string Tel { set; get; } } }
<script type="text/javascript"> $(function () { var model = kendo.data.Model.define({ id: "Id", fields: { Id: { type: 'number' }, // leave this set to 0 or undefined, so Kendo knows it is new. UserName: { type: 'string' }, CourseName: { type: 'string' }, Credit: { type: 'number' }, Email: { type: 'string' }, Tel: { type: 'string' } } }); }); </script>
<script type="text/javascript"> $(function () { var viewModel = kendo.observable({ accepted: false, course: new model() }); }); </script>
اتصال ViewModel به View برنامه
تعریف فرم ثبت نام را در اینجا ملاحظه میکنید. فیلدهای مختلف آن بر اساس نکات اعتبارسنجی HTML 5 با ویژگیهای خاص آن، مزین شدهاند. جزئیات آنرا در مطلب «اعتبار سنجی ورودیهای کاربر در Kendo UI» پیشتر بررسی کردهایم.
اگر به تعریف هر فیلد دقت کنید، ویژگی data-bind جدیدی را هم ملاحظه خواهید کرد:
<div id="coursesSection" class="k-rtl k-header"> <div class="box-col"> <form id="myForm" data-role="validator" novalidate="novalidate"> <h3>ثبت نام</h3> <ul> <li> <label for="Id">Id</label> <span id="Id" data-bind="text:course.Id"></span> </li> <li> <label for="UserName">نام</label> <input type="text" id="UserName" name="UserName" class="k-textbox" data-bind="value:course.UserName" required /> </li> <li> <label for="CourseName">دوره</label> <input type="text" dir="ltr" id="CourseName" name="CourseName" required data-bind="value:course.CourseName" /> <span class="k-invalid-msg" data-for="CourseName"></span> </li> <li> <label for="Credit">مبلغ پرداختی</label> <input id="Credit" name="Credit" type="number" min="1000" max="6000" required data-max-msg="عددی بین 1000 و 6000" dir="ltr" data-bind="value:course.Credit" class="k-textbox k-input" /> <span class="k-invalid-msg" data-for="Credit"></span> </li> <li> <label for="Email">پست الکترونیک</label> <input type="email" id="Email" dir="ltr" name="Email" data-bind="value:course.Email" required class="k-textbox" /> </li> <li> <label for="Tel">تلفن</label> <input type="tel" id="Tel" name="Tel" dir="ltr" pattern="\d{8}" required class="k-textbox" data-bind="value:course.Tel" data-pattern-msg="8 رقم" /> </li> <li> <input type="checkbox" name="Accept" data-bind="checked:accepted" required /> شرایط دوره را قبول دارم. <span class="k-invalid-msg" data-for="Accept"></span> </li> <li> <button class="k-button" data-bind="enabled: accepted, click: doSave" type="submit"> ارسال </button> <button class="k-button" data-bind="click: resetModel">از نو</button> </li> </ul> <span id="doneMsg"></span> </form> </div>
<script type="text/javascript"> $(function () { var model = kendo.data.Model.define({ // ... }); var viewModel = kendo.observable({ // ... }); kendo.bind($("#coursesSection"), viewModel); }); </script>
<input type="text" id="UserName" name="UserName" class="k-textbox" data-bind="value:course.UserName" required />
بنابراین تا اینجا به صورت خلاصه، مدلی را توسط متد kendo.data.Model.define، معادل مدل سمت سرور خود ایجاد کردیم. سپس وهلهای از این مدل را به صورت یک خاصیت جدید دلخواهی در ViewModel تعریف شده توسط متد kendo.observable در معرض دید View برنامه قرار دادیم. در ادامه اتصال ViewModel و View، با فراخوانی متد kendo.bind انجام شد. اکنون برای دریافت تغییرات کنترلهای برنامه، تنها کافی است ویژگیهای data-bind ایی را به آنها اضافه کنیم.
در ناحیهی تعریف شده توسط متد kendo.bind، کلیه خواص ViewModel در دسترس هستند. برای مثال اگر به تعریف ViewModel دقت کنید، یک خاصیت دیگر به نام accepted با مقدار false نیز در آن تعریف شدهاست (این خاصیت چون صرفا کاربرد UI داشت، در model برنامه قرار نگرفت). از آن برای اتصال checkbox تعریف شده، به button ارسال اطلاعات، استفاده کردهایم:
<input type="checkbox" name="Accept" data-bind="checked:accepted" required /> <button class="k-button" data-bind="enabled: accepted, click: doSave" type="submit"> ارسال </button>
ارسال دادههای تغییر کردهی ViewModel به سرور
تا اینجا 4 جزء اصلی الگوی MVVM که در ابتدای بحث عنوان شد، تکمیل شدهاند. مدل اطلاعات فرم تعریف گردید. ViewModel ایی که این خواص را به المانهای فرم متصل میکند نیز در ادامه اضافه شدهاست. توسط ویژگیهای data-bind کار Declarative data binding انجام میشود.
در ادامه نیاز است تغییرات ViewModel را به سرور، جهت ثبت، به روز رسانی و حذف نهایی منتقل کرد.
<script type="text/javascript"> $(function () { var model = kendo.data.Model.define({ //... }); var dataSource = new kendo.data.DataSource({ type: 'json', transport: { read: { url: "api/registrations", dataType: "json", contentType: 'application/json; charset=utf-8', type: 'GET' }, create: { url: "api/registrations", contentType: 'application/json; charset=utf-8', type: "POST" }, update: { url: function (course) { return "api/registrations/" + course.Id; }, contentType: 'application/json; charset=utf-8', type: "PUT" }, destroy: { url: function (course) { return "api/registrations/" + course.Id; }, contentType: 'application/json; charset=utf-8', type: "DELETE" }, parameterMap: function (data, type) { // Convert to a JSON string. Without this step your content will be form encoded. return JSON.stringify(data); } }, schema: { model: model }, error: function (e) { alert(e.errorThrown); }, change: function (e) { // فراخوانی در زمان دریافت اطلاعات از سرور و یا تغییرات محلی viewModel.set("coursesDataSourceRows", new kendo.data.ObservableArray(this.view())); } }); var viewModel = kendo.observable({ //... }); kendo.bind($("#coursesSection"), viewModel); dataSource.read(); // دریافت لیست موجود از سرور در آغاز کار }); </script>
متصل کردن DataSource به ViewModel
تا اینجا DataSource ایی جهت کار با سرور تعریف شدهاست؛ اما مشخص نیست که اگر رکوردی اضافه شد، چگونه باید اطلاعات خودش را به روز کند. برای این منظور خواهیم داشت:
<script type="text/javascript"> $(function () { $("#coursesSection").kendoValidator({ // ... }); var model = kendo.data.Model.define({ // ... }); var dataSource = new kendo.data.DataSource({ // ... }); var viewModel = kendo.observable({ accepted: false, course: new model(), doSave: function (e) { e.preventDefault(); console.log("this", this.course); var validator = $("#coursesSection").data("kendoValidator"); if (validator.validate()) { if (this.course.Id == 0) { dataSource.add(this.course); } dataSource.sync(); // push to the server this.set("course", new model()); // reset controls } }, resetModel: function (e) { e.preventDefault(); this.set("course", new model()); } }); kendo.bind($("#coursesSection"), viewModel); dataSource.read(); // دریافت لیست موجود از سرور در آغاز کار }); </script>
در متد doSave، ابتدا بررسی میکنیم آیا اعتبارسنجی فرم با موفقیت انجام شدهاست یا خیر. اگر بله، توسط متد add منبع داده، اطلاعات فرم جاری را توسط شیء course که هم اکنون به تمامی فیلدهای آن متصل است، اضافه میکنیم. در اینجا بررسی شدهاست که آیا Id این اطلاعات صفر است یا خیر. از آنجائیکه از همین متد برای به روز رسانی نیز در ادامه استفاده خواهد شد، در حالت به روز رسانی، Id شیء ثبت شده، از طرف سرور دریافت میگردد. بنابراین غیر صفر بودن این Id به معنای عملیات به روز رسانی است و در این حالت نیازی نیست کار بیشتری را انجام داد؛ زیرا شیء متناظر با آن پیشتر به منبع داده اضافه شدهاست.
استفاده از متد add صرفا به معنای مطلع کردن منبع داده محلی از وجود رکوردی جدید است. برای ارسال این تغییرات به سرور، از متد sync آن میتوان استفاده کرد. متد sync بر اساس متد add یک درخواست POST، بر اساس شیءایی که Id غیر صفر دارد، یک درخواست PUT و با فراخوانی متد remove بر روی منبع داده، یک درخواست DELETE را به سمت سرور ارسال میکند.
متد دلخواه resetModel سبب مقدار دهی مجدد شیء course با یک وهلهی جدید از شیء model میشود. همینقدر برای پاک کردن تمامی کنترلهای صفحه کافی است.
تا اینجا دو متد جدید را در ViewModel برنامه تعریف کردهایم. در مورد نحوهی اتصال آنها به View، به کدهای دو دکمهی موجود در فرم دقت کنید:
<button class="k-button" data-bind="enabled: accepted, click: doSave" type="submit"> ارسال </button> <button class="k-button" data-bind="click: resetModel">از نو</button>
مدیریت سمت سرور ثبت، ویرایش و حذف اطلاعات
در حالت ثبت، متد Post توسط آدرس مشخص شده در قسمت create منبع داده، فراخوانی میگردد. نکتهی مهمی که در اینجا باید به آن دقت داشت، نحوهی بازگشت Id رکورد جدید ثبت شدهاست. اگر این تنظیم صورت نگیرد، Id رکورد جدید را در لیست، مساوی صفر مشاهده خواهید کرد و منبع داده این رکورد را همواره به عنوان یک رکورد جدید، مجددا به سرور ارسال میکند.
using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; using System.Web.Http; using KendoUI07.Models; namespace KendoUI07.Controllers { public class RegistrationsController : ApiController { public HttpResponseMessage Delete(int id) { var item = RegistrationsDataSource.LatestRegistrations.FirstOrDefault(x => x.Id == id); if (item == null) return Request.CreateResponse(HttpStatusCode.NotFound); RegistrationsDataSource.LatestRegistrations.Remove(item); return Request.CreateResponse(HttpStatusCode.OK, item); } public IEnumerable<Registration> Get() { return RegistrationsDataSource.LatestRegistrations; } public HttpResponseMessage Post(Registration registration) { if (!ModelState.IsValid) return Request.CreateResponse(HttpStatusCode.BadRequest); var id = 1; var lastItem = RegistrationsDataSource.LatestRegistrations.LastOrDefault(); if (lastItem != null) { id = lastItem.Id + 1; } registration.Id = id; RegistrationsDataSource.LatestRegistrations.Add(registration); // ارسال آی دی مهم است تا از ارسال رکوردهای تکراری جلوگیری شود return Request.CreateResponse(HttpStatusCode.Created, registration); } [HttpPut] // Add it to fix this error: The requested resource does not support http method 'PUT' public HttpResponseMessage Update(int id, Registration registration) { var item = RegistrationsDataSource.LatestRegistrations .Select( (prod, index) => new { Item = prod, Index = index }) .FirstOrDefault(x => x.Item.Id == id); if (item == null) return Request.CreateResponse(HttpStatusCode.NotFound); if (!ModelState.IsValid || id != registration.Id) return Request.CreateResponse(HttpStatusCode.BadRequest); RegistrationsDataSource.LatestRegistrations[item.Index] = registration; return Request.CreateResponse(HttpStatusCode.OK); } } }
نمایش آنی اطلاعات ثبت شده در یک لیست
ردیفهای اضافه شده به منبع داده را میتوان بلافاصله در همان سمت کلاینت توسط Kendo UI Template که قابلیت کار با ViewModelها را دارد، نمایش داد:
<div id="coursesSection" class="k-rtl k-header"> <div class="box-col"> <form id="myForm" data-role="validator" novalidate="novalidate"> <!--فرم بحث شده در ابتدای مطلب--> </form> </div> <div id="results"> <table class="metrotable"> <thead> <tr> <th>Id</th> <th>نام</th> <th>دوره</th> <th>هزینه</th> <th>ایمیل</th> <th>تلفن</th> <th></th> <th></th> </tr> </thead> <tbody data-template="row-template" data-bind="source: coursesDataSourceRows"></tbody> <tfoot data-template="footer-template" data-bind="source: this"></tfoot> </table> <script id="row-template" type="text/x-kendo-template"> <tr> <td data-bind="text: Id"></td> <td data-bind="text: UserName"></td> <td dir="ltr" data-bind="text: CourseName"></td> <td> #: kendo.toString(get("Credit"), "c0") # </td> <td data-bind="text: Email"></td> <td data-bind="text: Tel"></td> <td><button class="k-button" data-bind="click: deleteCourse">حذف</button></td> <td><button class="k-button" data-bind="click: editCourse">ویرایش</button></td> </tr> </script> <script id="footer-template" type="text/x-kendo-template"> <tr> <td colspan="3"></td> <td> جمع کل: #: kendo.toString(totalPrice(), "c0") # </td> <td colspan="2"></td> <td></td> <td></td> </tr> </script> </div> </div>
<script type="text/javascript"> $(function () { // ... var viewModel = kendo.observable({ accepted: false, course: new model(), coursesDataSourceRows: new kendo.data.ObservableArray([]), doSave: function (e) { // ... }, resetModel: function (e) { // ... }, totalPrice: function () { var sum = 0; $.each(this.get("coursesDataSourceRows"), function (index, item) { sum += item.Credit; }); return sum; }, deleteCourse: function (e) { // the current data item is passed as the "data" field of the event argument var course = e.data; dataSource.remove(course); dataSource.sync(); // push to the server }, editCourse: function(e) { // the current data item is passed as the "data" field of the event argument var course = e.data; this.set("course", course); } }); kendo.bind($("#coursesSection"), viewModel); dataSource.read(); // دریافت لیست موجود از سرور در آغاز کار }); </script>
- ابتدا خاصیت دلخواه coursesDataSourceRows به viewModel اضافه میشود تا در ناحیهی coursesSection در دسترس قرار گیرد.
- سپس اگر به انتهای تعریف DataSource دقت کنید، داریم:
<script type="text/javascript"> $(function () { var dataSource = new kendo.data.DataSource({ //... change: function (e) { // فراخوانی در زمان دریافت اطلاعات از سرور و یا تغییرات محلی viewModel.set("coursesDataSourceRows", new kendo.data.ObservableArray(this.view())); } }); }); </script>
کدهای کامل این مثال را از اینجا میتوانید دریافت کنید:
KendoUI07.zip