Related
I am looking for best practises or advice in how to create interactive elements within Shapes (elements) using the JointJS (Rappid) library.
This is what I have done currently:
The code
const shape = new joint.shapes.basic.Generic({
type: 'shape',
attrs: {},
markup: [
{
tagName: 'foreignObject',
selector: 'foreignObject',
attributes: {
x: '10',
y: '10',
width: '60',
height: '100',
},
children: [
{
tagName: 'div',
namespaceURI: 'http://www.w3.org/1999/xhtml',
selector: 'content',
style: {
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
},
children: [
{
tagName: 'select',
selector: 'select',
style: {
'z-index': 1,
},
children: [
{
tagName: 'option',
value: 'test1',
textContent: 'test 1',
},
{
tagName: 'option',
value: 'test2',
textContent: 'test 2',
},
],
},
],
},
],
},
],
});
You can see there is a shape rendered and it has a select element in it by using a foreignObject (https://developer.mozilla.org/en-US/docs/Web/SVG/Element/foreignObject).
However, using foreignObject is troublesome due to that they do not play well in browsers and the drop down you see here doesn't actually work.
I could start hacking around and get it to work but this is the point of the question, is there a cleaner way in achieving this?
I need something like this
https://resources.jointjs.com/tutorial/html-elements
But the html library is now decrepitated and JointJS wants you to use markup JSON instead.
Lastly, I have seen this post which doesn't fill me much with confidence...
https://groups.google.com/g/jointjs/c/-yXXlnreq6M
But I am hoping as this is from 5 years ago, it is outdated and we don't need to have to do the workarounds as it suggests?
HTML can be added inside foreignObject, but as you mentioned there can be some issues in browsers. One example of using foreignObject can be found here.
The code demonstrates how to create a text input with foreignObject.
var paper = new joint.dia.Paper({
el: document.getElementById('paper'),
width: 650,
height: 400,
gridSize: 10,
model: graph,
guard: function(evt) {
return evt.target instanceof HTMLInputElement;
}
});
paper.on('blank:pointerdown cell:pointerdown', function() {
document.activeElement.blur();
});
var Example = joint.dia.Element.define('example.ForeignObject', {
attrs: {
body: {
refWidth: '100%',
refHeight: '100%',
stroke: '#333333',
fill: '#ffffff',
strokeWidth: 2
},
foreignObject: {
refWidth: '100%',
refHeight: '100%'
}
}
}, {
markup: [{
tagName: 'rect',
selector: 'body'
}, {
tagName: 'foreignObject',
selector: 'foreignObject',
attributes: {
'overflow': 'hidden'
},
children: [{
tagName: 'div',
namespaceURI: 'http://www.w3.org/1999/xhtml',
selector: 'content',
style: {
fontSize: 14,
width: '100%',
height: '100%',
position: 'static',
backgroundColor: 'transparent',
textAlign: 'center',
margin: 0,
padding: '0px 10px',
boxSizing: 'border-box',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center'
},
children: [{
tagName: 'span',
textContent: 'First Name'
}, {
tagName: 'input',
selector: 'firstname',
attributes: {
'type': 'input',
'name': 'firstname'
},
style: {
position: 'static',
width: '100%'
}
}, {
tagName: 'span',
textContent: 'Last Name'
}, {
tagName: 'input',
selector: 'lastname',
attributes: {
'type': 'input',
'name': 'lastname'
},
style: {
position: 'static',
width: '100%'
}
}]
}]
}]
}, {
attributes: {
value: {
set: function(text, _, node) {
if ('value' in node) node.value = text;
}
}
}
});
joint.shapes.example.ForeignObjectView = joint.dia.ElementView.extend({
events: {
'change input': 'onInputChange'
},
onInputChange: function(evt) {
var input = evt.target;
this.model.attr(input.name + '/value', input.value);
}
});
The article that you linked is deprecated, but there is an updated version of creating elements with a HTML face here. You can see some select fields demonstrated there, but it is a little more involved.
I have the following custom JointJS element defined:
joint.shapes.webtp.BowTie = joint.dia.Element.define('webtp.BowTie',
{
size: { width: 400, height: 100 },
attrs: {
body: {
strokeWidth: 2,
stroke: '#000000',
fill: '#FFFFFF',
},
},
},
{
markup: [
{
tagName: 'g',
selector: 'g1',
attributes: {
class: 'rotatable',
},
children: [
{
tagName: 'g',
selector: 'g2',
attributes: {
class: 'scalable',
},
children: [
{
tagName: 'path',
selector: 'body',
attributes: {
d: 'm0,0l0,100l200,-25l200,25l0,-100l-200,25l-200,-25',
},
},
]
}
]
},
],
});
Using resize or scale on the shape does not resize it, however. It always ends up being 400x100.
I thought the issue originally was that it needed to be wrapped in a class="scalable" <g> but that didn't fix it either.
I also tried using<line>s instead of <path> but no luck.
Thanks!
The answer is in the refDResetOffset attribute, which (like the other ref custom attributes scales with the parent):
joint.shapes.webtp.BowTie = joint.dia.Element.define('webtp.BowTie',
{
attrs: {
body: {
strokeWidth: 2,
stroke: '#000000',
fill: '#FFFFFF',
refDResetOffset: 'm0,0l0,100l200,-25l200,25l0,-100l-200,25l-200,-25'
},
},
},
{
markup: [
{
tagName: 'g',
selector: 'g1',
attributes: {
class: 'rotatable',
},
children: [
{
tagName: 'g',
selector: 'g2',
attributes: {
class: 'scalable',
},
children: [
{
tagName: 'path',
selector: 'body',
},
]
}
]
},
],
});
I dont know what to do...
Without hbox the grid appears,
but with hbox not.
I added with & height and flex to each child element,
but it doesnt work...
Thanks in advance!
And here's the code:
Ext.onReady(function() {
var myStore = Ext.create('Ext.data.SimpleStore', {
fields: [ 'bMin', ], });
var myData = [ { "bMin": 10, } ];
myStore.loadData(myData);
var grid = new Ext.grid.GridPanel({
layout : { type : 'hbox', align : 'stretch', flex:2,
Height: 150,
Width: 300,
},
cls: 'custom-grid',
store: myStore,
columns: [
{text: "bMin", dataIndex: 'bMin', type: 'float',},
],
viewConfig: {
emptyText: 'No records',
forceFit : true,
},
renderTo: Ext.getBody(),
});
var myPanel = new Ext.Panel({
layout : {
type : 'hbox',
align : 'stretch',
},
title: 'Hello',
minHeight : 150,
minWidth: 300,
Height: 150,
Width: 300,
items: [
grid,
{xtype: 'button', width: 50, height: 50, flex: 1}
],
renderTo: Ext.getBody()
});
});
On the Grid you don't need a 'layout' config, also when using an HBox Height and Width is ignored on the child components so using flex is the way to go. I also added a 'pack' attribute to the hbox layout.
Try this:
Ext.onReady(function() {
var myStore = Ext.create('Ext.data.SimpleStore', {
fields: [ 'bMin', ], });
var myData = [ { "bMin": 10, } ];
myStore.loadData(myData);
var grid = new Ext.grid.GridPanel({
flex: 1,
cls: 'custom-grid',
store: myStore,
columns: [
{text: "bMin", dataIndex: 'bMin', type: 'float',},
],
viewConfig: {
emptyText: 'No records',
forceFit : true,
},
renderTo: Ext.getBody(),
});
var myPanel = new Ext.Panel({
layout : {
type : 'hbox',
align : 'stretch',
pack: 'start'
},
title: 'Hello',
minHeight : 150,
minWidth: 300,
Height: 150,
Width: 300,
items: [
grid,
{xtype: 'button', flex: 1, text: 'test'}
],
renderTo: Ext.getBody()
});
});
JSFiddle Example:
http://jsfiddle.net/vzURb/2/
I see a number of problems here...
height and width config properties should not be capitalized
The flex, height, and width properties should all be on the panel itself, NOT on the panel's layout
The flex attribute will be weighted, so using flex: 1 on your button and flex: 2 on your panel will almost certainly not be what you are looking to do
You have renderTo on the grid, which is supposed to be inside your panel, so it should not use this config
You have commas at the ends of property lists, this can lead to lots of problems
You have type on a column, which is not a valid config
You don't need the layout on the grid
I can't tell exactly what it is that you want to do, but having an hbox layout with a button as the second item will look quite strange. But, if that is what you are intending, this is probably more what you want:
var grid = new Ext.grid.GridPanel({
cls: 'custom-grid',
store: myStore,
columns: [
{text: "bMin", dataIndex: 'bMin'}
],
viewConfig: {
emptyText: 'No records',
forceFit : true
}
});
var myPanel = new Ext.Panel({
layout : {
type : 'hbox',
align : 'stretch'
},
title: 'Hello',
height: 150,
width: 300,
items: [
grid,
{xtype: 'button', width: 50, height: 50, flex: 1}
],
renderTo: Ext.getBody()
});
I have to do the following layout :
The red container has the layout card and contains :
A titlebar
A container : This one display a map and should take all the size of the screen below the titlebar
A panel : This is to display custom control buttons, it should be over the map and not hide it (background is transparent)
I tried the following code but it didn't work, I can't figure out how to place components over another one. If I use the hbox layout, the custom control buttons will be below the map, and not on the map...
Ext.define('Sencha.view.MapPanel', {
extend: 'Ext.Container',
requires: ['Ext.ux.LeafletMap'],
xtype: 'mappanel',
config: {
itemId: 'mapanel',
layout: 'card',
items: [{
xtype: 'titlebar',
title: 'title',
docked: 'top'
}, {
xtype: 'panel',
config:{
layout: 'fit',
height: '100px',
width: '100px',
itemId: 'controlButtons'
}
}, {
xtype: 'leafletmap',
mapOptions: {
zoom: 13,
zoomControl: false
},
config: {
layout: 'fit'
}
}]
}
});
Here the controlsButton show but not the map. If I put the controlsButton after the leafletMap, the map shows but not the buttons...
Any help welcome!
Ext.define('MyApp.view.MyContainer', {
extend: 'Ext.Container',
config: {
html: 'Main Container',
style: 'border: 2px solid black;',
layout: {
type: 'card'
},
items: [
{
xtype: 'container',
style: 'border:2px solid red',
layout: {
type: 'card'
},
items: [
{
xtype: 'titlebar',
docked: 'top',
title: 'Title Bar'
},
{
xtype: 'container',
html: 'Container',
style: 'border:2px solid blue;',
layout: {
type: 'hbox'
},
items: [
{
xtype: 'panel',
docked: 'bottom',
height: '20%',
html: 'Panel that holds buttons',
style: 'border: 2px solid green;',
top: '',
width: '50%'
}
]
}
]
}
]
}
});
This is as same as you have wanted (per screenshot). Please ignore the border style. That was just to show you the difference. Hope you get an idea from this. :) Best luck!!
I have a panel which is fullscreen;
PortalDashboard.views.Dashboardcard = Ext.extend(Ext.Panel, {
fullscreen: true,
title: 'Dashboard',
html: '',
cls: 'card5',
iconCls: 'team',
layout: Ext.Viewport.orientation == 'landscape' ? {
type: 'hbox',
pack: 'center',
align: 'stretch'
} : {
type: 'vbox',
align: 'stretch',
pack: 'center'
},
monitorOrientation: true,
listeners: {
orientationchange : this.onOrientationChange,
},
styleHtmlContent: false,
initComponent: function () {
Ext.apply(this, {
items: [
{
xtype: 'dashboardbox',
items: [rep1, rep2, rep3]
}, {
xtype: 'dashboardbox',
items: [rep4, rep5, rep6]
}]
});
PortalDashboard.views.Dashboardcard.superclass.initComponent.apply(this, arguments);
}
})
So the panel has a hbox layout with 2 child panels. The child panels actually take up the full amount of horizontal space, but not vertically.
I can set the min-height in the css, which gets respected on in chrome and safari on my pc... but the ipad ignores it.
The child panels are defined as;
PortalDashboard.views.DashboxBox = Ext.extend(Ext.Panel, {
cls: 'dashbox',
monitorOrientation: true,
listeners: {
orientationchange : this.onOrientationChange,
},
layout: Ext.Viewport.orientation == 'landscape' ? {
type: 'vbox',
align: 'stretch',
pack: 'center'
} : {
type: 'hbox',
align: 'stretch',
pack: 'center'
},
defaults: {
flex: 1
}
});
Ext.reg('dashboardbox', PortalDashboard.views.DashboxBox);
I've had this before with a TabPanel parent of some sub panels. Try layout: 'fit' in your parent panel. (Although I'm not sure if adding it to your current list of layout options will work or if it needs to be set by itself.)