Custom TinyMCE 4.x button to increase letter spacing not working - tinymce-4

I implemented a TinyMCE button to increase the letter spacing at http://fiddle.tinymce.com/Z9eaab/31. If you enter the words "some text" in the text area at the bottom and then select "some" and hit the "Increase letter spacing" button multiple times, the letter spacing only increases the first time. If you select the second word, "text," the spacing increases each time you hit "Increase letter spacing." as it should.
I can see from the console.log on line 9 that when it doesn't work it's because the current spacing read doesn't reflect the last increase, so it just keeps redoing the first one.
<script type="text/javascript">
tinymce.PluginManager.add('example', function(editor, url) {
// Add a button that opens a window
editor.addButton('example1', {
text: 'Increase letter spacing',
icon: false,
onclick: function() {
var currentSpacing = new Number($(tinyMCE.activeEditor.selection.getNode()).css('letter-spacing').replace('px', ''));
console.log("spacing read is" + currentSpacing);
currentSpacing = currentSpacing + 1;
tinymce.activeEditor.formatter.register('increaseSpacing', {
inline: 'span',
styles: {
'letter-spacing': currentSpacing + 'px'
}
});
tinymce.activeEditor.formatter.apply('increaseSpacing');
}
});
editor.addButton('example2', {
text: 'Decrease letter spacing',
icon: false,
onclick: function() {
var currentSpacing = new Number($(tinyMCE.activeEditor.selection.getNode()).css('letter-spacing').replace('px', ''));
currentSpacing = currentSpacing - 1;
tinymce.activeEditor.formatter.register('decreaseSpacing', {
inline: 'span',
styles: {
'letter-spacing': currentSpacing + 'px'
}
});
tinymce.activeEditor.formatter.apply('decreaseSpacing');
}
});
// Adds a menu item to the tools menu
editor.addMenuItem('example', {
text: 'Example plugin',
context: 'tools',
onclick: function() {
// Open window with a specific url
editor.windowManager.open({
title: 'TinyMCE site',
url: 'http://www.tinymce.com',
width: 400,
height: 300,
buttons: [{
text: 'Close',
onclick: 'close'
}]
});
}
});
});
tinymce.init({
selector: "textarea",
plugins: "example",
toolbar: "example1 example2 undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image"
});
</script>
<form method="post" action="dump.php">
<textarea name="content"></textarea>
</form>
Does anyone know what's going on?

You're using selection.getNode() which finds the common parent node of the start and end points of the selection. This is not the node that is in the current selection.
In your case you want the <span> you've created, but what you've actually asked for is its enclosing <p> (subsequently you're checking its current letter-spacing CSS value, which it won't have).
To correct this, after applying the formatting, grab the span (either created previously, or newly added), and set the current selection to it. You can do this using selection.getStart():
var spanNode = tinyMCE.activeEditor.selection.getStart();
tinymce.activeEditor.selection.select(spanNode);
When used after the tinymce.activeEditor.formatter.apply(), it will be the correct span.
Here's the updated code (I've made a number of other formatting changes):
<script type="text/javascript">
tinymce.PluginManager.add('example', function(editor, url) {
// Add a button that opens a window
editor.addButton('example1', {
text: 'Increase letter spacing',
icon: false,
onclick: function() {
var currentSpacing = 0;
var $selectedContent = $(tinyMCE.activeEditor.selection.getContent({'format': 'html'}));
if ($selectedContent.is("span") && $selectedContent.css('letter-spacing')) {
currentSpacing = +($selectedContent.css('letter-spacing').replace('px', ''));
}
currentSpacing += 1;
tinymce.activeEditor.formatter.apply('letterSpacing', {
value: currentSpacing + 'px'
});
var spanNode = tinyMCE.activeEditor.selection.getStart();
tinymce.activeEditor.selection.select(spanNode);
}
});
editor.addButton('example2', {
text: 'Decrease letter spacing',
icon: false,
onclick: function() {
var currentSpacing = 0;
var $selectedContent = $(tinyMCE.activeEditor.selection.getContent({'format': 'html'}));
if ($selectedContent.is("span") && $selectedContent.css('letter-spacing')) {
currentSpacing = +($selectedContent.css('letter-spacing').replace('px', ''));
}
currentSpacing -= 1;
tinymce.activeEditor.formatter.apply('letterSpacing', {
value: currentSpacing + 'px'
});
var spanNode = tinyMCE.activeEditor.selection.getStart();
tinymce.activeEditor.selection.select(spanNode);
}
});
// Adds a menu item to the tools menu
editor.addMenuItem('example', {
text: 'Example plugin',
context: 'tools',
onclick: function() {
// Open window with a specific url
editor.windowManager.open({
title: 'TinyMCE site',
url: 'http://www.tinymce.com',
width: 400,
height: 300,
buttons: [{
text: 'Close',
onclick: 'close'
}]
});
}
});
});
tinymce.init({
selector: "textarea",
plugins: "example",
toolbar: "example1 example2 undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image",
formats: {
'letterSpacing': {
inline: 'span',
styles: {
'letter-spacing': '%value'
}
}
}
});
</script>
<form method="post" action="dump.php">
<textarea name="content"></textarea>
</form>
Demo: http://fiddle.tinymce.com/wYfaab/2

For me, made some changes on the code above and this works.
Change this:
tinymce.activeEditor.formatter.apply('letterSpacing', {
value: currentSpacing + 'px'
});
For this:
tinymce.activeEditor.formatter.register('mycustomformat', {
inline: 'span',
styles: {'letterSpacing': currentSpacing+'px'}
});
tinymce.activeEditor.formatter.apply('mycustomformat');
Complete script:
<script>
tinymce.PluginManager.add('example', function(editor, url) {
// Add a button that opens a window
editor.addButton('example1', {
text: 'Increase letter spacing',
icon: false,
onclick: function() {
var currentSpacing = 0;
var $selectedContent = $(tinyMCE.activeEditor.selection.getContent({'format': 'html'}));
if ($selectedContent.is("span") && $selectedContent.css('letter-spacing')) {
currentSpacing = +($selectedContent.css('letter-spacing').replace('px', ''));
}
currentSpacing += 1;
tinymce.activeEditor.formatter.register('mycustomformat', {
inline: 'span',
styles: {'letterSpacing': currentSpacing+'px'}
});
tinymce.activeEditor.formatter.apply('mycustomformat');
var spanNode = tinyMCE.activeEditor.selection.getStart();
tinymce.activeEditor.selection.select(spanNode);
}
});
editor.addButton('example2', {
text: 'Decrease letter spacing',
icon: false,
onclick: function() {
var currentSpacing = 0;
var $selectedContent = $(tinyMCE.activeEditor.selection.getContent({'format': 'html'}));
if ($selectedContent.is("span") && $selectedContent.css('letter-spacing')) {
currentSpacing = +($selectedContent.css('letter-spacing').replace('px', ''));
}
currentSpacing -= 1;
tinymce.activeEditor.formatter.register('mycustomformat2', {
inline: 'span',
styles: {'letterSpacing': currentSpacing+'px'}
});
tinymce.activeEditor.formatter.apply('mycustomformat2');
var spanNode = tinyMCE.activeEditor.selection.getStart();
tinymce.activeEditor.selection.select(spanNode);
}
});
// Adds a menu item to the tools menu
editor.addMenuItem('example', {
text: 'Example plugin',
context: 'tools',
onclick: function() {
// Open window with a specific url
editor.windowManager.open({
title: 'TinyMCE site',
url: 'http://www.tinymce.com',
width: 400,
height: 300,
buttons: [{
text: 'Close',
onclick: 'close'
}]
});
}
});
});
tinymce.init({
selector: "textarea",
plugins: "example",
toolbar: "example1 example2 undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image",
formats: {
'letterSpacing': {
inline: 'span',
styles: {
'letter-spacing': '%value'
}
}
}
});
</script>
<form method="post" action="dump.php">
<textarea name="content"></textarea>
</form>

Related

onDragStop tells me jsEvent is undefined

I'm trying to make it possible to drag events to an external div and back into the calendar again. However, whenever I drop an event I get TypeError: jsEvent is undefined.
I'm not entirely sure why it is doing this, this should be a valid parameter that is passed to the function of eventDragStop.
I'm using the latest FullCalendar 4.
Here is my code
// Load the CSS stuff
import {Tooltip} from "bootstrap";
require('#fortawesome/fontawesome-free/css/fontawesome.min.css');
require('#fortawesome/fontawesome-free/css/brands.min.css');
require('#fortawesome/fontawesome-free/css/solid.min.css');
require('../css/app.scss');
// Load the JS stuff
let $ = require('jquery');
require('bootstrap');
require('./libs/navbar.js');
require('jquery-ui/ui/widgets/draggable');
import apiclient from "./libs/apiclient";
import { Calendar } from '#fullcalendar/core';
import dayGridPlugin from '#fullcalendar/daygrid';
import timeGridPlugin from '#fullcalendar/timegrid';
import interactionPlugin from '#fullcalendar/interaction';
import bootstrapPlugin from '#fullcalendar/bootstrap';
// $(document).ready(function () {
//
// });
document.addEventListener('DOMContentLoaded', () => {
/* initialize the external events
-----------------------------------------------------------------*/
$('#external-events .fc-event').each(function() {
// store data so the calendar knows to render an event upon drop
$(this).data('event', {
title: $.trim($(this).text()), // use the element's text as the event title
stick: true // maintain when user navigates (see docs on the renderEvent method)
});
// make the event draggable using jQuery UI
$(this).draggable({
zIndex: 999,
revert: true, // will cause the event to go back to its
revertDuration: 0 // original position after the drag
});
});
let calendarEl = document.getElementById('calendar-holder');
let calendar = new Calendar(calendarEl, {
views: {
jira: {
type: 'dayGridWeek',
duration: {months: 3},
buttonText: 'Jira'
}
},
defaultView: 'jira',
themeSystem: 'bootstrap',
editable: true,
droppable: true,
firstDay: 1,
contentHeight: 'auto',
weekNumbersWithinDays: true,
weekNumbers: true,
eventSources: [
{
url: "/fc-load-events",
method: "POST",
extraParams: {
filters: JSON.stringify({})
},
failure: () => {
// alert("There was an error while fetching FullCalendar!");
},
},
],
header: {
left: 'prev,next today',
center: 'title',
right: 'jira,dayGridMonth,timeGridWeek',
},
plugins: [ bootstrapPlugin, interactionPlugin, dayGridPlugin, timeGridPlugin ], // https://fullcalendar.io/docs/plugin-index
timeZone: 'UTC',
eventRender: function(info) {
var tooltip = new Tooltip(info.el, {
title: info.event.title+'<br>'+info.event.extendedProps.assignee,
placement: 'top',
trigger: 'hover',
container: 'body',
html: true
});
},
dragRevertDuration: 0,
drop: function() {
// is the "remove after drop" checkbox checked?
if ($('#drop-remove').is(':checked')) {
// if so, remove the element from the "Draggable Events" list
$(this).remove();
}
},
eventDragStop: function( event, jsEvent, view ) {
if(isEventOverDiv(jsEvent.clientX, jsEvent.clientY)) {
$('#calendar-holder').calendar('removeEvents', event._id);
var el = $( "<div class='fc-event'>" ).appendTo( '#external-events-listing' ).text( event.title );
el.draggable({
zIndex: 999,
revert: true,
revertDuration: 0
});
el.data('event', { title: event.title, id :event.id, stick: true });
}
}
});
calendar.render();
let isEventOverDiv = function(x, y) {
var external_events = $( '#external-events' );
var offset = external_events.offset();
offset.right = external_events.width() + offset.left;
offset.bottom = external_events.height() + offset.top;
// Compare
if (x >= offset.left
&& y >= offset.top
&& x <= offset.right
&& y <= offset .bottom) { return true; }
return false;
}
});
I figured out that the jsEvent is now located in event.jsEvent. This is where I can get the position from now.

image in magnific popup in iframe

i have a magnific popup being displayed dynamically but the image is rendered after the load event so i cannot get the value of the image in the open event. is there any onload event in magnific popup?
function OpenPopup(el) {
var temp = el.innerHTML;
var mysrc = $(temp).attr("src");
var isobj = $.magnificPopup.open({
items: {
src: mysrc,
onload: 'myevent()',
},
type: 'iframe',
closeOnBgClick: false,
markup: '<div class="mfp-iframe-scaler">' +
'<div class="mfp-close"></div>' +
'<iframe class="mfp-iframe" frameborder="0" onload="myevent()"></iframe>' +
'<div class="mfp-bottom-bar">' +
'<div class="mfp-title"></div>' +
'</div>' +
'</div>',
callbacks: {
beforeOpen: function () {
console.log('Start of popup initialization');
},
elementParse: function (item) {
// Function will fire for each target element
// "item.el" is a target DOM element (if present)
// "item.src" is a source that you may modify
debugger;
console.log('Parsing content. Item object that is being parsed:', item);
},
change: function () {
console.log('Content changed');
console.log(this.content); // Direct reference to your popup element
},
resize: function () {
console.log('Popup resized');
// resize event triggers only when height is changed or layout forced
},
open: function () {
console.log('Popup is opened');
debugger;
var iframe = $.magnificPopup.instance,
t = $(iframe.currItem.el[0]);
var contents = iframe.contents();
$(contents).css('max-width', '100%');
$(contents).css('max-height', '100%');
}
}
});
Not a proper solution put i added a timed event which resizes the image once it is loaded. Had to play a lot to get the perfect time so that there is no glitch.
Here is the code:
function OpenPopup(el) {
var temp = el.innerHTML;
var mysrc = $(temp).attr("src");
var isobj = $.magnificPopup.open({
items: {
src: mysrc,
},
delegate: 'a',
type: 'iframe',
closeOnBgClick: false,
});
setTimeout(setImage, 500);
};
function setImage() {
var iframe = $('.mfp-iframe');
var contents = iframe.contents();
$(contents).find('img').attr('width', '100%');
$(contents).find('img').attr('height', '100%');
}

Fetching all GoogleCalendar Events before FullCalendar has loaded

I currently am using Adam Shaw's jQuery Calendar 'FullCalendar' and am experiencing significant delays in the calendar rendering. In short, the page appears, 1 second passes, the Calendar pops in, another second passes, and then the events populate the page, here. Is there a way to only fetch a certain number of events behind and before today's date? Or even loading the calendar immediately would be an improvement. I am also using Craig Thompson's qTip2.
Javascript
<script type=text/javascript>
// Setup FullCalendar
jQuery(document).ready
(function() {
var date = new Date();
var d = date.getDate();
var m = date.getMonth();
var y = date.getFullYear();
var tooltip = $('<div/>').qtip({
id: 'fullcalendar',
prerender: true,
content: {
text: ' ',
title: {
},
},
events: {
render: function(event, api) {
var elem = api.elements.bgiframe;
}
},
position: {
my: 'bottom center',
at: 'top center',
target: 'event',
viewport: $(window),
adjust: {
mouse: false,
scroll: true,
method: 'shift',
resize: true
}
},
show: {
modal: {
on: false,
blur: true,
stealfocus: false
}
},
hide: false,
style: 'qtip-bootstrap'
}).qtip('api');
$('#fullcalendar').fullCalendar({
eventSources: ["https://www.google.com/calendar/feeds/emailaddresshere/public/basic",
"http://www.google.com/calendar/feeds/usa__en%40holiday.calendar.google.com/public/basic"],
header: {
left: 'title',
center: '',
right: 'today prev,next'
},
selectable: true,
eventClick: function(data, event, view) {
var content = '<h3>'+data.title+'</h3>' +
'<p><b>Start:</b> '+data.start+'<br />' +
(data.end && '<p><b>End:</b> '+data.end+'</p>' || '');
tooltip.set({
'content.text': content
})
.reposition(event).show(event);
},
dayClick: function() { tooltip.hide() },
eventResizeStart: true,
eventDragStart: false,
viewDisplay: function() { tooltip.hide() }
});
}());
</script>

Extjs alignment of icon next to textfield control

I want the icon on right side of textfield , What i am trying is i wrap the textbox and that wrapper has relative position and then to this wrap i append a div with absolute position and then set the class to it to show the icon. My question is the icon with the absolute position shouldn't it sit within the relative positioned wrapper. I see the wrapper and icon div outside of the textfield table in the HTML tab of firebug after it is rendered. My code with the screen shot is as below.
<style type="text/css">
.icon {
background-image: url("../Content/images/not_documented.png");
cursor: pointer;
height: 16px;
width: 16px;
}
</style>
<script type="text/javascript">
Ext.onReady(function () {
//define a new custom text field
Ext.define('WithStatusTextField', {
extend: 'Ext.form.field.Text',
alias: 'widget.withStatusTextField',
iconCls: 'icon ',
fieldStyle: {
textTransform: "uppercase"
},
initComponent: function () {
this.callParent(arguments);
},
afterRender: function () {
if (this.iconCls) {
var iconCls = this.iconCls;
//delete this.iconCls;
this.setIconCls(iconCls);
}
this.callParent(arguments);
},
renderIconEl: function () {
if (!this.wrap) {
this.wrap = this.el.wrap({ cls: "x-form-field-wrap" });
this.positionEl = this.wrap;
}
this.wrap.applyStyles({ position: "relative" });
this.icon = Ext.DomHelper.append(this.el.up("div.x-form-field-wrap") || this.wrap,
{
tag: "div",
style: "position:absolute"
}, true)
if (!this.width) {
this.wrap.setWidth(this.el.getWidth() + 50);
}
this.icon.on("click", function (e, t) {
this.fireEvent("iconclick", this, e, t);
}, this);
},
setIconCls: function (iconCls) {
if (this.iconCls) {
this.renderIconEl();
}
this.iconCls = iconCls;
this.icon.dom.className = iconCls;
//this.icon.alignTo(this.el, 'tl-tr', [2, 0]);
},
listeners: {
change: function (obj, newValue) {
console.log(newValue);
obj.setRawValue(newValue.toUpperCase());
}
}
});
Ext.create('Ext.form.Panel', {
title: 'Simple Form',
bodyPadding: 5,
width: 350,
height: 150,
// The fields
defaultType: 'withStatusTextField',
items: [{
fieldLabel: 'First Name',
name: 'first',
allowBlank: false
},{
fieldLabel: 'Last Name',
name: 'last',
allowBlank: false
}],
// Reset and Submit buttons
buttons: [{
text: 'Reset',
handler: function() {
this.up('form').getForm().reset();
}
}, {
text: 'Submit',
formBind: true, //only enabled once the form is valid
disabled: true,
handler: function() {
var form = this.up('form').getForm();
if (form.isValid()) {
form.submit({
success: function(form, action) {
Ext.Msg.alert('Success', action.result.msg);
},
failure: function(form, action) {
Ext.Msg.alert('Failed', action.result.msg);
}
});
}
}
}],
renderTo: Ext.getBody()
});
when i put the debug point in firebug at the point where the class for the div that show the icon is set it shows the icon properly next to the textfield the screenshot of which is but after the complete render the icon sits somewhere else.
Wrap on textfield with icon class afterRender - the visual display of icon after the icon class is set.
Wrap on textfield with icon class - this.wrap.dom.outerHtml
dom Structure - after the complete render the div icon class with field wrap class goes outside the textfield div.

sencha touch :: how to create a panel for website-preview inside iFrame

I need to allow some website previews inside sencha touch. I see two different possibilities:
opening safariMobile-app to show the link
generating a panel with an iFrame inside the 'html'-property.
because I don't know how to achieve 1. I started with 2. but run into some trouble:
a) the content of my iFrameis not scrollable. if I try to scroll the content, the whole viewport scrolls, including my bottom-tabPanel-Buttons!
b) the displayed website seems to load without any css or images
here is my previewPanel-code:
app.views.WebsitePreview = Ext.extend(Ext.Panel, {
layout: 'card',
scroll: 'vertical',
styleHtmlContent: true,
fullscreen: true,
initComponent: function(){
this.html = '<iframe width="100%" height="100%" src="'+ this.theLink + '"></iframe>',
var toolbarBase = {
xtype: 'toolbar',
title: 'Vorschau ' //+ this.childData.childUsername,
};
if (this.prevCard !== undefined) {
toolbarBase.items = [
{
xtype: 'button',
ui: 'back',
text: 'zurück', //this.prevCard.title,
scope: this,
handler: function(){
this.baseScope.setActiveItem(this.prevCard, { type: 'slide', reverse: true });
}
}
]
};
this.dockedItems = toolbarBase;
app.views.WebsitePreview.superclass.initComponent.call(this);
}
});
Ext.reg('websitepreview', app.views.WebsitePreview);
thnx for your help!
I spent two days fighting with the same problem. It seems that finally I found a solution.
The first thing you should try is to use new built-in feature introduced in iOS 5.
-webkit-overflow-scrolling:touch;
You need to wrap your iframe with div, something like:
...
this.html = '<div style="-webkit-overflow-scrolling:touch; height: 500px; overflow: auto;"><iframe .../></div>'
...
If it doesn't work (in my case it worked only first time) then you can try to handle touch events by yourself. Let's say you have the following structure in html:
<div id="wrapper">
<iframe id="my-iframe" .../>
</div>
to make iframe scrollable you need to add this JS
var startY = 0;
var startX = 0;
var ifrDocument = document.getElementById("my-iframe").contentWindow.document;
ifrDocument.addEventListener('touchstart', function (event) {
window.scrollTo(0, 1);
startY = event.targetTouches[0].pageY;
startX = event.targetTouches[0].pageX;
});
ifrDocument.addEventListener('touchmove', function (event) {
event.preventDefault();
var posy = event.targetTouches[0].pageY;
var h = document.getElementById("wrapper");
var sty = h.scrollTop;
var posx = event.targetTouches[0].pageX;
var stx = h.scrollLeft;
h.scrollTop = sty - (posy - startY);
h.scrollLeft = stx - (posx - startX);
startY = posy;
startX = posx;
});
Source of the second solution is here
The only way I got this to work was by nesting the <iframe> in 2 panels, but this will probably only work if you know the dimensions of the document in the <iframe>, I also placed a transparent <div> over the <iframe> so the touch events still trigger the "scroll events"
root = new Ext.Panel({
fullscreen: true,
layout: 'card',
version: '1.1.1',
scroll: false,
dockedItems: [{ xtype: 'toolbar', title: 'hello'}],
items: [{
xtype: 'panel',
scroll: 'both',
items: [{
id: 'iframe',
layout: 'vbox',
width: '1200px',
height: '1000px',
html: ['<div style="width:1200px;height:1000px;position:fixed;top:0;left:0;background-color:Transparent;float:left;z-index:99;"></div>',
'<iframe style="position:fixed;top:0;left:0;float:left;z-index:1;" width="1200px" height="1000px" src="http://google.com/"></iframe>']
}]
}]
});
So using your code:
this.items = [{
id: 'iframe',
layout: 'vbox',
width: '1200px',
height: '1000px',
html: ['<div style="width:1200px;height:1000px;position:fixed;top:0;left:0;background-color:Transparent;float:left;z-index:99;"></div>',
'<iframe style="position:fixed;top:0;left:0;float:left;z-index:1;" width="1200px" height="1000px" src="' this.theLink + '"></iframe>']
}]

Resources