مطالب
نمایش خروجی RSS سایت‌های دیگر به کمک jQuery
شاید خیلی از دوستان (مثل گذشته نه چندان دور خودم ) خیلی بیش از اندازه به برنامه نویسی‌های سمت سرور اهمیت  می دهند که این کار باعث از دست دادن ، سرعت و سادگی برنامه نویسی  سمت کلاینت می‌شود.
معمولا ما برای کار با خروجی‌های XML از کد‌های سمت سرور استفاده می‌کنیم ، بدون اینکه از قدرت جی کوئری در این زمینه  اطلاعی داشته باشیم. البته در این مقاله خیلی به پردازش XML  توسط جی کوئری نمی‌پردازیم و کار اصلی را گوگل برای ما انجام می‌دهد.
برای انجام این کار ما ابتدا توسط یکی از کتابخانه‌های گوگل ، خروجی RSS یک وب سایت رامی خوانیم و بعد به کمک jQuery  آن‌ها را نمایش می‌دهیم.
    <script src="jquery-1.8.0.min.js" type="text/javascript"></script>
    <script type="text/javascript" src="https://www.google.com/jsapi"></script>
    <script type="text/javascript">

        google.load("feeds", "1");

        function initializeda() {
            var feed = new google.feeds.Feed("http://www.drupaleasy.ir/rss.xml");
            feed.setNumEntries(5);
            feed.setResultFormat(google.feeds.Feed.JSON_FORMAT);
            feed.load(function (result) {
                if (!result.error) {
                    for (var i = 0; i < result.feed.entries.length; i++) {
                        var entry = result.feed.entries[i];
                        $('#drupaleasy ul').append('<li><a href="' + entry.link + '">' + entry.title + '</a></li>');
                    }
                }
            });
        }

google.setOnLoadCallback(initializeda);
    </script>
توضیحات کد :
ابتدا نیاز داریم کتابخانه گوگل را به صفحه اضافه کنیم. شما می‌توانید مستندات کامل این کتابخانه در این لینک  بخوانید.
<script type="text/javascript" src="https://www.google.com/jsapi"></script>
در ابتدا معرفی می‌کنیم که به کدام امکان این کتابخانه نیاز داریم ، در این جا ما از ورژن "1" تابع feed  استفاده می‌کنیم.بد نیست برای آشنایی بیشتر سری هم به این لینک  بزنید.
google.load("feeds", "1");
ابتدا لینک مورد نظر که باید خروجی از آن گرفته شود را معرفی می‌کنیم و آن را در متغییری قرار می‌دهیم.
var feed = new google.feeds.Feed("http://www.drupaleasy.ir/rss.xml");
تنظیمات دلخواه خودمان را نظیر تعداد پست واکشی شده و نوع خروجی مشخص می‌کنیم. تجربه (شخصی )نشان داده که بهترین نوع خروجی JSON   است.
feed.setNumEntries(5);
feed.setResultFormat(google.feeds.Feed.JSON_FORMAT);
و بعد هم به کمک jQuery  ساختار مورد نظر خودمان را تنظیم می‌کنیم. شما می‌توانید به کمک مستندات ، و سلیقه شخصی خودتان این کار را انجام بدهید ، و در آخر هم تنظیم می‌کنید ، اجرای تابع شما در چه زمانی اتفاق بیافتید.
            feed.load(function (result) {
                if (!result.error) {
                    for (var i = 0; i < result.feed.entries.length; i++) {
                        var entry = result.feed.entries[i];
                        $('#drupaleasy ul').append('<li><a href="' + entry.link + '">' + entry.title + '</a></li>');
                    }
                }
            });
در این جا من خروجی فید را به صورت یک لیست (ul)  تنظیم کردم ، به این صورت که به ازای هر سطر یک li  همراه با تگ a  تولید کرده ام و به ul  مورد نظر append  کردم. 
فایل HTML :
            <div id="drupaleasy" class="feeds">
            <span>DrupalEasy.ir</span>
                <ul>
                </ul>
                <a href="http://drupaleasy.ir">more</a>
            </div>
همچنین خیلی راحت می‌توانید به کمک CSS  استایل‌های دلخواه خودتون رو اعمال کنید.
        .feeds
        {
            float: right;
            background-color: rgba(234, 242, 243, 0.73);
            margin: 5px;
            border-radius: 20px;
            padding: 8px;
            width: 293px;
            height: 217px;
            border: 1px solid #293883;
        }

        #drupaleasy ul
        {
            list-style-image: url("img/drupal.png");
        }
این هم شد خروجی کار  من 

مطالب
React 16x - قسمت 31 - React Hooks - بخش 2 - مقایسه حالت‌های مختلف مدیریت حالت با useState Hook
در قسمت قبل، با useState Hook آشنا شدیم. همچنین چندین مثال را در مورد نحوه‌ی تعریف تکی و یا چندتایی آن در یک کامپوننت تابعی، با انواع و اقسام داده‌های مختلف، بررسی کردیم؛ اما بهتر است از کدام حالت استفاده شود؟ آیا بهتر است به ازای هر خاصیت state، یکبار useState Hook جدیدی را تعریف کنیم و یا بهتر است همانند کامپوننت‌های کلاسی، یک شیء کامل را به همراه چندین خاصیت، به یک تک useState Hook معرفی کنیم؟


پیاده سازی یک فرم لاگین با استفاده از چندین useState Hook

در ابتدا، یک مثال کاربردی‌تر را به کمک useState Hook‌ها پیاده سازی می‌کنیم. در اینجا هر المان فرم را به یک useState Hook مجزا، متصل کرده‌ایم. کدهای کامل این کامپوننت را در ادامه مشاهده می‌کنید:
import React, { useState } from "react";

export default function Login() {
  const [username, setUsername] = useState("");
  const [password, setPassword] = useState("");
  const [user, setUser] = useState(null);

  const handleSubmit = event => {
    event.preventDefault();

    const userData = {
      username,
      password
    };
    setUser(userData);

    setUsername("");
    setPassword("");
  };

  return (
    <>
      <h2 className="mt-3">Login</h2>
      <form onSubmit={handleSubmit}>
        <div className="form-group">
          <label htmlFor="username">Username</label>
          <input
            type="text"
            name="username"
            id="username"
            onChange={event => setUsername(event.target.value)}
            value={username}
            className="form-control"
          />
        </div>
        <div className="form-group">
          <label htmlFor="password">Password</label>
          <input
            type="password"
            name="password"
            id="password"
            onChange={event => setPassword(event.target.value)}
            value={password}
            className="form-control"
          />
        </div>
        <button type="submit">Submit</button>
      </form>

      {user && JSON.stringify(user, null, 2)}
    </>
  );
}
توضیحات:
- اگر دقت کرده باشید، اینبار این کامپوننت تابعی را به صورت متداول ()function Login تعریف کرده‌ایم. مزیت یک چنین تعریفی، امکان export در محل آن می‌باشد:
export default function Login() {
و دیگر برخلاف حالت استفاده‌ی از arrow function‌ها برای تعریف کامپوننت‌های تابعی، نیازی نیست تا این export را جداگانه در این ماژول درج کرد.
به علاوه وجود واژه‌ی default در اینجا سبب می‌شود که برای import آن، بتوان از هر نام دلخواهی استفاده کرد و در اینجا اجباری به استفاده‌ی از نام Login وجود ندارد که نمونه‌ی استفاده‌ی از آن در فایل index.js، می‌تواند به صورت زیر باشد:
import App from "./components/part02/Login";
- همانطور که در قسمت قبل نیز بررسی کردیم، useState Hook‌ها را با هر نوع داده‌ی دلخواهی می‌توان مقدار دهی اولیه کرد؛ برای مثال با یک int و یا یک object. همچنین الزامی هم به تعریف فقط یک useState Hook وجود ندارد و هر قسمتی از state را می‌توان توسط یک useState Hook مجزا، تعریف و مدیریت کرد.
- فرم لاگین تعریف شده، از یک فیلد نام کاربری و یک فیلد کلمه‌ی عبور تشکیل شده‌است.
- اکنون می‌خواهیم اطلاعات دریافت شده‌ی از کاربر را در state کامپوننت جاری منعکس کنیم. به همین جهت، کار با import متد useState شروع می‌شود. سپس به ازای هر فیلد در فرم، یک state مجزا را تعریف می‌کنیم:
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
- اکنون برای به روز رسانی مقادیر درج شده‌ی در state‌های تعریف شده بر اساس اطلاعات وارد شده‌ی توسط کاربر، از رویداد onChange استفاده می‌کنیم؛ برای مثال:
<input type="text" name="username" id="username"
       onChange={event => setUsername(event.target.value)}
       value={username}
        className="form-control"
/>
در اینجا تابع مدیریت کننده‌ی رویداد onChange، به صورت inline تعریف شده‌است. پیشتر اگر با کامپوننت‌های کلاسی می‌خواستیم اینکار را انجام دهیم، نیاز به clone شیء state، دسترسی به خاصیت متناظر با نام فیلد تعریف شده‌ی در آن به صورت پویا، به روز رسانی آن و در آخر به روز رسانی state با مقدار جدید شیء state می‌بود. اما در اینجا نیازی به دانستن نام المان و یا نام خاصیتی نیست.
- پس از به روز رسانی state، می‌خواهیم در حین submit فرم، این اطلاعات را برای مثال به صورت یک شیء، به سمت سرور ارسال کنیم. به همین جهت نیاز است رویداد onSubmit فرم را  مدیریت کرد. در این متد ابتدا از post back معمول آن به سمت سرور جلوگیری می‌شود و سپس بر اساس متغیرهای تعریف شده‌ی در state، یک شیء را ایجاد کرده‌ایم:
  const handleSubmit = event => {
    event.preventDefault();

    const userData = {
      username,
      password
    };
    setUser(userData);

    setUsername("");
    setPassword("");
  };
همچنین چون در پایین فرم نیز می‌خواهیم این اطلاعات را به صورت JSON نمایش دهیم:
{user && JSON.stringify(user, null, 2)}
 یک state مجزا را هم برای این شیء تعریف:
const [user, setUser] = useState(null);
 و در handleSubmit، به روز رسانی کرده‌ایم.

- دو سطر بعدی را که در انتهای handleSubmit مشاهده می‌کنید، روشی است برای خالی کردن المان‌های فرم، پس از ارسال اطلاعات فرم، برای مثال به backend server. البته این حالت فقط برای حالتی نیاز است که فرم قرار نباشد به آدرس دیگری Redirect شود. برای خالی کردن المان‌های فرم، المان‌های آن‌را باید تبدیل به controlled elements کرد که اینکار با مقدار دهی value آن‌ها توسط value={username} صورت گرفته‌است. به این ترتیب محتوای این المان‌ها با اطلاعاتی که در state داریم، قابل کنترل می‌شوند.


پیاده سازی فرم ثبت نام با استفاده از تنها یک useState Hook

مثال دوم این مطلب نیز در مورد مدیریت المان‌های یک فرم توسط useState Hook است؛ با این تفاوت که در اینجا تنها یک شیء، کل state را تشکیل می‌دهد. کدهای کامل این مثال را در ادامه مشاهده می‌کنید:
import React, { useState } from "react";

const initialFormState = {
  username: "",
  email: "",
  password: ""
};

export default function Register() {
  const [form, setForm] = useState(initialFormState);
  const [user, setUser] = useState(null);

  const handleChange = event => {
    setForm({
      ...form,
      [event.target.name]: event.target.value
    });
  };

  const handleSubmit = event => {
    event.preventDefault();

    setUser(form);
    setForm(initialFormState);
  };

  return (
    <>
      <h2 className="mt-3">Register</h2>
      <form onSubmit={handleSubmit}>
        <div className="form-group">
          <label htmlFor="username">Username</label>
          <input
            type="text"
            name="username"
            id="username"
            onChange={handleChange}
            value={form.username}
            className="form-control"
          />
        </div>
        <div className="form-group">
          <label htmlFor="email">Email</label>
          <input
            type="email"
            name="email"
            id="email"
            onChange={handleChange}
            value={form.email}
            className="form-control"
          />
        </div>
        <div className="form-group">
          <label htmlFor="password">Password</label>
          <input
            type="password"
            name="password"
            id="password"
            onChange={handleChange}
            value={form.password}
            className="form-control"
          />
        </div>
        <button type="submit" className="btn btn-primary">
          Submit
        </button>
      </form>

      {user && JSON.stringify(user, null, 2)}
    </>
  );
}
توضیحات:
- فرم ثبت نام فوق از سه فیلد نام کاربری، ایمیل و کلمه‌ی عبور تشکیل شده‌است.
- اینبار نحوه‌ی تشکیل state مرتبط با این سه فیلد را بسیار شبیه به حالت مدیریت state در کامپوننت‌های کلاسی، تعریف کرده‌ایم؛ که تنها با یک تک شیء، انجام می‌شود و نام آن‌را form در نظر گرفته‌ایم:
const [form, setForm] = useState({ username: "",  email: "", password: ""});
- اکنون باید راهی را بیابیم تا این خواص شیء form را بر اساس ورودی‌های کاربر، به روز رسانی کنیم. به همین جهت رویداد onChange این ورودی را به متغیر handleChange که متد منتسب به آن، این تغییرات را ردیابی می‌کند، متصل می‌کنیم:
<input type="text" name="username" id="username"
       onChange={handleChange} value={form.username}
       className="form-control" />
متد رویدادگردان منتسب به handleChange نیز به صورت زیر تعریف می‌شود:
  const handleChange = event => {
    setForm({
      ...form,
      [event.target.name]: event.target.value
    });
  };
این متد بر اساس name المان‌های ورودی عمل می‌کند (در مثال اول این قسمت، نیازی به دانستن نام المان‌ها نبود). زمانیکه یک شیء را به صورت [event.target.name]: event.target.value تعریف می‌کنیم، یعنی قرار است نام خاصیت این شیء را به صورت پویا تعریف کنیم و مقدار آن نیز از target.value شیء رویداد رسیده، تامین می‌شود. سپس این شیء جدید، با فراخوانی متد setForm، سبب به روز رسانی شیء form موجود در state می‌شود.
- علت وجود spread operator تعریف شده‌ی در اینجا یعنی form...، این است که در حالت استفاده‌ی از useState، برخلاف حالت کار با کامپوننت‌های کلاسی، خواص اضافه شده‌ی به state، به شیء نهایی به صورت خودکار اضافه نمی‌شوند و باید کار یکی سازی را توسط spread operator انجام داد. برای مثال فرض کنید که کاربر، فیلد نام کاربری را ابتدا ثبت می‌کند. بنابراین در این لحظه، شیء ارسالی به setForm، فقط دارای خاصیت username خواهد شد. اکنون اگر در ادامه، کاربر فیلد ایمیل را تکمیل کند، اینبار فقط خاصیت ایمیل در این شیء قرار خواهد گرفت (یا مقدار قبلی را به روز رسانی می‌کند) و از سایر خواص صرفنظر می‌شود؛ مگر اینکه توسط spread operator، سایر خواص پیشین موجود در شیء form را نیز در اینجا لحاظ کنیم، تا اطلاعاتی را از دست نداده باشیم.
بنابراین به صورت خلاصه در روش سنتی کار با کامپوننت‌های کلاسی، فراخوانی متد this.setState کار merge خواص را انجام می‌دهد؛ اما در اینجا فقط کار replace صورت می‌گیرد و باید کار merge خواص یک شیء را به صورت دستی و توسط یک spread operator انجام دهیم. البته در قسمت قبل چون تمام خواص شیء تعریف شده‌ی در state را با هم به روز رسانی می‌کردیم:
    setMousePosition({
      x: event.pageX,
      y: event.pageY
    });
نیازی به تعریف spread operator نبود؛ اما در مثال جاری، هربار فقط یک خاصیت به روز رسانی می‌شود.

- سایر فیلدهای فرم نیز به همین روش onChange={handleChange}، به متد رویدادگردان فوق متصل می‌شوند.
- در پایان برای مدیریت رخ‌داد ارسال فرم، handleSubmit را به صورت زیر تعریف کرده‌ایم:
  const handleSubmit = event => {
    event.preventDefault();

    setUser(form);
    setForm(initialFormState);
  };
در اینجا برخلاف مثال اول، دیگر نیازی به تشکیل دستی یک شیء جدید برای ارسال به سرور وجود ندارد و هم اکنون اطلاعات کل شیء form، در اختیار برنامه است.
- همچنین چون در پایین فرم نیز می‌خواهیم این اطلاعات را به صورت JSON نمایش دهیم:
{user && JSON.stringify(user, null, 2)}
 یک state مجزا را هم برای این شیء تعریف:
const [user, setUser] = useState(null);
 و در handleSubmit، آن‌را با فراخوانی متد setUser، به روز رسانی کرده‌ایم.
- برای پاک کردن المان‌های فرم، پس از submit آن، ابتدا نیاز است این المان‌ها را تبدیل به controlled elements کرد که اینکار با مقدار دهی value آن‌ها توسط برای مثال  value={form.username} صورت گرفته‌است. به این ترتیب محتوای این المان‌ها با اطلاعاتی که در state داریم، قابل کنترل می‌شوند. اکنون اگر setForm را با یک شیء خالی مقدار دهی کنیم، به صورت خودکار المان‌های فرم را پاک می‌کند. برای اینکار بجای تعریف شیء موجود در state به صورت inline:
const [form, setForm] = useState({ username: "",  email: "", password: ""});
می‌توان آن‌را خارج از تابع کامپوننت قرار داد:
const initialFormState = {
  username: "",
  email: "",
  password: ""
};

export default function Register() {
  const [form, setForm] = useState(initialFormState);
و سپس آن‌را به عنوان مقدار اولیه، به صورت setForm(initialFormState)، فراخوانی کرد؛ تا سبب پاک شدن المان‌های فرم شود.


مقایسه‌ی روش‌های مختلف مدیریت state توسط useState Hook

همانطور که مشاهده کردید، با useState Hook، به انعطاف پذیری بیشتری برای مدیریت حالت، نسبت به روش سنتی کامپوننت‌های کلاسی رسیده‌ایم. در حالت تعریف یک useState به ازای هر فیلد، روش تعریف رویدادگردان‌ها و همچنین تبدیل المان‌ها به المان‌های کنترل شده، نسبت به روش تعریف تنها یک useState به ازای کل فرم، ساده‌تر و قابل درک‌تر است. اما زمانیکه نیاز به پاک کردن المان‌های فرم باشد، روش کار کردن با یک تک شیء، ساده‌تر است. درکل بهتر است برای خواص غیرمرتبط state، به ازای هر کدام، یک useState را تعریف کرد و برای یک فرم، همان روش قرار دادن اطلاعات تمام المان‌ها در یک شیء، برای کار با فرم‌های طولانی‌تر، سریع‌تر و قابلیت مدیریت ساده‌تری را به همراه دارد.

کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: sample-30-part-02.zip
مطالب
بررسی چند کتابخانه آپلود با پشتیبانی از DragDrop
برای یکی از پروژه‌ها نیاز به یک آپلودر داشتم که قابلیت  Drag&Drop را نیز داشته باشد و در ضمن پیاده سازی آسانی هم داشته باشد. در این بین به تعدادی از کتابخانه‌های جی کوئری می‌پردازیم.
FileDrop
اولین کتابخانه‌ای که با آن آشنا شدم و از آن استفاده کردم، کتابخانه‌ی FileDrop است که بسیار ساده و در عین حال قابلیت‌های خوبی را می‌دهد و از فناوری Filereader  (+) در Html5 برای اینکار استفاده می‌کند. مرورگرهای کروم، فایرفاکس 3.6 به بعد، IE10 به بعد و Opera 12 به بعد از آن پشتیبانی می‌کنند.
فایل‌های مورد نیاز را از اینجا دانلود کنید . فایل اسکریت آن را ابتدا صدا بزنید:
 <script src="~/scripts/jquery.filedrop.js" type="text/javascript"></script>
سپس المان‌های زیر را به کد HTML خود اضافه کنید:
 <div id="dropZone">فایل برنامه را به داخل این کادر بکشانید</div>
        <br>
        فایل یا فایل‌های آپلود شده:
        <ul id="uploadResult"></ul>
تگ اول، محلی است که فایل‌ها به سمت آن درگ و روی آن دراپ می‌شوند که از این به بعد به آن محل آپلود می‌گوییم. المان بعدی جهت گزارش فایل‌هایی است که آپلود شده‌اند. با آپلود شدن هر تعداد فایل، اسم آن به لیست اضافه می‌گردد.
کدهای css زیر را هم به صفحه اضافه کنید تا محل آپلود زیباتر شود:
        .files {
            min-height: 42px;
            background: #CCC none repeat scroll 0% 0%;
            border-top: 1px solid #FFF;
            margin: 11px 0px;
            padding: 11px 13px;
            border-radius: 6px;
        }

        #dropZone.mouse-over {
            background-color: #1d4257;
        }

کد جی کوئری زیر را به صفحه اضافه کنید:
      $('#dropZone').filedrop({
                url: uploadAddress,
                paramname: 'files',
                maxFiles: 1,
                dragOver: function() {
                    $('#dropZone').addClass('mouse-over');
                },
                dragLeave: function() {
                    $('#dropZone').removeClass('mouse-over');
                },
                drop: function() {
                    $('#dropZone').removeClass('mouse-over');
                },
                afterAll: function() {
                    $('#dropZone').html('آپلود با موفقیت انجام شد');
                },
                uploadFinished: function(i, file, response, time) {
                    $('#uploadResult').append('<li>' + file.name + '</li>');
                }
            });
ابتدا پلاگین جی کوئری را روی تگ مربوطه اعمال می‌کنیم و سپس پارامترها را با مشخصات زیر اعمال می‌کنیم:
Url  آدرسی که قرار است فایل‌ها به آن سمت ارسال شوند. 
 Paramname  در سمت سرور باید فایل‌ها را با استفاده از این نام پارامتر دریافت  کنید.
 maxFiles  تعداد فایلهایی که میتوان با درگ و دراپ کردن روی آن به دست آورد. در بالا به یک فایل محدود شده است.
 dragOver  این رویداد زمانی اجرا خواهد شد که اشاره گر با حالت درگ کرده فایل‌ها را به محل آپلود آورده است.
 dragLeave  موقعی که ماوس از محل آپلود خارج می‌شود
 drop  موقعی که شما فایل‌ها را روی محل  آپلود رها می‌کنید.
 afterAll  بعد از اینکه همه کارها تمام شد اجرا می‌شود.(آخرین رویداد)
 uploadFinished  کار آپلود به پایان رسیده است. در مثال بالا پس از پایان آپلود، نام فایل آپلود شده را به کاربر نشان داده‌ایم.

نحوه‌ی دریافت آن در سمت سرور, در یک اکشن متد به صورت زیر است:

[HttpPost]
        public virtual ActionResult UpdateApp(IEnumerable<HttpPostedFileBase>files)
        {
            foreach (HttpPostedFileBase file in files)
            {
                string filePath = Path.Combine(TempPath, file.FileName);
                file.SaveAs(filePath);
            }

            return Json(new {state = "success", message = "با موفقیت عملیات ارسال فایل انجام شد"}, JsonRequestBehavior.AllowGet);
        }

در اکشن متد بالا ما فایل‌ها را از طریق نام پارامتر files که مشخص کرده بودیم، به عنوان یک لیست شمارشی دریافت می‌کنیم.  کدها بالا برای ساده‌ترین راه اندازی ممکن کفایت می‌کنند.

این موارد از اصلی‌ترین‌ها هستند که به کار می‌آیند. به غیر این‌ها یک سری خصوصیات اضافه‌تری هم برای آن وجود دارد.


fallback_id 
 اگر دوست دارید این آپلودر را نیر به یک آپلودر معمولی اتصال دهید از این شناسه استفاده کنید.
 withCredentials   با استفاده از کوکی‌ها یک درخواست cross-origin ایجاد می‌کند.
 data  اگر دوست دارید به همراه فایل‌ها اطلاعات دیگری هم به همراه آن ارسال و پست شوند از این طریق اقدام نمایید. می‌تواند در قالب یک متغیر باشد یا خروجی یک تابع.
data: {
        param1: 'value1',           
        param2: function(){
            return calculated_data; 
        }
 headers برای ارسال مقدار اضافه‌تر در هدر درخواست به کار میرود و صدا زدن آن همانند کد data می‌باشد. 
 error   در صورتیکه در فرایند آپلود خطایی رخ دهد، اجرا می‌گردد. نحوه‌ی کدنویسی آن و بررسی خطاهای آن به شرح زیر است:
error: function(err, file) {
        switch(err) {
            case 'BrowserNotSupported':
                alert('مرورگر از این فناوری پشتیبانی نمی‌کند')
                break;
            case 'TooManyFiles':
                // قصد آپلود همزمان فایل‌های بیشتری از حد مجاز تعیین شده دارید
                break;
            case 'FileTooLarge':
                //حداقل حجم یکی از فایل‌ها از حجم مجاز تعیین شده بیشتر است
                //برای دسترسی به نام آن فایل از کد زیر استفاده کنید
              //file.name
                break;
            case 'FileTypeNotAllowed':
                // نوع حداقل یکی از فایل‌ها با نوع‌ها مشخص شده ما یکی نیست
                break;
            case 'FileExtensionNotAllowed':
                // پسوند حداقل یکی از فایل‌ها مورد تایید نیست
                break;
            default:
                break;
        }
    }
 allowedfiletypes   نوع فایل‌های مجاز را تعیین می‌کند:
allowedfiletypes: 
['image/jpeg','image/png','image/gif']
 allowedfileextensions   پسوند فایل هایی که برای آپلود مجاز هستند را معرفی می‌کند.
allowedfileextensions:
 ['.apk','.jar']
 maxfilesize   حداکثر حجم مجاز برای هر فایل که به مگابایت بیان می‌شود.
 docOver   این رویداد زمانی اجرا می‌شود که فایل‌های درگ شده شما وارد محیط یا پنجره مرورگر می‌شود.
 uploadStarted 
 این رویداد زمانی اجرا میگردد که فرایند آپلود هر فایل به طور جداگانه در حال آغاز شدن است:
متغیر i در کد زیر شامل اندیس فایلی است که آپلودش آغاز شده است و این اندیس از صفر آغاز می‌شود.
متغیر file دسترسی شما را به اطلاعات یک فایل باز میکند مانند نام فایل.
متغیر len تعداد فایل هایی را که کاربر در محل آپلود رها کرده است، باز میگرداند.
function(i, file, len){

    },
uploadFinished 
با اتمام آپلود هر فایل، این رویداد فراخوانی می‌گردد. دو پارامتر اول آن، همانند سابق هستند. پارامتر response خروجی json ایی را که در سمت سرور برگرداندیم، به ما باز می‌گرداند. پارامتر بعدی، زمانی را که برای آپلود طول کشیده است، بر می‌گرداند.
 function(i, file, response, time) {
    }
progressUpdated 
این رویداد برای نمایش پیشرفت یک آپلود مناسب است که آخرین پارامتر آن یک عدد صحیح از پیشرفت فایل را بر می‌گرداند.
function(i, file, progress) {
    },
globalProgressUpdated 
این رویداد میزان پیشرفت کلیه فایل‌ها را به درصد باز می‌گرداند:
function(progress) {
        $('#progress div')
.width(progress+"%"); }
speedUpdated 
سرعت آپلود هر فایل را با کیلوبیت بر ثانیه مشخص می‌کند.
function(i, file, speed) {
    }
rename
در صورتی که قصد تغییر نام فایل ارسالی را دارید می‌توانید از این رویداد استفاده کنید. پارامتر name، نام اصلی فایل را بر می‌گرداند که می‌توانید آن را دستکاری کنید و نام جدیدی را به عنوان خروجی برگردانید. نمونه کاربردی از این رویداد
 rename: function(name) {
    }

beforeEach 
این رویداد قبل از آپلود هر فایل آغاز می‌گردد و برگرداندن مقدار false در آن باعث جلوگیری و کنسل شدن آپلود آن فایل می‌گردد.
function(file) {
    }

beforeSend 
پارامترهای اولی تکراری هستند ولی آخرین پارامتر یک تابع done را می‌توان به آن پاس کرد که قبل از اجرای کل عملیات آپلود صدا زده می‌شود.
 function(file, i, done) {
    }
رویدادی به اسم queuefiles هم هست تعداد فایل‌هایی را که میتوانند به طور موازی و همزمان آپلود گردند، مشخص می‌کند. ولی دراین حالت maxfiles مورد استفاده قرار نمی‌گیرد. جهت بررسی یک مثال عملی و همچنین کدهای سمت سرور در PHP میتوانید از این آموزش استفاده کنید.
با تستی که به صورت لوکال رو آن انجام دادم به نظر نمی‌رسد برای فایل‌های با حجم متوسط به بالا مناسب باشد و برای فایل‌های با حجم کم مناسب می‌باشد. یک فایل 8 مگابایتی در حالت لوکال 9 ثانیه آپلود آن زمان برد و برای فایل‌های بزرگتر، فایرفاکس دیالوگ Stop Script را نشان داد.

PlUpload
این کتابخانه متن باز هم بسیار کارآمد و ساده و قابل انعطاف است و مثالهای آماده زیادی دارد. سایت سابسن هم در بخش آپلود زیرنویس‌ها از این کتابخانه استفاده می‌کند. از آنجا که آموزش این کتابخانه در سایت جاری آمده است از ذکر نکات بیشتر در مورد آن خودداری می‌نماییم.

Bootstrap FileStyle
اگر از قالب بوت استراپ استفاده می‌کنید و دوست دارید روی المان input file  قدیمی، ولی به شکلی مدرن کار کنید این کتابخانه هم فراموش نشود.

DropZoneJS
این کتابخانه به نسبت DropFile امکانات بیشتری را دارد و در سایت اختصاصی آن مثال‌ها و مستندات خوبی قرار گرفته است. در ساده‌ترین حالت آن ابتدا فایل کتابخانه  را صدا زده و سپس تگ فرم را به آن نسبت دهید:
<script src="https://rawgit.com/enyo/dropzone/master/dist/dropzone.js"></script>

<form action="/upload-target" class="dropzone"></form>
ولی اگر بخواهید آن را به سمت سرور ارسال کنید و  از آنجا آن را کنترل کنید، کد فرم را به شکل زیر تغییر دهید:
ابتدا بسته‌ی نیوگت آن را صدا بزنید:
Install-Package dropzone

با نصب این کتابخانه یک سری فایل CSS هم به سیستم اضافه می‌شود که می‌توانید برای استایل دهی هر چه بیشتر از آن بهره ببرید. کد فرم را به شکل زیر تغییر دهید:
    <form action="~/Home/SaveUploadedFile" method="post" enctype="multipart/form-data" class="dropzone" id="dropzoneForm" style="width: 50px; background: none; border: none;">
        <div class="fallback">
            <input name="file" type="file" multiple />
            <input type="submit" value="Upload" />
        </div>
    </form>
تگی که با کلاس fallback مزین شده است موقعی به کار می‌آید که مرورگر از این کتابخانه پشتیبانی نکرده و مجبور به نمایش یک آپلود معمولی می‌شویم.
با استفاده از کدنویسی هم می‌توان یک المان را به یک آپلودر تبدیل کرد:
var myDropzone = new Dropzone("div#myId", { url: "/file/post"});

//============ OR ====================
$("div#myId").dropzone({ url: "/file/post" });
همانطور که می‌بینید الزامی برای اینکه از یک تگ فرم استفاده کنید ندارد.
برای کانفیگ آپلودرهایی که از طریف المانهای Html ایجاد می‌شوند، می‌توان از کد زیر استفاده کرد و یک تنظیم عمومی برای تمامی آپلودرهای html آن صفحه ایجاد کرد.
Dropzone.options.myId= {
  paramName: "file", //نام پارامتری که فایل از طریق آن انتقال می‌بابد
  maxFilesize: 2, // MB
  accept: function(file, done) {
    if (file.name == "justinbieber.jpg") {
      done("Naha, you don't.");
    }
    else { done(); }
  }
};
تابع بالا یک آرگومان از نوع file را برگردانده و اگر این تابع، تابع done را با پارامتری رشته‌ای صدا بزند، عملیات آپلود آن فایل کنسل شده و پیام خطایی را نمایش می‌دهد و در صورتیکه بدون پارامتر صدا زده شود، عمل آپلود بدون مشکل انجام می‌شود.
ازآنجا که این کتابخانه از تنظیمات وسیعی استفاده می‌کند و از حوصله‌ی این مقاله خارج است، بهتر هست که صفحه‌ی مستندات آن را که کامل هم هست، مطالعه بفرمایید. از سری قابلیت‌هایی که پشتیبانی می‌کند: موارد پوشش داده شده در FileDrop، ساخت layout، ایجاد صف، متد حذف و اضافه و از این قبیل، ایجاد تصویر تمبر مانند و ...

یک نکته تکمیلی در مورد آپلود: در ASP.net به طور پیش فرض نهایت حجم فایل آپلودی 4 مگابایتی تعیین شده است که میتوانید آن را از طریق web.config تغییر دهید:
<configuration>
    <system.web>
        <httpRuntime maxRequestLength="1048576" />
    </system.web>
</configuration>
برای IIS 7 به بعد هم از تکه کد زیر استفاده کنید:
<system.webServer>
   <security>
      <requestFiltering>
         <requestLimits maxAllowedContentLength="1073741824" />
      </requestFiltering>
   </security>
 </system.webServer>
در هر دو کد بالا نهایت حجم بر روی یک گیگابایت تعیین شده است که maxRequestLength به صورت کیلوبایت و maxAllowContentLength به صورت بایت تعیین شده است. توصیه می‌شود هر دو شکل آن را وارد کنید. به خصوص که IIS Express از کد ابتدایی استفاده می‌کند و بخواهید نتیجه‌ی آن را در تست‌ها ببینید.

مطالب
نوشتن افزونه برای مرورگرها: قسمت اول : کروم
افزونه چیست؟
افزونه‌ها جزء مهمترین قسمت‌های یک مرورگر توسعه پذیر به شمار می‌آیند. افزونه‌ها سعی دارند تا قابلیت هایی را به مرورگر شما اضافه کنند. افزونه‌ها از آخرین فناوری‌های html,CSS و جاوااسکریپت تا به آنجایی که مرورگر آن‌ها را پشتیبانی کند، استفاده می‌کنند.
در این سری سعی خواهیم کرد برای هر مرورگر شناخته شده، یک افزونه ایجاد کنیم و ابتدا از آنجا که خودم از کروم استفاده می‌کنم، اولین افزونه را برای کروم خواهم نوشت.

این افزونه قرار است چه کاری انجام دهد؟
کاری که برای این افزونه تدارک دیده‌ام این است: موقعی‌که سایت dotnettips.info به روز شد مرا آگاه کند. این آگاه سازی را از طریق یک نوتیفیکیشن به اطلاع کاربر میرسانیم. صفحه تنظیمات این افزونه شامل گزینه‌های "آخرین مطالب"،"نظرات آخرین مطالب"،"آخرین اشتراک ها"و"آخرین نظرات اشتراک ها" خواهد بود که به طور پیش فرض تنها گزینه اول فعال خواهد بود و همچنین یک گزینه نیز برای وارد کردن یک عدد صحیح جهت اینکه به افزونه بگوییم هر چند دقیقه یکبار سایت را چک کن. چک کردن سایت هم از طریق فید RSS صورت می‌گیرد.

فایل manifest.json
این فایل برای ذخیره سازی اطلاعاتی در مورد افزونه به کار می‌رود که شامل نام افزونه، توضیح کوتاه در مورد افزونه و ورژن و ... به کار می‌رود که همه این اطلاعات در قالب یا فرمت json نوشته می‌شوند و در بالاترین حد استفاده برای تعریف اهداف افزونه و اعطای مجوز به افزونه از آن استفاده می‌کنیم. این فایل بخش‌های زیر را در یک افزونه تعریف می‌کند که به مرور با آن آشنا می‌شویم.


کد زیر را در فایل manifest.json می‌نویسیم:
{
  "manifest_version": 2,

  "name": "Dotnettips Updater",
  "description": "This extension keeps you updated on current activities on dotnettips.info",
  "version": "1.0",
  "icons": { "16": "icon.png",
           "48": "icon.png",
          "128": "icon.png" },

  "browser_action": {
    "default_icon": "icon.png",
    "default_popup": "popup.html"
  },
  "permissions": [
    "activeTab",
    "https://www.dntips.ir"
  ]
}
اطلاعات اولیه شامل نام و توضیح و ورژن افزونه است. ورژن برنامه برای به روزآوری افزونه بسیار مهم است. موقعی که ورژن جدیدی از افزونه ارائه شود، گوگل وب استور اعلان آپدیت جدیدی را برای افزونه میکند. آیکن قسمت‌های مختلف افزونه هم با icons مشخص می‌شود که در سه اندازه باید ارائه شوند و البته اگه اندازه آن نباشد scale می‌شود. قسمت بعدی تعریف UI برنامه هست که گوگل کروم، به آن Browser Action می‌گوید. در اینجا یک آیکن و همچنین یک صفحه اختصاصی برای تنظیمات افزونه معرفی می‌کنیم. این آیکن کنار نوار آدرس نمایش داده می‌شود و صفحه popup موقعی نشان داده می‌شود که کاربر روی آن کلیک می‌کند. آیکن‌ها برای browser action در دو اندازه 19 و 38 پیکسلی هستند و در صورتی که تنها یک آیکن تعریف شود، به صورت خودکار عمل scale و تغییر اندازه صورت می‌گیرد. برای تعیین عکس برای هر اندازه می‌توانید کد را به صورت زیر بنویسید:
"default_icon": {                    // optional
            "19": "images/icon19.png",           // optional
            "38": "images/icon38.png"            // optional
          }
قسمت popup برای نمایش تنظیمات به کار می‌رود و درست کردن این صفحه همانند صفحه همیشگی html هست و خروجی آن روی پنجره popup افزونه رندر خواهد شد.
گزینه default_title نیز یکی از دیگر خصیصه‌های مهم و پرکاربرد این قسمت هست که متن tooltip می‌باشد و موقعی که که کاربر، اشاره‌گر را روی آیکن ببرد نمایش داده می‌شود و در صورتی که نوشته نشود، کروم نام افزونه را نمایش می‌دهد؛ برای همین ما هم چیزی ننوشتیم.

صفحات پس‌زمینه
اگر بخواهید برای صفحه popup کد جاوااسکریت بنویسید یا از jquery استفاده کنید، مانند هر صفحه‌ی وبی که درست می‌کنید آن را کنار فایل popup قرار داده و در popup آنها را صدا کرده و از آن‌ها استفاده کنید. ولی برای پردازش هایی که نیاز به UI وجود ندارد، می‌توان از صفحات پس زمینه استفاده کرد. در این حالت ما دو نوع صفحه داریم:
  1. صفحات مصر یا Persistent Page
  2. صفحات رویدادگرا یا Events Pages
اولین نوع صفحه، همواره فعال و در حال اجراست و دومی موقعی فعال می‌شود که به استفاده از آن نیاز است. گوگل توصیه می‌کند که  تا جای ممکن از نوع دوم استفاده شود تا  مقدار حافظه مصرفی  حفظ شود  و کارآیی مروگر بهبود بخشیده شود. کد زیر یک صفحه پس زمینه را از نوع رویدادگرا می‌سازد. به وضوح روشن است در صورتی که خاصیت Persistent با true مقداردهی شود، این صفحه مصرانه در تمام وقت باز بودن مرورگر، فعال خواهد بود:
"background": {
    "scripts": ["background.js"],
    "persistent": false
}

Content Script یا اسکریپت محتوا
در صورتی که بخواهید با هر صفحه‌ای که باز یا رفرش می‌شود، به DOM آن دسترسی پیدا کنید، از این خصوصیت استفاده کنید. در کد زیر برای پردازش اطلاعات DOM از فایل جاوااسکریپت بهره برده و در قسمت matches می‌گویید که چه صفحاتی باید از این کد استفاده کنند که در اینجا از پروتکل‌های HTTP استفاده میشود و اگر مثلا نوع FTP یا file صدا زده شود کد مورد نظر اجرا نخواهد شد. در مورد اینکه matches چگونه کار می‌کند و چگونه می‌توان آن را نوشت، از این صفحه استفاده کنید.
"content_scripts": [
    {
        "matches": ["http://*/*", "https://*/*"],
        "js": ["content.js"]
    }
]

آغاز کدنویسی (رابط‌های کاربری)


اجازه دهید بقیه موارد را در حین کدنویسی تجربه کنیم و هر آنچه ماند را بعدا توضیح خواهیم داد. در اینجا من از یک صفحه با کد HTML زیر بهره برده ام که یک فرم دارد به همراه چهار چک باکس و در نهایت یک دکمه جهت ذخیره مقادیر. نام صفحه را popup.htm گذاشته ام و یک فایل popup.js هم دارم که در آن کد jquery نوشتم. قصد من این است که بتوان یک action browser به شکل زیر درست کنم:


کد html آن به شرح زیر است:
<html>
<head>
<meta charset="utf-8"/>

<script src="jquery.min.js"></script> <!-- Including jQuery -->
<script type="text/javascript" src="popup.js"></script>
</head>
<body style="direction:rtl;width:250px;">
<form >
<input type="checkbox" id="chkarticles" value="" checked="true">آخرین مطالب سایت</input><br/>
<input type="checkbox" id="chkarticlescomments" value="" >آخرین نظرات مطالب سایت</input><br/>
<input type="checkbox" id="chkshares" value="" >آخرین اشتراک‌های سایت</input><br/>
<input type="checkbox"  id="chksharescomments" value="" >آخرین نظرات اشتراک‌های سایت</input><br/>
<input id="btnsave" type="button" value="ذخیره تغییرات" />
    <div id="messageboard" style="color:green;"></div>
</form>

</body>
</html>
کد popup.js هم به شرح زیر است:
$(document).ready(function () {
    $("#btnsave").click(function() {
        var articles = $("#chkarticles").is(':checked');
        var articlesComments = $("#chkarticlescomments").is(':checked');
        var shares = $("#chkshares").is(':checked');
        var sharesComments = $("#chksharescomments").is(':checked');

        chrome.storage.local.set({ 'articles': articles, 'articlesComments': articlesComments, 'shares': shares, 'sharesComments': sharesComments }, function() {
            $("#messageboard").text( 'تنظیمات جدید اعمال شد');
        });
    });
});
در کد بالا موقعی که کاربر بر روی دکمه ذخیره، کلیک کند رویداد کلیک jquery فعال شده و مقادیر چک باکس‌ها را در متغیرهای مربوطه نگهداری می‌کند. نهایتا با استفاده از کلمه کلیدی کروم به ناحیه ذخیره سازی داده‌های کروم دست پیدا کرده و درخواست ذخیره مقادیر چک باکس را بر اساس ساختار نام و مقدار، ذخیره می‌کنیم و بعد از اعمال، توسط یک تابع callback به کاربر اعلام می‌کنیم که اطلاعات ذخیره شده است.
اولین مورد جدیدی که در بالا دیدیم، کلمه‌ی کلیدی chrome است. کروم برای توسعه دهندگانی که قصد نوشتن افزونه دارند api هایی را تدارک دیده است که میتوانید با استفاده از آنها به قسمت‌های مختلف مرورگر مثل بوک مارک یا تاریخچه فعالیت‌های مرورگر و ... دست پیدا کنید. البته برای اینکار باید در فایل manifest.json هم مجوز اینکار را درخواست نماییم. این ویژگی باید برای برنامه نویسان اندروید آشنا باشد. برای آشنایی هر چه بیشتر با مجوزها این صفحه را ببینید.
برای دریافت مجوز، کد زیر را به manifest اضافه می‌کنیم:
"permissions": [
    "storage"
  ]
مجوزی که در بالا درخواست کرده‌ایم مجوز دسترسی به ناحیه ذخیره سازی است. بعد از کلمه کلیدی chrome، کلمه‌ی local آمده است و می‌گوید که باید داده‌ها به صورت محلی و لوکال ذخیره شوند ولی اگر میخواهید داده‌ها در گوگل سینک شوند، باید به جای لوکال از کلمه کلیدی sync استفاده کنید یعنی:
chrome.storage.sync.set
فایل manifest نهایی:
{
  "manifest_version": 2,

  "name": "Dotnettips Updater",
  "description": "This extension keeps you updated on current activities on dotnettips.info",
  "version": "1.0",

  "browser_action": {
    "default_icon": "icon.png",
    "default_popup": "popup.html"
  },
  "permissions": [
    "storage"
  ]
}
الان باید 4 فایل داشته باشید: فایل آیکن، popup.htm,popup.js و manifest.json. همه را داخل یک دایرکتوری قرار داده و در مروگر کروم به قسمت extensions بروید و گزینه Developer mode را فعال کنید تا یک تستی از کد نوشته شده بگیریم. گزینه Load Unpacked Extension را بزنید و آدرس دایرکتوری ایجاد شده را به آن بدهید.
chrome://extensions

الان باید مانند تصویر بالا یک آیکن کنار نوار آدرس یا به قول گوگل، Omni box ببینید. گزینه‌ها را تیک بزنید و روی دکمه ذخیره کلیک کنید. باید پیام مقادیر ذخیره شدند، نمایش پیدا کند. الان یک مشکل وجود دارد؛ داده‌ها ذخیره می‌شوند ولی موقعی که دوباره تنظیمات افزونه را باز کنید حالت اولیه نمایش داده میشود. پس باید تنظیمات ذخیره شده را خوانده و به آن‌ها اعمال کنیم. کد زیر را جهت دریافت مقادیر ذخیره شده می‌نویسیم. اینبار به جای استفاده از متد set از متد get استفاده می‌کنیم. به صورت آرایه، رشته نام مقادیر را درخواست می‌کنیم و در تابع callback، مقادیر به صورت آرایه برای ما برگشت داده می‌شوند.
    chrome.storage.local.get(['articles', 'articlesComments', 'shares', 'sharesComments'], function ( items) {
        console.log(items[0]);
        $("#chkarticles").attr("checked", items["articles"]);
        $("#chkarticlescomments").attr("checked", items["articlesComments"]);
        $("#chkshares").attr("checked", items["shares"]);
        $("#chksharescomments").attr("checked", items["sharesComments"]);

    });
حالا برای اینکه افزونه‌ی شما متوجه تغییرات شود، به تب extensions رفته و در لیست افزونه‌ها به دنبال افزونه خود بگردید و گزینه Reload را انتخاب نمایید تا افزونه تغییرات را متوجه شود و صفحه را تست کنید.

Page Action
روش دیگر برای ارائه یک رابط کاربری، page action هست. این روش دقیقا مانند روش قبلی است، ولی جای آیکن عوض می‌شود. قبلا بیرون از نوار آدرس بود، ولی الان داخل نوار آدرس قرار می‌گیرد. جالب‌ترین نکته در این مورد این است که این آیکن در ابتدا مخفی شده است و شما تصمیم می‌گیرید که این آیکن چه موقع نمایش داده شود. مثلا آیکن RSS تنها موقعی نمایش داده می‌شود که وب سایتی که باز شده است، دارای محتوای RSS باشد یا بوک مارک کردن یک آدرس برای همه‌ی سایت‌ها باز باشد و سایر موارد.
کد زیر نحوه‌ی تعریف یک page action را در manifest نشان می‌دهد. ما در این مثال یک page action را به طور موقت اضافه می‌کنیم و موقعی هم آن را نشان میدهیم که سایت dotnettips.info باز باشد. دلیل اینکه موقت اضافه می‌کنیم این است که باید یکی از دو گزینه رابط کاربری که تا به حال گفتیم، استفاده شود. در غیر این صورت کروم در هنگام خواندن فایل manifest در هنگام افزودن افزونه به مرورگر، پیام خطا خواهد داد و این مطلب را به شما گوشزد می‌کند. پس نمی‌توان دو گزینه را همزمان داشت و من میخواهم افزونه را در حالت browser action ارائه کنم. پس در پروژه نهایی، این مطلب page action نخواهد بود. برای داشتن یک page action کد زیر را در manifest بنویسید.
  "page_action": {
    "default_icon": {
        "19": "images/icon19.png",
        "38": "images/icon38.png"
    },
    "default_popup": "popup.html"
گزینه page action تعریف شد حالا باید کاری کنیم تا هر موقع صفحه‌ای باز می‌شود چک کند آیا سایت مورد نظر است یا خیر، اینکار را توسط صفحه‌ی پردازشی انجام می‌دهیم. پس تکه کد زیر را هم به manifest اضافه می‌کنیم:
"background": {
    "scripts": ["page_action_validator.js"]
}

تا اینجا فایل جاوااسکریپت معرفی شد که کد زیر را دارد و در پس زمینه شروع به اجرا می‌کند.
function UrlValidation(tabId, changeInfo, tab) {
if (tab.url.indexOf('dotnettips.info') >-1) {
chrome.pageAction.show(tabId);
}
};
chrome.tabs.onUpdated.addListener(UrlValidation);
چون از api در این کد بهره برده‌ایم و آن هم مدیریت بر روی تب هاست، پس باید مجوز آن هم گرفته شود. کلمه "tabs" را در قسمت permissions اضافه کنید.
یک listener برای tabها ایجاد کرده‌ایم که اگر تب جدید ایجاد شد، یا تب قبلی به آدرس جدیدی تغییر پیدا کرد تابع UrlValidation را اجرا کند و در این تابع چک می‌کنیم که اگر url این تب شامل نام وب سایت می‌شود، page action روی این تب ظاهر شود. پس از انجام تغییرات، مجددا افزونه را بارگذاری می‌کنیم و تغییرات اعمال شده را می‌بینیم. سایت dotnettips را باز کنید یا صفحه را مجددا رفرش کنید تا تغییر اعمال شده را ببینید.

تغییرات موقت را حذف و کدها را به حالت قبلی یعنی browser action بر میگردانم.

OmniBox
omnibox یک کلمه کلیدی است که در نوار آدرس مرورگر وارد می‌شود و در واقع می‌توانیم آن را نوع دیگری از رابط کاربری بنامیم. موقعی که شما کلمه کلیدی رزرو شده را وارد می‌کنید، در نوار آدرس کلماتی نشان داده میشود که کاربر میتواند یکی از آن‌ها را انتخاب کند تا عملی انجام شود. ما هم قرار است این کار را انجام دهیم. به این مثال دقت کنید:
میخواهیم موقعی که کاربرکلمه net. را تایپ می‌کند، 5 عبارت آخرین مطالب و آخرین اشتراک‌ها و آخرین نظرات مطالب و آخرین نظرات اشتراک‌ها و صفحه اصلی سایت نمایش داده شود و با انتخاب هر کدام، کاربر به سمت آن صفحه هدایت شود.
برای افزودن کلمه کلیدی در manifest خطوط زیر را اضافه کنید:
"omnibox": { "keyword" : ".net" }
با نوشتن خط بالا کلمه net. در مرورگر یک کلمه‌ی کلیدی به حساب خواهد آمد و موقعی که کاربر این کلمه را وارد کند، در سمت راست نوشته خواهد شد. در این حالت باید کلید تب را بزند تا به محیط دستوری آن برود.

در این حین می‌توانیم همزمان با تایپ کاربر، دستوراتی را به آن نشان بدهیم. من دوست دارم موقعی که کاربر حرفی را وارد کرد، لیستی از نام صفحات نوشته شود.

 
 برای اینکار باید کدنویسی کنیم ، پس یک فایل پس زمینه را به manifest معرفی کنید:
"background": {
    "scripts": ["omnibox.js"]
در فایل ominbox.js دستوراتی که مرتبط با omnibox است را می‌نویسیم و کد زیر را به آن اضافه می‌کنیم:
chrome.omnibox.onInputChanged.addListener(function(text, suggest) {
    suggest([
  {content: ".net tips Home Page", description: "صفحه اصلی"},
      {content: ".net tips Posts", description: "آخرین مطالب"},
      {content: ".net tips News", description: "آخرین نظرات مطالب"},
      {content: ".net tips Post Comments", description: "آخرین اشتراک ها"},
      {content: ".net tips News Comments", description: "آخرین نظرات اشتراک ها"}
    ]);
});
chrome.omnibox شامل 4 رویداد می‌شود:
 onInputStarted   بعد از اینکه کاربر کلمه کلیدی را وارد کرد اجرا می‌شود
onInputChanged 
  بعد از وارد کردن کلمه کلیدی هربار که کاربر تغییری در ورودی نوارد آدرس می‌دهد اجرا می‌شود.
 onInputEntered   کاربر ورودی خود را تایید می‌کند. مثلا بعد از وارد کردن، کلید enter را می‌فشارد
 onInputCancelled  کاربر از وارد کردن ورودی منصرف شده است؛ مثلا کلید ESC را فشرده است. 
با نوشتن chrome.omnibox.onInputChanged.addListener ما یک listener ساخته‌ایم تا هر بار کاربر ورودی را تغییر داد، یک تابع callback که دو آرگومان را دارد، صدا بزند. این آرگومان‌ها یکی متن ورودی‌است و دیگری آرایه‌ی suggest که شما با تغییر آرایه می‌توانید عباراتی که همزمان با تایپ به کاربر پیشنهاد می‌شود را نشان دهید. البته می‌توانید با تغییر کد کاری کنید تا بر اساس حروفی که تا به حال تایپ کرده‌اید، دستورات را نشان دهد؛ ولی من به دلیل اینکه 5 دستور بیشتر نبود و کاربر راحت باشد، چنین کاری نکردم. همچنین وقتی شما برای هر یک description تعریف کنید، به جای نام پیشنهادی، توضیح آن را نمایش می‌دهد.
حالا وقت این است که کد زیر را جهت اینکه اگر کاربر یکی از کلمات پیشنهادی را انتخاب کرد، به صفحه‌ی مورد نظر هدایت شود، اضافه کنیم:
chrome.omnibox.onInputEntered.addListener(function (text) {

var location="";
    switch(text)
{
case ".net tips Posts":
location="https://www.dntips.ir/postsarchive";
break;
case ".net tips News":
location="https://www.dntips.ir/newsarchive";
break;
case ".net tips Post Comments":
location="https://www.dntips.ir/commentsarchive";
break;
case".net tips News Comments":
location="https://www.dntips.ir/newsarchive/comments";
break;
default:
location="https://www.dntips.ir/";
}

    chrome.tabs.getSelected(null, function (tab) {
        chrome.tabs.update(tab.id, { url: location });
    });
});
ابتدا یک listener برای روی رویداد onInputEntered قرار داده تا وقتی کاربر عبارت وارد شده را تایید کرد، اجرا شود. در مرحله بعد چک می‌کنیم که عبارت وارد شده چیست و به ازای هر عبارت مشخص شده، آدرس آن صفحه را در متغیر location قرار می‌دهیم. در نهایت با استفاده از عبارت chrome.tabs.getSelected تب انتخابی را به یک تابع callback بر میگردانیم. اولین آرگومان windowId است، برای زمانی که چند پنجره کروم باز است که می‌توانید وارد نکنید تا پنجره فعلی و تب فعلی محسوب شود. برای همین نال رد کردیم. در تابع برگشتی، شیء tab شامل اطلاعات کاملی از آن تب مانند url و id و title می‌باشد و در نهایت با استفاده از دستور chrome.tabs.update اطلاعات تب را به روز می‌کنیم. آرگومان اول id تب را میدهیم تا بداند کدام تب باید تغییر کند و آرگومان بعدی می‌توانید هر یک از ویژگی‌های تب از قبیل آدرس فعلی یا عنوان آن و ... را تغییر دهید که ما آدرس آن را تغییر داده ایم.

Context Menu
یکی دیگر از رابط‌های کاربری، منوی کانتکست هست که توسط chrome.contextmenus ارائه می‌شود و به مجوز "contextmenus" نیاز دارد. فعال سازی منوی کانتکست در قسمت‌های زیر ممکن است:
all, page, frame, selection, link, editable, image, video, audio 
من گزینه‌ی dotenettips.info را برای باز کردن سایت، به Contextmenus اضافه می‌کنم. کد را در فایلی به اسم contextmenus.js ایجاد می‌کنم و در قسمت background آن را معرفی می‌کنم. برای باز کردن یک تب جدید برای سایت، نیاز به chrome.tabs داریم که البته  نیاز به مجوز tabs هم داریم.
محتوای فایل contextmenus.js
var root = chrome.contextMenus.create({
    title: 'Open .net tips',
    contexts: ['page']
}, function () {
    var Home= chrome.contextMenus.create({
        title: 'Home',
        contexts: ['page'],
        parentId: root,
        onclick: function (evt) {
            chrome.tabs.create({ url: 'https://www.dntips.ir' })
        }
    });
var Posts = chrome.contextMenus.create({
        title: 'Posts',
        contexts: ['page'],
        parentId: root,
        onclick: function (evt) {
            chrome.tabs.create({ url: 'https://www.dntips.ir/postsarchive/' })
        }
    });
});
در کد بالا یک گزینه به context menu اضافه میشود و دو زیر منو هم دارد که یکی صفحه‌ی اصلی سایت را باز میکند و دیگری هم صفحه‌ی مطالب سایت را باز می‌کند.

تا به اینجا ما قسمت ظاهری کار را آماده کرده ایم و به دلیل اینکه مطلب طولانی نشود، این مطلب را در دو قسمت ارائه خواهیم کرد. در قسمت بعدی نحوه خواندن RSS و اطلاع رسانی و دیگر موارد را بررسی خواهیم کرد.
مطالب
Blazor 5x - قسمت هشتم - مبانی Blazor - بخش 5 - تامین محتوای نمایشی کامپوننت‌های فرزند توسط کامپوننت والد
تا اینجا با نحوه‌ی تعریف کامپوننت‌ها و انتقال اطلاعات به آن‌ها و برعکس، آشنا شدیم. در ادامه علاقمندیم اگر کامپوننتی را به صورت زیر تعریف کردیم:
<Comp1>This is a content coming from the parent</Comp1>
محتوایی را که بین تگ‌های این کامپوننت فرزند، توسط کامپوننت والد تنظیم شده‌است، در قسمت مشخصی از UI کامپوننت فرزند نمایش دهیم.


معرفی مفهوم Render Fragment

برای درج محتوای تامین شده‌ی توسط کامپوننت والد در یک کامپوننت فرزند، از ویژگی به نام Render Fragment استفاده می‌شود. مثالی جهت توضیح جزئیات آن:
در ابتدا یک کامپوننت والد جدید را در مسیر Pages\LearnBlazor\ParentComponent.razor به صورت زیر تعریف می‌کنیم:
@page "/ParentComponent"

<h1 class="text-danger">Parent Child Component</h1>

<ChildComponent Title="This title is passed as a parameter from the Parent Component">
    A `Render Fragment` from the parent!
</ChildComponent>

<ChildComponent Title="This is the second child component"></ChildComponent>

@code {

}
- در اینجا یک کامپوننت متداول با مسیریابی منتهی به ParentComponent/ تعریف شده‌است.
- سپس دوبار کامپوننت فرضی ChildComponent به همراه پارامتر Title و یک محتوای جدید قرار گرفته‌ی در بین تگ‌های آن، در صفحه تعریف شده‌اند.
- بار دومی که ChildComponent در صفحه قرار گرفته‌است، به همراه محتوای جدیدی در بین تگ‌های خود نیست.

برای دسترسی به این کامپوننت از طریق منوی برنامه، مدخل منوی آن‌را به کامپوننت Shared\NavMenu.razor اضافه می‌کنیم:
<li class="nav-item px-3">
    <NavLink class="nav-link" href="ParentComponent">
       <span class="oi oi-list-rich" aria-hidden="true"></span> Parent/Child Relation
    </NavLink>
</li>
اکنون می‌خواهیم کامپوننت ChildComponent را افزوده و انتظارت یاد شده (دریافت یک پارامتر و نمایش محتوای سفارشی تنظیم شده) را پیاده سازی کنیم. به همین جهت فایل جدید Pages\LearnBlazor\LearnBlazor‍Components\ChildComponent.razor را با محتوای زیر ایجاد می‌کنیم:
<div>
    <div class="alert alert-info">@Title</div>
    <div class="alert alert-success">
        @if (ChildContent == null)
        {
            <span> Hello, from Empty Render Fragment </span>
        }
        else
        {
            <span>@ChildContent</span>
        }
    </div>
</div>


@code {
    [Parameter]
    public string Title { get; set; }

    [Parameter]
    public RenderFragment ChildContent { get; set; }
}
توضیحات:

- خاصیت عمومی Title که توسط ویژگی Parameter مزین شده‌است، امکان تنظیم مقدار مشخصی را توسط کامپوننت دربرگیرنده‌ی ChildComponent میسر می‌کند.
- در اینجا پارامتر عمومی دیگری نیز تعریف شده‌است که اینبار از نوع ویژه‌ی RenderFragment است. توسط آن می‌توان به محتوایی که در کامپوننت والد ChildComponent در بین تگ‌های آن تنظیم شده‌است، دسترسی یافت. همچنین اگر این محتوا توسط کامپوننت والد تنظیم نشده باشد، مانند دومین باری که ChildComponent در صفحه قرار گرفته‌است، می‌توان با بررسی نال بودن آن، یک محتوای پیش‌فرض را نمایش داد.


با این خروجی:



روش دیگری برای فراخوانی Event Call Back ها

در قسمت قبل روش انتقال اطلاعات را از کامپوننت‌های فرزند، به والد مشاهده کردیم. فراخوانی آن‌ها در سمت Child Component نیاز به یک متد اضافی داشت و همچنین تنها یک پارامتر را هم ارسال کردیم. برای ساده سازی این عملیات از روش زیر نیز می‌توان استفاده کرد:
<button class="btn btn-danger"
        @onclick="@(() => OnClickBtnMethod.InvokeAsync((1, "A message from child!")))">
   Show a message from the child!
</button>


@code {
    // ...

    [Parameter]
    public EventCallback<(int, string)> OnClickBtnMethod { get; set; }
}
- در اینجا برای تعریف و ارسال بیش از یک پارامتر، از tuples استفاده شده‌است.
- همچنین فراخوانی OnClickBtnMethod.InvokeAsync را نیز در محل تعریف onclick@ بدون نیازی به یک متد اضافی، مشاهده می‌کنید. نکته‌ی مهم آن، قرار دادن این قطعه کد داخل ()@ است تا ابتدا و انتهای کدهای #C مشخص شود؛ وگرنه کامپایل نمی‌شود.

در سمت کامپوننت والد برای دسترسی به OnClickBtnMethod که اینبار یک tuple را ارسال می‌کند، می‌توان به صورت زیر عمل کرد:
@page "/ParentComponent"

<h1 class="text-danger">Parent Child Component</h1>

<ChildComponent
    OnClickBtnMethod="ShowMessage"
    Title="This title is passed as a parameter from the Parent Component">
    A `Render Fragment` from the parent!
</ChildComponent>

<ChildComponent Title="This is the second child component">
    <p><b>@MessageText</b></p>
</ChildComponent>

@code {
    string MessageText = "";

    private void ShowMessage((int Value, string Message) args)
    {
        MessageText = args.Message;
    }
}
در اینجا OnClickBtnMethod تعریف شده، به متد ShowMessage متصل شده‌است که یک tuple از جنس (int, string) را دریافت می‌کند. سپس با انتساب خاصیت رشته‌ای آن به فیلد جدید MessageText، سبب نمایش آن می‌شویم.


امکان تعریف چندین RenderFragment

تا اینجا یک RenderFragment را در کامپوننت فرزند تعریف کردیم. امکان تعریف چندین RenderFragment در ChildComponent.razor نیز وجود دارند:
@code {
    // ...

    [Parameter]
    public RenderFragment ChildContent { get; set; }

    [Parameter]
    public RenderFragment DangerChildContent { get; set; }
}
در یک چنین حالتی، روش استفاده‌ی از این پارامترهای RenderFragment در سمت والد (ParentComponent.razor) به صورت زیر خواهد بود:
<ChildComponent
    OnClickBtnMethod="ShowMessage"
    Title="This title is passed as a parameter from the Parent Component">
    <ChildContent>
        A `Render Fragment` from the parent!
    </ChildContent>
    <DangerChildContent>
        A danger content from the parent!
    </DangerChildContent>
</ChildComponent>
که به همراه ذکر صریح نام پارامترها به صورت تگ‌هایی جدید، داخل تگ اصلی است.

از آنجائیکه ذکر این تگ‌ها اختیاری است، نیاز است در ChildComponent.razor بر اساس null بودن آن‌ها، تصمیم به رندر محتوایی پیش‌فرض گرفت:
    @if(DangerChildContent == null)
    {
        @if (ChildContent == null)
        {
            <span> Hello, from Empty Render Fragment </span>
        }
        else
        {
            <span>@ChildContent</span>
        }
    }
    else
    {
        <span>@DangerChildContent</span>
    }




کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید: Blazor-5x-Part-08.zip
مطالب
ارتقاء به ASP.NET Core 1.0 - قسمت 12 - معرفی Tag Helpers
یکی دیگر از تغییرات مهم Razor در ASP.NET Core، معرفی Tag Helpers است که همانند HTML Helpers نگارش‌های پیشین ASP.NET MVC، کار رندر کردن HTML را انجام می‌دهند و در اغلب موارد می‌توان آن‌ها را جایگزین HTML Helpers کرد. مزیت استفاده‌ی از Tag helpers، شبیه بودن آن‌ها به المان‌ها و ویژگی‌های HTML است. در کل اینکه باید از HTML Helpers استفاده کرد و یا از Tag Helpers، بیشتر یک انتخاب شخصی و سلیقه‌ای است.


فعال سازی استفاده‌ی از Tag Helpers برای تمام Viewهای برنامه

برای اینکه تمام Viewهای سایت بتوانند به امکانات Tag Helpers دسترسی پیدا کنند، باید یک سطر ذیل را به فایل ViewImports.cshtml_ اضافه کرد:
 @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
در اینجا * به معنای استفاده‌ی از تمام Tag Helpers موجود در اسمبلی ذکر شده‌است.

Microsoft.AspNetCore.Mvc.TagHelpers به همراه افزودن وابستگی Microsoft.AspNetCore.Mvc در حین فعال سازی ASP.NET MVC، به پروژه اضافه می‌شود:



فعال سازی Intellisense مربوط به Tag Helpers در ویژوال استودیو

هرچند فعال سازی ASP.NET MVC، تنها وابستگی است که برای کار با Tag Helpers نیاز است، اما برای فعال سازی Intellisense آن‌ها باید بسته‌ی Microsoft.AspNetCore.Razor.Tools را نیز به فایل prject.json برنامه، جهت نصب معرفی کرد:
{
    "dependencies": {
         //same as before
         "Microsoft.AspNetCore.Mvc.TagHelpers": "1.0.0",
         "Microsoft.AspNetCore.Razor.Runtime": "1.0.0",
         "Microsoft.AspNetCore.Razor.Tools": {
            "version": "1.0.0-preview2-final",
            "type": "build"
        }
    },
 
    "tools": {
         //same as before
        "Microsoft.AspNetCore.Razor.Tools": "1.0.0-preview2-final"
    } 
}
ضمنا اگر از ReSharper استفاده می‌کنید (تا نگارش resharper-2016.1)، فعلا مجبور هستید که آن‌را غیرفعال کنید. اطلاعات بیشتر


یک مثال: ایجاد لینکی به یک اکشن متد
 <a asp-controller="Home" asp-action="Index" asp-route-id="123">Home</a>
در اینجا نحوه‌ی ایجاد لینکی را مشاهده می‌کنید که به کنترلر Home و اکشن متد Index آن اشاره می‌کند. این syntax جدید، جایگزین ActionLink مربوط به HTML Helperها است. در اینجا asp-route-id را نیز مشاهده می‌کنید. قسمت asp-route آن جهت مقدار دهی پارامترهای مسیریابی است و قسمت id- بنابر نام پارامتری که قرار است مقدار دهی شود، متغیر خواهد بود.
اگر نیاز به اشاره‌ی به مسیریابی خاصی از طریق نام آن وجود دارد (همان نام‌هایی که در حین تعریف یک مسیریابی ذکر می‌شوند) می‌توان به صورت ذیل عمل کرد:
 <a asp-route="login">Login</a>
و یا برای مشخص سازی پروتکل خاصی و یا ذکر دقیق نام هاست، می‌توان از روش زیر استفاده کرد:
 <a asp-controller="Account"
   asp-action="Register"
   asp-protocol="https"
   asp-host="asepecificdomain.com"
   asp-fragment="fragment">Register</a>


راهنمای تبدیل HTML Helpers به Tag Helpers

در جدول ذیل، مثال‌هایی را از HTML Helpers متداول و معادل‌های Tag Helper آن‌ها مشاهده می‌کنید:

Tag Helper
HTML Helper
<label asp-for="Email" class="col-md-2 control-label"></label>
@Html.LabelFor(m => m.Email, new { @class = "col-md-2 control-label" })
<a asp-controller="MyController" asp-action="MyAction" 
class="my-css-classname" my-attr="my-attribute">Click me</a>
@Html.ActionLink("Click me", "MyController", "MyAction", 
{ @class="my-css-classname", data_my_attr="my-attribute"})
<input asp-for="FirstName" style="width:100px;"/>
@Html.TextBox("FirstName", Model.FirstName, new { style = "width: 100px;" })
<input asp-for="Email" class="form-control" />
@Html.TextBoxFor(m => m.Email, new { @class = "form-control" })
<input asp-for="Password" class="form-control" />
@Html.PasswordFor(m => m.Password, new { @class = "form-control" })
<input asp-for="UserName" class="form-control" />
@Html.EditorFor(l => l.UserName,
 new { htmlAttributes = new { @class = "form-control" } })
<form asp-controller="Account" asp-action="Register" 
method="post" class="form-horizontal" role="form">
@using (Html.BeginForm("Register", "Account",
 FormMethod.Post, new { @class = "form-horizontal", role = "form" }))
{
    @Html.AntiForgeryToken()
<span asp-validation-for="UserName" class="text-danger"></span>
@Html.ValidationMessageFor(m => m.UserName, "",
 new { @class = "text-danger" })
<div asp-validation-summary="ValidationSummary.All" class="text-danger"></div>
@Html.ValidationSummary("", new { @class = "text-danger" })


نکات تکمیلی کار با فرم‌ها توسط Tag Helpers

نمونه‌ای از مثال Tag helper کار با فرم‌ها را در جدول فوق ملاحظه می‌کنید. چند نکته‌ی تکمیلی ذیل را می‌توان به آن اضافه کرد:
- در حین کار با Tag Helpers، درج anti forgery token به صورت خودکار صورت می‌گیرد. اگر می‌خواهید که این توکن ذکر نشود، آن‌را توسط ویژگی "asp-anti-forgery="false خاموش کنید.
- برای درج پارامترهای مسیریابی خاص، از asp-route به همراه نام پارامتر مدنظر استفاده کنید:
 <form asp-controller="Account"
      asp-action="Login"
      asp-route-returnurl="@ViewBag.ReturnUrl"
      method="post" >
</form>
که در نهایت به یک چنین حالتی رندر می‌شود
 <form action="/Account/Login?returnurl=%2FHome%2FAbout" method="post">
- همانند action linkها در اینجا نیز برای اشاره‌ی به یک مسیریابی از طریق نام آن می‌توان از ویژگی asp-route استفاده کرد
 <form asp-route="login"
      asp-route-returnurl="@ViewBag.ReturnUrl"
      method="post" >
</form>


Tag helpers مخصوص تعریف اسکریپت‌ها و CSSها

 در اینجا Tag Helpers صرفا به عنوان جایگزین‌های HTML Helpers مطرح نیستند. توسط آن‌ها قابلیت‌های جدیدی نیز ارائه شده‌است. برای مثال اگر تگ اسکریپت را به صورت ذیل تعریف کنیم:
 <script asp-src-include="~/app/**/*.js"></script>
یک چنین خروجی فرضی را تولید می‌کند:
 <script src="/app/app.js"></script>
<script src="/app/controllers/controller1.js"></script>
<script src="/app/controllers/controller2.js"></script>
<script src="/app/controllers/controller3.js"></script>
<script src="/app/controllers/controller4.js"></script>
<script src="/app/services/service1.js"></script>
<script src="/app/services/service2.js"></script>
به این معنا که یک سطر asp-src-include، بر اساس الگویی که دریافت می‌کند، تمام فایل‌های اسکریپت موجود در یک پوشه را یافته و برای آن‌ها، تگ اسکریپت تولید می‌کند. دراینجا ذکر ** به معنای بررسی تمام زیرپوشه‌های app است. اگر تنها پوشه‌ی خاصی مدنظر است، باید ** را حذف کرد.
در این بین اگر می‌خواهید از پوشه‌ی خاصی صرفنظر کنید، از asp-src-exclude استفاده کنید:
 <script asp-src-include="~/app/**/*.js"
        asp-src-exclude="~/app/services/**/*.js">
</script>
همچنین در اینجا امکان تعریف CDN و fallback هم وجود دارد. استفاده‌ی از CDNها جهت کاهش ترافیک سرور و بهبود کارآیی برنامه با ارائه‌ی نمونه‌های کش شده‌ی فریم ورک‌های معروف، متداول هستند که در اینجا نمونه‌ای از نحوه‌ی تعریف آن‌ها را مشاهده می‌کنید. همچنین تعریف fallback در اینجا به این معنا است که اگر CDN در دسترس نبود، به نمونه‌ی محلی موجود بر روی سرور مراجعه شود.
 <link rel="stylesheet" href="//ajax.aspnetcdn.com/ajax/bootstrap/3.0.0/css/bootstrap.min.css"
      asp-fallback-href="~/lib/bootstrap/css/bootstrap.min.css"
      asp-fallback-test-class="hidden"
      asp-fallback-test-property="visibility"
      asp-fallback-test-value="hidden" />
 
<script src="//ajax.aspnetcdn.com/ajax/bootstrap/3.0.0/bootstrap.min.js"
        asp-fallback-src="~/lib/bootstrap/js/bootstrap.min.js"
        asp-fallback-test="window.jQuery">
</script>

به علاوه اگر ویژگی asp-file-version را نیز ذکر کنید:
 <link rel="stylesheet" href="~/css/site.min.css" asp-file-version="true"/>
یک چنین لینکی تولید می‌شود:
 <link rel="stylesheet" href="/css/site.min.css?v=UdxKHVNJA5vb1EsG9O9uURFDfEE3j1E3DgwL6NiDGMc" />
هدف آن نیز اصطلاحا cache busting است. به این معنا که با تغییر محتوای این فایل‌ها، کوئری استرینگ تولید شده، مجددا محاسبه شده و مرورگر همواره آخرین نگارش موجود را دریافت خواهد کرد و دیگر از نمونه‌ی کش شده‌ی قدیمی استفاده نمی‌کند.

یک نکته: ویژگی asp-file-version را برای تصاویر هم می‌توان بکار برد:
 <img src="~/images/logo.png"
     alt="company logo"
     asp-file-version="true" />
که یک چنین خروجی را تولید می‌کند و هدف آن نیز جلوگیری از کش شدن تصویر، با تغییر محتوای آن است:
 <img src="/images/logo.png?v=W2F5D366_nQ2fQqUk3URdgWy2ZekXjHzHJaY5yaiOOk"
     alt="company logo"/>


بررسی Environment Tag Helper

با متغیرهای محیطی و نحوه‌ی تعریف آن‌ها در قسمت‌های قبل آشنا شدیم. در اینجا tag helper سفارشی خاصی برای کار با آن‌ها ارائه شده‌است که شیبه به if/else عمل می‌کنند:
<environment names="Development">    
   <link rel="stylesheet" href="~/css/site1.css" />
   <link rel="stylesheet" href="~/css/site2.css" />
</environment>

<environment names="Staging,Production">
   <link rel="stylesheet" href="~/css/site.min.css" asp-file-version="true"/>
</environment>
هدف این است که اگر متغیر محیطی به Development تنظیم شده بود، لینک‌های ساده و اصلی فایل‌های css یا اسکریپت در HTML نهایی درج شوند و اگر حالت توسعه تنظیم شده بود، لینک‌های min یا فشرده شده‌ی آن‌ها ارائه شوند؛ به همراه asp-file-version که cache busting را فعال می‌کند.


کار با دراپ داون‌ها توسط Tag helpers

فرض کنید ViewModel یک view جهت نمایش یک دراپ داون به این صورت تنظیم شده‌است:
public class CustomerViewModel
{
   public string Vehicle { get; set; }  
   public List<SelectListItem> Vehicles { get; set; }
برای نمایش SelectListItem توسط tag helpers می‌توان به صورت ذیل عمل کرد:
 <select asp-for="Vehicle" asp-items="Model.Vehicles">
</select>
asp-for به نام خاصیتی اشاره می‌کند که در نهایت مقدار انتخاب شده را دریافت می‌کند و asp-items لیست آیتم‌های دراپ داون را رندر می‌کند.
مطالب
تخته وایت برد آنلاین توسط SignalR
همانطور که در دوره SignalR سایت نیز مطرح شده‌است : "یکی از کاربردهای جالب SignalR می‌تواند به روز رسانی مداوم صفحه نمایش کاربران، توسط اطلاعات ارسالی از طرف سرور باشد." در ادامه میخواهیم به طراحی یک "تخته وایت برد" آنلاین بپردازیم.
در این پروژه برای ترسم خطوط بر روی صفحه از Canvas در  HTML5 استفاده میشود.

پیشنیازها :
پیشنیازهای این مطلب با مطلب «مثال - نمایش درصد پیشرفت عملیات توسط SignalR» یکی است. برای مثال، نحوه دریافت وابستگی‌ها، تنظیمات فایل global.asax و افزودن اسکریپت‌ها، تفاوتی با مقاله‌ی ذکر شده ندارد و تنها تعدادی اسکریپت و CSS جهت زیبایی کار افزوده شده است :
<link href="Styles/bootstrap.min.css" rel="stylesheet" />

//برای نمایش و استفاده از جعبه‌ی رنگ در بوت استراپ
<link href="Styles/bootstrap-colorpalette.css" rel="stylesheet" />
<script src="Scripts/jquery-1.8.2.js"></script>
<script type="text/javascript" src='Scripts/jquery.signalR-1.1.3.js'></script>
<script src="Scripts/bootstrap.min.js"></script>
<script src="Scripts/bootstrap-colorpalette.js"></script>
<script type="text/javascript" src='<%: ResolveClientUrl("~/signalr/hubs") %>'></script>

//حاوی متدهایی برای رسم خط با استفاده از HTML5
<script src="Scripts/draw.js" type="text/javascript"></script>

//تعریف کلاینت‌های متصل به هاب
<script src="Scripts/Whiteboard.js"></script>

  تعریف کلاس Hub برنامه :

using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Hubs;
using OnlineSignalRWhiteboard.Model;

namespace OnlineSignalRWhiteboard.Hubs
{
    [HubName("onWhiteboard")]
    public class Whiteboard : Hub
    {
        public void OnDrawPen(Point prev, Point current, string color, int width)
        {
            Clients.All.drawPen(prev, current, color, width);
        }

        public void ClearBoard()
        {
            Clients.All.clear();
        }

    }
}
در ابتدا توسط ویژگی HubName یک نام مشخص برای هاب خود انتخاب کرده ایم. سپس متدی به نام OnDrawPen تعریف شده است که پس از فراخوانی از سمت کلاینت، مقادیر دریافتی خود را با صدا زدن متدی در سمت کلاینت به نام drawPen  ، به تمام کلاینت‌ها ارسال و نقاط مربوطه در سمت کلاینت، بر روی صفحه رسم میشوند.
متد دیگری به نام ClearBoard نیز تعریف شده است که روال رخدادگردان clear در سمت کلاینت را فراخوانی میکند و باعث پاک شدن صفحه نمایش میشود.

کدهای کلاینت‌های متصل به هاب در فایل Whiteboard.js 
 :
$(function () {
    $.connection.hub.logging = true;
    var whiteboard = $.connection.onWhiteboard;

    $("#clear").click(function () {
        //پاک کردن صفحه نمایش
        whiteboard.server.clearBoard();
    });

    var color = function (colors) {
        //تغییر رنگ قلم
        draw.colour = colors;
    };

    $(".size").click(function () {
        //تغییر سایز قلم
        draw.lineWidth = $(this).height();
        $('#size').css('height', $(this).height());
    });

    $.connection.hub.start().done(function () {
    })
    .fail(function () {
        alert("Could not Connect!");
     });

    draw.onDraw = function (prev, current, color, width) {
        //ارسال پارامترها به سمت سرور تا سرور بتواند داده‌ها را به سمت سایر کلاینت‌ها نیز ارسال کند
        whiteboard.server.onDrawPen(prev, current, color, width);
    };

    whiteboard.client.drawPen = function (prev, current, color, width) {
        draw.drawPen(prev, current, color, width);
    };

    whiteboard.client.clear = function () {
        draw.clear();
    };

    $('#colorpalette3').colorPalette()
      .on('selectColor', function (e) {
          $('#color').css('background-color', e.color);
          color(e.color);
      });

    var canvas = document.getElementById('draw');
    //تغییر سایر کانواس متناسب با پنجره‌ی مرورگر
    window.addEventListener('resize', resizeCanvas, false);

    function resizeCanvas() {
        canvas.width = window.innerWidth - 20;
        canvas.height = window.innerHeight - 70;
    }
    resizeCanvas();

});
در این قطعه کد ابتدا ارجاعی به هاب مشخص شده است و سپس روال‌های رخداد گردانی مانند whiteboard.client.drawPen و whiteboard.client.clear تعریف شده اند که به ترتیب متصل هستند به فراخوانی های Clients.All.drawPen و  Clients.All.clear در سمت سرور.
همچنین یک متد به نام draw.onDraw تعریف شده است که وظیفه‌ی آن ارسال اطاعاتی نظیر موقعیت موس در صفحه، رنگ، اندازه و ...  به سمت متدی به نام onDrawPen در هاب است.

کدهای کامل سمت کلاینت :
<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
    <link href="Styles/bootstrap.min.css" rel="stylesheet" />
    <link href="Styles/bootstrap-colorpalette.css" rel="stylesheet" />
    <script src="Scripts/jquery-1.8.2.js"></script>
    <script type="text/javascript" src='Scripts/jquery.signalR-1.1.3.js'></script>
    <script src="Scripts/bootstrap.min.js"></script>
    <script src="Scripts/bootstrap-colorpalette.js"></script>
    <script type="text/javascript" src='<%: ResolveClientUrl("~/signalr/hubs") %>'></script>
    <script src="Scripts/draw.js" type="text/javascript"></script>
    <script src="Scripts/Whiteboard.js"></script>
</head>
<body>
    <form id="form1" runat="server">
        <div>
            <div style="position: static;">
              <div>
                <div>
                  <a data-toggle="collapse" data-target=".navbar-inverse-collapse">
                    <span></span>
                    <span></span>
                    <span></span>
                  </a>
                  <a href="#">تخته وایت برد آنلاین توسط SignalR و HTML5</a>
                  <div>
                    <ul>
                      <li>
                          <div>
                              <a id="selected-color2" data-toggle="dropdown">
                                  <div style="width: 20px; height: 20px; background: black" id="color">
                                  </div>
                              </a>
                              <ul style="width:293px;">
                                <li style="display:inline-block;">
                                  <div>رنگ پس زمینه</div>
                                  <div id="colorpalette3"></div>
                                </li>
                              </ul>
                          </div>
                      </li>
                      <li></li>
                      <li>
                          <div>
                              <a data-toggle="dropdown" style="width: 120px; height: 20px" href="#">
                                <hr style="width: 120px; height: 1px; background-color: black;margin: 0px; display: table-cell" id="size">
                              </a>
                              <ul style="width: 120px">
                                <li><hr style="width: 140px; height: 1px; background-color: black"></li>
                                  <li></li>
                                <li><hr style="width: 140px; height: 3px; background-color: black"></li>
                                  <li></li>
                                <li><hr style="width: 140px; height: 7px; background-color: black"></li>
                                  <li></li>
                                <li><hr style="width: 140px; height: 10px; background-color: black"></li>
                                  <li></li>
                                <li><hr style="width: 140px; height: 20px; background-color: black"></li>
                              </ul>
                            </div>
                      </li>
                      <li></li>
                      <li>
                          <div>
                            <a href="#" id="clear"><i></i>جدید</a>
                          </div>
                      </li>
                    </ul>
                  </div><!-- /.nav-collapse -->
                </div>
              </div><!-- /navbar-inner -->
            </div>
        </div>
        
        <div>
            <div>
                 <canvas id="draw" style="cursor: crosshair;border: 1px solid black;"></canvas>
            </div>
       </div>
    </form>
</body>
</html>
در ابتدا یک دکمه برای تغییر رنگ قلم و یک لیست بازشو برای تعیین اندازه‌ی قلم و همچنین یک دکمه برای پاک کردن صفحه نمایش قرار داده شده است. سپس یک Canvas به نام draw ایجاد کرده ایم که بتوانیم خطوط خود را بر روی آن رسم کنیم.

مشاهده نسخه نمایشی 
کدهای کامل این مثال را میتوانید از اینجا دانلود کنید : OnlineSignalRWhiteboard.zip  
و همچنین میتوانید نمونه‌ی بهینه شده‌ی آن را نیز از این قسمت دانلود کنید :  OnlineCustomSignalRWhiteboard.zip  
مطالب
نمایش اخطارها و پیام‌های بوت استرپ به کمک TempData در ASP.NET MVC
در بوت استرپ برای نمایش اعلانی به کاربر، از کلاس alert می‌توان استفاده کرد. برای نمایش این اعلان کافی است محتوای خود را درون یک div با کلاس alert قرار دهیم: 
<div class="alert">
    نمایش اعلانات
</div>

تعدادی کلاس دیگر نیز جهت استفاده از رنگ‌های مختلف نیز توسط بوت استرپ ارائه شده است: 

همچنین اگر مایل بودید می‌توانید با افزودن یک دکمه با کلاس close و ویژگی data-dismiss مساوی alert، امکان بستن پیام را در اختیار کاربر قرار دهید: 

<div class="alert alert-warning alert-dismissable">
  <button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button>
  نمایش اعلان
</div>

در ادامه قصد داریم این پیام را بعد از ثبت اطلاعات، به کاربر نمایش دهیم. یعنی در داخل کد، امکان صدا زدن این نوع پیام‌ها را داشته باشیم.

ابتدا کلاس‌های زیر را تعریف می‌کنیم:

کلاس Alert

public class Alert
{
        public const string TempDataKey = "TempDataAlerts";
        public string AlertStyle { get; set; }
        public string Message { get; set; }
        public bool Dismissable { get; set; }
}

در کلاس فوق خصوصیت یک alert را تعریف کرده‌ایم (از خاصیت TempDataKey جهت پاس دادن alert‌ها به view استفاده می‌کنیم).

  کلاس AlertStyles
public class AlertStyles
{
        public const string Success = "success";
        public const string Information = "info";
        public const string Warning = "warning";
        public const string Danger = "danger";
}
کلاس فوق نیز جهت نگهداری اسامی کلاس‌های alert، مورد استفاده قرار می‌گیرد. قدم بعدی، استفاده از کلاس‌های فوق و انتقال alerts توسط TempData به داخل viewها می‌باشد. برای جلوگیری از زیاد شدن حجم کدهای تکراری داخل کنترلرها و همچنین به عنوان یک Best practice، یک کنترلر Base را برای اینکار تعریف می‌کنیم و متدهای موردنیاز را داخل آن می‌نویسیم:
public class BaseController : Controller
{
        public void Success(string message, bool dismissable = false)
        {
            AddAlert(AlertStyles.Success, message, dismissable);
        }

        public void Information(string message, bool dismissable = false)
        {
            AddAlert(AlertStyles.Information, message, dismissable);
        }

        public void Warning(string message, bool dismissable = false)
        {
            AddAlert(AlertStyles.Warning, message, dismissable);
        }

        public void Danger(string message, bool dismissable = false)
        {
            AddAlert(AlertStyles.Danger, message, dismissable);
        }

        private void AddAlert(string alertStyle, string message, bool dismissable)
        {
            var alerts = TempData.ContainsKey(Alert.TempDataKey)
                ? (List<Alert>)TempData[Alert.TempDataKey]
                : new List<Alert>();

            alerts.Add(new Alert
            {
                AlertStyle = alertStyle,
                Message = message,
                Dismissable = dismissable
            });

            TempData[Alert.TempDataKey] = alerts;
        }
}
از متدهایی که به صورت عمومی تعریف شده‌اند جهت ارسال پیام به view استفاده می‌کنیم. متد AddAlert نیز جهت ایجاد لیستی از پیام‌ها(Alert) مورداستفاده قرار می‌گیرد؛ زیرا ممکن است بخواهید هم زمان از چندین متد عمومی فوق استفاده کنید، یعنی چندین پیام را به کاربر نمایش دهید. 

نکته: در کد فوق از TempData جهت پاس دادن شیء alerts استفاده کرده‌ایم. TempData به صورت short-lived عمل می‌کند به دو دلیل: 1- بلافاصله بعد از خوانده شدن، حذف خواهد شد. 2- پس از پایان درخواست از بین خواهد رفت. از TempData جهت پاس دادن داده‌ها از درخواست فعلی به درخواست بعدی (redirect از یک صفحه به صفحه دیگر) استفاده می‌شود. یعنی در زمان redirect سعی می‌کند داده‌های بین redirectها را در خود نگه دارد. اگر از ViewBag و ViewData استفاده می‌کردیم داده‌های داخل آنها بلافاصله بعد از redirect شدن null می‌شدند.
به طور مثال اکشن متد زیر را در نظر بگیرید:
public ActionResult Index()
        {
            var userInfo = new
            {
                Name = "Sirwan",
                LastName = "Afifi",
            };
 
            ViewData["User"] = userInfo;
            ViewBag.User = userInfo;
            TempData["User"] = userInfo;

            return RedirectToAction("About");
        }
view :
@{
    ViewBag.Title = "About";
}

<h1>Tempdata</h1><p>@TempData["User"]</p>
<h1>ViewData</h1><p>@ViewData["User"]</p>
<h1>ViewBag</h1><p>@ViewBag.User</p>
اگر کد فوق را تست کنید خواهید دید که در خروجی تنها اطلاعات داخل TempData نمایش داده می‌شود.
معمولاً برای ارسال داده‌های خطاها از TempData استفاده می‌شود.

اکنون در هر کنترلری که می‌خواهید پیامی را به صورت alert، پس از ثبت اطلاعات به کاربر نمایش دهید، باید از کنترلر BaseController  ارث‌بری کنید:
public class NewsController : BaseController
{
readonly INewsService _newsService;
        readonly IUnitOfWork _uow;
        public NewsController(INewsService newsService, IUnitOfWork uow)
        {
            _newsService = newsService;
            _uow = uow;
        }
        [HttpPost]
        [ValidateAntiForgeryToken]
        [ValidateInput(false)]
        public ActionResult Create(News news)
        {
            if (ModelState.IsValid)
            {
                _newsService.AddNews(news);
                _uow.SaveChanges();
                Success(string.Format("خبر با عنوان  <b>{0}</b> با موفقیت ذخیره گردید!", news.Title), true);
                return RedirectToAction("Index");
            }
            Danger("خطا در هنگام ثبت اطلاعات ");
            return View(news);
        }
        [HttpPost]
        public ActionResult Delete(int id)
        {
            _newsService.DeleteNewsById(id);
            _uow.SaveChanges();
            Danger("اطلاعات مورد نظر با موفقیت حذف گردید!", true);
            return RedirectToAction("Index");
        }
}

نمایش پیام‌ها 
برای نمایش پیام‌ها یک partial view با نام _Alerts در مسیر Views\Shared ایجاد می‌کنیم: 
@{
    var alerts = TempData.ContainsKey(Alert.TempDataKey)
                ? (List<Alert>)TempData[Alert.TempDataKey]
                : new List<Alert>();

    if (alerts.Any())
    {
        <hr />
    }

    foreach (var alert in alerts)
    {
        var dismissableClass = alert.Dismissable ? "alert-dismissable" : null;
        <div class="alert alert-@alert.AlertStyle @dismissableClass">
            @if (alert.Dismissable)
            {
                <button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button>
            }
            @Html.Raw(alert.Message)
        </div>
    }
}
در کد فوق، ابتدا شیء alerts را از TempData دریافت کرده‌ایم و توسط یک حلقه foreach، داخل آن به ازای هر آیتم، آن را پیمایش می‌کنیم و در نهایت کد html متناظر با هر alert را در خروجی نمایش می‌دهیم.
اکنون جهت استفاده از partial view فوق در جایی که می‌خواهید پیام نمایش داده شود partial view فوق را فراخوانی کنید (به عنوان مثال داخل فایل Layout): 
<div>
                    @{ Html.RenderPartial("_Alerts"); }
                    @RenderBody()
</div>

مطالب
بررسی وجود نام کاربر با استفاده از jQuery Ajax در ASP.Net

شاید بعضی از سایت‌ها را دیده باشید که در حین ثبت نام، پس از وارد کردن یک نام کاربری و سپس مشغول شدن به پر کردن فیلد کلمه‌ی عبور، در قسمت نام کاربری شروع به جستجو در مورد آزاد بودن نام کاربری درخواستی می‌کنند یا نمونه‌ای دیگر، فرم پرداخت الکترونیکی بانک سامان. پس از اینکه شماره قبض را وارد کردید، بلافاصله بدون ریفرش صفحه به شما پیغام می‌دهد که این شماره معتبر است یا خیر. امروز قصد داریم این قابلیت را با استفاده از کتابخانه‌ی Ajax مجموعه jQuery در ASP.Net پیاده سازی کنیم (بدون استفاده از ASP.Net Ajax مایکروسافت).
ابتدا سورس کامل را ملاحظه نمائید:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="AjaxTest.aspx.cs" Inherits="testWebForms87.AjaxTest" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>jQuery Ajax Text</title>

<script src="jquery.min.js" type="text/javascript"></script>
<script type="text/javascript">
$(document).ready(function() {
$("#<%= TextBox1.ClientID %>").blur(function(event) {
$.ajax({
type: "POST",
url: "AjaxTest.aspx/IsUserAvailable",
data: "{'username': '" + $('#<%= TextBox1.ClientID %>').val() + "'}",
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function(msg) {
$('#valid').html("<img src='ajaxImages/waiting.gif' alt='لطفا کمی تامل کنید'>");
var delay = function() {
AjaxSucceeded(msg);
};

setTimeout(delay, 2000); //remove this
},
error: AjaxFailed
});
});
});
function AjaxSucceeded(result) {
if (result.d == true)
$('#msg').html("<img src='ajaxImages/available.gif' alt='نام کاربری درخواستی موجود است'>");
else
$('#msg').html("<img src='ajaxImages/taken.gif' alt='متاسفانه نام کاربری مورد نظر پیشتر دریافت شده‌است'>");
}
function AjaxFailed(result) {
alert(result.status + ' ' + result.statusText);
}
</script>

</head>
<body>
<form id="form1" runat="server">
<div>
user name:
<asp:TextBox ID="TextBox1" runat="server"></asp:TextBox>
<span id="msg"></span>
<br />
pass:
<asp:TextBox ID="TextBox2" TextMode="Password" runat="server"></asp:TextBox>
</div>
<!-- preload -->
<div style="display: none">
<img src="ajaxImages/available.gif" alt="available" />
<img src="ajaxImages/taken.gif" alt="taken" />
<img src="ajaxImages/waiting.gif" alt="waiting" />
</div>
</form>
</body>
</html>


using System;
using System.Web.Services;

namespace testWebForms87
{
public partial class AjaxTest : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
}

[WebMethod]
public static bool IsUserAvailable(string username)
{
// این مورد را با خواندن اطلاعات از دیتابیس می‌شود تعویض کرد
return username != "test";
}
}
}

توضیحات:
همانطور که ملاحظه می‌کنید صفحه‌ی ASP.Net ما بسیار ساده است و از دو تکست باکس استاندارد تشکیل می‌شود، به همراه تصاویر مربوط به Ajax که یک سری تصاویر ساده چرخان معروف منتظر بمانید ، یافت شد یا موجود نیست می‌باشند. این تصاویر در یک div مخفی (display: none) در صفحه قرار گرفته‌اند و در هنگام بارگذاری صفحه، این‌ها نیز بارگذاری شده و حاضر و آماده خواهند بود. بنابراین هنگام استفاده از آن‌ها، کاربر تاخیری را مشاهده نخواهد کرد. همچنین یک span با id مساوی msg‌ را هم پس از تکست باکس اضافه کرده‌ایم تا تصاویر مربوط به رخدادهای Ajax را با استفاده از توانایی‌های jQuery به آن اضافه کنیم.

اسکریپت Ajax ما با دراختیار گرفتن روال رخداد گردان blur شیء textBox1 شروع می‌شود. همانطور که در مقالات پیشین سایت نیز ذکر شد، روش صحیح دریافت ID یک کنترل ASP.Net در کدهای سمت کلاینت جاوا اسکریپتی، بر اساس خاصیت ClientID آن است که در اولین سطر کدهای ما مشخص است (زیرا در ASP.Net نام و ID یک کنترل در هنگام رندر شدن به همراه ID کنترل‌های دربرگیرنده آن نیز خواهد بود، بنابراین بهتر است این مورد را داینامیک کرد).

کار بررسی موجود بودن نام کاربری (یا مثلا یک شماره قبض و امثال آن) توسط WebMethod ایی به نام IsUserAvailable در code behind صفحه انجام می‌شود که پیاده سازی آن‌را ملاحظه می‌کنید. بدیهی است در این مثال ساده، تنها نام کاربری از پیش رزرو شده، کلمه‌ی test است و در یک کد واقعی این مورد با مقایسه‌ی نام کاربری با اطلاعات موجود در دیتابیس باید صورت گیرد (و حملات تزریق اس کیوال را هم فراموش نکنید. برای رهایی از آن‌ها "حتما" باید از پارامترهای ADO.Net استفاده کرد و گرنه کد شما مستعد به این نوع حملات خواهد بود).

سؤال: چرا از web method استفاده شد و همچنین چرا این متد static است؟
زمانیکه یک متد با کلمه کلیدی static‌ مشخص می‌شود حالت state less پیدا می‌کند یعنی مستقل از وهله‌ی کلاس عمل می‌کند. در این حالت نیازی به ارسال ViewState نبوده (بنابراین در کوئری مورد نظر ما بسیار بهینه و سبک عمل می‌کنند) و همچنین نیازی به ایجاد یک وهله‌ای از کلاس صفحه‌ی ما نیز نخواهد بود. برای توضیحات بیشتر به این مقاله مراجعه نمائید. (به صورت خلاصه، دلیل اصلی، کارآیی بالا و بهینه بودن این روش در این مساله ویژه است و در ASP.Net Ajax مایکروسافت به صورت گسترده‌ای در پشت صحنه مورد استفاده قرار می‌گیرد)
استفاده از ویژگی WebMethod عملکرد صفحه‌ی ما را شبیه به یک وب سرویس خواهد کرد و امکان دسترسی به آن در متدهای استاندارد POST به صورت ارسال دیتا به آدرس WebService.asmx/WebMethodName‌ خواهد بود. یک مثال ساده و عملی

بررسی تابع Ajax بکار رفته:
این تابع هنگام فراخوانی رخداد blur تکست‌باکس ما (مطابق کد فوق) فراخوانی می‌شود. ساختار ساده‌ای دارد که به شرح زیر است:
type: "POST"
نحوه‌ی ارسال داده را به متد وب سرویس ما مشخص می‌کند.

url: "AjaxTest.aspx/IsUserAvailable"
اطلاعات پست شده، به صفحه‌ی AjaxTest.aspx و وب متد IsUserAvailable ارسال خواهد شد

data: "{'username': '" + $('#<%= TextBox1.ClientID %>').val() + "'}",
داده‌ای که به آرگومان username ما ارسال می‌شود، همان مقدار تایپ شده در TextBox1 است (که باز هم دریافت ID آن به صورت داینامیک صورت گرفته تا مشکل زا نشود)

contentType: "application/json; charset=utf-8",
dataType: "json",
در این دو سطر از نوع داده‌ی json استفاده شده است که فرمت بسیار سبک و بهینه‌ای برای تبادل اطلاعات در وب به‌شمار می‌آید و توسط کتابخانه‌های جاوا اسکریپتی به سادگی پردازش شده و تبدیل به اشیاء مورد نظر خواهند شد. برای مثال اگر خروجی یک وب سرویس در حالت xml به صورت زیر باشد:

<xx yy="nn"></xx>
معادل json آن به شرح زیر است:
{ "xx": {"yy":"nn"} }
success: function(msg)
Success ، پس از موفقیت آمیز بودن عملیات ajax در jQuery فراخوانی می‌شود
error: AjaxFailed
و اگر در این بین خطایی رخ داده باشد، قسمت error فراخوانی می‌شود.

در این مثال برای نمایش بهتر عملیات، یک وقفه‌ی 2 ثانیه‌ای توسط setTimeout ایجاد شده و بدیهی است در یک مثال واقعی باید آن‌را حذف نمود.

نکته: با استفاده از افزونه‌ی فایرباگ فایرفاکس، می‌توان جزئیات این عملیات را بهتر مشاهده نمود:





مطالب
Angular Material 6x - قسمت دوم - معرفی Angular Flex layout
در این سری قصد داریم یک برنامه‌ی ساده‌ی دفترچه تلفن را توسط Angular 6x و کامپوننت‌های متریال آن ایجاد کنیم؛ اما Grid جزئی از بسته‌ی Angular Material نیست. بنابراین برای طرحبندی برنامه و قرار دادن المان‌های مختلف در مکان‌های تعیین شده‌ی صفحه، از Angular FlexBox Module استفاده خواهیم کرد که محصور کننده‌ی CSS 3 FlexBox است.


آشنایی با Flex Layout Box Model

برای طراحی ظاهر یک برنامه‌ی وب نیاز است عناصر آن‌را در مکان‌های مختلفی از صفحه قرار داد که به آن Layout گفته می‌شود. برای این منظور عموما 4 روش ذیل مرسوم هستند:
1. Table
2. Float, position, clear
3. CSS Grids
4. FlexBox CSS

امروزه دیگر آنچنان روش‌های 1 و 2 به صورت مستقیم مورد استفاده قرار نمی‌گیرند. CSS Grid روش نهایی طراحی Layout در آینده خواهد بود و در حال حاضر تعداد مرورگرهایی که از آن پشتیبانی می‌کنند، قابل توجه نیست؛ اما از FlexBox در IE 11، کروم 21 و فایرفاکس 22 به بعد پشتیبانی می‌شود.


FlexBox CSS، سیلان المان‌های قرار گرفته‌ی در داخل آن‌را سبب می‌شود. در اینجا یک container اصلی وجود دارد که در برگیرنده‌ی المان‌ها است. در تصویر فوق دو محور را مشاهده می‌کنید. محور افقی از چپ به راست ادامه پیدا می‌کند. محور عمودی نحوه‌ی ارتباط عناصر را مشخص می‌کند.
اکنون این سؤال مطرح می‌شود که چه تفاوتی بین یک Grid و FlexBox CSS وجود دارد؟ در یک Grid طراحی دو بعدی سطرها و ستون وجود دارد. اما به FlexBox باید به صورت سیلان یک بعدی سلول‌ها نگاه کرد. برای مثال عناصر قرار گرفته‌ی درون Container یا به صورت افقی درون آن گسترده شده و قرار می‌گیرند و یا به صورت عمودی.


نحوه‌ی تفکر و کارکرد با FlexBox چگونه است؟

در اینجا باید به دو مفهوم دقت داشت:
الف) سیلان عناصر درون Container که می‌تواند افقی و یا عمودی باشد.
ب) اندازه‌ی المان‌ها که می‌تواند ثابت و یا نسبی باشد.

یک Container، جهت سیلان عناصر درون آن‌را مشخص می‌کند. المان‌های آن، اندازه، فاصله‌ی از یکدیگر و ترتیب قرارگیری را ارائه می‌دهند. یک flex container می‌تواند شامل چندین flex container تو در تو نیز باشد.


نحوه‌ی سیلان عناصر در FlexBox چگونه است؟

برای نمونه طرحبندی متداول ذیل را درنظر بگیرید:


نحوه تفکر در مورد طراحی این طرحبندی، باید از بیرون به درون و از بالا به پایین (سیلان عمودی) باشد:


سپس نحوه‌ی سیلان عناصر درون Containerهای تعریف شده را مشخص می‌کنیم. برای مثال اولین Container دارای سیلان افقی از چپ به راست خواهد بود که عنصر سوم آن به دلیل اندازه‌های مشخص دو عنصر قبلی، به سطر دوم منتقل شده‌است.


در ادامه به قسمت میانی می‌رسیم که آن نیز دارای سیلان افقی از چپ به راست است:


در اینجا نیز می‌توان سه Container را متصور شد که وسطی دارای سیلان افقی از راست به چپ است و مواردی که بر اساس اندازه‌ی آن‌ها در یک سطر جا نشده‌اند، به سطر بعدی منتقل خواهند شد:


و تمام این سیلان‌ها و انتقال به سطرهای بعدی بر اساس اندازه‌ی المان‌ها صورت می‌گیرد:


البته در این تصویر یک ایراد هم وجود دارد. با توجه به اینکه در ناحیه‌ی میانی سه Container تعریف خواهند شد. Container ایی که در میان آن قرار می‌گیرد، دارای سیلان خاص خودش است و اندازه‌های آن باید نسبت به این Container تعریف شوند و نه نسبت به کل ناحیه‌ی میانی. یعنی بجای اینکه 50 درصد، 25، 25 و 50 درصد را داشته باشیم، این‌ها در اصل 100 درصد، 50 و 50 درصد و سپس 100 درصد هستند.


معرفی کتابخانه‌ی Angular Flex Layout

برای کار با Flex CSS نیاز است:
- مقدار زیادی کد CSS نوشت.
- نیاز به درک عمیقی از Flex Box دارد.
- نیاز است با باگ‌های مرورگرها و تفاوت‌های پیاده سازی‌های آن‌ها در مورد FlexBox آشنا بود.
- نیاز به Prefixing دارد.
- برای Angular طراحی نشده‌است.

جهت رفع این مشکلات و محدودیت‌ها، تیم Angular کتابخانه‌ای را به نام Angular Flex Layout مخصوص نگارش‌های جدید Angular طراحی کرده‌است. این کتابخانه مستقل از Angular Material است اما عموما به همراه آن استفاده می‌شود.

مزایای کار با کتابخانه‌ی Angular flex layout
- یک کتابخانه‌ی متکی به خود و مستقل است و برای کار با آن الزامی به استفاده‌ی از Angular Material نیست.
- به همراه هیچ فایل CSS جانبی ارائه نمی‌شود.
- پیاده سازی TypeScript ایی دارد. در اصل یک سری directives مخصوص Angular است که با TypeScript نوشته شده‌است.
- به صورت پویا و inline تمام CSSهای مورد نیاز را تولید و تزریق می‌کند.
- به همراه یک API استاتیک است و همچنین یک API واکنشگرا
- با Angular CLI نیز یکپارچه شده‌است.


نصب و تنظیم کتابخانه‌ی Angular Flex layout

برای نصب این کتابخانه، در ریشه‌ی پروژه دستور زیر را صادر کنید:
 npm install @angular/flex-layout --save
سپس ماژول آن‌را باید به shared.module.ts اضافه کرد:
import { FlexLayoutModule } from "@angular/flex-layout";

@NgModule({
  imports: [
    FlexLayoutModule
  ],
  exports: [
    FlexLayoutModule
  ]
})
export class SharedModule {
}


کار با API استاتیک Angular Flex layout

API استاتیک Angular Flex layout شامل این مزایا و مشخصات است:
- به صورت یکسری دایرکتیو Angular طراحی شده‌است که به HTML قالب کامپوننت‌ها اضافه می‌شود.
- از data binding پشتیبانی می‌کند.
- CSS نهایی را به صورت پویا و inline تولید و به صفحه تزریق می‌کند. Inline CSS تزریق شده به ویژگی‌های styles هر المان تزریق می‌شوند و موارد مشابه را در صورت وجود بازنویسی می‌کنند.
- از تشخیص تغییرات پشتیبانی می‌کند.
- به همراه ویژگی‌های fxHide و fxShow است.
- کارآیی مطلوبی دارد.

در اینجا برای تعریف container اصلی از دایرکتیوهای زیر استفاده می‌شود:
- fxLayout جهت‌های flex را مشخص می‌کند.
<div fxLayout="row" 
     fxLayout.xs="column"></div>
- fxLayout می‌تواند دارای مقداری مانند row، column و row-reverse و column-reverse باشد. برای مثال مقدار row-reverse‌، نمایش از راست به چپ را سبب می‌شود.
- fxLayoutWrap مشخص می‌کند که آیا المان‌ها باید به سطر و یا ستون بعدی منتقل شوند یا خیر؟
<div fxLayoutWrap></div>
- fxLayoutGap فاصله‌ی بین المان‌ها را مشخص می‌کند.
<div fxLayoutGap="10px"></div>
- fxLayoutAlign نحوه‌ی چیدمان المان را تعیین می‌کند.
<div fxLayoutAlign="start stretch"></div>

چند مثال:


و یا حالت راست به چپ آن به صورت زیر است:


و برای تعریف آیتم‌های قرار گرفته‌ی درون containers می‌توان از دایرکتیوهای زیر استفاده کرد:
- fxflex برای تعیین اندازه و flex المان‌ها
<div fxFlex="1 2 calc(15em + 20px)"></div>
در اینجا سه مقداری که ذکر می‌شوند (و یا تنها یک مقدار) چنین معنایی را به همراه دارند:
 fxFlex="grow shrink basis"
و یا
 fxFlex="basis"
- grow به این معنا است که آیتم جاری در صورت وجود فضا (طراحی واکنشگرا و واکنش نشان دادن به اندازه‌ی صفحه)، نسبت به سایر المان‌ها تا چه اندازه‌ای می‌تواند بزرگ شود.
- shrink به این معنا است که اگر به اندازه‌ی کافی فضا وجود نداشت، این المان نسبت به سایر المان‌های دیگر تا چه اندازه‌ای می‌تواند کوچک شود.
- basis به معنای اندازه‌ی پیش‌فرض المان است.

در اینجا اندازه‌ها برحسب پیکسل، درصد و یا calcs, em, cw, vh می‌توانند تعیین شوند. همچنین یک سری نام مستعار مانند grow, initial, auto, none, nogrow, noshrink هم قابل استفاده هستند.

- fxflexorder برای تعیین ترتیب قرارگیری یک المان
<div fxFlexOrder="2"></div>
-  fxflexoffset برای تعیین فاصله یک المان از container آن
 <div fxFlexOffset="20px"></div>
-  fxflexAlign برای تعیین محل قرارگیری المان
 <div fxFlexAlign="center"></div>
- fxflexfill برای تعیین اینکه این المان کل ردیف یا ستون را پر خواهد کرد
 <div fxFlexFill></div>

چند مثال:


در اینجا سه نمایشی را که در ذیل تعریف div‌ها مشاهده می‌کنید بر اساس تغییر اندازه‌ی صفحه حاصل شده‌اند. چون آیتم دوم دارای مقدار grow مساوی 5 است، به همین جهت با تغییر اندازه‌ی صفحه و دسترسی به مقدار فضای بیشتر، بزرگ‌تر شده‌است.

یک مثال کامل
اگر علاقمند باشید تا توانمندی‌های angular flex layout را در قالب یک مثال کامل مشاهده کنید، به آدرس زیر مراجعه نمائید:
https://tburleson-layouts-demos.firebaseapp.com/#/docs
در این مثال با تغییر گزینه‌‌های مختلف، کد معادل angular flex layout آن نیز تولید می‌شود.
همچنین wiki خود پروژه نیز به همراه مثال‌های بیشتری است:
https://github.com/angular/flex-layout/wiki



کار با API واکنشگرای Angular Flex layout


در طراحی واکنشگرا، container و عناصر داخل آن بر اساس تغییرات اندازه‌ی صفحه و یا اندازه‌ی وسیله‌ی نمایشی، تغییر اندازه و همچنین موقعیت می‌دهند و این تغییرات بر اساس انطباق با viewport وسیله‌ی نمایشی صورت می‌گیرند. به همین جهت برای طراحی واکنشگرا نیاز به Flex CSS و همچنین Media Query است. نوشتن Medial Query و ترکیب آن با Flex CSS کار مشکلی است. به همین جهت Angular Flex layout به همراه یک API واکنشگرا نیز هست که در پشت صحنه Flex CSS را بر اساس طراحی متریال و Medial Queries مورد استفاده قرار می‌دهد.
اگر علاقمند هستید تا اندازه‌های واکنشگرای استاندارد متریال را ملاحظه کنید، می‌توانید به آدرس زیر مراجعه نمائید (قسمت Breakpoint system آن):
https://material.io/design/layout/responsive-layout-grid.html#breakpoints
برای مثال هر اندازه‌ای کمتر از 600px در گروه extra small قرار می‌گیرد (با مخفف xs). از 600px تا 1024px در بازه‌ی small (با مخفف sm)، از 1024px تا 1440px در بازه‌ی medium (با مخفف md) و از 1440px تا 1920px در بازه‌ی large (با مخفف lg) و بیشتر از آن در بازه‌ی xlrage قرار می‌گیرند (با مخفف xl). این اعداد و بازه‌ها، پایه‌ی طراحی API واکنشگرای Angular Flex layout هستند. به همین جهت نام این بازه‌ها در این API به صورت مخفف xs, sm, md, lg, xl درنظر گرفته شده‌اند و مورد استفاده قرار می‌گیرند. همچنین اگر اندازه‌های مدنظر از این بازه‌ها کمتر باشند، می‌توان از lt-sm, lt-md, lt-lg, lt-xl نیز استفاده کرد. در اینجا lt به معنای less than است و یا اگر بازه‌های مورد نیاز بیش از این اندازه‌ها باشند می‌توان با gt-xs, gt-sm, gt-md, gt-lg کار کرد. در اینجا gt به معنای greater than است.
به این مخفف‌ها «media query alias» هم گفته می‌شود و اکنون که لیست آن‌ها مشخص است، تنها کافی است آن‌ها را به API استاتیکی که پیشتر بررسی کردیم، اضافه کنیم. برای مثال:
fxLayout.sm = "..."
fxLayoutAlign.md = "..."
fxHide.gt-sm = "..."
برای نمونه فرض کنید یک چنین طرحبندی دسکتاپی را داریم:


معادل طراحی آن با API استاتیک Angular Flex Layout به صورت زیر است:

که در اینجا دو container را ملاحظه می‌کنید. ابتدا Container بیرونی جهت ارائه‌ی ستونی از سه المان اضافه شده‌است. سپس یک Container میانی برای  تعریف ردیفی از سه المان تعریف شده‌است. توسط روش "fxFlex="grow shrink basis نیز اندازه‌های آن‌ها مشخص شده‌اند.

اکنون که این طرحبندی دسکتاپ را داریم، چگونه باید آن‌را تبدیل به طرحبندی موبایل، مانند شکل زیر کنیم؟


برای اینکار ابتدا fxLayout.xs را به سطر میانی اضافه می‌کنیم تا هرگاه به این اندازه رسیدیم، بجای ردیف، تبدیل به ستون شود. سپس توسط fxFlexOrder.xs، در اندازه‌ی xs، محل قرارگیری المان‌های این ستون را هم مشخص می‌کنیم:


همانطور که ملاحظه می‌کنید کار کردن با این API بسیار ساده‌است و نیازی به کارکردن مستقیم با Media Queries و یا برنامه نویسی مستقیم ندارد و تمام آن در قالب HTML یک کامپوننت قابل پیاده سازی است.
یک نکته: مثال کاملی که پیشتر در این بحث مطرح شد، به همراه مثال واکنشگرا نیز هست که برای مشاهده‌ی اثر آن‌ها بهتر است اندازه‌ی مرورگر را کوچک و بزرگ کنید.


مخفی کردن و یا نمایش قسمتی از صفحه بر اساس اندازه‌ی آن

علاوه بر media query alias هایی که عنوان شد، امکان نمایش و یا مخفی سازی قسمت‌های مختلف صفحه بر اساس اندازه‌ی صفحه‌ی نمایشی نیز هست:
 <div fxShow fxHide.xs="false" fxHide.lg="true"></div>
در اینجا fxShow سبب نمایش این div در حالت عادی می‌شود (پیش‌فرض آن xl، md و sm است). اما اگر اندازه‌ی صفحه lg باشد، fxHide.lg تنظیم شده‌ی به true سبب مخفی سازی آن خواهد شد و در اندازه‌ی xs مجددا نمایش داده می‌شود.


تغییر اندازه‌ی قسمتی از صفحه بر اساس اندازه‌ی آن

در مثال زیر اگر اندازه‌ی صفحه gt-sm باشد (بیشتر از small)، اندازه‌ی این div به 100 درصد بجای 50 درصد حالت‌های دیگر، تنظیم می‌شود:
 <div fxFlex="50%" fxFlex.gt-sm="100%"></div>

حالت‌های ویژه‌ی طراحی واکنشگرا در Angular Flex Layout

در API واکنشگرای آن حالت‌های ویژه‌ی fxshow, fxhide, ngclass  و  ngstyle نیز درنظر گرفته شده‌اند که امکان فعالسازی آن‌ها در اندازه‌های مختلف صفحه مسیر است:
<div fxShow [fxShow.xs]="isVisibleOnMobile()"></div>
<div fxHide [fxHide.gt-sm]="isVisibleOnDesktop()"></div>
<div [ngClass.sm]="{'fxClass-sm': hasStyle}" ></div>
<div [ngStyle.xs]="{color: 'blue'}"></div>


امکان کار با API واکنشگرا از طریق برنامه نویسی

برای این منظور می‌توان از سرویس ObservableMedia مانند مثال زیر استفاده کرد:


در اینجا به فعالسازی یک بازه‌ی خاص گوش فرا خواهیم داد. برای مثال اگر اندازه‌ی صفحه xs بود، سبب بارگذاری محتوای خاص مرتبط با موبایل خواهیم شد.



برای مطالعه‌ی بیشتر
قسمت‌های عمده‌ای از مطلب جاری، از ویدیوی زیر که توسط نویسنده‌ی اصلی angular flex layout تهیه شده‌است، گردآوری شدند.