Quantcast
Channel: ‫فید مطالب .NET Tips
Viewing all 1980 articles
Browse latest View live

‫Symbols در ES 6

$
0
0
در مطلب Iteratorsبه بررسی حلقه‌های for of پرداختیم. اما سؤال مهم اینجا است که for of چگونه یک iterator را پیدا می‌کند و چه چیزی سبب می‌شود تا بتواند این پیمایش را انجام دهد؟ پاسخ به این سؤال نیاز به آشنایی با مفهوم جدیدی در ES 6به نام Symbols دارد.
Symbol یک primitive data type جدید در ES 6 است؛ دقیقا مانند اعداد، Boolean، رشته‌ها و امثال آن‌ها. دو نکته‌ی مهم در مورد Symbols وجود دارد:
الف) منحصربفرد و immutable (غیرقابل تغییر) هستند.
ب) می‌توان از آن‌ها به عنوان کلیدهایی جهت افزودن خواص جدید به اشیاء استفاده کرد.

ایجاد یک Symbol باید بدون استفاده از کلمه‌ی new انجام شود (چون یک primitive data type است):
 let s = Symbol();
همچنین در اینجا یک توضیح را نیز می‌توان ذکر کرد:
 let s1 = Symbol("some description");
سمبل ایجاد شده، منحصربفرد بوده و غیرقابل تغییر است. همین منحصربفرد بودن آن سبب شده‌است که در لایه‌های زیرین ES 6 از آن برای ساخت کلیدهای خواص اشیاء استفاده شود:
let firstName = Symbol();
let person = {
    lastName: "Vahid",
    [firstName]: "N",
};
// person.lastName = "Vahid"
// person[firstName] = "N"
در این مثال ابتدا یک سمبل جدید ایجاد شده و سپس از این سمبل به عنوان کلیدی منحصربفرد، جهت تعریف یک خاصیت جدید کمک گرفته شده‌است.  
در ES 5 (نگارش فعلی جاوا اسکریپت)، کتابخانه‌های مختلف از time stamp و یا اعداد اتفاقی برای شبیه سازی چنین قابلیتی استفاده می‌کنند اما در ES 6 یک راه حل استاندارد به نام Symbols برای این مساله ارائه شده‌است.

چند نکته
- زمانیکه خاصیتی با کلیدی از نوع Symbol تعریف می‌شود، دیگر در حلقه‌های for in قدیمی ظاهر نخواهد شد.
 let names = [];
for(var p in person) {
   names.push(p);
}
- همچنین این خواص سمبلی، توسط Object.getOwnPropertyNames نیز قابل دسترسی و یافت شدن نیستند. به عبارتی با امکانات ES 5 نمی‌توان آن‌‌ها را مشاهده کرد.


سؤال: ES 6 چگونه از Symbols جهت تعریف Iterators استفاده می‌کند؟

مطابق استاندارد ES 6 اگر متد خاصی با نام iterator@@ در شیءایی ظاهر شود، این شیء قابل پیمایش بوده و به عنوان منبع حلقه‌ی for of قابل استفاده‌است.
خوب، اکنون چگونه می‌توان بررسی کرد که آیا شیءایی دارای متد ویژه‌ی iterator@@ است؟ برای این منظور باید بررسی کرد که آیا این شیء دارای عضو Symbol.iterator هست یا خیر؟ خاصیت iterator متصل به متد Symbol، یکی از سمبل‌های پیش فرض ES 6است.
برای مثال آرایه‌ی ذیل را درنظر بگیرید:
 var numbers = [1, 2, 3];
برای اینکه بررسی کنیم آیا قابل پیمایش هست یا خیر، می‌توان نوشت:
 numbers[Symbol.iterator];


همانطور که در تصویر مشاهده می‌کنید، آرایه و یا رشته‌ی تعریف شده، دارای Iterator هستند؛ اما عدد تعریف شده، خیر.

و یا اگر بخواهیم همان مثال while دار مطلب بررسی Iteratorsرا با Symbol.iterator بازسازی کنیم، به مثال زیر خواهیم رسید:
 var numbersIterator = numbers[Symbol.iterator]();
numbersIterator.next();
// Result: Object {value: 1, done: false}
numbersIterator.next();
// Result: Object {value: 2, done: false}
numbersIterator.next();
// Result: Object {value: 3, done: false}
numbersIterator.next();
// Result: Object {value: undefined, done: true}
کاری که در اینجا انجام شده، دقیقا عملیاتی است که توسط حلقه‌ی for of در پشت صحنه انجام می‌شود. ابتدا بررسی می‌کند که آیا خاصیت Symbol.iterator در دسترس است یا خیر؟ اگر بله، متد next آن‌را تا زمان true شدن خاصیت done بازگشتی، فراخوانی می‌کند.


ایجاد یک Iterator سفارشی با استفاده از Symbol.iterator

در این مثال قصد داریم یک پیمایشگر سفارشی را بر روی یک رشته‌ی دریافتی، ایجاد کنیم. ابتدا ایجاد سازنده‌ی شیء:
 function Words(str) {
   this._str = str;
}
و سپس بدنه‌ی Iterator:
Words.prototype[Symbol.iterator] = function() {
  var re = /\S+/g;
  var str = this._str;
return {
    next: function() {
      var match = re.exec(str);
      if (match) {
        return {value: match[0], done: false};
      }
      return {value: undefined, done: true};
    }
  }
};
در اینجا شیءایی بازگشت داده می‌شود که دارای متد next است و هر بار {value: nextWordInTheString, done: false} را بازگشت می‌دهد تا دیگر کلمه‌‌ای در رشته باقی نماند. نمونه‌ای از نحوه‌ی استفاده‌ی از آن نیز به صورت زیر است:
 var helloWorld = new Words("Hello world");
for (var word of helloWorld) {
   console.log(word);
}
// Result: "Hello"
// Result: "world"

‫#Defensive Code in C - قسمت سوم

$
0
0

رفع مشکلات:  

در قسمت قبلبا ذکر یک مثال و بیان مشکلات آن از دیدگاه اصول Defensive Code قصد داشتیم که مساله را روشن‌تر کنیم. مواردی که در قسمت قبل ذکر شدند، به ساده‌ترین شکل ممکن بیان  شدند و شما به راحتی با بررسی این موارد و تفکر در کد‌های خود، می‌توانید این موارد را در کدی که خودتان می‌نویسید رعایت کنید. حل پیچیدگی‌های موجود در کد قبل، با در نظر گرفتن اصول مذکور و اصول‌های طراحی مختلف می‌تواند به روش‌های مختلفی انجام گیرد. برای مثال می‌توان برای هر یک از کارهایی که کد مثال قبل انجام می‌دهد، یک کلاس مجزا ایجاد نمود و اصول مذکور را در آن رعایت کرد. درنهایت این کلاس‌ها را در قالب یک Class Library دسته بندی کرد.

Predictability: 

در ادامه قصد داریم در مورد قابلیت پیش بینی و فواید رعایت اصول آن در کد، بحث کنیم. به صورت کلی یک متد، یکسری پارامتر‌ها را به عنوان ورودی دریافت می‌کند؛ یکعملیات خاص را بر روی پارامتر‌های ورودی انجام می‌دهد و در نهایت، در صورت لزوم یک مقدار را بر می‌گرداند. پارامتر‌های ورودی می‌توانند از سمت کاربر و از یک سورس خارجی وارد شوند و یا می‌توانند از یک متد دیگر به این متد ارسال شوند. اولین مرحله برای ایجاد قابلیت Predictability این است که یکسری گارد‌ها را به کد خود اضافه کنید تا به وسیله‌ی آنها پارامتر‌های ورودی را بررسی کنید و فقط اجازه‌ی ورود، ورودی‌های معتبر را بدهیم. برای مثال کد ذیل را درنظر بگیرد.

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

بر اساس اصول (GIGO (Garbage in-Garbage out در برنامه نویسی متدی که ورودی‌های نامعتبر به آن پاس داده شوند، خروجی‌های نامعتبری هم پس خواهد داد. بنابراین برای جلوگیری از این مسئله باید از ورود ورودی‌های نامعتبر به متد‌ها جلوگیری کرد. گارد‌ها از ورود مقادیر نامعتبر به متد‌ها جلوگیری خواهند کرد و در نتیجه خروجی مناسب و قابل پیش بینی از متد گرفته خواهد شد.  برای جلوگیری از ورود داده‌های نامعتبر، باید با استفاده از این دستورات که در ابتدای متد قرار داده می‌شوند، از ورود داده‌های نامعتبر جلوگیری کرد. به این دستورات Guard Clauses گفته می‌شود. غیر از این مساله، کاهش دادن تعداد پارامتر‌ها و قراردادن قانونی برای تعیین اولویت پارامتر‌های متدها (برای مثال با توجه به اهمیت) می‌تواند به افزایش Predictability متد‌ها بسیار کمک کند. با پیروی کردن از این اصول ساده شما می‌توانید میزان خطاهایی که از پارامتر‌های ورودی منشاء می‌گیرند را کاهش دهید.

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

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

private decimal CalculatePercentOfGoalSteps (string goalSteps, string actualSteps)
{
            return (Convert.ToDecimal(actualSteps) / Convert.ToDecimal(goalSteps)) * 100;
}

این متد دارای دو پارامتر از نوع string می باشد و نتیجه هم در قالب یک مقدار decimal بازگشت داده خواهد شد. این جمله کلیتی از متد را بیان خواهد کرد. نحوه‌ی فراخوانی این متد هم در کد ذیل آورده شد است. 

private void Calculate_Click(object sender, EventArgs e)
{
  var result =CalculatePercentOfGoalSteps (stepGoalForTodayTxt.Text, numberOfStepsForToday.Text);
            lblResult.Text = "شما به" + result + "% از هدف تان رسیده اید";
}

حال Application را اجرا کرده و نتیجه کار را مشاهده می‌کنیم. برای مثال شکل ذیل:

   

در این مثال با توجه به مقادیر وارد شده، به 40 درصد از هدف مورد نظر رسیده‌ایم. اما هدف از بیان این مثال، این نیست که مشخص گردد که ما چقدر به هدفمان نزدیک شده‌ایم. بلکه هدف مسایل دیگری است. در نظر بگیرید که بجای 5000، صفر را وارد کنید. در این حالت با یک Exceptionروبرو می‌شویم:

همانطور که در شکل بالا مشاهده می‌کنید، خطای Divide by zero رخ داده است. برای رفع این خطا و جلوگیری از رخداد این خطا، می‌توان کد ذیل را پیشنهاد داد. 

private decimal CalculatePercentOfGoalSteps(string goalSteps, string actualSteps)
{
            decimal result =0;
            var goalStepsCount = Convert.ToDecimal(goalSteps);
            if (goalStepsCount>0)
            {
                result = (Convert.ToDecimal(actualSteps) / goalStepsCount) * 100;
            }
            return result;
}

با تغییر کد به این صورت مشکل  Exception  بالا حل می‌شود،اما باز هم مشکل دیگری وجود دارد. فرض کنید همانند شکل ذیل textbox اول را خالی کنیم و بعد از آن سعی در محاسبه داشته باشیم،

باز هم یک  Exception دیگر

علت بوجود آمدن این مشکل این است که ما در کد امکان خالی بودن پارامتر‌های متد را در نظر نگرفته‌ایم و پیش بینی‌های لازم صورت نگرفته استبنابراین دستور Convert  .با مشکل مواجه شد. برای حل این مشکل می‌توان به جای Convert از decimal.Tryparse استفاده کرد.

private decimal CalculatePercentOfGoalSteps(string goalSteps, string actualSteps)
        {
            decimal result = 0;
            decimal goalStepsCount = 0;
            decimal.TryParse(goalSteps, out goalStepsCount);
            decimal actualStepsCount = 0;
            decimal.TryParse(actualSteps, out actualStepsCount);
            if (goalStepsCount>0)
            {
                result = (actualStepsCount / goalStepsCount) * 100;
            }
            return result;
        }

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

برای اینکه بتوانیم این کد به راحتی debug کنیم باید از مفهوم Fail Fast استفاده کنیم . این مفهوم قابلیتی را در کد ایجاد می‌کند که در صورتی که کد، داده‌های نامعتبری را دریافت کرد، سریعا اجرای آن متوقف می‌شود و همزمان نیز اطلاعاتی در مورد خطا در اختیار کاربر قرار می‌دهد. برای این منظور با قرار دادن یکسری Guard Clauses، کد بالا را همانند شکل ذیل تغییر خواهیم داد.

private decimal CalculatePercentOfGoalSteps(string goalSteps, string actualSteps)
        {
            decimal goalStepsCount = 0;
            decimal actualStepsCount = 0;
            /// اطمینان حاصل می‌کنند که پارامتر‌های ورودی دارای مقدار هستند 
            if (string.IsNullOrWhiteSpace(goalSteps)) throw new ArgumentException("مقدار هدف باید وارد شود", "goalSteps");
            if (string.IsNullOrWhiteSpace(actualSteps)) throw new ArgumentException("مقدار واقعی باید وارد شود", "goalSteps");

            ///اطمینان حاصل می‌کنند که مقادیر وارد شده حتما عددی هستند
            if (!decimal.TryParse(goalSteps, out goalStepsCount)) throw new ArgumentException("مقدار هدف باید عددی باشد", goalSteps);
            if(!decimal.TryParse(actualSteps, out actualStepsCount)) throw new ArgumentException("مقدار واقعی باید عددی باشد", actualSteps);

            ///اطمینان حاصل می‌کند که مقدار متغیر نباید صفر باشد
            if (goalStepsCount <= 0) throw new ArgumentException("مقدار هدف نباید صفر و یا کمتر از صفر باشد", "goalStepsCount");
            return (actualStepsCount / goalStepsCount) * 100;
        }

ایجاد کردن این تغییرات در متد باعث افزایش خوانایی کد می‌شود و هدف متد را روشن‌تر بیان خواهد کرد. اضافه کردن این کدها به دلیل اینکه تمامی شرایط تست را تعیین خواهیم کرد Test-ability کد را بالا می‌برد. اضافه کردن کد‌های بالا به برنامه کمک خواهد کرد که شرایط خطا در برنامه به درستی هندل شود و به طبع آن تصمیمات مناسبی گرفته شود و در نهایت Predictability متد‌ها و کل برنامه را افزایش می‌هد.

‫کلاس‌ها در ES 6

$
0
0
رسمی‌ترین زبان‌های شیء گرا از کلاس‌ها و وراثت مربوط به آنها پشتیبانی می‌کنند؛ ولی از زمانی که JavaScript ساخته شد، به دلیل نداشتن کلاس‌ها باعث سردرگمی بیشتر توسعه دهنده‌ها شد. برای آشنایی با مباحث شیء گرایی در جاوااسکریپت  ^ و را مطالعه کنید.
در واقع کلاس‌ها در ES 6 هم واقعا مانند کلاس‌ها در سایر زبان‌ها نبوده و صرفا یک syntax آسان بر فراز روش‌های پیاده سازی انواع داده‌های شخصی در ورژن‌ها قبلی می‌باشند. این syntax به معنای تولید مدل جدید شیء گرایی در JavaScript نمی‌باشد و در ادامه خواهیم دید که این کلاس‌ها چیزی بجز یک function نیستند. در ورژن‌های قبل ES، تعریف نوع داده جدید به عنوان مثال به شکل زیر بود:
function PersonType(name) {
    this.name = name;
}

PersonType.prototype.sayName = function() {
    console.log(this.name);
};

let person = new PersonType("Nicholas");
person.sayName();   // outputs "Nicholas"

console.log(person instanceof PersonType);  // true
console.log(person instanceof Object);      // true
‫در کد بالا که مربوط است به ورژن 5 اکما اسکریپت، PersonType یک تابع سازنده است که دارای یک پراپرتی به نام name و یک متد در سطح آبجکت به نام sayName میباشد.
Class declarations یکی از روش‌های تعریف کلاس در ES 6 میباشد. به عنوان مثال در ورژن جدید، تعریف کلاس مثال فوق به شکل زیر خواهد بود:
class PersonClass {

    // equivalent of the PersonType constructor
    constructor(name) {
        this.name = name;
    }

    // equivalent of PersonType.prototype.sayName
    sayName() {
        console.log(this.name);
    }
}

let person = new PersonClass("Nicholas");
person.sayName();   // outputs "Nicholas"

console.log(person instanceof PersonClass);     // true
console.log(person instanceof Object);          // true

console.log(typeof PersonClass);                    // "function"
console.log(typeof PersonClass.prototype.sayName);  // "function"
در کد بالا این بار به جای تعریف یک  تابع (function) به عنوان سازنده، برای ساخت نوع داده‌ی شخصی، خواهید توانست به صورت مستقیم این سازنده را درون کلاس خود با نام constructor که مشخصا برای این منظور در نظر گرفته شده است، تعریف کنید. همانطور که در خطوط آخر کد بالا مشخص است، کلاس PersonClass چیزی بجز یک function نیست و همین مورد گفته‌های ابتدایی مطلب را تأیید می‌کند.  باید توجه داشت که در تعریف هر کلاسی فقط یک تابع سازنده با نام constructor می‌تواند وجود داشته باشد؛ در غیر این صورت خطای syntax error را دریافت خواهیم کرد.
شباهت‌هایی و معادل‌هایی که در پیاده سازی مثال بالا در دو ورژن مختلف وجود دارد باعث خواهد شد که بدون نگرانی از اینکه با کدام ورژن کار می‌کنید، به صورت ترکیبی از آنها استفاده کنید.
Class Expressions روش دوم پیاده سازی کلاس‌ها در ES 6 می‌باشد؛ به دو صورت named و unnamed که به صورتیکه در زیر مشاهده می‌کنید، قابل تعریف خواهد بود:
//unnamed class expressions do not require identifiers after "class"
let PersonClass = class {

    // equivalent of the PersonType constructor
    constructor(name) {
        this.name = name;
    }

    // equivalent of PersonType.prototype.sayName
    sayName() {
        console.log(this.name);
    }
};

let person = new PersonClass("Nicholas");
person.sayName();   // outputs "Nicholas"

console.log(person instanceof PersonClass);     // true
console.log(person instanceof Object);          // true

console.log(typeof PersonClass);                    // "function"
console.log(typeof PersonClass.prototype.sayName);  // "function"


//named
let PersonClass = class PersonClass2 {

    // equivalent of the PersonType constructor
    constructor(name) {
        this.name = name;
    }

    // equivalent of PersonType.prototype.sayName
    sayName() {
        console.log(this.name);
    }
};

console.log(PersonClass === PersonClass2);  // true
همانطور که متوجه شدید، class‌ها به همانند function‌ها به دو شکل declarations و expressions قابل تعریف هستند (یکی دیگر از شباهت ها). یک نکته در حالت تعریف به صورت named این است که میتوان PerssonClass2 و PerssonClass را به دلیل اینکه هر دوی آنها اشاره‌گر به یک کلاس هستند، به جای هم استفاده کنید.
نکته جالب این که class expressions‌ها را می‌توان به عنوان آرگومان توابع دیگر هم ارسال کرد؛ برای مثال :
function createObject(classDef) {
    return new classDef();
}

let obj = createObject(class {
    sayHi() {
        console.log("Hi!");
    }
});

obj.sayHi();        // "Hi!"
در کد بالا createObject، متدی است که class expression ما به عنوان آرگومان آن پاس داده شده است و در نهایت توانسته‌ایم از این کلاس پاس داده شده در داخل متد نمونه سازی کرده و آن را به عنوان نتیجه‌ی برگشتی return کنیم. 
نکته جالب دیگر این که با استفاده از class expressions‌ها خواهیم توانست singleton‌ها را با فراخوانی بلافاصله‌ی سازنده کلاس، پیاده سازی کنیم. برای این منظور باید کلمه‌ی کلیدی new را قبل از کلمه‌ی کلیدی class نوشته و در پایان هم از دو پرانتز باز و بسته استفاده کنید که معادل فراخوانی سازنده‌ی کلاس خواهد بود.
let person = new class {
    constructor(name) {
        this.name = name;
    }

    sayName() {
        console.log(this.name);
    }
}("Nicholas");

person.sayName();       // "Nicholas 

در کد بالا ، "Nicholas" به عنوان آرگومان سازنده کلاس بی نام در هنگام ساخت نمونه از طریق پرانتز‌های باز و بسته انتهایی، پاس داده شده است. استفاده از class declarations یا class expressions برای کار با کلاس‌ها به سبک کاری شما مربوط خواهد شد و بس. ولی نکته این است که هر دو شکل پیاده سازی کلاس‌ها بر خلاف function declarations و function expressions ، قابلیت  hoisting را نخواهند داشت و به صورت پیش فرض در حالت strict modeاجرا خواهند شد.

Accessor Properties

کلاس‌ها این امکان را دارند تا بتوان برای پراپرتی‌هایی که در سازنده‌ی کلاس تعریف شده‌اند، accessor property تعریف کرد. سینتکس استفاده شده‌ی برای این منظور، شبیه به ساخت object literal accessor‌ها در ES 5 میباشد.برای مثال:

class CustomHTMLElement {

    constructor(element) {
        this.element = element;
    }

    get html() {
        return this.element.innerHTML;
    }

    set html(value) {
        this.element.innerHTML = value;
    }
}

var descriptor = Object.getOwnPropertyDescriptor(CustomHTMLElement.prototype,\
 "html");

console.log("get" in descriptor);   // true
console.log("set" in descriptor);   // true

در کد بالا ، getter و setter برای محتوای html مربوط به پراپرتی element در نظر گرفته شده است که در واقعا نمایندگان (delegates) مربوط به متد innterHTML خود element می‌باشند. معادل همین پیاده سازی بدون استفاده از سینتکس کلاس، به شکل زیر خواهد بود:

// direct equivalent to previous example
let CustomHTMLElement = (function() {
    "use strict";

    const CustomHTMLElement = function(element) {
        // make sure the function was called with new
        if (typeof new.target === "undefined") {
            throw new Error("Constructor must be called with new.");
        }
        this.element = element;
    }

    Object.defineProperty(CustomHTMLElement.prototype, "html", {
        enumerable: false,
        configurable: true,
        get: function() {
            return this.element.innerHTML;
        },
        set: function(value) {
            this.element.innerHTML = value;
        }
    });
    return CustomHTMLElement;
}());

حتما متوجه شدید که با استفاده از سینتکس کلاس برای تعریف accessor property‌ها حجم کد نویسی شما خیلی کاهش خواهد یافت و این تنها تفاوت بین دو شکل پیاده سازی فوق میباشد.

Static Members

ساخت اعضای استاتیک در ورژن قبل برای مثال به شکل زیر بود:

function PersonType(name) {
    this.name = name;
}

// static method
PersonType.create = function(name) {
    return new PersonType(name);
};

// instance method
PersonType.prototype.sayName = function() {
    console.log(this.name);
};

var person = PersonType.create("Nicholas");

 در کد بالا یک متد استاتیک برای نوع داده شخصی PersonType در نظر گرفته شده است. این مورد در ES 6 بهبود یافته و فقط با قرار دادن کلمه‌ی کلیدی static قبل از نام متد و یا accessor property می‌توان به نتیجه‌ی مثال بالا دست یافت:

class PersonClass {

    // equivalent of the PersonType constructor
    constructor(name) {
        this.name = name;
    }

    // equivalent of PersonType.prototype.sayName
    sayName() {
        console.log(this.name);
    }

    // equivalent of PersonType.create
    static create(name) {
        return new PersonClass(name);
    }
}

let person = PersonClass.create("Nicholas");

نکته این که نمی‌توان سازنده‌ی استاتیک در کلاس خود تعریف کرد. 


Inheritance

مشکل دیگری که در ES 5 برای پیاده سازی انواع داده شخصی وجود داشت، حجم بالای کد و مراحلی بود که برای پیاده سازی وراثت می‌بایستی متحمل می‌شدیم. برای مثال در ورژن قبلی باید به شکل زیر عمل میکردیم:

function Rectangle(length, width) {
    this.length = length;
    this.width = width;
}

Rectangle.prototype.getArea = function() {
    return this.length * this.width;
};

function Square(length) {
    Rectangle.call(this, length, length);
}

Square.prototype = Object.create(Rectangle.prototype, {
    constructor: {
        value:Square,
        enumerable: true,
        writable: true,
        configurable: true
    }
});

var square = new Square(3);
console.log(square.getArea());              // 9
console.log(square instanceof Square);      // true
console.log(square instanceof Rectangle);   // true

درکد بالا Square از Rectangle ارث بری کرده که برای این منظور Square.prototype را با ساخت نمونه‌ای از Rectangle.prototype بازنویسی کرده‌ایم. این سینتکس باعث سردرگمی اغلب تازه کاران خواهد شد. برای این منظور در ES 6 خیلی راحت با استفاده از کلمه‌ی کلیدی  extends بعد از نام کلاس و سپس نوشتن نام کلاس پایه خواهیم توانست به نتیجه‌ی بالا دست یابیم. به عنوان مثال:

class Rectangle {

    constructor(length, width) {
        this.length = length;
        this.width = width;
    }

    getArea() {
        return this.length * this.width;
    }
}

class Square extends Rectangle {
    constructor(length) {
        // same as Rectangle.call(this, length, length)
        super(length, length);
    }
}

var square = new Square(3);
console.log(square.getArea());              // 9
console.log(square instanceof Square);      // true
console.log(square instanceof Rectangle);   // true

در کد بالا نیز کلاس Square از کلاس Rectangle ارث بری کرده و همانطور که مشخص است و انتظار داشتیم، متد getArea در یکی از اعضای به ارث برده شده از کلاس پایه، قابل دسترسی می‌باشد. در سازنده‌ی کلاس Square با استفاده از ()super توانسته‌ایم سازنده‌ی کلاس Rectangle را با آرگومان‌های مشخصی فراخوانی کنیم. 

اگر برای subclass، سازنده در نظر گرفته شود، باید سازنده‌ی کلاس پیاده سازی کننده حتما فراخوانی شود. در غیر این صورت با خطا روبرو خواهید شد. ولی در مقابل اگر هیچ سازنده‌ای برای subclass در نظر نگرفته باشید، به صورت خودکار سازنده‌ی کلاس پایه هنگام ساخت نمونه از این subclass فراخوانی خواهد شد:

class Square extends Rectangle {
    // no constructor
}

// Is equivalent to
class Square extends Rectangle {
    constructor(...args) {
        super(...args);
    }
}

همانطور که در کد بالا مشخص است اگر سازنده‌ای برای subclass در نظر گرفته نشود، تمام آرگومان‌های ارسالی، هنگام نمونه سازی از آن، به ترتیب به سازنده‌ی کلاس پایه نیز پاس داده خواهند شد.

 چند نکته

- فقط زمانی میتوان ()super را فراخوانی کرد که از بعد از نام کلاس از کلمه‌ی کلیدی extends استفاده شده باشد.
- باید قبل از دسترسی به کلمه‌ی کلیدی this در سازنده subclass، سازنده‌ی کلاس پایه را با استفاده از ()super فراخوانی کرد.
 

Class Methods 

 اگر در subclass متدی همنام متد کلاس پایه داشته باشید، به صورت خودکار متد کلاس پایه override خواهد شد. البته همیشه میتوان متد کلاس پایه را مستقیم هم فراخوانی کرد؛ به عنوان مثال:

class Square extends Rectangle {
    constructor(length) {
        super(length, length);
    }

    // override, shadow, and call Rectangle.prototype.getArea()
    getArea() {
        return super.getArea();
    }
}

در کد بالا متد getArea کلاس پایه بازنویسی شده است. ولی با این حال با استفاده از کلمه‌ی super به متد اصلی در کلاس پایه دسترسی داریم. 

نام متد‌ها حتی می‌توانند قابلیت محاسباتی داشته باشند. به عنوان مثال خواهید توانست به شکل زیر عمل کنید:

let methodName = "getArea";

class Square extends Rectangle {
    constructor(length) {
        super(length, length);
    }

    // override, shadow, and call Rectangle.prototype.getArea()
    [methodName]() {
        return super.getArea();
    }
}

کد بالا دقیقا با مثال قبل یکسان است با این تفاوت که نام متد getArea را به صورت رشته‌ای با قابلیت محاسباتی در نظر گرفتیم.

ارث بردن اعضای استاتیک یک مفهوم جدید در جاوااسکریپت می‌باشد که نمونه‌ی آن را می‌توانید در کد زیر مشاهده کنید:

class Rectangle {
    constructor(length, width) {
        this.length = length;
        this.width = width;
    }

    getArea() {
        return this.length * this.width;
    }

    static create(length, width) {
        return new Rectangle(length, width);
    }
}

class Square extends Rectangle {
    constructor(length) {
        // same as Rectangle.call(this, length, length)
        super(length, length);
    }
}

var rect = Square.create(3, 4);
console.log(rect instanceof Rectangle);     // true
console.log(rect.getArea());                // 12
console.log(rect instanceof Square);        // false

در کد بالا متد استاتیک create یک متد استاتیک در کلاس پایه Rectangle می‌باشد که این بار در کلاس Square هم قابل دسترسی است.

قدرتمندترین جنبه‌ی کلاس‌های مشتق شده در ES 6 ، توانایی ارث بری از expression‌ها می‌باشد. شما می‌توانید کلمه‌ی کلیدی extends را با هر expression ای استفاده کنید. برای مثال:

function Rectangle(length, width) {
    this.length = length;
    this.width = width;
}

Rectangle.prototype.getArea = function() {
    return this.length * this.width;
};

class Square extends Rectangle {
    constructor(length) {
        super(length, length);
    }
}

var x = new Square(3);
console.log(x.getArea());               // 9
console.log(x instanceof Rectangle);    // true

در کد بالا Rectangle یک تابع سازنده برای تعریف نوع داده شخصی در ES 5 و Square، نوع داده با سینتکس کلاس در ES 6 می‌باشند. ولی با این حال کلاس Square توانسته است از Rectangle ارث بری کند.

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

function Rectangle(length, width) {
    this.length = length;
    this.width = width;
}

Rectangle.prototype.getArea = function() {
    return this.length * this.width;
};

function getBase() {
    return Rectangle;
}

class Square extends getBase() {
    constructor(length) {
        super(length, length);
    }
}

var x = new Square(3);
console.log(x.getArea());               // 9
console.log(x instanceof Rectangle);    // true

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

new.target

با استفاده از مقدار موجود در این شیء، در سازنده‌ی کلاس می‌توان مشخص کرد که به چه شکلی به  کلاس مورد نظر استناد شده‌است. برای مثال:

class Rectangle {
    constructor(length, width) {
        console.log(new.target === Rectangle);
        this.length = length;
        this.width = width;
    }
}

// new.target is Rectangle
var obj = new Rectangle(3, 4);      // outputs true

در کد بالا با استفاده از new.target توانستیم که مشخص کنیم شیء ایجاد شده از نوع Rectangle می‌باشد. با استفاده از این امکان خوب می‌توان به ساخت کلاس‌های abstract رسید. برای مثال:

// abstract base class
class Shape {
    constructor() {
        if (new.target === Shape) {
            throw new Error("This class cannot be instantiated directly.")
        }
    }
}

class Rectangle extends Shape {
    constructor(length, width) {
        super();
        this.length = length;
        this.width = width;
    }
}

var x = new Shape();                // throws error
var y = new Rectangle(3, 4);        // no error
console.log(y instanceof Shape);    // true

در کد بالا که کاملا هم مشخص است؛ در سازنده‌ی کلاس Shape مشخص کرده‌ایم که اگر مستقیما از کلاس Shape نمونه سازی شد، یک exception را پرتاب کند. با این اوصاف ما توانسته‌ایم که کلاس Shape را به صورت Abstract معرفی کنیم.

‫Generators در ES 6

$
0
0
Generators در حقیقت نوعی Iteratorهستند. آن‌ها نوع خاصی از توابع هستند که قابلیت تعلیق و از سرگیری مجدد را دارند. برای رسیدن به این هدف، اینبار تعریف function به صورت *function خواهد بود و در آن برای بازگشت مقادیر، از واژه‌ی کلیدی yield استفاده می‌شود.
یک نمونه مثال ابتدایی از Generators را در کدهای زیر مشاهده می‌کنید:
function* generator () {
   yield 1;
   // pause
   yield 2;
   // pause
   yield 3;
   // pause
   yield 'done?';
   // done
}
این متد خاص با یک ستاره پس از نام function مشخص شده‌است و همچنین برای بازگشت مقادیر از واژه‌ی کلیدی yield استفاده می‌کند. روش فراخوانی دستی آن نیز به صورت زیر است:
 let gen = generator(); // [object Generator]
console.log(gen.next()); // Object {value: 1, done: false}
console.log(gen.next()); // Object {value: 2, done: false}
console.log(gen.next()); // Object {value: 3, done: false}
console.log(gen.next()); // Object {value: 'done?', done: false}
console.log(gen.next()); // Object {value: undefined, done: true}
console.log(gen.next()); // Object {value: undefined, done: true}
همانطور که مشاهده می‌کنید، همانند Iterators، هر بار که متد next آن‌ها فراخوانی می‌شود، شیءایی را با خواص value و done بازگشت می‌دهند. هر زمانیکه done مساوی true شد، یعنی کار آن به پایان رسیده‌است.
و یا می‌توان بجای فراخوانی دستی متد next، از حلقه‌ی جدید for of نیز برای کار با آن‌ها استفاده کرد:
for (let val of generator()) {
   console.log(val); // 1
   // 2
   // 3
   // 'done?'
}
امکان ترکیب Generators نیز وجود دارد:
function* random (max) {
   yield Math.floor(Math.random() * max) + 1;
}

function* random1_20 () {
   while (true) {
      yield* random(20);
   }
}

let rand = random1_20();
console.log(rand.next());
console.log(rand.next());
در اینجا یک متد Generator به نام random1_20، به تعداد نامتناهی اعداد اتفاقی بین 1 تا 20 را بازگشت می‌دهد و در این بین، yield آن خود نیز یک Generator دیگر است.
فقط در این حالت بجای yield معمولی از *yield استفاده می‌شود. از *yield برای کار با هر نوع Iterator ایی می‌توان استفاده کرد:
function* multiplier (value) {
  yield value * 2;
  yield value * 3;
  yield value * 4;
  yield value * 5;
}
function* trailmix () {
  yield 0;
  yield* [1, 2];
  yield* [...multiplier(2)];
  yield* multiplier(3);
}
در این مثال از yield معمولی برای بازگشت اعداد و از *yiled برای کار با انواع و اقسام Iterators از آرایه‌ها گرفته تا spread operator، استفاده شده‌است.


کاهش مصرف حافظه‌ی برنامه با استفاده از Generators

در مثال زیر، قرار است لیستی از rows بازگشت داده شود. در اینجا یک آرایه تشکیل شده و هربار اطلاعاتی به آن push می‌شود و در نهایت این آرایه بازگشت داده خواهد شد:
function splitIntoRows(icons, rowLength) {
   var rows = [];
   for (var i = 0; i < icons.length; i += rowLength) {
       rows.push(icons.slice(i, i + rowLength));
   }
   return rows;
}
اما با استفاده از Generators دیگر نیازی نیست تا یک آرایه برای جمع آوری این لیست تشکیل شود و به این ترتیب مصرف حافظه‌ی برنامه کاهش خواهد یافت و همچنین اینبار این خروجی می‌تواند نامتنهاهی باشد (کاری که با استفاده از آرایه‌های معمولی قابل انجام نیست):
 function* splitIntoRows(icons, rowLength) {
   for (var i = 0; i < icons.length; i += rowLength) {
      yield icons.slice(i, i + rowLength);
  }
}

روش‌هایی برای خاموش کردن Generators

Generators علاوه بر متد next، دارای متدهای return و throw نیز هستند.
فراخوانی (generator.throw(error همانند این است که در بین کار، به متدی برخورده‌ایم که استثنایی را صادر کرده‌است و سبب خاتمه‌ی کار Generator شده‌است.
اگر متد return آن‌ها فراخوانی شود، کار Generator پایان می‌یابد (خاصیت done شیء بازگشتی بلافاصله true می‌شود) و فراخوانی next، پس از آن، دیگر اثری نخواهد داشت:
function* numbers () {
  yield 1
  yield 2
  yield 3
}

var g = numbers()
console.log(g.next())
// <- { done: false, value: 1 }
console.log(g.return())
// <- { done: true }
console.log(g.next())
// <- { done: true }, as we know
مشابه آن حالتی است که در بین yieldها یک return وجود داشته باشد:
function* numbers () {
  yield 1
  yield 2
  return 3 
  yield 4
}

console.log([...numbers()])
// <- [1, 2]
در این حالت نیز return سبب خاتمه‌ی Generator شده‌است.

اگر به متد return خود Generator، پارامتری ارسال شود، این مقدار، مقدار نهایی بازگشت داده شده خواهد بود:
function* numbers () {
  yield 1
  yield 2
  return 3
  yield 4
}

var g = numbers()
console.log(g.next())
// <- { done: false, value: 1 }
console.log(g.return(5))
// <- { done: true, value: 5 }
console.log(g.next())
// <- { done: true }
در این مثال (g.return(5 سبب خاتمه‌ی Generator شده‌است و همچنین مقدار نهایی آن‌را نیز تعیین کرده‌است (عدد 5 بجای عدد 2).
در حالتیکه به متد return پارامتری ارسال می‌شود، قسمت finally بدنه‌ی try/finally نوشته شده حتما اجرا خواهد شد و سپس کار خاتمه پیدا می‌کند:
function* numbers () {
  yield 1
  try {
       yield 2
  } finally {
       yield 3
       yield 4
  }
  yield 5
}

var g = numbers()
console.log(g.next())
// <- { done: false, value: 1 }
console.log(g.next())
// <- { done: false, value: 2 }
console.log(g.return(6))
// <- { done: false, value: 3 }
console.log(g.next())
// <- { done: false, value: 4 }
console.log(g.next())
// <- { done: true, value: 6 }
در این مثال (g.return(6 پس از yield 2 فراخوانی شده‌است. اما همانطور که مشاهده می‌کنید، بدنه‌ی finally کاملا اجرا شده و سپس Generator با عدد 6 خاتمه یافته‌است.

‫ساختارهای داده‌ی توکار ES 6

$
0
0
در ES 5 تنها آرایه (Array) و آبجکت (Object) را به عنوان ساختار داده‌ایی، به صورت توکار در اختیار داریم.
Array یک کالکشن مبتنی بر ایندکس است. همچنین می‌توان هر نوع مقداری را در آن ذخیره کرد:
var collection = ['a', 1, /3/, {}];
یعنی هر کدام از اعضای آرایه می‌توانند جنس متفاوتی داشته باشند. همانطور که در کد فوق مشاهده می‌کنید اعضای آرایه به ترتیب از کاراکتر، عدد، عبارت با قاعده و در نهایت یک شیء خالی تشکیل شده است. همانطور که عنوان شد آرایه‌ها در جاوا اسکریپت همانند دیگر زبان‌های برنامه‌نویسی مبتنی بر ایندکس هستند، یعنی می‌توان براساس ایندکس به هر کدام از اعضای آرایه دسترسی داشت:
collection[0];
می‌توان از پراپرتی length نیز برای دریافت سایز آرایه استفاده کرد:
collection.length
همانطور که در مثال ابتدای بحث مشاهده کردید، آرایه‌ها در جاوا اسکریپت توسط سینتکس [] قابل تعریف هستند. تعدادی تابع توکار برای کار با آرایه‌ها موجود است که در اینجا می‌توانید لیست کامل آنها را مشاهد نمائید. همچنین می‌توانید از کتابخانه‌های دیگری مانند Underscore.js که در واقع هدف آن‌ها افزودن یکسری قابلیت‌ها به جاوا اسکریپت است، استفاده کنید:
var numbers = [1, 2, 3];

_.each(numbers, function (num) {
    write(num);
});
در ES 6 تعدادی تابع جدید به Array اضافه شده که کار با آرایه‌ها را ساده‌تر کرده است. در ادامه تعدادی از این توابع را بررسی خواهیم کرد.
تابع find
این تابع از ورودی، یک callback را گرفته و نتایج یافته شده را در خروجی برمی‌گرداند:
var ary = [1, 5, 10];
var match = ary.find(item => item > 8);
تابع findIndex
این تابع مشابه تابع find عمل می‌کند با این تفاوت که در خروجی ایندکس عنصر یافته شده را برمی‌گرداند:
var match = ary.findIndex(item => item > 8);
تابع fill
از این تابع می‌توان جهت مقداردهی اعضای آرایه با پارامتر موردنظر استفاده کرد:
ary.find('a');
// خروجی ["a", "a", "a"]
لازم به ذکر است، به این تابع می‌توانیم دو پارامتر دیگر را نیز پاس دهیم:
var ary = [1, 5, 10, 5, 6];
ary.fill('a', 2, 3)
در کد فوق پارامتر دوم یعنی نقطه شروع و پارامتر سوم یعنی نقطه پایان:
// خروجی
[ 1, 5, "a", 5, 6 ]
تابع copyWithin
با کمک این تابع می‌توانیم قسمتی از یک آرایه را کپی کرده و در محل دیگری از آرایه ذخیره کنیم:
[1, 2, 3, 4, 5].copyWithin(0, 3);
کد فوق از نقطه‌ی سوم شروع به کپی کردن آیتم‌ها کرده و آنها را در موقعیت صفرم آرایه به بعد قرار می‌دهد. در نتیجه خروجی آن به این صورت است:
// [4, 5, 3, 4, 5]
لازم به ذکر است که یک پارامتر سوم را نیز می‌توانیم جهت تعیین نقطه‌ی پایان به تابع فوق اضافه کنیم:
[1, 2, 3, 4, 5].copyWithin(0, 3, 4);
// خروجی 
// [4, 2, 3, 4, 5]
در ES 6 علاوه بر سینتکس literal می‌توان از سازنده‌ی کلاس Array نیز جهت تعریف آرایه‌ها، استفاده کرد:
var ary = new Array(1, 2);
کد فوق یک آرایه را با دو مقدار 1 و 2 ایجاد می‌کند. اگر بخواهیم یک آیتم جدید را به آرایه‌ی فوق اضافه کنیم، باید آن را نیز به پارامترهای فوق اضافه کرد:
var ary = new Array(1, 2, 3);
ممکن است فکر کنید توسط کد زیر آرایه‌ایی تنها با یک آیتم برای ما ایجاد خواهد شد:
var ary = new Array(3);
در واقع کد فوق یک آرایه با اندازه‌ی سه و محتوای undefined را برای شما ایجاد خواهد کرد. در نتیجه برای ایجاد آرایه‌ایی با یک آیتم و مقدار 3 باید از متد Of کلاس Array استفاده کنیم:
var Ofary = Array.of(3);

Set
Set یک ساختار داده‌ایی جدید در ES 6 است. این ساختار داده‌ایی امکان تعریف کالکشنی از مقادیر را به صورت unique، در اختیارمان قرار می‌دهد. برخلاف آرایه‌ها مقادیر درون Set نمی‌تواند یکسان باشند. در کد زیر نحوه‌ی ایجاد یک Set نشان داده شده است:
var set = new Set();
set.add(1);
set.add(2);
set.add(3);
console.log(set.size); // logs 3
 Set به صورت یک مجموعه‌ی  Iterable است یعنی می‌توان اعضای این مجموعه را آیتم به آیتم پیمایش کرد. همانطور که در کد فوق مشاهده می‌کنید توسط add می‌توانیم آیتم جدیدی را به مجموعه اضافه کنیم. همچنین اگر مایل بودید می‌توانید مجموعه را توسط یک آرایه به صورت زیر نیز مقداردهی کنید:
var set = new Set([1, 2, 3]);
console.log(set.size); // logs 3
از توابع has, delete, clear نیز به ترتیب می‌توان جهت خالی کردن مجموعه، حذف یک آیتم از مجموعه و بررسی یک آیتم در مجموعه استفاده کرد:
var set = new Set();
set.has(1); // false
set.add(1);
set.has(1); // true
set.clear();
set.has(1); // false
set.add(1);
set.add(2);
set.size;   // 2
set.delete(2);
set.size;   // 1
از تابع feorach نیز می‌توانیم برای حرکت بین آیتم‌های مجموعه استفاده کنیم:
var set = new Set();
set.add('Vahid');
set.add('Sirwan');

var i = 0;
set.forEach(item => i++);
console.log(i);
همچنین از سینتکس for...of نیز می‌توان برای پیمایش مجموعه استفاده کرد:
var set = new Set();
set.add('Vahid');
set.add('Sirwan');

var i = 0;
for(let item of set) {
    i++;
}
console.log(i);
Set دارای یک تابع دیگر با نام entries است. با کمک این تابع یک iterator از مجموعه برگردانده خواهد شد که با کمک تابع next می‌توان به عناصر بعدی مجموعه دسترسی پیدا کرد:
var set = new Set();
set.add("Sirwan");
set.add(1);
set.add("Afifi");

var setIter = set.entries();

console.log(setIter.next().value); // ["Sirwan", "Sirwan"]
console.log(setIter.next().value); // [1, 1]
console.log(setIter.next().value); // ["Afifi", "Afifi"]

Map
برخلاف Set که یک مجموعه از مقادیر (values) است، Map یک مجموعه از کلید/مقدار (key/value) می‌باشد. در اینجا نیز کلیدها باید unique باشند. همچنین می‌توان از هر نوعی برای کلید استفاده کرد. برای افزودن یک مقدار به این مجموعه باید از تابع set استفاه کنیم:
var map = new Map();
map.set('name', 'Sirwan');

map.get('name'); // Sirwan
همانطور که مشاهده می‌کنید توسط تابع get نیز می‌توانیم با استفاده از کلید، به مقدار آن دسترسی داشته باشیم. همچنین می‌توانیم آرایه‌ایی از آرایه‌ها را به عنوان کلید در یک Map ذخیره کنیم:
var map = new Map([['name', 'Sirwan'], ['age', 27]]);
map.has('age'); // true
map.get('age'); // 27
map.get('name'); // Sirwan

نکته‌ایی که در استفاده از Map باید به آن دقت کنید این است که در اینجا هیچ تبدیل نوعی را بر روی کلیدها نداریم:
var map = new Map();

map.set(1, true);
map.has("1"); // false

map.set("1", true);
map.has("1"); // true
همانند Set برای Map نیز می‌توانیم از توابع delete و clear استفاده کنیم. برای استفاده از foreach باید برای callback دو پارامتر را ارائه دهیم. یکی برای value و دیگری برای key:
var map = new Map([['name', 'Sirwan'], ['age', 27]]);
var i = 0;
map.forEach(function (value, key) {
    i++;
});
console.log(i); // log 2
برای سینتکس for...of نیز می‌توانیم به اینصورت عمل کنیم:
for (var [key, value] of map) {
    i++;
}
شاید بپرسید که همین کار را می‌توان با استفاده از آرایه‌ها نیز انجام داد و چه نیازی به یک ساختار داده‌ایی جدید است؟
اگر بخواهید Map را با استفاده از آرایه‌ها شبیه‌سازی کنید باید از Associative Arrays استفاده کنید؛ به زبان ساده در این‌حالت به جای استفاده از عدد به جای ایندکس می‌توان رشته‌ها نیز استفاده کرد. به عنوان مثال کد زیر را در نظر بگیرید:
var newArray = new Array();
newArray["name"] = "Sirwan";
newArray["lastName"] = "Afifi";
در اینجا ایندکس‌ها به ترتیب name و lastName هستند و به عنوان کلید مورد استفاده قرار می‌گیرند. کلیدها نیز به مقادیر "Sirwan" و "Afifi" مپ شده‌اند. حالت فوق شبیه به یک دیکشنری عمل می‌کند. اما همانطور که عنوان شد در اینجا کلید به صورت رشته‌ایی است و نمی‌توان از اشیاء به عنوان کلید استفاده کرد؛ زیرا در نهایت تبدیل به رشته خواهند شد:
let user1 = { name: "Vahid" };
let user2 = { name: "Sirwan" };

let result = {};
result[user1] = 10;
result[user2] = 20;

console.log(result[user1]); // logs 20
console.log(result[user2]); // logs 20
در کد فوق هر کدام از شیء‌ها را به عنوان کلید در نظر گرفته‌ایم و برای هر کدام مقادیر 10 و 20 را ست کرده‌ایم. اما خروجی هر کدام 20 است؛ در حالیکه باید به ترتیب عدد 10 و سپس عدد 20 در خروجی نمایش داده شود. دلیل آن نیز کاملاً مشخص است زیرا اگر در جاوا اسکریپت برای یک شیء تابع toString را فراخوانی کنیم، مقدار "[object object]" در خروجی نمایش داده خواهد شد. در نتیجه در کد فوق در واقع هر بار ایندکس "[object object]" را به‌روز رسانی کرده‌ایم:
result["[object object]"] = 10;
result["[object object]"] = 20;

console.log(result["[object object]"]); // logs 20
console.log(result["[object object]"]); // logs 20

WeakMap and WeakSet
فرض کنید درون DOM سه عنصر div دارید و می‌خواهید این سه div را درون یک Set ذخیره کنید:

در این‌حالت آیتم‌های درون Set ارجاع مستقیمی را به عناصر موجود در DOM دارند. اکنون حالتی را در نظر بگیرید که بخواهیم یکی از عناصر موجود درون DOM را حذف کنیم. در اینحالت آیتم درون Set که به این عنصر اشاره دارد هنوز حذف نشده است و همچنان ارجاعی را به آن عنصر دارد. بنابراین تا زمانیکه آیتم از Set حذف نشود Garbage Collector نمی‌تواند حافظه‌ی اختصاص داده شده را مجدداً بازیابی کند. در نتیجه استفاده از Set و یا Map در چنین سناریوهایی منجر به نشتی حافظه خواهد شد. برای حل این مشکل می‌توانیم از WeakMap و یا WeakSet استفاده کنیم. در این‌حالت WeakMap و WeakSet ارجاع مستقیمی به اشیایی که به آنها اضافه می‌شوند، ندارند. در نتیجه GC به راحتی می‌تواند حافظه‌ی اختصاص داده شده را بعد از حذف اشیاء بازیابی کند.
صرف‌نظر از رفع مشکل حافظه، WeakMap و WeakSet شبیه به Map و Set عمل می‌کنند، اما یکسری محدودیت‌هایی در استفاده از آنها وجود دارد:
  • WeakMap و WeakSet فاقد پراپرتی‌های size, entries, values و متد foreach هستند.
  • WeakMap همچنین فاقد keys است.

‫معرفی Selector های CSS - قسمت 7

$
0
0
61- :any-link
تمامی تگ‌هایی را انتخاب می‌کند که می‌توانند نقش لینک را در صفحه ایفا کنند. در واقع تگ‌های a، area و link را انتخاب می‌نماید که دارای ویژگی href هستند و به عنوان لینک عمل می‌کنند.
<style>
    :any-link {
        color: red;
    }</style><a href="page1.htm">Link 1</a>
در مثال فوق، متن Link 1 به رنگ قرمز نمایش می‌یابد.
      Selector نسخه CSS
NoNo
No
NoNo :any-link  4
62- :local-link
تمامی لینک هایی را انتخاب می‌کند که با آدرس صفحه‌ی جاری یکسان می‌باشند.
<style>
    :local-link {
        color: red;
    }</style><a href="http://www.example.com/article/">Link 1</a><a href="http://www.example.com/article">Link 2</a><a href="http://www.example.com/article/">Link 3</a><a href="http://www.example.com/article/12">Link 4</a>
با فرض اینکه آدرس صفحه‌ی جاری http://www.example.com/article/ می باشد مثال فوق را بررسی می‌کنیم. متن Link 1 و Link3 به رنگ قرمز نمایش می‌یابند، زیرا مشابه آدرس صفحه‌ی جاری می‌باشند.
      Selector نسخه CSS
NoNo
No
NoNo :local-link  4 
63- :local-link(n)
تمامی لینک هایی را انتخاب می‌کند که آدرس آنها با آدرس صفحه‌ی جاری یکسان بوده و n بخش از آدرس آنها با بخش‌های آدرس جاری یکسان باشند. در واقع n، تعداد بخش هایی از آدرس لینک را مشخص می‌کند که با بخش‌های آدرس صفحه‌ی جاری مطابقت داشته باشند. در مطابقت آدرس، از scheme، username، password، port، query string و fragment صرف نظر می‌شود تا یکسان بودن آدرس را تشخیص دهد. سپس تشخیص می‌دهد کدام بخش‌های آدرس با هم یکسان می‌باشند. جهت اطلاع باید عرض کنم که شکل کامل یک آدرس به صورت scheme:[//[user:password@]host[:port]][/]path[?querystring][#fragment] می‌باشد.
<style>
    :local-link {
        color: red;
    }
    :local-link(0) {
        text-decoration: none;
    }
    :local-link(1) {
        text-decoration: overline;
    }
    :local-link(2) {
        background: blue;
    }</style><a href="http://www.example.com">Link 1</a><a href="http://www.example.com/2016">Link 2</a><a href="http://www.example.com/2016/01">Link 3</a><a href="http://www.example.com/2016/01/">Link 4</a><a href="http://www.example.com/2016/01/02">Link 5</a><a href="https://www.example.com/2016/01/">Link 6</a><a href="http://example.com/2016/01">Link 7</a>
با فرض اینکه آدرس صفحه‌ی جاری http://www.example.com/2016/01/ می باشد مثال فوق را بررسی میکنیم. Link 1 تحت تاثیر Selector دوم، بدون زیرخط نمایش می‌یابد. Link 2 تحت تاثیر Selector دوم و سوم، بدون زیرخط و با روخط نمایش می‌یابد. Link 3، Link 5 و Link 6 تحت تاثیر Selector دوم و سوم و چهارم، بدون زیرخط، با روخط و پس زمینه‌ی آبی نمایش می‌یابند. Link 6 به دلیل استفاده از https نمی‌تواند تحت تاثیر Selector اول قرار بگیرد. Link 4 تحت تاثیر Selector اول و دوم و سوم و چهارم، به رنگ قرمز، بدون زیرخط، با روخط و پس زمینه‌ی آبی نمایش می‌یابد. Link 7 هم بدون تاثیر هیچ Selector ی بر روی آن، بدون قالب باقی می‌ماند، زیرا قسمت host لینک با آدرس مطابقت ندارد.
      Selector نسخه CSS
NoNo
No
NoNo :local-link(n)  4
64- :active-drop-target
تگی را انتخاب می‌کند که در حال حاضر به عنوان مقصد یک تگ drag (کشیده) شده در نظر گرفته شده است. به عبارت دیگر، تگی را با عمل drag کشیده ایم و بر روی یک تگ مقصد قرار داده ایم، ولی هنوز عمل drop یا رها کردن صورت نگرفته است.
         Selector نسخه CSS
NoNo
No
NoNo:active-drop-target  4 
65- :valid-drop-target
تگی را انتخاب می‌کند که در حال حاضر به عنوان مقصد یک تگ drag (کشیده) شده در نظر گرفته شده است و برای عمل drop معتبر می‌باشد. به عبارت دیگر، تگ مقصد امکان پذیرش یک تگ را با عمل Drag & Drop دارد.
         Selector نسخه CSS
NoNo
No
NoNo:valid-drop-target  4 
66- :invalid-drop-target
تگی را انتخاب می‌کند که در حال حاضر به عنوان مقصد یک تگ drag (کشیده) شده در نظر گرفته شده است و برای عمل drop معتبر نمی‌باشد. به عبارت دیگر، تگ مقصد امکان پذیرش یک تگ را با عمل Drag & Drop ندارد.
         Selector نسخه CSS
NoNo
No
NoNo:invalid-drop-target  4 
<style>
    :active-drop-target {
        background: blue;
    }
    :valid-drop-target {
        border: 1px solid green;
    }
    :invalid-drop-target {
        border: 1px solid red;
    }
    .container {
        width: 200px;
        height: 200px;
    }</style><div class="container"></div><img src="image.jpg" draggable="true"/>
در مثال فوق، با Drag نمودن تصویر بر روی تگ div، تحت تاثیر Selector اول، رنگ پس زمینه‌ی div آبی می‌شود. اگر امکان پذیرش img توسط تگ div وجود داشته باشد، کادری سبز رنگ دور تگ div نمایش می‌یابد در غیر اینصورت کادر قرمز رنگ دور آن نمایش خواهد یافت.
67- :current
تگی را انتخاب می‌کند که در حال حاضر در حال نمایش یا ارائه می‌باشد. این Selector در زمان پردازش صوت، تصویر، نمایش زیر نویس و غیره در تگ canvas مورد استفاده قرار می‌گیرد.
         Selector نسخه CSS
NoNo
No
NoNo:current  4 
68- :past
تگی را انتخاب می‌کند که قبل از :current نمایش یافته است.
         Selector نسخه CSS
NoNo
No
NoNo:past  4 
69- :future
تگی را انتخاب می‌کند که بعد از :current نمایش یافته است.
         Selector نسخه CSS
NoNo
No
NoNo:future  4 
هر سه Selector فوق می‌توانند با دریافت آرگومان به صورت مجموعه ای از Selector ها، تگ‌های خاصی را انتخاب کنند یا نتیجه‌ی انتخاب را محدود نمایند. اگر بدون آرگومان به کار روند، تک مورد نظر را بدون در نظر گرفتن محدویت انتخاب می‌نمایند.
<style>
    :past(div,p) {
        background: red;
    }
    :current(div,p) {
        background: green;
    }
    :future(div,p) {
        background: yellow;
    }</style>
در مثال فوق، تگ‌های div یا p که در حال حاضر در حال نمایش می‌باشند، با پس زمینه‌ی سبز، تگی که قبل از :current نمایش یافته است با پس زمینه‌ی قرمز و تگی که بعد از :current نمایش خواهد یافت با پس زمینه‌ی زرد نمایش می‌یابد.
70- :placeholder-shown
تمامی تگ‌های input و textarea را انتخاب می‌کند که در حال نمایش یا حاوی placeholder می‌باشند و هنوز متنی در آنها وارد نشده است. در حال حاضر به صورت آزمایشی و با vendor prefix قابل استفاده است.
<style>
    :placeholder-shown {
        color: gray;
    }</style><input placeholder="Enter Username"/><input placeholder="Enter Password"/>
در مثال فوق، اگر در هر کدام از تگ‌های input، هیچ متنی وارد نشده باشد، متن‌های Enter Username و Enter Password به رنگ طوسی نمایش می‌یابند.
         Selector نسخه CSS
-webkit--wekit-
-ms-
-moz--webkit-:placeholder-shown  4 















‫توسعه سیستم مدیریت محتوای DNTCms - قسمت اول

$
0
0
قصد داریم طی یک سری مقالات به توسعه یک سیستم مدیریت محتوا بپردازیم. مسلما فاصله‌ی زمانی بین انتشار مقالات این سری، کمی زیاد خواهد بود. ولی سعی خواهیم کرد تا قدم به قدم و با تحلیل و توضیح کافی هر بخش به این هدف برسیم.
همکاران این قسمت:
پیشنیاز‌ها:
در زیر بخش هایی که برای این سیستم تا به امروز در نظر گرفتیم (مسلما با ارائه ایده‌ها و بازخورد‌های دوستان این امکانات دستخوش تغییر خواهند شد) ، به شرح زیر میباشد:
  • انجمن
  • ارتباط دوستی
  • سیستم ترفیع رتبه
  • Themeable
  • سیستم Following
  • صفحات داینامیک
  • سیستم پیام رسانی
  • امکان ساخت گروه‌های شخصی برای انتشار مطالب خود (توسط کاربران) با اعمال دسترسی مختلف
  • پیغام خصوصی
  • وبلاگ
  • نظرسنجی ها
  • مدیریت کاربران با دسترسی‌ها داینامیک
  • اخبار
  • آگهی ها
در این قسمت مدل‌های مربوط به بخش وبلاگ را بررسی میکنیم. ابتدا قصد داشتیم تا امکان نگارش یک پست توسط چندین کاربر را به طور همزمان، در سیستم قرار دهیم و ایده کار هم که توسط یکی دوستان (محمد شریفی) پیشنهاد شد به صورت زیر بود:
ابتدا کاربری به عنوان ایجاد کننده اصلی پست، بخش‌ها مختلفی را برای یک پست در نظر بگیرد و هر قسمت را به یک همکار نسبت دهد و با اتمام تمام بخش‌ها و اعلام آن توسط همکاران، کاربر اصلی ایجاد کننده پست بتواند بخش‌ها را باهم ادغام کند و برای آماده انتشار نهایی باشد.
ولی خب به نظر بنده الان سیستم‌های ارتباطی قدرتمندتری هم هست که همکاران در مورد نگارش یک پست به صورت مشارکتی بپردازند و صرفا در مرحله‌ی انتشار، این کاربران به عنوان کاربران همکار به پست منتشر شده نسبت داده شوند تا در زیر پست مشخصات کامل آنها قابل مشاهد باشد (راه حلی که پیاده سازی خواهیم کرد).
مدل‌های در نظر گرفته شده برای سیستم وبلاگ به صورت زیر می‌باشد:

مخزن برچسب ها
 /// <summary>
    /// Represents the lable 
    /// </summary>
    public class Tag
    {
        #region Ctor
        /// <summary>
        /// Create one instance of <see cref="Tag"/>
        /// </summary>
        public Tag()
        {
            Id = SequentialGuidGenerator.NewSequentialGuid();
        }
        #endregion

        #region Properties
        /// <summary>
        /// sets or gets Tag's identifier
        /// </summary>
        public Guid  Id { get; set; }
        /// <summary>
        /// sets or gets Tag's name
        /// </summary>
        public  string Name { get; set; }
        #endregion

        #region NavigationProperties
        /// <summary>
        /// sets or gets Tag's posts
        /// </summary>
        public virtual ICollection<BlogPost> BlogPosts { get; set; }
        #endregion
    }
کلاس بالا مشخص کننده‌ی مخزن برچسب‌های سیستم ما خواهد بود. جدول حاصل از این مدل با جداول سایر بخش‌ها که نیاز به داشتن برچسب خواهند داشت، ارتباط چند به چند خواهد داشت. برای این منظور همانطور که مشخص است در قسمت NavigationProperties یک لیست از BlogPost معرفی شده است و در ادامه خواهیم دید که لیستی از کلاس Tag هم در کلاس BlogPost معرفی خواهد شد.

مدل پیش نویس ها
  /// <summary>
    /// Represents the Post's Draft
    /// </summary>
    public  class BlogDraft
    {
        #region Ctor
        /// <summary>
        /// create one instance of <see cref="BlogDraft"/>
        /// </summary>
        public  BlogDraft()
        {
            Id = SequentialGuidGenerator.NewSequentialGuid();
        }
        #endregion

        #region Properties
        /// <summary>
        /// gets or sets Id of post's draft
        /// </summary>
        public virtual  Guid Id { get; set; }
        /// <summary>
        /// gets or sets body of post's draft
        /// </summary>
        public virtual  string Body { get; set; }
        /// <summary>
        /// gets or set title of post's draft
        /// </summary>
        public virtual  string Title { get; set; }
        /// <summary>
        /// gets or sets tags of post's draft that seperated using ','
        /// </summary>
        public virtual  string TagNames { get; set; }
        /// <summary>
        /// gets or sets value indicating whether this draft is ready to publish
        /// </summary>
        public virtual  bool IsReadyForPublish { get; set; }
        /// <summary>
        /// ges ro sets DateTime that this draft added
        /// </summary>
        public virtual  DateTime CreatedOn { get; set; }
        /// <summary>
        /// gets or sets information of User-Agent
        /// </summary>
        public virtual string Agent { get; set; }
        /// <summary>
        /// gets or sets date that this draft publish as ready
        /// </summary>
        public virtual DateTime? ReadyForPublishOn { get; set; }
        #endregion

        #region NavigationProperties
        /// <summary>
        /// gets or sets Id of user that he is owner of this draft
        /// </summary>
        public virtual  long OwnerId { get; set; }
        /// <summary>
        /// gets or sets user that he is owner of this draft
        /// </summary>
        public virtual  User Owner { get; set; }
        #endregion
    }
کلاس فوق برای ذخیره سازی پست‌های پیشنویس کاربران در نظر گرفته شده است. شاید بهتر بود این پیش نویس‌ها هم در همان مدل پستی که در ادامه مشاهده می‌کنید ادغام شوند. ولی این یک تصمیم شخصی برای این کار بوده و برای سیستی بزرگی که امکان دارد حذف و درج پیشنویس‌ها زیاد باشد و فرض بر اینکه long هم برای آی دی جواب گو نخواهد بود و نیاز است از Guid ای که به صورت متوالی افزایش میابد به منظور جلوگیری از Fragmentation، استفاده کرد. بقیه فیلد‌ها هم مشخص هستند و نیازی به توضیح اضافی نخواهد بود. مسلما هر کاربر می‌تواند چندین پیشنویس داشته باشد. لذا ارتباط یک به چند مابین کاربر و پیشنویس پست، خواهیم داشت.
بخش‌های مختلفی که در ابتدای مقاله مطرح شدند، دارای یکسری خصوصیات مشترک می‌باشند و برای این منظور این خصوصیات را در یک کلاس پایه کپسوله کرده‌ایم. شاید تفکر شما این باشد که میخواهیم ارث بری TPH یا TPT را اعمال کنیم. ولی با توجه به سلیقه‌ی شخصی، قصد استفاده از ارث بری را ندارم.

مدل امتیاز دهی کاربران
/// <summary>
    /// Section of Rating
    /// </summary>
    public enum RatingSection
    {
        News,
        Announcement,
        ForumTopic,
        BlogComment,
        NewsComment,
        PollComment,
        AnnouncementComment,
        ForumPost,
        ...
    }


    /// <summary>
    /// Represents Rating Record regard by section type for Rating System
    /// </summary>
    public class UserRating
    {
        #region Ctor
        /// <summary>
        /// Create one instance of <see cref="UserRating"/>
        /// </summary>
        public UserRating()
        {
            Id = SequentialGuidGenerator.NewSequentialGuid();
        }
        #endregion

        #region Properties
        /// <summary>
        /// gets or sets Id of Rating Record
        /// </summary>
        public virtual Guid Id { get; set; }
        /// <summary>
        /// gets or sets value of rate
        /// </summary>
        public virtual double RatingValue { get; set; }
        /// <summary>
        /// gets or sets Section's Id 
        /// </summary>
        public virtual long SectionId { get; set; }
        /// <summary>
        /// gets or sets Section 
        /// </summary>
        public virtual RatingSection Section { get; set; }
        #endregion

        #region Navigation Properties
        /// <summary>
        /// gets or sets user that rate one section
        /// </summary>
        public virtual User Rater { get; set; }
        /// <summary>
        /// gets or sets Rater Id that rate one section
        /// </summary>
        public virtual long RaterId { get; set; }

        #endregion
    }
نوع داده‌ی شمارشی RatingSection مشخص کننده‌ی بخش‌های مختلف سیستم می‌باشد که نیاز به امتیاز دهی دارند. چون سیستم ما یک سیستم بسته می‌باشد و برای امتیاز دهی نیاز به احراز هویت خواهد بود، لذا باید از امتیاز دادن بیش از یک بار برای هر بخش جلوگیری کنیم. برای این کار می‌توان بین تمام بخش‌های موجود ارتباط‌های چند به چند در نظر گرفت. برای مثال یک ارتباط چند به چند بین کاربر و نظرات که مشخص کننده‌ی این است که یک کاربر می‌تواند به چند نظر امتیاز دهد و هر نظر هم میتواند چندین امتیاز دهنده داشته باشد ولی تعداد جداول بالا خواهد رفت و کار آن هم زیاد خواهد شد. برای این منظور میتوان یک جدول به مانند UserRating در نظر گرفت که صرفا با جدول کاربر ما یک ارتباط یک به چند دارد و با استفاده از خصوصیت RatingSection موجود در این کلاس وخصوصیت SectionId و همچنین در نظر گرفتن یک کلید منحصر به فرد بر روی خصوصیت‌های RatingSection ، RaterId و SectionId، از امتیاز دادن چند باره‌ی کاربر به یک بخش جلوگیری کرد.
/// <summary>
    /// Represent the rating as ComplexType
    /// </summary>
    [ComplexType]
    public class Rating
    {
        /// <summary>
        /// sets or gets total of rating
        /// </summary>
        public virtual double? TotalRating { get; set; }
        /// <summary>
        /// sets or gets rater's count
        /// </summary>
        public virtual long? RatersCount { get; set; }
        /// <summary>
        /// sets or gets average of rating
        /// </summary>
        public virtual double? AverageRating { get; set; }
    }
کلاس بالا یک ComplexType می‌باشد که در نهایت به هیچ جدولی مپ نخواهد شد و صرفا به منظور کپسوله کردن یکسری فیلد مورد استفاده قرار گرفته است و از این کلاس می‌توان در هر بخش که لازم است امتیاز دهی داشته باشیم، به عنوان یک خصوصیت، استفاده کرد.

کلاس پایه‌ی محتوا
 /// <summary>
    /// Represents a base class for every content in system
    /// </summary>
    public abstract class BaseContent
    {
        #region Properties

        /// <summary>
        /// get or set identifier of record
        /// </summary>
        public virtual long Id { get; set; }
        /// <summary>
        /// gets or sets date of publishing content
        /// </summary>
        public virtual DateTime PublishedOn { get; set; }
        /// <summary>
        /// gets or sets Last Update's Date
        /// </summary>
        public virtual DateTime ModifiedOn { get; set; }
        /// <summary>
        /// gets or sets the blog pot body
        /// </summary>
        public virtual string Body { get; set; }
        /// <summary>
        /// gets or sets the content title
        /// </summary>
        public virtual string Title { get; set; }
        /// <summary>
        /// gets or sets value  indicating Custom Slug
        /// </summary>
        public virtual string SlugUrl { get; set; }
        /// <summary>
        /// gets or sets meta title for seo
        /// </summary>
        public virtual string MetaTitle { get; set; }
        /// <summary>
        /// gets or sets meta keywords for seo
        /// </summary>
        public virtual string MetaKeywords { get; set; }
        /// <summary>
        /// gets or sets meta description of the content
        /// </summary>
        public virtual string MetaDescription { get; set; }
        /// <summary>
        /// gets or sets 
        /// </summary>
        public virtual string FocusKeyword { get; set; }
        /// <summary>
        /// gets or sets value indicating whether the content use CanonicalUrl
        /// </summary>
        public virtual bool UseCanonicalUrl { get; set; }
        /// <summary>
        /// gets or sets CanonicalUrl That the Post Point to it
        /// </summary>
        public virtual string CanonicalUrl { get; set; }
        /// <summary>
        /// gets or sets value indicating whether the content user no Follow for Seo
        /// </summary>
        public virtual bool UseNoFollow { get; set; }
        /// <summary>
        /// gets or sets value indicating whether the content user no Index for Seo
        /// </summary>
        public virtual bool UseNoIndex { get; set; }
        /// <summary>
        /// gets or sets value indicating whether the content in sitemap
        /// </summary>
        public virtual bool IsInSitemap { get; set; }
        /// <summary>
        /// gets or sets a value indicating whether the content comments are allowed 
        /// </summary>
        public virtual bool AllowComments { get; set; }
        /// <summary>
        /// gets or sets a value indicating whether the content comments are allowed for anonymouses 
        /// </summary>
        public virtual bool AllowCommentForAnonymous { get; set; }
        /// <summary>
        /// gets or sets  viewed count by rss
        /// </summary>
        public virtual long ViewCountByRss { get; set; }
        /// <summary>
        /// gets or sets viewed count 
        /// </summary>
        public virtual long ViewCount { get; set; }
        /// <summary>
        /// Gets or sets the total number of  comments
        /// <remarks>The same as if we run Item.Comments.where(a=>a.Status==Status.Approved).Count()
        /// We use this property for performance optimization (no SQL command executed)
        /// </remarks>
        /// </summary>
        public virtual int ApprovedCommentsCount { get; set; }
        /// <summary>
        /// Gets or sets the total number of  comments
        /// <remarks>The same as if we run Item.Comments.where(a=>a.Status==Status.UnApproved).Count()
        /// We use this property for performance optimization (no SQL command executed)
        public virtual int UnApprovedCommentsCount { get; set; }
        /// <summary>
        /// gets or sets value  indicating whether the content is logical deleted or hidden
        /// </summary>
        public virtual bool IsDeleted { get; set; }
        /// <summary>
        /// gets or sets rating complex instance
        /// </summary>
        public virtual Rating Rating { get; set; }
        /// <summary>
        /// gets or sets value indicating whether the content show with rssFeed
        /// </summary>
        public virtual bool ShowWithRss { get; set; }
        /// <summary>
        /// gets or sets value indicating maximum days count that users can send comment
        /// </summary>
        public virtual int DaysCountForSupportComment { get; set; }
        /// <summary>
        /// gets or sets information of User-Agent
        /// </summary>
        public virtual string Agent { get; set; }
        /// <summary>
        /// gets or sets icon name with size 200*200 px for snippet 
        /// </summary>
        public string SocialSnippetIconName { get; set; }
        /// <summary>
        /// gets or sets title for snippet
        /// </summary>
        public string SocialSnippetTitle { get; set; }
        /// <summary>
        /// gets or sets description for snippet
        /// </summary>
        public string SocialSnippetDescription { get; set; }
        /// <summary>
        /// gets or sets body of content's comment
        /// </summary>
        public byte[] RowVersion { get; set; }
        /// <summary>
        /// gets or sets name of tags seperated by comma that assosiated with this content fo increase performance
        /// </summary>
        public virtual string TagNames { get; set; }
        /// <summary>
        /// gets or sets counter for Content's report
        /// </summary>
        public virtual int ReportsCount { get; set; }
        #endregion

        #region NavigationProperties

        /// <summary>
        /// get or set user that create this record
        /// </summary>
        public virtual User Author { get; set; }

        /// <summary>
        /// gets or sets Id of user that create this record
        /// </summary>
        public virtual long AuthorId { get; set; }
        /// <summary>
        /// get or set  the tags integrated with content
        /// </summary>
        public virtual ICollection<Tag> Tags { get; set; }
        #endregion

    }

نکته‌ای که وجود دارد فیلد‌های ApprovedCommentsCount  UnApprovedCommentsCount و TagNames می‌باشند که هنگام درج نظر جدید باید تعداد نظرات ذخیره شده را  ویرایش کنیم و هنگام ویرایش خود پست یا خبر با ... و یا حتی ویرایش خود تگ یا حذف آن تگ باید TagNames که لیست برچسب‌های محتوا را به صورت جدا شده با (,) از هم دیگر می‌باشد، ویرایش کنیم (جای بحث دارد).

مشخص است که هر یک از مطالب منتشر شده در بخش‌های وبلاگ، اخبار، نظرسنجی و آگهی‌ها، یک کابر ایجاد کننده (Author نامیده‌ایم) خواهد داشت و هر کاربر هم می‌تواند چندین مطلب را ایجاد کند. لذا رابطه‌ی یک به چند بین تمام این بخش‌ها مذکور و کاربر ایجاد خواهد شد.

مدل LinkBack

 /// <summary>
    /// Represents link for implemention linkback
    /// </summary>
    public class LinkBack
    {

        #region Ctor
        /// <summary>
        /// create one instance of <see cref="LinkBack"/>
        /// </summary>
        public LinkBack()
        {
            CreatedOn = DateTime.Now;
            Id = SequentialGuidGenerator.NewSequentialGuid();
        }
        #endregion

        #region Properties
        /// <summary>
        /// gets or sets link's Id
        /// </summary>
        public virtual Guid Id { get; set; }
        /// <summary>
        /// gets or sets text for show Link
        /// </summary>
        public virtual string Title { get; set; }
        /// <summary>
        /// gets or sets link's address 
        /// </summary>
        public virtual string Url { get; set; }
        /// <summary>
        /// gets or set value indicating whether this link is internal o external
        /// </summary>
        public virtual LinkBackType Type { get; set; }
        /// <summary>
        /// gets or sets date that this record is added
        /// </summary>
        public virtual DateTime CreatedOn { get; set; }
        #endregion

        #region NavigationProperties
        /// <summary>
        /// gets or sets Post that associated
        /// </summary>
        public virtual BlogPost Post { get; set; }
        /// <summary>
        /// gets or sets id of Post that associated
        /// </summary>
        public virtual long PostId { get; set; }
        #endregion
    }


 /// <summary>
    /// represents Type of ReferrerLinks
    /// </summary>
    public enum LinkBackType
    {
        /// <summary>
        /// Internal link
        /// </summary>
        Internal,
        /// <summary>
        /// External Link
        /// </summary>
        External
    }

مطمئنا در خیلی از وبلاگ‌ها مثل سایت جاری متوجه نمایش لینک‌ها ارجاع دهنده‌های خارجی و داخلی در زیر مطلب شده‌اید. کلاس LinkBack هم دقیقا برای این منظور در نظرگرفته شده است که عنوان صفحه‌ای که این پست در آنجا لینک داده شده است، به همراه آدرس آن صفحه، در جدول حاصل از این کلاس ذخیره خواهند شد. نوع داده LinkBackType هم برای متمایز کردن رکورد‌های درج شده به عنوان LinkBack در نظر گرفته شده است که بتوان آنها را متمایز کرد، به ارجاعات داخلی و خارجی.

مدل پست ها

  /// <summary> 
    /// Represents a blog post
    /// </summary>
    public  class BlogPost : BaseContent
    {
        #region Ctor
        /// <summary>
        /// Create one Instance of <see cref="BlogPost"/>
        /// </summary>
        public BlogPost()
        {
            Rating = new Rating();
            PublishedOn = DateTime.Now;
        }
        #endregion

       #region Properties
        /// <summary>
        /// gets or sets Status of LinkBack Notifications
        /// </summary>
        public virtual LinkBackStatus LinkBackStatus { get; set; }
        #endregion

        #region NavigationProperties

        /// <summary>
        /// get or set  blog post's Reviews
        /// </summary>
        public virtual ICollection<BlogComment> Comments { get; set; }
        /// <summary>
        /// get or set collection of links that reference to this blog post
        /// </summary>
        public virtual ICollection<LinkBack> LinkBacks { get; set; }
        /// <summary>
        /// get or set Collection of Users that Contribute on this post
        /// </summary>
        public virtual ICollection<User> Contributors  { get; set; }
        #endregion
    }


  /// <summary>
    /// Represents Status for ReferrerLinks
    /// </summary>
    public enum  LinkBackStatus
    {
        [Display(Name ="غیرفعال")]
        Disable,
        [Display(Name = "فعال")]
        Enable,
        [Display(Name = "لینک‌ها داخلی")]
        JustInternal,
        [Display(Name = "لینک‌ها خارجی")]
        JustExternal
    }
 کلاس بالا نشان دهنده‌ی مدل پست‌های ما در سیستم می‌باشد که بیشتر خصوصیات خود را از کلاس پایه‌ی BaseContent به ارث برده و لازم به تکرار آنها نیست. به علاوه یکسری خصوصیات دیگر، از جلمه‌ی آنها یک لیست از کلاس LinkBack، لیستی از کاربران مشارکت کننده به منظور نمایش مشخصات آنها در زیر پست و لیستی از کلاس BlogComment که نشان دهنده‌ی نظرات پست می‌باشد، هم خواهد داشت. خصوصیتی از نوع داده‌ی LinkBackStatus هم صرفا به منظور تنظیمات مدیریتی در نظر گرفته شده است. 

کلاس پایه نظرات
  /// <summary>
    /// Represents a base class for every comment in system 
    /// </summary>

    public abstract class BaseComment
    {
        #region Properties
        /// <summary>
        /// get or set identifier of record
        /// </summary>
        public virtual long Id { get; set; }
        /// <summary>
        /// gets or sets date of creation 
        /// </summary>
        public virtual DateTime CreatedOn { get; set; }
        /// <summary>
        /// gets or sets displayName of this comment's Creator if  he/she is Anonymous
        /// </summary>
        public virtual string CreatorDisplayName { get; set; }
        /// <summary>
        /// gets or sets body of blog post's comment
        /// </summary>
        public virtual string Body { get; set; }
        /// <summary>
        /// gets or sets body of blog post's comment
        /// </summary>
        public virtual Rating Rating { get; set; }
        /// <summary>
        /// gets or sets informations of agent
        /// </summary>
        public virtual string UserAgent { get; set; }
        /// <summary>
        /// gets or sets siteUrl of Creator if he/she is Anonymous
        /// </summary>
        public virtual string SiteUrl { get; set; }
        /// <summary>
        /// gets or sets Email of Creator if he/she is anonymous
        /// </summary>
        public virtual string Email { get; set; }
        /// <summary>
        /// gets or sets status of comment
        /// </summary>
        public virtual CommentStatus Status { get; set; }
        /// <summary>
        /// gets or sets Ip Address of Creator
        /// </summary>
        public virtual string CreatorIp { get; set; }
        /// <summary>
        /// gets or sets datetime that is modified
        /// </summary>
        public virtual DateTime? ModifiedOn { get; set; }
        /// <summary>
        /// gets or sets counter for report this comment
        /// </summary>
        public virtual int ReportsCount { get; set; }
        #endregion

        #region NavigationProperties

        /// <summary>
        /// get or set user that create this record
        /// </summary>
        public virtual User Creator { get; set; }

        /// <summary>
        /// get or set Id of user that create this record
        /// </summary>
        public virtual long? CreatorId { get; set; }
        #endregion
    }

public enum CommentStatus
    {
        /* 0 - approved, 1 - pending, 2 - spam, -1 - trash */
        [Display(Name = "تأیید شده")]
        Approved = 0,
        [Display(Name = "در انتظار بررسی")]
        Pending = 1,
        [Display(Name = "جفنگ")]
        Spam = 2,
        [Display(Name = "زباله دان")]
        Trash = -1
    }
به مانند BaseContent که برای کپسوله کردن خصوصیات تکراری و نه برای اعمال ارث بری TPH یا TPT، کلاس BaseComment را در نظر گرفته‌ایم. نکته‌ی مهم این است که هر نظری یک درج کننده دارد. ولی اگر فیلد AllowCommentForAnonymous مربوط به مطلب True باشد، این امکان وجود خواهد داشت تا کاربران احراز هویت نشده نیز نظر ارسال کنند. لذا CreatorId در کلاس BaseComment به صورت Nullable در نظر گرفته شده است و یک سری خصوصیت‌ها از قبیل SiteUrl ، CreatorDisplayName ، Email برای کاربران احراز هویت نشده در نظر گرفته شده است. نوع شمارشی CommentStatus نیز برای اعمال مدیریتی در نظر گرفته شده است.
مدل نظرات پست ها
/// <summary>
    /// Represents a blog post's comment
    /// </summary>
    public class BlogComment : BaseComment
    {
        #region Ctor
        /// <summary>
        /// Create One Instance for <see cref="BlogComment"/>
        /// </summary>
        public BlogComment()
        {
            Rating = new Rating();
            CreatedOn = DateTime.Now;
        }
        #endregion

        #region NavigationProperties
        /// <summary>
        /// gets or sets BlogComment's identifier for Replying and impelemention self referencing
        /// </summary>
        public virtual long? ReplyId { get; set; }
        /// <summary>
        /// gets or sets blog's comment for Replying and impelemention self referencing
        /// </summary>
        public virtual BlogComment Reply { get; set; }
        /// <summary>
        /// get or set collection of blog's comment for Replying and impelemention self referencing
        /// </summary>
        public virtual ICollection<BlogComment> Children { get; set; }
        /// <summary>
        /// gets or sets post that this comment sent to it
        /// </summary>
        public virtual BlogPost Post { get; set; }
        /// <summary>
        /// gets or sets post'Id that this comment sent to it
        /// </summary>
        public virtual long PostId { get; set; }
        #endregion
    }
کلاس بالا مشخص کننده‌ی نظرات پست‌های وبلاگ می‌باشند که بیشتر خصوصیت‌های خود را از کلاس پایه‌ی BaseComment به ارث برده است و همچنین ساختار سلسله مراتبی آن نیز  قابل مشاهده است. 
برای سیستم Report یا همان گزارش خطا هم سیستمی شبیه به امتیاز دهی ستاره‌ای در نظر گرفته ایم. برای اینکه مقاله خیلی طولانی شد، بهتر است در مقاله‌ای جدا کار را ادامه داد.

‫ماژول‌ها در ES 6

$
0
0
ماژول‌ها در ES 6

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


روش IIFE Module

الگوی ماژول‌ها، سال‌ها است که در جاوا اسکریپت مورد استفاده‌است:
(function(target){
  var privateDoWork = function(name) {
    return name +" is working";
  };

  var Employee = function(name) {
      this.name = name;
  }

  Employee.prototype = {
     doWork: function() {
       return privateDoWork(this.name);
     }
  }

  target.Employee = Employee;

}(window));
IIFE یا immediately invoked function expression، متدی خود اجرا شونده است. به صورت پیش فرض هر متغیری که داخل IIFE تعریف می‌شود، سطح دید آن محدود است به بدنه‌ی همان ماژول و دیگر دسترسی عمومی ندارد. برای مثال شیء Employee فقط داخل این ماژول قابل دسترسی است. تنها زمانیکه توسط target.Employee، این شیء را به window متصل می‌کنیم، خارج از این ماژول قابل دسترسی می‌شود.
بنابراین با روش IIFE به مزیت‌های یک سیستم ماژول می‌رسیم:
الف) امکان مدیریت کدها را به صورت یک unit و واحد فراهم می‌کند.
ب) همچنین در اینجا امکان کنترل میدان دید متغیرها و متدها نیز میسر است.


روش CommonJS

از سال 2009 استفاده از جاوا اسکریپت به خارج از مرورگرها گسترش یافت؛ برای مثال نوشتن برنامه‌های سمت سرور NodeJS یا MongoDB با جاوا اسکریپت. در یک چنین حالتی برای مدیریت پیچیدگی برنامه‌های گسترده‌ی سمت سرور و پرهیز از متغیرها و اشیاء عمومی، پروژه‌ی CommonJSشکل گرفت. در CommonJS نحوه‌ی تعریف ماژول‌ها بسیار شبیه است به IIFE. با این تفاوت که دیگر خبری از متد خود اجرا شونده وجود ندارد و همچنین بجای target از exports، جهت درمعرض دید قرار دادن اشیاء استفاده می‌کند.
  var privateDoWork = function(name) {
    return name +" is working";
  };

  var Employee = function(name) {
      this.name = name;
  }

  Employee.prototype = {
     doWork: function() {
       return privateDoWork(this.name);
     }
  }

exports.Employee = Employee;
این تعاریف در یک فایل مجزای JS قرار می‌گیرند. سپس برای دسترسی به آن‌ها در فایلی دیگر، از روش ذیل استفاده می‌کنند:
 var Employee = require("./Employee").Employee;
var e1 = new Employee("Vahid");
console.log(e1.doWork());
در متد require، مسیر فایل و ماژول تعریف شده، مشخص می‌شود؛ بدون ذکر پسوند .js فایل.


روش AMD

از CommonJS بیشتر در برنامه‌های جاوا اسکریپتی که خارج از مرورگر اجرا می‌شوند، استفاده می‌شود. برای حالت‌های اجرای برنامه‌ها درون مرورگرها و خصوصا بلاک نشدن ترد نمایش صفحه در حین پردازش ماژول‌ها، روش دیگری به نام AMD API و یا Asynchronous module definition به وجود آمد. پیاده سازی محبوب این API عمومی، توسط کتابخانه‌ای به نام RequireJSانجام شده‌است.
define(function(){

  var privateDoWork = function(name) {
    // ...
  };

  var Employee = function(name) {
    // ...
  }

  return Employee;
});
در اینجا یک ماژول تعریف شده‌ی در یک فایل مجزای جاوا اسکریپتی با define function شروع می‌شود و در نهایت یک return دارد.
تفاوت مهم این روش با روش IIFE این است که در روش IIFE تمام کد باید مهیا بوده و همچنین بلافاصله قابل اجرا باشد. در اینجا تنها زمانیکه نیاز به کار با ماژولی باشد، اطلاعات آن بارگذاری شده و استفاده می‌شود.
برای استفاده‌ی از این ماژول‌ها نیز از همان define استفاده می‌شود و پارامتر اول ارسالی، آرایه‌ای است که ماژول‌های مورد نیاز را تعریف می‌کند (تعریف وابستگی‌ها). برای مثال employee تعریف شده در اینجا سبب بارگذاری فایل employee.js می‌شود و سپس امکانات آن به صورت یک پارامتر، به متدی که به آن نیاز دارد ارسال می‌گردد:
define(["employee"], function(Employee){
      var e = new Employee("Vahid");
});


ماژول‌ها در ES 6

سیستم تعریف ماژول‌ها در ES 6 بسیار شبیه است به روش‌های CommonJS و AMD API. در اینجا یک نمونه از روش تعریف ماژول‌ها را در نگارش جدید جاوا اسکریپت مشاهده می‌کنید:
export class Employee {
  constructor(name) {
    this[s_name] = name;
  }

  get name() {
    return this[s_name];
  }

  doWork() {
    return `${this.name} is working`;
  }
}
در اینجا واژه‌ی کلیدی export سبب در دسترس قرار گرفتن تعریف یک کلاستعریف شده‌ی در این ماژول که در اینجا یک فایل جاوا اسکریپتی است، می‌شود. در یک فایل می‌توان چندین export را داشت؛ اما بهتر است یک export را به ازای هر فایل درنظر گرفت.
پس از این export، اکنون برای استفاده‌ی از آن در یک فایل js دیگر، از واژه‌ی کلیدی import کمک گرفته می‌شود:
 import {Employee} from "./employee";
var e1 = new Employee("Vahid");
console.log(e1.doWork());
در اینجا پس از from، مسیر فایل js، بدون ذکر پسوند آن مشخص می‌شود.

و یا برای ارائه‌ی یک متد خروجی، به نحو ذیل عمل می‌شود:
export function multiply (x, y) {
   return x * y;
};
و اگر یک متغیر و یا متد تعریف شده‌ی در سطح ماژول را بخواهیم عمومی کنیم، باید از {} استفاده شود:
var hello = 'Hello World',
multiply = function (x, y) {
   return x * y;
};
export { hello, multiply };


حالت پیش فرض ماژول‌های ES 6 همان strict mode است

در سیستم ماژول‌های ES 6، حالت strictبه صورت پیش فرض روشن است. برای مثال متغیرها حتما باید تعریف شوند.


امکان تعریف خروجی‌های متفاوت از یک ماژول در ES 6

در همان فایلی که export class Employee فوق را در آن تعریف کرده‌ایم، یک چنین تعریف‌هایی را نیز می‌توان ارائه داد:
export let log = function(employee) {
   console.log(employee.name);
}

export let defaultRaise = 0.03;

export let modelEmployee = new Employee("Vahid");
در اینجا نحوه‌ی export متد log و یا متغیر defaultRaise و همچنین شیء modelEmployee را مشاهده می‌کنید. سپس برای استفاده‌ی از این خروجی‌ها، قسمت import نیز باید به نحو ذیل تغییر کند:
 import {Employee, log, defaultRaise, modelEmployee} from "./employee";
log(modelEmployee);
برای ساده سازی دریافت تمام خروجی‌های یک ماژول ES 6، می‌توان از واژه‌ی کلیدی module استفاده کرد:
 module m from "./employee";
در اینجا متغیر m امکان دسترسی به Employee, log, defaultRaise, modelEmployee را بدون نیاز به ذکر آن‌ها در قسمت import میسر می‌کند. در یک چنین حالتی برای دسترسی به خروجی‌ها، از .m استفاده می‌شود. برای مثال:
 console.log(m.defaultRaise);
و یا
 var e1 = new m.Employee("Vahid");
console.log(e1.doWork());

روش دیگر انجام اینکار، استفاده از * است برای درخواست تمام وابستگی‌های مورد نیاز:
 import * from "./employee";


امکان استفاده از یک ماژول در ماژولی دیگر

برای اینکه از امکانات یک ماژول در ماژولی دیگر استفاده کنیم نیز می‌توان از همان روش تعریف import در ابتدای ماژول استفاده کرد:
 import {Employee} from "./employee";


امکان تعریف ماژول پیش فرض در ES 6

اگر ماژول شما (همان فایل js) تنها دارای یک export است، می‌توانید آن‌را با واژه‌ی کلیدی default مشخص کنید:
  export default class Employee {
به این ترتیب برای استفاده‌ی از این ماژول تنها کافی است بنویسیم:
 import factory from "./employee";
var e1 = new factory("Vahid");
console.log(e1.doWork());
در اینجا factory یک نام متغیر دلخواه است و هر نام دیگری را نیز می‌تواند داشته باشد.

البته باید دقت داشت که یک چنین تعریف‌هایی نیز مجاز است و می‌توان خروجی پیش فرض و همچنین نامداری را نیز با هم ترکیب کرد:
export hello = 'Hello World';
export default function (x, y) {
   return x * y;
};
در این حالت تعریف ذیل به این معنا است که pow2 به متد پیش فرض بدون نام و hello به متغیر hello اشاره می‌کنند:
 import pow2, { hello } from 'modules';


امکان مخفی سازی اطلاعات در ماژول‌های ES 6

یکی از انتظارات از سیستم ماژول، امکان مخفی سازی اطلاعات است. در اینجا تنها کافی است شیء، متد و یا متغیر تعریف شده، با واژه‌ی کلیدی export مزین نشوند:
let privateFunction = function() {

}

 export default class Employee {
در این مثال، متد privateFunction در ماژول employee تعریف شده‌است؛ اما چون دارای واژه‌ی کلیدی export نیست، سطح دسترسی آن خصوصی است.

یک نکته:اگر در کلاس export شده، خواستید تا دسترسی به s_name را محدود کنید، از Symbol هابه نحو ذیل کمک بگیرید:
let s_name = Symbol();

export class Employee {
  constructor(name) {
    this[s_name] = name;
  }

  get name() {
    return this[s_name];
  }

  doWork() {
   return `${this.name} is working`;
  }
}

‫Arrow Functions در ES6

$
0
0
توابع Arrow در خیلی از زبان‌های سطح بالا مثل #C و Java8 وجود دارد. حال این امکان به جاوااسکریپت نیز اضافه شده‌است که syntax ایی مشابه lambda expression در سی شارپ دارد. در این مقاله سعی بر معرفی تابع arrow در جاوا اسکریپت داریم و خواهیم گفت که به منظور خلاصه کردن سینتکس و اشتراک گذاری this نحوی با قلمروی والد خود بکار می‌روند. اجازه دهید تا به هر کدام از آنها به صورت جزیی‌تر بپردازیم.

یک سینتکس جدید برای توابع

Arrow Functions راه میانبری را برای نوشتن توابع بی نام (anonymous functions) در جاوا اسکریپت ارایه می‌کنند و در خیلی از قسمت‌ها با هم یکی هستند ولی با کد نویسی کمتر. به این مثال که در ES5 احتمالا دیده‌اید توجه کنید:
var myFunction = function(arg) {    return arg.toUpperCase(); 
};
حالا آن را می‌توان به سادگی در ES6 نوشت:
var myFunction = (arg) => arg.toUpperCase();
کجا از Arrow Functions استفاده کنیم؟
به طور معمول Arrow Functions‌ها برای پاس دادن یک تابع بی نام به تابعی دیگر استفاده می‌شوند. برای مثال می‌توان به توابع filter و map اشاره کرد. به مثال زیر توجه کنید. 
var digits = [1,2,3,4,5,6];
var even = digits.filter( x => x%2 === 0); // فیلتر بر اساس یک شرط
var evenSquares = even.map( x => x*x ); 
console.log(even, evenSquares);
//[2,4,6] [4,16,36]
نکات مهمی که باید به آنها توجه شود:
  • توابع arrow زمانیکه داخل بدنه‌ی آنها بیش از یک عبارت قرار گیرد لازم است که به طور صریح از کلید واژه return استفاده شود.
  • برای برگشت یک شیء خالی باید از سینتکس زیر استفاده کنیم:
const emptyObject = () => {};
emptyObject(); // ? در این حالت یک باگ به حساب می‌آید

//باید دقت شود که اکولاد‌ها را در بین پرانتز‌ها قرار دهیم تا تابع به درستی کار کند

const emptyObject = () => ({});
emptyObject(); // {}
  • تمامی ویژگی‌هایی را که برای پارمترهاگفته شد، می‌توان برای توابع arrow بکار برد.
function () { return arguments[0]; }
(...args) => args[0]
توابع arrow سازنده نیستند. به این معنا که نمی توان عملکرد new را روی آن‌ها بکار برد. به عبارتی دیگر، نوشتن کد زیر به شما خطا خواهد داد.
let NotGood = () => {};
let wontWork = new NotGood();

this لکسیکال (lexical) یا نحوی

یکی از مشکلات موجود در ES5 مساله this در توابع است. به طور پیش فرض this در یک تابع به محیط فعلی آن تابع اشاره می‌کند. ولی زمانیکه می‌خواهید از this در توابعی که به تابعی دیگر داده شده‌اند استفاده کنیم دو حالت داریم. اگر از strict مد جاوا اسکریپت استفاده کنیم ("use strict;") آنگاه this مقدار undefined خواهد داشت و در غیر این صورت this به محیط global اشاره می‌کند که این یک مشکل در ES5 است! به مثال زیر توجه کنید:
$('.current-time').each(function () {  setInterval(function () {    $(this).text(Date.now());  }, 1000);
});

که برنامه نویسان از راه حل زیر استفاده می‌کنند.
$('.current-time').each(function () {  var self = this;   setInterval(function () {    $(self).text(Date.now());  }, 1000);
});
یا به این صورت:
$('.current-time').each(function () {   setInterval(function () {    $(this).text(Date.now());  }.bind(this), 1000);
});

ولی حال در ES6 به راحتی می‌توان این مشکل را با خود تابع arrow حل کرد؛ به صورت زیر:
$('.current-time').each(function () {  setInterval(() => $(this).text(Date.now()), 1000);
});
و این مورد به دلیل این است که this در توابع arrow یک this نحوی است و به همان ترتیبی که تابع در کد قرار می‌گیرد آن this به محیط تابع فعلی اشاره می‌کند و این تغییر مهمی است که خیلی از دردسر‌ها را کم می‌کند.

‫توسعه سیستم مدیریت محتوای DNTCms - قسمت دوم

$
0
0

در مقاله‌ی قبلتوانستیم یک سری از مدل‌های مربوط به وبلاگ را آماده کنیم. در ادامه به تکمیل آن و همچین آغاز تهیه‌ی مدل‌های مربوط به اخبار و پیغام خصوصی می‌پردازیم.
همکاران این قسمت:
سلمان معروفی

مدل گزارش دهی

    /// <summary>    
    /// Repersents a Report template for every cms section
    /// </summary>
    public class Report
    {
        #region Ctor
        /// <summary>
        /// Create one instance for <see cref="Report"/>
        /// </summary>
        public Report()
        {
            ReportedOn = DateTime.Now;
            Id = SequentialGuidGenerator.NewSequentialGuid();
        }
        #endregion

        #region Properties
        /// <summary>
        /// gets or sets identifier for Report
        /// </summary>
        public virtual Guid Id { get; set; }
        /// <summary>
        /// gets or sets reason of report
        /// </summary>
        public virtual string Reason { get; set; }
        /// <summary>
        /// gets or sets section that is reported
        /// </summary>
        public virtual ReportSection Section { get; set; }
        /// <summary>
        /// gets or sets sectionid that is reported
        /// </summary>
        public virtual long SectionId { get; set; }
        /// <summary>
        /// gets or sets type of report
        /// </summary>
        public virtual ReportType Type{ get; set; }
        /// <summary>
        /// gets or sets report's datetime
        /// </summary>
        public virtual DateTime ReportedOn { get; set; }
        /// <summary>
        /// indicate this report is read by admin
        /// </summary>
        public virtual bool IsRead { get; set; }
        #endregion

        #region NavigationProperties
        /// <summary>
        /// gets or sets id of user that is reporter
        /// </summary>
        public virtual long ReporterId { get; set; }
        /// <summary>
        /// gets or sets id of user that is reporter
        /// </summary>
        public virtual User Reporter { get; set; }
        #endregion
    }

/// <summary>
    /// Represents Report Section
    /// </summary>
   public  enum  ReportSection
    {
        News,
        Poll,
        Announcement,
        ForumTopic,
        BlogComment,
        BlogPost,
        NewsComment,
        PollComment,
        AnnouncementComment,
        ForumPost,
        User,
      ...
    }

/// <summary>
    /// Represents Type of Report
    /// </summary>
    public enum  ReportType
    {
        Spam,
        Abuse,
        Advertising,
       ...
    }

قصد داریم در این سیستم به کاربران خاصی دسترسی گزارش دادن در بخش‌های مختلف را بدهیم. این دسترسی‌ها در بخش تنظیمات سیستم قابل تغییر خواهند بود (برای مثال براساس امتیاز ، براساس تعداد پست و ... ) . این امکان می‌تواند برای مدیریت سیستم مفید باشد.
برای سیستم گزارش دهی به مانند سیستم امتیاز دهی عمل خواهیم کرد. در کلاس Report، خصوصیت ReportSection  از نوع داده‌ی شمارشی می‌باشد که در بالا تعریف آن نیز آماده است و مشخص کننده‌ی بخش‌هایی می‌باشد که لازم است امکان گزارش دهی داشته باشند. خصوصیت Type هم که از نوع شمارشی ReportType می‌باشد، مشخص کننده‌ی نوع گزارشی است که داده شده است. 
علاوه بر نوع گزارش، می‌توان دلیل گزارش را هم ذخیره کرد که برای این منظور خصوصیت Reason در نظر گرفته شده‌است. خصوصیت IsRead هم برای مدیریت این گزارشات در پنل مدیریت در نظر گرفته شده است. اگر در مقاله‌ی قبل دقت کرده باشید، متوجه وجود خصوصیتی به نام ReportsCount در کلاس BaseContent و  BaseComment خواهید شد که برای نشان دادن تعداد گزارش‌هایی است که برای آن مطلب یا نظر داده شده است، استفاده می‌شود.

کلاس پایه فایل‌های ضمیمه

 /// <summary>
    /// Represents a base class for every attachment
    /// </summary>
    public abstract class BaseAttachment
    {
        #region Ctor

        public BaseAttachment()
        {
            Id = SequentialGuidGenerator.NewSequentialGuid();
            AttachedOn = DateTime.Now;
        }
        #endregion

        #region Properties
        /// <summary>
        /// sets or gets identifier for attachment
        /// </summary>
        public virtual Guid Id { get; set; }
        /// <summary>
        /// sets or gets name for attachment
        /// </summary>
        public virtual string FileName { get; set; }
        /// <summary>
        /// sets or gets type of attachment
        /// </summary>
        public virtual string ContentType { get; set; }
        /// <summary>
        /// sets or gets size of attachment
        /// </summary>
        public virtual long Size { get; set; }
        /// <summary>
        /// sets or gets Extention of attachment
        /// </summary>
        public virtual string Extension { get; set; }
        /// <summary>
        /// sets or gets bytes of data
        /// </summary>
        //public byte[] Data { get; set; }
        /// <summary>
        /// sets or gets Creation Date
        /// </summary>
        public virtual DateTime AttachedOn { get; set; }
        /// <summary>
        /// gets or sets counts of download this file
        /// </summary>
        public virtual long DownloadsCount { get; set; }
        /// <summary>
        /// gets or sets datetime that is modified
        /// </summary>
        public virtual DateTime ModifiedOn { get; set; }
        /// <summary>
        /// gets or sets section that this file attached there
        /// </summary>
        public virtual AttachmentSection Section { get; set; }
        /// <summary>
        /// gets or sets information of user agent 
        /// </summary>
        public virtual string Agent { get; set; }
        #endregion

        #region NavigationProperties
        /// <summary>
        /// sets or gets identifier of attachment's owner
        /// </summary>
        public virtual long OwnerId { get; set; }
        /// <summary>
        /// sets or gets identifier of attachment's owner
        /// </summary>
        public virtual User Owner { get; set; }
        #endregion
    }



    public enum  AttachmentSection
    {
        News,
        Announcement,
        ForumTopic,
        Conversation,
        BlogComment,
        NewsComment,
        PollComment,
        AnnouncementComment,
        ForumPost,
        BlogPost,
        Group,
        ...
    }

کلاس بالا اکثر خصوصیات لازم برای مدل Attachment ما را در خود دارد. قصد داریم ازارث بری TPHبرای مدیریت فایل‌های ضمیمه استفاده کنیم. در سیستم بسته‌ی ما، تنها کاربران احراز هویت شده می‌توانند فایل ضمیمه کنند و برای همین منظور OwnerId را که همان ارسال کننده‌ی فایل می‌باشد، به صورت Nullable در نظر نگرفته‌ایم.
یک سری از مشخصات که نیاز به توضیح اضافی ندارند، ولی خصوصیت AttachmentSection که از نوع شمارشی AttachmentSection است، برای دسترسی راحت کاربر به فایل‌های ارسالی خود در پنل کاربری در نظر گرفته شده است. برای بخش‌های (وبلاگ - اخبار - نظرسنجی‌ها - آگهی‌ها - انجمن)  که نیاز به Privacy خاصی نیست و احراز هویت کفایت می‌کند، مدل زیر را در نظر گرفته ایم:

مدل فایل‌های ضمیمه عمومی

 /// <summary>
    /// Repersent the attachment for file
    /// </summary>
    public class Attachment : BaseAttachment
    {
    }
  مدل بالا صرفا برای بخش‌های مذکور کفایت خواهد کرد. در ادامه مقالات، برای بخش‌هایی مانند پیغام خصوصی، گروه‌هایی که کاربران ایجاد می‌کنند، برای انتشار تجربیات خود و هر بخشی که اضافه شود و نیاز به Privacy داشته باشد، نیاز خواهند بود تا مدل Attachment آنها با خود بخش هم در ارتباط باشد و تمام خصوصیت آنها که اکثرا کلید خارجی خواهند بود به صورت Nullable تعریف شوند.
مدل اخبار
 /// <summary>
    /// Represents one news item 
    /// </summary>
    public class NewsItem : BaseContent
    {
        #region Ctor
        /// <summary>
        /// create one instance of <see cref="NewsItem"/>
        /// </summary>
        public NewsItem()
        {
            Rating = new Rating();
            PublishedOn = DateTime.Now;
        }
        #endregion

        #region Properties
        /// <summary>
        /// indicating that this news show on sidebar
        /// </summary>
        public virtual bool ShowOnSideBar { get; set; }
        /// <summary>
        /// indicate this NewsItem is approved by admin if NewsItem.Moderate==true
        /// </summary>
        public virtual bool IsApproved { get; set; }

        #endregion

        #region NavigationProperties

        /// <summary>
        /// gets or sets  newsitem's Reviews
        /// </summary>
        public ICollection<NewsComment> Comments { get; set; }

        #endregion
    }

                  کلاس بالا نشان دهنده‌ی اشتراک‌های ما خواهند بود. این مدل ما هم از کلاس پایه‌ی BaseContent بحث شده در مقاله‌ی قبل، ارث بری کرده و علاوه بر آن دو خصوصیت دیگر تحت عنوان IsApproved برای اعمال مدیریتی در نظر گرفته شده است (اگر در بخش تنظیمات سیستم اخبار، مدیریت تصمیم گرفته باشد تا اخبار جدید به اشتراک گذاشته شده با تأیید مدیریتی منتشر شوند) و خصوصیت ShowOnSideBar هم به عنوان یک تنظیم مدیریتی برای خبر خاصی در نظر گرفته شده که لازم است به صورت sticky در سایدبار نمایش داده شود.
برای اخبار نیز امکان ارسال نظر خواهیم داشت که برای این منظور لیستی از مدل زیر (NewsComment) در مدل بالا تعریف شده است .

مدل نظرات اخبار 

 public class NewsComment : BaseComment
    {
        #region Ctor
        public NewsComment()
        {
            Rating = new Rating();
            CreatedOn = DateTime.Now;

        }
        #endregion

        #region NavigationProperties

        /// <summary>
        /// gets or sets body of blog NewsItem's comment
        /// </summary>
        public virtual long? ReplyId { get; set; }
        /// <summary>
        /// gets or sets body of blog NewsItem's comment
        /// </summary>
        public virtual NewsComment Reply { get; set; }
        /// <summary>
        /// gets or sets body of blog NewsItem's comment
        /// </summary>
        public virtual ICollection<NewsComment> Children { get; set; }
        /// <summary>
        /// gets or sets NewsItem that this comment sent to it
        /// </summary>
        public virtual NewsItem NewsItem { get; set; }
        /// <summary>
        /// gets or sets NewsItem'Id that this comment sent to it
        /// </summary>
        public virtual long NewsItemId { get; set; }
        #endregion
    }

                           مدل بالا نشان دهنده‌ی نظرات داده شده‌ی برای اخبار می‌باشند که از کلاس BaseComment بحث شده در مقاله‌ی قبل ارث بری کرده و ساختار درختی آن نیز مشخص است و همچنین برای اعمال ارتباط یک به چند نیز خصوصیتی تحت عنوان NewsItem  با کلید NewsItemId در این کلاس در نظر گرفته شده است.

مدل‌های پیغام خصوصی
/// <summary>
    /// Indicate one conversation
    /// </summary>
    public class Conversation
    {
        #region Ctor
        /// <summary>
        /// create one instance of <see cref="Conversation"/>
        /// </summary>
        public Conversation()
        {
            Id = SequentialGuidGenerator.NewSequentialGuid();
            SentOn = DateTime.Now;
        }
        #endregion

        #region Properties
        /// <summary>
        /// gets or sets identifier of record
        /// </summary>
        public virtual Guid Id { get; set; }
        /// <summary>
        /// represents this conversaion is seen
        /// </summary>
        public virtual bool IsRead { get; set; }
        /// <summary>
        /// gets or sets subject of this conversation
        /// </summary>
        public virtual string Subject { get; set; }
        /// <summary>
        /// gets or sets Date that this record added
        /// </summary>
        public virtual DateTime SentOn { get; set; }
        /// <summary>
        /// indicate this record deleted by sender
        /// </summary>
        public virtual bool DeletedBySender { get; set; }
        /// <summary>
        /// indicate this record deleted by receiver
        /// </summary>
        public virtual bool DeletedByReceiver { get; set; }
        /// <summary>
        /// gets or sets Messagescount that Unread  by sender of this conversation
        /// </summary>
        public virtual int UnReadSenderMessagesCount { get; set; }
        /// <summary>
        /// gets or sets Messagescount that Unread  by receiver of this conversation
        /// </summary>
        public virtual int UnReadReceiverMessagesCount { get; set; }
        /// <summary>
        /// gets or sets Messagescount of this conversation for increase performance
        /// </summary>
        public virtual int MessagesCount { get; set; }
        #endregion

        #region NavigationProperties
        /// <summary>
        /// gets or sets if of  user that start this conversation
        /// </summary>
        public virtual long SenderId { get; set; }
        /// <summary>
        /// gets or sets user that start this conversation
        /// </summary>
        public virtual User Sender { get; set; }
        /// <summary>
        /// gets or sets id of  user that is recipient
        /// </summary>
        public virtual long ReceiverId { get; set; }
        /// <summary>
        /// gets or sets   user that is recipient
        /// </summary>
        public virtual User Receiver { get; set; }
        /// <summary>
        /// get or set Messages of this conversation
        /// </summary>
        public virtual ICollection<ConversationReply> Messages { get; set; }
        /// <summary>
        /// get or set Attachments that attached in this conversation
        /// </summary>
        public virtual ICollection<ConversationAttachment> Attachments { get; set; }
        #endregion

مدل بالا نشان دهنده‌ی گفتگوی بین دو کاربر می‌باشد. هر گفتگو امکان دارد با موضوع خاصی ایجاد شود و مسلما یک کاربر به‌عنوان دریافت کننده و کاربر دیگری بعنوان ارسال کننده خواهد بود. برای این منظور خصوصیات Receiver و Sender که از نوع User هستند را در این کلاس در نظر گرفته‌ایم.
خصوصیات DeletedBySender و DeletedByReceiver هم برای این در نظر گفته شده‌اند که اگر یک طرف این گفتگو خواهان حذف آن باشد، برای آن کاربر حذف نرم انجام دهیم و فعلا برای کاربر مقابل قابل دسترسی باشد.
UnReadSenderMessagesCount و UnReadReceiverMessagesCount هم برای بالا بردن کارآیی سیستم در نظر گفته شده‌اند و در واقع تعداد پیغام‌های خوانده نشده در یک گفتگو به صورت متمایز برای هر دو طرف، ذخیره می‌شود. هر گفتگو شامل یکسری پیغام رد و بدل شده خواهد بود که بدین منظور لیستی از ConversationReply‌ها را در مدل بالا تعریف کرده‌ایم.
در هر گفتگو یکسری فایل هم ممکن است ضمیمه شود ، برای این منظور هم یک لیستی از کلاس ConversationAttachment در مدل گفتگو تعریف شده است که در ادامه پیاده سازی کلاس ConversationAttachment را هم خواهیم دید.   
مدل  ConversationReply به شکل زیر می‌باشد:

  /// <summary>
    /// Represents One Reply to Conversation
    /// </summary>
    public class ConversationReply
    {
        #region Ctor
        /// <summary>
        /// create one instance of <see cref="ConversationReply"/>
        /// </summary>
        public ConversationReply()
        {
            Id = SequentialGuidGenerator.NewSequentialGuid();
            SentOn = DateTime.Now;
        }
        #endregion

        #region Properties
        /// <summary>
        /// gets or sets identifier of record
        /// </summary>
        public virtual Guid Id { get; set; }
        /// <summary>
        /// represents this conversaionReply is seen
        /// </summary>
        public virtual bool IsRead { get; set; }
        /// <summary>
        /// gets or sets body of this conversationReply
        /// </summary>
        public virtual string Body { get; set; }
        /// <summary>
        /// gets or sets Date that this record added
        /// </summary>
        public virtual DateTime SentOn { get; set; }
        #endregion

        #region NavigationProperties
        /// <summary>
        /// gets or sets  Parent's Id Of this ConversationReply
        /// </summary>
        public virtual Guid? ParentId { get; set; }
        /// <summary>
        /// gets or sets Parent Of this ConversationReply
        /// </summary>
        public virtual ConversationReply Parent { get; set; }
        /// <summary>
        /// get or set Children Of this ConversationReply
        /// </summary>
        public virtual ICollection<ConversationReply> Children { get; set; }
        /// <summary>
        /// gets or sets if of  user that start this conversationReply
        /// </summary>
        public virtual long SenderId { get; set; }
        /// <summary>
        /// gets or sets user that start this conversationReply
        /// </summary>
        public virtual User Sender { get; set; }
        /// <summary>
        /// gets or sets Conversation that this message sent in it 
        /// </summary>
        public virtual Conversation Conversation{ get; set; }
        /// <summary>
        /// gets or sets Id of Conversation that this message sent in it 
        /// </summary>
        public virtual Guid? ConversationId { get; set; }
        #endregion
    }

مدل بالا نشان دهنده‌ی پیغام‌های داده شده در یک گفتگو با موضوعی خاص می‌باشد. ساختار درختی آن هم برای ایجاد امکان جواب دهی برای پیغام‌ها در نظر گرفته شده است (الزامی نیست). هر پیغام در یک گفتگو ارسال شده و یک ارسال کننده نیز دارد که برای این منظور به ترتیب دو خصوصیت Conversation از نوع کلاس Conversation و Sender از نوع User در نظر گرفته‌ایم.  
با توجه به وجود Privacy در گفتگو نیاز است تا مدل فایل ضمیمه بخش گفتگو‌ها به شکل زیر باشد:

/// <summary>
    /// Represents the attachment That attached in Conversation
    /// </summary>
    public class ConversationAttachment : BaseAttachment
    {
        #region NavigationProperties

        public virtual Conversation Conversation { get; set; }
        public virtual Guid? ConversationId { get; set; }
        #endregion
    }

همانطور که کمی بالاتر بحث شد، قصد اعمال ارث بری TPHرا برای مدیریت فایل‌های ضمیمه داریم. برای این منظور مدل بالا نیز از کلاس BaseAttachment ارث بری کرده و دو خصوصیت اضافه هم برای اعمال ارتباط یک به چند با گفتگو خواهد داشت. توجه کنید که ConversationId به صورت Nullable تعریف شده‌است.

‫برنامه نویسی Async با ES 6

$
0
0
جاوا اسکریپت به صورت single-thread عمل می‌کند. به این معنا که دو اسکریپت نمی‌توانند به صورت همزمان اجرا شوند و باید یکی پس از دیگری اجرا شوند. ساده‌ترین شکل برنامه‌نویسی غیرهمزمان در جاوا اسکریپت استفاده از callback می‌باشد. به عنوان مثال در سناریوی زیر Caller یکسری عملیات غیرهمزمان را مانند یک فراخوانی XHR و یا یک تایمر، انجام می‌دهد. زمانیکه Caller عملیات غیرهمزمانی را آغاز کرد، یک callback را به آن ارسال خواهد کرد و بعد از مطمئن شدن از موفق بودن عملیات، callback را فراخوانی می‌کند. بعد از پایان عملیات، callback درون call stack قرار خواهد گرفت و هر وقت که بقیه‌ی عملیات به اتمام رسید، اجرا خواهد شد.

این روش چندین مشکل دارد:
  • تنها caller از پایان یافتن عملیات غیرهمزمان مطلع خواهد شد.
  • هندل کردن خطا و همچنین مدیریت چندین عملیات asynchronous به صورت همزمان خیلی سخت خواهد بود.
در اینحالت callback باید به چندین کار رسیدگی کند:
  • پردازش نتایج فراخوانی‌های async
  • اجرای دیگر عملیات براساس پاسخ
کد زیر را در نظر بگیرید:
function getCompanyFromOrderId(orderId) {
    getOrder(orderId, function(order) {
        getUser(order.userId, function(user) {
            getCompany(user.companyId, function(company) {
                // do something with company
            });
        });
    });
}
در کد فوق به اطلاعات یک شرکت براساس شماره سفارش آن دسترسی خواهیم داشت. پس از دریافت سفارش، اطلاعات کاربر را دریافت خواهیم کرد. پس از پایان آن، می‌توانیم به اطلاعات شرکت دسترسی داشته باشیم. این سبک نوشتن کدها به صورت تودرتو خوانایی/ نگهداری کد را کاهش خواهد داد. همانطور که مشاهده می‌کنید callback نه تنها پاسخ بلکه یک callback دیگر را هربار به تابع بعدی ارسال می‌کند. در این‌حالت اگر بخواهیم استثناء‌ها را نیز مدیریت کنیم کدها به مراتب پیچیده‌تر خواهند شد:
function getCompanyFromOrderId(orderId) {
    try {
        getOrder(orderId, function (order) {
            try {
                getUser(order.userId, function (user) {
                    try {
                        getCompany(user.companyId, function (company) {
                            try {
                                // do something with company
                            } catch (ex) {
                                // handle exception
                            }
                        });
                    } catch (ex) {
                        // handle exception
                    }
                });
            } catch (ex) {
                // handle exception
            }
        });
    } catch (ex) {
        // handle exception
    }
}
ممکن است بگوئید که نیازی به این همه try/catch در کد فوق نیست. اما هر callback ایی که به صورت مجزا وارد call stack می‌شود، هر خطایی که صادر شود توسط شروع کننده‌ی اصلی درخواست async به دام انداخته نخواهد شد و در نتیجه نوشتن این همه try/catch ضروری است. لازم به ذکر است، به این نوع نوشتن callback به صورت تودرتو callback hell و یا pyramid of doom نیز گفته می‌شود.

راه‌حل: استفاده از Promises
Promises همیشه به عنوان یک راه‌حل برای callback hell شناخته شده هستند. Promises در واقع اشیایی هستند که این اطمینان را به شما خواهند داد تا بعد از پایان یک عملیات غیرهمزمان، پاسخ را صرفنظر از اینکه عملیات fail و یا success شده باشد، در اختیارتان قرار خواهند داد. یک Promise از دو قسمت تشکیل شده است:
  • Control
  • Promise
قسمت اول یا Control در بیشتر کتابخانه‌ها با نام Deferred نیز از آن نامبرده می‌شود و در واقع یک شیء مستقل است. در بعضی از پیاد‌ه‌سازی‌ها این شیء در واقع خودش یک callback است. قسمت دوم نیز خود Promise است. این شیء می‌تواند دیگر قسمت‌های کد را از پایان یافتن عملیات غیرهمزمان مطلع سازد.
یک Promise می‌تواند یکی از حالت‌های زیر را داشته باشد:
  • pending: یعنی وضعیت اولیه، هنوز به پایان نرسیده است.
  • fulfilled: یعنی عملیات با موفقیت پایان پذیرفته است.
  • rejected: یعنی عملیات با شکست مواجه شده است.
با ایجاد یک Promise، وضعیت آن در اولین مرحله و pending خواهد بود. سپس تبدیل به یکی از وضعیت‌های fulfilled و یا rejected خواهد شد.

اکنون اگر بخواهیم کد قبلی را با استفاده از Promises پیاده‌سازی کنیم به این چنین نتیجه‌ایی خواهیم رسید:
function getCompanyFromOrderId(orderId) {
    getOrder(orderId).then(function(order) {
        return getUser(order.orderId);
    }).then(function(user) {
        return getCompany(user.companyId);
    }).then(function(company) {
        // do something with company
    }).then(undefined, function(error) {
        // handle error
    })
}
در اینجا به جای استفاده از callback در تابع getCompanyFromOrderId یک promise را برمی‌گردانیم. وقتی تابع getOrder با موفقیت کارش را انجام داد، callback بعدی اجرا خواهد شد که در واقع تابع getUser را فراخوانی می‌کند. یک عملیات غیرهمزمان دیگر نیز یک promise را برمی‌گرداند. بعد از آن calback دیگری فراخوانی خواهد شد و به همین ترتیب... در نهایت نیز توسط یک then دیگر می‌توانیم خطاها را هندل کنیم. بنابراین در این‌حالت کد فوق خواناتر و نگهداری آن به مراتب ساده‌تر از حالت قبل می‌باشد.
promises خیلی وقت است که در قالب کتابخانه‌های third-party مانند QwhenWinJSRSVP.js در اختیار برنامه‌نویسان جاوا اسکریپت قرار دارد. در نتیجه کد فوق به صورت جنریک است؛ به این معنا که با هر کدام از کتابخانه‌های عنوان شده سازگاری دارد.

نحوه‌ی ایجاد Promise
ساختار اولیه برای ایجاد یک promise به اینصورت است:
var promise = new Promise(function(resolve, reject) {
  // انجام یکسری عملیات به عنوان مثال دریافت اطلاعات از سرور و...

  if (/* اگر کدهای فوق با موفقیت انجام شدند */) {
    resolve("عملیات با موفقیت انجام پذیرفت");
  }
  else {
    reject(Error("خطایی رخ داده است"));
  }
});
همانطور که مشاهده می‌کنید سازنده‌ی promise یک callback را از ورودی دریافت خواهد کرد. این callback نیز از ورودی دو پارامتر را دریافت میکند: resolve و reject. درون callback می‌توانیم کدهایمان را بنویسیم. بعد از اینکه عملیات با موفقیت انجام گرفت resolve را فراخوانی خواهیم کرد. در غیراینصورت reject را فراخوانی می‌کنیم. همچنین به جای استفاده از throw درون reject از شیء Error استفاده کرده‌ایم. زیرا بعداً برای دیباگ خطا می‌توانیم به اطلاعات کامل stack trace دسترسی داشته باشیم.
در ادامه نحوه‌ی استفاده از promise فوق را مشاهده می‌کنید:
promise.then(function(result) {
  console.log(result); // "عملیات با موفقیت انجام پذیرفت "
}, function(err) {
  console.log(err); // Error: "خطایی رخ داده است"
});
در اینجا تابع then دو آرگومان را از ورودی دریافت خواهد کرد؛ یکی برای حالت success و  دیگری برای حالت failure. لازم به ذکر است، هر دوی این آرگومان‌ها اختیاری هستند.
به عنوان یک مثال عملی می‌توانیم متد get جی‌کوئری را به این صورت درون یک Promise قرار دهیم:
function get(url){
    return new Promise(function(resolve, reject) {
       $.get(url, function(data) {
           resolve(data);
       }) 
       .fail(function(){
          reject(); 
       });
    });
}
به اینصورت می‌توانیم از Promise فوق استفاده کنیم:
get('users.all').then(function(users){
    myController.users = users;
}, function(){
   delete myController.users; 
});
اگر هم مایل بودید می‌توانید به جای ارائه‌ی آرگومان دوم درون callback برای به دام انداختن خطاها از catch استفاده کنید:
get('users.all').then(function(users){
    myController.users = users;
})
.catch(function(){
    delete myController.users;
});

در شرایطی ممکن است بخواهیم بعد از اینکه تمامی Promise هایمان کارشان به اتمام رسید، یکسری عملیات دیگر را انجام دهیم:
var usersPromise = get('users.all');
var postsPromise = get('posts.everyone');

Promise.all([usersPromise, postsPromise])
.then(function(result){
    myController.users = result[0];
    myController.posts = result[1];
}, function(){
   delete myController.users;
   delete myController.posts; 
});
لازم به ذکر است اگر حتی یکی از Promiseها reject شود Promise اصلی نیز reject خواهد شد.

اگر خروجی then به صورت رشته‌ایی باشد چه اتفاقی خواهد افتاد؟
در حالت کلی خروجی هر then. به then بعدی پاس داده خواهد شد. به عنوان مثال در کد زیر نتایج به صورت رشته‌ایی برگردانده خواهند شد و می‌توانیم آن‌ها را به سادگی توسط JSON.parse به then بعدی ارسال کنیم:
get('users.all').then(function(usersString){
    return JSON.parse(usersString);
}).then(function(users){
   myController.users = users; 
});
و یا به صورت خلاصه‌تر می‌توانیم به اینصورت اینکار را انجام دهیم:
get('users.all').then(JSON.parse).then(function(users){
   myController.users = users; 
});
برای مشاهده‌ی دیگر متدهای استاتیک Promise می‌توانید به اینجا مراجعه نمائید.

‫استفاده از EF7 با پایگاه داده SQLite تحت NET Core. به کمک Visual Studio Code

$
0
0
در این مقاله سعی داریم مراحل نوشتن و اجرای یک برنامه‌ی ساده را تحت NET Core. و با بهره گیری از دیتابیس SQLite و EF7، دنبال کنیم. همچنین از آنجایی‌که NET Core. به صورت چندسکویی طراحی شده‌است و تحت لینوکس و مکینتاش هم قابل اجراست، در نتیجه مناسب دیدم که ابزار نوشتن این پروژه‌ی ساده نیز قابلیت چندسکویی داشته و تحت لینوکس و مکینتاش نیز قابل اجرا باشد. در نتیجه به جای Visual Studio در این مقاله از Visual Studio Code استفاده شده است.

ابزارهای پیش نیاز:
  1. Visual Studio Code
  2. .NET Core
در اولین قدم، برنامه‌ی متن باز Visual Studio Code را از اینجادانلود و نصب کنید. برنامه‌ی Visual Studio Code که در ادامه‌ی فعالیت‌های جدید متن باز مایکروسافت به بازار عرضه شده است، سریع، سبک و کاملا قابل توسعه و سفارشی سازی است و از اکثر زبان‌های معروف پشتیبانی می‌کند.
در قدم بعدی، شما باید NET Core. را از اینجا (64 بیتی) دانلود و نصب کنید.

 .NET Core چیست؟
NET Core. در واقع پیاده سازی بخشی از NET. اصلی است که به صورت متن باز در حال توسعه می‌باشد و بر روی لینوکس و مکینتاش هم قابل اجراست. موتور اجرای دات نت کامل CLR نام دارد و NET Core. نیز دارای موتور اجرایی CoreCLRاست و شامل فریمورک CoreFXمی‌باشد.

در حال حاضر شما می‌توانید با استفاده از NET Core. برنامه‌های کنسولی و تحت وب با ASP.NET 5 بنویسید و احتمالا در آینده می‌توان امیدوار بود که از ساختارهای پیچیده‌تری مثل WPF نیز پشتیبانی کند.

پس از آنکه NET Core. را دانلود و نصب کردید، جهت شروع پروژه، یک پوشه را در یکی از درایوها ساخته (در این مثال E:\Projects\EF7-SQLite-NETCore) و Command prompt را در آنجا باز کنید. سپس دستورات زیر را به ترتیب اجرا کنید:

dotnet new
dotnet restore
dotnet run

دستور dotnet newیک پروژه‌ی ساده‌ی Hello World را در پوشه‌ی جاری ایجاد می‌کند که حاوی فایل‌های زیر است:
  • NuGet.Config (این فایل، تنظیمات مربوط به نیوگت را جهت کشف و دریافت وابستگی‌های پروژه، شامل می‌شود)
  •  Program.cs (این فایل سی شارپ حاوی کد برنامه است)
  • project.json (این فایل حاوی اطلاعات پلتفرم هدف و لیست وابستگی‌های پروژه است)

دستور dotnet restoreبر اساس لیست وابستگی‌ها و پلتفرم هدف، وابستگی‌های لازم را از مخزن نیوگت دریافت می‌کند. (در صورتی که در هنگام اجرای این دستور با خطای NullReferenceException مواجه شدید از دستورdnu restoreاستفاده کنید. این خطادر گیت هاب در حال بررسی است)

دستور dotnet runهم سورس برنامه را کامپایل و اجرا می‌کند. در صورتی که پیام Hello World را مشاهده کردید، یعنی برنامه‌ی شما تحت NET Core. با موفقیت اجرا شده است.

توسعه‌ی پروژه با Visual Studio Code

در ادامه، قصد داریم پروژه‌ی HelloWorld را تحت Visual Studio Code باز کرده و تغییرات بعدی را در آنجا اعمال کنیم. پس از باز کردن Visual Studio Code از منوی File گزینه‌ی Open Folder را انتخاب کنید و پوشه‌ی حاوی پروژه (EF7-SQLite-NETCore) را انتخاب کنید. اکنون پروژه‌ی شما تحت VS Code باز شده و قابل ویرایش است.

سپس از لیست فایل‌های پروژه، فایل project.json را باز کرده و در بخش "dependencies" یک ردیف را برای EntityFramework.SQLite به صورت زیر اضافه کنید. به محض افزودن این خط در project.json و ذخیره‌ی آن، در صورتیکه قبلا این وابستگی دریافت نشده باشد، Visual Studio Code با نمایش یک هشدار در بالای برنامه به شما امکان دریافت اتوماتیک این وابستگی را می‌دهد. در نتیجه کافیست دکمه‌ی Restore را زده و منتظر شوید تا وابستگی EntityFramework.SQLite از مخزن ناگت دانلود و برای پروژه‌ی شما تنظیم شود.

"EntityFramework.SQLite": "7.0.0-rc1-final"

دریافت اتوماتیک وابستگی‌ها توسط Visual Studio Code


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

اکنون همه‌ی موارد، جهت توسعه‌ی پروژه آماده است. ماوس خود را بر روی ریشه‌ی پروژه در VS Code قرار داده و New Folder را انتخاب کنید و نام Models را برای آن تایپ کنید. این پوشه قرار است مدل کلاس‌های پروژه را شامل شود. در اینجا ما یک مدل به نام Book داریم و نام کانتکست اصلی پروژه را هم LibraryContext گذاشته‌ایم.

بر روی پوشه‌ی Models راست کلیک کرده و گزینه‌ی New File را انتخاب کنید. سپس فایل‌های Book.cs و LibraryContext.cs را ایجاد کرده و کدهای زیر را برای مدل و کانتکست، در درون این دو فایل قرار دهید.

Book.cs

namespace Models
{
    public class Book
    {
        public int ID { get; set; }
        public string Title { get; set; }
        public string Author{get;set;}
        public int PublishYear { get; set; }
    }
}
LibraryContext.cs
using Microsoft.Data.Entity;
using Microsoft.Data.Sqlite;

namespace Models
{
    public class LibraryContext : DbContext
    {
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            var connectionStringBuilder = new SqliteConnectionStringBuilder { DataSource = "test.db" };
            var connectionString = connectionStringBuilder.ToString();
            var connection = new SqliteConnection(connectionString);
            optionsBuilder.UseSqlite(connection);
        }
        public DbSet<Book> Books { get; set; }
    }
}
در فایل Book.cs یک مدل ساده به نام Book داریم که حاوی اطلاعات یک کتاب است. فایل LibraryContext.cs نیز حاوی کلاس LibraryContext است که یک مجموعه از کتاب‌ها را با نام Books نگهداری می‌کند. در متد OnConfiguring تنظیمات لازم را جهت استفاده از دیتابیس SQLite با نام test.db، قرار داده‌ایم که البته این کد را در پروژه‌های کامل می‌توان در خارج از LibraryContext قرار داد تا بتوان مسیر ذخیره سازی دیتابیس را کنترل و قابل تنظیم کرد.

در قدم آخر هم کافیست که فایل Program.cs را تغییر دهید و مقادیری را در دیتابیس ذخیره و بازخوانی کنید.
Program.cs
using System;
using Models;

namespace ConsoleApplication
{
    public class Program
    {
        public static void Main(string[] args)
        {
            Console.WriteLine("EF7 + Sqlite with the taste of .NET Core");

            try
            {
                using (var context = new LibraryContext())
                {
                    context.Database.EnsureCreated();

                    var book1 = new Book()
                    {
                        Title = "Adaptive Code via C#: Agile coding with design patterns and SOLID principles ",
                        Author = "Gary McLean Hall",
                        PublishYear = 2014
                    };

                    var book2 = new Book()
                    {
                        Title = "CLR via C# (4th Edition)",
                        Author = "Jefrey Ritcher",
                        PublishYear = 2012
                    };

                    context.Books.Add(book1);
                    context.Books.Add(book2);

                    context.SaveChanges();

                    ReadData(context);
                }

                Console.WriteLine("Press any key to exit ...");
                Console.ReadKey();
            }
            catch (Exception ex)
            {
                Console.WriteLine($"An exception occured: {ex.Message}\n{ex.StackTrace}");
            }
        }

        private static void ReadData(LibraryContext context)
        {
            Console.WriteLine("Books in database:");
            foreach (var b in context.Books)
            {
                Console.WriteLine($"Book {b.ID}");
                Console.WriteLine($"\tName: {b.Title}");
                Console.WriteLine($"\tAuthor: {b.Author}");
                Console.WriteLine($"\tPublish Year: {b.PublishYear}");
                Console.WriteLine();
            }
        }
    }
}
در فایل Program.cs جهت تست پروژه، از روی کلاس Book دو نمونه ساخته و آن‌ها را به دیتابیس افزوده و ذخیره می‌کنیم و در انتها، اطلاعات تمامی کتاب‌های موجود در دیتابیس را با جزییات نمایش می‌دهیم.

جهت اجرای برنامه کافیست Command prompt را در آدرس پروژه باز کرده و دستور dotnet runرا اجرا کنید. پروژه‌ی شما کامپایل و اجرا می‌شود و خروجی مشابه زیر را مشاهده خواهید کرد. اگر برنامه را مجددا اجرا کنید، به جای دو کتاب اطلاعات چهار کتاب نمایش داده خواهد شد؛ چرا که در هر مرحله اطلاعات دو کتاب در دیتابیس درج می‌شود.

.NET Core + EF7 + SQLite

اگر به پوشه‌ی bin که در پوشه‌ی پروژه ایجاد شده است، نگاهی بیندازید، خبری از فایل باینری نیست. چرا که در لحظه‌، تولید و اجرا شده است. جهت build کردن پروژه و تولید فایل باینری کافیست دستور dotnet buildرا اجرا کنید، تا فایل باینری در پوشه‌ی bin ایجاد شود.

جهت انتشار برنامه می‌توانید دستور dotnet publish را اجرا کنید. این دستور نه تنها برنامه، که تمام وابستگی‌های مورد نیاز آن را برای اجرای در یک پلتفرم خاص تولید می‌کند. برای مثال بعد از اجرای این دستور یک پوشه‌ی win7-x64 حاوی 211 فایل در مجموع تولید شده است که تمامی وابستگی‌های این پروژه را شامل می‌شود.

Publishing .NET Core app

در واقع این پوشه تمام وابستگی‌های مورد نیاز پروژه را همراه خود دارد و در نتیجه جهت اجرای این برنامه برخلاف برنامه‌های معمولی دات نت، دیگر نیازی به نصب هیچ وابستگی مجزایی نیست و حتی پروژه‌های نوشته شده تحت NET Core. را می‌توانید در سیستم‌های عامل‌های دیگری مثل لینوکس و مکینتاش و یا  Windows IoT بر روی سخت افزار Raspberry Pi 2 هم اجرا کنید.

جهت مطالعه‌ی بیشتر:

‫توسعه سیستم مدیریت محتوای DNTCms - قسمت سوم

$
0
0

در این قسمت به پیاده سازی و توضیح مدل‌های انجمن خواهیم پرداخت. قبل از شروع پیشنهاد می‌کنم مقالات قبلیرا مطالعه کنید.
همکاران این قسمت:
سلمان معروفی 
سید مجتبی حسینی 
پیشنیاز این قسمت:
مقالات SQL Antipattern 

بنده سعی کردم چندین پروژه‌ی سورس باز را هم بررسی کنم و در نهایت کاملترین و بهترین روش را پیاده سازی کنیم. NForum ، MyBB ، MVCForum، بخش CMS مربوط به SmartStore و ساختار دیتابیس StackOverFlow ازجمله‌ی آنها هستند.

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

مدل انجمن
    /// <summary>
    /// Represents the Forum
    /// </summary>
    public class Forum
    {       
        #region Properties
        /// <summary>
        /// gets or sets Id that Identify Forum
        /// </summary>
        public virtual  long Id { get; set; }
        /// <summary>
        /// gets or sets Forum's title
        /// </summary>
        public virtual  string Title { get; set; }
        /// <summary>
        /// gets or sets Description of forum
        /// </summary>
        public virtual  string Description { get; set; }
        /// <summary>
        /// gets or sets value indicating Custom Slug
        /// </summary>
        public virtual string SlugUrl { get; set; }
        /// <summary>
        /// gets or sets order for display forum
        /// </summary>
        public virtual  long DisplayOrder { get; set; }
        /// <summary>
        /// Indicating This Forum is Active or Not
        /// </summary>
        public virtual  bool IsActive { get; set; }
        /// <summary>
        /// Indicating This Forum is Close or Not
        /// </summary>
        public virtual  bool IsClose { get; set; }
        /// <summary>
        /// Indicating This Forum is Private or Not
        /// </summary>
        public virtual bool IsPrivate { get; set; }
        /// <summary>
        /// sets or gets password for login to Private forums
        /// </summary>
        public virtual string PasswordHash { get; set; }
        /// <summary>
        /// sets or gets depth of forum in tree structure of forums
        /// </summary>
        public virtual int Depth { get; set; }
        /// <summary>
        /// sets or gets Count of  posts That they are Approved
        /// </summary>
        public virtual long ApprovedPostsCount { get; set; }
        /// <summary>
        /// sets or gets Count of  topics That they are Approved
        /// </summary>
        public virtual long ApprovedTopicsCount { get; set; }
        /// <summary>
        /// Gets or sets the id of last topic
        /// </summary>
        public virtual long? LastTopicId { get; set; }
        /// <summary>
        /// gets or sets date of creation of last topic
        /// </summary>
        public virtual DateTime? LastTopicCreatedOn { get; set; }
        /// <summary>
        /// gets or sets title of last topic
        /// </summary>
        public virtual string LastTopicTitle { get; set; }
        /// <summary>
        /// gets or sets creator of last topic
        /// </summary>
        public virtual string LastTopicCreator { get; set; }
        /// <summary>
        /// gets or sets id of creator that create last topic
        /// </summary>
        public virtual long? LastTopicCreatorId { get; set; }
        /// <summary>
        /// Indicate in this Forum Moderate Topics Before Display 
        /// </summary>
        public virtual bool ModerateTopics { get; set; }
        /// <summary>
        /// Indicate in this Forum Moderate Posts Before Dipslay
        /// </summary>
        public virtual bool ModeratePosts { get; set; }
        /// <summary>
        /// gets or sets Count of posts that they are UnApproved
        /// </summary>
        public virtual long UnApprovedPostsCount { get; set; }
        /// <summary>
        /// gets or sets Count of topics that they are UnApproved
        /// </summary>
        public virtual long UnApprovedTopicsCount { get; set; }
        /// <summary>
        /// gets or sets Rowversion
        /// </summary>
        public virtual  byte[] RowVersion { get; set; }
        /// <summary>
        /// gets or sets icon name with size 200*200 px for snippet 
        /// </summary>
        public virtual string SocialSnippetIconName { get; set; }
        /// <summary>
        /// gets or sets title for snippet
        /// </summary>
        public virtual string SocialSnippetTitle { get; set; }
        /// <summary>
        /// gets or sets description for snippet
        /// </summary>
        public virtual string SocialSnippetDescription { get; set; }
        /// <summary>
        /// gets or sets path for tree structure antipattern (1/3/4/23)
        /// </summary>
        public virtual string Path { get; set; }
        /// <summary>
        /// Indicate this forum inherit moderators from parent forum
        /// </summary>
        public virtual bool IsModeratorsInherited { get; set; }
        #endregion

        #region NavigationProperties
        /// <summary>
        /// sets or gets identifier forum's parent
        /// </summary>
        public virtual  long? ParentId { get; set; }
        /// <summary>
        /// sets or gets forum's parent
        /// </summary>
        public virtual Forum Parent { get; set; }
        /// <summary>
        /// sets or gets sub forums of forum
        /// </summary>
        public virtual  ICollection<Forum> Children  { get; set; }
        /// <summary>
        /// set or get topics of forum
        /// </summary>
        public virtual ICollection<ForumTopic> Topics  { get; set; }
        /// <summary>
        /// get or set moderators of this forum
        /// </summary>
        public virtual ICollection<ForumModerator> Moderators  { get; set; }
        /// <summary>
        /// get or set Subscriptions List 
        /// </summary>
        public virtual ICollection<ForumSubscription> Subscriptions  { get; set; }
        /// <summary>
        /// get or set Announcements Collection of this Forum
        /// </summary>
        public virtual ICollection<ForumAnnouncement> Announcements  { get; set; }
        #endregion
    }
مدل بالا نشان دهنده‌ی ساختار انجمن‌های ما می‌باشد. خصوصیت هایی که نیاز به توضیح دارند به شکل زیر می‌باشند:
  1. IsActive : مشخص کننده‌ی این است که در این انجمن امکان ارسال تاپیک و پست وجود دارد و در صورت false بودن این خصوصیت، بر تمام زیر انجمن‌ها هم اعمال خواهد شد و برای زمانی مفید است که میخواهیم برای مدتی به هر دلیل خاصی امکان ارسال تاپیک و پست را برای انجمن خاصی، ندهیم. 
  2. IsColsed : خصوصت اولی که مطرح شد اگر مقدار false بگیرد، همچنان کاربران می‌توانند تایپک‌ها و پست‌های قبلی را مشاهده و مطالعه کنند. ولی با مقدار دهی این خصوصیت با مقدار false، امکان کلیه‌ی فعالیت‌ها و مشاهده‌ای را از محتوای این انجمن و زیر انجمن‌های آن نخواهیم داشت.
  3. IsPrivate : برای مواقعی که لازم است برای انجمن خاصی کلمه‌ی عبور در نظر بگیریم تا افراد خاص که کلمه‌ی عبور آن را دارند بتوانند در آن انجمن فعالیت کنند، در نظر گرفته شده است.
  4. ApprovedPostsCount , UnApprovedPostsCount,ApprovedTopicsCount,UnApprovedTopicsCount : برای بالا بردن کارآیی سیستم به مانند مدل‌های قبل در نظر گرفته شده‌اند.
  5. LastTopicId, LastTopicTitle , LastTopicCreator , LastTopicCreatorId , LastTopicCreatedOn: همچنین برای افزایش کارآیی سیستم و نمایش به عنوان قسمتی از مشخصات قابل مشاهده از هر انجمن، در نظر گرفته شده‌اند.
  6. Depth : برای نشان دادن عمق گره در درخت استفاده می‌شود که هنگام درج انجمن، این مورد از نتیجه‌ی جمع عمق پدر انتخاب شده و یک، به دست خواهد آمد. این مورد هنگام واکشی برای مثال 4 سطح اول برای نمایش آنها در صفحه‌ی اول انجمن به صورت سلسله مراتبی خیلی مفید خواهد بود.
  7. Path : برای استفاده از SQL Antipattern شمارش مسیر در نظر گرفته شده است. این مورد جزء Best Practice‌‌ها می‌تواند باشد. چون هم با استفاده از ساختار خود ارجاع، درخت خود را داریم و با این Antipattern کوئری‌های مربوط به درخت خیلی راحت خواهد بود.
  8. IsModeratorsInherited : اگر لازم است مدیران انجمن، پدر را به عنوان مدیر خود قبول کنند، این خصوصیت مقدار true خواهد گرفت.
ساختار درختی آن هم قابل مشاهده بوده  و نیاز به توضیح خاضی ندارد. در هر انجمن ما، یکسری تاپیک مطرح خواهد شد و برای این منظور لیستی از ForumTopic را در این کلاس معرفی کرده‌ایم. هر انجمنی می‌تواند یکسری مشترک نیز داشته باشد (به منظور اطلاع رسانی با درج یک تاپیک جدید در خود انجمن یا زیر انجمن‌های آن) برای همین منظور لیستی از ForumSubscription را که در ادامه پیاده سازی آن را خواهیم آورد، تعریف کرده‌ایم.
علاوه بر اینها انجمن‌های ما مدیرانی هم خواهند داشت که برای این منظور نیز لیستی از ForumModerator را در مدل بالا تعریف کرده‌ایم. همچنین تصمیم گرفتیم امکانی را برای سیستم انجمن در نظر بگیرم تا اگر لازم بود یک سری اعلان در بالای انجمن‌ها نشان داده شوند و در بخش مدیریت بتوان این امکان را هندل کرد؛ که لیستی  است از ForumAnnouncement‌های مدل بالا.

مدل مدیران انجمن
 /// <summary>
    /// Represents The Moderator For Forum
    /// </summary>
    public class ForumModerator
    {
        #region NavigationProperties
        /// <summary>
        /// gets or sets Forum
        /// </summary>
        public virtual Forum Forum { get; set; }
        /// <summary>
        /// gets or sets identifier of forum
        /// </summary>
        public virtual long ForumId { get; set; }
        /// <summary>
        /// gets or sets user that moderate forum
        /// </summary>
        public virtual User Moderator { get; set; }
        /// <summary>
        /// gets or sets id of user that moderate forum
        /// </summary>
        public virtual long ModeratorId { get; set; }
        /// <summary>
        /// gets or sets permission of user that moderate forum
        /// </summary>
        public virtual ForumModeratorPermissions Permissions { get; set; }
        /// <summary>
        /// indicate moderator's permissions in this forum apply with
        /// </summary>
        public virtual bool ApplyChildren { get; set; }
        #endregion
    }

    [Flags]
    public enum  ForumModeratorPermissions
    {
        CanEditPosts=1,
        CanDeletePosts=2,
        CanManageTopics=4,
        CanOpenCloseTopics=8,
       ...
    }

این مدل نشان می‌دهد که کاربر x به عنوان مدیر انجمن y می‌باشد و یکسری دسترسی‌ها را نیز در این انجمن خواهد داشت.
  1. ApplyChildren : برای اعمال دسترسی‌های مدیریتی کاربر x به زیر انجمن‌های انجمن y البته اگر خصوصیت IsModeratorsInherited زیر انجمن‌های مورد نظر با مقدار true مقدار دهی شده باشد.
  2. Permissions : از نوع ForumModeratorPermissions و نگهدارنده دسترسی‌های کاربر x به عنوان مدیر انجمن y، می‌باشد.
    مدل اعلان‌های انجمن
        /// <summary>
        /// Represents the Announcement that shown Top Of Forums
        /// </summary>
        public class ForumAnnouncement
        {
            #region Ctor
            /// <summary>
            /// create one instance of <see cref="ForumAnnouncement"/>
            /// </summary>
            public ForumAnnouncement()
            {
                Id = SequentialGuidGenerator.NewSequentialGuid();
               CreatedOn = DateTime.Now;
            }
            #endregion
    
            #region Properties
            /// <summary>
            /// gets or sets Identifier 
            /// </summary>
            public virtual Guid Id { get; set; }
            /// <summary>
            /// gets or sets DateTime That this Announcement Will be Shown
            /// </summary>
            public virtual DateTime StartOn { get; set; }
            /// <summary>
            /// gets or sets DateTime That this Announcement Will be Finished 
            /// </summary>
            public virtual DateTime? ExpireOn { get; set; }
            /// <summary>
            /// gets or sets Content of this Announcement
            /// </summary>
            public virtual string Message { get; set; }
            /// <summary>
            /// Indicate this Announcement Will be shown on Children Forums
            /// </summary>
            public virtual bool ApplyChildren { get; set; }
            /// <summary>
            /// gets or sets datetime that this record created
            /// </summary>
            public virtual DateTime CreatedOn { get; set; }
            #endregion
    
            #region NavigationProperties
            /// <summary>
            /// gets or sets Forum that associated With this Announcement
            /// </summary>
            public virtual Forum Forum { get; set; }
            /// <summary>
            /// gets or sets Identifier of Forum that associated With this Announcement
            /// </summary>
            public virtual long ForumId { get; set; }
            #endregion
        }
    مدل بالا نشان دهنده‌ی اعلان‌هایی است که می‌توان با امکان تنظیم زمان آغاز و اتمام، آنها را در صفحات انجمن‌ها نمایش داد. این امکان هم از لحاظ مدیریتی می‌تواند مفید باشد و هم اگر لازم شد، تبلیغی انجام شود. برای اعمال رابطه‌ی یک به چند، یک خصوصیت از نوع Forum با همراه ForumId را در مدل بالا تعریف کرده‌ایم.
    1. ApplyChildren : برای مشخص کردین نمایش این اعلان در زیر انجمن‌های انجمن مورد نظر
    2. ExpireOn : به این دلیل نال پذیر در نظر گرفته شده است که اگر لازم بود، در زمان مشخصی به پایان نرسد و با null مقدار دهی شود.
    3. نکته : برای این مدل آی دی در نظر گرفته نشده است و از کلید مرکب متشکل از ForumId و ModeratorId استفاده خواهیم کرد.

    مدل مربوط به مشترکین انجمن
     /// <summary>
        /// Represents Subscriptions of Forums
        /// </summary>
        public class ForumSubscription
        {
            #region NavigationProperties
            /// <summary>
            /// gets or sets Forum
            /// </summary>
            public virtual Forum Forum { get; set; }
            /// <summary>
            /// gets or sets Identifier Of Forum 
            /// </summary>
            public virtual long ForumId { get; set; }
            /// <summary>
            /// gets or sets User That Subscribe to Forum
            /// </summary>
            public virtual User Subscriber { get; set; }
            /// <summary>
            /// gets or sets id of User That Subscribe to Forum
            /// </summary>
            public virtual long SubscriberId { get; set; }
            #endregion
    البته میتوانستیم این مدل را حذف و از رابطه‌ی چند به چند بین مدل‌ها استفاده کنیم که در آن حالت یا باید در پیاده سازی برنامه از کوئری خام T-SQL برای دسترسی به جدول واسط (junction table) استفاده می‌کردیم یا خیر، از کوئری‌های جوین دار مربوط به ORM استفاده می‌کردیم. به خاطر همین موارد، این ارتباط چند به چند را به دو ارتباط یک به چند شکسته‌ایم. در این مدل هم از کلید مرکب متشکل از ForumId و SubscriberId استفاده خواهیم کرد و به همین دلیل از آی دی استفاده نشده است.

    کلاس پایه AuditBaseEntity 
     /// <summary>
        /// Represents a base class for AuditLog
        /// </summary>
        public abstract class AuditBaseEntity
        {
            #region Properties
            /// <summary>
            /// sets or gets identifier
            /// </summary>
            public virtual long Id { get; set; }
            /// <summary>
            /// gets or sets rowversion for synchronization problem
            /// </summary>
            public virtual byte[] RowVersion { get; set; }
            /// <summary>
            /// gets or sets datetime that is created
            /// </summary>
            public  virtual DateTime CreatedOn { get; set; }
            /// <summary>
            /// gets or sets datetime that is modified
            /// </summary>
            public virtual DateTime? ModifiedOn { get; set; }
            #endregion
    
            #region NavigationProperties
            /// <summary>
            /// gets or sets creator of this record
            /// </summary>
            public virtual User Creator { get; set; }
            /// <summary>
            /// gets or sets creator's Id of this record
            /// </summary>
            public virtual long CreatorId { get; set; }
            /// <summary>
            /// gets or sets user that is modifier
            /// </summary>
            public virtual User Modifier { get; set; }
            /// <summary>
            /// gets or sets id of user that is modifier
            /// </summary>
            public virtual long ModifierId { get; set; }
            #endregion
        }
    این کلاس به منظور کپسوله کردن یکسری فیلد تکراری برای مدل‌هایی که نیاز است آخرین تغییر دهنده و زمان آن را ذخیره کنند، در نظر گرفته شده است و هدف از آن هیچ گونه اعمال ارث بری TPH یا TPT هم نیست. خصوصیات آن مشخص هستند که به طور کلی هر مدلی از این کلاس ارث بری کند، به صورت پیش فرض دو ارتباط با مدل کاربر ایجاد خواهد کرد؛ یکی بعنوان ایجاد کننده دیتا و دیگری هم به عنوان آخرین ویرایش کننده‌ی آن می‌باشد.

    مدل تاپیک ها
      /// <summary>
        /// Represents the Topic in the Forums
        /// </summary>
        public class ForumTopic : AuditBaseEntity
        {
            #region Ctor
            /// <summary>
            /// create one instance of <see cref="ForumTopic"/>
            /// </summary>
            public ForumTopic()
            {
                CreatedOn = DateTime.Now;
            }
            #endregion
    
            #region Properties
            /// <summary>
            /// 
            /// </summary>
            public virtual string Title { get; set; }
            /// <summary>
            /// 
            /// </summary>
            public virtual string Body { get; set; }
            /// <summary>
            /// gets or sets name of tags that assosiated with 
            /// this content fo increase performance
            /// </summary>
            public virtual string TagNames { get; set; }
            /// <summary>
            /// indicate this topic is Sticky and will be shown top of forum
            /// </summary>
            public virtual bool IsSticky { get; set; }
            /// <summary>
            /// indicate this topic is closed
            /// </summary>
            public virtual bool IsClosed { get; set; }
            /// <summary>
            /// gets or sets identifier of  last post in this topic
            /// </summary>
            public virtual long? LastPostId { get; set; }
            /// <summary>
            /// gets or sets identifier of Last user that post in this topic
            /// </summary>
            public virtual long? LastPosterId { get; set; }
            /// <summary>
            /// gets or sets title of last Post in this topic
            /// </summary>
            public virtual string LastPostTitle { get; set; }
            /// <summary>
            /// gets or sets displayName of user that create lastpost in this topic
            /// </summary>
            public virtual string LastPoster { get; set; }
            /// <summary>
            /// gets or sets datetime that last post posted in this topic
            /// </summary>
            public virtual DateTime? LastPostCreatedOn { get; set; }
            /// <summary>
            /// indicate this topic is approved
            /// </summary>
            public virtual bool IsApproved { get; set; }
            /// <summary>
            /// indicate this topic is type of Announcements and shown in Annoucements sections
            /// </summary>
            public virtual bool IsAnnouncement { get; set; }
            /// <summary>
            /// gets or sets viewed count 
            /// </summary>
            public virtual long ViewCount { get; set; }
            /// <summary>
            /// gets or sets count of posts that they are approved
            /// </summary>
            public virtual int ApprovedPostsCount { get; set; }
            /// <summary>
            /// gets or sets count of posts that they are Unapproved
            /// </summary>
            public virtual int UnApprovedPostsCount { get; set; }
            /// <summary>
            /// gets or sets specifications of this topic's rating
            /// </summary>
            public virtual Rating Rating { get; set; }
            /// <summary>
            /// gets or sets datetime that this topic closed
            /// </summary>
            public virtual DateTime? ClosedOn { get; set; }
            /// <summary>
            /// gets or sets reason that this topic colsed
            /// </summary>
            public virtual string ClosedReason { get; set; }
            /// <summary>
            /// gets or sets count of reports
            /// </summary>
            public virtual int ReportsCount { get; set; }
            /// <summary>
            /// gets or sets information of User-Agent
            /// </summary>
            public virtual string Agent { get; set; }
            /// <summary>
            /// indicate the creator's Signature should be display under the topic
            /// </summary>
            public virtual bool DisplaySignature { get; set; }
            /// <summary>
            /// indicate the posts of this topic should be Moderate Before Dipslay
            /// </summary>
            public virtual bool ModeratePosts { get; set; }
            #endregion
            #region NavigationProperties
    
            /// <summary>
            /// gets or sets Collection of tags that associated with this topic
            /// </summary>
            public virtual ICollection<Tag> Tags { get; set; }
            /// <summary>
            /// gets or sets forum
            /// </summary>
            public virtual Forum Forum { get; set; }
            /// <summary>
            /// gets or sets identifier of Forum
            /// </summary>
            public virtual long ForumId { get; set; }
            /// <summary>
            /// gets or sets Posts Of this topic
            /// </summary>
            public virtual ICollection<ForumPost> Posts { get; set; }
            /// <summary>
            /// get or set Subscriptions List 
            /// </summary>
            public virtual ICollection<ForumTopicSubscription> Subscriptions  { get; set; }
            #endregion
        }

    مدل بالا مشخص کننده‌ی تاپیک‌های انجمن می‌باشد. خصوصیاتی که نیاز به توضیح دارند:
    1. LastPostId , LastPosterId, LastPoster  , LastPostTitle  ,LastPostCreatedOn: برای افزایش کارآیی سیستم در نظر گرفته شده‌اند.
    2. ModeratePosts : اگر لازم است پست‌های یک تاپیک خاص، قبل از نمایش مدیریت شوند، با true مقدار دهی خواهد شد.
    3. Tags : لیستی از برچسب‌ها که برای اعمال رابطه‌ی چند به چند با مدل برچسب‌های معرفی شده‌ی در مقاله اول، در نظر گرفته شده است.
    4. Posts : در هر تاپیکی یک سری پست به عنوان جواب‌های آن ارسال خواهد شد.
    5. Subscriptions  : به مانند انجمن‌ها، تاپیک‌های ما هم می‌توانند یک سری مشترک داشته باشند، تا از تغییرات این تاپیک مطلع شوند .
    نکته : شاید بهتر بود پست اول تاپیک به عنوان محتوای تاپیک در نظر گرفته می‌شد، ولی بر اساس یک تصمیم شخصی این کار را انجام ندادم  و پیچیدگی کار هم نسبت به آن حالت کم است.

    مدل مربوط به مشترکین تاپیک
     public class ForumTopicSubscription
        {
            #region NavigationProperties
            /// <summary>
            /// gets or sets Topic
            /// </summary>
            public virtual ForumTopic ForumTopic { get; set; }
            /// <summary>
            /// gets or sets TopicId
            /// </summary>
            public virtual long ForumTopicId { get; set; }
            /// <summary>
            /// gets or sets User that subscribe on topic
            /// </summary>
            public virtual User Subscriber { get; set; }
            /// <summary>
            /// gets or sets Id of User that subscribe on topic
            /// </summary>
            public virtual long SubsciberId { get; set; }
            #endregion
        }
    مدل بالا در واقع مانند مدل ForumSubscription، مشخص می‌کند که کاربر x، مشترک تاپیک y شده است. ما یک ارتباط چند به چند مابین کاربر و تاپیک را به دو ارتباط یک به چند شکسته‌ایم (توضیحات لازم در بالا داده شد).
    نکته : میتوان یکسری خصوصیت enum دیگر هم برای مثال به عنوان نوع Notification ارسالی که با ایمیل باشد، اضافه کرد که یا در سیستم ذخیره شوند و یا در همان زمان، در سیستم برای کاربر ارسال شوند.

     حتما لازم خواهد بود تاریخچه‌ی تغییراتبرای تاپیک‌ها و پست‌های ارسالی ذخیره شوند؛ در مقاله‌ی بعدی به این موضوع هم خواهیم پرداخت.

    ‫پروکسی‌های اشیاء در ES 6

    $
    0
    0
    پروکسی‌ها، پایه‌ی مباحث AOPهستند. این اشیاء ویژه‌ی ES 6، امکان ردیابی تغییرات را بر روی اشیاء جاوا اسکریپتی فراهم می‌کنند. ابتدایی‌ترین مثالی را که در این زمینه می‌توان ارائه داد، بررسی تغییرات خواص Get و Set اشیاء هستند. فرض کنید شیء unicorn به صورت زیر تعریف شده‌است:
    var unicorn = {
       legs: 4,
       color: 'brown',
       horn: true
    };
    اکنون می‌خواهیم اگر کسی درخواست مقدار خاصیت color این شیء را ارائه داد، بجای رنگ قهوه‌ای، یک مقدار سفارشی سازی شده را دریافت کند. برای تداخل در این بین و کنترل مقدار بازگشت داده شده‌ی توسط یک شیء مفروض، می‌توان از شیء جدیدی به نام Proxy استفاده کرد:
    var proxyUnicorn = new Proxy(unicorn, {
          get: function(target, property) {
                if(property === 'color') {
                      return 'awesome ' + target[property];
                  } else {
                      return target[property];
               }
          }
    });
    کار شیء پروکسی، ایجاد یک شیء جدید از unicorn نیست. بلکه به صورت غشایی نامرئی ظاهر شده و محصور کننده‌ی این شیء می‌شود. بنابراین کلیه‌ی درخواست‌های رسیده‌ی به unicorn، ابتدا باید از این غشاء رد شود و سپس به unicorn برسد. اینجا است که امکان ردیابی و همچنین سفارشی سازی دسترسی به خواص را می‌توان پیاده سازی کرد.
    شیء Proxy در ES 6 دو پارامتر را دریافت می‌کند. پارامتر اول آن، شیء اصلی است که باید محصور شود و پارامتر دوم آن مشخص می‌کند که چه عملیاتی باید تحت کنترل و سفارشی سازی قرار گیرد. در این مثال عملیات get تحت نظر قرار گرفته‌است و برای اینکار متدی که تعریف شده (به آن handler نیز می‌گویند)، پارامتر اول آن target یا همان unicorn در این مثال است و property نام خاصیتی است که هم اکنون قرار است مقدار آن بازگشت داده شود. در مثال فوق دو حالت دسترسی به خاصیتی به نام color و همچنین سایر خواصی که این نام را ندارند، پیاده سازی شده‌است.
    پس از این عملیات، اگر به خواص ارائه شده‌ی توسط شیء پروکسی دسترسی پیدا کنیم، یک چنین خروجی را دریافت خواهیم کرد:
     console.log(proxyUnicorn.legs); //4
    console.log(proxyUnicorn.color); //'awesome brown'

    مثالی دیگر در این زمینه می‌تواند کنترل عملیات دسترسی به حالت set باشد (هر دوی این حالت‌ها را با یک شیء پروکسی نیز می‌توان مدیریت کرد):
    var proxyUnicorn = new Proxy(unicorn, {
               set: function(target, property, value) {
                        if(property === 'horn' && value === false) {
                             console.log('unicorn cannot ever lose its horn!');
                          } else {
                             target[property] = value;
                          }
               }
    });
    در حالت set، متد handler تعریف شده، پارامتر سومی را به نام value دارد و این مقدار، مساوی مقداری است که هم اکنون توسط کاربر تنظیم شده‌است. بنابراین در اینجا می‌توان پیاده سازی منطق خاصی را پیگیری کرد. برای مثال در اینجا اگر خاصیت مدنظر horn باشد و کاربر سعی کند مقدار false را به آن نسبت دهد، توسط این پروکسی از انجام عملیات منع خواهد شد.


    ردگیری فراخوانی‌های توابع توسط پروکسی‌های ES 6

    در ادامه همان شیء unicorn را مشاهده می‌کنید که متد hornAttack نیز به آن اضافه شده‌است.
    var unicorn = {
       legs: 4,
       color: 'brown',
       horn: true,
       hornAttack: function(target) {
            return target.name + ' was obliterated!';
       }
    };
    اکنون می‌خواهیم دسترسی به متد hornAttack را تحت نظر قرار داده و اجازه‌ی استفاده‌ی از آن‌را به سایر اشیاء ندهیم.
     var thief = { name: 'Rupert'}
    thief.attack = unicorn.hornAttack;
    thief.attack();
    برای نمونه در این حالت نمی‌خواهیم که شیء thief بتواند از hornAttack یک unicorn استفاده کند. به همین جهت برای unicorn.hornAttack یک پروکسی جدید را تعریف می‌کنیم که دسترسی به آن‌را تحت نظر قرار دهد:
    unicorn.hornAttack = new Proxy(unicorn.hornAttack, {
            apply: function(target, context, args) {
                      if(context !== unicorn) {
                         return 'nobody can use unicorn horn but unicorn!';
                       } else {
                         return target.apply(context, args);
                     }
            }
    });
    پس از این تعریف و انتساب، unicorn.hornAttack دیگر همان unicorn.hornAttack اصلی نخواهد بود و اکنون یک proxy object است. در این پروکسی برای کنترل متدها از کلید apply استفاده می‌شود. متد handler آن دارای سه پارامتر است. پارامتر target همان متد مدنظر است. پارامتر context شیءایی است که قرار است این متد را فراخوانی کند. پارامتر سوم نیز لیست پارامترهای ارسالی به متد است.
    در ادامه اگر متد thief.attack فراخوانی شود، این فراخوانی عمل نکرده (چون حالت context !== unicorn برقرار است) و پیام «nobody can use unicorn horn but unicorn» نمایش داده می‌شود.

    در این متد handler (پارامتر دوم شیء پروکسی) مواردی مانند افزودن یا حذف خواص را نیز می‌توان تحت کنترل قرار داد:
    function NOPE() {
      throw new Error("can't modify read-only view");
    }
    
    var handler = {
      // Override all five mutating methods.
      set: NOPE,
      defineProperty: NOPE,
      deleteProperty: NOPE,
      preventExtensions: NOPE,
      setPrototypeOf: NOPE
    };

    ‫ایجاد اشیاء دفاعی با ES 6 Proxy

    $
    0
    0
    ممکن است برای شما نیز پیش آمده باشد که به یک خصوصیت از یک شیء که وجود ندارد، ارجاع داده باشید و متوجه علت خطای رخ داده نشده و مدتی را به دنبال علت خطا صرف کرده باشید. بعضی از افراد به همین علت از جاوااسکریپت متنفر هستند و می‌گویند اگر از یک زبان type-safe استفاده می‌کردیم آنگاه در صورتیکه به خصوصیتی ارجاع می‌دادیم که وجود ندارد، نبودن خصوصیت ارجاع داده شده را اعلام می‌کرد. این مشکل وجود داشت تا وقتی که ECMAScript 6 ارائه شد.

    ECMAScript 5

    در حالیکه ECMAScript 5 قابلیت‌های فوق العاده‌ای را برای کنترل کردن خصوصیات موجود در اشیاء، در اختیار شما قرار می‌دهد، اما هیچ راه کاری را برای خصوصیاتی که موجود نیستند، ندارد. شما می‌توانید برای خواص موجود، از رونویسی (تنظیم writable برابر false) و یا حذف شدن (تنظیم configurable برابر false) جلوگیری کنید. شما می‌توانید از اختصاص خصوصیات جدید به اشیاء با استفاده از ()Object.preventExtensions و یا تنظیم تمام خصوصیات به صورت فقط خواندنی و یا غیرقابل حذف ()Object.freeze جلوگیری کنید.

    اگر شما نمی‌خواهید تمام خصوصیات را فقط خواندنی کنید می‌توانید از ()Object.seal استفاده کنید. این‌ها مانع از اضافه کردن خصوصیات و یا حذف کردن خصوصیات موجود می‌شوند. اگر به یک شیء مهر و موم شده (sealed)، زمانی که از strict mode استفاده می‌کنید، یک خصوصیت جدید اضافه کنید باعث ایجاد خطا می‌شود:

    "use strict";
    
    var person = {
        name: "Vahid Mohammad Taheri"
    };
    
    Object.seal(person);
    person.age = 27;    // Error!
    این کار باعث اطلاع شما می‌شود که در حال تلاش برای تغییر اینترفیس یک شیء، با استفاده از اضافه کردن یک ویژگی به آن هستید. هنگامیکه سعی در خواندن ویژگی از یک شیء که جزئی از اینترفیس آن نیست، دارید نیز با خطا مواجه می‌شوید.

    نجات با Proxyها

    پروکسی‌ها، دارای سابقه طولانی و پیچیده ای در ECMAScript 6 است. طرح اولیه آن توسط Firefox و Chrome قبل از تصمیم TC-39 به تغییر پروکسی‌ها، اجرا شده است. این تغییرات، برای بهتر و روان‌تر شدن پروکسی‌ها از طرح اولیه پروکسی‌ها انجام گرفت.

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

    این کار از طریق یک سری از روش‌هایی که به مخفی کردن عملیات در ECMAScript مطابقت دارند، انجام می‌شود. به عنوان مثال زمانیکه بر روی یک ویژگی از یک شیء، عمل خواندن انجام می‌شود، عملیات  [[Get]] در موتور جاوااسکریپت انجام می‌گیرد. نحوه‌ی رفتار [[Get]] را نمی‌توان تغییر داد؛ با این حال، با استفاده از پروکسی‌ها می‌توان دامی برای زمان فراخوانی [[Get]] قرار داد و عملیات خاص مورد نظر خود را اعمال کرد. به مثال زیر توجه کنید:

    var proxy = new Proxy({ name: "Vahid" }, {
        get: function(target, property) {
            if (property in target) {
                return target[property];
            } else {
                return 13;
            }
        }
    });
    
    console.log(proxy.time);        // 13
    console.log(proxy.name);        // "Vahid"
    console.log(proxy.title);       // 13
    این پروکسی از یک شیء ساخته شده به عنوان هدف (آرگومان اول به ()Proxy) استفاده می‌کند. آرگومان دوم دامی را که می‌خواهید برای این شیء بسازید، تعریف می‌کند. با استفاده از متد get عملیات مربوط به [[Get]] به دام افتاده و تابع تعریف شده‌ی ما اجرا می‌شود (باقی عملیات به صورت عادی اجرا می‌شوند). دامی که برای شیء مورد نظر تعریف کرده‌ایم دو پارامتر دریافت می‌کند (اول شی هدف، دوم ویژگی مورد نظر). با استفاده از کد نوشته شده در آن ابتدا بررسی می‌شود که شیء مورد نظر دارای ویژگی ارسال شده است یا خیر؟ در صورتی که وجود داشته باشد، مقدار آن بازگشت داده می‌شود و در غیر اینصورت به صورت ثابت مقدار 13 برگشت داده می‌شود.

    برای ایجاد اشیاء دفاعی لازم است چگونگی رهگیری عملیات [[Get]]را درک کنید و هدف از این کار صدور خطا در زمان دستیابی به ویژگی ای از شیءایی که وجود ندارد است.
    function createDefensiveObject(target) {
    
        return new Proxy(target, {
            get: function(target, property) {
                if (property in target) {
                    return target[property];
                } else {
                    throw new ReferenceError("Property \"" + property + "\" does not exist.");
                }
            }
        });
    }

    تابع ()createDefensiveObject  یک شیء را به عنوان هدف می‌پذیرد و یک شیء دفاعی برای آن ایجاد می‌کند. پروکسی یک دام به نام getدارد؛ برای زمانی که عمل خواندن انجام می‌شود. اگر ویژگی خوانده شده در شیء وجود داشت، مقدار آن برگشت داده می‌شود و از سوی دیگر، وقتی ویژگی خوانده شده در شیء وجود نداشته باشد، سبب بروز خطا می‌شود. به مثال زیر توجه کنید:
    var person = {
        name: "Vahid"
    };
    
    var defensivePerson = createDefensiveObject(person);
    
    console.log(defensivePerson.name);        // "Vahid"
    console.log(defensivePerson.age);         // Error!
    در اینجا ویژگی name به طور معمول کار خواهد کرد؛ ولی ویژگی age باعث صدور خطا می‌شود.
    اشیاء دفاعی باعث می‌شوند تا بر روی ویژگی‌هایی که در شیء وجود دارند، بتوان عمل خواندن را انجام داد و در ویژگی‌هایی که موجود نیستند در هنگام خواندن، باعث صدور پیام خطا می‌شوند. با این حال هنوز هم شما می‌توانید ویژگی‌های جدید را بدون خطا اضافه کنید:
    var person = {
        name: "Vahid"
    };
    
    var defensivePerson = createDefensiveObject(person);
    
    console.log(defensivePerson.name);        // "Vahid"
    
    defensivePerson.age = 13;
    console.log(defensivePerson.age);         // 13
    بنابراین اشیاء توانایی خود را برای جهش و تغییر حفظ می‌کنند. در صورتی که شما چیزی را برای تغییر آنها انجام دهید، همیشه می‌توانید ویژگی‌هایی را به اشیاء اضافه کنید ولی عمل خواندن بر روی ویژگی‌های غیرموجود همیشه باعث صدور خطا و بازگشت مقدار undefinedمی‌شود.
    روش‌های تشخیص ویژگی‌های استاندارد هنوز هم به طور معمول و بدون خطا کار می‌کنند.
    var person = {
        name: "Vahid"
    };
    
    var defensivePerson = createDefensiveObject(person);
    
    console.log("name" in defensivePerson);               // true
    console.log(defensivePerson.hasOwnProperty("name"));  // true
    
    console.log("age" in defensivePerson);                // false
    console.log(defensivePerson.hasOwnProperty("age"));   // false
    شما می‌توانید از اینترفیس یک شیء، زمانیکه دسترسی به یک ویژگی آن وجود ندارد، صورت می‌گیرد، با رد کردن اضافات و صدور پیام‌های خطا، دفاع کنید.
    var person = {
        name: "Vahid"
    };
    
    Object.preventExtensions(person);
    
    var defensivePerson = createDefensiveObject(person);
    
    
    defensivePerson.age = 27;                 // Error!
    console.log(defensivePerson.age);         // Error!
    در این مورد، defensivePerson برای هر دو حالت خواندن و نوشتن ویژگی‌هایی که وجود ندارند، خطا صادر می‌کند.
    شاید مفیدترین زمان برای استفاده از اشیاء دفاعی، در هنگام تعریف یک سازنده باشد و شما می‌توانید این کار را به عنوان یک قرارداد در نوشتن اشیاء حفظ کنید.
    برای مثال:
    function Person(name) {
        this.name = name;
    
        return createDefensiveObject(this);
    }
    
    var person = new Person("Vahid");
    console.log(person.age);         // Error!
    به وسیله فراخوانی تابع ()createDefensiveObject درون سازنده، می‌توانید اطمینان کامل داشته باشید که همه‌ی نمونه‌های ساخته شده‌ی از شیء Person، دارای حالت دفاعی می‌باشند.


    ‫بررسی اینترفیس ICommand در WPF

    $
    0
    0
    مدتی هست که مشغول مطالعه و یادگیری WPF از طریق مطالب سایت هستم؛ به همین خاطر تصمیم گرفتم مطلبی را حول محور اینترفیس ICommnad  گردآوری کنم و در اختیار کاربران سایت قرار دهم.

    سرفصل‌های این مطلب :
    • Command چیست
    • اینترفیس ICommand چیست 
    • چرا اینترفیس ICommand
    • ایجاد UI مورد نیاز 
    • چگونگی استفاده از ICommand 
    • استفاده از INotifyPropertyChanged

    Command چیست ؟
    در برنامه نویسی WPF به هر کلاسی که اینترفیس ICommandرا پیاده سازی کند، اصطلاحا Commnad گوییم. تفاوت کوچکی بین یک Event و Command وجود دارد. رخداد‌ها برای کنترل‌های UI ساخته و تخصیص داده می‌شوند؛ اما Command‌ها انتزاعی‌تر هستند و تمرکز آنها بر روی نحوه‌ی انجام کارها می‌باشد.
    برای تعاملات در برنامه‌ها از Commandها و Event‌ها استفاده می‌کنیم. ما در WPF دو روش برای تعامل با UI داریم:
    1- استفاده از Event‌ها و نوشتن کد‌های مورد نیاز در بخش CodeBehind (رعایت نکردن الگوی MVVM).
    WPF تعداد زیادی RoutedEvent پیش ساخته (Built In) دارد که از آنها می‌توان در این روش استفاده کرد. 
    2- استفاده از Command و درگیر کردن کدهای اجرایی نوشته شده در ViewModel با استفاده از Command ها.
    در زمان استفاده از الگوی MVVM مجاز به نوشتن کد در بخش CodeBehind نیستیم. بنابراین از سیستم رخداد‌های طراحی شده‌ی در WPF که RoutedEvent می‌باشد، نمی‌توان استفاده کرد. به این خاطر که رخداد‌ها در ViewModel قابل دسترسی نمی‌باشد.

    اینترفیس ICommand:
    این اینترفیس سه عضو دارد که آن‌ها را در جدول زیر مشاهده می‌کنید:

    نام عضو

    توضیحات

    Bool CanExecute(object parameter)

    این تابع پارامتری از نوع objectرا دریافت می‌کند و یک مقدار boolرا به خروجی تابع می‌فرستد. اگر مقدار خروجی متد، true  باشدcommand  اجرا خواهد شد و در غیر اینصورت اتفاقی رخ نخواهد داد. اغلب ازDelegate  ها به عنوان پارامتر این تابع استفاده می‌شود؛Delegate های پیش ساخته‌ای همچون Action,Predicate,Func

    Event EventHandler CanExecuteChanged

    این رویداد برای آگاه سازی UIکه به Command متصل است، استفاده می‌شود .بر اساس خروجی تابع CanExecute، این رویداد اتفاق می‌افتد.

    Void Execute(Object parameter)

    این متد کار اصلی را در Command‌ها انجام می‌دهد. این متد تنها زمانی اجرا می‌شود که متدCanExecute  مقدار trueرا بازگرداند. این تابع پارامتری را از نوع objectدریافت می‌کند، اما عموما ما یکDelegate را به آن ارسال می‌کنیم. Delegateارجاعی را به متدی، در خود نگاه می‌دارد که انتظار داریم در صورت اجرایcommand ، اجرا شود.


    چرا اینترفیس ICommand :
    هسته‌ی اصلی Command‌ها در WPF، اینترفیس ICommand می‌باشد. این اینترفیس به‌صورت گسترده‌ای در الگوی MVVM و Prism  استفاده شده است و استفاده‌ی از آن محدود به MVVM نمی‌باشد. تعداد زیادی Commnad بصورت پیش ساخته وجود دارند که این اینترفیس را پیاده سازی کرده‌اند .کلاس پایه RoutedCommand اینترفیس ICommand را پیاده سازی کرده است و WPF فرمان‌های زیر را فراهم کرده است:
    • MediaCommnads
    • ApplicationCommnads
    • NavigationCommands
    • ComponentCommnads
    • EditingCommnads
    که همگی از کلاس RoutedCommandاستفاده کرده‌اند.
    در این مطلب به دنبال ایجاد برنامه‌ای هستیم که حاصل جمع مفدار دو Textbox را پس از فشردن کلید جمع در یک textblock نمایش دهد.

    ساخت UI مورد نیاز :
    گام اول : 
    با اجرای ویژوال استودیو، برنامه‌ای را با نام ICommnadSample ایجاد کنید. ساختار پروژه به شکل زیر است:
    همانطور که می‌بینید View و ViewModel و در نهایت Command‌ها در پوشه‌های مجزایی ساماندهی شده‌اند.

    گام دوم:
    ابتدا قالب گرافیکی را مشخص می‌کنیم. در پوشه‌ی Views یک UserControl را به نام CalculatorView.xaml ایجاد کرده و کد زیر را در آن نوشتیم:
    <Grid DataContext="{Binding Source={StaticResource calculatorVM}}" Background="#FFCCCC"><Grid.RowDefinitions><RowDefinition Height="80"/><RowDefinition/><RowDefinition Height="80"/><RowDefinition Height="44"/></Grid.RowDefinitions><Grid.ColumnDefinitions><ColumnDefinition/><ColumnDefinition/><ColumnDefinition/><ColumnDefinition/></Grid.ColumnDefinitions><Label Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="4" FontSize="25"
                   VerticalAlignment="Top" HorizontalAlignment="Center" Foreground="Blue" Content="ICommand Sample"/><Label Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" 
                   Margin="10,0,0,0" VerticalAlignment="Bottom" FontSize="20"  Content="First Input"/><Label Grid.Row="0" Grid.Column="2" Grid.ColumnSpan="2" 
                   Margin="10,0,0,0" VerticalAlignment="Bottom" FontSize="20"  Content="Second Input"/><TextBox Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" Margin="10,0,0,0" FontSize="20" 
                     HorizontalAlignment="Left" Height="30"  Width="150" TextAlignment="Center" Text="{Binding FirstValue, Mode=TwoWay}"/><TextBox Grid.Row="1" Grid.Column="2" Grid.ColumnSpan="2" Margin="10,0,0,0" FontSize="20"
                     HorizontalAlignment="Left"  Height="30" Width="150" TextAlignment="Center" Text="{Binding SecondValue, Mode=TwoWay}"/><Rectangle Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="4" Fill="LightBlue"/><Button Grid.Row="2" Grid.Column="0" Content="+"  Margin="10,0,0,0" HorizontalAlignment="Left" Height="50" Width="50" FontSize="30" Command="{Binding AddCommand}"/><Label Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="2" FontSize="25" Margin="10,0,0,0" HorizontalAlignment="Left" Height="50"  Content="Result : "/><TextBlock Grid.Row="3" Grid.Column="2" Grid.ColumnSpan="2" FontSize="20" Margin="10,0,0,0" Background="BlanchedAlmond"
                       TextAlignment="Center"  HorizontalAlignment="Left" Height="36" Width="150" Text="{Binding Output}"/></Grid>
    برای استفاده از این UserControl در پنجره‌ی اصلی برنامه (فایل MainWindows.Xaml) به شکل زیر عمل می‌کنیم:
    ابتدا فضای نام View را به فایل MainWindows.xaml اضافه می‌کنیم :
       xmlns:myview="clr-namespace:ICommnadSample.Views"
    ایجاد تگ برای استفاده از View تولید شده در  Grid اصلی برنامه :
    <Grid><myview:CalculatorView/></Grid>

    گام سوم :
    همانطور که مشاهده می‌کنید، کنترل‌هایی که در عملیات انقیاد داده‌ها (DataBinding) شرکت می‌کنند، از طریق خاصیت Binding و معرفی خصوصیت مورد نظر مشخص می‌شوند.
    برای این منظور در پوشه‌ی ViewModels و به کلاس CalculatorViewModel سه خصوصیت به‌همراه فیلد خصوصی، به شکل زیر اضافه می‌کنیم:
    private double firstValue; 
    private double secondValue;
    private double output;
    public double FirstValue
            {
                get { return firstValue; }
                set
                {
                    firstValue = value;
                    OnPropertyChanged("FirstValue");
                }
            }
            public double SecondValue
            {
                get { return secondValue; }
                set
                {
                    secondValue = value;
                    OnPropertyChanged("SecondValue");
                }
            }
            public double Output
            {
                get { return output; }
                set
                {
                    output = value;
                    OnPropertyChanged("Output");
                }
            }

    چگونگی استفاده‌ی از اینترفیس ICommand :
    برای ایجاد ارتباط بین Command ‌ها و UI می‌بایست اینترفیس ICommand توسط کلاس مورد نظر ما پیاده سازی شود. در ابتدا کلاسی با عنوان PlusCommnad  ایجاد و از اینترفیس مورد نظر ارث بری می‌کنیم.
    هدف ما شبیه سازی رویداد کلیک دکمه‌ی موجود در صفحه با استفاده از Command می‌باشد.

    گام چهارم:
    پس از پیاده سازی اینترفیس لازم است تا کمی تغییر در بدنه متد‌ها ایجاد کنیم:
    public class PlusCommand : ICommand
    {
        private CalculatorViewModel calculatorViewModel;
        public PlusCommand(CalculatorViewModel vm)
        {
            calculatorViewModel = vm;
        }
        public bool CanExecute(object parameter)
        {
            return true;
        }
        public void Execute(object parameter)
        {
            calculatorViewModel.Add();
        }
        public event EventHandler CanExecuteChanged;
    }
    همانطور که می‌بینید در ابتدا فیلدی از جنس کلاس CalculatorViewModel ایجاد و از طریق سازنده‌ی کلاس آن را مقدار دهی کردیم (قصد داریم نمونه‌ای از ViewModel مورد نظر را به این کلاس ارسال کنیم).
    در ادامه بصورت دستی (Hard Code) مقدار بازگردانده شده را برای تابع CanExecute به مقدار true  تغییر دادیم و متد تابع Execute را به شکلی تغییر دادیم تا تابع Add را در CalculatorViewModel، اجرا کند.

    گام پنجم:
    از کلاس PlusCommand در CalculatorViewModel، یک شیء ساخته و در سازنده‌ی CalculatorViewModel آن را مقدار دهی می‌کنیم: 
            private PlusCommand plusCommand;
            public CalculatorViewModel()
            {
                plusCommand = new PlusCommand(this);
            }

    گام ششم:
    در فایل CalculatorView ارجاعی را به فضای نام کلاس CalculatorViewModel ایجاد می‌کنیم :
       xmlns:vm="clr-namespace:ICommnadSample.ViewModels"
    پس از اضافه کردن فضای نام، از تگ UserControl.Resource برای رجیستر کردن CalculatorViewModel با کلید calculatorVM جهت مشخص کردن منبع داده استفاده کردیم.
    <UserControl.Resources><vm:CalculatorViewModel x:Key="calculatorVM" /></UserControl.Resources>

    گام هفتم:
    اضافه کردن تابع Add در CalculatorViewModel برای عملیات جمع :
    public void Add()
    {
       Output = firstValue + secondValue;
    }

    گام هشتم:
    تعریف یک Command  برای عملیات جمع به نام AddCommand. این همان خصوصیتی است که باید بجای رویداد کلیک دکمه از طریق خاصیت Command موجود در کنترل و ویژگی Binding به کنترل متصل شود.
      public ICommand AddCommand {
                get
                {
                    return plusCommand;
                }
            }

    نحوه‌ی استفاده:
       Command="{Binding AddCommand}"
     
    گام نهم :
    برای تکمیل عملیات انقیاد داده‌ها، کلاسی به نام ViewModelBase تعریف شده است. این کلاس از اینترفیس INotifyPropertyChange ارث بری کرده و اعضای این کلاس را پیاده سازی کرده است.
      public class ViewModelBase:INotifyPropertyChanged
        {
            public event PropertyChangedEventHandler PropertyChanged;
    
            protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
            {
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    پیاده سازی این اینترفیس سبب می‌شود تا کلاس‌های ViewModel ایی که احتیاج به این اینترفیس برای عملیات انقیاد داده‌ها دارند، تنها با ارث بری، از این ظرفیت استفاده کنند و نیازی به پیاده سازی این اینترفیس در هر کلاس نباشد.

    گام دهم:
    ارث بری کلاس CalculatorViewModel از کلاس ViewModelBase:
       public class CalculatorViewModel : ViewModelBase
    در این مرحله هر کنترلی را که قصد داریم با تغییر منبع داده بروز شود و یا با تغییر وضعیتش منبع داده تغییر کند، اعلام می‌کنیم:
       OnPropertyChanged("FirstValue");
    برای هر سه خصوصیت ViewModel کد زیر را در بلاک Set تکرار می‌کنیم (توجه شود که پارامتر ارسالی باید نام پراپرتی مورد نظر باشد)
    کد کامل کلاس CalculatorViewModel به شکل زیر است:
    public class CalculatorViewModel : ViewModelBase
        {
            private double firstValue;
            private double secondValue;
            private double output;
            private PlusCommand plusCommand;
            public CalculatorViewModel()
            {
                plusCommand = new PlusCommand(this);
            }
    
            public double FirstValue
            {
                get { return firstValue; }
    
                set
                {
                    firstValue = value;
                    OnPropertyChanged("FirstValue");
                }
            }
            public double SecondValue
            {
                get { return secondValue; }
                set
                {
                    secondValue = value;
                    OnPropertyChanged("SecondValue");
                }
            }
            public double Output
            {
    
                get { return output; }
    
                set
                {
                    output = value;
                    OnPropertyChanged("Output");
                }
            }
    
    
            public ICommand AddCommand
            {
                get
                {
                    return plusCommand;
                }
            }
    
            internal void Add()
            {
                Output = firstValue + secondValue;
            }
        }


    حال می‌توانید برنامه را اجرا و تست کنید:



    برای درک بهتر عملیات انقیاد دادها می‌توانید به این مقاله مراجعه کنید.

    ‫توسعه سیستم مدیریت محتوای DNTCms - قسمت چهارم

    $
    0
    0

    در این قسمت مدل‌های مربوط به بخش انجمن را تکمیل کرده و همچنین سیستم نظرسنجی را نیز بررسی خواهیم کرد.
    همکاران این قسمت: 
    سلمان معروفی
    سید مجبتی حسینی

    مدل پست‌های انجمن

     /// <summary>
        /// Represents The Post of Forum
        /// </summary>
        public class ForumPost : AuditBaseEntity
        {
            #region Ctor
            /// <summary>
            /// create one instance of <see cref="ForumPost"/>
            /// </summary>
            public ForumPost()
            {
                CreatedOn = DateTime.Now;
            }
            #endregion
    
            #region Properties
            /// <summary>
            /// gets or sets body of this post
            /// </summary>
            public virtual string Body { get; set; }
            /// <summary>
            /// gets or sets Count of this post's reports
            /// </summary>
            public virtual int ReportsCount { get; set; }
            /// <summary>
            /// gets or sets information of User-Agent
            /// </summary>
            public virtual string Agent { get; set; }
            /// <summary>
            /// gets or sets rating values 
            /// <remarks>is a complex type</remarks>
            /// </summary>
            public virtual Rating Rating { get; set; }
            /// <summary>
            /// gets or sets author's ip address
            /// </summary>
            public virtual string CreatorIp { get; set; }
            /// <summary>
            /// gets or sets status of this post
            /// </summary>
            public virtual ForumPostStatus Status { get; set; }
            #endregion
    
            #region NavigationProperties
            /// <summary>
            /// gets or sets ParentPost of this post
            /// </summary>
            public virtual ForumPost Reply { get; set; }
            /// <summary>
            /// gets or sets ParentPost's Id of this post
            /// </summary>
            public virtual long? ReplyId { get; set; }
            /// <summary>
            /// gets or sets 
            /// </summary>
            public virtual ICollection<ForumPost> Children { get; set; }
            /// <summary>
            /// gets or sets Topic That Associated with this Post
            /// </summary>
            public virtual ForumTopic Topic { get; set; }
            /// <summary>
            /// gets or sets Id of Topic That Associated with this Post
            /// </summary>
            public virtual long TopicId { get; set; }
            /// <summary>
            /// get or sets  Histories of this Post's Updates
            /// </summary>
            public virtual ICollection<ForumPostHistory> Histories { get; set; }
            /// <summary>
            /// gets or sets Forum that this post created in it . used for retrive posts count 
            /// </summary>
            public virtual Forum Forum { get; set; }
            /// <summary>
            /// gets or sets id of Forum that this post created in it . used for retrive posts count 
            /// </summary>
            public virtual long ForumId { get; set; }
            #endregion
        }
    
     public enum ForumPostStatus 
        {
            /* 0 - approved, 1 - pending, 2 - spam, -1 - trash */
            [Display(Name = "تأیید شده")]
            Approved = 0,
            [Display(Name = "در انتظار بررسی")]
            Pending = 1,
            [Display(Name = "جفنگ")]
            Spam = 2,
            [Display(Name = "زباله دان")]
            Trash = -1
        }

    مدل بالا مشخص کننده‌ی پست‌هایی که در پاسخ به تاپیک‌ها ارسال می‌شوند، می‌باشد. ساختار درختی آن به منظور امکان پاسخ به پست‌ها در نظر گرفته شده است. در هر تاپیک چندین پست ارسال می‌شود که اولین پست ارسال شده، همان محتوای اصلی تاپیک می‌باشد. بدین منظور خصوصیت Topic را در مدل بالا تعریف کرده‌ایم. برای این پست‌های ارسالی امکان امتیاز دهی و اخطار دادن نیز خواهیم داشت که به ترتیب خصوصیات Rating و ReportsCount  (بحث شده در مقالات قبل) را در مدل بالا تعریف کرده‌ایم. خصوصیت Status به منظور اعمال مدیریتی در نظر گرفته شده است که از نوع ForumPostStatus می‌باشد و در بالا تعریف آن نیز آمده است.

    نکته : خصوصیتی از نوع مدل Forum نیز در مدل بالا تعریف شده است. هدف از آن افزایش سرعت ویرایش خصوصیات ApprovedPostsCount و UnApprovedPostsCount موجود در مدل Forum می‌باشد. در واقع هنگام درج پست جدید یا حذف پستی و یا ... ، لازم است خصوصیات مذکور به روز شوند.

    علاوه بر این موارد ، لازم است تاریخچه‌ی تغییرات پست‌های ارسالی را هم نگهداری کرد تا در صورت نیاز به آنها استناد کنیم. از طرفی پست‌های ارسالی را می‌توان چندین بار ویرایش کرد. به همین دلیل خصوصیت Histories را که لیستی از مدل ForumPostHistory می‌باشد، در مدل بالا تعریف کرده‌ایم.

    مدل تاریخچه‌ی تغییرات پست

     /// <summary>
        /// Represents History Of Post's Updates
        /// </summary>
        public class ForumPostHistory 
        {
            #region Ctor
            /// <summary>
            /// create one instance of <see cref="ForumPostHistory"/>
            /// </summary>
            public ForumPostHistory()
            {
                Id = SequentialGuidGenerator.NewSequentialGuid();
                CreatedOn = DateTime.Now;
            }
            #endregion
    
            #region Properties
            /// <summary>
            /// gets or sets Identifier of this history
            /// </summary>
            public virtual Guid Id { get; set; }
            /// <summary>
            /// gets or sets Reason of  update
            /// </summary>
            public virtual string Reason { get; set; }
            /// <summary>
            /// gets or sets DateTime that this record added
            /// </summary>
            public virtual DateTime CreatedOn { get; set; }
            /// <summary>
            /// gets or sets body of this post
            /// </summary>
            public virtual string Body { get; set; }
            #endregion
            #region NavigationProperties
            /// <summary>
            /// gets or sets Post
            /// </summary>
            public virtual ForumPost Post { get; set; }
            /// <summary>
            /// gets or sets Id Of Post
            /// </summary>
            public virtual long PostId { get; set; }
            /// <summary>
            /// gets or sets User that modified this Record
            /// </summary>
            public virtual User Modifier { get; set; }
            /// <summary>
            /// gets or sets if of User that modified this Record
            /// </summary>
            public virtual long ModifierId { get; set; }
            #endregion
        }

    اگر خصوصیت ModifyLocked مربوط به مدل ForumPost که آن را از کلاس پایه AuditBaseEntity به ارث برده است، دارای مقدار true باشد، این امکان وجود خواهد داشت تا بتوان پست مورد نظر را ویرایش کرده و اطلاعات قبلی، در قالب یک رکورد در جدول حاصل از مدل بالا ثبت شوند.

    • Reason : دلیل این ویرایش به عمل آماده 
    • Body : محتوای پست یا تاپیک
    • Modifier : کاربر انجام دهنده‌ی این ویرایش 
    • CreatedOn : زمانی که این ویرایش انجام شده است
    مدل ردیابی انجمن ها
    در خیلی از انجمن‌ها حتما متوجه شده‌اید که لینک برخی از انجمن‌ها یا تاپیک‌های درج شده‌ی در آنها برای شما bold شده نشان داده می‌شود. در واقع، هدف مطلع کردن شما از اینکه در حال حاضر یکسری تاپیک یا پست در انجمن ثبت شده است که شما آنها را مشاهده نکرده‌اید.
     public class ForumTracker
        {
            #region Ctor
            /// <summary>
            /// create one instance of <see cref="ForumTracker"/>
            /// </summary>
            public ForumTracker()
            {
                LastMarkedOn = DateTime.Now;
            }
    
            #endregion
    
            #region Properties
            /// <summary>
            /// gets or sets DateTime Of Las Visit by User
            /// </summary>
            public virtual DateTime LastMarkedOn { get; set; }
    
            #endregion
    
            #region NavigationProperties
            /// <summary>
            /// gets or sets Forum that Tracked
            /// </summary>
            public virtual Forum Forum { get; set; }
            /// <summary>
            /// gets or sets Id of Forum tath Tracked
            /// </summary>
            public virtual long ForumId { get; set; }
            /// <summary>
            /// gets or sets User that tracked The forum
            /// </summary>
            public virtual User Tracker { get; set; }
            /// <summary>
            /// gets or sets Id Of User that Tracked the forum
            /// </summary>
            public virtual long TrackerId { get; set; }
            #endregion
        }
    
    
       public class ForumTopicTracker
          {
            #region Ctor
            /// <summary>
            /// create one instance of <see cref="ForumTopicTracker"/>
            /// </summary>
            public ForumTopicTracker()
            {
                LastVisitedOn = DateTime.Now;
            }
    
            #endregion
    
            #region Properties
            /// <summary>
            /// gets or sets DateTime Of Las Visit by User
            /// </summary>
            public virtual DateTime LastVisitedOn { get; set; }
    
            #endregion
    
            #region NavigationProperties
            /// <summary>
            /// gets or sets topc that Tracked
            /// </summary>
            public virtual ForumTopic Topic { get; set; }
            /// <summary>
            /// gets or sets Id of topic that Tracked
            /// </summary>
            public virtual long TopicId { get; set; }
            /// <summary>
            /// gets or sets User that tracked The topic
            /// </summary>
            public virtual User Tracker { get; set; }
            /// <summary>
            /// gets or sets Id Of User that Tracked the topic
            /// </summary>
            public virtual long TrackerId { get; set; }
            /// <summary>
            /// gets or sets Forum 
            /// </summary>
            public virtual Forum Forum { get; set; }
            /// <summary>
            /// gets or sets Identifier of Forum . used for delete 
            /// </summary>
            public virtual long ForumId { get; set; }
    
            #endregion
          }
    در سیستم، برای کاربران احراز هویت شده، این امکان را مهیا ساخته‌ایم تا انجمن‌ها و تایپک‌هایی که پست جدید ارسال شده دارند و توسط کاربر خوانده نشده است، به نحوی متمایز نشان داده شوند. 
    برای این منظور از دو مدل پیاده سازی شده‌ی در بالا و یک خصوصیت از نوع تاریخ تحت عنوان LastMarkedOn در مدل User، استفاده خواهیم کرد. در واقع از LastMarkedOn مدل User، برای نگه داری آخرین تاریخی استفاده می‌شود که کاربر تمام انجمن‌ها را خوانده شده علامت گذاری کرده است. در این صورت می‌توان تمام رکورد‌های ذخیره شده‌ی در جداول ForumTrackers و ForumTopicTrackers را که قبل از این تاریخ هستند، حذف کرد. از LastMarkedOn مدل ForumTracker هم برای نگهداری تاریخی استفاده می‌شود که یک انجمن خاص را خوانده شده علامت گذاری کرده است و همچنین می‌توان تمام رکورد‌های مربوط به آن انجمن را در جدول ForumTopicTrackers حذف کرد.

    از مدل ForumTopicTracker هم برای مشخص کردن اینکه کاربر کدام تاپیک را و در چه تاریخی آخرین بار مشاهده کرده است، کمک می‌گیریم. برای این منظور از خصوصیت LastVisitedOn استفاده می‌شود.

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

    این قسمت از کار کمی پیچیده است و برای خودم نیز چالش داشت. سعی کردم انجمن‌های سورس باز PHP را بررسی کنم تا در نهایت به تحلیل بالا دست یافتم. مدل‌های ارائه شده انجمن تا این قسمت، نیازهای مورد نظر ما را برآورده خواهند کرد.

    مدل سیستم نظرسنجی
    public class Poll : BaseContent
        {
            #region Ctor
            /// <summary>
            /// create one instance of <see cref="Poll"/>
            /// </summary>
            public Poll()
            {
                Rating = new Rating();
                PublishedOn = DateTime.Now;
            }
            #endregion
    
            #region Properties
            /// <summary>
            /// gets or set Date that this Poll will Expire
            /// </summary>
            public virtual DateTime? ExpireOn { get; set; }
            /// <summary>
            ///indicating this poll allow to select multi item
            /// </summary>
            public virtual bool IsMultiSelect { get; set; }
            /// <summary>
            /// gets or sets Count of this poll's votes 
            /// </summary>
            public virtual long VotesCount { get; set; }
            /// <summary>
            /// indicate this Poll is approved by admin if Poll.Moderate==true
            /// </summary>
            public virtual bool IsApproved { get; set; }
    
            #endregion
    
            #region NavigationProperties
            /// <summary>
            /// get or set comments of this poll
            /// </summary>
            public virtual ICollection<PollComment> Comments { get; set; }
            /// <summary>
            /// get or set Options Of Poll For selection
            /// </summary>
            public virtual ICollection<PollOption> Options { get; set; }
            /// <summary>
            /// get or set Users List That vote for this poll
            /// </summary>
            public virtual ICollection<User> Voters { get; set; }
            #endregion
        }
    مدل بالا مشخص کننده‌ی نظرسنجی‌های سیستم ما می‌باشد. این مدل نیز از کلاس پایه مطرح شده در مقاله اولارث بری کرده است و علاوه بر آن یکسری خصوصیت دیگر را به شرح زیر دارد:
    • ExpireOn : زمان اتمام فرصت رای دهی که اگر نال باشد در آن صورت زمان انقضا نخواهد داشت.
    • IsMultiSelect : اگر انتخاب چندگزینه‌ای مجاز باشد، این خصوصیت، با مقدار true مقدار دهی می‌شود.
    • VotesCount : به منظور افزایش کارآیی در نظر گرفته شده است و تعداد کل رای‌های داده شده‌ی به نظرسنجی را در بر می‌گیرد.
    • Voters : برای جلوگیری از رای دهی چند باره‌ی کاربر به یک نظرسنجی، یک ارتباط چند به چند بین کاربر و نظرسنجی برقرار کرده‌ایم. هر کاربر به چند نظر سنجی می‌تواند پاسخ دهد و به هر نظرسنجی توسط چندین کاربر رای داده می‌شود.
    • PollOptions : هر نظر سنجی تعدادی گزینه‌ی انتخابی هم خواهد داشت که برای همین منظور و اعمال ارتباط یک به چند بین نظرسنجی و گزینه‌های انتخابی، لیستی از PollOption را در مدل بالا تعریف کرده‌ایم.
      مدل گزینه‌های نظرسنجی
       public class PollOption
          {
              #region Properties
              /// <summary>
              /// gets or sets identifier of this polloption
              /// </summary>
              public virtual long Id { get; set; }
              /// <summary>
              /// gets or sets Title of this polloption
              /// </summary>
              public virtual string Title { get; set; }
              /// <summary>
              /// gets or sets count of votes 
              /// </summary>
              public virtual long VotesCount { get; set; }
              /// <summary>
              /// gets or sets Description of this Option for more details
              /// </summary>
              public virtual string Description { get; set; }
              #endregion
      
              #region NavigationProperties
              /// <summary>
              /// gets or sets the poll that assosiated with this Polloption
              /// </summary>
              public virtual Poll Poll { get; set; }
              /// <summary>
              /// gets or sets the id of poll that assosiated with this Polloption
              /// </summary>
              public virtual long PollId { get; set; }
              #endregion
          }
      مدل بالا نشان دهنده‌ی گزینه‌های انتخابی در هر نظر سنجی می‌باشد. خصوصیت Poll و به دنبال آن PollId به منظور اعمال ارتباط یک به چند بین نظرسنجی و گزینه‌ها در نظر گرفته شده‌اند. 
      • Title: عنوان گزینه‌ی مورد نظر
      • Description: توضیح بیشتر برای گزینه‌ی مورد نظر
      • VotesCount: تعداد باری که یک گزینه در نظر سنجی انتخاب شده است.
      در این سیستم نیازی نیست که بدانیم چه کاربرانی در یک نظر سنجی کدام گزینه را انتخاب کرده‌اند و لذا مدل بالا برای کار ما کافی است.
      مدل نظرات سیستم نظرسنجی
       public class PollComment : BaseComment
          {
              #region Ctor
              public PollComment()
              {
                  CreatedOn = DateTime.Now;
                  Rating = new Rating();
              }
              #endregion
      
              #region NavigationProperties
      
              /// <summary>
              /// gets or sets body of blog poll's comment
              /// </summary>
              public virtual long? ReplyId { get; set; }
              /// <summary>
              /// gets or sets body of blog poll's comment
              /// </summary>
              public virtual PollComment Reply { get; set; }
              /// <summary>
              /// gets or sets body of blog poll's comment
              /// </summary>
              public virtual ICollection<PollComment> Children { get; set; }
              /// <summary>
              /// gets or sets poll that this comment sent to it
              /// </summary>
              public virtual Poll Poll { get; set; }
              /// <summary>
              /// gets or sets poll'Id that this comment sent to it
              /// </summary>
              public virtual long PollId { get; set; }
              #endregion
          }
      مدل بالا نیز از کلاس پایه‌ی BaseComment مورد بحث در مقاله‌ی اول  ارث بری کرده است و ساختار درختی آن نیز مشخص است و همچنین یک ارتباط یک به چند بین نظرسنجی‌ها و نظرات وجود خواهد داشت که برای این منظور خصوصیت Poll را در مدل بالا تعریف کرده‌ایم.

      در مقاله‌ی بعد به بررسی سیستم پیام رسانی و همچنین بخشی از سیستم تحت عنوان Collections (امکان ساخت گروه‌های شخصی برای انتشار مطالب خود (توسط کاربران) با اعمال دسترسی‌های مختلف) خواهیم پرداخت.

      نتیجه تا این قسمت

        ‫برنامه نویسی اندروید با Xamarin.Android - قسمت اول

        $
        0
        0
        وقتی صحبت از ساخت برنامه‌های کاربردی iOS و Android می‌شود، بسیاری از افراد تنها گزینه را Objective-C یا Java می‌دانند. اما در این چند سال اکوسیستم‌هایی (مجموعه ای از ابزارها) برای ایجاد برنامه‌های کاربردی موبایل ظهور کرده‌اند و البته تمرکز آن‌ها بر روی Cross Platform بودن آن‌ها بوده است. هر کدام از آن‌ها قابلیت‌هایی را برای ما به ارمغان می‌آورند. البته بعضی فقط به ما امکان نوشتن کدهای Html و Java Script را می‌دهند و برخی دیگر از کدهای C++/C که کدهای low-level هستند، استفاده می‌کنند.
        ما در اینجا قصد معرفی Xamarin را داریم. تنها پلتفرمی که تمام امکانات بومی موبایل را به همراه امکانات بسیار دیگری، برای ما فراهم می‌کند. این امکانات شامل موارد ذیل هستند:
        1- اتصال کامل به SDK بومی: Xamarin شامل اتصالاتی برای استفاده از تمامی (تقریبا) امکانات iOS و Android می‌باشد. این اتصالات به صورت Strongly-typed هستند. به این معنا که برای بررسی و استفاده، آسان هست و همچنین در حین توسعه و کامپایل به خوبی صحت کد‌ها را چک می‌کند.
        2- قابلیت ارتباط با Objective-C،Java، C،C :زامارین امکاناتی را برای فراخوانی مستقیم کتابخانه‌هایی که با Objective-C، Java، C و ++C نوشته شده‌اند، نیز فراهم کرده است. این یک امکان فوق العاده هست که شما بتوانید از تعداد بسیار زیاد کتابخانه‌های نوشته شده برای iOS و Android استفاده کنید.
        3- استفاده از زبان مدرن #C:برنامه‌های Xamarin با #C نوشه می‌شوند که بهبود‌های قابل توجهی نسبت به زبان‌های Objective-C و Java داشته است. امکاناتی مانند عبارات لامبدا، LINQ، برنامه نویسی موازی و ....
        4- مجموعه کلاس‌های فوق العاده:برنامه‌های Xamarin از Net BCL. که مجموعه‌ای عظیم و جامع از ویژگی‌های قدرتمند، مانند استفاده از XML، بانک اطلاعاتی، شبکه، IO و ...است، استفاده می‌کند که امکانات فوق العاده‌ای را برای توسعه دهندگان فراهم می‌نماید.
        5- استفاد ه از یک IDE قدرتمند: برای Mac OS X شما Xamarin Studio  و برای ویندوز Xamarin Studio و Visual Studio را در اختیار دارید که برای یک توسعه دهنده‌ی نرم افزار چیزی را کم نگذاشته‌اند.
        6- Cross Platform بودن: Xamarin برای سه پلتفرم مطرح موبایل، شامل iOS، Android و Windows Phone قابل استفاده می‌باشد و تقریبا 90 درصد از کدهای شما قابل استفاده‌ی مجدد در هر سه پلتفرم می‌باشد.
        البته با ارائه‌ی Xamarin.Forms این میزان به 100درصد رسیده است!

        نحوه‌ی نصب Xamarin:
        می‌توانید Xamarin Studio و Xamarin For Visual Studio را از سایت Xamarinدانلود نموده و به راحتی نصب نمایید. برای آنکه بتوانید Xamarin را نصب و استفاده نمایید، لازم است که موارد زیر را نیز به روی سیستم خود داشته باشید:
        1- Android SDK  
        2- GTK#
        3- Android NDK
        4- Java SDK(JDK)
        هر آنچه را که برای ادامه‌ی مسیر با ما لازم دارید، از ehsanavr.comدانلود نمایید.
         و البته نحوه‌ی نصب Xamarin به صورت کامل و همراه با تصاویر مربوطه نیز در آدرس زیر وجود دارد:

        Emulator یا شبیه ساز اندروید: Xamarin یک شبیه ساز بسیار عالی برای تست برنامه‌های اندرویدی در اختیار ما قرار داده است که از Virtual Box استفاده می‌کند. می‌توانید این نرم افزار را با نام Xamarin Android Player از اینجا دانلود نمایید. بعد از نصب و اجرای آن شما باید Imageهای مربوط به هر نسخه‌ای را که میخواهید، دانلود کنید:

        Xamarin Android Player


          
        کمی درباره سطوح مختلف APIهای اندروید:
        اندروید برای تشخیص سازگاری برنامه‌های اندروید، از سطوح مختلف APIها(API Levels) استفاده می‌کند. هر سطح از این APIها یک ورژن از اندروید را شامل می‌شوند. برای مثال Marshmallow که به اندروید 6 معروف می‌باشد، از API Level شماره 23 بهره می‌برد و Lollipop نسخه‌ی 5، شامل API Level شماره 21 و Lollipop 5.1 شامل API Level شماره 22 می‌باشد و الی آخر.
        اهمیت دانستن این موضوع، به این دلیل می‌باشد که انتخاب API Level مناسب، ارتباط مستقیمی با موبایل هایی دارد که می‌توانند برنامه‌ی شما را اجرا کنند. می‌توانید لیست کامل API‌های موجود را از اینجا مشاهده نمایید:

        برای هر برنامه‌ی اندروید نوشته شده، 3 تنظیم برای SDK مورد استفاده قرار می‌گیرد:
        Target Framework: مشخص کننده‌ی نوع فریموورکی می‌باشد که برنامه با آن کامپایل می‌شود.
        Minimum Android Version: مشخص کننده‌ی قدیمی‌ترین نسخه‌ی اندرویدی می‌باشد که می‌خواهید برنامه‌ی شما روی آن اجرا شود. این API Level در زمان اجرا استفاده می‌شود.
        Target Android Version: نسخه‌ای را که برنامه‌ی شما بر روی آن اجرا می‌شود، مشخص می‌نماید. این API Level در زمان اجرا استفاده می‌شود. همیشه میزان این API Level باید برابر یا بیشتر از Target Framework باشد.
        البته معمولا این سه تنظیمات را روی یک API Level تنظیم می‌کنند.
        قبل از اینکه بخواهید API Level مورد نظر را انتخاب کنید، باید SDK مربوط به آن را دانلود و نصب نمایید. برای مدیریت نسخه‌های SDKهای نصب شده بر روی سیستم خود می‌توانید از Android SDK Manager که در فولدر SDK قرار دارد می‌توانید استفاده نمایید.


        کمی درباره‌ی معماری Xamarin:
        برنامه‌های نوشته شده در Xamarin.Android در محیط Mono اجرا می‌شوند و Mono در کنار ماشین مجازی زمان اجرای اندروید، اجرا می‌شود. این دو سیستم روی هسته‌ی لینوکس اجرا می‌شوند و APIهای مختلفی را در اختیار برنامه نویسان قرار می‌دهند. Mono با زبان C نوشته شده است. شما می‌توانید کلاس‌های NET. مانند: System، System.IO، System.Net را برای دسترسی به قابلیت‌های لینوکس مورد استفاده قرار بدهید.
        در اندروید، بیشتر قابلیت‌های سیستم مانند صدا، گرافیک، OpenGL و قابلیت‌های تلفن، مستقیم در دسترس برنامه‌های بومی(Native) نیستند. آن‌ها فقط از طریق APIهای Android Runtime Java در دسترس هستند که در فضای نام Java.* یا Android.* قرار داردند. تصویر زیر این توضیحات را به خوبی نشان می‌دهد.
         

        Xamarin

        توسعه دهندگان Xamarin.Android به امکانات مختلفی از سیستم عامل با فراخوانی API‌های NET. دسترسی دارند و همچنین کلاس‌های موجود در فضای نام Android، پُلی برای استفاده از API‌های اندروید توسط برنامه نویسان Xamarin می‌باشد.
        نکته‌ی مهم دیگر این است که Packageهای برنامه‌های نوشته شده با Xamarin ساختاری شبیه به برنامه‌های معمول اندرویدی دارد، البته همراه با موارد زیر:
        1- اسمبلی‌های برنامه (شامل IL)
        2- کتابخانه‌های بومی، که باید حتما برنامه‌های Xamarin.Android کتابخانه‌های زمان اجرای مناسب با معماری اندروید مانند:armeabi، armeabi-v7a، x86 را در اختیار داشته باشد.

        در بخش بعد اولین برنامه‌ی اندرویدی خود را با Xamarin اجرا می‌نماییم.

        ‫برنامه نویسی اندروید با Xamarin.Android - قسمت دوم

        $
        0
        0
        اولین برنامه‌ی Xamarin:
        پروژه‌ی جدیدی را در ویژوال استودیو از نوع Android(Blank) Project ایجاد نمایید. اگر در حال حاضر برنامه را اجرا نمایید، ویژوال استودیو شبیه ساز مورد نظر را اجرا می‌کند و بعد از آن Package برنامه‌ی شما را ساخته و برنامه را در شبیه ساز اجرا می‌کند (ما در قسمت قبل Xamarin Android Player را معرفی کردیم).
        بیایید یک نگاهی به Solution برنامه بیندازیم. برنامه از یک پروژه تشکیل شده است. پروژه شامل بخش‌های مختلفی می‌باشد.
        یکی از بخش‌های مهم آن، Properties می‌باشد که شامل چندین بخش می‌شود. قسمت Application در قسمت اول توضیح داده شد. قسمت‌های دیگر هم به مرور بررسی می‌شوند.
        فولدر Asset می‌تواند شامل فایل‌ها، فونت‌ها و هر چیزی که برنامه‌ی ما احتیاج دارد باشد (حتی دیتابیس).
        فولدر Resource که در زیر مجموعه‌ی آن:
        - فولدر drawable وجود دارد که حاوی آیکن‌ها و تصاویر و همچنین استایل‌های ما می‌باشد.
        - فولدر layout که  طرح های(layout) برنامه را شامل می‌شود.
        - فولدر value که شامل مجموعه‌ای از فایل‌های XML می‌باشد که می‌تواند شامل stringها، colorها و مقادیر عددی ثابت باشد.
        - فولدر menu که منوهای برنامه را در خود جای داده است.
        و البته منابع(Resources) دیگری که به صورت پیش فرض تعبیه شده‌اند و می‌توان از آن‌ها استفاده کرد مانند: anim، animator، color، raw، xml.
        Resourceها مزایای زیادی برای ما دارند! کدهای برنامه را از تصاویر، متن‌ها، آیکن‌ها، منوها و انیمیشن‌ها و ... جدا می‌کند و به راحتی پشتیبانی از تنظیمات مختلف دستگاه‌ها را برای ما فراهم می‌نماید. برای مثال بدون نیاز به کدنویسی می‌توانید با دستگاه‌های مختلفی از لحاظ سایز و Local و ... ارتباط برقرار نمایید.
        Resourceها به صورت static در اختیار کدهای برنامه قرار می‌گیرند و در زمان کامپایل چک می‌شوند و احتیاجی به اجرای برنامه برای اطمینان از صحت آن‌ها وجود ندارد.
         فایل Resource.Designer.cs که برای دسترسی از طریق کد به منابع تعبیه شده و به ازای هر یک از منابع مقداری را از طریق پروپرتی‌های static در اختیار ما قرار می‌دهد. شما به هیچ وجه آن را تغییر ندهید؛ اما اجازه‌ی مشاهده‌ی کلاس را دارید!
        public partial class Resource {
            public partial class Attribute
            {
            }
            public partial class Drawable {
                public const int Icon=0x7f020000;
            }
            public partial class Id
            {
                public const int Textview=0x7f050000;
            }
            public partial class Layout
            {
                public const int Main=0x7f030000;
            }
            public partial class String
            {
                public const int App_Name=0x7f040001;
                public const int Hello=0x7f040000;
            }
        }
        نحوه‌ی استفاده از Resourceها در کد به این صورت می‌باشد:
        @[<PackageName>.]Resource.<ResourceType>.<ResourceName>
        که از سمت چپ به ترتیب شامل:
        • نام Package که برای منابع پروژه‌ی جاری نیازی به ذکر آن نیست.
        • Resource که همیشه قید می‌شود.
        • <ResourceType> نوع منبع را مشخص می‌کند و می‌تواند Id، String، Color، Layout،Drawable و ... باشد.
        • <ResourceName> نام منبع را مشخص مینماید.

        برای استفاده‌ی از منابع در XML به صورت زیر عمل می‌کنیم:
        @[<PackageName>:]<ResourceType>/<ResourceName>
        به سلوشن برمی‌گردیم و به سراغ کلاس‌های Activity می‌رویم.
        Activityها اساس ساختمان برنامه‌های اندرویدی می‌باشند و در واقع Screen‌های ما هستند و در طول عمرشان (از ایجاد تا تخریب) شامل حالت‌های زیادی می‌باشند. نحوه‌ی اجرای برنامه‌ها در اندروید بسیار متفاوت است با برنامه‌های رایج. معمولا برای شروع یک برنامه، تابعی static با نام main وجود دارد که نقطه‌ی شروع برنامه می‌باشد. در اندروید هر کلاسی می‌تواند به عنوان نقطه‌ی شروع برنامه باشد. البته فقط یک Activity می‌تواند شروع کننده باشد. اما اگر برنامه crash کند و یا توسط اندروید متوقف شود، سیستم عامل نیز می‌تواند از همان نقطه‌ی توقف و یا هر نقطه‌ی دیگری برنامه را دوباره اجرا نماید. 
        Activityها برای هر حالت دارای یک متد هستند و به اندروید کمک می‌کنند تا Activityهایی را که زمان زیادی مورد استفاده قرار نگرفته‌اند، تشخیص داده و حافظه و منابع را مدیریت کند. در شکل زیر حالت‌های مختلف یک Activity را می‌توانید مشاهده نمایید.

        برای مدیریت این حالت‌ها برای Activity‌ها، متدهایی نیز تعبیه شده‌است که ما با استفاده از آن‌ها می‌توانیم در هر حالتی از Activity، تصمیمات لازم را اتخاذ کنیم. برای استفاده از این متد‌ها شما باید آن‌ها را داخل Activity خود override نمایید. متدهای موجود به قرار زیر است:

        OnCreate:اولین متدی می‌باشد که موقع ایجاد Activity فراخوانی می‌شود. این متد همیشه برای مقداردهی اولیه overrideمی‌شود. شما می‌توانید برای ساخت ویوها، مقداردهی متغیرها و همچنین مقداردهی لیست‌ها از آن استفاده نمایید. آرگومان ورودی این متد (bundle) از نوع کلاس Bundle می‌باشد و در صورتی که null نباشد، یعنی برنامه Restart شده (با توجه به تصویر حالت‌های مختلف Activity) و اگر null باشد، یعنی برنامه شروع به کار نموده است. از این bundle که در واقع یک دیکشنری است می‌توان برای نگهداری حالت‌های برنامه استفاده نمود.
        OnStart:همیشه بعد از OnCreateاجرا می‌شود و برای انجام کارهایی که لازم داریم قبل از نمایش Activity به کاربر مورد استفاده قرار می‌گیرد.
        OnResume:بعد از نمایش Activity به کاربر این متد اجرا می‌شود. از این متد می‌توان برای اجرای انیمیشن‌ها، گوش دادن به بروزرسانی‌های GPS، نمایش پیغام و ... در ابتدای نمایش Activity استفاده کرد.
        OnPause: این متد موقعی اجرا می‌شود که برنامه به Background برود. شما اگر می‌خواهید کارهایی مانند:
        • آزادسازی منابع
        • بستن دیالوگ‌های باز شده!
        • ذخیره سازی اطلاعات تایید نشده
        • توقف انیمیشن‌ها و ...
        را انجام دهید باید این متد را overrideنمایید. نکته‌ی مهم این است که یکی از دو متد OnResumeو OnStopامکان دارد بعد از این متد اجرا شوند (به همین دلیل یکی از مهمترین متدهای برنامه، OnResumeمی‌باشد).
        OnStop:زمانیکه یک Activity، دیگر به کاربر نمایش داده نمی‌شود، این متد اجرا می‌شود و در یکی از حالت‌ها زیر این اتفاق می‌افتد:
        • یک Activity جدید اجرا شود.
        • فعالیت یک Activity که قبلا اجرا شده، ادامه پیدا کند.
        • برنامه متوقف گردد.
        ممکن است در بعضی مواقع به دلیل کمبود حافظه این متد اجرا نشود.
        OnDestroy:زمانی که برنامه کاملا از حافظه پاک شود، این متد اجرا می‌گردد.
        OnRestart:این متد بعد از Stop شدن یک Activity و قبل از Start دوباره‌ی آن اجرا می‌شود.

        خوب! به سراغ متد OnCreate داخل Activity که به صورت اتوماتیک ایجاد شده می‌رویم. در جاوا باید تمام Activityها را در Manifest معرفی نماییم و نقطه‌ی شروع را مشخص کنیم. اما همیشه در سی شارپ کار برای ما راحت‌تر بوده است! نحوه‌ی کار به این صورت است که با اجرای برنامه‌ی ما، آن کلاسی که از Activityارث برده باشد و باActivityAttributeبا مقدار ورودی MainLauncher = true مزین شده باشد، اجرا می‌شود.
        با استفاده از SetContentView می‌توانیم یک ویو (View) را برای نحوه‌ی نمایش Activity مشخص کنیم و باید از قبل ویو را در پوشه‌ی Layout ساخته باشیم که البته این کار بصورت اتوماتیک انجام شده است.

        ‫نگاهی به مزایا و معایب Xamarin.Android

        $
        0
        0
        حجم Package نهایی Xamarin.Android:
        Xamarin هنگام ایجاد Package برنامه، روش‌های مختلفی را برای کاهش حجم آن به کار می‌برد که البته این روش‌ها همراه با حفظ کارآیی برنامه در حالت‌های Debug و Release می‌باشد.
        یک برنامه‌ی Xamarin برای اجرا باید شامل: برنامه‌ی ما، کتابحانه‌های ارتباطی، محتویات، Mono runtime، اسمبلی‌های (BCL(Base Class Library باشد. برای مثال اگر شما همان مثال پیش فرض Hello work را که با ساخت Solution جدید ایجاد می‌شود، در نظر بگیرید، Package کامل آن بعد از ایجاد (build) به صورت زیر است:



        واقعیت این هست که برای چنین برنامه‌ی کوچکی، 15مگابایت حجم زیادی به حساب می‌آید. بیشتر این حجم به دلیل کتابخانه‌ی کلاس‌های پایه (BCL) می‌باشد که شامل mscorlib.lib، system و Mono.Android هستند. این کلاس‌ها کامپوننت‌هایی را که برنامه‌ی ما برای اجرا به آن‌ها احتیاج دارند، فراهم می‌کنند. البته واقعیت این است که برنامه‌ی ما از تمام این امکانات استفاده نمی‌کند و می‌توان از خیلی از آن‌ها صرف نظر کرد.

        وقتی شما برنامه‌ای را برای توزیع آماده می‌کنید، Xamarin پروسه‌ای را که به Linking معروف است، اجرا می‌کند. در این پروسه کدهایی که استفاده نشده‌اند حذف می‌شوند. به این ترتیب حجم کدهای برنامه را کاهش می‌دهند. در واقع بخش‌هایی از BCL را که استفاده نکرده‌ایم از Package نهایی حذف می‌کند. برای مثال پروژه‌ی "Hello Word"را در نظر بگیرید (پروژه‌ی پیش فرض). به دلیل آنکه ما از کلاس‌های خاصی استفاده نکرده‌ایم، مقدار زیادی از کدهای بلااستفاده‌ی BCL حذف می‌شوند. تصویر زیر حجم برنامه را مشخص می‌کند:



        چه زمانی از Xamarin.Forms استفاده کنیم:

        یکی از راه‌های ایجاد برنامه‌های بومی برای اندروید و iOS، استفاده از Xamarn.Android و Xamarin.iOS است. راه دیگر آن Xamarin.Forms است که بیشترین قابلیت اشتراک UI را دارا می‌باشد. در Xamarin.Forms ما می‌توانیم از XAML برای ایجاد UI استفاده کنیم. اما کی بهتر است از آن استفاده کنیم و چه وقت خوب نیست؟

        مواردی که بهتر است از Xamarin.Forms استفاده کنیم:

        • برنامه‌های ورود اطلاعات (ِData Entry)
        • ایجاد نمونه‌های اولیه
        • برنامه‌هایی که به بازه‌ی وسیعی از قابلیت‌های بومی دستگاه مورد نظر احتیاج ندارد.
        • برنامه‌هایی که اشتراک کد برای ما مهمتر از نمای ظاهری و زیبایی برنامه باشد.


        مواردی که بهتر است از Xamarin.Forms استفاده نکنیم

        • برنامه هایی که تعامل زیادی با کاربر دارد.
        • تهیه‌ی برنامه‌هایی با ظاهر بسیار زیبا و پر رنگ و لعاب!
        • برنامه‌هایی که نیاز به استفاده‌ی از بازه‌ی وسیعی از API‌های بومی را دارند.
        • برنامه هایی که در آن‌ها UIهای سفارشی مهم‌تر از اشتراک کد می‌باشند.
        Viewing all 1980 articles
        Browse latest View live


        <script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>