I wanted to know how to customize the ListItem content by combining different JSON data fields.
I have three JSON fields: {caption},{subCaption},{source}.
So far, I have been able to use dataMap and use custom classes to wrap additional text and styling around each. However, the only way I have been able to add content is to do so sequentially with the use of apply/update functions. As a result, my ListItems are simply {caption},{subCaption},{source} in their own lines.
Here's how I would like each ListItem to look like:
Combine {caption} and {subCaption} text and create a short story and add this as a panel to the ListItem
Render {source} in a small panel docked at the bottom right of the panel created in step 1.
How can I do the above? The distilled question would be: How can I access and combine the data from different JSON fields and render into ListItem?
My current code for ListItem is copied below for reference.
As always, any help is greatly appreciated! Thanks!
Mohammad
San Jose, CA
Ext.define('qxtapp.view.ResultsListItem', {
extend: 'Ext.dataview.component.ListItem',
requires: [
'qxtapp.view.ResultsListItemCaption'
],
xtype : 'resultslistitem',
alias : 'widget.resultslistitem',
config: {
caption: true,
subCaption: true,
source: true,
dataMap: {
getCaption: {
setHtml: 'caption'
},
getSubCaption: {
setHtml: 'subCaption'
},
getSource: {
setHtml: 'source'
}
},
layout: {
type: 'vbox'
}
},
applyCaption: function(config) {
return Ext.factory(config, qxtapp.view.ResultsListItemCaption, this.getCaption());
},
updateCaption: function(newCaption) {
if (newCaption) {
this.add(newCaption);
}
},
applySubCaption: function(config) {
return Ext.factory(config, Ext.Component, this.getSubCaption());
},
updateSubCaption: function(newSubCaption) {
if (newSubCaption) {
this.add(newSubCaption);
}
},
applySource: function(config) {
return Ext.factory(config, Ext.Component, this.getSource());
},
updateSource: function(newSource) {
if (newSource) {
this.add(newSource);
}
}
});
Ext.define('qxtapp.view.ResultsListItemCaption', {
extend: 'Ext.Component',
applyHtml: function(caption) {
// do some customization to caption and return it
return caption;
}
});
I'm not sure why you need to go through all that trouble, why not use an item template in a simple list?
Ext.define('qxtapp.view.ResultsList', {
extend: 'Ext.dataview.List',
alias: 'widget.resultslist',
config: {
...
itemTpl: new Ext.XTemplate(
"<div class='result-item'>",
"<p class='result-story'>",
"{[this.getStoryHtml(values.caption, values.subCaption)]}",
"</p>",
"<img src='{source}' alt='{caption}' />",
"</div>",
{
// This is a new function on the template created above and can be called
// from within the template html
getStoryHtml: function(caption, subCaption) {
// customize the text to your needs, then return the html to insert
}
}
),
...
}
});
Of course, you would then need to style these items using CSS, but that should be the easy part. ;)
Related
I have embedded ember-cli-chart in my hbs file as
<div class="chart">
{{ember-chart type='line' data=data options=options}}
</div>
In my component file I have created an options property as
options: computed('metric', function() {
let opts = defaultOptions;
if (this.metric === 'height') {
opts.scales.yAxes = [{
ticks: {
callback: function(value, index, values) {
// code to return labels
}
}
}]
} else {
opts.scales.yAxes = [{
ticks: {
callback: function(item, index, items) {
// code to return labels
}
}
}]
}
return opts;
});
I want to display Y-Axis labels based on the current selected metric.
When first time chart loads it renders correct labels on y-Axis and if I change the metric then the same callback is getting used instead of the other one (in else part) and renders same labels but with updated data values.
Can anyone help on this?
Hmmm I don't know the addon or chart.js for the matter, but when looking at the source code for the ember-chart component, I see
didUpdateAttrs() {
this._super(...arguments);
this.updateChart();
},
updateChart() {
let chart = this.get('chart');
let data = this.get('data');
let options = this.get('options');
let animate = this.get('animate');
if (chart) {
chart.config.data = data;
chart.config.options = options;
if (animate) {
chart.update();
} else {
chart.update(0);
}
}
}
So, in order for chart.js to update, you need didUpdateAttrs to fire, which means in your case here that options itself needs to change. I don't know how you're creating defaultOptions, but assuming this reference never changes, there's no reason that didUpdateAttrs would fire since you aren't changing the reference to options (you're only changing child props of defaultOptions in the computed). I would suppose that:
import { assign } from '#ember/polyfills';
...
options: computed('metric', function() {
let opts = assign({}, defaultOptions);
if (this.metric === 'height') {
opts.scales.yAxes = [{
ticks: {
callback: function(value, index, values) {
// code to return labels
}
}
}]
} else {
opts.scales.yAxes = [{
ticks: {
callback: function(item, index, items) {
// code to return labels
}
}
}]
}
return opts;
})
would be enough to trigger the behavior you want since we always return a new object when a recomputation of options occurs.
I'm new to ember and am creating a search filtering app. I have my search filter "buckets" set up as controller properties and they are bound nicely to query parameters.
I'm looking to create a "your selected filters" component that summarizes what filters the user has currently active. I'm thinking maybe a computed property is the way to do this? In my controller I created one called selectedFilters:
export default Ember.Controller.extend(utils, {
queryParams: ['filter_breadcrumb','filter_price','filter_size_apparel','filter_color'],
filter_breadcrumb: [],
filter_price: [],
filter_size_apparel: [],
filter_color: [],
selectedFilters: Ember.computed('this{filter_breadcrumb,filter_price,filter_size_apparel,filter_color}', function() {
let filterContainer = {};
for (let bucket of this.queryParams) {
let bucketArray = this.get(bucket);
if (bucketArray.length > 0) { // only add if bucket has values
filterContainer[bucket] = {
'title' : cfg.filterTitles[bucket], // a "pretty name" hash
'values' : bucketArray
};
}
}
return filterContainer;
})
});
The contents of selectedFilters would look something like this when a user has chosen filters:
{
filter_breadcrumb: { title: 'Category', values: [ 'Home > Stuff', 'Garage > More Stuff' ] },
filter_price: { title: 'Price', values: [ '*-20.0' ] },
filter_color: { title: 'Color', values: [ 'Black', 'Green' ] }
}
And then the template would be:
<h1>Selected Filters</h1>
{{#each-in selectedFilters as |selectedFilter selectedValues|}}
{{#each selectedValues.values as |selectedValue|}}
<strong>{{selectedValues.title}}</strong>: {{selectedValue}} <br>
{{/each}}
{{/each-in}}
This actually works (kind of). The view is not updating when filters are added and removed. When I hard-refresh the page, they do show up. I'm wondering why they aren't updating even though the "input" properties to selectedFilters do?
I'm thinking either I'm doing it wrong or perhaps there's a better way to do this. Any help appreciated!
You can't use this for computed property dependent key because it's undefined in that scope.
Arrays and objects defined directly on any Ember.Object are shared across all instances of that object. so initialize it in init(). refer initializing instances ember guide
init(){
this._super(...arguments);
this.set('filter_breadcrumb',[]);
}
For definining computed properties using arrays as dependant key refer ember guide
In your case if you want your computed property to recalculate based array item added/removed or changed to different array then use .[]
export default Ember.Controller.extend(utils, {
queryParams: ['filter_breadcrumb', 'filter_price', 'filter_size_apparel', 'filter_color'],
init(){
this._super(...arguments);
this.set("filter_breadcrumb",[]);
this.set("filter_price",[]);
this.set("filter_size_apparel",[]);
this.set("filter_color",[]);
},
selectedFilters: Ember.computed('filter_breadcrumb.[]','filter_price.[]','filter_size_apparel.[]','filter_color.[]', function() {
let filterContainer = {};
for (let bucket of this.queryParams) {
let bucketArray = this.get(bucket);
if (bucketArray.length > 0) { // only add if bucket has values
filterContainer[bucket] = {
'title': cfg.filterTitles[bucket], // a "pretty name" hash
'values': bucketArray
};
}
}
return filterContainer;
})
});
In case if you want computed property to recalculate based on each individual item change then consider filter_price.#each.price
Figured it out. It appears the brace expansion doesn't work on this. I tried:
selectedFilters: Ember.computed('this{filter_breadcrumb,filter_price,filter_size_apparel,filter_color}', function() {
and
selectedFilters: Ember.computed('this.{filter_breadcrumb,filter_price,filter_size_apparel,filter_color}', function() {
This works tho:
selectedFilters: Ember.computed('filter_breadcrumb', 'filter_price', 'filter_size_apparel', 'filter_color', function() {
But I'm still wondering if this is the recommended way of accomplishing my "filter summary" task.
I am working with Ember and Ember-data. But the JSON which i receive is not par with the Ember side-loading standards. The JSON does'nt have a root model. Also the models are embedded and some times haves Ids and sometimes does not have Id.
I have seen couple of links on how to add root model using extract hook and also how to play with embedded model using
App.ColorSerializer = DS.RestSerializer.extend(DS.EmbeddedRecordsMixin, {
attrs: {
foos: {embedded: 'always'}
}
});
This code is taken from this link.
This is the JSON used there
{
colors:[
{
id: 1,
color: "red",
foos:[
{
id:1,
name:'something 1'
},
{
id:2,
name:'something 2'
}
]
},
...
Now the problem that i am facing is that my JSON could also look like below(no root model "color")
{
id: 1,
color: "red",
foos:[
{
id:1,
name:'something 1'
},
{
id:2,
name:'something 2'
}
]
},
...
or even like this(without Ids for foo objects)
{
id: 1,
color: "red",
foos:[
{
name:'something 1'
},
{
name:'something 2'
}
]
},
...
Is there any way i can handle this? How do i add Ids to the embedded model foo? Also is there some solution/plugin which would accept any kind of embedded JSON and convert it into side loaded JSON and added Ids if needed.
I have seen this solution. Does it really work? Because it does not use the latest EmbeddedRecordsMixin
I used a generic transform for arrays:
// /transforms/array.js
import DS from "ember-data";
import Ember from "ember";
export default DS.Transform.extend({
deserialize: function (value) {
if (Ember.isArray(value)) {
return Ember.A(value);
} else {
return Ember.A();
}
},
serialize: function (value) {
if (Ember.isArray(value)) {
return Ember.A(value);
} else {
return Ember.A();
}
}
});
Then in my model, I simply use:
foos: DS.attr("array")
Here I am at the beginning of a project. I am using zurb-foundation and marionette. I have an element that is rendering a template that is supposed to be tabs. As it stands:
define([
"backbone",
"marionette"
], function(Backbone, Marionette) {
MyItem = Backbone.Marionette.ItemView.extend({
template: "#design-tabs",
className: "section-container tabs",
onRender: function() {
$(this.el).foundation();
}
});
return MyItem;
});
there are no tabs. I think this is because the <div> being rendered to replace the <script> tag in the template does not have a particular data attribute (data-section). I went looking for something like 'className' that I could add to the ItemView declaration above in order to include data-attributes, but I have come up dry. I want something like:
MyItem = Backbone.Marionette.ItemView.extend({
template: "#design-tabs",
data: {
data-section: "",
data-foo: "bar"
},
className: "section-container tabs",
.
.
.
How do I add data attributes to the <div> (or otherwise) that replaces the <script> in a template?
To add data properties, use Backbone's attributes hash:
var MyView = Backbone.Marionette.ItemView.extend({
template: "#design-tabs",
className: "section-container tabs",
attributes: {
"data-section": "",
"data-foo": "bar"
}
});
Documentation: http://backbonejs.org/#View-attributes
If you prefer or need dynamic values, you can do in this way:
attributes: function() {
return {
'src': this.model.get('avatar_src')
};
}
I have my list which is getting data from php service, the data received is in the order I need. But sencha automatically sort my list alphabetically.
Below is my code:
Ext.define('MyList', {
extend: 'Ext.dataview.List',
config: {
grouped: true,
plugins: [
{
xclass: 'Ext.plugin.PullRefresh',
pullRefreshText: 'Pull down to refresh'
},
{
xclass: 'Ext.plugin.ListPaging',
autoPaging: true,
noMoreRecordsText: 'No More Records'
}
]
},
initialize: function () {
this.callParent(arguments);
var store = Ext.create('Ext.data.Store', {
pageParam: 'page',
grouper: {
groupFn: function (record) {
return record.data.group_label;
}
},
model: 'ListItem',
proxy: {
type: 'ajax',
url: '/m/services/activity_list_items.php',
reader: {
type: 'json',
rootProperty: 'root.results'
}
}
});
var template = Ext.create('GenericListItem', {
hascounts: true,
hasicon: true,
varmap: {
descr: 'subtext',
count: 'sub_item_cnt',
itemid: 'itemid',
uniqid: 'uniqid'
}
});
var emptyText = 'Recent Activity Items';
this.setStore(store);
this.setItemTpl(template);
this.setEmptyText(emptyText);
}
});
How can I avoid the auto sorting of list?
Add the following to your store config.
remoteSort : true,
remoteSort defaults to false in sencha. So sencha automatically sorts in the client side. Check the link for more details http://docs.sencha.com/touch/2-0/#!/api/Ext.data.Store-cfg-remoteSort
Just remove this:
grouped: true
from your list config if you don't want a header for each item and compulsory to remove this:
grouper: {
groupFn: function (record) {
return record.data.group_label;
}
}
from your store because basically in your situation grouper property are using for grouping your item alphabetically based on your group_label field. Hope it helps :)