<%@ Page Title="Home Page" Language="C#" MasterPageFile="~/Site.master" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="WebAppSingleSubmit._Default" %> <asp:Content ID="HeaderContent" runat="server" ContentPlaceHolderID="HeadContent"> <script type="text/javascript"> $(document).ready(function () { $("input[type=submit]").click(function (e) { if ((typeof (Page_ClientValidate) == 'function') && (Page_ClientValidate() == false)) { return false; } if (!confirm("آیا مطمئن هستید؟")) { return false; } this.disabled = true; this.value = 'در حال پردازش اطلاعات ...'; __doPostBack($(this).attr('name'), ''); }); }); </script> </asp:Content> <asp:Content ID="BodyContent" runat="server" ContentPlaceHolderID="MainContent"> <p> <asp:TextBox ID="TextBox1" runat="server"></asp:TextBox> <asp:RequiredFieldValidator ID="RequiredFieldValidator1" runat="server" ControlToValidate="TextBox1" ErrorMessage="RequiredFieldValidator"></asp:RequiredFieldValidator> <br /> <asp:Button ID="Button1" runat="server" Text="Button" OnClick="Button1_Click" /> </p> </asp:Content>
<script type="text/javascript"> function LaodWordInfo(id) { showProgress(); $.ajax({ type: "Post", url: "test/Info", data: JSON.stringify({ ID: id }), contentType: "application/json; charset=utf-8", dataType: "json", complete: function (xhr, status) { var data = xhr.responseText; if (status === 'error' || !data) { } else { var dialog = $("#dialog"); dialog.html(data); dialog.dialog("open"); } hideProgress(); return false; } } ); } function showProgress() { $('#Progress').css("display", "block"); } function hideProgress() { $('#Progress').css("display", "none"); } $(function () { $("#dialog").dialog({ autoOpen: false, show: "fade", hide: "fade", width: 550, title: "WordInfo", resizable: false }); }); </script>
در این بین ... اتفاقی رخ نمیدهد و کاربر از پیشرفت عملیات آگاه نمیشود. در این مطلب قصد داریم این وضعیت را بهبود دهیم.
افزودن یک 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
روش جاری شباهت زیادی به استفاده از Context در React دارد:
Context provides a way to pass data through the component tree without having to pass props down manually at every level
// parent component providing 'foo' var Provider = { provide: { foo: 'bar' }, // ... }
// child component injecting 'foo' var Child = { inject: ['foo'], created () { console.log(this.foo) // => "bar" } // ... }
Using an injected value as the default for a prop //دریافت میکنیم props در قسمت child را در کامپوننت foo مقدار const Child = { inject: ['foo'], props: { bar: { default () { return this.foo } } } } Using an injected value as data entry //دریافت میکنیم data در قسمت child را در کامپوننت foo مقدار const Child = { inject: ['foo'], data () { return { bar: this.foo } } }
<!DOCTYPE html> <html> <head> <meta http-equiv="content-type" content="text/html; charset=UTF-8"> <script src="https://cdn.jsdelivr.net/npm/vue"></script> <title>Dependency injection</title> </head> <body> <div id="app"> <button @click="counter++">Increment counter</button> <h2>Parent</h2> <p>{{counter}}</p> <div> <h3>Child</h3> <child></child> </div> </div> <script> const Child = { inject: ['counter_in_child'], template: `<div>Counter Child is:{{ counter_in_child }}</div>` }; new Vue ({ el: "#app", components: { Child }, provide() { return { counter_in_child: this.counter }; }, data() { return { counter: 0 }; } }); </script> </body> </html>
<!DOCTYPE html> <html> <head> <meta http-equiv="content-type" content="text/html; charset=UTF-8"> <script src="https://cdn.jsdelivr.net/npm/vue"></script> <title>Dependency injection</title> </head> <body> <div id="app"> <button @click="counter++">Increment counter</button> <h2>Parent</h2> <p>{{counter}}</p> <div> <h3>Child</h3> <child></child> </div> </div> <script> const Child = { inject: ['counter_in_child'], template: `<div>Counter Child is:{{ counter_in_child.counter }}</div>` }; new Vue ({ el: "#app", components: { Child }, provide() { const counter_in_child={}; Object.defineProperty(counter_in_child,'counter',{ enumerable:true, get:()=>this.counter }) return { counter_in_child }; }, data() { return { counter: 0 }; } }); </script> </body> </html>
provide
and inject
are primarily provided for advanced plugin / component library use cases. It is NOT recommended to use them in generic application code برای شروع به نصب و پیکربندی، باید بدانیم به چه چیزهایی احتیاج داریم. قطعا به کتابخانه React نیاز داریم. اما بسته به نوع کدنویسی که میخواهیم در پیش بگیریم، احتمالا به کتابخانههای دیگری هم احتیاج پیدا خواهیم کرد. در قسمت قبل نحوهی ساخت یک تگ HTML، با React آورده شد. دوباره به آن نگاهی بیاندازیم:
var ClickableImage = function (props) { return ( <a href={props.href}> <img src={props.src} /> </a> ) };
React از دو روش برای ساخت تگها استفاده میکند. روش سادهتر همین مثالی است که در بالا آمده؛ یعنی از تگهای HTML را به صورت مستقیم استفاده میکند. روش دیگر، استفاده از زبان جاوااسکریپت به تنهایی است. مثلا تگهای <a> و <img> بالا به صورت زیر نوشته میشوند:
var ClickableImage = function(props) { React.createElement( "a", {href: props.href}, React.createElement( "img", {src: props.src} ) ) };
وقتی تصور کنیم که قرار است یک جدول یا یک فرم را ایجاد کنیم، ارزش روش سادهتر، مشخص میشود. در واقع تگهایی که استفاده شده، واقعا تگهای HTML نیستند؛ چیزی هستند به نام JSX.
JSX
JSX زبان یا روشی است که اجازه میدهد از تگهای مشابه با HTML در جاوااسکریپت استفاده کنیم. به دلیل تفاوتهای مختصری که دارند، گفته شد که این تگها دقیقا HTML نیستند. برای مثال در تگهای HTML خاصیتهای class و for را داریم؛ اما در تگهای JSX باید به ترتیب از className و htmlFor استفاده کنیم. مسئله بعدی این است که اساسا JSX همراه با React ارائه نشده و برای اینکه بتوانیم از JSX استفاده کنیم، نیاز به ابزاری اضافه داریم تا JSX را برای JavaScript و مرورگر ترجمه کند که فیسبوک، Babel را پیشنهاد میدهد. اگر از JSX بدون ابزار مترجم استفاده کنیم با پیام خطای زیر مواجه میشویم.
> Uncaught SyntaxError: Unexpected token
یعنی کاراکتر شروع (>) تگها را تشخیص نمیدهد.
نصب کتابخانهها
این کتابخانهها را میشود به مدلهای مختلفی دریافت و پیکربندی کرد. بسته به نوع پروژه و محیط توسعه و پیکربندیهای خاص هر پروژه، روش کار میتواند متفاوت باشد. هدف اصلی، مروری بر امکانات React است. پس سادهترین روش نصب را برای ادامه کار انتخاب میکنیم. مانند هر کتابخانهی دیگری میشود بطور یکجا React و Babel را از سایتهای اصلی یا Github دانلود و به پروژه اضافه و استفاده کرد؛ مثل jQuery و Bootstrap. اما راه استاندارد و پیشنهاد شده، استفاده از ابزارهای مدیریت بستهها مثل npm است. در قدم اول با فرض بر اینکه VSCode و npm بر روی سیستم نصب هستند، اول یک پوشه خالی را در VSCode به عنوان پروژه باز میکنیم و از منوی View -> Integrated Terminal را انتخاب میکنیم. در ترمینال باز شده دستور نصب زیر را وارد میکنیم.
npm install react react-dom
npm install babel-standalone
نحوه استفاده
فایل index.html را به ریشه پروژه اضافه کنید و کدهای زیر را درون تگ Body قرار دهید:
<div id="reactTestContainer"></div> <script src="node_modules/react/dist/react.min.js"></script> <script src="node_modules/react-dom/dist/react-dom.min.js"></script> <script src="node_modules/babel-standalone/babel.min.js"></script> <script src="react-test.js" type="text/babel"></script>
برای کار با کتابخانه React به دو فایل react.js و react-dom.js نیاز داریم. اولی بخش اصلی کتابخانه و دومی برای ساخت تگها و تبادل با بخش HTML DOM استفاده میشود؛ مثلا اضافه کردن تگهای ساخته شده به HTML. ذکر ویژگی "type="text/babel برای فایلهایی که در آنها از تگهای JSX استفاده شده ضروری است؛ در غیر اینصورت Babel تشخیص نمیدهد که کار ترجمه را برای چه فایلهایی باید انجام دهد. در نهایت قطعه کد زیر را در فایل react-test.js وارد کنید.
var ClickableImage = function (props) { return ( <a href={props.href}> <img src={props.src} /> </a> ) }; ReactDOM.render( <ClickableImage href="http://google.com" src="google.jpg"/>, document.getElementById("reactTestContainer") )
با اجرا کردن پروژه، تصویری قابل کلیک به مقصد گوگل، در تگ <div>، با ویژگی "id=”reactTestContainer ایجاد خواهد شد.
تا به این قسمت متوجه شدیم که کتابخانه React چیست و چطور میشود از آن استفاده کرد. در قسمت بعد نگاهی دقیقتر به کامپوننتهای React خواهیم داشت.
در این مقاله، به نحوهی دریافت مقادیر Query String با استفاده از زبان جاوااسکریپت خواهیم پرداخت. گاها در پروژهها نیاز است تا کاربر را با پارامترهایی به صورت query string، به صفحهای دیگر منتقل کنیم. با این حال به دو صورت میتوان این مقادیر را فراخوانی نمود:
- فراخوانی سمت سرور
- فراخوانی سمت کلاینت
زمانیکه صحبت از کد نویسی سمت کلاینت میشود، گزینهی بهتری بجز JavaScript وجود ندارد؛ که البته بسیار کارا و پر کاربرد است.
برای دریافت query string با زبان جاوااسکریپت میتوان از کد زیر استفاده کرد:
<script> (function () { // we can call getQueryStringByName from anywhere we want var result = getQueryStringByName('id'); alert(result); })(); function getQueryStringByName(name) { name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]"); var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"), results = regex.exec(location.search); return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " ")); } </script>
getQueryStringByName:
با استفاده از این متد میتوان به query string دسترسی یافت. تابع getQueryStringByName حاوی یک ورودی میباشد که باید نام query string در آن قرار گیرد. در مثال بالا نام query string برابر است با language که بعد از اجرای پروژه، مقدار آن به صورت دیالوگ نمایش داده میشود.
برای دانلود پروژه اینجا کلیک کنید.
کلیات پروژه
پیاده سازی
var jsonFormatter = config.Formatters.OfType<JsonMediaTypeFormatter>().First(); jsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
var angularExample = angular.module('angularexample', ["restangular"]) angularExample.config(["RestangularProvider", function (RestangularProvider) { //RestangularProvider.setRestangularFields({ // id: "id" //}); RestangularProvider.setBaseUrl('/api'); }]); angularExample.controller("MainCtrl", ["Restangular", "$scope", function (Restangular, $scope) { var resource = Restangular.all('books'); resource.getList().then(function (response) { $scope.books = response; }); $scope.add = function () { resource.post($scope.newBook).then(function (newResource) { $scope.books.push(newResource); }) } }]);
<!DOCTYPE html> <html ng-app="angularexample" xmlns="http://www.w3.org/1999/xhtml"> <head> <title>Rest Angular Sample</title> <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.1.5/angular.min.js"></script> <script src="http://cdn.jsdelivr.net/underscorejs/1.5.1/underscore-min.js"></script> <script src="/Scripts/restangular.min.js"></script> <script src="/app/app.js"></script> </head> <body> <div ng-controller="MainCtrl"> <div ng-repeat="item in books"> name is: {{item.name}}<br /> change name: <input type="text" ng-model="item.name" /><button type="submit" ng-click="item.put();">update</button> </div> <div> add new: <br /> <input type="text" ng-model="newBook.name" /><button type="submit" ng-click="add()">add</button> </div> </div> </body>
var resource = Restangular.all('books'); resource.getList().then(function (response) { $scope.books = response; });
$scope.add = function () { resource.post($scope.newBook).then(function (newResource) { $scope.books.push(newResource); }) }
<div ng-repeat="item in books"> name is: {{item.name}}<br /> change name: <input type="text" ng-model="item.name" /><button type="submit" ng-click="item.put();">update</button> </div>
سورس پروژه: RestangularSample.rar
پیشنیازهای سمت سرور
- ابتدا یک پروژهی خالی ASP.NET را ایجاد کنید. نوع آن مهم نیست که Web Forms باشد یا MVC.
- سپس قصد داریم مدل کاربران سیستم را توسط یک ASP.NET Web API Controller در اختیار Ember.js قرار دهیم. مباحث پایهای Web API نیز در وب فرمها و MVC یکی است.
مدل سمت سرور برنامه چنین شکلی را دارد:
namespace EmberJS02.Models { public class User { public int Id { set; get; } public string UserName { set; get; } public string Email { set; get; } } }
using System.Collections.Generic; using System.Web.Http; using EmberJS02.Models; namespace EmberJS02.Controllers { public class UsersController : ApiController { // GET api/<controller> public IEnumerable<User> Get() { return UsersDataSource.UsersList; } } }
همچنین فرض بر این است که مسیریابی سمت سرور ذیل را نیز به فایل global.asax.cs، جهت فعال سازی دسترسی به متدهای کنترلر UsersController تعریف کردهاید:
using System; using System.Web.Http; using System.Web.Routing; namespace EmberJS02 { public class Global : System.Web.HttpApplication { protected void Application_Start(object sender, EventArgs e) { RouteTable.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional } ); } } }
پیشنیازهای سمت کاربر
پیشنیازهای سمت کاربر این قسمت با قسمت «تهیهی اولین برنامهی Ember.js» دقیقا یکی است.
ابتدا فایلهای مورد نیاز Ember.js به برنامه اضافه شدهاند:
PM> Install-Package EmberJS
App = Ember.Application.create(); App.IndexRoute = Ember.Route.extend({ setupController:function(controller) { controller.set('content', ['red', 'yellow', 'blue']); } });
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title></title> <script src="Scripts/jquery-2.1.1.js" type="text/javascript"></script> <script src="Scripts/handlebars.js" type="text/javascript"></script> <script src="Scripts/ember.js" type="text/javascript"></script> <script src="Scripts/app.js" type="text/javascript"></script> </head> <body> <script type="text/x-handlebars" data-template-name="application"> <h1>Header</h1> {{outlet}} </script> <script type="text/x-handlebars" data-template-name="index"> Hello, <strong>Welcome to Ember.js</strong>! <ul> {{#each item in content}} <li> {{item}} </li> {{/each}} </ul> </script> </body> </html>
در ادامه قصد داریم به هدر صفحه، دو لینک Home و About را اضافه کنیم؛ به نحوی که لینک Home به مسیریابی index و لینک About به مسیریابی about که صفحهی جدید «دربارهی برنامه» را نمایش میدهد، اشاره کنند.
تعریف صفحهی جدید About
برنامههای Ember.js، برنامههای تک صفحهای وب هستند و صفحات جدید در آنها به صورت یک template جدید تعریف میشوند که نهایتا متناظر با یک مسیریابی مشخص خواهند بود.
به همین جهت ابتدا در فایل app.js مسیریابی about را اضافه خواهیم کرد:
App.Router.map(function() { this.resource('about'); });
بنابراین به صفحهی index.html برنامه مراجعه کرده و صفحهی about را توسط یک قالب جدید تعریف میکنیم:
<script type="text/x-handlebars" data-template-name="about"> <h2>Our about page</h2> </script>
در این حالت اگر برنامه را در حالت معمولی اجرا کنید، خروجی خاصی را مشاهده نخواهید کرد. بنابراین نیاز است تا لینکی را جهت اشاره به این مسیر جدید به صفحه اضافه کنیم:
<script type="text/x-handlebars" data-template-name="application"> <h1>Ember Demo App</h1> <ul class="nav"> <li>{{#linkTo 'index'}}Home{{/linkTo}}</li> <li>{{#linkTo 'about'}}About{{/linkTo}}</li> </ul> {{outlet}} </script>
در اینجا با استفاده از امکان یا directive ویژهای به نام linkTo، لینکهایی به مسیریابیهای index و about اضافه شدهاند. به این ترتیب اگر کاربری برای مثال بر روی لینک About کلیک کند، کتابخانهی Ember.js او را به صورت خودکار به مسیریابی about و سپس نمایش قالب مرتبط با آن (قالب about ایی که پیشتر تعریف کردیم) هدایت خواهد کرد؛ مانند تصویر ذیل:
همانطور که در آدرس صفحه نیز مشخص است، هرچند صفحهی about نمایش داده شدهاست، اما هنوز نیز در همان صفحهی اصلی برنامه قرار داریم. به علاوه در این قسمت جدید، همچنان منوی بالای صفحه نمایان است؛ از این جهت که تعاریف آن به قالب application اضافه شدهاند.
دریافت و نمایش اطلاعات از سرور
اکنون که با نحوهی تعریف یک صفحهی جدید و برپایی سیم کشیهای مرتبط با آن آشنا شدیم، میخواهیم صفحهی دیگری را به نام Users به برنامه اضافه کنیم و در آن لیست کاربران ارائه شده توسط کنترلر Web API سمت سرور ابتدای بحث را نمایش دهیم.
بنابراین ابتدا مسیریابی جدید users را به صفحه اضافه میکنیم تا لیست کاربران، در آدرس users/ قابل دسترسی شود:
App.Router.map(function() { this.resource('about'); this.resource('users'); });
App.UsersLink = Ember.Object.extend({}); App.UsersLink.reopenClass({ findAll: function () { var users = []; $.getJSON('/api/users').then(function(response) { response.forEach(function(item) { users.pushObject(App.UsersLink.create(item)); }); }); return users; } });
پس از اینکه نحوهی دریافت اطلاعات از سرور مشخص شد، باید اطلاعات این مدل را در اختیار مسیریابی Users قرار داد:
App.UsersRoute = Ember.Route.extend({ model: function() { return App.UsersLink.findAll(); } }); App.UsersController = Ember.ObjectController.extend({ customHeader : 'Our Users List' });
همچنین در کنترلری که تعریف شده، صرفا یک خاصیت سفارشی و دلخواه جدید، به نام customHeader برای نمایش در ابتدای صفحه تعریف و مقدار دهی گردیدهاست.
اکنون قالبی که قرار است اطلاعات مدل را نمایش دهد، چنین شکلی را خواهد داشت:
<script type="text/x-handlebars" data-template-name="users"> <h2>{{customHeader}}</h2> <ul> {{#each item in model}} <li> {{item.Id}}-{{item.UserName}} ({{item.Email}}) </li> {{/each}} </ul> </script>
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید:
EmberJS02.zip
قبل از هر چیز در ابتدا Script زیر را اجرا نمایید.که شامل یک جدول به نام FindField ، یک Stored Procedure به نام PROCEDURE_FindField و یک Function به نام FUNCTION_FindField میباشد.
Create Table FindField (ID int, Firstname varchar(255), Lastname varchar(255)) Go CREATE PROCEDURE PROCEDURE_FindField AS SELECT Firstname FROM FindField; GO CREATE FUNCTION FUNCTION_FindField (@id int) RETURNS int WITH EXECUTE AS CALLER AS begin declare @id1 int; set @id1= (Select Max(Firstname) from dbo.FindField) ; RETURN (@id1); END;
SELECT OBJECT_NAME(object_id) as 'Procedure/Function/View Name', definition FROM sys.sql_modules WHERE definition LIKE '%' + 'Firstname' + '%'
همانطور که در شکل مشاهده مینمایید، فیلد Firstnameدر Function و Stored Procedure مورد استفاده واقع شده است.
جهت کسب اطلاعات بیشتر به آدرس زیر مراجعه نمایید:
<link href="admin/editor/contents.css" rel="stylesheet" type="text/css" /> <script src="admin/editor/ckeditor.js" type="text/javascript"></script>
var clientId = "txtPostBody"; var strEditor = strBodyFa = "CKEDITOR.replace( '" + clientId + "',{skin : 'office2003' , language : 'fa' , contentsLangDirection : 'rtl'});"; ClientScript.RegisterStartupScript(GetType(), clientId + "_Editor", strBodyFa, true);
نکته: پوشه مربوط به ادیتور در محلی که در دسترس صفحه باشد باید قرار گیرد که در اکثر موارد قسمت ادمین وب یا اپلیکشن است