How can I determine if a component returns nothing dynamically? - ember.js

I have a ember component that is accessing a database and returning the results in a datatable type UI component. I would like to be able to use "N/A" when the result of the component is null or nothing.
For example, I have:
{{each bar in foobars}}
<td class="classyTD">
{{getBars bar=bar}}
</td>
{{/each}}
This works great when I have data, but returns nothing when I don't have data. The designers would prefer an "N/A". Modifying the database isn't an option and while modifying the component getBars is an option, it will be extremely painful.
Is there a method/way to handle this after the execution of the component? If not, or if it's a terrible idea - I'll suffer through changing the component, I trust the community's opinion.

You really should do this inside the component template. You can give the N/A string as a parameter, if that's of any help: http://emberjs.jsbin.com/lemabekuwi/2/edit?html,css,js,output
Or you could change the component that it indicates emptiness through a class and use some css magic: http://emberjs.jsbin.com/duqazahegi/1/edit?html,css,js,output

If you want to limit logic in the handlebars you can have the following in the component js file:
({
setBar: (function() {
if (!this.get('bar')) {
return this.set('bar', 'N/A');
}
}).observes('bar')
});

Related

Ember helper has undefined values

I can't figure out why the helper I created in app/helpers/shop-name.js:
import { helper } from '#ember/component/helper';
export function shopName(shop) {
return shop.identifier + '-' + shop.name;
}
export default helper(shopName);
doe not format the values as needed in my template application.hbs:
<select>
{{#each model as |shop|}}
<option value={{shop.id}}>{{shop-name shop}}</option>
{{/each}}
</select>
I get undefined-undefined displayed in my select list.
If I do the same conversion directly in the template:
<option value={{shop.id}}>{{shop.identifier}}-{{shop.name}}</option>
it works as needed. What's wrong with that helper ?
I'm using Ember 3.0.
Thank you.
I'm not sure that helpers can take inputs that aren't strings. I hope someone can confirm.
Anyway, your use case is really better handled at the model level, using a computed property:
shopName: computed('identifier', 'name', function() {
return `${identifier} - ${name}`;
})
and then you would just use {{shop.shopName}} in your template
There are multiple issues in your code.
shop is an object and I bet it's an Ember.Object. If dealing with Ember.Objects, you should always use get() method to retrieve values for now. This might change soon. Since you are not using get() method, you get undefined. Embers template engine is using getter and therefore it's a huge difference between shop.identifier in your helper and {{shop.identifier}} in your template. Resolve the issue by shop.get('identifier') or import { get } from '#ember/object'; [...] get(shop, 'identifier'); Use the last one if you can't be sure that shop is an instance of Ember.Object.
Since you are passing shop and not shop.identifier as argument to your helper, the helper will only be recomputed if the reference of shop changes (e.g. replaced by another object). Changes to the value of it's properties will not recompute the helper.
One solution for you problem is to use a computed property as suggested by ctcpip. Another one is the concat helper: {{concat shop.identifier '-' shop.name}}.

How to render Ember component on same line

This Ember template code...
<div class="detail bedrooms">
<span>Number of bedrooms:</span> {{rental.bedrooms}}
</div>
<div class="detail temperature">
<span>Current Temperature:</span> {{city-temperature location=rental.city}}
</div>
...results in this rendering...
How could one get the "25C" text to render on the same line as "Current Temperature" in the same way that "San Francisco" is on the same line as "Location"?
I have tried getting the city-temperature component to return only text, but (secondary question here) is it even possible for a component to return only text if the text is from a remote ajax request since the promise seems to need a DOM element to append to?
This Ember app is available here on GitHub. It's a modified version of the official Ember.js tutorial app with this "Current Temperature" detail added.
The problem is that; city-temperature is a component; and by default ember components is assigned div as their tags. Due to this; the content of city-temperature starts at a new line.
What can you do? Play with css and make sure div tag of city-temperature does not start at a new line; but instead floating right within the owner div. The second option is making city-temperature component tagless; so that it does not start at a new line. To achieve that; you can just declare:
tagName: ''
within city-temperature.js. You can see the following twiddle to see the second option I mentioned.
After reading your comments; I have forked your repository and made minor modifications to achieve what you want. You can check my repository and my corresponding commit to see what I changed.
Basically; I really did not like the idea that weather service is returning a DOM element. I have changed it to return an instance of ember promise (I mean Ember.RSVP.Promise). Within city-temperature.js I just set a retrieved weather value from weather service and set it to the component instead of making DOM modification. Finally; I modified city-temperature.hbs to render weatherValue and added a simple css item in order to prevent weather-container div to insert a new line break.
I am not sure; whether you will like my solution or not; but I guess you will retrieve the visual appearance you want. You can always rely on promises for async tasks; you do not need to create DOM elements and pass them around to components and append them to somewhere within DOM tree of the corresponding component. I believe one of the reasons we are making use of a framework like Ember is to prevent such DOM modifications. Best regards.
An ember component adds a div by default. If you don't want to add this div tag, you need to set tagName to an empty string.
That's called tagless component.
Further, for your case, you can give a tagName='span' so you will not need a span in your template.
export default Ember.Component.extend({
tagName: 'span',
classNames: ['weather-container'],
weather: Ember.inject.service(),
//rest of your code...
});

Simple search with Emberjs not working

I am trying to implement a simple search feature, which will filter the results and update the listing.
I tried almost every single tutorial out there, every tutorial seems to be working with its jsfiddle but when I apply the same thing on my project, it does not work at all.
Here is what I am having as a first problem, my search field does not seems to be bound with computed property in controller.
I also tried it as a component but again same issue, if I type anything in search field it does not reflect anything.
Let me share my code here,
input type="text" value=searchText placeholder="Search..."
searchResults: ( ->
console.log "On every key press it should come here.."
model = #get('model')
searchText = #get('searchText')
filterByPath = #get('filterByPath')
visualPath = #get('visualPath')
if searchText
searchText = searchText.toLowerCase()
model = model.filter(item) ->
Ember.get(item, filterByPath).toLowerCase().indexOf(searchText)>= 0
model.getEach(visualPath)
).property('searchText')
Tried almost the same thing with component, but no luck so far. I am not using handlebars but Emblemjs.
You actually need to use computed property in template if you want it to be compiled, then recompiled etc. It is somewhat lazily evaluated so you can't debug that without using this in template first or calling from JavaScript code. So basically you need to use searchResults in your template first.
In demo there was model instead of searchResults. I guess in your application you have similiar setup and same bug.
Fix:
{{#each searchResults as |item|}}
<li>{{item}}</li>
{{/each}}
Working demo.

Waiting for template render in Meteor, inconsistent

Been scratching my head on this, so here's the simplest way to view it - I'm using this:
Template.today.rendered = function() {
console.log(this.firstNode.children.length);
};
Simply to try and get the count of items that are supposedly rendered. The template looks like:
<template name="today">
<div class="todaySlider">
{{#each dayOfWeek}}
{{> singleDay}}
{{/each}}
</div>
</template>
and if it's of any importance, singleDay looks like:
<template name="singleDay">
<div class="day {{isCurrent}}">
<h2 class="date">{{date}}</h2>
{{#each items}}
{{> item }}
{{/each}}
</div>
</template>
I'm trying to wait for all the "singleDays" to render, however that count I'm logging is usually different on refresh. I'll get anything from 0 to the correct value, and I don't understand why. This seems to be the right place to call it, I fear that maybe the double "each" is too slow?
I've tried timers (which I honestly shouldn't) and even DOM Mutation Observers (which seem like overkill) but surely there is a pure Meteor approach to this, any ideas?
Template.rendered happens when the template is rendered, but that doesn't mean there'll be any data in it.
I'm pretty sure you'll need to do this inside a helper.
each helpers don't have to return cursors, they can also return an array. If the number of "singleDays" is short, you could send an array to the template instead of a cursor. It's kind of ugly, and there might be a better way to do this, but I think this will work.
Template.today.helpers({
dayOfWeek: function() {
var days = DaysCollection.find({}).fetch();
if (days[days.length - 1]) days[days.length - 1].isLast = true;
return days;
}
});
I assume {{isCurrent}} is where you add the extra class that you're talking about. If so, just have the isCurrent helper look for this.isLast to be true.
Seems like what is happening here is that the template is being rendered before the Mongo collection is sent to the client. To be more specific, meteor renders your template as fast as possible, which means that it has no concept of 'waiting' for any data to be sent from the server to the client. Therefore, if you place anything indirectly regarding database queries inside of a non-reactive call (Template.rendered), then it will execute with the data as undefined.
I'm assuming your dayOfWeek helper looks something like this:
Template.today.helpers({
daysOfWeek: function () {
var today = CollectionName.findOne();
return today.daysOfWeek;
}
})
(Or maybe you are using the router to pass the day directly to the template)
Either way, within your router you need to wait for the Mongo collection item to be sent to the client before any rendering takes place. If you are using Iron-Router, you simply have to 'wait' for your data/subscription.
More information can be found here: https://github.com/iron-meteor/iron-router/blob/devel/Guide.md#wait-and-ready
If you are still using autopublish, then you can replace the subscription with your database query.

How to render a component based on model parameter

I have the requirement to render a template based on a property of my model.
My stratergy was to use custom if block helpers for the handle bars templates, something like this:
{{#ifequals type 'cars'}}
{{cars-component}}
{{/ifequals}}
{{#ifequals type 'planes'}}
{{planes-component}}
{{/ifequals}
but I cant create a block helper that resolves parameter passed by the template to the helper.
if I use Handlebars.registerHandlebars, it resolves the variable name insted of the variable.
the reason i need to do this is because it is part of a plugin framework.
Option blocks (special if statements) don't work properly in ember handlebars helpers. https://github.com/emberjs/ember.js/issues/2237
isCar: Ember.computed.equals('type', 'cars')
isPlane: Ember.computed.equals('type', 'planes')
{{#if isCar}}
{{cars-component}}
{{/if}}
{{#if isPlan}}
{{planes-component}}
{{/if}}
It almost looks like you can do it, but there is a problem with it, as is pointed out in the github issue above.
Ember.Handlebars.helper('iff', function(value,property, options) {
if(value === property){
return options.fn.apply();
} else {
return options.inverse.apply();
}
});
http://emberjs.jsbin.com/UhOWeWiJ/1/edit
http://emberjs.jsbin.com/UhOWeWiJ/2/edit
You will get a bound blocks error when you try and implement something like this and the values actually change
https://github.com/emberjs/ember.js/issues/2237
Uncaught You can't use appendChild outside of the rendering process
http://emberjs.jsbin.com/UhOWeWiJ/3/edit
http://emberjs.jsbin.com/UhOWeWiJ/3/edit