We're building app that allows users to post messages to various social media outlets. Our designer has created a series of interactions which allow users to change various settings in their application by use of sliding panels. I've done a quick screen cap to illustrate:
http://screencast.com/t/tDlyMud7Yb7e
The question I have is one of architecture. I'm not sure whether I should be using a View or a Controller (or both) to store some of the methods these panels will contain. Here's the HTML for the panels. They're not currently in a script tag or view:
<div id="panel-account-settings" class="panel closed">
<div class="panel-inner">
<i class="icon-cancel"></i>close
<h3>Account Settings</h3>
Google Analytics
Link Shortening
Disconnect Account
</div>
<div id="panel-google-analytics" class="panel-inner">
<i class="icon-arrow-right"></i>back
<h3>Google Analytics</h3>
<div class="toggle">
<label>Off</label>
</div>
<p>We <strong>won't</strong> append stuff to your links, so they <strong>won't</strong> be trackable in your Google Analytics account.</p>
<img src="{{ STATIC_URL }}images/ga-addressbar.png" />
</div>
<div id="panel-disconnect" class="panel-inner">
<i class="icon-arrow-right"></i>back
<h3>Disconnect This Account</h3>
<p>If you disconnect this account you will lose all the metrics we tracked for each message. Are you absolute sure you want to get rid of them?</p>
<div class="button-group">
Disconnect
</div>
</div>
</div>
The gear icon shown in the video is contained with the accounts template
<script type="text/x-handlebars" data-template-name="accounts">
{{#each account in controller}}
<div class="avatar-name">
<p>{{account.name}}</p>
<p>#{{account.username}}</p>
<i class="icon-cog" {{action "openPanel" Social.SettingsView account }}></i>
</div>
{{/each}}
</script>
which has a bare bones controller
Social.AccountsController = Ember.ArrayController.extend({
openPanel: function(view,account){
console.log(view,account);
$(this).parents(".item-account").addClass("active");
$("#panel-account-settings").prepareTransition().removeClass("closed");
}
});
as well as a Route and a Model. Given the interaction I'm looking to accomplish, my question is where should I be putting the pieces and parts? At a minimum I need to pass in the current Account model so that I know which account I'll be applying changes to. I thought about creating a mainPanel view which would contain the other view...something like this:
<script type="text/x-handlebars" data-template-name="panelView">
<div id="panel-account-settings" class="panel closed">
{{ partial "panelSettingsView" }}
{{ partial "panelAnalyticsView" }}
{{ partial "panelDisconnectView" }}
</div>
</script>
and then the action helper on the gear icon could pass in the account AND the required view. But I'm not sure if that's the right approach. I'd appreciate some input or suggestions. Thanks.
UPDATE 1
Ideally I'd like to eventually load in the content of each panel via AJAX but that's a want to, not a need to.
UPDATE 2
I tried creating a PanelView which would contain the logic on which panels to load:
Social.PanelView = Ember.View.extend({
tagName: 'div',
classNames: ['panel-inner'],
openPanel: function(view,account){
console.log(view,account);
}
});
But when I tried to call it from the gear icon I got an error. This:
<i class="icon-cog" {{action openPanel target="Social.PanelView" }}></i>
Threw this error:
Uncaught Error: assertion failed: The action 'openPanel' did not exist on Social.PanelView
Isn't that the correct syntax?
UPDATE 3
Adding version information:
DEBUG: Ember.VERSION : 1.0.0-rc.1
DEBUG: Handlebars.VERSION : 1.0.0-rc.3
DEBUG: jQuery.VERSION : 1.9.1
The best practice is to always put any DOM- or UI-related logic into your view, and leave data representation to the controller (i.e., a reference to a 'selected' item in the controller is a common example).
Your Social.AccountsController.openPanel method has logic that touches the DOM, which is entirely a view concern. A good start would be to move that logic into the view (Social. SettingsView ?).
It'd be a bit easier to understand your goals and offer more suggestions if you had a jsfiddle of what you have so far.
EDIT: Another good practice is to decompose things into very small objects. So you could explore having a selectedAccount ObjectController whose content is the currently chosen Account (and a corresponding View for it).
Related
I'm trying to create a custom login only using Facebook and only looking for two endpoints: "name" and "avatar".
For starters I don't know if "avatar" is even a real endpoint name but that's what I'm trying to access.
I have created a test app on FB, I have also installed all of the Meteor packages that I need so the groundwork is done.
I've create the following template:
<template name="Login">
<h2>Login</h2>
{{#if currentUser}}
{{currentUser.services.facebook.name}}
{{currentUser.services.facebook.avatar}}
<button id="logout">Logout</button>
{{else}}
<button id="facebook-login" class="btn btn-default">Login with Facebook</button>
{{/if}}
</template>
and then in the SERVERS directory I have create a .js file to store my API keys.
My questions:
My first question is where to find the names of these endpoints as I've been going the entire documentation on FB and nothing references "name" or "avatar" so the first thing I need to understand is where to find these endpoints as I haven't been able to locate even the "name".
Second question is the API shows JSON objects and that's usually how you would hookup your endpoints but in Meteor since all of that is abstracted it's unclear where this "facebook" object exists to then study more in depth the nested properties like "name" and "avatar" (which again i'm uncertain if that is the correct name for that property). I'm assuming because I'm using Meteor that calling an endpoint like this {{currentUser.services.facebook.name}} is enough, am I thinking about this correctly?
Final question is if I have to call these endpoints like this inside of my template:
{{#if currentUser}}
{{currentUser.services.facebook.name}}
{{currentUser.services.facebook.gender}}
<button id="logout">Logout</button>
{{else}}
<button id="facebook-login" class="btn btn-default">Login with Facebook</button>
{{/if}}
Then even if I wrap my facebook name and gender in their own divs like this:
{{#if currentUser}}
<div class="name">
{{currentUser.services.facebook.name}}
</div>
<div class="avatar">
{{currentUser.services.facebook.avatar}}
</div>
<button id="logout">Logout</button>
{{else}}
<button id="facebook-login" class="btn btn-default">Login with Facebook</button>
{{/if}}
This still doesn't make it very obvious to me how to move it say the header?
So in other words how would I have the user login from the main body of the page, yet after they login have the actually username and avatar up in the header?
There is no obvious way for me to do this.
What am I missing? How would I DOM shuffle to move the .name and .avatar divs to the header when I just logged the user in via the body of the page?
Does this make sense?
My hunch is that I would have to create another template within the header that calls these values?
Anyone play around with this that could offer some insight?
Thank you.
The first part of your question is answered here:
https://stackoverflow.com/a/15019052/1327678
The answer to the second part of your question is in the docs. You could make a template helper to check this:
Template.header.helpers({
currentUser: function(){
if(Meteor.user()){
return true;
}
else{
return false;
}
}
});
And in your template just write:
{{#if currentUser}}
{{!-- your facebook code here --}}
{{/if}}
In my meteor project, I have added iron:layout, iron:dynamic-template along with iron:router.
My question is, how can you prevent the dynamic template from rendering if there is no data available in the Session? The reason is, the dynamic template is currently being rendered with all html content within it except for data context. This is the problem when the user initially arrives onto the page.
I have a list of names on 'postlist' template. These are 'usernames' of the person who created the post. When a user clicks on the name, the template 'viewpost' is rendered with the relevant data passed...that is fine. But as stated earlier, there is no data context when the user first arrives onto the page. So the see all the content except for the dynamic content.
The following is my current code, with help received from my previous post. Meteor: Render template inside a template
HTML:
<template name="postlist">
<div class="container">
<div class="col-sm-3">
{{#each post}}
<li>{{fullname}}</li>
{{/each}}
</div>
</div>
{{> Template.dynamic template='viewpost' data=currentPost}}
</template>
Click event to capture post _id / helper file:
Template.postlist.helpers({
currentPost: function(){
return Posts.findOne(Session.get('currentPost'));
}
});
Template.postlist.events({
'click li': function(e){
e.preventDefault();
Session.set("currentPost", this._id);
}
});
This is one alternative hack method but understand is not good practice. This is what I am effectively wanting to achieve so you get an idea. But I would like non-hack suggestions for this issue. Thank you.
html:
<template name="viewpost">
{{#if hasData}}
<div class="container">
Post creator is : {{username}} - Info: {{body_text}}
</div>
{{/if}}
</template>
js:
Template.viewpost.helpers({
"hasData":function(){
return Session.get("currentPost");
}
});
I see that ember has a very nice mechanism for wrapping content in a component using the {{yield}} mechanism documented here.
So, to use the example in the documentation, I can have a blog-post component template defined like so:
<script type="text/x-handlebars" id="components/blog-post">
<h1>{{title}}</h1>
<div class="body">{{yield}}</div>
</script>
I can then embed blog-post into any other template using the form:
{{#blog-post title=title}}
<p class="author">by {{author}}</p>
{{body}}
{{/blog-post}}
My question is, can I specify two different {{yield}} outlets in the components template?
Something like this is possible via Named Outlets in Ember.Route#renderTemplate like so:
Handlebars:
<div class="toolbar">{{outlet toolbar}}</div>
<div class="sidebar">{{outlet sidebar}}</div>
JavaScript:
App.PostsRoute = Ember.Route.extend({
renderTemplate: function() {
this.render({ outlet: 'sidebar' });
}
});
I'm not sure I can take this path for a component which will not know what route's template would be rendering it.
EDIT 1:
For the sake of clarity, I'm trying to implement the Android Swipe for Action Pattern as an Ember component.
So, I'd like users of this component to be able to specify two different templates:
A template for the normal list item, and
A template for the actions that are revealed when a swipe on (1) is detected.
I want to make this into a component, because quite a lot of javascript goes into handling the touch(start/move/end) events, while still managing smooth touch based scrolling of the list. Users would supply the two templates and this component would manage handling of touch events and necessary animations.
I've managed to get the component working in the block form, where the block's contents are treated like (1). The second template (2) is specified through a parameter (actionPartial below) which is the name of a partial template for the actions:
Component Handlebars Template: sfa-item.handlebars
<div {{bind-attr class=":sfa-item-actions shouldRevealActions:show" }}>
{{partial actionPartial}}
</div>
<div {{bind-attr class=":sfa-item-details isDragging:dragging shouldRevealActions:moveout"}}>
{{yield}}
</div>
Calling Handlebars Template:
{{#each response in controller}}
<div class="list-group-item sf-mr-item">
{{#sfa-item actionPartial="mr-item-action"}}
<h5>{{response.name}}</h5>
{{/sfa-item}}
</div>
{{/each}}
Where the mr-item-action handlebars is defined like so:
mr-item-action.handlebars:
<div class="sf-mr-item-action">
<button class="btn btn-lg btn-primary" {{action 'sfaClickedAction'}}>Edit</button>
<button class="btn btn-lg btn-primary">Delete</button>
</div>
Problem is, actions from the user supplied partial, sfaClickedAction above, are not bubbled up from the component. A fact which is mentioned in the docs in para 4.
So, now I do not know how a user could capture actions that he defined in the supplied actions template. A component cannot catch those actions because it doesn't know about them either.
EDIT 2
I sprung a follow up question here
This blog post describes the most elegant solution for Ember 1.10+: https://coderwall.com/p/qkk2zq/components-with-structured-markup-in-ember-js-v1-10
In your component you pass yield names into {{yield}}s:
<header>
{{yield "header"}}
</header>
<div class="body">
{{yield "body"}}
</div>
<footer>
{{yield "footer"}}
</footer>
When you invoke your component, you accept the yield name as a block param... and use an esleif chain!
{{#my-comp as |section|}}
{{#if (eq section "header")}}
My header
{{else if (eq section "body")}}
My body
{{else if (eq section "footer")}}
My footer
{{/if}}
{{/my-comp}}
PS eq is a subexpression helper from the must-have ember-truth-helpers addon.
PPS Relevant RFC: proposal, discussion.
Since it is not possible to have two {{yield}} helpers within one component (how would the component know where one {{yield}}'s markup stops and the next one begins?) you may be able to approach this problem from a different direction.
Consider the pattern of nested components. Browsers do this already with great success. Take, for example, the <ul> and <li> components. A <ul> wants to take many bits of markup and render each one like a member of a list. In order to accomplish this, it forces you to separate your itemized markup into <li> tags. There are many other examples of this. <table>, <tbody>, <tr>, <td> is another good case.
I think you may have stumbled upon a case where you can implement this pattern. For example:
{{#sfa-item}}
{{#first-thing}}
... some markup
{{/first-thing}}
{{#second-thing}}
... some other markup
{{/second-thing}}
{{/sfa-item}}
Obviously first-thing and second-thing are terrible names for your specialized components that represent the things you'd want to wrap with your first and second templates. You get the idea.
Do be careful since the nested components won't have access to properties within the outer component. You'll have to bind values with both outer and inner components if they are needed in both.
I'm really struggling to understand the routing concept in Ember but is more complicate than what it seem. From the doc. you can see you have different route whenever there is different url or path and if you have different path in the same url, easy you just need to create a nested template.
But what about when you have 3 different path in one url?
And what's the difference from this.resource and this.route?
Since live example are always better than pure theory, here my app.
In index or '/' I should render "list template", "new template" and when user click on a list link, the "note template" is render instead "new template".
My router:
Notes.Router.map(function () {
this.resource('index', { path: '/' }, function (){
this.resource('list', {path: ':note_title'});
this.resource('new', {path: '/'});
this.resource('note', { path: ':note_id' });
});
});
My template:
<script type="text/x-handlebars" data-template-name="index">
<div class="wrap">
<div class="bar">
{{input type="text" class="search" placeholder="Where is my bookmark??" value=search action="query"}}
<div class="bar-buttons">
<button {{action "addNote"}}> NEW </button>
</div>
</div>
{{outlet}}
</div>
</script>
<script type="text/x-handlebars" data-template-name="list">
<aside>
<h4 class="all-notes">All Notes {{length}}</h4>
{{#each item in model}}
<li>
{{#link-to 'note' item}} {{item.title}} {{/link-to}}
</li>
{{/each}}
</aside>
</script>
<script type="text/x-handlebars" data-template-name="new">
<section>
<div class="note">
{{input type="text" placeholder="Title" value=newTitle action="createNote"}}
<div class="error" id="error" style="display:none"> Fill in the Title! </div>
{{input type="text" placeholder="What you need to remember?" value=newBody action="createNote"}}
{{input type="text" placeholder="Url:" value=newUrl action="createNote"}}
</div>
</section>
</script>
<script type="text/x-handlebars" data-template-name="note">
<section>
<div class="note">
{{#if isEditing}}
<h2 class="note-title input-title"> {{edit-input-note value=title focus-out="modified" insert-newline="modified"}} </h2>
<p class="input-body"> {{edit-area-note value=body focus-out="modified" insert-newline="modified"}} </p>
{{edit-input-note value=url focus-out="modified" insert-newline="modified"}}
{{else}}
<h2 {{action "editNote" on="doubleClick"}} class="note-title" > {{title}} </h2>
<button {{action "removeNote"}} class="delete"> Delete </button>
<p {{action "editNote" on="doubleClick"}}> {{body}} </p>
{{input type="text" placeholder="URL:" class="input" value=url }}
{{/if}}
</div>
</section>
</script>
Or here the Js Bin: http://jsbin.com/oWeLuvo/1/edit?html,js,output
If my controllers or model are needed I will add that code as well.
thanks in advance
Your example seems to be working.
You just miss dependencies. You haven't included Handlebars and Ember.data
If you'd have checked your javascript console, you'd have seen the error thrown.
working example: http://jsbin.com/oWeLuvo/2/
In Ember a resource and a route are both routes. They have two names in order for Ember to differentiate between what is a resource and a route. In all honesty to remember that they are both routes and to keep your sanity you could refer to them respectively as a 'resource route' and a 'route'. A resource can be nested and have children resources or routes nested within them. Routes on the other hand cannot have nested anything.
Install the Ember Inspector if you are not already using it. It is a chrome extension and will help you with routes, controllers, templates, data and alot of other things with Ember, that you install into the Chrome Web Browser. The last that I heard the Ember Inspector is available in the FireFox Dev Tools as well. https://chrome.google.com/webstore/detail/ember-inspector/bmdblncegkenkacieihfhpjfppoconhi?hl=en
So if have a resource, you can nest a resource, and a route. The nested resources will preserve their name space, routes will get appended to nested name space. Remember you can not nest anything within a route.
App.Router.map(function() {
//creating a resource
this.resource('foo', {path: 'somePathYouPut'}, function() {
//nesting stuff inside of the resource above
//could open another nest level here in bar if you want
this.resource('bar');
//can not nest in route. Sad face. Sorry
this.route('zoo');
});
});
Since you can not nest anything into a route your {{outlet}} in the index template does not have any children to look for and by default and render into that {{outlet}}. I believe that is what you think is going to happen.
<script type="text/x-handlebars" data-template-name="index">
<div class="wrap">
<div class="bar">
{{input type="text" class="search"
placeholder="Where is my bookmark??" value=search action="query"}}
<div class="bar-buttons">
<button {{action "addNote"}}> NEW </button>
</div>
</div>
{{outlet}}
</div>
</script>
In your code you referred to the index as a resource, its a route. Since the index is a route, remember you can not nest elements within a route, your code should have looked more like this. Also your resource 'new' path / can be removed as well.
Notes.Router.map(function () {
//this is a route not a resource you had it as resource
//and tried to nest in your code
this.route('index', { path: '/' });
this.resource('list', {path: ':note_title'});
this.resource('new');
this.resource('note', { path: ':note_id' });
});
You get an index at each nested level starting with the top most level which comes from the application level but you don't have to explicitly define them in the Router they are just there. The index route that you get for free at each nested level is associated with its immediate parent by default and render into its parents 'outlet' by default. You could think of the Router looking something like this.
For Illustrative purposes only:
Notes.Router.map(function() {
//think of this as your application level stuff that Ember just does!!
//this is not written like this but to illustrate what is going on
//you would get application template, ApplicationRoute, ApplicationController
this.resource('application', function() {
//this is an index that you get for free cause its nested
//its that index template, IndexController, and IndexRoute you were using
this.route('index', { path: '/' });
this.resource('foo', {path: 'somePathYouPutHere' }, function() {
//since you started another nested level surprise you get another `index`
//but this one is `foo/index` template.
this.route('index', {path: '/'});
this.route('zoo');
});
});
});
The first part of the above exaggerated router example, Ember does automatically behind the scenes, its part of the 'magic' you hear about. It does two things it sets up an Application environment for its self and you get ApplicationRoute, ApplicationController, and a application template which are always there behind the scene. Second it makes that index and you get IndexController, IndexRoute, and a index template that you can use or ignore. So if you do nothing else, no other code that declaring and Ember App in a file like var App = Ember.Application.create(); and open the Ember Inspector and look into routes you will see the above mentioned assets.
Now, the resource foo in the above router is an example of a resource you would make and as you see you get an index in there because you started to nest. As mentioned above you do not have to define the index at each nest level, this.route('index', {path: '/'}); from inside foo could be totally omitted and Ember will still do the same thing. You will end up with foo/index template, FooIndexRoute, FooIndexController along with the expected foo template, FooRoute, and FooController. You can think of thefooindex as a place that says 'hey' before anything else gets rolled into my parentfoo` and gets rendered I can show something if you want, use me.
This is also a good time to highlight what happens with namespace when you nest route in a resource like this.route('zoo') in the above example. The namespace of the route zoo is now going to be appended to resource foo and you end up with foo/zoo template, FooZooRoute and a FooZooController.
If you were to change zoo to a resource nested in the foo resource this.resource('zoo'); the namespace will be keep for zoo. You will end up with 'zoo' template, ZooRoute and a ZooController. The namespace is kept. Ok, enough side tracking what about your App.
You said that you wanted / url of your app to render the list template. In order to accomplish that you have to override the default behavior that happens when Ember boots up. You override the top level / by adding the {path: '/'} to the first resource or route in the Router. From the fake router code above the first index route you get is associate with the application. By default if you do nothing Ember will push that index template into the application template. However that is not what you want you want your list template to be pushed into the application template when you hit the base url of /' for your App.
Notes.Router.map(function() {
this.resource('list', {path: '/'});
this.resource('new');
this.resource('note', { path: ':note_id' });
});
By adding the code {path: '/'} to the first resource like I did above, you are now telling Ember 'hey' when my app url reaches the base url of / or when you boot up use my list template not your default index template and render that into the application template. In addition since the other resources are not nested when your App transitions to those routes via the url they will blow out whats in the application template {{outlet}} and replace themselves in there.
You mentioned about defining a "path" in a route what that does is tell Ember how you want to represent that route in the url. In the above example if you leave the new resource as is, Ember by default will use the routes name as the path, the url would be /new. You can put any name in path for the new resource, this.resource(new, {path :'emberMakesMeKingOfWorld'}); and that name will be represented in the url as /emberMakesMeKingOfWorld and that long thing will still be associated with you new route. Your user might be confused what this url means but behind the scence you would know its tied to your new route. Its just an example but probably good practice to use descriptive names so your user knows what hitting a url in your App is meant to do.
After overriding the default index template that is associated with the application. I moved your code into the application template. The reason for that it seemed as though you wanted that bar with the 'newNote' action to be present all the time. If you want something present all the time in your App, like a navigation bar, footer, im sure you can think of better stuff, place it in the application template.
Here is a JSBIN and I adjusted you code a little
http://jsbin.com/oWeLuvo/8
I hope this helps Happy Coding.
I am using Twitter Bootstrap's modals throughout a web application. I am also using Mustache templates to generate the information to display inside the modals. The problem is that I find myself creating new modals for nearly every single form that is rendered to the screen and I feel that this violates DRY. I am considering creating a global modal object that is defined in the 'window' object and can be accessed throughout my application. When I want to display a new form I just render the form into the global modal object and then show it. Can anyone give me some advice on how to better handle numerous forms with modals?
I think you have the right idea. If you have a lot of modals, creating new ones can get repetitive. I've done something similar to what you proposed: create a single modal object that can be reused for a variety of modals.
In the past I used jQuery dialog, but the principle is entirely the same. Create a JavaScript module with some boilerplate HTML, that you can use to display any number of forms (essentially HTML content).
I'll try to propose a very basic implementation without knowing too much about your application.
HTML based from the Bootstrap example here:
<!-- Modal -->
<div id="myModal" class="modal hide fade" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h3 id="dynamicHeader">
<!-- Our header will go here -->
</h3>
</div>
<div class="modal-body" id="dynamicBody">
<!-- Our body will go here -->
</div>
<div class="modal-footer">
Close
Save changes
</div>
</div>
Notice the unique id's I've added to the h3 and the div.modal-body. We'll use those in JavaScript to dynamically inject each form's title and content.
JavaScript:
var ModalManager = (function() {
// cache some DOM references
var $dynamicHeader = $('#dynamicHeader');
var $dynamicBody = $('#dynamicBody');
var $myModal = $('#myModal');
var launch = function(header, body) {
$dynamicHeader.html(header);
$dynamicBody.html(body);
$myModal.modal(/* options here */);
};
return {
launch: launch
/* expose more API methods here! */
};
}());
Here is an example usage!
HTML w/ JavaScript:
<div id="form1">
<div class="formHeader">
Form One
</div>
<div class="formBody">
<p>Html and stuff</p>
</div>
</div>
<script type="text/javascript">
// Using a closure to protect globals
// This would probably go in your click handler to launch a given modal
(function() {
var headerHtml = $('form1 .formHeader').html();
var bodyHtml = $('form1 .formBody').html();
ModalManager.launch(headerHtml, bodyHtml );
}());
</script>
Finally, I wrapped all of that up in a jsFiddle which demonstrates the ability to launch two different forms.
Link: jsFiddle
I used jQuery as it should be included with the Bootstrap code for the modal. It will pull out the header and body HTML that are specific to each form, and populate your common modal HTML in the DOM. Then when you launch the modal it will display what looks like a different modal, but you've centralized the common aspects so you're not repeating them anymore!
There's a lot more you can do but that's basically the gist. My own implementation exposed means to configure the buttons dynamically, for example. Depending on what you want configurable, you can add an options parameter that passes on to the modal() function, or has other properties specific to your application that ModalManager can handle. You can definitely use templating to carry out some of these features, it's just not essential to the example I've setup.
I hope that helps!
I'm not quite sure what you are asking. Mustache does have looping functionality, so you could pass in an array of modals, and Mustache should generate all of the code.
I actually just created a video showing how to build a Mustache template for Twitter Bootstrap's Alert component, and implement it via PHP and JavaScript. It also features the looping functionality I spoke of. Maybe that will help? Here is the link: http://mikemclin.net/mustache-templates-for-php-and-javascript/