When using Ember.StateManager, the most common transition between Em.States involve some parameter or another. Currently, I am using instance variables within the StateManager to pass parameters between States, when I do go from one state to another using goToState. This seems incredibly ugly to me. Is there a reason there is not a more standard way of passing parameters? Or should I use a different pattern.
For example,
App.stateManager = Em.StateManager.create({
initialState: 'listContacts',
listContacts: Em.ViewState.create({
...
actionSelectContact: function(manager, context) {
manager.set('selectedContact', context);
manager.goToState('showContact');
}
}),
showContact: Em.ViewState.create({
enter: function(manager, transition) {
var contactToShow = manager.get('selectedContact');
...
}
...
})
})
Is there a better way to do this parameter passing between states?
Tom Dale just added a transitionTo method to deal with this. transitionTo takes a context object along with the name of the target state. Now within your action you could do something like,
viewStates = Ember.StateManager.create({
showingPeople: Ember.ViewState.create({
view: ContactListView
}),
showDetailAction: function(mgr, selectedPerson) {
mgr.transitionTo('showingPersonDetail', selectedPerson);
},
showingPersonDetail: Ember.ViewState.create({
setupContext: function(manager, context) {
this.set('person', context);
},
view: PersonDetailView
})
})
You could also get more fancier and pass parameters for multiple states along the way like,
stateManager.transitionTo(['planters', { company: true }], ['nuts', { product: true }]);
I'm not an Ember expert, but I think you could achieve this using stateManager.send() method where the second argument will be the object you want to pass between states.
Most of your answer is in the Ember.StateManager documentation.
There was a pull request in Ember talking about extra params in goToState() method here, but it has been closed because goToState() should only be used internally as joewest says here with tomdale:
goToState should only be called from within a state. To accomplish this, just implement an action that takes additional parameters, and have it call goToState for you.
Related
I'm currently using the excellent ember-power-select add on as part of an ember-bootstrap form.
I have multiple drop down items on the form and I am trying to unify how they are handled into a single function that can be used as the onChange action in the power-select invocations:
{{#form.element
controlType="power-select"
label="Destination"
value=destinationSelection
options=destinationOptions
as |el|}}
{{#el.control
onChange=(action "setDropDown")
searchField="name"
as |item|}}
{{item.name}}
{{/el.control}}
{{/form.element}}
My handler function will simply set some values based on the selection of the drop down:
actions: {
setDropDown(selected, string) {
handleDropDown(selected, dropdown, this)
}
}
function handleDropDown(selected, dropdown, controller) {
let selection = `${dropdown}Selection`
let modelid = `model.${dropdown}_id`
set(controller, selection, selected)
set(controller, modelid, selected.id)
}
In order for this to work I really need to be able to pass a string to the setDropDown action from the onChange part of the component call, otherwise I have no way of telling the handler function which particular fields it should be setting without creating an action per dropdown.
However when I try passing in multiple arguments like
onChange=(action "setDropDown" "destination")
or
onChange=(action "setDropDown" selected "destination")
I lose the basic functionality of the onChange action taking the selected item as it's first argument.
I looked through the documentation and couldn't find any examples where the library author is passing multiple arguments into the onChange action and wondered if it was possible without breaking the functionality of the library.
You can use a specialized higher order helper function to create an action for ember-power-select that will ultimately invoke your action with extra arguments. Consider this helper handle-dropdown
import { helper } from '#ember/component/helper';
export function invokeFunction([prop, action]) {
return function(){
action(prop, ...arguments);
}
}
export default helper(invokeFunction);
So what we are doing here is creating the function that will be invoked by ember-power-select. In this function, we are invoking the original action with prop first, followed by every argument that ember-power-select invoked our onchange function with.
In your template, invoke this helper when passing your action to power-select
{{#power-select
onchange=(handle-dropdown 'foo' (action 'dropdownChanged'))
as |dropdown|}}
And then your action would be
actions: {
dropdownChanged(keyToSet, selectedValue){
this.set(keyToSet, selectedValue);
}
}
This would ultimately call dropdownChanged('foo', /* the selected value */)
Ember Bootstrap's Power Select integration gives you a nice API for use cases like this one. Let me give you an example.
Lets take a country selector as an example. We have a list of countries represented by a list of objects holding their two-letters country code as defined by ISO 3166-1 as id property and their name as name. The selected country should be represented on the model which is a POJO by there country code.
export default Component.extend({
// country code of country selected or null
selectedCountry: null,
// Using a computed property here to ensure that the array
// isn't shared among different instances of the compontent.
// This isn't needed anymore if using native classes and
// class fields.
countries: computed(() => {
return [
{ id: 'us', name: 'United States of America' },
{ id: 'ca', name: 'Canada' },
];
}),
// Using a computed property with getter and setter to map
// country code to an object in countries array.
selectedCountryObject: computed('selectedCountry', {
get() {
return this.countries.find((_) => _.id === this.selectedCountry);
},
set(key, value) {
this.set('selectedCountry', value.id);
return value;
}
}),
});
Now we could use Ember Bootstrap Power Select as expected:
{{#bs-form model=this as |form|}}
{{form.element controlType="power-select" property="selectedCountryObject" label="Country" options=this.countries}}
{{/bs-form}}
Disclaimer: Haven't tested that code myself, so there might be typos but I hope you get the idea.
I am designing a SaaS application and have been directed to Backbone.js. The service in part tracks DOM events such as how many of each have occurred and then applies scores based on this information.
Decoupling data into Models and Collections is very appealing, but before I go any deeper I want to enquire as to whether it is the right tool for the job.
I want to work with existing DOM elements written in the HTML of a site owners page rather than create JavaScript templates. I will therefore be tracking DOM events on existing elements which then update the data model. The site owner making use of the service will then be able to use the data in the Model to create their own Views and render their own templates specific to their needs.
I understand that I will need to use Backbone.View to track the events, and from what I have read so far it seems Backbone has the flexibility to allow this. However, I haven’t seen any examples in my research of Backbone used to track a bunch of events on a number of form elements.
Take this code for example:
App.Models.Event = Backbone.Model.extend({
defaults: {
clicks: 0,
dblClicks: 0,
tabs: 0,
kbdFunctions: 0
},
urlRoot: 'events'
});
App.Views.Event = Backbone.View.extend({
model: new App.Models.Event(),
events: {
'click input' : 'clickCount',
'dblclick input' : 'dblClickCount',
'tabEvent input' : 'tabCount',
'kbdEvent input' : 'kbdEventCount'
},
initialize: function () {
this.el = $('[data-transaction=start]');
},
clickCount: function (e) {
console.log('click counted');
},
dblClickCount: function (e) {
console.log('double click counted');
},
tabCount: function (e) {
console.log('tab counted');
},
kbdEventCount: function (e) {
console.log('keyboard event counted');
}
});
I want to be able to track clicks, double clicks, tabs and other custom keyboard events that occur on input, textarea, select options and button that are contained within the [data-transaction=start] element. Firstly, is this an applicable use case for Backbone, and secondly, if so what is the best way of adding multiple elements within the Backbone.View events object literals? I haven't seen any examples of this in the documentation or anywhere else, but it would be good if I could add a variable into this like:
...
var someVariable = input, textarea, select, button;
events: {
'click someVariable' : 'clickCount',
...
Events are assigned by Backbone using the delegateEvents method in view. This method is called AFTER your view initialize method (code reference)
so you could pass your variables in view constructor
myView = new App.Views.Events ( someVariable )
in your initialize method, you can assign events:
initialize: function(someVariable) {
//assign this.events from someVariable as you would like
}
EDIT:
just read in Backbone documentation:
The events property may also be defined as a function that returns an
events hash, to make it easier to programmatically define your events,
as well as inherit them from parent views.
I'm writing an ember application that pulls the majority of it's data from the Lastfm API. The API is not RESTful. I'm not sure what what level of abstraction I should customize. Should I go down the path of writing a custom LastFm ember-data adapter? Or should I just sidestep ember-data all together?
They return data similar to this:
{ "recenttracks" : { "meta" : {}, "tracks" : [ { track info }, { track info } ] } }
For requesting data, they have a scheme that involves sending a method parameter. So, not the worst thing ever, but certainly not RESTful.
Anyway, just looking for a bit of direction as I'm new to ember-data.
Thanks!
Personally, I would create a new adapter, not necessarily RESTAdapter, passing parameters to find and findAll:
var lastFmAdapter = DS.Adapter.create({
find: function (store, type, id) { },
findAll: function (store, type) { }
});
I am trying to inject another component into an element that is rendered by the template of another Coomponent..but in the afterrender event, the template is yet to be rendered so the call to Ext.get(el-id) returns null: TypeError el is null.
tpl:
new Ext.XTemplate(
'<tpl for=".">',
'<ul>',
'<li class="lang" id="cultureSelector-li"></li>',
'</ul>',
'</tpl>'
),
listeners: {
afterrender: {
fn: function (cmp) {
console.log(Ext.get('cultureSelector-li')); // < null :[
Ext.create('CultureSelector', {
renderTo: 'cultureSelector-li'
});
}
}
},
So when can I add this component so that the element is targeting has been created in the DOM?
I think it depends on the component that you are working with. For example, the Data Grid View has a "viewready" event that would suite your needs, and depending what you are attempting, the "boxready" function could work for combo box (only the first render though). Other than that, you can either go up through the element's parent classes searching for the XTemplate render function being called (might be in the layout manager) and extend it to fire an event there, or risk a race condition and just do it in a setTimeout() call with a reasonable delay.
I ended up having to do the work myself. So, I now have the template as a property called theTpl, and then rendered it in beforerender, and then i was able to get a handle on the element in afterrender. This seems wholly counter-intuitive, does anyone have any insight?
beforeRender: {
fn: function (me) {
me.update(me.theTpl.apply({}));
}
},
edit in fact I just extended Component thus:
Ext.define('Ext.ux.TemplatedComponent', {
extend: 'Ext.Component',
alias: 'widget.templatedComponent',
template: undefined,
beforeRender: function () {
var me = this;
var template = new Ext.XTemplate(me.template || '');
me.update(template.apply(me.data || {}));
me.callParent();
}
})
...template accepts an array of html fragments
Turns out I was using the wrong things - apparently we should be using the render* configs for this type of thing (so what are thetpl & data configs for?)
Here's a working fiddle provided for me from the sencha forums:
http://jsfiddle.net/qUudA/10/
I have a model built from a JSON object.
// extend the json model to get all props
App.Model = Ember.Object.extend(window.jsonModel);
I want to automatically save the model when anything is updated. Is there any way I can add an observer to the whole model?
EDIT: // adding the solution I currently go
For now I do:
// XXX Can't be right
for (var prop in window.jsonModel) {
if (window.jsonModel.hasOwnProperty(prop)) {
App.model.addObserver(prop, scheduleSave);
}
}
This is a large form, which means I'm adding tons of observers – it seems so inefficient.
A firebug breakpoint at Ember.sendEvent() reveals that there are events called App.model.lastName:change being sent. I could hack in an intercept there, but was hoping for an official way.
You can bind to isDirty property of subclass of DS.Model. The isDirty changes from false to true when one of model properties changes. It will not serve well for all cases because it changes only once until reset or committed, but for your case -
I want to automatically save the model when anything is updated. Is there any way I can add an observer to the whole model?
it may work fine.
From the article:
autosave: function(){
this.save();
}.observes('attributes'),
save: function(){
var self = this,
url = this.get('isNew') ? '/todos.json' : '/todos/'+this.get('id')+'.json',
method = this.get('isNew') ? 'POST' : 'PUT';
$.ajax(url, {
type: 'POST',
// _method is used by Rails to spoof HTTP methods not supported by all browsers
data: { todo: this.get('attributes'), _method: method },
// Sometimes Rails returns an empty string that blows up as JSON
dataType: 'text',
success: function(data, response) {
data = $.trim(data);
if (data) { data = JSON.parse(data); }
if (self.get('isNew')) { self.set('id', data['todo']['id']); }
}
});
},
isNew: function(){
return !this.get('id');
}.property('id').cacheable(),
I had the same requirement, and not finding a suitable answer, I implemented one.
Try this: https://gist.github.com/4279559
Essentially, the object you want to observe all the properties of MUST be a mixed of Ember.Stalkable. You can observe the properties of that object as 'item.#properties' (or, if you bake observers directly on the Stalkable, '#properties' alone works. "#ownProperties", "#initProperties" and "#prototypeProperties" also work, and refer to (properties that are unique to an instance and not defined on any prototype), (properties that are defined as part of the create() invocation), and (properties that are defined as part of the class definition).
In your observers, if you want to know what properties changed and invoked the handler, the property "modifiedProperties", an array, will be available with the names of the changed properties.
I created a virtual property _anyProperty that can be used as a dependent key:
import Ember from 'ember';
Ember.Object.reopen({
// Virtual property for dependencies on any property changing
_anyPropertyName: '_anyProperty',
_anyProperty: null,
propertyWillChange(keyName) {
if (keyName !== this._anyPropertyName) {
this._super(this._anyPropertyName);
}
return this._super(keyName);
},
propertyDidChange(keyName) {
if (keyName !== this._anyPropertyName) {
this._super(this._anyPropertyName);
}
return this._super(keyName);
}
});