Is it possible to pass multiple arguments to onChange action in Ember Power Select? - ember.js

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.

Related

Programmatically trigger the equivalent of the #click event in a list item

In a list of items (in quasar a q-list) is it possible to programmatically select a particular item from the list, for example, from a button?
The problem is mainly to programmatically trigger the equivalent of the #click event in the list item (or select event if it exists in Vue or Quasar).
In the following way it is possible to obtain the item to select, but I think that, somehow, I will have to use the 'el' of the component
let itemToSelect = 2;
this.item = this.itens.find( (item) => {
return item.id === itemToSelect
})
console.log(this.item);
EDITED
To better illustrate what I want to achieve:
https://codepen.io/ijose/pen/vYEwazj
Usually to trigger a click of a button that is rendered in the template, you put a ref on the specific button and call it like this:
template
<button ref="button1" #click="alert"></button>
the call for example
mounted() {
this.$refs.button1.click();
},
methods: {
alert(){
alert("hello");
}
}

Ember.js dynamic content inside helper

I have this template with a bunch of attributes coming from its view. For all of these attributes, I need to check if they whether they are null or not and show them:
...
<div {{bind-attr class=":(foo-class) view.foo::hidden"}}>{{view.foo}}</div>
<div {{bind-attr class=":(bar-class) view.bar::hidden"}}>{{view.bar}}</div>
...
To make the template less verbose, I would like to create a helper, let's say displayAttribute, that takes the name of the parameter and returns the proper HTML content.
However, I don't know how to build the string to return inside the helper in a way that the bindings are working.
Ember.Handlebars.helper('displayAttribute', function(attr, tag) {
if (typeof(tag) === 'undefined') {
tag = 'div';
}
// TODO: generate output string
return new Handlebars.SafeString(output);
});
How should I proceed?
inorder to make bindings work, you are required to use Ember.Handlebars.registerBoundHelper
Ember.Handlebars.registerBoundHelper('displayAttribute', function(value,options) {
if (typeof(tag) === 'undefined') {
tag = 'div';
}
// TODO: generate output string
return new Handlebars.SafeString(output);
}, dependentKeys);
Note that it takes 3 params.
#param {String} name
#param {Function} function
#param {String} dependentKeys*
For more documentation, i suggest to go through following links.
Ember API Docs
Ember Source Code
Here is the content from code docs
Example with options
Like normal handlebars helpers, bound helpers have access to the options
passed into the helper call.
Ember.Handlebars.registerBoundHelper('repeat', function(value, options) {
var count = options.hash.count;
var a = [];
while(a.length < count) {
a.push(value);
}
return a.join('');
});
This helper could be used in a template as follows:
{{repeat text count=3}}
## Example with bound options
Bound hash options are also supported. Example:
{{repeat text countBinding="numRepeats"}}
In this example, count will be bound to the value of
the numRepeats property on the context. If that property
changes, the helper will be re-rendered.
## Example with extra dependencies
The Ember.Handlebars.registerBoundHelper method takes a variable length
third parameter which indicates extra dependencies on the passed in value.
This allows the handlebars helper to update when these dependencies change.
Ember.Handlebars.registerBoundHelper('capitalizeName', function(value) {
return value.get('name').toUpperCase();
}, 'name');
## Example with multiple bound properties
Ember.Handlebars.registerBoundHelper supports binding to
multiple properties, e.g.:
Ember.Handlebars.registerBoundHelper('concatenate', function() {
var values = Array.prototype.slice.call(arguments, 0, -1);
return values.join('||');
});
Which allows for template syntax such as {{concatenate prop1 prop2}} or
{{concatenate prop1 prop2 prop3}}. If any of the properties change,
the helpr will re-render. Note that dependency keys cannot be
using in conjunction with multi-property helpers, since it is ambiguous
which property the dependent keys would belong to.
Here is the working jsbin as an example

Using Backbone.js in SaaS without templates

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.

When is the template (.tpl) rendered for an Ext JS Component?

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/

Passing parameters between states in Ember.StateManager

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.