How to make a image dynamically change to text - html

I am making an online game using Phaser and I need to make buttons with text on them for it that can change based on the text because the text can be different each time. I tried checking the API document but when I put in the get size function to try to get the bounds of the text my button disappears or the code will stop working with the error saying cannot read properties of undefined (reading getBounds) and it will swap between the two every time I reload the page.
count = Phaser.Math.Between(1,4)
for(let i = 50;i <= 750;i = i +200){
bingus = this.add.text(i, 400, quiz[category][difficulty][quest][count])
answs.push(bingus)
gorp.push(count)
count++
}
if(count > 4){
count = 1
}
}
this.butt1.setDisplaySize(Phaser.Geom.Rectangle.GetSize(answs[gorp[0]].getBounds()))

You could use the Phaser.GameObjects.Text and it's displayWidth and / or displayHeight properties, together with the global Phaser.Display.Align.In.Center function.
Maybe this works also for your UseCase.
Basically:
set the text of the text Object with setText
get the current displayWidth and displayHeight of the text Object
update/adjust the size of the button Object, also with displayWidth and displayHeight properties
Center the text Object inside of the button Object, with the Phaser.Display.Align.In.Center function
Here a small working Demo:
document.body.style = 'margin:0;';
var config = {
type: Phaser.AUTO,
width: 500,
height: 180,
scene: {
create
},
banner: false
};
let text;
let button;
let texts = ['first Text', 'Next', 'Second Text', 'Last', 'multiline1.\nmultiline2..\nmultiline3...' ];
let padding = 20;
let currentTextIdx = 0;
function create () {
this.add.text(10, 10, 'Text cycles about every second.')
button = this.add.rectangle(250, 90, 100, 40, 0xff0000 )
.setOrigin(.5);
text = this.add.text(250, 90, texts[currentTextIdx])
.setOrigin(.5);
this.time.addEvent({ delay: 1000, startAt:999, loop: true , callback: _ => {
currentTextIdx++;
if(currentTextIdx >= texts.length){
currentTextIdx = 0;
}
let newText = texts[currentTextIdx];
text.setText(newText);
button.displayWidth = padding * 2 + text.displayWidth;
button.displayHeight = padding * 2 + text.displayHeight;
Phaser.Display.Align.In.Center(text, button);
}});
}
new Phaser.Game(config);
<script src="//cdn.jsdelivr.net/npm/phaser#3.55.2/dist/phaser.js"></script>

Related

Google Chrome and Safari on MacOS grabs the character preceding the selected text when you drag'n'drop it into <textarea>

Clean textarea code (as in sandbox) without any side factors (like scripts and specific styles) its work via drag'n'drop in the Chrome browser is followed by a strange phenomenon.
<textarea cols="80" rows="10">
row1w1 row1w2 row1w3 row1w4 row1w5
row2w1 row2w2 row2w3 row2w4 row2w5
row3w1 row3w2 row3w3 row3w4 row3w5
row4w1 row4w2 row4w3 row4w4 row4w5
row5w1 row5w2 row5w3 row5w4 row5w5
row6w1 row6w2 row6w3 row6w4 row6w5
row7w1 row7w2 row7w3 row7w4 row7w5
</textarea>
For example, I drag the first word from any line somewhere like in the GIF image.
Along with the word, for some reason the drag-n-drop event grabs the character before the word (in this case, it's a line break character), even though that character was not selected. It is "compensated", if I may say so, by a space put before the word inserted in the new place. In some cases, this space can stand before the word, after the word and even turn into two spaces on both sides of the word, and sometimes does not appear at all.
The actual question is. Is there a way to make Chrome behave like a text editor or like Firefox? Is there a standard somewhere, or at least some description of how the phenomenon works, when it puts a space and when it doesn't?
I tried to solve this problem with a script. But the code works very crudely. For example, I can not track the difference between moving the selected text one character forward or backward. I think there is a better solution.
// If Google Chrome used
if (/Chrome/.test(window.navigator.userAgent)) {
// This variable handles source drag info
let dragging = null;
// This flag indicates whether the browser has captured a character before the word
let captureFlag = null;
// DragStart event handler
textarea.ondragstart = (e) => {
// First of all, аt this point we can read the initial state before dragging
const {
value,
selectionStart,
selectionEnd
} = textarea;
// Selected text
const selectionData = value.substring(selectionStart, selectionEnd);
// And keep the symbol before of the selected word
const selectionBefore = selectionStart ? value[selectionStart - 1] : '';
dragging = {
selectionStart,
selectionEnd,
selectionData,
selectionBefore
};
}
// Input event handler
textarea.oninput = (e) => {
// When dragging, this event is triggered twice
if (e.inputType == 'deleteByDrag') {
// At this point the highlighted word has already been deleted.
const {
selectionStart
} = textarea;
// If the cursor moved one character backward,
// the browser has definitely captured this character
captureFlag = dragging && selectionStart == (dragging.selectionStart - 1);
} else if (e.inputType == 'insertFromDrop') {
// At this point, the selected word is already inserted in its new location.
if (dragging) {
// Chrome browser leaves the word selected.
const {
value,
selectionStart,
selectionEnd
} = textarea;
// First we cut out what is inserted from the value
let newValue = value.substr(0, selectionStart) + value.substr(selectionEnd);
// If the browser does grab a character before selecting
if (captureFlag && dragging.selectionStart > 0) {
// Let's calculate his position
const insertPos = dragging.selectionStart - 1;
// Put the missing symbol back in place
newValue = newValue.substr(0, insertPos) +
dragging.selectionBefore +
newValue.substr(insertPos);
}
// Next, we have to determine the offset.
// If the pasted text was inserted somewhere after the deletion,
// we need to add to the offset one character that we just inserted
const offset = (captureFlag && (selectionStart > dragging.selectionStart)) ? 1 : 0;
// Now let's count the positions where we insert the originally selected text
const newSelectionStart = selectionStart + offset;
const newSelectionEnd = dragging.selectionEnd - dragging.selectionStart +
selectionStart + offset;
// Insert text that was originally selected (still in the dragstart event)
newValue = newValue.substr(0, newSelectionStart) +
dragging.selectionData +
newValue.substr(newSelectionStart);
// Apply the new value
textarea.value = newValue;
// And let's select this area of text like the browser
textarea.setSelectionRange(newSelectionStart, newSelectionEnd, "none");
}
}
}
// Reset the variables when the drag is finished
textarea.ondragend = () => {
dragging = null;
captureFlag = null
};
}
<textarea id="textarea" cols="80" rows="10">
row1w1 row1w2 row1w3 row1w4 row1w5
row2w1 row2w2 row2w3 row2w4 row2w5
row3w1 row3w2 row3w3 row3w4 row3w5
row4w1 row4w2 row4w3 row4w4 row4w5
row5w1 row5w2 row5w3 row5w4 row5w5
row6w1 row6w2 row6w3 row6w4 row6w5
row7w1 row7w2 row7w3 row7w4 row7w5
</textarea>
UPDATE
I was fixed to track the dragging of the word one character backward or forward.
// If Google Chrome used
if (/Chrome/.test(window.navigator.userAgent)) {
// This variable handles source drag info
let dragging = null;
// This flag indicates whether the browser has captured a character before the word
let captureFlag = null;
// DragStart event handler
textarea.ondragstart = (e) => {
// First of all, аt this point we can read the initial state before dragging
const {
value,
selectionStart,
selectionEnd
} = textarea;
// Selected text
const selectionData = value.substring(selectionStart, selectionEnd);
// And keep the symbol before of the selected word
const selectionBefore = selectionStart ? value[selectionStart - 1] : '';
// Save drag position
dragging = {
selectionStart,
selectionEnd,
selectionData,
selectionBefore,
dragLayerX: e.layerX,
dragLayerY: e.layerY,
dropLayerX: -1,
dropLayerY: -1,
};
}
// drop event handler
textarea.ondrop = (e) => {
// Save drop position
dragging.dropLayerX = e.layerX;
dragging.dropLayerY = e.layerY;
}
// Input event handler
textarea.oninput = (e) => {
// When dragging, this event is triggered twice
if (e.inputType == 'deleteByDrag') {
// At this point the highlighted word has already been deleted.
const {
selectionStart
} = textarea;
// If the cursor moved one character backward,
// the browser has definitely captured this character
captureFlag = dragging && selectionStart == (dragging.selectionStart - 1);
} else if (e.inputType == 'insertFromDrop') {
// At this point, the selected word is already inserted in its new location.
if (dragging) {
// Chrome browser leaves the word selected.
const {
value,
selectionStart,
selectionEnd
} = textarea;
// First we cut out what is inserted from the value
let newValue = value.substr(0, selectionStart) + value.substr(selectionEnd);
// If the browser does grab a character before selecting
if (captureFlag && dragging.selectionStart > 0) {
// Let's calculate his position
const insertPos = dragging.selectionStart - 1;
// Put the missing symbol back in place
newValue = newValue.substr(0, insertPos) +
dragging.selectionBefore +
newValue.substr(insertPos);
}
// Next, we have to determine the offset.
let offset;
// If text was dragged from end of row and dropped to start of next row
if(selectionStart == dragging.selectionStart){
offset = 1;
}
// If text was dragging on one char backward or forward
// It was need to be calculated
else if(selectionStart - dragging.selectionStart == -1){
// First of all, lets calculate one text line height
let ctx = document.createElement('canvas').getContext('2d');
ctx.font = getComputedStyle(textarea).font;
const measure = ctx.measureText(textarea.value);
const lineHeight = measure.fontBoundingBoxAscent + measure.fontBoundingBoxDescent;
// Then set offset 0 if text moved backward or 2 if forward
if(Math.abs(dragging.dragLayerY - dragging.dropLayerY) > 0.6 * lineHeight){
offset = dragging.dragLayerY < dragging.dropLayerY ? 2 : 0;
}
else {
offset = dragging.dragLayerX < dragging.dropLayerX ? 2 : 0;
}
}
else {
// If the pasted text was inserted somewhere after the deletion,
// we need to add to the offset one character that we just inserted
offset = (captureFlag && (selectionStart > dragging.selectionStart)) ? 1 : 0;
}
// Now let's count the positions where we insert the originally selected text
const newSelectionStart = selectionStart + offset;
const newSelectionEnd = dragging.selectionEnd - dragging.selectionStart +
selectionStart + offset;
// Insert text that was originally selected (still in the dragstart event)
newValue = newValue.substr(0, newSelectionStart) +
dragging.selectionData +
newValue.substr(newSelectionStart);
// Apply the new value
textarea.value = newValue;
// And let's select this area of text like the browser
textarea.setSelectionRange(newSelectionStart, newSelectionEnd, "none");
}
}
}
// Reset the variables when the drag is finished
textarea.ondragend = () => {
dragging = null;
captureFlag = null
};
}
<textarea id="textarea" cols="80" rows="10">
row1w1 row1w2 row1w3 row1w4 row1w5
row2w1 row2w2 row2w3 row2w4 row2w5
row3w1 row3w2 row3w3 row3w4 row3w5
row4w1 row4w2 row4w3 row4w4 row4w5
row5w1 row5w2 row5w3 row5w4 row5w5
row6w1 row6w2 row6w3 row6w4 row6w5
row7w1 row7w2 row7w3 row7w4 row7w5
</textarea>

ag-Grid - Is it possible to create a floating menu for each row?

I'm trying to create a menu that appears when a user hovers on a row, just like in the image below.
I did not find any built-in option to achieve this. Also tried using a custom CellRenderer function to create an element that I could move around later, but that didn't work as expected since it presented some other challenges (css wise) and was not really achieving the goal.
Is there a way to build this kind of menus in ag-Grid?
To work around the problem, you could use onCellMouseOver & onCellMouseOut methods:
var gridOptions = {
columnDefs: columnDefs,
onCellMouseOver : onCellMouseOver,
onCellMouseOut: onCellMouseOut,
...
};
Define both functions:
var onCellMouseOver = function(event){
//Get current row
var rowIndex = event.node.rowIndex;
var row = gridOptions.api.getDisplayedRowAtIndex(rowIndex);
//Set current row as not selected - in order to base on 'cellStyle' function
row.setSelected(true);
//Setup refresh params
var params = {
force: true, //Force refresh as cell value didn't change
rowNodes: [row]
};
//Refresh current row cells
gridOptions.api.refreshCells(params);
}
var onCellMouseOut = function(event){
//Get current row
var rowIndex = event.node.rowIndex;
var row = gridOptions.api.getDisplayedRowAtIndex(rowIndex);
//Set current row as not selected - in order to base on 'cellStyle' function
row.setSelected(false);
//Setup refresh params
var params = {
force: true, //Force refresh as cell value didn't change
rowNodes: [row]
};
Then define 'cellStyle' function for your column:
var columnDefs = [
{headerName: "your_column_name", field: "your_column",
cellStyle: function(params) {;
console.log('Is row selected', params.node.selected);
if (params.node.selected) {
return {display : 'none'};
} else {
return {display : 'inherit'};
}
}
}
];
You can find more about data refresh here: https://www.ag-grid.com/javascript-grid-refresh/
The full implementation of the code above might be found here: Working example
Second solution, edited after comments:
Another way is to use css classes to achieve the result.
{
headerName: "Price",
field: "price",
cellStyle: { "text-align": "center" },
cellRenderer: function(params) {
return (
"<div class='buttons'>" +
"<div class='back'>" +
params.value +
"</div>" +
"<div class='front'><button>Option A</button><button>Option B</button></div>" +
"</div>"
);
}
Buttons are shown on hover based on .ag-row-hover ag-grid class:
.front {
display: none;
}
.ag-row-hover .front {
display: inherit;
}
Working example

How to move 3D model on Cesium

I wanted to move the model dynamically using keyboard shortcuts. I could not find relevant article on that.
So for now, I'm trying to move the model on click. When click on the model. The model has to move in one direction (increment the value 1 on tick). Find below the sandcastle code for that.
var selectedMesh; var i=0;
var viewer = new Cesium.Viewer('cesiumContainer', {
infoBox: false,
selectionIndicator: false
});
var handle = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
function createModel(url, height) {
viewer.entities.removeAll();
var position = Cesium.Cartesian3.fromDegrees(-123.0744619, 44.0503706, height);
var heading = Cesium.Math.toRadians(135);
var pitch = 0;
var roll = 0;
var orientation = Cesium.Transforms.headingPitchRollQuaternion(position, heading, pitch, roll);
var entity = viewer.entities.add({
name: url,
position: position,
orientation: orientation,
model: {
uri: url,
minimumPixelSize: 128
}
});
viewer.trackedEntity = entity;
viewer.clock.onTick.addEventListener(function () {
if (selectedMesh) {
console.log("Before 0 : " + selectedMesh.primitive.modelMatrix[12]);
selectedMesh.primitive.modelMatrix[12] = selectedMesh.primitive.modelMatrix[12] + 1;
console.log("After 0 : " + selectedMesh.primitive.modelMatrix[12]);
}
});
}
handle.setInputAction(function (movement) {
console.log("LEFT CLICK");
var pick = viewer.scene.pick(movement.position);
if (Cesium.defined(pick) && Cesium.defined(pick.node) && Cesium.defined(pick.mesh)) {
if (!selectedMesh) {
selectedMesh = pick;
}
}
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
var options = [{
text: 'Aircraft',
onselect: function () {
createModel('../../SampleData/models/CesiumAir/Cesium_Air.bgltf', 5000.0);
}
}, {
text: 'Ground vehicle',
onselect: function () {
createModel('../../SampleData/models/CesiumGround/Cesium_Ground.bgltf', 0);
}
}, {
text: 'Milk truck',
onselect: function () {
createModel('../../SampleData/models/CesiumMilkTruck/CesiumMilkTruck.bgltf', 0);
}
}, {
text: 'Skinned character',
onselect: function () {
createModel('../../SampleData/models/CesiumMan/Cesium_Man.bgltf', 0);
}
}];
Sandcastle.addToolbarMenu(options);
When I click, the model is moving for the first time. After that, It stays on the same place. I've printed the value in the console. It seems the value is not changing. I'm not sure about the problem here. or I'm implementing the transformation wrongly.
If you keep track of the current lat and lon of the entity, and adjust that lat and lon based on user input, all you need to do is update the orientation of the entity.
var lon = // the updated lon
var lat = // updated lat
var position = Cesium.Cartesian3.fromDegrees(lon, lat, height);
var heading = Cesium.Math.toRadians(135);
var pitch = 0;
var roll = 0;
// create an orientation based on the new position
var orientation = Cesium.Transforms.headingPitchRollQuaternion(position, heading, pitch, roll);
Then you just need to update the orientation of the entity.
entity.orientation = orientation;
By changing the value, the models orientation, and therefore position will get updated.

Exporting HTML table to PDF with many columns using jsPDF

I'd like to link this question that doesn't have any answers. Exporting HTML table to PDF with its format with jsPDF. I'm having the same problem with him and the tables looks exactly alike. I have a 20 column html table and I want them to be exported to pdf without any problem. I'm using jsPDF for exporting the table. I have tried the html <colgroup> tag for the column width of my table and it didn't work out. I have the first 8 columns showing and 12 columns hidden. I want all of them to be exported to pdf.
I'd like to try this code but I didn't know how I will execute it using my button in my html.
$(document).on("click", "#btnExportToPDF", function () {
var table1 =
tableToJson($('#table1').get(0)),
cellWidth = 35,
rowCount = 0,
cellContents,
leftMargin = 2,
topMargin = 12,
topMarginTable = 55,
headerRowHeight = 13,
rowHeight = 9,
l = {
orientation: 'l',
unit: 'mm',
format: 'a3',
compress: true,
fontSize: 8,
lineHeight: 1,
autoSize: false,
printHeaders: true
};
var doc = new jsPDF(l, '', '', '');
doc.setProperties({
title: 'Test PDF Document',
subject: 'This is the subject',
author: 'author',
keywords: 'generated, javascript, web 2.0, ajax',
creator: 'author'
});
doc.cellInitialize();
$.each(table1, function (i, row)
{
rowCount++;
$.each(row, function (j, cellContent) {
if (rowCount == 1) {
doc.margins = 1;
doc.setFont("helvetica");
doc.setFontType("bold");
doc.setFontSize(9);
doc.cell(leftMargin, topMargin, cellWidth, headerRowHeight, cellContent, i)
}
else if (rowCount == 2) {
doc.margins = 1;
doc.setFont("times ");
doc.setFontType("italic"); // or for normal font type use ------ doc.setFontType("normal");
doc.setFontSize(8);
doc.cell(leftMargin, topMargin, cellWidth, rowHeight, cellContent, i);
}
else {
doc.margins = 1;
doc.setFont("courier ");
doc.setFontType("bolditalic ");
doc.setFontSize(6.5);
doc.cell(leftMargin, topMargin, cellWidth, rowHeight, cellContent, i); // 1st=left margin 2nd parameter=top margin, 3rd=row cell width 4th=Row height
}
})
})
doc.save('sample Report.pdf'); })
function tableToJson(table) {
var data = [];
// first row needs to be headers
var headers = [];
for (var i=0; i<table.rows[0].cells.length; i++) {
headers[i] = table.rows[0].cells[i].innerHTML.toLowerCase().replace(/ /gi,'');
}
// go through cells
for (var i=1; i<table.rows.length; i++) {
var tableRow = table.rows[i];
var rowData = {};
for (var j=0; j<tableRow.cells.length; j++) {
rowData[ headers[j] ] = tableRow.cells[j].innerHTML;
}
data.push(rowData);
}
return data; }
This is my code btw,
function demoFromHTML() {
$(document).find('tfoot').remove();
$('#table td:nth-child(8)').remove();
var pdf = new jsPDF('p', 'pt', 'letter', true);
// source can be HTML-formatted string, or a reference
// to an actual DOM element from which the text will be scraped.
source = $('#table')[0];
// we support special element handlers. Register them with jQuery-style
// ID selector for either ID or node name. ("#iAmID", "div", "span" etc.)
// There is no support for any other type of selectors
// (class, of compound) at this time.
specialElementHandlers = {
// element with id of "bypass" - jQuery style selector
'#bypassme': function (element, renderer) {
// true = "handled elsewhere, bypass text extraction"
return true
}
};
margins = {
top: 80,
bottom: 60,
left: 55,
width: 522
};
// all coords and widths are in jsPDF instance's declared units
// 'inches' in this case
pdf.fromHTML(
source, // HTML string or DOM elem ref.
margins.left, // x coord
margins.top, { // y coord
'width': margins.width, // max width of content on PDF
'elementHandlers': specialElementHandlers
},
function (dispose) {
// dispose: object with X, Y of the last line add to the PDF
// this allow the insertion of new lines after html
var name = document.getElementById("name").innerHTML;
pdf.save(name);
}, margins);
setTimeout("window.location.reload()",0.0000001);
}
With this code btw, $(document).find('tfoot').remove(); $('#table td:nth-child(8)').remove(); I remove my footer and the 8th column of my table.

markerclusterer: anchor offset for cluster icons

I'm trying to slightly offset cluster icons created by the Google Maps Markerclusterer (V3). Short of modifying the existing code, I can't find a way to do this. Does anybody have an idea?
The Styles object in which you can provide a custom image URL accepts an anchor property, but this is to offset the generated marker item count.
Thanks!
The proper way to do it is to adjust the anchorIcon property like this:
var clusterStyles = [
{
height: 64,
width: 53,
anchorIcon: [20, 140]
},
{
height: 64,
width: 53,
anchorIcon: [20, 140]
},
{
height: 64,
width: 53,
anchorIcon: [20, 140]
}
];
var mcOptions = {
styles: clusterStyles
};
var markerCluster = new MarkerClusterer(map, markers, mcOptions);
The accepted answer does not work well enough for me - adding transparent space to the icon image can change the way click and hover events behave due to the increased size of the object.
I would use the anchorIcon property except it's only available in Marker Clusterer Plus, not the other Marker Clusterer plugin (which I'm using).
For those that specifically want to use Marker Clusterer - you can override ClusterIcon.prototype.getPosFromLatLng_. The ClusterIcon object is global, so we can modify it at the top-level of any script file without messing with the plugin's source code.
This will anchor the marker to the bottom of the icon:
ClusterIcon.prototype.getPosFromLatLng_ = function (latlng) {
var pos = this.getProjection().fromLatLngToDivPixel(latlng);
pos.x -= parseInt(this.width_ / 2);
pos.y -= parseInt(this.height_);
return pos;
};
I changed the code of marcerclusterer.js to support anchorText parameter by modifying following two functions:
/**
* Sets the icon to the the styles.
*/
ClusterIcon.prototype.useStyle = function() {
var index = Math.max(0, this.sums_.index - 1);
index = Math.min(this.styles_.length - 1, index);
var style = this.styles_[index];
this.url_ = style['url'];
this.height_ = style['height'];
this.width_ = style['width'];
this.textColor_ = style['textColor'];
this.anchor_ = style['anchor'];
this.anchorText_ = style['anchorText']; //added to support anchorText parameter by Frane Poljak, Locastic
this.textSize_ = style['textSize'];
this.backgroundPosition_ = style['backgroundPosition'];
};
/**
* Adding the cluster icon to the dom.
* #ignore
*/
ClusterIcon.prototype.onAdd = function() {
this.div_ = document.createElement('DIV');
if (this.visible_) {
var pos = this.getPosFromLatLng_(this.center_);
this.div_.style.cssText = this.createCss(pos);
////added to support anchorText parameter by Frane Poljak, Locastic
if (typeof this.anchorText_ === 'object' && typeof this.anchorText_[0] === 'number' && typeof this.anchorText_[1] === 'number') {
this.div_.innerHTML = '<span style="position:relative;top:' + String(this.anchorText_[0]) + 'px;left:' + String(this.anchorText_[1]) + 'px;">' + this.sums_.text + '</span>'
} else this.div_.innerHTML = this.sums_.text;
}
var panes = this.getPanes();
panes.overlayMouseTarget.appendChild(this.div_);
var that = this;
google.maps.event.addDomListener(this.div_, 'click', function() {
that.triggerClusterClick();
});
};
You could add some transparent space to one side of your cluster icon's PNG, so that the part of the icon which you'd like to be centred is actually also centred in your PNG. This should not increase the weight of your image by more than a few bytes.
anchor / anchorIcon/ anchorText properties didn't work for me...so I made kind of workaround:
I use setCalculator() function to set the cluster text:
https://google-maps-utility-library-v3.googlecode.com/svn/trunk/markerclusterer/docs/reference.html
when I am setting the cluster text property I am wrapping the value with <span>,
something like this:
markerCluster.setCalculator(function (markers) {
return {
text: '<span class="myClass">' + value+ '</span>',
index: index
};
});
now you can control the cluster label position with ".myClass":
span.myClass{
position: relative;
top: -15px;
.....
}