How to render tree - javascript

I have a grid and I would like to add a tree if a name is equal to “Beto Carlx” Does anyone know how to make this happen? Thank you in advance!
Here’s my code: LIVE DEMO
columns: [{
header: 'NAME',
renderer: function(val, metadata, record) {
var recordName = record.get('name');
if (recordName === "Beto carlx") {
return "TEST";
}
return recordName;
},
dataIndex: 'name',
}],
I'm trying to use this simple tree:
var store = Ext.create('Ext.data.TreeStore', {
root: {
expanded: true,
children: [
{ text: "Beto carlx", expanded: true, children: [
{ text: "item 1", leaf: true },
{ text: "item 2", leaf: true}
] }
]
}
});
Ext.create('Ext.tree.Panel', {
width: 200,
height: 150,
store: store,
rootVisible: false,
renderTo: Ext.getBody()
});

You can be achieve this functionality by html tag inside of renderer of gridcolumn.
I this FIDDLE, I have created a demo using html tag inside of renderer config. Hope this will help you to achieve your requirement.
Update
You can use cellclick to put collapseexpand function inside of ExtJS component or controller.
For design pas I have worked for that not fully same. I have used font-awesome for icons and put css for dashed border.
CODE SNIPPET
Ext.application({
name: 'Fiddle',
launch: function () {
Ext.define('UserModal', {
extend: 'Ext.data.Model',
fields: ['name']
});
Ext.create('Ext.data.Store', {
storeId: 'gridStore',
fields: ['name'],
groupField: 'status',
data: [{
"name": "Henry Watson"
}, {
"name": "Lucy"
}, {
"name": "Mike Brow"
}, {
"name": "Mary Tempa"
}, {
"name": "Beto Carlx"
}]
});
// Setting up the Grid
Ext.create('Ext.grid.Panel', {
title: 'Render Treen inside grid cell',
store: 'gridStore',
columns: [{
header: 'NAME',
renderer: function (val, metadata, record) {
if (val === "Beto Carlx") {
return `<ul class="root-tree">
<li><i class="fa fa-minus-square"></i> <span>Beto carlx</span>
<ul class="tree-item">
<li class="tree-item-li">item 1</li>
<li class="tree-item-li">item 1</li>
</ul>
</li>
</ul>`
}
return val;
},
dataIndex: 'name',
}],
listeners: {
cellclick: function (grid, td, cellIndex, record, tr, rowIndex, e) {
var list,
styles;
if (e.getTarget('ul.root-tree', 3)) {
list = td.querySelector('ul.tree-item');
var icon = td.querySelector('i.fa').classList;
if (icon.contains('fa-minus-square')) {
icon.remove('fa-minus-square');
icon.add('fa-plus-square');
list.style.display = 'none';
} else {
icon.remove('fa-plus-square');
icon.add('fa-minus-square');
list.style.display = 'block';
}
// styles = window.getComputedStyle(list);
// = (styles.getPropertyValue('display') === 'none' ? 'block' : 'none');
} else if (e.target.className == 'tree-item-li') {
alert(e.getTarget('li.tree-item-li').innerText)
}
}
},
height: 300,
renderTo: document.body
});
}
});
CSS part
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<style>
.root-tree {
cursor: pointer;
color: #5c5c5c;
font-weight:bold;
}
.root-tree span:hover {
color: green;
}
ul.tree-item,
ul.tree-item ul {
list-style: none;
margin: 0px 10px;
padding: 0;
}
ul.tree-item ul {
margin-left: 10px;
}
ul.tree-item li {
margin: 0;
padding: 0 7px;
line-height: 20px;
color: #5c5c5c;
font-weight: bold;
border-left: 1px dashed rgb(100, 100, 100)
}
ul.tree-item li:last-child {
border-left: none;
}
ul.tree-item li:before {
position: relative;
top: -0.3em;
height: 1em;
width: 12px;
color: white;
border-bottom: 1px dashed rgb(100, 100, 100);
content: "";
display: inline-block;
left: -7px;
}
ul.tree-item li:last-child:before {
border-left: 1px dashed rgb(100, 100, 100)
}
</style>
*Note I have implemented only for Beto Carlx with static html. You can put your logic with dynamic inside of renderer function.

I have a partial solution, the only problem is that extjs doesn't like nested grids ou treepanels. The events overlap and it gives some console erros like : "Uncaught TypeError: Cannot read property 'isGroupHeader' of null".
Here is the FIDDLE
var CreateTree = function(){
var store = Ext.create('Ext.data.TreeStore', {
root: {
expanded: true,
children: [
{ text: "Beto carlx", expanded: true, children: [
{ text: "item 1", leaf: true },
{ text: "item 2", leaf: true}
] }
]
}
});
var tree = Ext.create('Ext.tree.Panel', {
width: 200,
store: store,
rootVisible: false
});
tree.render('myDiv');
}
renderer: function (val, metadata, record) {
var recordName = record.get('name');
if (recordName === "Beto Carlx") {
setTimeout(CreateTree, 300);
return "<span id=myDiv ></span>";
}
return recordName;
}
"If it works, its not stupid!"

Related

Get a complete list of parent groups from child group. (Kendo UI)

Executable code snippet: https://demos.telerik.com/kendo-ui/grid/index (Just go to this example and hit "Edit")
I want to display the full list of parent groups displayed as a list in the groupFooterTemplate of the child group.
{
field: "Discontinued",
title: "In Stock",
template: "<span id='badge_#=ProductID#' class='badgeTemplate'></span>",
groupFooterTemplate: (data) => { return this.getPathToDataItem(data)} ,
width: 130,
}
Above I change one of the columns to reference a function getPathToDataItem(data) in its groupFooterTemplate. Now I define the function:
private getPathToDataItem(data): string {
var path: string = '';
//Javascript - JQuery - Black magic goes here!
return path; //Should look something like Seafood/Unavailable (assuming my groups are Category,
InStock in that order!)
}
to actually get the template. I want to use javascript/jquery/kendo to get the full list of groups in play and create a path for them. So if the groups in play are Category, Instock then the template should display Seafood/Unavailable, Produce/Available, etc.
My issue is I can't figure out how to get the parent groups in the child group template! The last one is easy, as that's just data.value, but how do I get the prior groups? I suspect I will need to do something like:
group1/group2/.../data.value
but how do I get group1, group2, and in general, groupN?
Any help would be appreciated, thanks!
Ok. Stick the code below in the Telerik DOJO. By the way, remove the typescript tag from your question. This isn't typescript, this is Kendo UI jQuery. If the groups in play are Category and In Stock, the footer will show something like 'Seafood/available', 'Seafood/not available', etc. The idea to get the prior groups is to assign a reference to the Kendo grid object so that we can access the dataSource in the groupFooterTemplate function and that's where the Javascript black magic happens. Hope the code below will help you solve your problem.
<!DOCTYPE html>
<html>
<head>
<base href="https://demos.telerik.com/kendo-ui/grid/index">
<style>html { font-size: 14px; font-family: Arial, Helvetica, sans-serif; }</style>
<title></title>
<link rel="stylesheet" href="https://kendo.cdn.telerik.com/2021.2.511/styles/kendo.bootstrap-v4.min.css" />
<script src="https://kendo.cdn.telerik.com/2021.2.511/js/jquery.min.js"></script>
<script src="https://kendo.cdn.telerik.com/2021.2.511/js/kendo.all.min.js"></script>
</head>
<body>
<script src="//cdnjs.cloudflare.com/ajax/libs/jszip/2.4.0/jszip.min.js"></script>
<div id="example">
<div id="grid"></div>
<script>
$(document).ready(function () {
var crudServiceBaseUrl = "https://demos.telerik.com/kendo-ui/service",
dataSource = new kendo.data.DataSource({
transport: {
read: {
url: crudServiceBaseUrl + "/detailproducts",
dataType: "jsonp"
},
update: {
url: crudServiceBaseUrl + "/detailproducts/Update",
dataType: "jsonp"
},
destroy: {
url: crudServiceBaseUrl + "/detailproducts/Destroy",
dataType: "jsonp"
},
parameterMap: function (options, operation) {
if (operation !== "read" && options.models) {
return { models: kendo.stringify(options.models) };
}
}
},
batch: true,
pageSize: 20,
autoSync: true,
aggregate: [{
field: "TotalSales",
aggregate: "sum"
}],
group: {
field: "Category.CategoryName",
dir: "desc",
aggregates: [
{ field: "TotalSales", aggregate: "sum" }
]
},
schema: {
model: {
id: "ProductID",
fields: {
ProductID: { editable: false, nullable: true },
Discontinued: { type: "boolean", editable: false },
TotalSales: { type: "number", editable: false },
TargetSales: { type: "number", editable: false },
LastSupply: { type: "date" },
UnitPrice: { type: "number" },
UnitsInStock: { type: "number" },
Category: {
defaultValue: {
CategoryID: 8,
CategoryName: "Seafood"
}
},
Country: {
defaultValue: {
CountryNameLong: "Bulgaria",
CountryNameShort: "bg"
}
}
}
}
}
});
var grid = $("#grid").kendoGrid({
dataSource: dataSource,
columnMenu: {
filterable: false
},
height: 680,
editable: "incell",
pageable: true,
sortable: true,
navigatable: true,
resizable: true,
reorderable: true,
groupable: true,
filterable: true,
dataBound: onDataBound,
toolbar: ["excel", "pdf", "search"],
columns: [{
selectable: true,
width: 75,
attributes: {
"class": "checkbox-align",
},
headerAttributes: {
"class": "checkbox-align",
}
}, {
field: "ProductName",
title: "Product Name",
template: "<div class='product-photo' style='background-image: url(../content/web/foods/#:data.ProductID#.jpg);'></div><div class='product-name'>#: ProductName #</div>",
width: 300,
groupFooterTemplate: function(dataItem) {
var ds = grid.dataSource;
var groupingFields = [];
for (let a = 0; a < ds._group.length; a++) {
groupingFields.push(ds._group[a].field);
}
var items = dataItem.items;
for (let a = 0; a < items.length; a++) {
if (items[a].hasOwnProperty('hasSubgroups')) {
items = items[a].items;
break;
}
}
var path = '';
for (let a = 0; a < items.length; a++) {
var item = items[a];
for (let b = 0; b < groupingFields.length; b++) {
var groupingField = groupingFields[b];
if (b > 0) {
path += '/';
}
if (groupingField.includes('.')) {
path += groupingField.split('.').reduce(getValue, item);
} else {
if (groupingField === 'Discontinued') {
path += item[groupingField] ? 'available' : 'not available';
} else {
path += item[groupingField];
}
}
if (groupingField === dataItem.field) {
break;
}
}
break;
}
return path;
function getValue(obj, prop) {
return obj[prop];
}
},
}, {
field: "UnitPrice",
title: "Price",
format: "{0:c}",
width: 105
}, {
field: "Discontinued",
title: "In Stock",
template: "<span id='badge_#=ProductID#' class='badgeTemplate'></span>",
width: 130,
}, {
field: "Category.CategoryName",
title: "Category",
editor: clientCategoryEditor,
groupHeaderTemplate: "Category: #=data.value#, Total Sales: #=kendo.format('{0:c}', aggregates.TotalSales.sum)#",
width: 125
}, {
field: "CustomerRating",
title: "Rating",
template: "<input id='rating_#=ProductID#' data-bind='value: CustomerRating' class='rating'/>",
editable: returnFalse,
width: 140
}, {
field: "Country.CountryNameLong",
title: "Country",
template: "<div class='k-text-center'><img src='../content/web/country-flags/#:data.Country.CountryNameShort#.png' alt='#: data.Country.CountryNameLong#' title='#: data.Country.CountryNameLong#' width='30' /></div>",
editor: clientCountryEditor,
width: 120
}, {
field: "UnitsInStock",
title: "Units",
width: 105
}, {
field: "TotalSales",
title: "Total Sales",
format: "{0:c}",
width: 140,
aggregates: ["sum"],
}, {
field: "TargetSales",
title: "Target Sales",
format: "{0:c}",
template: "<span id='chart_#= ProductID#' class='sparkline-chart'></span>",
width: 220
},
{ command: "destroy", title: " ", width: 120 }],
}).data('kendoGrid');
});
function onDataBound(e) {
var grid = this;
grid.table.find("tr").each(function () {
var dataItem = grid.dataItem(this);
if (dataItem === undefined) {
return;
}
var themeColor = dataItem.Discontinued ? 'success' : 'error';
var text = dataItem.Discontinued ? 'available' : 'not available';
$(this).find(".badgeTemplate").kendoBadge({
themeColor: themeColor,
text: text,
});
$(this).find(".rating").kendoRating({
min: 1,
max: 5,
label: false,
selection: "continuous"
});
$(this).find(".sparkline-chart").kendoSparkline({
legend: {
visible: false
},
data: [dataItem.TargetSales],
type: "bar",
chartArea: {
margin: 0,
width: 180,
background: "transparent"
},
seriesDefaults: {
labels: {
visible: true,
format: '{0}%',
background: 'none'
}
},
categoryAxis: {
majorGridLines: {
visible: false
},
majorTicks: {
visible: false
}
},
valueAxis: {
type: "numeric",
min: 0,
max: 130,
visible: false,
labels: {
visible: false
},
minorTicks: { visible: false },
majorGridLines: { visible: false }
},
tooltip: {
visible: false
}
});
kendo.bind($(this), dataItem);
});
}
function returnFalse() {
return false;
}
function clientCategoryEditor(container, options) {
$('<input required name="Category">')
.appendTo(container)
.kendoDropDownList({
autoBind: false,
dataTextField: "CategoryName",
dataValueField: "CategoryID",
dataSource: {
data: categories
}
});
}
function clientCountryEditor(container, options) {
$('<input required name="Country">')
.appendTo(container)
.kendoDropDownList({
dataTextField: "CountryNameLong",
dataValueField: "CountryNameShort",
template: "<div class='dropdown-country-wrap'><img src='../content/web/country-flags/#:CountryNameShort#.png' alt='#: CountryNameLong#' title='#: CountryNameLong#' width='30' /><span>#:CountryNameLong #</span></div>",
dataSource: {
transport: {
read: {
url: " https://demos.telerik.com/kendo-ui/service/countries",
dataType: "jsonp"
}
}
},
autoWidth: true
});
}
var categories = [{
"CategoryID": 1,
"CategoryName": "Beverages"
}, {
"CategoryID": 2,
"CategoryName": "Condiments"
}, {
"CategoryID": 3,
"CategoryName": "Confections"
}, {
"CategoryID": 4,
"CategoryName": "Dairy Products"
}, {
"CategoryID": 5,
"CategoryName": "Grains/Cereals"
}, {
"CategoryID": 6,
"CategoryName": "Meat/Poultry"
}, {
"CategoryID": 7,
"CategoryName": "Produce"
}, {
"CategoryID": 8,
"CategoryName": "Seafood"
}];
</script>
<style type="text/css">
.customer-photo {
display: inline-block;
width: 32px;
height: 32px;
border-radius: 50%;
background-size: 32px 35px;
background-position: center center;
vertical-align: middle;
line-height: 32px;
box-shadow: inset 0 0 1px #999, inset 0 0 10px rgba(0,0,0,.2);
margin-left: 5px;
}
.customer-name {
display: inline-block;
vertical-align: middle;
line-height: 32px;
padding-left: 3px;
}
.k-grid tr .checkbox-align {
text-align: center;
vertical-align: middle;
}
.product-photo {
display: inline-block;
width: 32px;
height: 32px;
border-radius: 50%;
background-size: 32px 35px;
background-position: center center;
vertical-align: middle;
line-height: 32px;
box-shadow: inset 0 0 1px #999, inset 0 0 10px rgba(0,0,0,.2);
margin-right: 5px;
}
.product-name {
display: inline-block;
vertical-align: middle;
line-height: 32px;
padding-left: 3px;
}
.k-rating-container .k-rating-item {
padding: 4px 0;
}
.k-rating-container .k-rating-item .k-icon {
font-size: 16px;
}
.dropdown-country-wrap {
display: flex;
flex-wrap: nowrap;
align-items: center;
white-space: nowrap;
}
.dropdown-country-wrap img {
margin-right: 10px;
}
#grid .k-grid-edit-row > td > .k-rating {
margin-left: 0;
width: 100%;
}
.k-grid .k-grid-search {
margin-left: auto;
margin-right: 0;
}
</style>
</div>
</body>
</html>

ExtJS - Add listener to title

I have a small panel with title and body.
var dashboardPanel1 = Ext.create('Ext.Panel', {
renderTo: Ext.getBody(),
collapsible: true,
margin: '0 0 50 0',
layout: {
type: 'table',
// The total column count must be specified here
columns: 3
},
defaults: {
// applied to each contained panel
bodyStyle: 'padding:20px',
border: 0
},
title: 'Key settings',
items: [{
html: '<img src="https://i.imgur.com/n7gOYrE.png" style="width: 20px; height: 20px">',
}, {
html: '|Your key is active|',
}, {
html: '|Expiring date: 27.04.2018|',
}],
});
How can I attach a listener to a title?
So, when I click on Key settings, it will be some action performed.
You can also trigger click event using [Element.on()](https://docs.sencha.com/extjs/6.2.0/classic/Ext.dom.Element.html#method-on).
The on method is shorthand for addListener. Appends an event handler to this object.
For example:
el.on("click", function(){
//do something...
//
}, this);
I this FIDDLE, I have created a demo using your code and make some modification. I hope this will help/guide you to achieve your requirement.
CODE SNIPPET
Ext.application({
name: 'Fiddle',
launch: function () {
Ext.create('Ext.Panel', {
renderTo: Ext.getBody(),
collapsible: true,
margin: '0 0 50 0',
layout: {
type: 'table',
// The total column count must be specified here
columns: 3
},
defaults: {
// applied to each contained panel
bodyStyle: 'padding:20px',
border: 0
},
title: '<span class="mytitle">Key settings</span>',
items: [{
html: '<img src="https://i.imgur.com/n7gOYrE.png" style="width: 20px; height: 20px">',
}, {
html: '|Your key is active|',
}, {
html: '|Expiring date: 27.04.2018|',
}],
listeners: {
afterrender: function (panel) {
Ext.get(panel.el.query('span.mytitle')[0]).on('click', function (e) {
alert(e.target.innerText)
}, panel);
}
}
});
}
});
Solution:
Replace
title: 'Key settings',
with custom header:
header: {
title: 'Custom header title',
listeners: {
click: function(h, e) {
var tm = new Ext.util.TextMetrics(h);
if (e.getX() < tm.getWidth('Custom header title')) {
Ext.Msg.alert('Information', 'Do some action!');
}
}
}
}
Working example:
var dashboardPanel1 = Ext.create('Ext.Panel', {
renderTo: Ext.getBody(),
collapsible: true,
margin: '0 0 50 0',
layout:
{
type: 'table',
// The total column count must be specified here
columns: 3
},
defaults:
{
// applied to each contained panel
bodyStyle: 'padding:20px',
border: 0
},
//title: 'Key settings',
header: {
title: 'Custom header title',
listeners: {
click: function(h, e) {
var tm = new Ext.util.TextMetrics(h);
if (e.getX() < tm.getWidth('Custom header title')) {
Ext.Msg.alert('Information', 'Do some action!');
}
}
}
},
items: [
{
html: '<img src="https://i.imgur.com/n7gOYrE.png" style="width: 20px; height: 20px">',
},
{
html: '|Your key is active|',
},
{
html: '|Expiring date: 27.04.2018|',
}],
});
Notes:
The example is tested with ExtJS 4.2. Title width is calculated with Ext.util.TextMetrics.

ComboBox showing HTML as text

My treecolumn has a ComboBox as the editor component. The choices in the options menu are rendered correctly with HTML, but the input box does not render HTML, it only shows the tags (See images below.)
How I can make it to also render the value as HTML?
P.S.
This solution here EXTJS 4 render HTML of a selected value in a combobox is seems like not working with extjs6 version, check here
Here's the problem place code (rendere in case depth.TypeParameter: returns text with html tags)
{
xtype: 'treecolumn',
dataIndex: 'text',
text: Poly.utils.locale.Base.localeName,
flex: 1,
getEditor: function (record) {
return me.getController().getEditor(record);
},
renderer: function (value, meta, record) {
var depth = Poly.view.fluidProperties.sample.Tree.depth;
switch (record.getDepth()) {
case depth.Temperature:
if (Ext.isEmpty(record.get('temperature'))) {
return value;
}
var text = Ext.String.format('T = {0} {1}',
record.get('temperature').toFixed(2),
Poly.utils.UniSum.GetUnit(me.getViewModel().get('temperatureUnitId')).name);
return text;
case depth.TypeParameter:
if (record.get('isNew')) {
return value;
}
return Poly.enums.TypeFluidParameter.getName(record.get('fluidParameter'), record.parentNode.get('typeFluid'), true);
}
return value;
}
}
Full code here
Ext.define('Poly.view.fluidProperties.sample.Tree', {
extend: 'Ext.tree.Panel',
xtype: 'fluidPropertiesSampleTree',
viewModel: {
type: 'fluidPropertiesSampleTreeViewModel'
},
controller: 'fluidPropertiesSampleTreeController',
statics: {
/** Уровень элемента в дереве */
depth: {
/** Корень */
Root: 0,
/** Замер */
Sample: 1,
/** Тип среды */
TypeFluid: 2,
/** Параметер */
TypeParameter: 3,
/** Температура */
Temperature: 4
}
},
lines: false,
rootVisible: false,
useArrows: true,
enableColumnHide: false,
enableColumnResize: false,
sortableColumns: false,
border: true,
viewConfig: {
cls: 'gridActionColumnHide'
},
dockedItems: [
{
xtype: 'toolbar',
dock: 'bottom',
ui: 'footer',
cls: 'transparent',
layout: {
type: 'hbox',
align: 'middle',
pack: 'center'
},
items: [
{
xtype: 'button',
cls: 'pvt-chart-button',
text: '', // локализация в initComponent
flex: 2,
name: 'addSample',
margin: 2
},
{
xtype: 'button',
cls: 'pvt-chart-button',
text: '', // локализация в initComponent
flex: 1,
name: 'import',
disabled: true,
margin: 2
},
{
xtype: 'button',
cls: 'pvt-chart-button',
text: '', // локализация в initComponent
flex: 1,
name: 'export',
disabled: true,
margin: 2
}
]
}
],
listeners: {
checkchange: 'nodeCheckChange',
edit: 'edit'
},
plugins: {
ptype: 'cellediting',
clicksToEdit: 2
},
bind: {
selection: '{selectedRecord}'
},
initComponent: function () {
var me = this,
store = Ext.create('Ext.data.TreeStore', {
root: {
expanded: true,
children: []
}
}),
controller = me.getController();
me.dockedItems[0].items[0].text = me.locale.addSample;
me.dockedItems[0].items[1].text = me.locale.importText;
me.dockedItems[0].items[2].text = me.locale.exportText;
Ext.applyIf(me, {
store: store,
columns: [
{
xtype: 'treecolumn',
dataIndex: 'text',
text: Poly.utils.locale.Base.localeName,
flex: 1,
getEditor: function (record) {
return me.getController().getEditor(record);
},
renderer: function (value, meta, record) {
var depth = Poly.view.fluidProperties.sample.Tree.depth;
switch (record.getDepth()) {
case depth.Temperature:
if (Ext.isEmpty(record.get('temperature'))) {
return value;
}
var text = Ext.String.format('T = {0} {1}',
record.get('temperature').toFixed(2),
Poly.utils.UniSum.GetUnit(me.getViewModel().get('temperatureUnitId')).name);
return text;
case depth.TypeParameter:
if (record.get('isNew')) {
return value;
}
return Poly.enums.TypeFluidParameter.getName(record.get('fluidParameter'), record.parentNode.get('typeFluid'), true);
}
return value;
}
},
{
width: 30,
xtype: 'widgetcolumn',
name: 'menuWidgetcolumn',
widget: {
xtype: 'button',
margin: '5 0 0 0',
arrowCls: '',
width: 15,
height: 15,
style: {
'background-color': '000000',
'border-color': '000000'
},
menu: {
xtype: 'colormenu',
listeners: {
select: function (component, color) {
var button = component.up('button');
button.setStyle('background-color', color);
}
}
}
},
onWidgetAttach: function (column, widget, record) {
widget.setVisible(Ext.isNumber(record.get('temperature')));
}
},
{
xtype: 'actioncolumn',
width: 25,
items: [
{
handler: 'removeTreeItem',
getClass: function (v, meta, rec) {
if (!rec.get('isNew')) {
return 'poly-trash-icon';
}
return '';
},
getTip: function (v, meta, rec) {
if (!rec.get('isNew')) {
return 'Delete';
}
return '';
}
}
]
}
]
});
me.getSampleNode = controller.getSampleNode;
me.setTypeMode = Ext.bind(controller.setTypeMode, controller);
me.callParent(arguments);
}
});
Html input element can't display HTML, so you need to change template add div. Div can be shown as an overlay over input.
Best way to achieve this is by extending ComboBox:
Ext.define('HtmlComboBox', {
extend: 'Ext.form.field.ComboBox',
fieldSubTpl: [ // note: {id} here is really {inputId}, but {cmpId} is available
'<input id="{id}" data-ref="inputEl" type="{type}" {inputAttrTpl}',
' size="1"', // allows inputs to fully respect CSS widths across all browsers
'<tpl if="name"> name="{name}"</tpl>',
'<tpl if="value"> value="{[Ext.util.Format.htmlEncode(values.value)]}"</tpl>',
'<tpl if="placeholder"> placeholder="{placeholder}"</tpl>',
'{%if (values.maxLength !== undefined){%} maxlength="{maxLength}"{%}%}',
'<tpl if="readOnly"> readonly="readonly"</tpl>',
'<tpl if="disabled"> disabled="disabled"</tpl>',
'<tpl if="tabIdx != null"> tabindex="{tabIdx}"</tpl>',
'<tpl if="fieldStyle"> style="{fieldStyle}"</tpl>',
'<tpl foreach="inputElAriaAttributes"> {$}="{.}"</tpl>',
' class="{fieldCls} {typeCls} {typeCls}-{ui} {editableCls} {inputCls}" autocomplete="off"/>',
// overlay element to show formatted value
'<div id="{cmpId}-overlayEl" data-ref="overlayEl"<tpl if="name"> name="{name}-overlayEl"</tpl> class="{fieldCls}-overlay {typeCls} {typeCls}-{ui} {inputCls}">{value}</div>',
{
disableFormats: true
}
],
forceSelection: true,
childEls: [
'overlayEl'
],
setRawValue: function(value) {
var me = this;
// set value in overlay
if (me.rendered) {
me.overlayEl.update(value);
}
return me.callParent([value]);
}
});
With that, some additional CSS is required:
.x-form-text-wrap {
position: relative;
}
.x-form-field-overlay {
background-color: #ffffff;
position: absolute;
top: 0;
}
Fiddle: https://fiddle.sencha.com/#fiddle/14mm
I suppose that your editor is combo, by default combo (as well as many other components) display HTML as plain text.
Example
I guess as workaround you could overrite combo (or any other component), i.e. change component <input> element to <div>. It will entail overrites of some methods (setValue() for example).

Add buttons in ExtJS menu item

Is it possible to add buttons inside a menuitem using ExtJS 4.2?
I need to achieve the following:
(To have an edit and delete buttons next to the checkbox of each menuitem. These will have their own handlers and will open new panels when clicking on them)
I've tried to achieve it by creating a container per each employee and inside it add the buttons needed plus the menuitem but it's not working properly since I can't click the custom menuitem anymore after doing it and the design didn't look good at all.
However, I found a possible "hint" in the following StackOverflow answer:
https://stackoverflow.com/a/11213707/1178686
(But this is not entirely what I expect since I don't need just an icon, I need a button with its own handler plus I don't know if interacting with the DOM is a good approach for this)
Here's what I got so far and where I'm doing my tests:
Live demo:
http://jsfiddle.net/oscarj24/hmqjqtqs/
ExtJS code:
Ext.define('Namespace.view.Panel', {
extend: 'Ext.panel.Panel',
title: 'Panel',
frame: true,
floating: true,
draggable: true,
resizable: false,
closable: true,
employees: null,
layout: {
type: 'vbox',
align: 'stretch'
},
constructor: function(cfg) {
Ext.apply(this, cfg || {});
this.items = [{
xtype: 'container',
border: false,
items: this.createItems()
}];
this.callParent(arguments);
},
createItems: function() {
var items = [];
items.push({
xtype: 'button',
text: 'Employees',
menu: {
xtype: 'menu',
items: this.createMenuItems()
}
});
return items;
},
createMenuItems: function() {
var employees = this.employees || [],
items = [];
for (var i = 0; i < employees.length; ++i) {
var employee = employees[i];
if (employee) {
items.push({
xtype: 'menucheckitem',
text: Ext.String.format('{0} {1}', employee.name, employee.lastname),
employeeId: employee.id,
listeners: {
scope: this,
checkchange: this.onMenuItemCheckChange
}
});
}
}
items.push({
xtype: 'menuitem',
iconCls: 'add',
text: 'Add'
});
return items;
},
onMenuItemCheckChange: function(item, checked, eOpts) {
console.log('Employee Id: %o was checked: %o', item.employeeId, checked);
},
destroy: function() {
delete this.employees;
this.callParent(arguments);
}
});
Ext.onReady(function() {
Ext.create('Namespace.view.Panel', {
employees: [
{id: 1, name: 'Oscar', lastname: 'Jara'},
{id: 2, name: 'Foo', lastname: 'Bar'}
]
}).show();
});
CSS:
.add {
background-image: url('http://icons.iconarchive.com/icons/awicons/vista-artistic/16/add-icon.png') !important; width: 16px; height: 16px; display: block;
}
.edit {
background-image: url('http://icons.iconarchive.com/icons/designcontest/outline/16/Pencil-icon.png') !important; width: 16px; height: 16px; display: block;
}
.delete {
background-image: url('http://icons.iconarchive.com/icons/oxygen-icons.org/oxygen/16/Actions-edit-delete-icon.png') !important; width: 16px; height: 16px; display: block;
}
If someone knows a "proper" way to achieve this please let me know since I am not sure if one of the configs specified in the API for this component could help and I wasn't able to find a good example on the internet:
http://docs.sencha.com/extjs/4.2.2/#!/api/Ext.menu.CheckItem
Thanks in advance.
I would probably go the route of using a container and just trying to make that work. I realize the styling might not be a nice as what the framework provides, but with enough fine-tuning, you could get it there... just make use of Sencha CMD, scss files, and their pre-defined CSS vars.
In this example, I do a quick unstyling of the buttons, so they can be clickable icons/areas, and I throw the employee record on the button itself... not really the best approach, but it works. If you don't like listeners on each individual component like that, you could make the parent container have the listener, and in that function you would check the target's CSS class... but that's besides the point. Working example:
Ext.application({
name: 'Fiddle',
launch: function() {
Ext.define('MyPanel', {
extend: 'Ext.panel.Panel',
title: 'My Panel',
renderTo: Ext.getBody(),
employees: [{
id: 1,
name: 'Oscar',
lastname: 'Jara'
}, {
id: 2,
name: 'Foo',
lastname: 'Bar'
}],
initComponent: function() {
this.createMenuItems();
this.callParent();
},
createMenuItems: function() {
var items = [];
var employees = this.employees;
if (employees) {
for (var i = 0; i < employees.length; i++) {
var employee = employees[i];
var containerItems = [];
var checkboxCmp = Ext.create('Ext.form.field.Checkbox', {
width: 20
});
containerItems.push(checkboxCmp);
containerItems.push({
xtype: 'button',
cls: 'my-custom-button',
employee: employee,
width: 22,
text: '',
iconCls: 'edit',
listeners: {
click: this.onClickEditButton
}
});
containerItems.push({
xtype: 'button',
cls: 'my-custom-button',
employee: employee,
width: 22,
text: '',
iconCls: 'delete',
listeners: {
click: this.onClickDeleteButton
}
});
containerItems.push({
xtype: 'component',
html: '<div style="border-left:1px solid #000;height:100%; display: inline-block;"></div>'
});
containerItems.push({
xtype: 'button',
cls: 'my-custom-button',
textAlign: 'left',
checkboxCmp: checkboxCmp,
employee: employee,
flex: 1,
text: employee.name + ' ' + employee.lastname,
listeners: {
click: this.onClickEmployee
}
});
items.push({
xtype: 'container',
layout: {
type: 'hbox',
align: 'stretch'
},
overCls: 'over-item-cls',
items: containerItems
});
}
}
this.tools = [{
xtype: 'button',
text: 'Employees',
menu: {
xtype: 'menu',
items: items,
plain: true
}
}];
},
onClickDeleteButton: function(button, event, eOpts) {
alert('clicked delete, check console for employee');
console.log('delete', button.employee);
},
onClickEditButton: function(button, event, eOpts) {
alert('clicked edit, check console for employee');
console.log('edit', button.employee);
},
onClickEmployee: function(button, event, eOpts) {
alert('employee checkbox changed, check console for employee');
console.log('employee', button.employee);
var checkboxCmp = button.checkboxCmp;
if (checkboxCmp) {
checkboxCmp.setValue(!checkboxCmp.getValue());
}
}
});
Ext.create('MyPanel');
}
});
CSS
.add {
background-image: url('http://icons.iconarchive.com/icons/awicons/vista-artistic/16/add-icon.png') !important; width: 16px; height: 16px; display: block;
}
.edit {
opacity: 0.4;
background-image: url('http://icons.iconarchive.com/icons/designcontest/outline/16/Pencil-icon.png') !important; width: 16px; height: 16px; display: block;
}
.edit:hover,
.delete:hover {
opacity: 1.0;
}
.delete {
opacity: 0.4;
background-image: url('http://icons.iconarchive.com/icons/oxygen-icons.org/oxygen/16/Actions-edit-delete-icon.png') !important; width: 16px; height: 16px; display: block;
}
.over-item-cls {
background-color: lightblue;
}
a.my-custom-button.x-btn-default-small {
background: none;
border: none;
}
a.my-custom-button.x-btn-default-small span {
color: #ababab;
}
a.my-custom-button.x-btn-default-small:hover span {
color: black;
}
I am not sure you are insterested in using tpl in menu items: If you are, I can improve this code below. And Fiddle: https://fiddle.sencha.com/#fiddle/tt3
Ext.define('Image', {
extend: 'Ext.data.Model',
fields: [
{ name:'buttonText1', type:'string' },
{ name:'buttonText2', type:'string' },
{ name:'menuText', type:'string' }
]
});
Ext.create('Ext.data.Store', {
id:'imagesStore',
model: 'Image',
data: [
{ buttonText1:'edit', buttonText2: 'add', menuText:'Drawing', id: '1' },
{ buttonText1:'edit', buttonText2: 'add', menuText:'Advanced', id: '2' },
]
});
var imageTpl = new Ext.XTemplate(
'<tpl for="."><table>',
'<div class="menu-row">',
'<input type="checkbox" id="check{id}">',
'<button class="{buttonText1}">{buttonText1}</button>',
'<button class="{buttonText2}" >{buttonText2}</button> {menuText}',
'</div>',
'<table></tpl>'
);
var dataview = Ext.create('Ext.view.View', {
itemId: 'idDataView',
store: Ext.data.StoreManager.lookup('imagesStore'),
tpl: imageTpl,
itemSelector: 'div.menu-row',
listeners: {
itemclick: function(dataview, record,items) {
Ext.each(items.children, function(item) {
if (item.id == 'check'+ record.get('id')) {
item.checked == false ? item.checked = true : item.checked = false;
}
});
}
}
});
var button = Ext.create('Ext.button.Split', {
text: 'menuButton',
margin: '100 100 100 100',
menu: {
//plain: true,
items: [dataview],
listeners: {
afterrender: function(menu) {
var task = new Ext.util.DelayedTask(function(){
Ext.each(menu.down('#idDataView').getEl().dom.children, function(nodes) {
Ext.each(nodes.children, function(node) {
if (node.className == 'edit') {
node.addEventListener('click', function() {
alert('edited');
});
} else if (node.className == 'add') {
node.addEventListener('click', function() {
alert('added');
});
}
})
});
});
task.delay(100);
}
},
},
renderTo: Ext.getBody()
});

ExtJS5: Checkbox afterSubTpl wraps icons

The snippet below will not run on this site, but you can access a working demo here on JSFiddle.
I am trying to get the icon to appear to the right of the checkbox, but it appears to be wrapping. Is there a property that I need to set so that it will appear in-line? This worked in ExtJS4, but after I switched to ExtJS5 this does not work.
afterSubTpl Documentation
Ext.form.field.Checkbox -> config -> afterSubTpl
An optional string or XTemplate configuration to insert in the field markup after the subTpl markup. If an XTemplate is used, the component's render data serves as the context.
Code
Ext.require(['*']);
Ext.define('App.view.MainView', {
extend: 'Ext.panel.Panel',
xtype: 'mainView',
alias: 'widget.mainview',
title: 'Checkbox Template Example',
referenceHolder: true,
layout: {
type: 'border'
},
initComponent: function () {
var me = this,
tooltip = me.getHelpIconWithTooltip();
me.items = [{
region: 'center',
xtype: 'panel',
title : 'A Panel Title',
margin : 20,
bodyStyle: 'padding: 8px;',
layout : 'vbox',
items : [{
xtype: 'checkbox',
fieldLabel : 'Checkbox',
afterSubTpl: tooltip
}]
}],
me.callParent();
},
getHelpIconWithTooltip: function () {
return this.getFormIconWithTooltip('help-icon help-form-icon',
'This is a help tooltip...');
},
getFormIconWithTooltip: function (iconClassList, toolTipText) {
var getSpan = function(cl, qt) {
return '<span class="'+cl+'" data-qtip="'+qt+'"></span>';
}
return [
'<tpl>',
getSpan(iconClassList, toolTipText),
'</tpl>'
];
}
});
Ext.define('App.app.App', {
extend: 'Ext.app.Application',
name: 'App',
launch: function () {
Ext.create('Ext.Viewport', {
layout: 'fit',
flex: 1,
items: [{
xtype: 'mainview'
}]
});
}
});
Ext.onReady(function () {
Ext.application('App.app.App');
});
.help-icon {
display: inline-block;
background: url(http://www.famfamfam.com/lab/icons/silk/previews/index_abc.png) -705px -1125px no-repeat;
width: 16px;
height: 16px;
}
.help-form-icon {
/*margin-left: 5px;*/
}
<link href="https://extjs.cachefly.net/ext/gpl/5.1.0/packages/ext-theme-crisp/build/resources/ext-theme-crisp-all-debug.css" rel="stylesheet" />
<script src="http://cdn.sencha.com/ext/gpl/5.1.0/build/ext-all.js"></script>
Edit
It looks like if I add a class to the checkbox, I can prevent it from wrapping, but now the icon is positioned too high.
{
xtype: 'checkbox',
fieldLabel: 'Checkbox',
afterSubTpl: tooltip,
cls: 'no-wrap-tooltip'
}
.no-wrap-tooltip .x-form-cb-wrap-inner {
display: inline-block;
}
I may have figured it out, but this solution uses magic numbers which make it work in this instance.
Demo
{
xtype: 'checkbox',
fieldLabel: 'Checkbox',
afterSubTpl: tooltip,
cls: 'no-wrap-tooltip'
}
.no-wrap-tooltip .x-form-cb-wrap-inner {
display: inline-block;
line-height: 28px;
}

Categories