how to build nested views in emberjs - ember.js

I'm trying to learn ember and to understand all concepts. I'm stucked now for a couple of days.
I tried to create a view with multiple "subviews" (is there a name for it in ember?). For example:
+-----------------------------------------------+------------------+
| Post title | new tag: ______ |
+-----------------------------------------------+ |
| Post text | * tag 1 |
| | * tag 2 |
| | |
+-----------------------------------------------+------------------+
| New comment: _____________ submit | related posts |
| | |
| * comment 1 text | * post 1 |
| * comment 2 text | * post 2 |
| * comment 3 text | |
| * comment 4 text | |
| * comment 5 text | |
| | |
+-----------------------------------------------+------------------+
My idea is to have a PostController with its own route to show details for a selected post. Adding nested elements for comments and widgets (with {{#each}}) is easy. This means I need to handle all actions and everything else for my nested elements in my PostController which smells bad and becomes messy.
Shouldn't be each of them (comments, tags and related) a separate controller? In this case I could keep my PostController clean and handle all nested action in its own controller. Is this the right way or has ember better ways to do it?
If yes, how can I implement these nested views. I read many topics around nested views in ember but I can't figure out its concepts. I would say, I don't need routes for tags and comments because its bound to a PostController and they are never used without a post, but I'm not sure.
After reading this post, I tried to use render but I don't understand the concept which controller and view is used in this case.
Is there maybe a good (updated) guide which I just missed or could someone please explain me how this could work or guide me in the right direction?

You can create separated views by Em.View.create,
then insert to post template with {{view}} for comments & tags.
Templates:
<script type='text/x-handlebars' data-template-name='post'>
<div class='post'>
<h1>{{title}}</h1>
<div class='content'>{{{content}}}</div>
<div class='tags'>{{view App.TagView}}</div>
<div class='comments'>{{view App.CommentView}}</div>
<div class='relatedPosts'>{{view App.RelatedPostView}}</div>
</div>
</script>
<script type='text/x-handlebars' data-template-name='comment'>
{{#each view.comments}}
<!--some HTML codes for comments-->
{{/each}}
</script>
Script:
App.CommentView = Em.View.create({
templateName:'comment',
comments:[],
didInsertElement: function(){
// Handler related to this view has to implement here
// as there are delays for inserting elements
}
});
App.PostController = Em.ArrayController.extend({
getContent: function(){
//some codes for loading content
//You can implement all methods in single controller
this.addComments(data.comments);
//Or call other methods after loaded the content
App.commentController.setup(data.comments);
},
addComments: function(data){
App.CommentView.set('comments',data);
}
});

Related

css - how to change grid layout without using media query

I'm creating a header for a site, which has several individual items. I'm using css grid and want to avoid using media query for changing the layout when contents don't fit. Having trouble getting the grid setup.
My html structure looks like this:
<header class="container">
<div id="brand">...</div>
<div id="hamburger-menu>...</div>
<nav id="nav1">...</nav>
<nav id="nav2">...</nav>
</header>
Below is my current css code:
#brand { grid-area: brand; }
#hamburger-icon { grid-area: hamburgerIcon; }
#nav1 { grid-area: nav1; }
#nav2 { grid-area: siteNav; }
.container {
display: grid;
grid-template-columns: 450px auto ;
grid-template-rows: auto;
grid-row-gap: 16px;
grid-template-areas:
"brand nav1"
"brand nav2";
}
The above CSS gives me a layout that looks like this:
| brand | nav1 |
| | ---- |
| | nav2 |
This is fine on a larger screen. In my case nav2 is expected to be much longer. So, in smaller screens (not mobile/tablet), the current layout overflows to the right side.
I'd like to have the nav1 and nav2 just flow "under" the brand, but only when necessary. For example, move nav2 to the second row when it doesn't fit anymore.
| brand | nav1 |
| ----- | ---- |
| nav2 |
I know I can probably set the media query and based on screen size change the grid-template-areas but I'm wondering if that's the only way.
This article is really good that shows media query is not necessary, but I'm having trouble applying it in my case.
https://css-tricks.com/look-ma-no-media-queries-responsive-layouts-using-css-grid/
Any suggestions?

Ember.js: Is it possible to have a component for each field in a model?

So I thought I had this solved already as it worked with my prototype using just arrays, but now that I'm actually dealing with the model, I'm not sure if it will work.
If I create a property on my controller like so:
myAttributes: [{
name: 'attr1',
label: 'Attribute 1',
value: null
}, {
name: 'attr2',
label: 'Attribute 2',
value: 1
}]
then I can loop through myAttributes with {{#each}}, so while each object is essentially a field, that's different than the fields on a single model, which only have a value, so I can't do {{#each model as |rec|}} when there's only one record.
In a nutshell, I want to have a button group to set the value for each field in my model, like so:
I have around 60 of these fields so that's why I wanted to use {{#each}} and my component for each field, but of course each goes over records in the model, not fields in a record.
Is this impossible to do? Do I just have to bite the bullet and write out the markup for each field like I would do if I had only a few fields?
Update: Even if I could loop through the fields on a single record (maybe with {{#each model.#each as |field|}}?), for this case what I also need to do is break out the fields into sections in the UI, so for example loop through fields 1-10 in the first section, and 11-20 in the next section, and there doesn't seem to be a good way to do that.
In the end, I think I'm better off just using a component on each field, like so:
{{attribute-component value=model.attr1}}
{{attribute-component value=model.attr2}}
.
.
.
There's a neat helper called each-in that iterates over key/attributes of an object.
https://guides.emberjs.com/v2.6.0/templates/displaying-the-keys-in-an-object/
There is a way to do that in your case, but I think that actually a simpler way to get the same effect is this:
template.hbs
{{#each myArrayOfPeople as | person | }}
{{#each attribs as | anAttrib | }}
{{get person (mut anAttrib)}},
{{/each}}
<br>
{{/each}}
component.js
attribKeys:['name', 'phoneNumber', 'otherAttrib'];
This will output something like:
joe, 123, something,
sam, 456, anotherthing,
sarah, 944, foo
you can use that same (mut anAttrib) helper to bind an attribute to an input or whatever your need is.
{{attribute-component value=(mut anAttrib)}}
I have now forgone trying to iterate over each field because I actually need to break out the fields into accordion containers based on other criteria, although #averydev's answer about nested {{#each}} was a very cool tip and useful.
I also updated to Ember 1.13 in order to use the mut helper (although I had previously used an action on the component, that passes the new value to an action in the controller, that sets the value to the model property. Since that was convoluted, this new method using mut is much more understandable.
Just to help anyone else out in the future, here's my code (simplified for SO, the real code has nothing to do with rooms and furniture). I have a custom component that uses a radio button group component to actually set the value (this is from ember-radio-button):
template.hbs
{{room-style title='Living Room' field=(mut model.livingRoomStyle)}}
{{room-style title='Master Bedroom' field=(mut model.masterStyle)}}
templates/components/room-style.hbs
<div class="btn-group" data-toggle="buttons">
{{#radio-button value=1 groupValue=field classNames="btn btn-default" changed="changed"}}
Modern
{{/radio-button}}
{{#radio-button value=2 groupValue=field classNames="btn btn-default" changed="changed"}}
French
{{/radio-button}}
{{#radio-button value=3 groupValue=field classNames="btn btn-default" changed="changed"}}
Rustic
{{/radio-button}}
</div>
components/room-style.js
import Ember from 'ember';
export default Ember.Component.extend({
actions: {
changed: function() {
//update() is a function you get with attrs.[prop] when using the mut helper
this.attrs.field.update(this.get('field'));
}
}
});

Deeply nested resources & deeply nested templates in Ember.js?

I will start by posting my router, As I think it makes it easier to explain:
#resource 'clients', path: 'clients', ->
#resource 'client', path: '/:client_id', ->
#resource 'client.contact', path: ':client_id/contact', ->
#route 'new', path: '/new'
#route 'edit'
What I am after is, Being able to add a new contact for a selected client, Without loosing/replacing the selected client template!
In other words, I want the client/contact/new.hbs to be nested inside the client/index.hbs without replacing it:
`templates/client/index.hbs`
{{!-- Some code has been removed/shortened for clarity --}}
{{model.name}}
{{model.phone}}
{{model.url}}
<button class="btn btn-danger" {{action 'delete'}}>Delete</button>
<h3>Contacts</h3>
{{#link-to 'client.contact.new'}}contactsLink{{/link-to}}
{{!-- here where i want 'contacts.new' to show up without replacing this template--}}
{{outlet}}
Using the current route (above), I achieved that, But, The URL looks really Ugly (I shortened the client-id for clarity):
/clients/6654../6654../contact/new
The client id is shown twice in the URL, And if I removed the dynamic segment from the nested client.contact resource's path #resource 'client.contact', path: ':client_id/contact', -> or even if i did not use the dot . notation to preserve the deeply nested namespace client.contact, Then the new contact template will replace the client.index template, and I don't want that...
Of course, I could use a simple div, With a Boolean controller variable to show/hide the div But, I need the nested resource structure for the future (Editing and deleting a client contact)
here is my templates structure:
templates/
|-- client/
| | edit.hbs
| | index.hbs
| |-- contact/
| | new.hbs
|-- clients/
| | new.hbs
So, How can I achive that? I don't get it why the contact.new template is replacing the client.index template in the first place, Isn't it supposed to be Nested resources === nested UI/templates ?
Any help, Tips, Or advice is highly appreciated, I really spent the last 2 days digging and trying different solutions with no luck...
Found a couple similar questions here, But they were trying to achieve different goals.

Ember Routing: Is it possible to nest inside a "verb" route, rather than just a "noun"?

I'm trying to display a list of "tracks" on the "edit" page of an "album".
My router.js.coffee looks like this:
#resource 'albums', ->
#resource 'album', path: '/:album_id', ->
#route 'edit', ->
#resource 'tracks'
If I visit /albums/1/edit, the 'edit.handlebars' template is showing correctly. However, if I put an {{outlet}} tag into that template, it's not triggering my TracksRoute.
Looking in the Ember inspector, it looks like the 'albums.edit.index' route is being used, and my 'tracks' route is nested at the same level as that, rather than below it:
albums (AlbumsRoute)
|
|
|-> album (AlbumRoute)
|
|
|-> album.edit (AlbumEditRoute)
|
|
|-> tracks (TrackRoute)
|
|
|-> album.edit.index (AlbumEdit.IndexRoute)
I think I want the TrackRoute below the AlbumEdit.IndexRoute, but I'll settle for any other way to list the child model records on the "edit" page of a parent (Ideally they'd be listed in a way that would mean I could do CRUD actions in-place)
You would need to use more than one controller in the same route for this:
Router.map(function() {
this.resource('albums', function() {
this.resource('album', {path: '/:album_id'}, function() {
this.route('edit');
});
});
});
App.AlbumEditRoute = Em.Route.extend({
setupController: function(editController, model) {
var tracksController = this.controllerFor('tracks');
this._super(editController, model);
tracksController.set('model', model.get('tracks'));
}
});
And in your template: album/edit/index.hbs
<div class="edit-form">
{{!-- anything related to edit --}}
</div>
<div class="edit-tracks">
{{render "tracks"}}
</div>

Ember js: Render Nested Navigation Sidebar together with a main view

I have a page whichs layout will look like the following:
There is a nested navigation on the left side. In this case it displays:
a list of libraries (l1, l2, l3). currently l1 is selected
a list of books (b1, b2) present in the library (1)
a list of pages (p1, p2, p3) contained in the book
In case a library is selected, the column for displaying the books in the library would automatically be updated. When selecting a book, the pages column would automatically be updated with all the pages in that book etc...
In addition to that there is a main view, which displays additional details for the currently selected entity. In this case page 2 (p2):
________________________________
|*l1 | b1 | p1 | |
|----|----|----| |
| l2 |*b2 |*p2 | Main View |
|----|----|----| (p2 details) |
| l3 | | p3 | |
--------------------------------
Here is an additional view when just a library is selected. It shows the selected library, all the books in that library in the sidebar and the details about the library in the main view.
________________________________
|*l1 | b1 | |
|----|----| |
| l2 | b2 | Main View |
|----|----| (l1 details) |
| l3 | | |
--------------------------------
or when just a book is selected:
________________________________
|*l1 | b1 | p1 | |
|----|----|----| |
| l2 |*b2 | p2 | Main View |
|----|----|----| (b2 details) |
| l3 | | p3 | |
--------------------------------
So, there are mainly two jobs when for instance a library is selected:
Display all the books in the library (navigation)
Display details about the selected library (main)
The navigation on its own works fine when using nested routes the following way:
Router.map(function() {
this.resource('libraries', { path: '/libraries' }, function() {
this.resource('books', { path: ':library'}, function() {
this.resource('pages', { path: ':book'});
});
});
});
Within the pages route I just get all the books for the selected library and render it into the sidebar outlet:
export default Ember.Route.extend({
model: function (params) {
return this.store.find('book', {song: params.library});
},
renderTemplate: function() {
this.render({ outlet: 'sidebar-books' });
}
});
However, by now I am stuck when trying to render the details about the currently selected library, book or page to the main view AND to the sidebar view.
I found that I can add an additional render call to the renderTemplate function, which lets me render additional content to the main view, however I can't figure out how to retrieve the entity I want to display from the store, as any additional routes won't get called.
What is the recommended why to do something like this?
After quite some try and error, loading two models within the same route and rendering both views with that models, seems to do the trick.
This is the books route.
routes/books.js:
export default Ember.Route.extend({
model: function (params) {
return Ember.RSVP.hash({
books: this.store.find('book', {library: params.library}),
library: this.store.find('library', params.library)
})
},
renderTemplate: function(controller, model) {
this.render('books',{
outlet:'sidebar-books'
});
this.render('library');
}
});
In the handlebar template I can now access the library via:
{{library.name}}
and iterate over the books via:
{{#each books}}
<div class="book">
<div>{{title}}</div>
</div>
{{/each}}
side navigation with dynamic menu
Example of rendering a side menu navigation with nested routes closer to what codeySmurf described, although the main issue of loading multiple models has not been tuckled, it has been solved by codeySmurf.
http://emberjs.jsbin.com/begewoza/1/edit
side navigation with static menu
An approach for rendering a sidebar menu along with the content could be something like the following. It is assumed that it is not required to render the parent models along with the children in main view area (eg show the lib1 on top underneath book1 and below that page1 at the same time) and the menu can be defined in template so no nested resources have been used. The menu has been rendered by using the render helper, although named outlets could also be used (named outlets example http://emberjs.jsbin.com/nibikufa/1/edit) .
http://emberjs.jsbin.com/hituxado/1/edit
js
App = Ember.Application.create();
App.Router.map(function() {
this.route("allLibraries",{path:"/libraries"});
this.resource("library",{path:"/libraries/:library"});
this.resource("book",{path:"/libraries/:library/books/:book"});
this.resource("page",{path:"/libraries/:library/books/:book/pages/:page"});
});
App.IndexRoute = Ember.Route.extend({
redirect:function(){this.transitionTo("allLibraries");}
});
App.LibraryRoute = Ember.Route.extend({
model:function(params){
return {libId:params.library};
}
});
App.BookRoute = Ember.Route.extend({
model:function(params){
return {libId:params.library,bookId:params.book};
}
});
App.PageRoute = Ember.Route.extend({
model:function(params){
return {libId:params.library,bookId:params.book,pageId:params.page};
}
});
App.MenuView = Ember.View.extend({
});
hbs
<script type="text/x-handlebars">
<h2> Welcome to Ember.js</h2>
<div style="float:left">{{render "menu"}}</div>
<div style="float:left;margin-left:30%">{{outlet}}</div>
</script>
<script type="text/x-handlebars" data-template-name="allLibraries">
all libs
</script>
<script type="text/x-handlebars" data-template-name="library">
the library {{libId}}
</script>
<script type="text/x-handlebars" data-template-name="book">
the book {{libId}} - {{bookId}}
</script>
<script type="text/x-handlebars" data-template-name="page">
the page {{libId}} - {{bookId}} - {{pageId}}
</script>
<script type="text/x-handlebars" data-template-name="menu">
{{#link-to "allLibraries"}}all libs{{/link-to}}
<br/>
{{#link-to "library" 1}}lib 1
<br/>
{{#link-to "book" 1 1}}book 1
<br/>
{{#link-to "page" 1 1 1}}page1{{/link-to}}
{{/link-to}}
<br/>
{{#link-to "book" 1 2}}book 2
<br/>
{{#link-to "page" 1 2 2}}page2{{/link-to}}
{{/link-to}}
{{/link-to}}
<br/><br/>
{{#link-to "library" 2}}lib 2
<br/>
{{#link-to "book" 2 2}}book 2
<br/>
{{#link-to "page" 2 2 2}}page2 2{{/link-to}}
{{/link-to}}
{{/link-to}}
</script>
css
a.active{
background-color:lightgrey;
border-radius:4px;
margin:8px;
padding:4px;
}