نظرات مطالب
ارتقاء به ASP.NET Core 1.0 - قسمت 5 - فعال سازی صفحات مخصوص توسعه دهنده‌ها
من از این میدلور استفاده کردم و زمانیکه خطایی رخ دهد فرضا آیدی درخواست بشه که توی رنج ایدیهای ثبت شده نباشد وارد پیج ارور میشود .
اما زمانیکه کاربر درخواست یک صفحه بیهوده را داشته باشد فرضا https://localhost:44330/mmmnbb  یک صفحه 404 نمایش میدهد و به صفحه error/index هدایت نمی‌شود .
مطالب
واکشی اطلاعات به صورت chunk chunk (تکه تکه) و نمایش در ListView
یکی از مسائلی که در هنگام کار با کنترلهای داده‌ای نظیر ListView , GridView و .. با آن روبرو هستیم مسئله صفحه بندی می‌باشد و در بسیاری از موارد، کل اطلاعات در هر درخواست، بارگذاری میشود. در حالیکه روش بهینه به این صورت است که با توجه به PageSize و Index رکورد، می‌توان تعداد رکورد مورد نظر در همان صفحه را بارگذاری کرد، نه کل رکوردها را.
در این مثال که از Ef Code First و الگوی Unit Of Work استفاده کرده ام ، قصد نمایش اطلاعات در یک ListView و صفحه بندی آن را بصورت chunk chunk دارم.
برای آشنایی بیشتر با الگوی Unit Of Work  می‌توانید به مقاله UOW در همین سایت مراجعه کنید.
   
ابتدا یک کنترل ListView روی صفحه ایجاد می‌کنیم. برای آشنایی بیشتر با این کنترل و بررسی قابلیتهای آن میتوان از این مقاله معرفی کنترل‌های ListView و DataPager استفاده کنید.

سپس با توجه به الگوی Unit Of Work از یک مدل ساده استفاده می‌کنیم. همچنین برای بارگذاری اطلاعات به صورت صفحه به صفحه، نیاز به داشتن Index رکورد و PageSize، جهت محاسبه تعداد رکورد مورد نیاز داریم.

public class User
    {       
        public Int64 UserId { get; set; }       
        public String UserName { get; set; }
     }        
 public interface IUserService
    {        
        int GetCustomerCount();
        List<User> GetCustomers(int StartIndex, int PageSize);
    }
public class ImplUserService : IUserService
    {
        IUnitOfWork _uow;
        IDbSet<User> _user;

        public ImplUserService(IUnitOfWork uow)
        {
            _uow = uow;
            _user = uow.Set<User>();
        }

        public int GetCustomerCount()
        {
            int totalCount = _user.ToList().Count;             
            return totalCount;
        }

        public List<User> GetCustomers(int StartIndex, int PageSize)
        {
            return _user.OrderBy(i => i.UserId).Skip(StartIndex).Take(PageSize).ToList();
        }
  }
در کدهای بالا متد GetCustomerCount تعداد کل رکوردهایی که باید واکشی شوند را مشخص می‌کند. همچنین می‌توان این تعداد را در cache یا viewState ذخیره کرد تا در دفعات بعدی در صورت خالی نبودن مقدار Cacheبا viewState ، نیاز به محاسبه مجدد count نداشته باشیم.

متد GetCustomer که کار اصلی را انجام میدهد از دو پارامتر استفاده می‌کند: StartIndex نقطه شروع و PageSize تعداد رکورد مورد نظر. در اینجا از دستورات Linq استفاده شده و دستور Skip مشخص میکند از کدام شماره رکورد به بعد شروع به واکشی نماید و دستور Take مشخص می‌کند که چه تعداد رکورد را واکشی نماید.

حالا به سراغ کدهای HTML می‌رویم. در آنجا علاوه بر ListView نیاز به DataPager جهت صفحه بندی و Object DataSource جهت کنترل بارگذاری اطلاعات به صورت chunk chunk داریم.
<asp:ListView ID="ListView1" runat="server" DataSourceID="ObjectDataSource1">
            <ItemTemplate>
                <tr style="background-color: #DCDCDC; color: #000000;">
                    <td>
                        <asp:Label ID="UserIdLabel" runat="server" Text='<%# Eval("UserId") %>' />
                    </td>
                    <td>
                        <asp:Label ID="UserNameLabel" runat="server" Text='<%# Eval("UserName") %>' />
                    </td>
                </tr>
            </ItemTemplate>
            
            
            <LayoutTemplate>
                <table runat="server">
                    <tr runat="server">
                        <td runat="server">
                            <table id="itemPlaceholderContainer" runat="server" border="1" style="background-color: #FFFFFF;
                                border-collapse: collapse; border-color: #999999; border-style: none; border-width: 1px;">
                                <tr runat="server" style="background-color: #DCDCDC; color: #000000;">
                                    <th runat="server">
                                        UserId
                                    </th>
                                    <th runat="server">
                                        UserName
                                    </th>
                                </tr>
                                <tr id="itemPlaceholder" runat="server">
                                </tr>
                            </table>
                        </td>
                    </tr>
                    <tr runat="server">
                        <td runat="server" style="text-align: center; background-color: #CCCCCC;
                            color: #000000;">
                            <asp:DataPager ID="DataPager1" runat="server" PageSize="2">
                                <Fields>
                                    <asp:NextPreviousPagerField ButtonType="Button" ShowFirstPageButton="True" ShowNextPageButton="False"
                                        ShowPreviousPageButton="False" />
                                    <asp:NumericPagerField />
                                    <asp:NextPreviousPagerField ButtonType="Button" ShowLastPageButton="True" ShowNextPageButton="False"
                                        ShowPreviousPageButton="False" />
                                </Fields>
                            </asp:DataPager>
                        </td>
                    </tr>
                </table>
            </LayoutTemplate>                        
        </asp:ListView>
همانظور که مشاهده میکنید در DataPager ، مقدار PageSize مشخص شده است.اما Object DataSource
<asp:ObjectDataSource ID="ObjectDataSource1" runat="server" EnablePaging="True" SelectCountMethod="GetCustomerCount"
            SelectMethod="GetCustomers" TypeName="UserService.ImplUserService" EnableViewState="False"
            MaximumRowsParameterName="PageSize" StartRowIndexParameterName="StartIndex">
که فکر می‌کنم با توجه به متدهای تعریف شده در بالا ، تعریف Object DataSource کاملاٌ گویا میباشد.

نکته مهم:
اگر الان برنامه را اجرا کنید با خطای No parameterless constructor defined for this object ر وبرو خواهید شد که جهت حل این مشکل از کد زیر استفاده میکنیم.
protected void ObjectDataSource1_ObjectCreating(object sender, ObjectDataSourceEventArgs e)
        {            
            e.ObjectInstance = ObjectFactory.GetInstance<IUserService>();            
        }
در واقع نیاز است تا یک وهله از کلاس مورد نظر را به Object DataSource معرفی کنیم.
حال با اجرای برنامه و Trace آن متوجه خواهید شد که با کلیک بر روی شماره صفحه، تنها به تعداد رکوردهای همان صفحه، واکشی خواهیم داشت.

با تشکر از راهنمایی‌های آقای نصیری، امیدوارم این مقاله برای دوستان مفید باشه. منتظر نظرات دوستان هستم و اینکه چه جوری بتونیم اینکار رو با jquery ajax و به صورت سبکتر انجام بدیم.

مطالب
بازسازی کد: استخراج متد (Extract method)
در این بازسازی، با قطعه کدی روبرو هستیم که می‌توانیم آن را به صورت یک متد جدید، گروه‌بندی کنیم. نام متد، توضیح دهنده عملیاتی است که توسط آن قطعه کد انجام می‌شود. 
این بازسازی یکی از پر استفاده‌ترین بازسازی‌های کد است. معمولا زمانیکه متدی بلند باشد یا تکه کدی نیاز به کامنتی برای توضیح درباره کاری که انجام می‌دهد داشته باشد، چنین بازسازی ای نیاز است. در صورتیکه نام متدها به طور مناسبی انتخاب شوند، ایجاد متدهای کوچک از دو جهت مفید است: 
  1. خواندن متن متدهای بالادستی را آسان‌تر می‌کند. در واقع نام متد عملا نقش کامنت را ایفا می‌کند. مراجعه کنید به Self documenting code  
  2.  احتمال اینکه متدها دوباره استفاده شوند را بالا می‌برد

الگوهای متفاوتی در مورد طول متدها وجود دارد. ولی قاعده کلی حاکم بر این مورد «ارتباط منطقی بدنه و نام متد» است. در صورت رعایت این قاعده، عموما متدهایی با اندازه مناسب به‌وجود می‌آیند. 
به‌طور مثال قطعه کد زیر را مشاهده کنید:  
public void PrintInfo(dynamic person)
{ 
       Console.WriteLine(person.Name); 
       Console.WriteLine(person.LastName); 
       Console.WriteLine(person.Job.Title); 
       Console.WriteLine(person.Job.Salary); 
} 
این قطعه کد را می‌توان به صورت زیر بازسازی کرد:
public void PrintInfo(dynamic person) 
{ 
       Console.WriteLine(person.Name); 
       Console.WriteLine(person.LastName); 
       PrintJob(person.Job); 
} 
public void PrintJob(dynamic job) 
{ 
       Console.WriteLine(job.Title); 
       Console.WriteLine(job.Salary); 
}


مراحل انجام این بازسازی کد 

بعد از تشخیص تکه کدی که نیاز به استخراج متد در آن وجود دارد، مراحل زیر را انجام دهید:
  1. متد جدیدی ایجاد کنید و نام آن را بر اساس کاری که قرار است تکه کد انتخاب شده انجام دهد، تعیین کنید (نام را بر اساس کاری که قرار است انجام دهد انتخاب نمایید؛ نه نحوه انجام دادن آن کار).
  2. تکه کد نیازمند استخراج را از بدنه متد قبلی به بدنه متد جدید کپی کنید.
  3. کد استخراج شده را برای پیدا کردن متغیرهای محلی به‌جا مانده از متد اصلی بررسی کنید. این متغیرها احتمالا متغیرهای محلی متد جدید و پارامترهای آن را تشکیل خواهند داد. 
  4. اگر متغیرهای محلی موجود در تکه کد استخراج شده فقط در آن استفاده شده‌اند، آن‌ها را به صورت متغیرهای محلی موقتی در متد جدید ایجاد کنید. 
  5. متغیرهای محلی تکه کد استخراج شده را بررسی کنید که آیا در جایی از آن ویرایش شده‌اند یا خیر؟ در صورتیکه ویرایش شده باشند، بررسی کنید که آیا می‌توان آن را به عنوان خروجی متد جدید در نظر گرفت و متغیرها را مقداردهی کرد یا خیر؟ (توضیح بیشتر در مثال) 
  6. پارامترهای متناظری را با متغیرهای محلی موقتی ایجاد شده، در متد جدید ایجاد کنید.
  7. بعد از این که تکلیف تمامی متغیرهای محلی مشخص شد، کد را کامپایل کنید.
  8. در متد مبدا تکه کد استخراج شده را به فراخوانی متد جدید تغییر دهید.
  9. کد را کامپایل و تست کنید .

مثال: تکه کدی با ویرایش کردن متغیرهای محلی 
ساده‌ترین حالتیکه کد استخراج شده از متغیرهای محلی متد مبدا استفاده می‌کند زمانی است که تنها مقادیر این متغیرها را می‌خواند. مثال ذکر شده در ابتدای نوشتار از این نوع است.
زمانی که متغیری در تکه کد استخراج شده ویرایش شده باشد، معمولا با دو حالت روبرو هستیم:
  • متغیر ویرایش شده فقط در تکه کد استخراج شده ویرایش و استفاده شده‌است.
  • متغیر ویرایش شده بعد از تکه کد استخراج شده نیز در بدنه متد اصلی مورد استفاده قرار گرفته‌است.
در حالت دوم نیاز است مقدار متغیر ویرایش شده، به متد بالادستی برگردانده شود. به طور مثال تکه کدی را در نظر بگیرید که مقدار رقم نهایی قابل پرداخت یک آیتم سفارش را در آن مقداردهی می‌کند. این روال را می‌توان به صورت زیر پیاده سازی کرد:
public void SetPayableAmount(dynamic orderItem) 
{ 
        var price = orderItem.Price; 
        var discount = 0; 
        var discountPlan = GetDiscountPlan(); 
        var discountPercentage = discountPlan.Percentage; 
        discount = price * discountPercentage; 
        orderItem.PayableAmount = orderItem.Price - discount 
} 
private dynamic GetDiscountPlan() 
{ 
        throw new NotImplementedException(); 
}
با کمی دقت در تکه کد بالا می‌توان به این نتیجه رسید که جدا کردن روال محاسبه تخفیف یک طراحی و بازسازی خوب است. در سه خطی که مربوط به محاسبه تخفیف است یک متغیر محلی به نام discount مقداردهی شده است و یک متغیر محلی به نام price فقط خوانده شده‌است. این کد را می‌توان به صورت زیر بازسازی کرد: 
public void SetPayableAmount(dynamic orderItem)
{ 
        var price = orderItem.Price; 
        var discount = GetDiscount(price); 
        orderItem.PayableAmount = orderItem.Price - discount; 
} 
public dynamic GetDiscount(decimal price) 
{ 
        var discountPlan = GetDiscountPlan(); 
        var discountPercentage = discountPlan.Percentage; 
        return price * discountPercentage; 
} 
private dynamic GetDiscountPlan() 
{ 
       throw new NotImplementedException(); 
}
در این بازسازی، به دلیل نیاز کد استخراج شده به مقدار متغیر price، این متغیر را به صورت یک پارامتر، به متد جدید ایجاد شده، ارسال کردیم. همچین مقدار تخفیف محاسبه شده را نیز به دلیل نیاز به آن در متد بالادستی به صورت مقدار خروجی به آن متد بازگرداندیم.

ابزارهای کمکی برای این بازسازی کد 

در ابزارهای زیر امکاناتی برای انجام این بازسازی کد تعبیه شده است:
مایکروسافت ویژوال استودیو: میانبر Ctrl+R,M را بعد از انتخاب کد منتخب برای استخراج، استفاده نمایید. 
Jetbrains Resharper: این ابزار را می‌توان با میانبرهای مایکروسافت ویژوال استودیو استفاده کرد. امکانات کلی این ابزار از امکانات موجود در مایکروسافت ویژوال استودیو بیشتر است.  
مطالب
بهبود کارآیی کنترل‌های لیستی WPF در حین بارگذاری تعداد زیادی از رکوردها
کنترل‌های WPF در حالت پیش فرض و بدون اعمال قالب خاصی به آن‌ها عموما خوب عمل می‌کنند. مشکل از جایی شروع می‌شود که قصد داشته باشیم حالت پیش فرض را اندکی تغییر دهیم و یا Visual tree این کنترل‌ها اندکی پیچیده شوند. برای نمونه مدل زیر را در نظر بگیرید:
using System;

namespace WpfLargeLists.Models
{
    public class User
    {
        public int Id { set; get; }
        public string FirstName { set; get; }
        public string LastName { set; get; }
        public string Address { set; get; }
        public DateTime DateOfBirth { set; get; }
    }
}
قصد داریم فقط 1000 رکورد ساده از این مدل را به یک ListView اعمال کنیم.
                    <ListView ItemsSource="{Binding UsersTab1}" Grid.Row="1" Margin="3">
                        <ListView.View>
                            <GridView>
                                <GridViewColumn Header="Id" Width="50" DisplayMemberBinding="{Binding Id, Mode=OneTime}" />
                                <GridViewColumn Header="FirstName" Width="100" DisplayMemberBinding="{Binding FirstName, Mode=OneTime}" />
                                <GridViewColumn Header="LastName" Width="100" DisplayMemberBinding="{Binding LastName, Mode=OneTime}" />
                                <GridViewColumn Header="Address" Width="100" DisplayMemberBinding="{Binding Address, Mode=OneTime}" />
                                <GridViewColumn Header="DateOfBirth" Width="150" DisplayMemberBinding="{Binding DateOfBirth, Mode=OneTime}" />
                            </GridView>
                        </ListView.View>
                    </ListView>
در اینجا UsersTab1، لیستی حاوی فقط 1000 رکورد از شیء User است. در حالت معمولی این لیست بدون مشکل بارگذاری می‌شود. اما با اعمال مثلا قالب MahApp.Metro، بارگذاری همین لیست، حدود 5 ثانیه با CPU usage صد در صد طول می‌کشد. علت اینجا است که در این حالت WPF سعی می‌کند تا ابتدا در VisualTree، کل 1000 ردیف را کاملا ایجاد کرده و سپس نمایش دهد.


راه حل توصیه شده برای بارگذاری تعداد بالایی رکورد در WPF  : استفاده از UI Virtualization

UI Virtualization روشی است که در آن تنها المان‌هایی که توسط کاربر در حال مشاهده هستند، تولید و مدیریت خواهند شد. در این حالت اگر 1000 رکورد را به یک ListBox یا ListView ارسال کنید و کاربر بر اساس اندازه صفحه جاری خود تنها 10 رکورد را مشاهده می‌کند، WPF فقط 10 عنصر را در VisualTree مدیریت خواهد کرد. با اسکرول به سمت پایین، مواردی که دیگر نمایان نیستند dispose شده و مجموعه نمایان دیگری خلق خواهند شد. به این ترتیب می‌توان حجم بالایی از اطلاعات را در WPF با میزان مصرف پایین حافظه و همچنین مصرف CPU بسیار کم مدیریت کرد. این مجازی سازی در WPF به وسیله VirtualizingStackPanel در دسترس است.

برای اینکه WPF virtualization به درستی کار کند، نیاز است یک سری شرایط مقدماتی فراهم شوند:
- از کنترلی استفاده شود که از virtualization پشتیبانی می‌کند؛ مانند ListBox و ListView.
- ارتفاع کنترل لیستی باید دقیقا مشخص باشد؛ یا درون یک ردیف از Grid ایی باشد که ارتفاع آن مشخص است. برای نمونه اگر ارتفاع ردیف Grid ایی که ListView را دربرگرفته است به * تنظیم شده، مشکلی نیست؛ اما اگر ارتفاع این ردیف به Auto تنظیم شده، کنترل لیستی برای محاسبه vertical scroll bar خود دچار مشکل خواهد شد.
- کنترل مورد استفاده نباید در یک کنترل  ScrollViewer محصور شود؛ در غیر اینصورت virtualization رخ نخواهد داد. علاوه بر آن در خود کنترل باید خاصیت ScrollViewer.HorizontalScrollBarVisibility نیز به Disabled تنظیم گردد.
- در کنترل در حال استفاده، ScrollViewer.CanContentScroll باید به true تنظیم شود.

مورد مشخص بودن ارتفاع بسیار مهم است. برای نمونه در برنامه‌ای پس از فعال سازی مجازی سازی، کنترل لیستی کلا از کار افتاد و حرکت scroll bar آن سبب بروز CPU Usage بالایی می‌شد. این مشکل با تنظیم ارتفاع آن به شکل زیر برطرف شد:
 Height="{Binding Path=RowDefinitions[1].ActualHeight, RelativeSource={RelativeSource AncestorType=Grid}}"
در این تنظیم، ارتفاع کنترل، به ارتفاع سطر دوم گرید دربرگیرنده ListView متصل شده است.

- پس از اعمال موارد یاد شده، باید VirtualizingStackPanel کنترل را فعال کرد. ابتدا دو خاصیت زیر باید مقدار دهی شوند:
   VirtualizingStackPanel.IsVirtualizing="True"
  VirtualizingStackPanel.VirtualizationMode="Recycling"
سپس ItemsPanelTemplate کنترل باید به صورت یک VirtualizingStackPanel مقدار دهی شود. برای مثال اگر از ListBox استفاده می‌کنید، تنظیمات آن به نحو زیر است:
 <ListBox.ItemsPanel>
    <ItemsPanelTemplate>
          <VirtualizingStackPanel IsVirtualizing="True" VirtualizationMode="Recycling" />
    </ItemsPanelTemplate>
</ListBox.ItemsPanel>
و اگر از ListView استفاده می‌شود، تنظیمات آن مشابه ListBox است:
 <ListView.ItemsPanel>
    <ItemsPanelTemplate>
          <VirtualizingStackPanel
               IsVirtualizing="True"
               VirtualizationMode="Recycling" />
    </ItemsPanelTemplate>
</ListView.ItemsPanel>
با این توضیحات ListView ابتدای بحث به شکل زیر تغییر خواهد یافت تا مجازی سازی آن فعال گردد:
                    <ListView ItemsSource="{Binding UsersTab2}" Grid.Row="1" Margin="3"
                              ScrollViewer.HorizontalScrollBarVisibility="Disabled"
                              ScrollViewer.CanContentScroll="True"
                              VirtualizingStackPanel.IsVirtualizing="True"
                              VirtualizingStackPanel.VirtualizationMode="Recycling">
                        <ListView.ItemsPanel>
                            <ItemsPanelTemplate>
                                <VirtualizingStackPanel 
                                        IsVirtualizing="True" 
                                        VirtualizationMode="Recycling" />
                            </ItemsPanelTemplate>
                        </ListView.ItemsPanel>
                        <ListView.View>
                            <GridView>
                                <GridViewColumn Header="Id" Width="50" DisplayMemberBinding="{Binding Id, Mode=OneTime}" />
                                <GridViewColumn Header="FirstName" Width="100" DisplayMemberBinding="{Binding FirstName, Mode=OneTime}" />
                                <GridViewColumn Header="LastName" Width="100" DisplayMemberBinding="{Binding LastName, Mode=OneTime}" />
                                <GridViewColumn Header="Address" Width="100" DisplayMemberBinding="{Binding Address, Mode=OneTime}" />
                                <GridViewColumn Header="DateOfBirth" Width="150" DisplayMemberBinding="{Binding DateOfBirth, Mode=OneTime}" />
                            </GridView>
                        </ListView.View>
                    </ListView>
کدهای کامل مثال فوق را از اینجا می‌توانید دریافت کنید: WpfLargeLists.zip
در این مثال دو برگه را ملاحظه می‌کنید. برگه اول حالت normal ابتدای بحث است و برگه دوم پیاده سازی UI Virtualization را انجام داده است.

مطالب
JSLint.VS

JSLint.VS افزونه‌ای است رایگان برای VS.Net2005/2008 جهت بررسی ساده‌تر مشکلات دستوری در فایل‌های JavaScript یک پروژه.


اکنون بجای اینکه در مرورگر به دنبال خطاهای گزارش شده بگردیم، می‌توان پیش از بررسی نهایی آن‌ها، در VS.Net‌ مشکلات ممکن را یافته و برطرف ساخت.
JSLint.VS به منوی کلیک راست بر روی یک فایل js اضافه می‌شود و یا در حالت انتخاب قطعه‌ای کد و سپس کلیک راست و بررسی مشکلات موجود و یا در حالت یکپارچه با امکانات build پروژه قابل استفاده است (برای یکپارچه سازی با Build باید به منوی Tools قسمت JSLint.VS options مراجعه کرد و سپس گزینه build را درصفحه ظاهر شده تیک زد).



پس از دریافت آن، محتویات پوشه bin آن‌را در مسیر زیر کپی نمائید:
%My Documents%/Visual Studio 2008/Addins
Or
%My Documents%/Visual Studio 2005/Addins

مطالب
ساخت یک گزارش ساز به کمک iTextSharp و Open Office

iTextSharp پایه کار با فایل‌های PDF را ارائه می‌دهد اما ابزاری را جهت ساده‌تر سازی تولید فایل‌های PDF به همراه ندارد؛ هر چند مثلا امکان تبدیل HTML به PDF را دارا است اما باید گفت: «تا حدودی البته». اگر نیاز باشد جدولی را ایجاد کنیم باید کد نویسی کرد، اگر نیاز باشد تصویری اضافه شود به همین ترتیب و الی آخر. البته این را هم باید در نظر داشت که کد نویسی انعطاف قابل توجهی را در اختیار برنامه نویس قرار می‌دهد؛ شاید به همین دلیل این روزها مباحث «Code first» بیشتر مورد توجه برنامه نویس‌ها است، تا مباحث «Wizard first» یک دهه قبل!
اما باز هم داشتن یک طراح بد نیست و می‌تواند در کاهش مدت زمان تولید نهایی یک فایل PDF مؤثر باشد. برای این منظور می‌توان از برنامه‌ی رایگان و معروف Open office استفاده کرد. توسط آن می‌توان یک فرم PDF را طراحی و سپس فیلدهای آن‌را (این قالب تهیه شده را) با iTextSharp پر کرد. این مورد می‌تواند برای تهیه گزارش‌هایی که تهیه آن‌ها با ابزارهای متداول گزارش سازی عموما میسر نیست،‌ بسیار مناسب باشد.


طراحی یک فرم PDF با استفاده از برنامه Open Office

آخرین نگارش برنامه Open office را از اینجا می‌توانید دریافت کنید و آنچنان حجمی هم ندارد؛ حدودا 154 مگابایت است.
پس از نصب و اجرای برنامه، حداقل به دو طریق می‌توان یک فرم جدید را شروع کرد:
الف) آغاز یک XML Form document جدید در Open office سبب خواهد شد که نوارهای ابزار طراحی فرم، مانند قرار دادن TextBox ، CheckBox و غیره به صورت خودکار ظاهر شوند.
ب) و یا آغاز یک سند معمولی و سپس مراجعه به منوی View->Toolbars->Form Controls هم همان حالت را به همراه خواهد داشت.


در اینجا برای طراحی یک گزارش یا فرم جدید تنها کافی است همانند روش‌های متداول تهیه یک سند معمولی رفتار کنیم و مواردی را که قرار است توسط iTextSharp مقدار دهی کنیم، با کنترل‌های نوار ابزار Form آن بر روی صفحه قرار دهیم که نمونه‌ی ساده آن‌را در شکل زیر ملاحظه می‌کنید:


برای گزارش‌های فارسی بهتر است Alignment یک کنترل به Right تنظیم شود و Border به حالت Without frame مقدار دهی گردد. نام این کنترل را هم بخاطر بسپارید و یا تغییر دهید. از این نام‌ها در iTextSharp استفاده خواهیم کرد. (صفحه خواص فوق با دوبار کلیک بر روی یک کنترل قرار گرفته بر روی فرم ظاهر می‌شود)

مرحله بعد، تبدیل این فرم به فایل PDF است. کلیک بر روی دکمه تهیه خروجی به صورت PDF در نوار ابزار اصلی آن برای اینکار کفایت می‌کند. این گزینه در منوی File نیز موجود است.


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


استفاده از iTextSharp جهت مقدار دهی فیلدهای یک فرم PDF

در ادامه می‌خواهیم این قالب گزارشی را که تهیه کردیم با کمک iTextSharp پر کرده و یک فایل PDF جدید تهیه کنیم. سورس کامل اینکار را در ذیل مشاهده می‌کنید:


using System;
using System.Diagnostics;
using System.IO;
using iTextSharp.text;
using iTextSharp.text.pdf;

namespace PdfForm
{
class Program
{
//روش صحیح ثبت و معرفی فونت در این کتابخانه
public static iTextSharp.text.Font GetTahoma()
{
var fontName = "Tahoma";
if (!FontFactory.IsRegistered(fontName))
{
var fontPath = Environment.GetEnvironmentVariable("SystemRoot") + "\\fonts\\tahoma.ttf";
FontFactory.Register(fontPath);
}
return FontFactory.GetFont(fontName, BaseFont.IDENTITY_H, BaseFont.EMBEDDED);
}

static void Main(string[] args)
{
string fileNameExisting = @"form.pdf";
string fileNameNew = @"newform.pdf";

using (var existingFileStream = new FileStream(fileNameExisting, FileMode.Open))
using (var newFileStream = new FileStream(fileNameNew, FileMode.Create))
{
var pdfReader = new PdfReader(existingFileStream);
using (var stamper = new PdfStamper(pdfReader, newFileStream))
{
//نکته مهم جهت کار با اطلاعات فارسی
//در غیراینصورت شاهد ثبت اطلاعات نخواهید بود
stamper.AcroFields.AddSubstitutionFont(GetTahoma().BaseFont);

//form.Fields.Keys = تمام فیلدهای موجود در فرم
var form = stamper.AcroFields;

//مقدار دهی فیلدهای فرم
form.SetField("TextBox1", "مقدار1");
form.SetField("TextBox2", "مقدار2");

// "Yes" and "Off" are valid values here
form.SetField("Check Box 1", "Yes");

// "" and "Off" are valid values here
form.SetField("Option Button 1", "");

// نحوه مقدار دهی لیست
form.SetListOption("ListBox1", new[] { "1مقدار یک", "مقدار دو1" }, null);
form.SetField("ListBox1", null);

// به این ترتیب فرم دیگر توسط کاربر قابل ویرایش نخواهد بود
//stamper.PartialFormFlattening --> جهت غیرقابل ویرایش نمودن فیلدی مشخص
stamper.FormFlattening = true;

stamper.Close();
pdfReader.Close();
}
}

Process.Start("newform.pdf");
}
}
}

توضیحات:
چون در اینجا فایل PDF، از پیش تهیه شده است، پس باید از اشیاء PdfReader و PdfStamper جهت خواندن و نوشتن اطلاعات در آن‌ها استفاده کرد. سپس توسط شیء stamper.AcroFields می‌توان به این فیلدها یا همان کنترل‌هایی که در برنامه‌ی Open office بر روی فرم قرار دادیم، دسترسی پیدا کنیم.
در ابتدا نیاز است فونت این فیلدها توسط متد AddSubstitutionFont مقدار دهی شود. این مورد برای گزارش‌های فارسی الزامی است؛ در غیراینصورت متنی را در خروجی مشاهده نخواهید کرد.
ادامه کار هم مشخص است. توسط متد form.SetField مقداری را به کنترل‌های قرار گرفته بر روی فرم نسبت می‌دهیم. آرگومان اول آن نام کنترل است و آرگومان دوم، مقدار مورد نظر می‌باشد. اگر کنترل CheckBox را بر روی صفحه قرار دادید، تنها مقدارهای Yes و Off را می‌پذیرد (آن هم با توجه به اینکه به کوچکی و بزرگی حروف حساس است). اگر یک Radio button یا در اینجا Option button را بر روی فرم قرار دادید، تنها مقدارهای خالی و Off را قبول خواهد کرد. نحوه‌ی مقدار دهی یک لیست هم در اینجا ذکر شده است.
در پایان چون نمی‌خواهیم کاربر نهایی قادر به ویرایش اطلاعات باشد، FormFlattening را true خواهیم کرد و به این ترتیب، کنترل‌ها فقط خواندنی خواهند شد. البته اگر همانطور که ذکر شد، border کنترل‌ها را در حین طراحی حذف کنید، PDF نهایی تولیدی یکپارچه و یک دست به نظر می‌رسد و اصلا مشخص نخواهد بود که این فایل پیشتر یک فرم قابل پر کردن بوده است.

مطالب
نحوه استفاده از Text template ها در دات نت - قسمت چهارم
در قسمت‌های قبلی (^ و ^ و ^) با Text Template در Visual Studio آشنا شدید. این قسمت برای تکمیل بحث در مورد ابزاری که Microsoft از آن در برنامه‌های خود از جمله Visual Studio جهت تولید کدهای اتوماتیک استفاده می‌نماید، صحبت خواهیم کرد.
قبل از آن بد نیست که بدانید چرا این ابزار T4  نام گرفته !
T4 مخفف  Text Template Transformation Toolkit می‌باشد (TTTT). شکل زیر مراحل اجرای یک کد Text Template را توسط T4 نشان میدهد: 


پس این ابراز، یک ابزار کاربردی می‌باشد که بدون Visual Studio نیز میتوان از آن استفاده کرد. نام فایل این ابزار، TextTransform.exe است و در مسیر زیر وجود دارد :

Program Files (x86)\Common Files\microsoft shared\TextTemplating\10.0
برای اطلاع از نحوه کار با TextTransform.exe خارج از محیط Visual Studio بهتر است دستور زیر را در cmd.exe اجرا کنید تا راهنمای استفاده و پارامتر‌های اختیاری آن را مشاهده نمایید:

  TextTransform.exe –h 

برای آزمایش، یک فایل متنی کنار فایل TextTransform.exe با نام Text2.tt ایجاد نمایید و کد زیر را در داخل آن بنویسید:
<#@ template debug="true" hostspecific="false" language="C#" #>
<#@ output extension=".txt"  #>

<#@ import namespace="System.Diagnostics" #>

Report In : <#= DateTime.Now #>

<#

Process[] Procs = Process.GetProcesses();
    for (int i = 0; i < Procs.Length; i++)
    {
        string Pstr = Procs[i].ProcessName + "  -|-  " + Procs[i].Id + Environment.NewLine ;
 #><#= Pstr #><#
    }

#>
 این مثال بعد از اجرا، لیست تمام Process های جاری سیستم را به همراه Id  آن‌ها، چاپ می‌نماید.
برای تولید فایل خروجی، دستور زیر را در cmd.exe اجرا کنید :
TextTransform.exe  -out  Report1.txt  Text2.tt
توجه کنید که فایل Text2.tt را کنار فایل TextTransform.exe قرار دهید و بعد از اجرای دستور بالا، باید خروجی در فایل Text2.tt در همان مسیر ایجاد گردد.

نکته: اگر User شما به این پوشه دسترسی ندارد و کاربر Admin نیستید احتمالا به مشکل بر می‌خورد. می‌توانید فایل TextTransform.exe را در مکان دیگری قرار دهید و دستور را از آن محل اجرا کنید و یا برای پوشه‌ی مذکور دسترسی ایجاد نمایید.

اگر میخواهید بیشتر در مورد معماری T4  بدانید بهتر است مقاله زیر را مطالعه کنید:  

http://www.olegsych.com/2008/05/t4-architecture/

نکته دیگر این که برای Visual Studio، ابزارهایی جهت بهبود کار با Text Template‌ها وجود دارند که با جستجوی T4 Editor، نمونه‌هایی از آنها را خواهید یافت. tangible T4 Editor نمونه ای از این Pluginها می‌باشد که به Visual Studio  افزوده می‌گردد و یا یک پروژه Open Source  هم برای آشنایی بسیار بیشتر با T4 در t4toolbox.codeplex.com وجود دارد که میتوانید مشاهده کنید.
مطالب
ساخت یک بلاگ ساده با Ember.js، قسمت سوم
پس از ایجاد کنترلرها، در این قسمت سعی خواهیم کرد تا آرایه‌ای ثابت از مطالب و نظرات را در سایت نمایش دهیم. همچنین امکان ویرایش اطلاعات را نیز به این آرایه‌های جاوا اسکریپتی مدل، اضافه خواهیم کرد.


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

فایل جدید Scripts\App\store.js را اضافه کرده و محتوای آن‌را به نحو ذیل تغییر دهید:
var posts = [
  {
      id: '1',
      title: "Getting Started with Ember.js",
      body: "Bla bla bla 1."
  },
  {
      id: '2',
      title: "Routes and Templates",
      body: "Bla bla bla 2."
  },
  {
      id: '3',
      title: "Controllers",
      body: "Bla bla bla 3."
  }
];
 
var comments = [
    {
        id: '1',
        postId: '3',
        text: 'Thanks!'
    },
    {
        id: '2',
        postId: '3',
        text: 'Good to know that!'
    },
    {
        id: '3',
        postId: '1',
        text: 'Great!'
    }
];
در اینجا دو آرایه ثابت از اشیاء مطالب و نظرات را مشاهده می‌کنید.
سپس جهت استفاده از آن، تعریف مدخل آن‌را به فایل index.html، پیش از تعاریف کنترلرها اضافه خواهیم کرد:
 <script src="Scripts/App/store.js" type="text/javascript"></script>


ویرایش قالب مطالب برای نمایش لیستی از عناوین ارسالی

قالب فعلی Scripts\Templates\posts.hbs صرفا دارای یک سری عنوان درج شده به صورت مستقیم در صفحه است. اکنون قصد داریم آن‌را جهت نمایش لیستی از آرایه مطالب تغییر دهیم.


همانطور که در تصویر ملاحظه می‌کنید، با درخواست آدرس صفحه‌ی مطالب، router آن مسیریابی متناظری را یافته و سپس بر این اساس، template، کنترلر و مدلی را انتخاب می‌کند. به صورت پیش فرض، قالب و کنترلر انتخاب شده، مواردی هستند همنام با مسیریابی جاری. اما مقدار پیش فرضی برای model وجود ندارد و باید آن‌را به صورت دستی مشخص کرد.
برای این منظور فایل Scripts\Routes\posts.js را به پوشه‌ی routes با محتوای ذیل اضافه کنید:
Blogger.PostsRoute = Ember.Route.extend({
    controllerName: 'posts',
    renderTemplare: function () {
        this.render('posts');
    },
    model: function () {
        return posts;
    }
});
در اینجا صرفا جهت نمایش پیش فرض‌ها و نحوه‌ی کار یک route، دو خاصیت controllerName و renderTemplare آن نیز مقدار دهی شده‌اند. این دو خاصیت به صورت پیش فرض، همنام مسیریابی جاری مقدار دهی می‌شوند و نیازی به ذکر صریح آن‌ها نیست. اما خاصیت model یک مسیریابی است که باید دقیقا مشخص شود. در اینجا مقدار آن‌را به آرایه posts تعریف شده در فایل Scripts\App\store.js تنظیم کرده‌ایم. به این ترتیب مدل تعریف شده در اینجا، به صورت خودکار در کنترلر posts و قالب متناظر با آن، قابل استفاده خواهد بود.
همچنین اگر به خاطر داشته باشید، در پوشه‌ی کنترلرها فایل posts.js تعریف نشده‌است. اگر اینکار صورت نگیرد، ember.js به صورت خودکار کنترلر پیش فرضی را ایجاد خواهد کرد. در کل، یک قالب هیچگاه به صورت مستقیم با مدل کار نمی‌کند. این کنترلر است که مدل را در اختیار یک قالب قرار می‌دهد.
سپس مدخل تعریف این فایل را به فایل index.html، پس از تعاریف کنترلرها اضافه نمائید:
 <script src="Scripts/Routes/posts.js" type="text/javascript"></script>

اکنون فایل Scripts\Templates\posts.hbs را گشوده و به نحو ذیل، جهت نمایش عناوین مطالب، ویرایش کنید:
<h2>Emeber.js blog</h2>
<ul>
    {{#each post in model}}
    <li>{{post.title}}</li>
    {{/each}}
</ul>
در این قالب، حلقه‌ای بر روی عناصر model تشکیل شده و سپس خاصیت title هر عضو نمایش داده می‌شود.




نمایش لیست آخرین نظرات ارسالی

در ادامه قصد داریم تا آرایه comments ابتدای بحث را در صفحه‌ای جدید نمایش دهیم. بنابراین نیاز است تا ابتدا مسیریابی آن تعریف شود. بنابراین فایل Scripts\App\router.js را گشوده و مسیریابی جدید recent-comments را به آن اضافه کنید:
Blogger.Router.map(function () {
    this.resource('posts', { path: '/' });
    this.resource('about');
    this.resource('contact', function () {
        this.resource('email');
        this.resource('phone');
    });
    this.resource('recent-comments');
});
سپس جهت تعیین مدل این مسیریابی جدید نیاز است تا فایل Scripts\Routes\recent-comments.js را در پوشه‌ی routes با محتوای ذیل اضافه کرد:
Blogger.RecentCommentsRoute = Ember.Route.extend({
    model: function () {
        return comments;
    }
});
در اینجا آرایه comments بازگشتی، همان آرایه‌ای است که در ابتدای بحث در فایل Scripts\App\store.js تعریف کردیم.
همچنین نیاز است تا تعریف مدخل این فایل جدید را نیز به انتهای تعاریف مداخل فایل index.html اضافه کنیم:
 <script src="Scripts/Routes/recent-comments.js" type="text/javascript"></script>

اکنون قالب application واقع در فایل Scripts\Templates\application.hbs را جهت افزودن منوی مرتبط با این مسیریابی جدید، به نحو ذیل ویرایش خواهیم کرد:
<div class='container'>
    <nav class='navbar navbar-default' role='navigation'>
        <ul class='nav navbar-nav'>
            <li>{{#link-to 'posts'}}Posts{{/link-to}}</li>
            <li>{{#link-to 'recent-comments'}}Recent comments{{/link-to}}</li>
            <li>{{#link-to 'about'}}About{{/link-to}}</li>
            <li>{{#link-to 'contact'}}Contact{{/link-to}}</li>
        </ul>
    </nav>
    {{outlet}}
</div>
و در آخر قالب جدید Scripts\Templates\recent-comments.hbs را برای نمایش لیست آخرین نظرات، با محتوای زیر اضافه می‌کنیم:
<h1>Recent comments</h1>
<ul>
  {{#each comment in model}}
    <li>{{comment.text}}</li>
  {{/each}}
</ul>
برای فعال شدن آن نیاز است تا تعریف این قالب جدید را به template loader برنامه، در فایل index.html اضافه کنیم:
<script type="text/javascript">
    EmberHandlebarsLoader.loadTemplates([
       'posts', 'about', 'application', 'contact', 'email', 'phone',
       'recent-comments'
    ]);
</script>



نمایش مجزای هر مطلب در یک صفحه‌ی جدید

تا اینجا در صفحه‌ی اول سایت، لیست عناوین مطالب را نمایش دادیم. در ادامه نیاز است تا بتوان هر عنوان را به صفحه‌ی متناظر و اختصاصی آن لینک کرد؛ برای مثال لینکی مانند http://localhost:25918/#/posts/3 به سومین مطلب ارسالی اشاره می‌کند. Ember.js به عدد 3 در اینجا، یک dynamic segment می‌گوید. از این جهت که مقدار آن بر اساس شماره مطلب درخواستی، متفاوت خواهد بود. برای پردازش این نوع آدرس‌ها نیاز است مسیریابی ویژه‌ای را تعریف کرد. فایل Scripts\App\router.js را گشوده و سپس مسیریابی post را به نحو ذیل به آن اضافه نمائید:
Blogger.Router.map(function () {
    this.resource('posts', { path: '/' });
    this.resource('about');
    this.resource('contact', function () {
        this.resource('email');
        this.resource('phone');
    });
    this.resource('recent-comments');
    this.resource('post', { path: 'posts/:post_id' });
});
قسمت پویای مسیریابی با یک : مشخص می‌شود.
با توجه به اینکه این مسیریابی جدید post نام گرفت (جهت نمایش یک مطلب)، به صورت خودکار، کنترلر و قالبی به همین نام را بارگذاری می‌کند. همچنین مدل خود را نیز باید از مسیریابی خاص خود دریافت کند. بنابراین فایل جدید Scripts\Routes\post.js را در پوشه‌ی routes با محتوای ذیل اضافه کنید:
Blogger.PostRoute = Ember.Route.extend({
    model: function (params) {
        return posts.findBy('id', params.post_id);
    }
});
در اینجا مدل مسیریابی post بر اساس پارامتری به نام params تعیین می‌شود. این پارامتر حاوی مقدار متغیر پویای post_id که در مسیریابی جدید post مشخص کردیم می‌باشد. در ادامه از آرایه posts تعریف شده در ابتدای بحث، توسط متد findBy که توسط Ember.js اضافه شده‌است، عنصری را که خاصیت id آن مساوی post_id دریافتی است، انتخاب کرده و به عنوان مقدار مدل بازگشت می‌دهیم.
برای مثال، جهت آدرس http://localhost:25918/#/posts/3، مقدار post_id به صورت خودکار به عدد 3 تنظیم می‌شود.

پس از آن نیاز است مدخل این فایل جدید را در صفحه‌ی index.html نیز اضافه کنیم:
 <script src="Scripts/Routes/post.js" type="text/javascript"></script>

در ادامه برای نمایش اطلاعات مدل نیاز است قالب جدید Scripts\Templates\post.hbs را با محتوای زیر اضافه کنیم:
 <h1>{{title}}</h1>
<p>{{body}}</p>
و template loader صفحه‌ی index.html را نیز باید از وجود آن باخبر کرد:
<script type="text/javascript">
    EmberHandlebarsLoader.loadTemplates([
       'posts', 'about', 'application', 'contact', 'email', 'phone',
       'recent-comments', 'post'
    ]);
</script>

اکنون به قالب Scripts\Templates\posts.hbs مراجعه کرده و هر عنوان را به مطلب متناظر با آن لینک می‌کنیم:
<h2>Emeber.js blog</h2>
<ul>
    {{#each post in model}}
    <li>{{#link-to 'post' post.id}}{{post.title}}{{/link-to}}</li>
    {{/each}}
</ul>
همانطور که ملاحظه می‌کنید، link-to امکان پذیرش id یک مطلب را به صورت متغیر نیز دارا است که سبب خواهد شد تا عناوین، به مطالب متناظر لینک شوند:


همچنین با کلیک بر روی هر عنوان نیز مطلب مرتبط نمایش داده خواهد شد:



افزودن امکان ویرایش مطالب

می‌خواهیم در صفحه‌ی نمایش جزئیات یک مطلب، امکان ویرایش آن‌را نیز فراهم کنیم. بنابراین فایل Scripts\Templates\post.hbs را گشوده و محتوای آن‌را به نحو ذیل ویرایش کنید:
<h2>{{title}}</h2>
{{#if isEditing}}
<form>
    <div class="form-group">
        <label for="title">Title</label>
        {{input value=title id="title" class="form-control"}}
    </div>
    <div class="form-group">
        <label for="body">Body</label>
        {{textarea value=body id="body" class="form-control" rows="5"}}
    </div>
    <button class="btn btn-primary" {{action 'save' }}>Save</button>
</form>
{{else}}
<p>{{body}}</p>
<button class="btn btn-primary" {{action 'edit' }}>Edit</button>
{{/if}}
شبیه به این if و else را در قسمت قبل حین ایجاد صفحات about و یا contact نیز مشاهده کرده‌اید. در اینجا اگر خاصیت isEditing مساوی true باشد، فرم ویرایش اطلاعات ظاهر می‌شود و اگر خیر، محتوای مطلب جاری نمایش داده خواهد شد.
در فرم تعریف شده، المان‌های ورودی اطلاعات از handlebar helper‌های ویژه‌ی input و textarea استفاده می‌کنند؛ بجای المان‌های متداول HTML. همچنین value یکی به title و دیگری به body تنظیم شده‌است (خواص مدل ارائه شده توسط کنترلر متصل به قالب). این مقادیر نیز داخل '' قرار ندارند؛ به عبارتی در یک handlebar helper به عنوان متغیر در نظر گرفته می‌شوند. به این ترتیب اطلاعات کنترلر جاری، به این المان‌های ورودی اطلاعات به صورت خودکار bind می‌شوند و برعکس. اگر کاربر مقادیر آن‌ها را تغییر دهد، تغییرات نهایی به صورت خودکار به خواص متناظری در کنترلر جاری منعکس خواهند شد (two-way data binding).
دو دکمه نیز تعریف شده‌اند که به اکشن‌های save و edit متصل هستند.
بنابراین نیاز به یک کنترلر جدید، به نام post داریم تا بتوان رفتار قالب post را کنترل کرد. برای این منظور فایل جدید Scripts\Controllers\post.js را با محتوای ذیل ایجاد کنید:
Blogger.PostController = Ember.ObjectController.extend({
    isEditing: false,
    actions: {
        edit: function () {
            this.set('isEditing', true);
        },
        save: function () {
            this.set('isEditing', false);
        }
    }
});
همچنین مدخل تعریف آن‌را نیز به فایل index.html اضافه نمائید (پس از تعاریف کنترلرهای موجود):
 <script src="Scripts/Controllers/post.js" type="text/javascript"></script>

اگر به کدهای این کنترلر دقت کرده باشید، اینبار زیرکلاسی از ObjectController ایجاد شده‌است و نه Controller، مانند مثال‌های قبل. ObjectController تغییرات رخ داده بر روی خواص مدل را که توسط کنترلر در معرض دید قالب قرار داده‌است، به صورت خودکار به مدل مرتبط نیز منعکس می‌کند (Ember.ObjectController.extend)؛ اما Controller خیر (Ember.Controller.extend). در اینجا مدل کنترلر، تنها «یک» شیء است که بر اساس id آن انتخاب شده‌است. به همین جهت از ObjectController برای ارائه two-way data binding کمک گرفته شد.
در ember.js، یک قالب تنها با کنترلر خودش دارای تبادل اطلاعات است. اگر این کنترلر از نوع ObjectController باشد، تغییرات خاصیتی در یک قالب، ابتدا به کنترلر آن منعکس می‌شود و سپس این کنترلر، در صورت یافتن معادلی از این خاصیت در مدل، آن‌را به روز خواهد کرد. در حالت استفاده از Controller معمولی، صرفا تبادل اطلاعات بین قالب و کنترلر را شاهد خواهیم بود و نه بیشتر.

در ابتدای کار مقدار خاصیت isEditing مساوی false است. این مورد سبب می‌شود تا در بار اول بارگذاری اطلاعات یک مطلب انتخابی، صرفا عنوان و محتوای مطلب نمایش داده شوند؛ به همراه یک دکمه‌ی edit. با کلیک بر روی دکمه‌ی edit، مطابق کدهای کنترلر فوق، تنها خاصیت isEditing به true تنظیم می‌شود و در این حالت، بدنه‌ی اصلی شرط if isEditing در قالب post، رندر خواهد شد.

برای مثال در ابتدا مطلب شماره یک را انتخاب می‌کنیم:


با کلیک بر روی دکمه‌ی edit، فرم ویرایش ظاهر خواهد شد:


نکته‌ی جالب آن، مقدار دهی خودکار المان‌های ویرایش اطلاعات است. در این حالت سعی کنید، عنوان مطلب جاری را اندکی ویرایش کنید:


با ویرایش عنوان، می‌توان بلافاصله مقدار تغییر یافته را در برچسب عنوان مطلب نیز مشاهده کرد. این مورد دقیقا مفهوم two-way data binding و اتصال مقادیر value هر کدام از handlebar helper‌های ویژه‌ی input و textarea را به عناصر مدل ارائه شده توسط کنترلر post، بیان می‌کند.
در این حالت در کدهای متد save، تنها کافی است که خاصیت isEditing را به false تنظیم کنیم. زیرا کلیه مقادیر ویرایش شده توسط کاربر، در همان لحظه در برنامه منتشر شده‌اند و نیاز به کار بیشتری برای اعمال تغییرات نیست.


اضافه کردن دکمه‌ی مرتب سازی بر اساس عناوین، در صفحه‌ی اول سایت

Ember.ObjectController.extend برای data bindg یک شیء کاربرد دارد. اگر قصد داشته باشیم با آرایه‌ای از اشیاء کار کنیم می‌توان از ArrayController استفاده کرد. فرض کنید در صفحه‌ی اول سایت می‌خواهیم امکان مرتب سازی مطالب را بر اساس عنوان آن‌ها اضافه کنیم. فایل Scripts\Templates\posts.hbs را گشوده و لینک Sort by title را به انتهای آن اضافه کنید:
<h2>Emeber.js blog</h2>
<ul>
    {{#each post in model}}
    <li>{{#link-to 'post' post.id}}{{post.title}}{{/link-to}}</li>
    {{/each}}
</ul>
 
<a href="#" class="btn btn-primary" {{action 'sortByTitle'}}>Sort by title</a>
در اینجا چون قصد تغییر رفتار قالب posts را توسط اکشن جدید sortByTitle داریم، نیاز است کنترلر متناظر با آن‌را نیز اضافه کنیم. برای این منظور فایل جدید Scripts\Controllers\posts.js را به پوشه‌ی کنترلرها اضافه کنید؛ با محتوای ذیل:
Blogger.PostsController = Ember.ArrayController.extend({
    sortProperties: ['id'],// مقادیر پیش فرض مرتب سازی
    sortAscending: false,
    actions: {
        sortByTitle: function () {
            this.set('sortProperties', ['title']);
            this.set('sortAscending', !this.get('sortAscending'));
        }
    }
});
sortProperties جزو خواص کلاس پایه ArrayController است. اگر مانند سطر اول به صورت مستقیم مقدار دهی شود، خاصیت یا خواص پیش فرض مرتب سازی را مشخص می‌کند. اگر مانند اکشن sortByTitle توسط متد set مقدار دهی شود، امکان مرتب سازی تعاملی و با فرمان کاربر را فراهم خواهد کرد.

در ادامه، تعریف مدخل این کنترلر جدید را نیز باید به فایل index.html، اضافه کرد:
 <script src="Scripts/Controllers/posts.js" type="text/javascript"></script>

اگر برنامه را در این حالت اجرا کرده و بر روی دکمه‌ی Sort by title کلیک کنید، اتفاقی رخ نمی‌دهد. علت اینجا است که ArrayController خروجی تغییر یافته خودش را توسط خاصیتی به نام arrangedContent در اختیار قالب خود قرار می‌دهد. بنابراین نیاز است فایل قالب Scripts\Templates\posts.hbs را به نحو ذیل ویرایش کرد:
<h2>Emeber.js blog</h2>
<ul>
    {{#each post in arrangedContent}}
    <li>{{#link-to 'post' post.id}}{{post.title}}{{/link-to}}</li>
    {{/each}}
</ul>
 
<a href="#" class="btn btn-primary" {{action 'sortByTitle'}}>Sort by title</a>
اینبار کلیک بر روی دکمه‌ی مرتب سازی بر اساس عناوین، هربار لیست موجود را به صورت صعودی و یا نزولی مرتب می‌کند.



یک نکته: حلقه‌ی ویژه‌ای به نام each

اگر قالب Scripts\Templates\posts.hbs را به نحو ذیل، با یک حلقه‌ی each ساده بازنویسی کنید:
<h2>Ember.js blog</h2>
<ul>
    {{#each}}
    <li>{{#link-to 'post' id}}{{title}}{{/link-to}}</li>
    {{/each}}
</ul>
 
<a href="#" class="btn btn-primary" {{action 'sortByTitle'}}>Sort by title</a>
هم در حالت نمایش معمولی و هم در حالت استفاده از ArrayController برای نمایش اطلاعات مرتب شده، بدون مشکل کار می‌کند و نیازی به تغییر نخواهد داشت.



کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید:
EmberJS03_03.zip