دليل لبناء تطبيقك الأول من Ember.js
نشرت: 2022-03-11نظرًا لأن تطبيقات الويب الحديثة تفعل المزيد والمزيد من جانب العميل (حقيقة أننا نشير إليها الآن باسم "تطبيقات الويب" بدلاً من "مواقع الويب" أمر واضح تمامًا) ، فقد كان هناك اهتمام متزايد بإطارات العمل من جانب العميل . يوجد الكثير من اللاعبين في هذا المجال ولكن بالنسبة للتطبيقات التي تحتوي على الكثير من الوظائف والعديد من الأجزاء المتحركة ، يبرز اثنان منهم على وجه الخصوص: Angular.js و Ember.js.
لقد نشرنا بالفعل [برنامج تعليمي Angular.js] شاملًا [https://www.toptal.com/angular-js/a-step-by-step-guide-to-your-first-angularjs-app] ، لذلك نحن سنركز على Ember.js في هذا المنشور ، حيث سننشئ تطبيق Ember بسيطًا لفهرسة مجموعتك الموسيقية. ستتعرف على اللبنات الأساسية لإطار العمل وستحصل على لمحة عن مبادئ التصميم الخاصة به. إذا كنت تريد رؤية الكود المصدري أثناء القراءة ، فهو متاح كموسيقى الروك أند رول على جيثب.
ماذا سنبني؟
إليك ما سيبدو عليه تطبيق Rock & Roll في نسخته النهائية:
على اليسار ، سترى أن لدينا قائمة بالفنانين ، وعلى اليمين ، قائمة الأغاني للفنان المختار (يمكنك أيضًا أن ترى أن لدي ذوقًا جيدًا في الموسيقى ، لكني أستطرد). يمكن إضافة فنانين وأغاني جديدة ببساطة عن طريق الكتابة في مربع النص والضغط على الزر المجاور. تعمل النجوم بجانب كل أغنية على تقييمها ، على غرار iTunes.
يمكننا تقسيم وظائف التطبيق الأولية إلى الخطوات التالية:
- يؤدي النقر فوق "إضافة" إلى إضافة فنان جديد إلى القائمة ، باسم محدد بواسطة حقل "فنان جديد" (ينطبق الأمر نفسه على الأغاني الخاصة بفنان معين).
- يؤدي إفراغ حقل "فنان جديد" إلى تعطيل الزر "إضافة" (ينطبق الأمر نفسه على الأغاني الخاصة بفنان معين).
- النقر على اسم الفنان يسرد أغانيه على اليمين.
- النقر على النجوم يصنف أغنية معينة.
لدينا طريق طويل لنقطعه لإنجاح هذا الأمر ، فلنبدأ.
المسارات: مفتاح تطبيق Ember.js
تتمثل إحدى السمات المميزة لـ Ember في التركيز الشديد على عناوين URL. في العديد من الأطر الأخرى ، يكون وجود عناوين URL منفصلة لشاشات منفصلة إما غير موجود أو يتم اعتباره فكرة لاحقة. في Ember ، يعد جهاز التوجيه - المكون الذي يدير عناوين url والانتقالات بينها - هو القطعة المركزية التي تنسق العمل بين الكتل الإنشائية. وبالتالي ، فهو أيضًا المفتاح لفهم الأعمال الداخلية لتطبيقات Ember.
فيما يلي طرق تطبيقنا:
App.Router.map(function() { this.resource('artists', function() { this.route('songs', { path: ':slug' }); }); });
نحدد مسار الموارد artists
وطريق songs
المتداخلة بداخله. سيعطينا هذا التعريف المسارات التالية:
لقد استخدمت المكون الإضافي Ember Inspector الرائع (وهو موجود لكل من Chrome و Firefox) لتظهر لك المسارات التي تم إنشاؤها بطريقة يمكن قراءتها بسهولة. فيما يلي القواعد الأساسية لمسارات Ember ، والتي يمكنك التحقق منها في حالتنا الخاصة بمساعدة الجدول أعلاه:
يوجد مسار
application
ضمني.يتم تفعيل هذا لجميع الطلبات (الانتقالات).
هناك مسار
index
ضمني.يتم إدخال هذا عندما ينتقل المستخدم إلى جذر التطبيق.
ينشئ كل مسار مورد مسارًا يحمل نفس الاسم وينشئ ضمنيًا مسار فهرس تحته.
يتم تنشيط مسار الفهرس هذا عندما ينتقل المستخدم إلى المسار. في حالتنا ، يتم تشغيل
artists.index
عندما ينتقل المستخدم إلى/artists
.المسار البسيط (غير المورد) والمتداخل سيكون له اسم المسار الأصلي كبادئة له.
المسار الذي حددناه على أنه
this.route('songs', ...)
سيكون له أسماءartists.songs
. يتم تشغيله عندما ينتقل المستخدم إلى/artists/pearl-jam
أو/artists/radiohead
.إذا لم يتم إعطاء المسار ، فمن المفترض أن يكون مساويًا لاسم المسار.
إذا احتوى المسار على
:
، فيُعتبر مقطعًا ديناميكيًا .الاسم المخصص لها (في حالتنا ،
slug
) سيتطابق مع القيمة الموجودة في المقطع المناسب من عنوان url. سيكون لشريحةslug
أعلاه قيمةpearl-jam
radiohead
أو أي قيمة أخرى تم استخلاصها من عنوان URL.
اعرض قائمة الفنانين
كخطوة أولى ، سنقوم ببناء الشاشة التي تعرض قائمة الفنانين على اليسار. يجب أن تظهر هذه الشاشة للمستخدمين عند انتقالهم إلى /artists/
:
لفهم كيفية عرض هذه الشاشة ، حان الوقت لتقديم مبدأ تصميم Ember شامل آخر: الاصطلاح على التكوين . في القسم أعلاه ، رأينا أن /artists
ينشطون مسار artists
. حسب الاصطلاح ، اسم كائن المسار هذا هو ArtistsRoute
. تقع على عاتق كائن المسار هذا مسؤولية جلب البيانات للتطبيق لعرضها. يحدث ذلك في خطاف نموذج المسار:
App.ArtistsRoute = Ember.Route.extend({ model: function() { var artistObjects = []; Ember.$.getJSON('http://localhost:9393/artists', function(artists) { artists.forEach(function(data) { artistObjects.pushObject(App.Artist.createRecord(data)); }); }); return artistObjects; } });
في هذا المقتطف ، يتم جلب البيانات عبر استدعاء XHR من النهاية الخلفية - وبعد التحويل إلى كائن نموذج - يتم دفعها إلى مصفوفة يمكننا عرضها لاحقًا. ومع ذلك ، لا تمتد مسؤوليات المسار إلى توفير منطق العرض ، والذي يتم التعامل معه بواسطة وحدة التحكم. لنلقي نظرة.
عفوًا - في الواقع ، لا نحتاج إلى تعريف وحدة التحكم في هذه المرحلة! يعد Ember ذكيًا بما يكفي لإنشاء وحدة التحكم عند الحاجة وتعيين سمة M.odel
التحكم على القيمة المرتجعة لخطاف النموذج نفسه ، أي قائمة الفنانين. (مرة أخرى ، هذا نتيجة لنموذج "الاصطلاح على التكوين".) يمكننا التنحي بطبقة واحدة وإنشاء قالب لعرض القائمة:
<script type="text/x-handlebars" data-template-name="artists"> <div class="col-md-4"> <div class="list-group"> {{#each model}} {{#link-to "artists.songs" this class="list-group-item artist-link"}} {{name}} <span class="pointer glyphicon glyphicon-chevron-right"></span> {{/link-to}} {{/each}} </div> </div> <div class="col-md-8"> <div class="list-group"> {{outlet}} </div> </div> </script>
إذا كان هذا يبدو مألوفًا ، فذلك لأن Ember.js يستخدم قوالب Handlebars ، التي تحتوي على بناء جملة بسيط للغاية ومساعدين ولكنها لا تسمح بالمنطق غير التافه (على سبيل المثال ، ORing أو ANDing المصطلحات في شرطي).
في القالب أعلاه ، نقوم بالتكرار من خلال النموذج (تم إعداده مسبقًا من خلال المسار إلى مصفوفة تحتوي على جميع الفنانين) ولكل عنصر فيه ، نقدم رابطًا artists.songs
إلى مسار الفنانين. الرابط يحتوي على اسم الفنان. #each
helper في Handlebars النطاق بداخله إلى العنصر الحالي ، لذا فإن {{name}}
سيشير دائمًا إلى اسم الفنان الذي يخضع للتكرار حاليًا.
المسارات المتداخلة لطرق العرض المتداخلة
هناك نقطة أخرى مهمة في المقتطف أعلاه: {{outlet}}
، والذي يحدد الفتحات في النموذج حيث يمكن عرض المحتوى. عند تداخل المسارات ، يتم تقديم قالب مسار المورد الخارجي أولاً ، متبوعًا بالمسار الداخلي ، والذي يعرض محتوى القالب الخاص به في {{outlet}}
المحدد بواسطة المسار الخارجي. هذا بالضبط ما يحدث هنا.
حسب الاصطلاح ، تقدم جميع المسارات محتواها في قالب يحمل نفس الاسم. أعلاه ، سمة data-template-name
للقالب أعلاه هي artists
مما يعني أنه سيتم تقديمهم للمسار الخارجي ، artists
. إنه يحدد منفذًا لمحتوى اللوحة اليمنى ، حيث يعرض المسار الداخلي ، artists.index
محتواه:
<script type="text/x-handlebars" data-template-name="artists/index"> <div class="list-group-item empty-list"> <div class="empty-message"> Select an artist. </div> </div> </script>
باختصار ، يعرض مسار واحد ( artists
) محتواه في الشريط الجانبي الأيسر ، ونموذجه هو قائمة الفنانين. طريق آخر ، يعرض موقع artists.index
المحتوى الخاص به في الفتحة التي يوفرها نموذج artists
. يمكن أن يجلب بعض البيانات ليكون نموذجًا له ولكن في هذه الحالة كل ما نريد عرضه هو نص ثابت ، لذلك لا نحتاج إلى ذلك.
قم بإنشاء فنان
الجزء 1: ربط البيانات
بعد ذلك ، نريد أن نكون قادرين على إنشاء فنانين ، وليس مجرد إلقاء نظرة على قائمة مملة.
عندما عرضت قالب artists
الذي يعرض قائمة الفنانين ، خدعت قليلاً. لقد قطعت الجزء العلوي للتركيز على ما هو مهم. الآن ، سأضيف ذلك مرة أخرى:
<script type="text/x-handlebars" data-template-name="artists"> <div class="col-md-4"> <div class="list-group"> <div class="list-group-item"> {{input type="text" class="new-artist" placeholder="New Artist" value=newName}} <button class="btn btn-primary btn-sm new-artist-button" {{action "createArtist"}} {{bind-attr disabled=disabled}}>Add</button> </div> < this is where the list of artists is rendered > ... </script>
نستخدم مساعد Ember ، input
، مع كتابة النص لتقديم إدخال نص بسيط. في ذلك ، نربط قيمة إدخال النص newName
الخاصة بوحدة التحكم التي تدعم هذا القالب ، ArtistsController
. نتيجة لذلك ، عندما تتغير خاصية قيمة الإدخال (بمعنى آخر ، عندما يكتب المستخدم نصًا فيه) ، سيتم الاحتفاظ newName
على وحدة التحكم في المزامنة.
نوضح أيضًا أنه يجب إطلاق إجراء createArtist
عند النقر على الزر. أخيرًا ، نربط خاصية تعطيل الزر بخاصية disabled
وحدة التحكم. إذن كيف تبدو وحدة التحكم؟
App.ArtistsController = Ember.ArrayController.extend({ newName: '', disabled: function() { return Ember.isEmpty(this.get('newName')); }.property('newName') });
تم تعيين newName
على فارغ في البداية مما يعني أن إدخال النص سيكون فارغًا. (هل تذكر ما قلته عن عمليات الربط؟ حاول تغيير newName
أنه ينعكس كنص في حقل الإدخال.)

تم disabled
بحيث أنه في حالة عدم وجود نص في مربع الإدخال ، سيعود true
وبالتالي سيتم تعطيل الزر. إن استدعاء .property
في النهاية يجعل هذه "خاصية محسوبة" ، وهي شريحة أخرى شهية من كعكة Ember.
الخصائص المحسوبة هي خصائص تعتمد على خصائص أخرى ، والتي يمكن أن تكون "طبيعية" أو محسوبة في حد ذاتها. يقوم Ember بتخزين قيمة هذه الأشياء حتى تتغير إحدى الخصائص التابعة. ثم يعيد حساب قيمة الخاصية المحسوبة ويخزنها مؤقتًا مرة أخرى.
إليك تمثيل مرئي للعملية المذكورة أعلاه. للتلخيص: عندما يُدخل المستخدم اسم فنان ، يتم تحديث خاصية newName
، متبوعة بالخاصية disabled
، وأخيراً ، يُضاف اسم الفنان إلى القائمة.
التفاف: مصدر واحد للحقيقة
فكر في ذلك للحظة. بمساعدة الروابط والخصائص المحسوبة ، يمكننا إنشاء بيانات (نموذجية) كمصدر وحيد للحقيقة . أعلاه ، يؤدي تغيير اسم الفنان الجديد إلى تغيير في خاصية وحدة التحكم ، والتي بدورها تؤدي إلى تغيير في الخاصية المعطلة. عندما يبدأ المستخدم في كتابة اسم الفنان الجديد ، يتم تمكين الزر ، كما لو كان بطريقة سحرية.
كلما كبر النظام ، زادت النفوذ الذي نحصل عليه من مبدأ "المصدر الوحيد للحقيقة". إنها تحافظ على الكود الخاص بنا نظيفًا وقويًا ، وتحافظ على تعريفات الممتلكات لدينا أكثر وضوحًا.
تركز بعض الأطر الأخرى أيضًا على أن تكون البيانات النموذجية هي المصدر الوحيد للحقيقة ولكن إما لا تذهب إلى أبعد من Ember أو تفشل في القيام بمثل هذا العمل الشامل. Angular ، على سبيل المثال ، لها روابط ثنائية الاتجاه - لكن ليس لها خصائص محسوبة. يمكنه "محاكاة" الخصائص المحسوبة من خلال وظائف بسيطة ؛ تكمن المشكلة هنا في أنه ليس لديها طريقة لمعرفة متى يتم تحديث "خاصية محسوبة" وبالتالي تلجأ إلى الفحص القذر ، مما يؤدي بدوره إلى فقدان الأداء ، خاصة في التطبيقات الأكبر حجمًا.
إذا كنت ترغب في معرفة المزيد حول هذا الموضوع ، فإنني أوصيك بقراءة منشور مدونة eviltrout للحصول على إصدار أقصر أو سؤال Quora هذا لإجراء مناقشة مطولة يشارك فيها المطورون الأساسيون من كلا الجانبين.
الجزء 2: معالجات العمل
دعنا نعود لنرى كيف يتم إنشاء إجراء createArtist
بعد إطلاقه (بعد الضغط على الزر):
App.ArtistsRoute = Ember.Route.extend({ ... actions: { createArtist: function() { var name = this.get('controller').get('newName'); Ember.$.ajax('http://localhost:9393/artists', { type: 'POST', dataType: 'json', data: { name: name }, context: this, success: function(data) { var artist = App.Artist.createRecord(data); this.modelFor('artists').pushObject(artist); this.get('controller').set('newName', ''); this.transitionTo('artists.songs', artist); }, error: function() { alert('Failed to save artist'); } }); } } });
يجب أن يتم تغليف معالجات الإجراءات في كائن actions
ويمكن تحديدها في المسار أو وحدة التحكم أو العرض. اخترت تعريفه على الطريق هنا لأن نتيجة الإجراء لا تقتصر على وحدة التحكم بل "العالمية".
لا يوجد شيء خيالي يحدث هنا. بعد أن أبلغتنا الجهة الخلفية بأن عملية الحفظ قد اكتملت بنجاح ، نقوم بثلاثة أشياء ، من أجل:
- أضف الفنان الجديد إلى نموذج القالب (كل الفنانين) حتى تتم إعادة تقديمه ويظهر الفنان الجديد كعنصر أخير في القائمة.
- امسح حقل الإدخال عبر ربط
newName
، مما يحفظنا من الاضطرار إلى معالجة DOM مباشرة. - الانتقال إلى مسار جديد (
artists.songs
) ، مرورًا بالفنان الذي تم إنشاؤه حديثًا كنموذج لذلك المسار.transitionTo
إلى هو طريقة التنقل بين المسارات داخليًا. (يعمل مساعدlink-to
القيام بذلك عبر إجراء المستخدم.)
عرض الأغاني لفنان
يمكننا عرض الأغاني للفنان إما بالضغط على اسم الفنان. نمر أيضًا بالفنان الذي سيصبح نموذجًا للطريق الجديد. إذا تم تمرير كائن النموذج بهذه الطريقة ، فلن يتم استدعاء خطاف model
الخاص بالمسار نظرًا لعدم وجود حاجة لحل النموذج.
المسار ArtistsSongsController
هنا artists/songs
artists.songs
. لقد رأينا بالفعل كيف يتم عرض النموذج في المنفذ الذي يوفره نموذج artists
حتى نتمكن من التركيز على القالب المتاح فقط:
<script type="text/x-handlebars" data-template-name="artists/songs"> (...) {{#each songs}} <div class="list-group-item"> {{title}} {{view App.StarRating maxRating=5}} </div> {{/each}} </script>
لاحظ أنني قمت بتجريد الكود لإنشاء أغنية جديدة لأنها ستكون تمامًا مثل تلك الخاصة بإنشاء فنان جديد.
تم إعداد خاصية songs
في جميع كائنات الفنان من البيانات التي يتم إرجاعها بواسطة الخادم. الآلية الدقيقة التي يتم بها ذلك لا تهم المناقشة الحالية. في الوقت الحالي ، يكفي أن نعرف أن كل أغنية لها عنوان وتقييم.
يتم عرض العنوان مباشرة في القالب ويتم تمثيل التصنيف بالنجوم ، عبر عرض StarRating
. دعونا نرى ذلك الآن.
أداة تصنيف النجوم
يقع تصنيف الأغنية بين 1 و 5 ويظهر للمستخدم من خلال عرض ، App.StarRating
. تتمتع المشاهدات بإمكانية الوصول إلى سياقها (الأغنية في هذه الحالة) ووحدة التحكم الخاصة بها. هذا يعني أنه يمكنهم قراءة وتعديل خصائصه. هذا على عكس لبنة Ember الإنشائية الأخرى ، المكونات ، التي هي عناصر تحكم معزولة وقابلة لإعادة الاستخدام مع إمكانية الوصول إلى ما تم تمريره إليها فقط. (يمكننا أيضًا استخدام مكون التصنيف بالنجوم في هذا المثال).
لنرى كيف يعرض العرض عدد النجوم ويضبط تصنيف الأغنية عندما ينقر المستخدم على إحدى النجوم:
App.StarRating = Ember.View.extend({ classNames: ['rating-panel'], templateName: 'star-rating', rating: Ember.computed.alias('context.rating'), fullStars: Ember.computed.alias('rating'), numStars: Ember.computed.alias('maxRating'), stars: function() { var ratings = []; var fullStars = this.starRange(1, this.get('fullStars'), 'full'); var emptyStars = this.starRange(this.get('fullStars') + 1, this.get('numStars'), 'empty'); Array.prototype.push.apply(ratings, fullStars); Array.prototype.push.apply(ratings, emptyStars); return ratings; }.property('fullStars', 'numStars'), starRange: function(start, end, type) { var starsData = []; for (i = start; i <= end; i++) { starsData.push({ rating: i, full: type === 'full' }); }; return starsData; }, (...) });
rating
، fullStars
و numStars
هي خصائص محسوبة والتي ناقشناها مسبقًا مع خاصية disabled
ArtistsController
. أعلاه ، لقد استخدمت ما يسمى ماكرو الخاصية المحسوبة ، والتي تم تعريف حوالي اثني عشر منها في Ember. إنها تجعل الخصائص المحسوبة النموذجية أكثر إيجازًا وأقل عرضة للخطأ (للكتابة). لقد قمت بتعيين rating
ليكون تصنيفًا للسياق (وبالتالي الأغنية) ، بينما قمت بتعريف خصائص fullStars
و numStars
بحيث يقرؤون بشكل أفضل في سياق أداة التصنيف النجمي.
طريقة stars
هي عامل الجذب الرئيسي. تقوم بإرجاع مجموعة من البيانات للنجوم حيث يحتوي كل عنصر على خاصية rating
(من 1 إلى 5) وعلامة ( full
) للإشارة إلى ما إذا كانت النجمة ممتلئة. هذا يجعل من السهل للغاية استعراضها في النموذج:
<script type="text/x-handlebars" data-template-name="star-rating"> {{#each view.stars}} <span {{bind-attr data-rating=rating}} {{bind-attr class=":star-rating :glyphicon full:glyphicon-star:glyphicon-star-empty"}} {{action "setRating" target=view}}> </span> {{/each}} </script>
يحتوي هذا المقتطف على عدة نقاط ملحوظة:
- أولاً ، يعين
each
مساعد أنه يستخدم خاصية عرض (على عكس خاصية على وحدة التحكم) عن طريق إضافة اسم الخاصية إلىview
. - ثانيًا ، تحتوي سمة
class
الخاصة بعلامة الامتداد على فئات ديناميكية وثابتة مختلطة تم تعيينها. أي شيء مسبوقًا بـ a:
يصبح فئة ثابتة ، بينماfull:glyphicon-star:glyphicon-star-empty
يشبه عامل التشغيل الثلاثي في JavaScript: إذا كانت الخاصية الكاملة صحيحة ، فيجب تعيين الفئة الأولى ؛ إذا لم يكن الأمر كذلك ، فالثاني. - أخيرًا ، عند النقر على العلامة ، يجب إطلاق الإجراء
setRating
— لكن Ember سيبحث عنها في العرض ، وليس المسار أو وحدة التحكم ، كما في حالة إنشاء فنان جديد.
وبالتالي يتم تحديد الإجراء في العرض:
App.StarRating = Ember.View.extend({ (...) actions: { setRating: function() { var newRating = $(event.target).data('rating'); this.set('rating', newRating); } } });
نحصل على التصنيف من سمة بيانات rating
التي قمنا بتعيينها في النموذج ثم قمنا بتعيينها rating
للأغنية. لاحظ أن التصنيف الجديد لا يستمر في النهاية الخلفية. لن يكون من الصعب تنفيذ هذه الوظيفة بناءً على الطريقة التي أنشأنا بها فنانًا وتُترك كتمرين للقارئ المتحمس.
التفاف كل شيء
لقد تذوقنا العديد من مكونات كعكة إمبر المذكورة أعلاه:
- لقد رأينا كيف أن المسارات هي جوهر تطبيقات Ember وكيف تعمل كأساس لاتفاقيات التسمية.
- لقد رأينا كيف تجعل روابط البيانات ثنائية الاتجاه والخصائص المحسوبة بيانات نموذجنا المصدر الوحيد للحقيقة وتسمح لنا بتجنب التلاعب المباشر في DOM.
- وقد رأينا كيفية إطلاق الإجراءات والتعامل معها بعدة طرق وإنشاء طريقة عرض مخصصة لإنشاء عنصر تحكم ليس جزءًا من HTML الخاص بنا.
جميل ، أليس كذلك؟
قراءة متعمقة (ومشاهدة)
هناك ما هو أكثر بكثير من إمبر مما يمكن أن أكون مناسبًا له في هذا المنشور وحده. إذا كنت ترغب في مشاهدة سلسلة screencast حول كيفية إنشاء إصدار أكثر تطوراً إلى حد ما من التطبيق أعلاه و / أو معرفة المزيد حول Ember ، يمكنك الاشتراك في القائمة البريدية للحصول على مقالات أو نصائح على أساس أسبوعي.
آمل أن أكون قد أثارت شهيتك لمعرفة المزيد عن Ember.js وأن تتخطى نموذج التطبيق الذي استخدمته في هذا المنشور. بينما تستمر في التعرف على Ember.js ، تأكد من إلقاء نظرة على مقالتنا حول Ember Data لمعرفة كيفية استخدام مكتبة بيانات ember. استمتع بالبناء!