I'm having some issues with Ember Power Select. I'm able to deal with my data properly alone on the template, but for some reason power select won't let me get to any of the data from the fetch call in my component code below...
components/flight-search.js
import Component from '#ember/component';
export default Component.extend({
flightResults: null,
airResults: null,
actions: {
searchIATA(term) {
let query = `https://iatacodes.org/api/v6/autocomplete?api_key=e7c1b7cf-62fb-440c-a0ef-4facebe1ab86&query=${term}`;
return fetch(query).then(function(response) {
return response.json();
}).then(results => {
this.set('airResults', results);
});
},
}
});
components/flight-search.hbs
{{#each airResults.response.airports as |airport|}}
{{airport.name}} - {{airport.code}}
{{/each}}
{{#power-select-typeahead
search=(action "searchIATA")
triggerClass="bootstrap-theme-trigger"
dropdownClass="slide-fade bootstrap-theme-dropdown"
selected=selectedType
loadingMessage="Searching..."
placeholder="e.g. New York, NY"
onchange=(action (mut selectedType))
as |result|
}}
<div class="-detail">
{{result.response.airports.name}}
</div>
{{/power-select-typeahead}}
Notice above and outside of power select I've illustrated that I can get my data as needed.
Much appreciated, thanks!
You can try this, I have included an arrow function in both place.
searchIATA(term) {
let query = `https://iatacodes.org/api/v6/autocomplete?api_key=e7c1b7cf-62fb-440c-a0ef-4facebe1ab86&query=${term}`;
return fetch(query).then(response => response.json()).then(results => {
this.set('airResults', results);
});
}
Related
I'm trying to implement a simple autosuggest in a component. I'm testing fastboot and therefore am using ember-network to communicate with my API. I'm not using ember-data right now. Whether or not this is the "ember" way to do it is a different question...I'm just trying to get this to work.
My component JS:
import Ember from 'ember';
import fetch from 'ember-network/fetch';
export default Ember.Component.extend({
searchText: null,
loadAutoComplete(query) {
let suggestCall = 'http://my.api.com/suggest?s=' + query;
return fetch(suggestCall).then(function(response) {
return response.json();
});
},
searchResults: Ember.computed('searchText', function() {
let searchText = this.get('searchText');
if (!searchText) { return; }
let searchRes = this.loadAutoComplete(searchText);
return searchRes;
})
});
And in the template:
{{input type="text" value=searchText placeholder="Search..."}}
{{ log "TEMPALTE RESULTS" searchResults }}
{{#each-in searchResults as |result value|}}
<li>{{result}} {{value}}</li>
{{/each-in}}
The template log directive is outputting this in my console:
The data is in "suggestions", so I know the fetch is working. I just can't figure out how to get at it. I can't loop over '_result'. What do I need to do to parse this and use it in a template?
Returning promise from computed property is not just straight forward, it's little tricky.
Option1. You can use ember-concurrency addon for this use case. You can look at auto complete feature explanation doc
Your component code,
import Ember from 'ember';
import { task, timeout } from 'ember-concurrency';
export default Ember.Component.extend({
searchText: null,
searchResults: task(function*(str) {
this.set('searchText', str);
let url = `http://my.api.com/suggest?s=${str}`;
let responseData = yield this.get('searchRequest').perform(url);
return responseData;
}).restartable(),
searchRequest: task(function*(url) {
let requestData;
try {
requestData = Ember.$.getJSON(url);
let result = yield requestData.promise();
return result;
} finally {
requestData.abort();
}
}).restartable(),
});
and your component hbs code,
<input type="text" value={{searchText}} onkeyup={{perform searchResults value="target.value" }}>
<div>
{{#if searchResults.isIdle}}
<ul>
{{#each searchResults.lastSuccessful.value as |data| }}
<li> {{data}} </li>
{{else}}
No result
{{/each}}
</ul>
{{else}}
Loading...
{{/if}}
</div>
Option2. You can return DS.PromiseObject or DS.PromiseArray
import Ember from 'ember';
import fetch from 'ember-network/fetch';
export default Ember.Component.extend({
searchText: null,
loadAutoComplete(query) {
let suggestCall = 'http://my.api.com/suggest?s=' + query;
return fetch(suggestCall).then(function(response) {
return response.json();
});
},
searchResults: Ember.computed('searchText', function() {
let searchText = this.get('searchText');
if (!searchText) { return; }
//if response.json returns object then you can use DS.PromiseObject, if its an array then you can use DS.PromiseArray
return DS.PromiseObject.create({
promise: this.loadAutoComplete(searchText)
});
})
});
Reference ember igniter article- The Guide to Promises in Computed Properties
First of all, IMO, it is not a good practice to call a remote call from a computed property. You should trigger it from input component/helper.
{{input type="text" value=searchText placeholder="Search..." key-up=(action loadAutoComplete)}}
And the new loadAutoComplete would be like:
loadAutoComplete(query) {
//check query is null or empty...
let suggestCall = 'http://my.api.com/suggest?s=' + query;
return fetch(suggestCall).then((response) => {
this.set('searchResults', response.json());
});
},
Your searchResults will no longer need to be a computed property. Just a property.
I am using a simple table for ember when same any record it got saved in database.
but there is no effect on table i have to click of sort icon see to changes.
Table code
{{#simple-table class="table table-hover" tData=refbody tColumns=headerConfig as |table|}}
{{#table.header-row as |row column|}}
<div class="th-inner">
{{#each (get row column) as |cell|}}
{{#if cell}}
<span>{{cell}} <i class="sort-arrow"></i></span>
{{/if}}
{{/each}}
</div>
{{/table.header-row}}
{{#table.body as |row|}}
{{reference-row record=row type=param action="updateEmployee"}}
{{/table.body}}
{{/simple-table}}
Rout code
saveempRcd: function (empRcd) {
if(this.isValidRecord(empRcd, empRcd.get('employee'))){
this.controller.set('loader', true);
empRcd.save().then(() => {
this.store.unloadAll();
return this.store.findRecord('employee-list', 1).then((response) => {
this.get('notify').success("Successfully saved");
return this.controllerFor('employee').set('cEmpData', response);
});
}).catch((exception) => {
this.get('notify').alert({
html: this.get('errorFormatter').formatErrorNotification(exception),
closeAfter: null
});
}).finally(() => {
this.controller.set('loader', false);
this.transitionTo("employee");
});
} else {
this.controller.set('errorMessage', empRcd.get('validations.messages'));
}
},
I am building a 'Watch this deal' functionality, which is similar to FB 'like' feature. (Ember version 1.13)
Here is the scenario:
There is an icon beside every deal which will enable the current user to 'watch' or 'not watch' the deal. The actions are completed and working and changes on the UI is also working fine. The problem is, when I click on that icon, I become a watcher of the deal but the icon doesn't change. I have to refresh the page to see that change.
controller:
actions:{
// add and remove watchers
addToWatcher: function(deal) {
var _this = this;
var currentUser = this.get('currentUser');
deal.get('watchers').addObject(currentUser);
deal.save().then(function () {
Ember.get(_this, 'flashMessages').success("You are now watching");
}, function() {
// Ember.get(_this, 'flashMessages').danger('apiFailure');
});
},
removeWatcher: function(deal) {
var _this = this;
var currentUser = this.get('currentUser');
deal.get('watchers').removeObject(currentUser);
deal.save().then(function () {
Ember.get(_this, 'flashMessages').success("You are now watching");
}, function() {
// Ember.get(_this, 'flashMessages').danger('apiFailure');
});
}
}
templates:
{{#if (check-watcher deal currentUser.id)}}
<i class="fa fa-2x sc-icon-watch watched" {{action 'removeWatcher' deal}} style="padding: 5px 10px;"></i><br>
{{else}}
<i class="fa fa-2x sc-icon-watch" {{action 'addToWatcher' deal}} style="padding: 5px 10px;"></i><br>
{{/if}}
Here check-watcher is a helper I wrote to check if the deal is being watched by the current user. If it is, the icon will be Red and clicking on it again will trigger 'removeWatcher' action. If not, icon will be black and clicking on it will make user watch the deal.
check-watcher helper:
import Ember from 'ember';
export function checkWatcher(object, currentUser) {
var currentUser = object[1];
var watchers = object[0].get('watchers').getEach('id');
if (watchers.contains(currentUser)) {
return true;
} else{
return false;
}
}
export default Ember.Helper.helper(checkWatcher);
If I were to just change the class, that would have been easy, but I have to change the action too in the views, that's where it's a little tricky.
So, how to make the change in UI happen between adding and removing watchers without refreshing the page?
In short, you need to define a compute method for the helper:
import Ember from 'ember';
export function checkWatcher(object, currentUser) {
var currentUser = object[1];
var watchers = object[0].get('watchers').getEach('id');
if (watchers.contains(currentUser)) {
return true;
} else{
return false;
}
}
export default Ember.Helper.extend({ compute: checkWatcher });
In that case, the helper will recompute its output every time the input changes.
And there is not need to change an action in a template. You could always call 'toggleWatcher' action from template, and then decide what to do in the controller:
toggleWatcher(deal) {
var currentUser = this.get('currentUser');
if (deal.get('watchers').contains(currentUser)) {
this.send('removeWatcher', deal);
} else {
this.send('addToWatcher', deal);
}
}
Both functions here return 'undefined'. I can't figure out what's the problem.. It seems so straight-forward??
In the controller I set some properties to present the user with an empty textfield, to ensure they type in their own data.
Amber.ProductController = Ember.ObjectController.extend ({
quantity_property: "",
location_property: "",
employee_name_property: "",
//quantitySubtract: function() {
//return this.get('quantity') -= this.get('quantity_property');
//}.property('quantity', 'quantity_property')
quantitySubtract: Ember.computed('quantity', 'quantity_property', function() {
return this.get('quantity') - this.get('quantity_property');
});
});
Inn the route, both the employeeName and location is being set...
Amber.ProductsUpdateRoute = Ember.Route.extend({
model: function(params) {
return this.store.find('product', params.product_id);
},
//This defines the actions that we want to expose to the template
actions: {
update: function() {
var product = this.get('currentModel');
var self = this; //ensures access to the transitionTo method inside the success (Promises) function
/* The first parameter to 'then' is the success handler where it transitions
to the list of products, and the second parameter is our failure handler:
A function that does nothing. */
product.set('employeeName', this.get('controller.employee_name_property'))
product.set('location', this.get('controller.location_property'))
product.set('quantity', this.get('controller.quantitySubtract()'))
product.save().then(
function() { self.transitionTo('products') },
function() { }
);
}
}
});
Nothing speciel in the handlebar
<h1>Produkt Forbrug</h1>
<form {{action "update" on="submit"}}>
...
<div>
<label>
Antal<br>
{{input type="text" value=quantity_property}}
</label>
{{#each error in errors.quantity}}
<p class="error">{{error.message}}</p>
{{/each}}
</div>
<button type="update">Save</button>
</form>
get rid of the ()
product.set('quantity', this.get('controller.quantitySubtract'))
And this way was fine:
quantitySubtract: function() {
return this.get('quantity') - this.get('quantity_property');
}.property('quantity', 'quantity_property')
Update:
Seeing your route, that controller wouldn't be applied to that route, it is just using a generic Ember.ObjectController.
Amber.ProductController would go to the Amber.ProductRoute
Amber.ProductUpdateController would go to the Amber.ProductUpdateRoute
If you want to reuse the controller for both routes just extend the product controller like so.
Amber.ProductController = Ember.ObjectController.extend ({
quantity_property: "",
location_property: "",
employee_name_property: "",
quantitySubtract: function() {
return this.get('quantity') - this.get('quantity_property');
}.property('quantity', 'quantity_property')
});
Amber.ProductUpdateController = Amber.ProductController.extend();
I ended up skipping the function and instead do this:
product.set('quantity',
this.get('controller.quantity') - this.get('controller.quantity_property'))
I still dont understand why I could not use that function.. I also tried to rename the controller.. but that was not the issue.. as mentioned before the other two values to fetches to the controller...
Anyways, thanks for trying to help me!
I am trying to build a blog application with Ember. I have models for different types of post - article, bookmark, photo. I want to display a stream of the content created by the user for which I would need a collection of objects of all these models arranged in descending order of common attribute that they all have 'publishtime'. How to do this?
I tried something like
App.StreamRoute = Ember.Route.extend({
model: function() {
stream = App.Post.find();
stream.addObjects(App.Bookmark.find());
stream.addObjects(App.Photo.find());
return stream;
}
}
where the resource name is stream
But it doesn't work. I am using the latest released Ember 1.0.0 rc 2 and handlebars 1.0.0 rc 3 with jQuery 1.9.1 and ember-data.
Probably the way I am trying to achieve this whole thing is wrong. The problem is even if I am able to use the collection of objects of multiple models to iterate in the template, I would still need to distinguish between the type of each object to display its properties apart from the common property of 'publishtime'.
You can use a computed property to combine the various arrays and then use Javascript's built in sorting to sort the combined result.
Combining the arrays and sorting them
computed property to combine the multiple arrays:
stream: function() {
var post = this.get('post'),
bookmark = this.get('bookmark'),
photo = this.get('photo');
var stream = [];
stream.pushObjects(post);
stream.pushObjects(bookmark);
stream.pushObjects(photo);
return stream;
}.property('post.#each', 'bookmark.#each', 'photo.#each'),
example of sorting the resulting computed property containing all items:
//https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/sort
streamSorted: function() {
var streamCopy = this.get('stream').slice(); // copy so the original doesn't change when sorting
return streamCopy.sort(function(a,b){
return a.get('publishtime') - b.get('publishtime');
});
}.property('stream.#each.publishtime')
});
rendering items based on a property or their type
I know of two ways to do this:
add a boolean property to each object and use a handlebars {{#if}} to check that property and render the correct view
extend Ember.View and use a computed property to switch which template is rendered based on which type of object is being rendered (based on Select view template by model type/object value using Ember.js)
Method 1
JS:
App.Post = Ember.Object.extend({
isPost: true
});
App.Bookmark = Ember.Object.extend({
isBookmark: true
});
App.Photo = Ember.Object.extend({
isPhoto: true
});
template:
<ul>
{{#each item in controller.stream}}
{{#if item.isPost}}
<li>post: {{item.name}} {{item.publishtime}}</li>
{{/if}}
{{#if item.isBookmark}}
<li>bookmark: {{item.name}} {{item.publishtime}}</li>
{{/if}}
{{#if item.isPhoto}}
<li>photo: {{item.name}} {{item.publishtime}}</li>
{{/if}}
{{/each}}
</ul>
Method 2
JS:
App.StreamItemView = Ember.View.extend({
tagName: "li",
templateName: function() {
var content = this.get('content');
if (content instanceof App.Post) {
return "StreamItemPost";
} else if (content instanceof App.Bookmark) {
return "StreamItemBookmark";
} else if (content instanceof App.Photo) {
return "StreamItemPhoto";
}
}.property(),
_templateChanged: function() {
this.rerender();
}.observes('templateName')
})
template:
<ul>
{{#each item in controller.streamSorted}}
{{view App.StreamItemView contentBinding=item}}
{{/each}}
</ul>
JSBin example - the unsorted list is rendered with method 1, and the sorted list is rendered with method 2
It's a little complicated than that, but #twinturbo's example shows nicely how to aggregate separate models into a single array.
Code showing the aggregate array proxy:
App.AggregateArrayProxy = Ember.ArrayProxy.extend({
init: function() {
this.set('content', Ember.A());
this.set('map', Ember.Map.create());
},
destroy: function() {
this.get('map').forEach(function(array, proxy) {
proxy.destroy();
});
this.super.apply(this, arguments);
},
add: function(array) {
var aggregate = this;
var proxy = Ember.ArrayProxy.create({
content: array,
contentArrayDidChange: function(array, idx, removedCount, addedCount) {
var addedObjects = array.slice(idx, idx + addedCount);
addedObjects.forEach(function(item) {
aggregate.pushObject(item);
});
},
contentArrayWillChange: function(array, idx, removedCount, addedCount) {
var removedObjects = array.slice(idx, idx + removedCount);
removedObjects.forEach(function(item) {
aggregate.removeObject(item);
});
}
});
this.get('map').set(array, proxy);
},
remove: function(array) {
var aggregate = this;
array.forEach(function(item) {
aggregate.removeObject(item);
});
this.get('map').remove(array);
}
});