Ember.js each loop with external/outbound link - javascript

Hello and thanks in advance for the help. I am using ember.js to make a basic site. I have an each loop, inside of which I want it to display a variable while linking to an external site (eg. google.com which I have stored as a different variable).
For some reason, when I am doing the inside the each loop, I am getting the error, "This link-to is in an inactive loading state because at least one of its parameters presently has a null/undefined value, or the provided route name is invalid." I cannot seem to figure out how to get this to go to an external site when clicked on. Here is my each loop and the example json.
{{#each}}
{{#link-to 'theme' theme tagName="tr"}}
<td class="noWrap">{{#link-to 'theme' title}}{{title}}{{/link-to}}</td>
<td>{{#link-to 'theme' this}}<img {{bind-attr src="image"}} \>{{/link-to}}</td>
<td class="tdCenter"><a {{bind-attr href="link"}}>{{price}}</a></td>
<td>{{description}}</td>
<td class="tdCenter">{{columns}}</td>
{{/link-to}}
{{/each}}
{
id: 1,
title: 'Title',
price: '$10',
description: 'random description',
columns: 1,
link:'https://google.com',
image: 'images/image.jpg'
}
and this is my theme route
App.ThemeRoute = Ember.Route.extend({
model: function(params) {
return App.THEMES.findBy('title', params.title);
}
});
Let me know if you have any ideas. Thanks again!
David B

If the {{#link-to 'theme' theme tagName="tr"}} is replaced with <tr> , the table row will be rendered without it being a link (unless this is not acceptable, unfortunately the link-to helper's purpose at that point is not clear to me).
So the result would be something like,
http://emberjs.jsbin.com/wuvazica/1#/theme/1
http://emberjs.jsbin.com/wuvazica/1/edit
hbs
<script type="text/x-handlebars" data-template-name="theme">
<table>
{{#each}}
<tr>
<td class="noWrap">{{#link-to 'theme' title}}{{title}}{{/link-to}}</td>
<td>{{#link-to 'theme' this}}<img {{bind-attr src="image"}} \>{{/link-to}}</td>
<td class="tdCenter"><a {{bind-attr href="link"}}>{{price}}</a></td>
<td>{{description}}</td>
<td class="tdCenter">{{columns}}</td>
</tr>
{{/each}}
</table>
</script>
By the way, the error that is shown it is due to the parameter theme in {{#link-to 'theme' theme tagName="tr"}}, if it is removed the error will go away but the rendered html will not function properly (at least what i noticed in the quick example i provided), because of the {{#link-to}} helper at that point.

Related

Emberjs toggle effect of dynamic data

I have a table loaded with dynamic data from an api. The <td></td> displays a company name and when clicked I want a div that has the companies information to be displayed. I saw a few examples and this is what I did:
Here is my handlebars:
{{#each m in model}}
<tr>
<td >
<p {{ action 'displayDetails' }}>{{m.name}}</p>
{{#if showDetails}}
<div class="company_details">
<p><strong>Wesbite URL: </strong>{{m.website}}</p>
<p><strong>Development Wesbite URL: </strong>{{m.dev_website}}</p>
</div>
{{/if}}
</td>
</tr>
{{/each}}
as you can see when you click on the name of the company the displayDetails action is called
here is my controller:
export default Ember.ArrayController.extend({
showDetails: false,
actions: {
displayDetails: function(){
this.toggleProperty('showDetails');
}
}
});
This works great; however if I click the company name all of the company details are displayed not just the company I clicked on. I obviously just want to display the specific detail of the company I clicked on how can I go about this?
Add a property to each item in the model named something like isDetailsShown.
You can modify you action by passing the item into the method.
<p {{ action 'displayDetails' item}}>{{m.name}}</p>
In the action, just toggle the property of the passed in item.
displayDetails: function(item){
item.toggleProperty('isDetailsShown');
}
Finally modify the if condition in the template to look like
{{#if item.isDetailsShown}}

HandlebarsJS each only prints last item in array

This is a branch off from this question: Handlebars.JS (w/ Dashbars) parse error "expecting open_endblock got inverse" (There's also a codepen therein.) I posted this there because I thought the problems were related, maybe (since {{else}} can be used with if's or each's,) but that turned out not to be the case.
Specifically:
I'm also having the problem of my outermost {{#each}} looping in such a way that only the last record returned is being output through the template. Everything logs in {{log this}} after that opening {{#each}}, but it's not even hiding in the HTML output somewhere.
So my table SHOULD have 4 rows, and objects 0 through 3 log to the console, but only the fourth item, item 3, is added to the table. Inner each's work as expected, as do any and all other iterator functions; I'm not sure why it's JUST the outer one that's failing. The JSON from which my array of objects is created validates in every single linter I've used, as does my JavaScript. Atom is supposed to have a handlebars linter, but it doesn't seem to actually...umm...work.
I AM using Dashbars with this, (with both its lodash.js and moment.js dependencies,) but this bug has existed since before I started using that library. FWIW, jQuery 2.1.3 IS installed, and loaded before any other library, and all of that is in the <head> tag. (I don't see where it would matter, but just in case.) And since JavaScript is involved, yes, I'm in Chrome. The version of Handlebars is the latest, 3.0.1, using the full version because my use-case doesn't allow pre-compiling. Another NB is that all of this is embedded in a .cfm file (not my choice,) so everything does get run through the CFML interpreter first.
So...I'd managed to outsmart myself again. I've created a fork of my original code, but HTTPS policy in my browser is keeping it from working =-\
The "money" differences are as follows (can't use SO's <ul> because I can't embed code in a bullet-block)
*I'd been trying to jam my template into a <tr> element, to avoid having Handlebars process more lines of code than I thought was necessary:
<tr class="searchResults" id="searchResultsHTML"></tr>
<script id="result-template" type="text/x-handlebars-template">
<!--- To accommodate for that this would all begin and end with a <tr>, I tried this: --->
{{#unless #first}}
<tr>
{{/unless}}
<!--- The rest of the template --->
{{#unless #last}}
</tr>
{{/unless}}
</script>
*In good-programmer fashion, I'd forked my actual file several times, trying different things. (Mostly did this to avoid endless git resetting and branching; I know git's meant for this sort of thing, but I just wanted to be able to refresh old and new tabs at once. Anyway!) Took out those {{unless}} blocks and...apparently, I'd misunderstood what that does (since the official documentation is a bit...scant...on that point. There's no TRUE inverse of {{#if}}) For the sake of those who don't want to bother clicking to the codepen:
<div class="searchResults" id="searchResultsHTML">
<script id="result-template" type="text/x-handlebars-template">
<table id="resultTable">
<tr>
<th>Personal Information</th>
<th>Education</th>
</tr>
<!--- The log tag DOES output all records, even the ones that aren't showing in the HTML. --->
{{#each this}} {{log #index}} {{log this}}
<tr>
<td>{{#with basicInformation}}
<p>{{MASTER_CUSTOMER_ID}} ({{CUSTOMER_STATUS_CODE}})<br />
{{SEARCH_NAME}}<br />
(<span class="lightBlue">ADD:{{ADDOPER}}–{{{d-format 'MM/DD/YYYY' (d-date 'YYYY-MM-DD' ADDDATE)}}}</span>)</p>
<p>DOB:{{{d-format 'MM/DD/YYYY' (d-date 'YYYY-MM-DD' BIRTH_DATE)}}}<br />
{{/with}}</td>
<!--- More table columns; six total --->
<td>{{#each education}}
<p>{{INSTITUTION_NAME}} ({{{d-format 'YYYY' (d-date 'YYYY-MM-DD' BEGIN_DATE)}}}–{{{d-format 'YYYY' (d-date 'YYYY-MM-DD' END_DATE)}}})</p>
{{else}}
<p>No education records found</p>
{{/each}}
</td>
</tr>
{{else}}
<tr class="alert largerError" id="errorMessageRow">
<td class="empty" id="errorMessage" colspan="6">Either there has been an error, or your search did not return any records from any datasource.</td>
</tr>
{{/each}}
</table>
</script>
Yes, I stuck with ColdFusion comments; since those are ONLY for my reference, or the poor next developer, they don't need to go to the client and that way Handlebars isn't taking valuable time rendering them. (I'm already annoyed Handlebars is going to have to churn through my header row and a bunch of other text.) This tool searches our databases for customer numbers that MIGHT represent accidental duplicate accounts, so if you search for John Smith...oi!
So for anyone who Googled their way over here...I'll be glad if this helps even one person avoid spinning their wheels the way I did.

How to create an Ember JS Master Detail where detail appears between rows of data

I'm trying to create a page that contains master/detail, which is obviously easy using a static {{ outlet }} in ember, however, I'd like the detail to slide down after the row that was selected. For example, on this page: http://jsbin.com/ijejap/1/edit, if the detail of the name was to appear after the currently selected name instead of at the bottom of the page.
The problem I'm trying to solve is that I can't have an outlet within a repeater so that when I click Row 1, I want the outlet to be positioned below Row 1, and when I click Row 2, I want the outlet after Row 2. In other words, I want to dynamically position an outlet, I think, unless there's another way to do it.
Ok, I finally made a conditionnal outlet depending on wether the user is selected or not, but I had to work with DS.Model and its FIXTURES to make this work.
To be brief, I added an action before the link to set a selected property on the user object and record which user is selected to prevent having two users at the same time.
The position of the {{outlet}} then depends on the current selected user.
Here's the template part:
{{#each model}}
<li {{action 'select' this}}>
{{#link-to "user" this}}{{first}}{{/link-to}}
</li>
{{#if selected}}
{{outlet}}
{{/if}}
{{/each}}
And the App.ApplicationController part:
App.ApplicationController = Ember.ArrayController.extend({
currentUser: null,
actions: {
select: function(newUser) {
var oldUser = this.get('currentUser');
if(newUser !== oldUser) {
if(oldUser)
oldUser.toggleProperty('selected');
this.set('currentUser', newUser);
newUser.toggleProperty('selected');
}
}
}
});
Finally, the model definition:
App.ApplicationRoute = Ember.Route.extend({
model: function() {
return this.store.find('user');
}
});
App.User = DS.Model.extend({
first: DS.attr(),
last: DS.attr(),
avatar: DS.attr()
});
Here's the link to the JSBin: http://jsbin.com/cilari/3/
You can make use of components power here.
I made it on JSBin : http://jsbin.com/zovos/2/
First, you declare your user details as a new component, say user-details :
<script type="text/x-handlebars" id="components/user-details">
<li>{{user.first}}</li>
<h2>
{{user.first}} {{user.last}}
<img {{bindAttr src="user.avatar"}} class="pull-right" width=50 />
</h2>
<dl>
<dt>First</dt>
<dd>{{user.first}}</dd>
<dt>Last</dt>
<dd>{{user.last}}</dd>
</dl>
</script>
Then you call it from your list, using the current user as the user in the component :
<script type="text/x-handlebars">
<div class="container">
<div class="row-fluid">
<ul class="nav nav-list span3 well">
{{#each model}}
{{user-details user=this}}
{{/each}}
</ul>
</div>
</div>
</script>
You add the selected property to your component and an action like expandDetails :
App.UserDetailsComponent = Ember.Component.extend({
selected: false,
actions: {
expandDetails: function() {
this.toggleProperty('selected');
}
}
});
Finally, you add the action to your component template and a conditional display on the selected property :
<script type="text/x-handlebars" id="components/user-details">
<li><a {{action "expandDetails"}}>{{user.first}}</a></li>
{{#if selected}}
<h2>
{{user.first}} {{user.last}}
...
</dl>
{{/if}}
</script>
And, of course, you get rid of your user route.
Here is a link to Ember's Guide that show this on a sample post example : http://emberjs.com/guides/components/handling-user-interaction-with-actions/
Hope this helps.
Are you looking for an accordion?
http://addepar.github.io/#/ember-widgets/accordion
I have an idea but unproved yet, maybe you can try it.
For detail, you only need one template. For master, you need write several named outlet (see: http://emberjs.com/guides/routing/rendering-a-template/) and place each below the corresponding item of master view.
When each master item has been clicked, you need pass a param to the detail route in order to let it know what kind of data should be populated.
The key part is the renderTemplate hook of route (see: http://emberjs.com/api/classes/Ember.Route.html#method_renderTemplate). You can take advantage of it because it allows you to specify which named outlet should be used to render the template.
Or you may load all details' data at once and cache it, then pass an identifier when master item got clicked, in this way you can filter the model in advance and get prepared for later renderTemplate use.
This is just an idea and currently don't have spare time to try it, but as your wish, you got the dedicated route, controller and model. I think it is highly doable, let me know it you make any progress.

Handlebars does not fill table

I am using Handlebars (v 1.0.0) to fill a table in HTML.
However somehow it does not fill the table like I am used to.
Here is my template:
{{#if users}}
<table>
{{#each users}}
<tr>
<td>{{username}}</td>
<td>{{email}}</td>
</tr>
{{/each}}
</table>
{{else}}
<h3>No users found!</h3>
{{/if}}
So I does find users because I do not see the "No users found!" and when I call an empty object it does show the "No users found!".
When I do not use the table and print out these users like the next example. The usersnames and mail address' will show up in my HTML.
{{#if users}}
{{#each users}}
{{username}}<br/>
{{email}}<br/>
{{/each}}
{{else}}
<h3>No users found!</h3>
{{/if}}
Here is how my template is build in the javascript:
var htmlSource = $(data).html();
var template = Handlebars.compile(htmlSource);
var compiled = template(usersArray);
that.$el.html(compiled);
Now when I console.log the compiled object, it doesn't show the table already.
Do you know why it doesn't work and can you help me out?
EDIT:
I just tested some more and found that the data will show up in the HTML when I leave out the <table> tags. However the <tr> and <td> won't show up in html. The data in it will be shown.
EDIT 2:
I found out that it seems to be a jquery issue or javascript issue.
When I console.log the htmlSource the HTML template is changed to this:
{{#if users}}
{{#each users}}
{{/each}}
{{else}}
<h3>No users found!</h3>
{{/if}}
<table><tr>
<td>{{username}}</td>
<td>{{email}}</td>
</tr></table>
As you can see the table is moved outside the if statement.
I tried other jquery versions (2.0.2, 2.0.3, 2.0.1, 1.10.2) but this didn't work.
I tried using the innerXHTML script however this works the same as jQuery.
So my guess is that it might be a FireFox issue (though I tried 25 and 26), Chrome does the same... maybe something in EcmaScript??
I will let you know soon...
EDIT 3:
I created an html file with the html I need. With a script I get the specific section of html I need and place this in the data variable.
Now when console logging the data (console.log(data)) there is nothing wrong.
When console logging the data with jQuery, the html is altered: console.log($(data));
It seems something is going wrong there.. but only when using table tags.
Is this something jQuery can't handle? I don't know. I know how to overcome this issue by using the script tag... Though I would like to load that using require ;)
P.S. nemesv thanks for you're edits ;)
Make sure you're outputting your template in a tag so the browser don't try to parse it.
<script type="x-template" id="the-tpl">
{{#if users}}
<table>
{{#each users}}
<tr>
<td>{{username}}</td>
<td>{{email}}</td>
</tr>
{{/each}}
</table>
{{else}}
<h3>No users found!</h3>
{{/if}}
</script>
If there's no type attribute on the script tag, the HTML parser will find a bug and try to resolve it. From your bug details, it really looks like it is the case here.
Using require
As you noted you load using Require, than make sure you load your template using the text! plugin: require("text!path/to/template.html")
To be even fancier, you could delegate all the handlebars compilation to a handlebars template loading plugin so templates are precompiled inside your build - this is probably the best.
Either way, your issue is that your template get parsed by the HTML parser and that is breaking your template content. Make sure you load it as "text" via XMLHttpRequest or with require or inside a script tag correctly typed.
Simon's solution is the right one and problem is discussed in various comments.
I am just putting the pieces here.
as #rescuecreative pointed out some browsers remove empty <table> tags when the html is inserted in DOM.
The case here is a bit similar. The browsers do misbehave when they see invalid markup. but in this case its not because of missing tr, td inside table. its because of extra lines before tr in the table.
Your problem starts here.
var htmlSource = $(data).html();
whatever loading mechanism you are using, you inserting the template in the DOM before compiling it with handlebars.
This is what happens when you add uncompiled template to DOM.
{{#if users}} <!-- browser does not understand this and tries to print it as it is-->
<table> <!-- browser sees the table tag and immediately looks for child TR, tbody, thead -->
{{#each users}} <!-- but instead finds plain text and hence considers it as invalid markup -->
<tr> <!-- the same story is repeated again>
<td>{{username}}</td>
<td>{{email}}</td>
</tr>
{{/each}} <!-- such plain strings are taken out of table markup and placed before or after depending upon the browser, in case of chrome placed before the table -->
</table>
{{else}}
<h3>No users found!</h3>
{{/if}}
This is how chrome renders it -
{{#if users}}
{{#each users}}
{{/each}}
<table>
<tbody>
<tr>
<td>{{username}}</td>
<td>{{email}}</td>
</tr>
</tbody>
</table>
{{else}}
<h3>No users found!</h3>
{{/if}}
I dont have firefox with me as of now. but i am sure firefox has its own markup correction method.
after this when you take it out of DOM using jquery and compile
var htmlSource = $(data).html();
var template = Handlebars.compile(htmlSource);
it will just output this
<table>
<tbody>
<tr>
<td>{{username}}</td>
<td>{{email}}</td>
</tr>
</tbody>
</table>
if you want you can dump it after compilation to see.
var compiled = template(usersArray);
console.log(compiled);
that.$el.html(compiled);
This also explains why you are getting username and email when you strip out table markup in the original template.
Now to solve this issue either use text plugin as Simon pointed out. or place the template inline.
I too use requriejs with something along the following
define([
'underscore', // you can replace this with handlebars
'text!path/to/template.html'
], function(_, Tmpl){
return _.template(Tmpl); //and handlebar pre-compilation here
});
this way you can also replace this module with a pre-compiled template in your build process.

Ember.js - Ember.Js View inside handlebars conditional: view not being displayed?

I've got a handlebars conditional statement in a table, and inside that I have a view.
If i remove the conditional then the view template is displayed. If I remove the view template, the straight html is displayed. But if both are there, the view template is never shown, and there is no error shown in the console:
<script type="text/x-handlebars" data-template-name="application">
<table>
<tr>
<td>
<button {{action 'click_me'}}>Click me</button>
</td>
</tr>
{{#if controller.new_visible}}
{{#view App.MyView}}
<tr>
<td>
Hello!
</td>
</tr>
{{/view}}
{{/if}}
</table>
</script>
What am I doing wrong?
Further to this, it would seem (from http://emberjs.com/guides/understanding-ember/the-view-layer/, section 4) that Ember creates virtual views for Handlebars logic block helpers (like {{#if}}, {{#unless}}, and so on. They're not part of the regular view hierarchy, so they don't play nicely with regular nested child views.
I suspect the answer to this is to be found somewhere in this line:
"When the path passed to an {{#if}} or {{#with}} changes, Ember automatically re-renders the virtual view, which will replace its contents, and importantly, destroy all child views to free up their memory." (from the section linked above).
My best guess is that the {{#if}} block is re-rendering on insert, which destroys the 'child' {{#view}} block within it.
move your condition inside view. see this jsFiddle
{{#view App.MyView}}
{{#if controller.new_visible}}
<tr>
<td>
Hello!
</td>
</tr>
{{/if}}
{{/view}}

Categories