آشنایی با Backbone
آشنایی با چارچوب جاوا اسکریپت Backbone.JS
Backbone.js در یک جمله
یک کتابخانه کوچک جاوا اسکریپت که کدهای پیچیده و شلوغ جاوا اسکریپت را ساختار میدهد و خوانایی و قابلیت استفاده مجدد را در آن بالا میبرد.
چارچوب Backbone تلاش میکند که قسمتهای قابل نمایش و قسمتهای مربوط به داده را در کدهای جاوا اسکریپت از هم جدا کند. به قسمتهای قابل نمایش View و به قسمتهای مربوط به داده، Model میگوییم. Collection ها مجموعهای از Model ها هستند و میتوانند توسط View جداگانه نمایش داده شوند. Router ها وظیفه مدیریت URL ها و Action های متناظر با آنها را بر عهده دارند. در این مقاله Router ها توضیح داده نمیشوند و علاقهمندان میتوانند به آدرس http://backbonejs.org/#Router مراجعه نمایند.
بخش اول: View ها
مقداردهی اولیه
اگر با برنامهنویسی شیءگرا آشنا باشید، حتماً تعبیر تابع سازنده (Constructor) را شنیدهاید. هدف از نوشتن تابع سازنده، انجام کارهایی است که میخواهید هنگام ساخته شدن شیء ای از یک کلاس، انجام شود. برای تعریف یک View و نوشتن تابع سازنده برای آن، از روش زیر استفاده میکنیم:
SearchView = Backbone.View.extend({
initialize: function(){
alert("Alerts suck.");
}
});
همان طور که ملاحظه میکنید، یک View با نام SearchView ایجاد کردهایم و در تابع سازنده آن پیغامی را به کاربر نشان میدهیم. بنابراین، هر وقت شیء ای از این View ساخته شد، پیغام موردنظر به کاربر نمایش داده میشود.
تعیین المان DOM
هر View علاوه بر اینکه یک شیء جاوا اسکریپت است، با یکی از المانهای صفحه (DOM) نیز تناظر دارد. بنابراین هنگام ساختن یک View باید بگوییم که این View متناظر با کدام یک از المانهای صفحه است. اگر میخواهیم یک برای این View یک المان جدید ایجاد کنیم که هنوز در صفحه موجود نیست، باید به شکل زیر، برای آن تگ HTML تعریف کنیم:
SearchView = Backbone.View.extend({
initialize: function(){…},
tagName:’ul’,
id: ‘searchPanel’
});
بنابراین، محتوای View ساخته شده در مثال بالا درون یک تگ UL قرار میگیرد. (که پس از رندر شدن میتواند به صفحه اضافه شود.)
اگر بخواهیم View را با یکی از المانهای موجود در صفحه تناظر دهیم، میتوانیم از روش زیر استفاده کنیم:
var search_view = new SearchView({ el: $("#search_container") });
که با JQuery المان با شناسه search_container را در صفحه انتخاب میکند و آن را به SearchView تناظر میدهد. همچنین میتوانیم المان یک View از قبل ساخته شده را به روش زیر تعیین کنیم:
var searchView = new SearchView();
var button1 = $('<button></button>');
view.setElement(button2);
دسترسی به المان View
برای دسترسی به المان یک View میتوانیم از روش زیر استفاده کنیم:
var searchView = new SearchView();
console.log(searchView.el);
رندر کردن
برای این که تعیین کنیم هر View در صفحه چگونه نمایش داده شود، باید تابع render را برای آن تعریف کنیم. معمولاً کدهای نوشته شده در تابع رندر، جنبه نمایشی دارند. (با المانهای درون View سر و کار دارند.)
SearchView = Backbone.View.extend({
initialize: function(){
alert("Alerts suck.");
},
render: function(){
var content=$(‘<div></div>’);
$(content).append($(‘<label>Search</label>’));
$(content).append($(‘<input type="text" id="search_input" />’));
$(content).append($(‘<input type="button" id="search_button" value="Search" />’));
this.el.html(content);
}
});
مشاهده میکنید که در تابع render یک المان div تعریف شده و المانهای دیگری در آن قرار گرفته است. در نهایت المان View جاری به همان المان div تغییر یافته است. پیشنهاد میشود که در تابع رندر، مقدار this.el را برگردانید تا بلافاصله پس از رندر، بتوانید از المان جدید استفاده کنید.
رندر کردن با استفاده از قالب
همان طور که در مثال قبل مشاهده کردید، دستوراتی که در تابع رندر نوشته میشود بیشتر با المانهای HTML سر و کار دارد. بهتر است که قسمتهای مربوط به طراحی از اسکریپتها جدا شوند تا خوانایی کد بالا رود. قالبها (Template ها) این کار را برای ما انجام میدهند. موتورهای قالب (Template Engine) مختلفی برای جاوا اسکریپت ارائه شدهاند که از جمله آنها میتوان به Mustache، Underscore، Embedded JS، HandlebarsJS و Jade Templating اشاره کرد. به دلیل سازگاری بالایی که Backbone با Underscore دارد، معمولاً از موتور قالب آن استفاده میشود. لازم به ذکر است که Underscore علاوه بر موتور قالب، قابلیتهای دیگری را نیز فراهم میکند که کار برنامه نویسان را بسیار ساده میکند. در ادامه مثالی از طراحی View با موتور قالب Underscore را مشاهده میکنید:
<script type="text/template" id=" search_template ">
<label><%= search_label %></label>
<input type="text" id="search_input" />
<input type="button" id="search_button“ value="Search" />
</script>
SearchView = Backbone.View.extend({
initialize: function(){
alert("Alerts suck.");
},
render: function(){
var template = _.template( $("#search_template").html(), {search_label: "Search"} );
this.el.html( template );
}
});
در مثال فوق، ابتدا یک قالب با شناسه search_template تعریف کردهایم. پارامترهایی را که در هنگام ترجمه باید مقدار بگیرند با استفاده از <%= ParameterName%> مشخص کردهایم. سپس در تابغ render با استفاده از underscore این قالب را برای لیست خاصی از پارامترها ترجمه کردهایم و المان View را به HTML تولید شده، تغییر دادهایم.
رندر کردن View های تو در تو
تصویر کنید یک View برای نمایش کتاب داریم و یک View جداگانه برای کتابخانه. هر کتابخانه ممکن است شامل چندین کتاب باشد. برای نمایش کتابخانه کافیاست تمام کتابهای موجود در کتابخانه را رندر کنیم. مثال زیر روش پیادهسازی را برای چنین سناریوهایی شرح میدهد:
var ItemView = Backbone.View.extend({
events: {},
render: function(){
this.$el.html(this.model.toJSON());
return this;
}
});
var ListView = Backbone.View.extend({
render: function(){var items = this.model.get('items');
_.each(items, function(item){
var itemView = new ItemView({ model: item });
this.$el.append( itemView.render().el );
}, this);
}
});
تعیین رخدادها
تعیین رخدادها برای المانهای موجود در یک View بسیار ساده است. مثال زیر، روش پیادهسازی را شرح میدهد:
SearchView = Backbone.View.extend({
initialize: function(){…},
render: function(){…},
events: {
"click input[type=button]": "doSearch"
},
doSearch: function( event ){
alert( "Search for " + $("#search_input").val() );
}
});
همان طور که مشاهده میکنید، اگر روی هر یک از دکمههای موجود در View رخداد click اتفاق افتد، تابع doSearch فراخوانی میشود.
تعریف رخداد دلخواه
گاهی اوقات لازم است رخداد دلخواهی را خودمان فعال کنیم تا سایر View هایی که از View جاری استفاده میکنند، از وقوع این رخداد با خبر شوند. این روش برنامهنویسی ماژولار را ممکن میسازد. هر View کار خود را به شکل مستقل انجام میدهد بدون اینکه از وجود View های دیگر با خبر باشد. در حالتی که عملیات یک View وابسته به View دیگر است، کافی است به رخدادهای آن View گوش کند و هنگام فعال شدن یک رخداد از سوی آن View، عمل مناسب را انجام دهد. مثال زیر نمونهای از رخدادهای دلخواه را نشان میدهد:
var View = Backbone.View.extend({
el: '#todo',
events: {
'click [type="checkbox"]': 'clicked',
},
Initialize: function () {
this.$el.click(this.jqueryClicked);
this.on (' bizhan', this.callback);
},
clicked: function(event) {
console.log("events handler for " + this.el.outerHTML);
this.trigger(‘bizhan', event.type);
},
jqueryClicked: function(event) {
console.log("jQuery handler for " + this.outerHTML);
},
callback: function(eventType) {
console.log("event type was " + eventType);
}
});
مشاهده میکنید که در تابع clicked رخداد bizhan فراخوانی میشود و در تابع سازنده به این رخداد گوش داده میشود. در صورتی که این رخداد به وقوع پیوست، تابع callback فراخوانی میشود.
بخش دوم : مدلها
در فصل قبل با View ها آشنا شدیم. معمولاً هر View با یک یا چند مدل در ارتباط است و برای نمایش دادههای آن مدل تعریف میشود. اکنون باید بیاموزیم که Backbone چگونه با استفاده از مدل، دادهها را سازماندهی میکند؟
تابع سازنده
تعریف تابع سازنده برای یک مدل دقیقاً مشابه تابع سازنده View است. مثال زیر یک مدل را تعریف میکند و برای آن تابع سازنده را پیادهسازی میکند. سپس بیرون مدل، شیء ای از آن مدل ساخته شدهاست:
Person = Backbone.Model.extend({
initialize: function(){
alert("Welcome to this world");
}
});
var person = new Person;
تعیین مقدار ویژگیها
در مثال زیر روش مقداردهی ویژگیهای مدل، هنگام ساختن شیء ای از آن مدل و نیز بعد از ساختن مدل را مشاهده میکنید:
var person = new Person({ name: "Thomas", age: 67});
person.set({ name: "Thomas", age: 67});
گرفتن مقدار ویژگیها
گرفتن مقدار ویژگیها نیز با استفاده از تابع get ممکن است:
var age = person.get("age");
var name = person.get("name");
اگر بخواهیم تمام ویژگیهای یک مدل را با مقادیر متناظرشان دریافت کنیم میتوانیم از یکی از دو روش زیر استفاده کنیم:
JSON.stringify(person)
Person.toJSON()
تعیین مقادیر پیشفرض ویژگیها
ممکن است بخواهیم برای ویژگیهای مدل در هنگام ساختن، مقادیر پیشفرضی را تعریف کنیم. این کار هم با روش زیر به سادگی امکانپذیر است:
Person = Backbone.Model.extend({
defaults: {
name: 'Fetus',
age: 0,
children: []
},
initialize: function(){...}
});
کار با ویژگیها درون مدل
با استفاده از اشارهگر this میتوان به ویژگیهای مدل جاری دست یافت. مثال زیر، این فرایند را توضیح میدهد:
Person = Backbone.Model.extend({
adopt: function( newChildsName ){
var children_array = this.get("children");
children_array.push( newChildsName );
this.set({ children: children_array });
}
});
تعریف رخداد برای یک مدل
تغییرات درون مدل را میتوان با استفاده از تابع bind گوش داد و هنگام تغییر، تابع دلخواهی را فراخوانی کرد. مثلاً View متناظر با آن را به روز نمود:
Person = Backbone.Model.extend({
defaults: {
name: 'Fetus',
age: 0,
children: []
},
initialize: function(){
this.bind("change:name", function(){
var name = this.get("name");
alert("Changed my name to " + name );
});
},
});
در مثال فوق، وقتی که مشخصه name از مدل Person تغییر میکند (change) یک پیغام به کاربر نمایش داده میشود.
سوال: در این مثال اگر بخواهیم ویژگی name را تغییر دهیم به طوری که رخداد change فعال نشود، آیا راهی وجود دارد؟
پاسخ: بله، خیلی ساده هنگام تغییر مقدار ویژگی، آن را در حالت سکوت قرار میدهیم:
person.set({name: 'Jeremy'}, {silent: true});
اعتبارسنجی مقدار ویژگیها
چارچوب Backbone راهی را برای چک کردن مقدار ویژگیها گذاشته است. هدف از گذاشتن چنین قابلیتی، این است که مقادیر غیرمجاز به یک ویژگی غیرقابل تخصیص باشد. مثلاً سن یک فرد هیچگاه نمیتواند منفی باشد. بنابراین هنگامی که کاربر مقداری منفی را برای سن وارد میکند باید جلوی او گرفته شود. برای اعتبارسنجی ویژگیهای مدل، باید تابع validate را برای آن پیادهسازی کنیم. در صورتی که این تابع یک رشته تهی بازگرداند، به معنای صحت تمام ویژگیهای وارد شده است. در غیر این صورت رشته بازگشتی، پیغام خطایی است که به کاربر نمایش داده میشود. ( این پیغام را خود Backbone به صورت alert نمایش میدهد. البته که کار غیرمعقولی است!)
Person = Backbone.Model.extend({
validate: function( attributes ){
if( attributes.age < 0 && attributes.name != "Dr Manhatten" )
return "You can't be negative years old";
}
}
});
هنگام مقداردهی ویژگیها اگر بخواهیم اعتبارسنجی انجام شود، حتماً باید مشخصه validate آن را فعال کنیم:
person.set('age', -20, {validate: true});
بخش سوم : مجموعهها (Collections)
Collectionها در واقع مجموعهای از مدلها هستند. این مدلها میتوانند همنوع باشند و یا دارای انواع مختلفی باشند. مثلاً مجموعهای از طلاب، یک مدرس را تشکیل میدهند. میگوییم مدرس، یک collection از مدل طلبه است.
تعریف مجموعه
برای تعیین مدل یک مجموعه کافیاست مشخصه model آن را تعیین کنیم:
var Song = Backbone.Model.extend({
initialize: function(){
console.log("Music is the answer");
}
});
var Album = Backbone.Collection.extend({
model: Song
});
پر کردن مجموعه
برای پر کردن یک مجموعه کافی است مدلها را توسط آرایه به مجموعه بدهیم.
var song1 = new Song({ name: "How Bizarre", artist: "OMC" });
var song2 = new Song({ name: “Spiritual Healing", artist: "Marvin Gaye" });
var song3 = new Song({ name: "Talk It Over In Meshkat", artist: "OMC" });
var myAlbum = new Album([ song1, song2, song3]);
console.log( myAlbum.models ); // [song1, song2, song3]
همان طور که مشاهده میکنید با استفاده از مشخصه models به مدلهای درون یک مجموعه میتوان دست یافت.
اضافه و حذف کردن مدل
برای اضافه و حذف کردن مدل از دستورات add و remove روی آن مجموعه استفاده میکنیم:
var a = new Todo({ title: 'Go to Jamaica.'}),
b = new Todo({ title: 'Go to China.'}),
c = new Todo({ title: 'Go to Disneyland.'});
var todos = new TodosCollection([a,b]);
todos.add(c);
todos.remove([a,b]);
نکته جالب (ادغام دو عملیات درج)
var items = new Backbone.Collection;
items.add([{ id : 1, name: "Dog" , age: 3}]);
items.add([{ id : 1, name: "Bear" }], {merge: true });
آنچه که در مجموعه ذخیره میشود، به شکل زیر است:
[{"id":1,"name":"Bear","age":3}]
یعنی اگر در هنگام درج در مجموعه به مشخصه merge مقدار true دهیم، به شکل خودکار آن را با مدلهایی که شناسه یکسانی با این مدل دارند ادغام میکند. (Backbone به صورت پیشفرض مشخصه id را به عنوان شناسه مدل تلقی میکند.)
استخراج مدل خاص از مجموعه
استخراج مدل از مجموعه نیز بر مبنای شناسه (که به صورت پیشفرض مشخصه id است) انجام میگیرد. مثال زیر نحوه استخراج مدلی با شناسه خاص را از یک مجموعه نشان میدهد.
var myTodo = new Todo({title:'Read the whole book', id: 2});
var todos = new TodosCollection([myTodo]);
var todo2 = todos.get(2);
تعیین مشخصه دلخواه به عنوان شناسه مدلهای مجموعه
بسیاری از اوقات میخواهیم از همان مدلی که از سمت سرور آوردهایم به عنوان مدل Backbone استفاده کنیم. بنابراین شناسه مدلها هم باید همان شناسهای باشد که از سمت سرور آمده ( و به احتمال زیاد، کلید اصلی جدولی در دیتابیس بوده). پس باید بتوانیم آنچه را که Backbone به عنوان شناسه مدلهای موجود در مجموعه تلقی میکند تغییر دهیم. برای این کار کافی است مشخصه idAttribute را برای مجموعه تعیین کنیم:
todos.set(‘idAttribute’,’BookCommentId’)
رخدادها برای مجموعهها
هنگامی که تغییری مثل افزودن مدل، حذف مدل و یا ویرایش مدل در یک مجموعه صورت میگیرد، میتوان این تغییرات را به صورت رخداد دریافت کرد و سپس عملیات دلخواهی را در صورت بروز این رخدادها انجام داد.
TodosCollection.on("add", function(todo) {
...
});
TodosCollection.on("remove", function(todo) {
...
});
TodosCollection.on("change:title", function(model) {
...
});
به روزرسانی مجموعهها
برای تهی کردن یک مجموعه (حذف مدلهای درون مجموعه) از تابع reset استفاده میکنیم.
myCollection.reset();
اگر بخواهیم بعد از تهی کردن مجموعه، مدلهای جدیدی در آن ریخته شود، همان مدلها را به عنوان پارامتر به تابع میدهیم:
TodosCollection.reset([
{ title: 'go to Cuba.', completed: false }
]);
بخش چهارم : ارتباط مستقیم مدلها با سمت سرور (Restful Persistence)
تا اینجا با قابلیتهای اصلی Backbone آشنا شدیم و دانستیم چگونه دادهها را در قالب مدلها سازماندهی کنیم، از آنها یک مجموعه بسازیم و با بهرهگیری از Viewها بتوانیم دادهها را به شکل دلخواه روی مرورگر رندر نماییم. در این بخش یک قابلیت دیگر از Backbone را میآموزیم که امکان ذخیره تغییرات روی مدلها و مجموعهها را بر روی سرور فراهم میکند. تنها شرطی که سرویسهای سمت سرور باید تأمین نمایند، پشتیبانی از پروتکل REST است. سپس هر گونه تغییری نظیر درج، حذف و ویرایش بر روی مدلها را میتوان مستقیماً بر روی سرور انجام داد. (مثلاً تغییرات را در دیتابیس نیز اعمال کرد.)
تعیین آدرس سرویسها برای یک مجموعه
هنگام ساختن یک مجموعه، کافی است آدرس سرویس پیاده شده را در مشخصه url مجموعه وارد کنیم:
var TodosCollection = Backbone.Collection.extend({
model: Todo,
url: '/todos'
});
گرفتن مدلها از سمت سرور
برای گرفتن مدلهای یک مجموعه از سمت سرور کافیاست از تابع fetch استفاده کنیم:
var todos = new TodosCollection();
todos.fetch(); // sends HTTP GET to /todos
ساختن مدل جدید روی سرور
برای ساختن مدل جدید روی مجموعه به طوری که بر روی سرور هم عمل درج انجام پذیرد، از تابع create استفاده میکنیم.
todos.create({title: 'Try out code samples'});
ویرایش مدلها روی سرور
برای ویرایش مدلی از یک مجموعه که تغییرات آن روی سرور هم لحاظ شود از تابع save استفاده میکنیم:
var todo2 = todos.get(2);
todo2.set('title', 'go fishing');
todo2.save(); // sends HTTP PUT to /todos/2
حذف مدل از روی سرور
برای حذف مدلی از یک مجموعه که حذف آن بر روی سرور هم انجام شود، از تابع destroy استفاده میکنیم:
var todo2 = todos.get(2);
todo2.destroy(); // sends HTTP DELETE to /todos/2 and removes from collection
بخش پنجم : قابلیتهای Underscore.JS در کنار Backbone
در فصلهای گذشته با قابلیت ترجمه قالب توسط کتابخانه Underscore آشنا شدیم. اما این تنها یکی از قابلیتهای این کتابخانه است. Underscore سازگاری بالایی با مجموعههای Backbone دارد که برای پردازش مدلهای موجود در مجموعه میتواند بسیار مفید باشد. در این بخش با برخی از این قابلیتها آشنا میشویم.
پیمایش مدلهای مجموعه
بسیاری از زبانهای برنامهنویسی سطح بالا دستور foreach را برای پیمایش مدلهای یک لیست یا آرایه پیادهسازی کردهاند. Underscore نیاز این قابلیت را برای آرایههای جاوا اسکریپت فراهم کرده است. از آنجا که مجموعههای Backbone نیز نوعی آرایه هستند، این ابزار برای دستیابی به مدلهای مجموعه قابل استفاده است:
Todos.forEach(function(model){
console.log(model.get('title'));
});
در مثال فوق، روی تمام مدلهای موجود در مجموعه Todos پیمایشی صورت گرفته و مشخصه title مدلها بر روی کنسول نمایش داده شده است.
مرتبسازی مدلهای مجموعه
با استفاده از Underscore.js میتوان مدلهای یک مجموعه را بر اساس مشخصه خاصی از آن مدلها مرتب نمود.
var sortedByAlphabet = Todos.sortBy(function (todo) {
return todo.get("title").toLowerCase();
});
در مثال فوق، مدلهای موجود در مجموعه Todos بر اساس حروف کوچک مشخصه title مرتب شدهاند.
یافتن حداکثر و حداقل
با استفاده از توابع min و max میتوان به مقادیر حداقل و حداکثر در یک مجموعه دست یافت:
Todos.max(function(model){
return model.id;
}).id;
Todos.min(function(model){
return model.id;
}).id;
گرفتن لیست مقادیر یک مشخصه
یکی از قابلیتهای جالب Underscore ، امکان استخراج مقادیر یک مشخصه در کل مجموعه در قالب یک لیست است. تابع pluck این قابلیت را فراهم میکند:
var captions = Todos.pluck('caption');
// returns list of captions
گرفتن اندیس یک مدل
برای گرفتن اندیس یک مدل در یک مجموعه از تابع indexOf استفاده میشود:
People.indexOf(rob)
گرفتن اندازه یک مجموعه
Todos.size();
تست تهی بودن یک مجموعه
Todos.isEmpty()
در این مقاله به ارائه چارچوب جاوا اسکریپت Backbone پرداختیم و قابلیتهای مختلف آن را معرفی نمودیم. امید است که مورد استفاده علاقهمندان قرار گرفته باشد.
- ۹۳/۰۲/۲۸