How to update the x/y position of a popper.js element? - javascript

How can I move the popper.js element to follow specific coords?
I was able to get (I think) the caret position in a textarea but now I need to make Popper.js follow it.
I tried, update and onUpdate on the root and in the modifiers. I do not understand at all the documentation.
I created a codepen to show what I was able to achieve so far:
https://codepen.io/anon/pen/gzGvvG
const refEl = document.getElementById('ref');
const popEl = document.getElementById('pop');
new Popper(refEl, popEl, {
placement: 'auto',
modifiers: {
offset: {
enabled: true,
offset: '0,10'
},
flip: {
behavior: ['left', 'bottom', 'top']
},
preventOverflow: {
enabled: true,
padding: 10,
escapeWithReference: false,
}
},
});
document.getElementById("ref").onkeyup = function() {
var xy = getCursorXY(refEl, refEl.selectionEnd)
document.getElementById("log").innerText = `X: ${xy.x}, Y: ${xy.y}`;
}
The getcursorXY function I got from Medium: https://medium.com/#jh3y/how-to-where-s-the-caret-getting-the-xy-position-of-the-caret-a24ba372990a

It's not a complete answer to your question ... as it needs a few tweaks to get it to work like how I think you are trying to get it to work. But, here's javascript that i modified from your CodePen which is believe is a step in the right direction for what you are looking to achieve.
const refEl = document.getElementById('ref');
const popEl = document.getElementById('pop');
function update_popper(x, y) {
new Popper(refEl, popEl, {
placement: 'auto',
modifiers: {
offset: {
enabled: true,
offset: (x - 150) + ',' + (-1 * (y - 140))
},
flip: {
behavior: ['left', 'bottom', 'top']
},
preventOverflow: {
enabled: true,
padding: 10,
escapeWithReference: false,
}
},
});
}
document.getElementById("ref").onkeyup = function () {
var xy = getCursorXY(refEl, refEl.selectionEnd)
document.getElementById("log").innerText = `X: ${xy.x}, Y: ${xy.y}`;
update_popper(xy.x, xy.y);
}
const getCursorXY = (input, selectionPoint) => {
const {
offsetLeft: inputX,
offsetTop: inputY,
} = input
// create a dummy element that will be a clone of our input
const div = document.createElement('div')
// get the computed style of the input and clone it onto the dummy element
const copyStyle = getComputedStyle(input)
for (const prop of copyStyle) {
div.style[prop] = copyStyle[prop]
}
// we need a character that will replace whitespace when filling our dummy element if it's a single line <input/>
const swap = '.'
const inputValue = input.tagName === 'INPUT' ? input.value.replace(/ /g, swap) : input.value
// set the div content to that of the textarea up until selection
const textContent = inputValue.substr(0, selectionPoint)
// set the text content of the dummy element div
div.textContent = textContent
if (input.tagName === 'TEXTAREA') div.style.height = 'auto'
// if a single line input then the div needs to be single line and not break out like a text area
if (input.tagName === 'INPUT') div.style.width = 'auto'
// create a marker element to obtain caret position
const span = document.createElement('span')
// give the span the textContent of remaining content so that the recreated dummy element is as close as possible
span.textContent = inputValue.substr(selectionPoint) || '.'
// append the span marker to the div
div.appendChild(span)
// append the dummy element to the body
document.body.appendChild(div)
// get the marker position, this is the caret position top and left relative to the input
const { offsetLeft: spanX, offsetTop: spanY } = span
// lastly, remove that dummy element
// NOTE:: can comment this out for debugging purposes if you want to see where that span is rendered
document.body.removeChild(div)
// return an object with the x and y of the caret. account for input positioning so that you don't need to wrap the input
return {
x: inputX + spanX,
y: inputY + spanY,
}
}
what I have done here is to move your Popper() into a function called update popper, which will rebuild the popper new each time with a new x,y offset. You don't have to worry much about creating the new Popper() over and over, as it is mostly all just math, with very little weight.
Anyways, i think that should help get you closer to your goal .. good luck!

Related

Appending content of a variable to a div with appendTo()

Hi I'm working on a project where I have a multi-image slider.
Each image needs to have a legend.
I'm getting the alt tag from the image and storing it in a p tag with a legend of legend--text.
I've then created a function that plays when I change slides. This is where I'm having a problem.
I'm setting a variable that gets the active slide, and another one that gets the legend from the active slide - this works fine.
I then tried to use the appendTo() function to copy the content from the legend variable to a div tag with a class of .project__information.
I understand that this is not working because it's not a DOM element, but I've looked for solutions and can't really find any.
Here's my code:
// For each active slide get the alt attribute and store it in <p>
$('.is--legend').each(function() {
var $this = $(this);
$this.append('<p class="legend--text">' + $this.parent().find('img').attr('alt') + '</p>');
});
// Set splide slider
var elms = document.getElementsByClassName('splide');
for (var i = 0, len = elms.length; i < len; i++) {
var splide = new Splide(elms[i], {
type: 'loop',
drag: 'free',
focus: 'center',
perPage: 3,
autoWidth: 'true',
breakpoints: {
767: {
perPage: 1,
}
}
}).mount();
}
function slideChange() {
// Gets active slide
var activeSlide = $(".splide__slide.is-active:not(.splide__slide--clone)");
console.log('Passing through the', activeSlide);
// Gets the legend
var myLegend = activeSlide.find('.legend--text').text();
console.log('Getting the new', myLegend);
myLegend.appendTo('.project__information');
}
splide.on("move", function() {
slideChange();
});
slideChange();
If anyone has an idea as to how I could achieve this, some related documentation about how to proceed, please let me know

How do I get a DIV to follow a transform

Long story short, I'm trying to put text inside a box that is animated with javascript.
var two = new Two({
fullscreen: true,
autostart: true
}).appendTo(document.body);
const card = two.makeRoundedRectangle(230/2, two.height/2, 230, 130, 10);
const div = document.createElement("div"); // THIS IS THE IMPORTANT STUFF
const text = document.createTextNode("This is text."); //
div.appendChild(text); // THIS IS THE IMPORTANT STUFF
two.update(); // this needs to be here to add to the dom
card._renderer.elem.style.cssText = 'position:relative;width:230px;height:130px;'; // THIS IS THE IMPORTANT STUFF
div.style.cssText = 'position:absolute;width:200px;height:200px;'; //
card._renderer.elem.appendChild(div); // THIS IS THE IMPORTANT STUFF
two.bind('update', function (framNum)
{
card.translation.x += 1
if (card.translation.x > two.width - 230/2)
{
card.translation.x = 230/2
}
}).play()
<script src="https://two.js.org/third-party/two.js"></script>
here's what I've tried. I'm trying to put document.createTextNode("This is text."); inside a parent that is moving
I was expecting the to see the text, or even a div with the inspector, but its not highlighting.
Any help would be greatly appreciated
You need to use two's text functionality instead of using DOM related functions. It appears that in this context, two renders to an SVG format, thus adding HTML tags won't work.
var two = new Two({fullscreen: true, autostart: true}).appendTo(document.body);
const card = two.makeRoundedRectangle(230/2, two.height/2, 230, 130, 10);
var styles = { size: 24, family: 'Calibri' };
var text = new Two.Text("This is text.", 120, 100, styles);
var group = two.makeGroup(card, text);
two.update(); // this needs to be here to add to the dom
card._renderer.elem.style.cssText = 'position:relative;width:230px;height:130px;';
two.bind('update', function (framNum)
{
group.translation.x += 1
if (group.translation.x > two.width - 230/2)
{
group.translation.x = 230/2
}
}).play()
<script src="https://two.js.org/third-party/two.js"></script>

Set width of ace editor instance according to the length of characters in it

I am working on the project where I have created a custom Rich Text Editor using contenteditable attribute. In this rich text editor I want insert single line ace editor instance of which width will be set according to the number of characters in it.
For restricting the ace editor instance to single line I have handled the "Enter" key event which does not let the ace instance to insert new line.
var editor = ace.edit(script_editor);
editor.commands.on("exec", function (e) {
editor.container.querySelector(".ace_content").style.transform = "none";
if (e.args && e.args.charCodeAt(0) == 10) {
e.preventDefault();
e.stopPropagation();
console.log("vdgscript-mode.js")
}
});
Now, the problem I am facing is that I want the ace instance width to adjust according to the number of character in it instead to have full width.
For that I am taking a canvas object and calculating the width of the text. But the problem with this code is, it is giving me the expected width on every key press but the css left property of the ace editor does not stay '0px' which makes the text in the ace editor instance to hide at the left side.
Code for setting the width is as follows:
var canvas = document.createElement('canvas');
var ctx = canvas.getContext("2d");
ctx.font = "15px sans-serif";
var width = ctx.measureText(code).width;
editor.container.style.width = (width + 3) + "px";
Actual Result: .
Expected Result: .
The black in the below image the ace instance in which I have entered an array.
you can use a method similar to the one used by the tree rename editor in cloud9 https://github.com/c9/core/blob/master/plugins/node_modules/ace_tree/lib/ace_tree/edit.js
<style>
#inlineEditor {
display: inline-block;
vertical-align: middle;
}
</style>
<div>inline editor <span id=inlineEditor>txt</span></div>
<script src=https://ajaxorg.github.io/ace-builds/src-noconflict/ace.js>
</script>
<script>
var inlineEditor = ace.edit("inlineEditor", {
maxLines: 1,
showGutter: false,
showPrintMargin: false,
theme: "ace/theme/solarized_light",
})
// make cursor movement nicer for
inlineEditor.renderer.screenToTextCoordinates = function(x, y) {
var pos = this.pixelToScreenCoordinates(x, y);
return this.session.screenToDocumentPosition(
Math.min(this.session.getScreenLength() - 1, Math.max(pos.row, 0)),
Math.max(pos.column, 0)
);
};
inlineEditor.renderer.on("beforeRender", updateSize)
function updateSize(e, renderer) {
var text = renderer.session.getLine(0);
var chars = renderer.session.$getStringScreenWidth(text)[0];
var width = Math.max(chars, 2) * renderer.characterWidth // text size
+ 2 * renderer.$padding // padding
+ 2 // little extra for the cursor
+ 0 // add border width if needed
// update container size
renderer.container.style.width = width + "px";
// update computed size stored by the editor
renderer.onResize(false, 0, width, renderer.$size.height);
}
updateSize(null, inlineEditor.renderer)
</script>

Word wrapping in JointJS

I am working on JointJS. I have various elements with text in it. However the element's width increases with increase in text. I want to dynamically set the size of element such that there is a maximum height and width that the box can attain and expands accordingly by text wrapping. If the text os unable to fit in the maximum height and width element, then the fontsize may be reduced dynamically.
I hav tried using style="word-wrap: break-word;" in my div id. However there is no effect.
<div id="myholder" style="word-wrap: break-word;"> </div>
My holder is defined in the JS file as follows:
var paper = new joint.dia.Paper({
el: $('#myholder'),
width: 1200,
height: 700,
model: graph
});
What strategy may I follow?
It is also possible (if you don't want to bother with extra shapes) to use the
joint.util.breakText()
utility. It works like this:
var wraptext = joint.util.breakText('My text here', {
width: holderElement.size.width,
height: optionalHeight
});
After that you can add wraptext to your holderElement as into attrs when creating it. Like this:
var holder = joint.shapes.basic.Rect({
//position, size blablabla
attrs: {
text: {
text: wraptext,
//text styling
}
}
});
I have to say it's a bit strange that your holder is an entire paper, but you can probably use it the same way, just put the attrs when you define it.
To get word wrap working you can use joint.shapes.basic.TextBlock.
Now, to work with TextBlock you are going to set a top level map entry for "content" (instead of including "text" inside of "attrs" => "text" map entry)
graph.addCell (
new joint.shapes.basic.TextBlock({
position: { x:100, y:100 },
size: { width: 100, height: 100 },
attrs: { rect: { fill: 'green' }},
content: "<p style='color:white;'>asdf asdf asdf asdf this needs to word wrap</p>"
})
);
As you can see, the "content" entry can be raw html and will be rendered as such.
For this to work your browser needs to have SVG ForeignObject support, which most browsers these days have. To first check that this is the case you can run this in your console:
document.implementation.hasFeature("w3.org/TR/SVG11/feature#Extensibility","1.1")
I made a javascript function to wrap words based on the Line size and Max size of the shape you want the sentence wrap in.
If the sentence is very long then the function trim it and put 3 duts instead of the rest of the sentence.
Every Line size of the sentence the function put a '\n' (newline ASCII).
var sentenceWrapped = function (sentence, lineSize, maxSize) {
var descriptionTrim = "";
if (sentence.length + 3 > maxSize) {
descriptionTrim = sentence.substring(0, maxSize - 3);
descriptionTrim = descriptionTrim + '...';
}
else {
descriptionTrim = sentence
}
var splitSentence = descriptionTrim.match(new RegExp('.{1,' + lineSize + '}', 'g'));
var sentenceWrapped = "";
for (i = 0; i < splitSentence.length; i++)
{
sentenceWrapped = sentenceWrapped + splitSentence[i] + '\n';
}
return sentenceWrapped;
}
LineSize = the max size of characters for every line you want inside
your shape
MaxSize = the max size of characters you want inside your
shape
sentence = description you want to put in your shape
If you are interested in creating custom element you can create like this
joint.shapes.devs.Model = joint.shapes.basic.TextBlock.extend( {
markup: ['>',
joint.env.test('svgforeignobject') ? '' : '',
''].join(''),
defaults: joint.util.deepSupplement({
content: 'A content of the Activity'
}});

Removing an image from html5 canvas when it's dragged to a certain location

I have an HTML5 canvas that is displaying a number of images and four description boxes. It is currently possible to drag and drop the images around the canvas, but I want to add functionality to remove an image when it is dragged to its correct description box.
I've tried writing the following function, but it does not currently seem to be doing anything... i.e. if I drag an image to its description box and drop it, it still remains on the canvas:
function initStage(images){
var stage = new Kinetic.Stage({
container: "container",
width: 1000,
height: 500
});
var descriptionLayer = new Kinetic.Layer();
//var imagesLayer = new Kinetic.Layer();
var allImages = [];
var currentScore = 0;
var descriptionBoxes = {
assetsDescriptionBox: {
x: 70,
y: 400
},
liabilitiesDescriptionBox: {
x: 300,
y: 400
},
incomeDescriptionBox: {
x: 530,
y: 400
},
expenditureDescriptionBox: {
x: 760,
y: 400
},
};
/*Code to detect whether image has been dragged to correct description box */
for (var key in sources){
/*Anonymous function to induce scope */
(function(){
var privateKey = key;
var imageSource = sources[key];
/*Check if image has been dragged to the correct box, and add it to that box's
array and remove from canvas if it has */
canvasImage.on("dragend", function(){
var descriptionBox = descriptionBoxes[privateKey];
if(!canvasImage.inRightPlace && isNearDescriptionBox(itemImage, descriptionBox)){
context.remove(canvasImage);
/*Will need to add a line in here to add the image to the box's array */
}
})
})();
}
}
The code I've written is based on the tutorial that I found at: http://www.html5canvastutorials.com/labs/html5-canvas-animals-on-the-beach-game-with-kineticjs/
Can anyone spot what I'm doing wrong, and how I can ensure that the image is removed from the canvas when it's dragged to its corresponding description box?
That example bugged me because it seemed old, so I edited it a little...
http://jsfiddle.net/LTq9C/1/
...keep in mind that I cant be positive that all my edits are the best way to do things, Im new and all ;)
And here I've edited it again for you to show the image being removed...
animal.on("dragend", function() {
var outline = outlines[privKey + "_black"];
if (!animal.inRightPlace && isNearOutline(animal, outline)) {
animal.remove();
animalLayer.draw();
}
});
http://jsfiddle.net/8S3Qq/1/

Categories

Resources