Given a view with a context like { id: 1, form_id: 5}, I want to create an {{action}} link to the form using the form_id.
My view code looks like:
<script type="text/x-handlebars" data-template-name="group">
{{action showForm form_id href=true}}
</script>
And the action in my router looks like:
showForm: function(router, event) {
var form_id = event.context;
router.transitionTo('root.form', { id: form_id });
},
I get an error that reads:
Uncaught Error: assertion failed: You must specify a target state for event 'showForm' in order to link to it in the current state 'root.index'.
I'm guessing that the problem is with the way I'm setting up the context for transitionTo, but I haven't been able to figure out the correct solution.
Here is the full code to reproduce the problem:
<script type="text/x-handlebars" data-template-name="application">
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="group">
{{action showForm form_id href=true}}
</script>
MyApp = Ember.Application.create({
autoinit: false
});
MyApp.router = Ember.Router.create({
root: Ember.Route.extend({
index: Ember.Route.extend({
route: '/',
// Throws error:
// You must specify a target state for event 'showForm' in
// order to link to it in the current state 'root.index'
//
showForm: function(router, event) {
var form_id = event.context;
router.transitionTo('root.form', { id: form_id });
},
// Won't work because form deserialize finds id, not form_id
//showForm: Em.Route.transitionTo('root.form'),
// This won't work either
// showForm: Em.Route.transitionTo('root.form', { id: this.form_id }),
connectOutlets: function( router, context ){
var group = Em.Object.create({ id:1, form_id: 5 });
router.get( 'applicationController' ).connectOutlet( 'group', group );
}
}),
form: Ember.Route.extend({
route: '/form/:id',
serialize: function( router, context ){
return { id: context.id }
},
deserialize: function( router, context ){
var form = Em.Object.create({ id: 5, name: 'my form' });
return MyApp.Form.find( context.id );
},
connectOutlets: function( router, context ){
// left out for fiddle example
}
})
})
});
MyApp.ApplicationController = Ember.Controller.extend({});
MyApp.GroupController = Em.ObjectController.extend({});
MyApp.GroupView = Em.View.extend({ templateName: 'group' });
MyApp.initialize(MyApp.router);
And the cooresponding fiddle:
http://jsfiddle.net/jefflab/LJGCz/
I was able to come up with an ugly solution to this problem using a computed property as the context of my action. The key snippets are:
<script type="text/x-handlebars" data-template-name="group">
<a {{action showForm view.formHash href=true}}>go to form</a>
</script>
MyApp.GroupView = Em.View.extend({
templateName: 'group',
formHash: function() {
return { id: this.get('controller').get('form_id') };
}.property('form_id')
});
And the working fiddle is here:
http://jsfiddle.net/jefflab/pGv8u/
However, after talking to Tom Dale, it is clear that the "right way" to solve to solve this problem is by using materialized objects instead of id values. If you are using Ember data, this is a great use case for the "sideloading" belongsTo feature.
Related
I am creating universal grid for some entities. For that I added this in routes:
this.route('record', { path: '/record' },function() {
this.route('index', {path: '/:entity'});
this.route('view', {path: '/:entity/:record_id'});
});
and created new "index" route:
export default Ember.Route.extend({
entity: '',
queryParams: {
sort: {
refreshModel: true
}
},
beforeModel: function(transition) {
var entity = transition.params[this.get('routeName')].entity;
this.set('entity', entity);
},
model: function(params) {
delete params.entity;
return this.store.findQuery(this.get('entity'), params);
},
}
my controller
export default Ember.ArrayController.extend({
queryParams: ['sort'],
sort: ''
}
how can I set default value for the "sort" based on dynamic segment?
For example in my settings I store sort values for all entites:
'settings.user.sort': 'email ASC',
'settings.company.sort': 'name ASC',
I tried to define "sort" as computed property, but its "get" method is called in time when I can't get a value of dynamic segment from currentHadlerInfo or from route.
Also defining of the property "sort" as computed property has strange effect, for example, when I define it as
sort: 'email ASC'
in my template it is displayed via {{sort}} as expected (email ASC).
but when I return a value from computed property, I see empty value in my template and this affects on a work of components (I can't get current sorted column)
What can I do?..
Here is a rough implementation of setting the sort properties based on dynamic segment values. The code will look like
<script type="text/x-handlebars" data-template-name="index">
{{#link-to 'sort' model.settings.user.sort}}Sort{{/link-to}}
</script>
<script type="text/x-handlebars" data-template-name="sort">
<ul>
{{#each arrangedContent as |item|}}
<li>{{item.val}}</li>
{{/each}}
</ul>
</script>
var settings = Em.Object.create({
settings: {
user: {
sort: 'val ASC'
}
}
});
App.Router.map(function() {
this.route('sort', {path: '/:sortParams'});
});
App.SortRoute = Ember.Route.extend({
params: null,
model: function(params) {
this.set('params', params);
return [{val:1}, {val:5}, {val:0}];
},
setupController: function(controller, model) {
this._super(controller, model);
var props = this.get('params').sortParams.split(' ');
var property = [props[0]];
var order = props[1] === 'ASC'? true : false;
controller.set('sortProperties', property);
controller.set('sortAscending', order);
}
});
The working demo can be found here..
As you can see I access the params object in the model hook and store it on the route. In the setupController hook I access the params and set the required values on the controller.
Ember noob here. I'm basically trying to have multiple input fields for multiple parameters. As the user types into the fields, this sends off a request to a PHP script which returns the relevant JSON and displays it.
Ember 1.6.1 (latest version is a pain to learn as all of the docs are
out of date)
Handlebars 1.3.0
jQuery 1.11.1
Here's the code so far (not working for multiple).
index.html
<script type="text/x-handlebars" data-template-name="search">
{{view App.SearchTextField elementId="bedrooms" valueBinding=bedrooms upKeyAction="searchProperties" placeholder="Bedrooms"}}
{{view App.SearchTextField elementId="suburb" valueBinding=suburb upKeyAction="searchProperties" placeholder="Sydney"}}
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="search/results">
{{#each}}
<h1>{{bedrooms}} - {{street}} {{suburb}}</h1>
{{/each}}
</script>
apps.js
App = Ember.Application.create();
App.Router.map(function() {
this.resource('search', {path: '/'}, function(){
this.route('results', {path: '/search/:suburb/:bedrooms'});
});
});
App.SearchRoute = Ember.Route.extend({
actions: {
searchProperties: function(suburb, bedrooms) {
console.log(suburb);
this.transitionTo('search.results', suburb, bedrooms);
}
}
});
App.SearchResultsRoute = Ember.Route.extend({
model: function(params) {
return Ember.$.getJSON('../test/data.php?suburb='+params.suburb+'&bedrooms='+params.bedrooms);
}
});
App.SearchTextField = Ember.TextField.extend({
keyUp: function (e) {
if (e.target.id == 'bedrooms') {
var bedrooms = e.target.value;
} else if (e.target.id == 'suburb') {
var suburb = e.target.value;
}
console.log(suburb + bedrooms);
this.sendAction('action', suburb, bedrooms);
}
});
After some playing around I got it to work using this (looking more jQuery than Ember, but hey it works)
App = Ember.Application.create();
App.Router.map(function() {
this.resource('search', {path: '/'}, function(){
this.route('results', {path: '/search/:suburb/:bedrooms'});
});
});
App.SearchRoute = Ember.Route.extend({
actions: {
searchProperties: function(data) {
this.transitionTo('search.results', data.suburb, data.bedrooms);
}
}
});
App.SearchResultsRoute = Ember.Route.extend({
model: function(params) {
return Ember.$.getJSON('../test/data.php?suburb='+params.suburb+'&bedrooms='+params.bedrooms);
}
});
App.SearchTextField = Ember.TextField.extend({
keyUp: function (e) {
var data = {suburb:$('#suburb').val(), bedrooms:$('#bedrooms').val()};
this.sendAction('upKeyAction', data);
}
});
Is there a better way to do this?
You are kind of over complicating things IMO,
I'd prefer to observe for the value changes in the controller and act accordingly. Result in much reduced code, and in fact you are actually exploiting the features, the framework provides.
Sample implementation, may need to modify to fulfill your needs
App.SearchController = Ember.ObjectController.extend({
suburb : null,
bedrooms : null,
doSearch : function(){
var model = [{street: this.get('suburb'), bedrooms: this.get('bedrooms')}];
//var model = Ember.$.getJSON('../test/data.php?suburb='+this.get('suburb')+'&bedrooms='+this.get('bedrooms'));
this.transitionToRoute('search.results', model);
}.observes('suburb', 'bedrooms')
});
App.SearchRoute = Ember.Route.extend({
});
App.SearchResultsRoute = Ember.Route.extend({
});
App.SearchTextField = Ember.TextField.extend({});
FIDDLE
In the IndexRoute of my Ember hello world app, I start a setInterval function that I wish to allow the end user to turn off (with clearInterval) by clicking a dom element in the template, which triggers an action in the IndexController. So, the setIntervalId is set in the IndexRoute, and I need to pass it to clearInterval in the IndexController, but the way I have it below, the setIntervalId is undefined. I also tried to use App.IndexRoute.setIntervalId to no avail.
How would I accomplish this?
(function() {
window.App = Ember.Application.create({
LOG_TRANSITIONS: true,
LOG_ACTIVE_GENERATION: true
});
App.IndexRoute = Ember.Route.extend({
setIntervalId: 0,
model: function() {
this.setIntervalId = setInterval(this.someInterval, 5000)
},
someInterval: function(){
var datasource = 'http://hackernews/blahblah';
return new Ember.$.ajax({url: datasource, dataType: "json", type: 'GET'}).then(function(data){
return data;
})
},
});
App.IndexController = Ember.ObjectController.extend({
actions: {
clearTimeout: function(){
console.log('clearing interval', this.setIntervalId); //undefined
clearInterval(this.setIntervalId);
}
}
})
})();
template
<script type="text/x-handlebars" data-template-name="index">>
<h1>Hi Babe</hi>
{{ outlet }}
<label {{action "clearTimeout" on="click"}}>clear timeout</label>
</script>
To set the model, you need to return the value in the route’s model function:
model: function() {
return this.setIntervalId = setInterval(this.someInterval, 5000)
}
To access the model in the controller, you need to use this.get('model').
actions: {
clearTimeout: function(){
console.log('clearing interval', this.get('model');
clearInterval(this.get('model'));
}
}
I have a route that displays a list of parcels, and an Ember.Select that allows the user to select which state's parcels to show.
Model
App.Parcel = DS.Model.extend({
addresses: DS.attr('array')
});
Route
App.ParcelsRoute = Ember.Route.extend({
state: null,
renderTemplate: function () {
this.render({ outlet: 'parcels' });
},
model: function (params) {
state = params.state;
App.ParcelAdapter.state = state;
App.ImageAdapter.state = state;
return Ember.RSVP.hash({
props: this.store.findAll('parcel'),
states: this.store.findAll('state'),
});
},
setupController: function (controller, model) {
controller.set('states', model.states);
controller.set('props', model.props);
controller.set('selectedState', state);
}
});
Controller
App.ParcelsController = Ember.ObjectController.extend({
selectedState: null,
props: null,
states: null,
first: true,
modelReloadNeeded: function () {
if (this.get('selectedState') != undefined && !this.get('first')) {
this.transitionToRoute('/parcels/' + this.get('selectedState'));
}else{
this.set('first', false);
}
}.observes('selectedState')
});
Handlebars
<script type="text/x-handlebars" id="parcels">
{{view Ember.Select content=states optionValuePath="content.id" optionLabelPath="content.id" value=selectedState}}
<input class="search" placeholder="Search"/>
<ul class="list nav">
{{#each props}}
<li>{{#link-to 'parcel' this}}<h3 class="name">{{addresses.0.street_address}}</h3>{{/link-to}}</li>
{{/each}}
</ul>
</script>
When the select transitions to the new route, both the old routes data and new routes are in the model, but if I reload the page, only the current routes data is loaded. Is there a way to clear the DS.RecordArray for props in the controller without a location.reload() call?
If I browse from the index to a game, the data is shown, because of {{#linkTo}}s context, but if I'm refreshing the site, every time the game name doesn't show up.
EDIT: Here is a fiddle, but unfortunately the fiddle-version with the fixture adapter works properly, even with ken's suggestion to remove the model from the game-template.
the returned data from /api/games is as follows:
{
"games":[
{
"id":1,
"name":"First Game"
}
]
}
and the returned data from /api/games/1
{
"game": [
{
"id": 1,
"name": "First Game"
}
]
}
<script type="text/x-handlebars" data-template-name="application">
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="games">
<ul>{{#each game in controller}}
<li>{{#linkTo 'game' game}}{{game.name}}{{/linkTo}}</li>
{{/each}}</ul>
</script>
<script type="text/x-handlebars" data-template-name="game">
<h1>Name:{{model.name}}</h1>
</script>
<script type="text/javascript">
App = Ember.Application.create();
App.Game = DS.Model.extend({
name: DS.attr('string')
});
App.Store = DS.Store.extend({
revision: 11,
adapter: DS.RESTAdapter.create({
namespace: 'api'
})
});
App.GamesController = Ember.ArrayController.extend({
content: []
});
App.GameController = Ember.ObjectController.extend({
content: null
});
App.Router.map(function(match) {
this.route("games", { path : "/" });
this.route("game", { path : "/games/:game_id" });
});
App.GameRoute = Ember.Route.extend({
model: function(params) {
return App.Game.find(params.game_id);
}
});
App.GamesRoute = Ember.Route.extend({
setupController: function(controller) {
controller.set('content', App.Game.find());
}
});
</script>
Has anyone an idea?
Remove model from your game template, try this instead
<script type="text/x-handlebars" data-template-name="game">
<h1>Name:{{name}}</h1>
</script>
JSFiddle your code to see where the problem is
The response from my server for a single game was wrong. I had an array with a single object in it, but I used the Object controller, so I fixed it with just responding with
{
"game": {
"id": 1,
"name": "First Game"
}
}