I have a react webapp that renders 2 text areas which are resize-vertical (using CSS). My requirement is that when one text area is resized by the user, the code captures it and apply the height to the other text area.
Based on my research, currently, there isn't a convenient method like textarea.onResize(). So, I am using MutationObserver. The API is very elegant and exactly what I need. I was able to configure it properly, capture the change in style attribute and apply it to other text area. This works fine in Google Chrome (Version 76.0.3809.132 (Official Build) (64-bit)) and Mozilla FireFox (Version 68.0.1esr (64-bit)). All this testing was done on Mac OS High Sierra (Version 10.13.6).
The problem
Although, when it comes to Safari browser (Version 12.1.2 (13607.3.10)), it doesn't seem to work as expected.
Upon debugging, I found out that when user resize a given text area, style attribute gets appended to it, with some assigned height and for each change, MutationObserver observes the change produces a MutationRecord, which I capture in my callback to take appropriate actions (in this case, apply height to other text area). Although, Safari browser does not produce MutationRecord for change in style attribute when text area is resized by the user.
Two things:
If I inspect the text area in developer mode of the browser and make a change to the style attribute of the element, MutationRecord is produced correctly and my callback does indeed execute, which is very weird.
I created an example in JS Fiddle, where, in Safari if you resize the text area, no MutationRecord is produced and you won't see any alerts but if you inspect and make a change manually, you will see the alert (appropriate MutationRecords are produced). In Chrome or Firefox, resizing the text area will produce the window alert without needing to make changes by inspecting the element.
Code
CSS
.textField textarea {
resize: vertical;
}
componentDidMount()
componentDidMount() {
if ('MutationObserver' in window) {
const config = {
attributes: true,
subtree: true,
attributeFilter: ['style'],
attributeOldValue: true
};
const sourceTextArea = window.getElementById('sourceTextArea');
this.sourceObserver = new MutationObserver(this.handleManualSourceTextAreaResize);
this.sourceObserver.observe(sourceTextArea, config);
const targetTextArea = window.getElementById('targetTextArea');
this.targetObserver = new MutationObserver(this.handleManualTargetTextAreaResize);
this.targetObserver.observe(targetTextArea, config);
}
}
Callback for source text area
handleManualSourceTextAreaResize = (mutationsList, observer) => {
const sourceTextArea = getElement('sourceTextArea');
const targetTextArea = getElement('targetTextArea').children[0];
if (!this.isStopPropagation) {
targetTextArea.setAttribute('style', 'height: ' + sourceTextArea.offsetHeight + 'px');
this.isStopPropagation = true;
} else {
this.isStopPropagation = false;
}
}
Callback for target text area
handleManualTargetTextAreaResize = (mutationsList, observer) => {
const sourceTextArea = getElement('sourceTextArea').children[0];
const targetTextArea = getElement('targetTextArea');
if (!this.isStopPropagation) {
sourceTextArea.setAttribute('style', 'height: ' + targetTextArea.offsetHeight + 'px'');
this.isStopPropagation = true;
} else {
this.isStopPropagation = false;
}
}
Related
I'm enhancing a React front end with Tampermonkey , by adding highlights to show cursor location in a grid, and allowing users to directly enter data , rather than then enter data.
After 2 or 3 cursor moves or data entry the grid refreshes or updates - no page change - and looses the highlighting I set up.
I'd like to catch the refresh/update and reset the highlighting.
I'm a noob..
The network tab shows post events so I tried https://jsbin.com/dixelocazo/edit?js,console
var open = window.XMLHttpRequest.prototype.open,
send = window.XMLHttpRequest.prototype.send;
to try and use POST events to detect the refresh. No joy !
I also looked at ajax events.
No luck :(
Can someone point me in the right direction here ?
Once I catch the event, I can then reset the highlighting to fix the problem
Since normally the userscripts run in a sandbox, JavaScript functions or objects cannot be used directly by default, here's what you can do:
Disable the sandbox:
// #grant none
You won't be able to use any GM functions, though.
Run in the page context via unsafeWindow:
const __send = unsafeWindow.XMLHttpRequest.prototype.send;
unsafeWindow.XMLHttpRequest.prototype.send = function () {
this.addEventListener('loadend', e => {
console.log('intercepted', e);
}, {once: true});
__send.apply(this, arguments);
};
Use MutationObserver to detect changes in page DOM:
const observer = new MutationObserver(mutations => {
const matched = [];
for (const {addedNodes} of mutations) {
for (const n of addedNodes) {
if (!n.tagName)
continue;
if (n.matches('.prey:not(.my-highlight)')) {
matched.push(n);
} else if (n.firstElementChild) {
matched.push(...n.querySelectorAll('.prey:not(.my-highlight)'));
}
}
}
// process the matched elements
for (const el of matched) {
el.classList.add('my-highlight');
}
});
observer.observe(document.querySelector('.surviving-ancestor') || document.body, {
subtree: true,
childList: true,
});
.surviving-ancestor means the element that isn't replaced/recreated by the page script. In devtools element inspector it's the one that isn't highlighted temporarily during DOM updates.
See also Performance of MutationObserver.
I have this code
var input = element.find('input').eq(0);
scope.focus = function() {
if (!input.length) return;
input[0].focus();
input[0].select();
var divWrapper = input.parent('.tags')[0];
divWrapper.scrollLeft = divWrapper.scrollWidth;
console.log(divWrapper.scrollWidth);
console.log(divWrapper.scrollLeft);
};
This is inside a directive fnLink and focus is called when ui is changed this because use overflow: hidden and input disapear when many items on left... so I need to scroll to far right and keep input visible.
The
divWrapper.scrollLeft = divWrapper.scrollWidth;
works fine on chrome dev tools but when focus is called don't update the scrollLeft attribute.
I am trying to integrate Sencha 4.1 (ExtJS) with the Leaflet mapping library while using Sencha Architect.
When the page loads, the tiles are mixed up and appear offset. I need to drag the page up to be able to see the tiles.
The full project is available here: https://github.com/breizo/SenchaLeaflet.
Here is an excerpt of the custom component created (see full code here: https://github.com/breizo/SenchaLeaflet/blob/master/ux/LeafletMap.js).
constructor: function () {
this.callParent(arguments);
this.on({
resize: 'doResize',
scope: this
});
var ll = window.L;
if (!ll) {
this.setHtml('Leaflet library is required');
}
}
onRender: function() {
this.callParent(arguments);
var renderTo = arguments[0].dom.id;
debugger;
var me = this,
ll = window.L,
element = me.mapContainer,
mapOptions = me.getMapOptions(),
map,
tileLayer;
if (ll) {
// if no center property is given -> use default position
if (!mapOptions.hasOwnProperty('center') || !(mapOptions.center instanceof ll.LatLng)) {
mapOptions.center = new ll.LatLng(47.36865, 8.539183); // default: Zuerich
}
me.setTileLayer(new ll.TileLayer(me.getTileLayerUrl(), me.getTileLayerOptions()));
tileLayer = me.getTileLayer();
mapOptions.layers = [tileLayer];
me.setMap(new ll.Map(renderTo, mapOptions));
map = me.getMap();
// track map events
map.on('zoomend', me.onZoomEnd, me);
map.on('movestart', me.onMoveStart, me);
map.on('moveend', me.onMoveEnd, me);
me.fireEvent('maprender', me, map, tileLayer);
}
},
When debugging it appears that when onRender is called, the parent container of the map is not properly sized yet, in particular its height is only enough to contain the attrib text, about 16 pix. WHen the doResize is called, the container is properly sized, but it doesn't change the end result: the tiles are mixed up and offset.
I tried various changes to the container, but nothing worked...
1) Problem with mixed layers is caused by CSS. Your leaflet.css has wrong path in html, so it's not attached in the document. To fix mixing issue set correct path to css file, or attach it from CDN:
<link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.4/leaflet.css" />
2) Wrong map offset is caused by extjs generated div:
<div class="x-llmap x-fit-item x-llmap-default" ...></div>
It pushes map container to the bottom and wrong offset calculations are made. You can also fix this using inline style or CSS:
.leaflet-map-pane {
top: 0;
}
In my mobile safari project, i need to create a message posting feature. it is requires scrolling inside a textarea when lines of texts exceed the max rows of the text area. i couldn't find 'scrollable' property in Ext.field.textarea, any idea how?
Cheers!
There is a bug in touch 2.0.x such that the framework explicitly prevents the scroll action. Supposedly a fix will be in 2.1, though I didn't see that officially, just from a guy on a forum.
Until then, there is kind of a solution for touch1 here http://www.sencha.com/forum/showthread.php?180207-TextArea-scroll-on-iOS-not-working that you can port to V2. It basically involves adding an eventlistener to the actual textarea field (not the sencha object) and then calling preventdefault if it's a valid scrollevent.
The full code is at that link, but the salient bits are here.
Grab the <textarea> field (not the Sencha Touch object) directly and use addListener to apply
'handleTouch' on touchstart and 'handleMove' on touchmove
handleTouch: function(e) {
this.lastY = e.pageY;
},
handleMove: function(e) {
var textArea = e.target;
var top = textArea.scrollTop <= 0;
var bottom = textArea.scrollTop + textArea.clientHeight >= textArea.scrollHeight;
var up = e.pageY > this.lastY;
var down = e.pageY < this.lastY;
this.lastY = e.pageY;
// default (mobile safari) action when dragging past the top or bottom of a scrollable
// textarea is to scroll the containing div, so prevent that.
if((top && up) || (bottom && down)) {
e.preventDefault();
e.stopPropagation(); // this tops scroll going to parent
}
// Sencha disables textarea scrolling on iOS by default,
// so stop propagating the event to delegate to iOS.
if(!(top && bottom)) {
e.stopPropagation(); // this tops scroll going to parent
}
}
Ext.define('Aspen.util.TextArea', {
override: 'Ext.form.TextArea',
adjustHeight: Ext.Function.createBuffered(function (textarea) {
var textAreaEl = textarea.getComponent().input;
if (textAreaEl) {
textAreaEl.dom.style.height = 'auto';
textAreaEl.dom.style.height = textAreaEl.dom.scrollHeight + "px";
}
}, 200, this),
constructor: function () {
this.callParent(arguments);
this.on({
scope: this,
keyup: function (textarea) {
textarea.adjustHeight(textarea);
},
change: function (textarea, newValue) {
textarea.adjustHeight(textarea);
}
});
}
});
I've created a new plug in as I could not find one that actually "works", hoping that if I do it from scratch it might fire.
The plug-in simply wraps selected text with a mailto: link.
I've added the plug-in to the includes file, as per the following response on a previous question: http://bit.ly/vGyQlE however, it's not working.
I've gone into the localization directory, identified the Composite.Web.VisualEditor.en-us.xml as the file that handles the localization, added my entry under :
<string key="ToolBar.ToolTipMailTo" value="Mail To" />
But when I hover of the "blank" block where the menu item should appear, it returns (?). This is the first part where I picked up on something wierd. When you actually click on where the item should appear, nothing happens. So, I can't assume that the click event has got to do with an image, I re-wrote the command to return an alert, when clicked:
tinymce.create('tinymce.plugins.MailTo', {
init : function(ed, url) {
ed.addButton('mailto', {
title : 'mailto.mailto_desc',
cmd : 'mceMailTo',
image : url + '/images/mailto.gif'
});
ed.addCommand('mceMailTo', function() {
var selectedText = ed.selection.getContent({format : 'text'});
var MailToLink = "alert(" + selectedText + ");";
ed.execCommand('mceInsertContent', false, MailToLink);
});
I've added the "mailTo" element to visualeditor.js:
plugins : "...,paste,lists,mailto",
And ensured that the "mailto" plug-in is situated under the plug-ins directory for tiny_mce. I've gone as far as to clear my cache several times, but nothing? Can it be this difficult to add new plug-ins to tiny-mce in Composite?
1) Composite C1 does not support internal tiny_mce buttons
Do you add button to editor?
In file Composite\content\misc\editors\visualeditor\includes\toolbarsimple.inc add
<ui:toolbargroup>
<ui:toolbarbutton cmd="mceMailTo" tooltip="Mail to" image="${icon:paste}" isdisabled="false" />
</ui:toolbargroup>
2) Do you write valid plugin code?
(function () {
tinymce.create('tinymce.plugins.MailTo', {
init: function (ed, url) {
ed.addCommand('mceMailTo', function () {
var selectedText = ed.selection.getContent({ format: 'text' });
var MailToLink = "alert(" + selectedText + ");";
ed.execCommand('mceInsertContent', false, MailToLink);
});
}
});
tinymce.PluginManager.add('mailto', tinymce.plugins.MailTo); })();