Ctrl+f (find) in Chrome app - google-chrome-app

Is it somehow possible to enable the browser feature Ctrl+F (find in the current view) in a chrome app?
I am talking about an app I am writing myself and I am really missing that feature.

I thought an example might be nice. I wanted this too.
Here is what I came up with from the ideas above.
I edited the Kiosk App sample from Google with these changes:
HTML:
enter code here
CSS:
#live-search{
display:none;
}
.text-input{
right: 25px;
z-index: 10000;
position: absolute;
border: #000 solid 1px;
}
JS:
window.onresize = doLayout;
var isLoading = false;
var webview //made global so that I can access it easier everywhere.
onload = function() {//existing function, added more to it.
webview = document.querySelector('webview');
webview.addEventListener('consolemessage', function(e) {
//this lets the webview send to the console so that I can see why something doesn't work.
console.log('Guest page log at '+ e.line + ", ", e.message);
});
doLayout();
$().ready(function()//using jquery onload here.
{
var ctrlDown = false;
var ctrlKey = 17, fKey = 70;
$(document).keydown(function(e)//detect CTRL key.
{
if (e.keyCode == ctrlKey) ctrlDown = true;
}).keyup(function(e)
{
if (e.keyCode == ctrlKey) ctrlDown = false;
});
$(document).keydown(function(e)//detect CTRL+f
{
if (ctrlDown && e.keyCode == fKey){
$("#live-search .text-input").val("").focus();//clear search box
$("#live-search").toggle();
};
});
$("#live-search .text-input").keyup(function(e){//typed in search, so search.
webview.executeScript(
{code:
'window.find("'+ $("#live-search .text-input")[0].value +'",false, false, true);'
});
});
});
JS in one other function :
function handleLoadStop(event) {//hide the search when they go to another page.
$("#live-search").hide();

You can't "enable the browser feature" because a Chrome App window is not a browser. True, it's it's always initially loaded from an HTML file, but that doesn't make it a browser. What you have to do is set a handler for the keyboard event (a technique not unique to Chrome Apps) and then in that handler do what you want. You can process the DOM representing the contents of the window to search for something if you like, but this is entirely up to your own JavaScript coding.

If you are using WebView in your application to embed some page, so you can use this:
var wv = document.querySelector('webview');
wv.executeScript({code: "window.find('Some text',false, false, true);"})

Related

Position the dialog at the center of the screen in Fiori

I have a SAPUI5 Fiori application.
I use theme sap_fiori_3 as the base theme.
I customized this theme and only attached a background image to the theme.
The interesting part is when I activate this customized theme (that only has an extra background image in comparison to original sap_fiori_3 theme), the dialog are not centered in my app anymore.
The dialog are made with sap.m.dialog class.
I wrote a small snippet of code to center the dialog like following:
onAfterDialogOpen: function(oEvent){
var oDialog = oEvent.getSource(),
$Dialog = oDialog.$(),
oPosition = $Dialog.position(),
iTop = oPosition.top,
iLeft = oPosition.left,
iDialogWidth = $Dialog.width(),
iDialogHeight = $Dialog.height(),
iScreenWidth = sap.ui.Device.resize.width,
iScreenHight = sap.ui.Device.resize.height,
iNewTop = Math.floor((iScreenHight-iDialogHeight)/2),
iNewLeft = Math.floor((iScreenWidth-iDialogWidth)/2);
if(Math.abs(iNewLeft-iLeft) > 10 & Math.abs(iNewTop-iTop) > 10){
$Dialog.offset({top: iNewTop, left: iNewLeft});
}
},
But it is not a good solution. Why? Because it makes a motion on my screen like following:
Now the question is, how can I center the dialog without Java Script and by settings or some other tricks that when the dialog is opened, it be already centered.
Please note that using onBeforeOpen event is not possible as I need the size and position of the dialog!
I finally found out what is the source of the problem. It seems the Theme Designer of SAP is buggy and some part of the theme code does not transfer to the exported file.
When I use the theme designer to customize the theme it not only made the mentioned error, but also some other strange behavior appear in the deployed applications in the fiori launchpad which use the customized theme. However, we don't have those errors in the development time in the WEB IDE.
Therefore as I only needed to customize the following items:
background image
logo
favicon
I tried to use the standard theme like sap_fiori_3 and work around for setting these properties.
So for the first 2 issues I used the CSS hack:
div.sapUShellFullHeight {
background-image: url(../images/myBackgroundImage.svg);
background-repeat: no-repeat;
background-size: contain;
background-position: right;
}
a#shell-header-logo > img#shell-header-icon {
content:url(../images/logo.svg);
}
And for the favicon I used the promise solution. Please notice in the fiori launchpad each time that you switch between the applications fiori will reset the favicon, so I used the JavaScript promise to set it.
// Set the favicon dynamically to get read of blinking
function waitForElementAppear(selector) {
return new Promise(function(resolve, reject) {
var element = document.querySelector(selector);
if(element) {
resolve(element);
return;
}
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
var nodes = Array.from(mutation.addedNodes);
for(var node of nodes) {
if(node.matches && node.matches(selector)) {
observer.disconnect();
resolve(node);
return;
}
};
});
});
observer.observe(document.documentElement, { childList: true, subtree: true });
});
}
//
function waitForElementRemoved(selector) {
return new Promise((resolve) => {
var element = document.querySelector(selector);
if(!element) {
resolve();
return;
}
var observer = new MutationObserver((mutations, observer) => {
for (const mutation of mutations) {
for (const removedNode of mutation.removedNodes) {
if (removedNode === element) {
observer.disconnect();
resolve();
}
}
}
});
observer.observe(element.parentElement, { childList: true });
});
}
//
function changeFavIcon(selector) {
waitForElementAppear(selector).then(function(element) {
element.setAttribute("href", "icon/favicon.ico");
waitForElementRemoved(selector).then(function() {
changeFavIcon(selector);
});
});
};
changeFavIcon("link[rel='shortcut icon']");
It recursively checks when the favicon is injected then it will set its href and as soon as it is removed, this function will observe the next injection!
As I know somebody may says why not used sapui5 its original solution for setting the favicon, like this:
jQuery.sap.setIcons({
'phone': '/images/cimt-logo.png',
'phone#2': '/images/cimt-logo.png',
'tablet': '/images/cimt-logo.png',
'tablet#2': '/images/cimt-logo.png',
'favicon': '/icon/favicon.ico',
'precomposed': true
});
I must say it was not working in my case!

Get artwork_url from Soundcloud API and show album covers in custom SC/SM2 player

I've been trying to sort out how artwork_url can be used from soundclouds API in order to output each cover into this custom player, and have each appropriate thumb next to its own track in the playlist?
I understand that I need to use the artwork_url property, however I do not understand how this is achieved, nor how to integrate it into this particular custom player plugin.
Any code examples in particular and/or help is highly appreciated!
Note: Also would be nice to be able to control the "size" of the artwork as well through other means then just CSS.
Best
EDIT #1
I switched the Soundcloud Custom Player on Heroku since after I was able to get it up and running I discovered it to have a much faster load time in contrast to the original player I cited above (even though that one is still quite awesome)...
Im still posed with the same task now however as before - How to add album art to the script and output accordingly?
Pasted below is the Heroku player:
// # SoundCloud Custom Player
// Make sure to require [SoundManager2](http://www.schillmania.com/projects/soundmanager2/) before this file on your page.
// And set the defaults for it first:
soundManager.url = 'http://localhost:8888/wp-content/themes/earpeacerecords/swf';
soundManager.flashVersion = 9;
soundManager.useFlashBlock = false;
soundManager.useHighPerformance = true;
soundManager.wmode = 'transparent';
soundManager.useFastPolling = true;
// Wait for jQuery to load properly
$(function(){
// Wait for SoundManager2 to load properly
soundManager.onready(function() {
// ## SoundCloud
// Pass a consumer key, which can be created [here](http://soundcloud.com/you/apps), and your playlist url.
// If your playlist is private, make sure your url includes the secret token you were given.
var consumer_key = "915908f3466530d0f70ca198eac4288f",
url = "http://soundcloud.com/poe-epr/sets/eprtistmix1";
// Resolve the given url and get the full JSON-worth of data from SoundCloud regarding the playlist and the tracks within.
$.getJSON('http://api.soundcloud.com/resolve?url=' + url + '&format=json&consumer_key=' + consumer_key + '&callback=?', function(playlist){
// I like to fill out the player by passing some of the data from the first track.
// In this case, you'll just want to pass the first track's title.
$('.title').text(playlist.tracks[0].title);
// Loop through each of the tracks
$.each(playlist.tracks, function(index, track) {
// Create a list item for each track and associate the track *data* with it.
$('<li>' + track.title + '</li>').data('track', track).appendTo('.tracks');
// * Get appropriate stream url depending on whether the playlist is private or public.
// * If the track includes a *secret_token* add a '&' to the url, else add a '?'.
// * Finally, append the consumer key and you'll have a working stream url.
url = track.stream_url;
(url.indexOf("secret_token") == -1) ? url = url + '?' : url = url + '&';
url = url + 'consumer_key=' + consumer_key;
// ## SoundManager2
// **Create the sound using SoundManager2**
soundManager.createSound({
// Give the sound an id and the SoundCloud stream url we created above.
id: 'track_' + track.id,
url: url,
// On play & resume add a *playing* class to the main player div.
// This will be used in the stylesheet to hide/show the play/pause buttons depending on state.
onplay: function() {
$('.player').addClass('playing');
$('.title').text(track.title);
},
onresume: function() {
$('.player').addClass('playing');
},
// On pause, remove the *playing* class from the main player div.
onpause: function() {
$('.player').removeClass('playing');
},
// When a track finished, call the Next Track function. (Declared at the bottom of this file).
onfinish: function() {
nextTrack();
}
});
});
});
// ## GUI Actions
// Bind a click event to each list item we created above.
$('.tracks li').live('click', function(){
// Create a track variable, grab the data from it, and find out if it's already playing *(set to active)*
var $track = $(this),
data = $track.data('track'),
playing = $track.is('.active');
if (playing) {
// If it is playing: pause it.
soundManager.pause('track_' + data.id);
} else {
// If it's not playing: stop all other sounds that might be playing and play the clicked sound.
if ($track.siblings('li').hasClass('active')) { soundManager.stopAll(); }
soundManager.play('track_' + data.id);
}
// Finally, toggle the *active* state of the clicked li and remove *active* from and other tracks.
$track.toggleClass('active').siblings('li').removeClass('active');
});
// Bind a click event to the play / pause button.
$('.play, .pause').live('click', function(){
if ( $('li').hasClass('active') == true ) {
// If a track is active, play or pause it depending on current state.
soundManager.togglePause( 'track_' + $('li.active').data('track').id );
} else {
// If no tracks are active, just play the first one.
$('li:first').click();
}
});
// Bind a click event to the next button, calling the Next Track function.
$('.next').live('click', function(){
nextTrack();
});
// Bind a click event to the previous button, calling the Previous Track function.
$('.prev').live('click', function(){
prevTrack();
});
// ## Player Functions
// **Next Track**
var nextTrack = function(){
// Stop all sounds
soundManager.stopAll();
// Click the next list item after the current active one.
// If it does not exist *(there is no next track)*, click the first list item.
if ( $('li.active').next().click().length == 0 ) {
$('.tracks li:first').click();
}
}
// **Previous Track**
var prevTrack = function(){
// Stop all sounds
soundManager.stopAll();
// Click the previous list item after the current active one.
// If it does not exist *(there is no previous track)*, click the last list item.
if ( $('li.active').prev().click().length == 0 ) {
$('.tracks li:last').click();
}
}
});
});
EDIT #2
So I strangely was able to work something out... I have no clue if its semantically correct however...
$.getJSON('http://api.soundcloud.com/resolve?url=' + url + '&format=json&consumer_key=' + consumer_key + '&callback=?', function(playlist){
// I like to fill out the player by passing some of the data from the first track.
// In this case, you'll just want to pass the first track's title.
$('.title').text(playlist.tracks[0].title);
$('.album_art').attr('src', playlist.artwork_url);
// Loop through each of the tracks
$.each(playlist.tracks, function(index, track) {
// Create a list item for each track and associate the track *data* with it.
$('<li>' + '<img src="' + playlist.artwork_url + '">' + track.title + '</li>').data('track', track).appendTo('.tracks');
// * Get appropriate stream url depending on whether the playlist is private or public.
// * If the track includes a *secret_token* add a '&' to the url, else add a '?'.
// * Finally, append the consumer key and you'll have a working stream url.
url = track.stream_url;
(url.indexOf("secret_token") == -1) ? url = url + '?' : url = url + '&';
url = url + 'consumer_key=' + consumer_key;
// ## SoundManager2
// **Create the sound using SoundManager2**
soundManager.createSound({
// Give the sound an id and the SoundCloud stream url we created above.
id: 'track_' + track.id,
url: url,
// On play & resume add a *playing* class to the main player div.
// This will be used in the stylesheet to hide/show the play/pause buttons depending on state.
onplay: function() {
$('.player').addClass('playing');
$('.title').text(track.title);
},
onresume: function() {
$('.player').addClass('playing');
},
// On pause, remove the *playing* class from the main player div.
onpause: function() {
$('.player').removeClass('playing');
},
// When a track finished, call the Next Track function. (Declared at the bottom of this file).
onfinish: function() {
nextTrack();
}
});
});
EDIT #3
Below is the HTML and CSS markup that works with the player for better clarification...
HTML
<div class='title'></div>
<a class='prev'>Previous</a>
<a class='play'>Play</a>
<a class='pause'>Pause</a>
<a class='next'>Next</a>
</div>
CSS
/*
-------------------------------------------------------------------------
Soundcloud Player
-------------------------------------------------------------------------
*/
#sticky_header #sticky_content .player {
height: 570px;
overflow: hidden;
}
#sticky_header #sticky_content .tracks {
}
#sticky_header #sticky_content .tracks li {
cursor: pointer;
height: 40px;
text-align: left;
}
#sticky_header #sticky_content .tracks li img.album_art {
width: 40px;
height: 40px;
border-radius: 5px;
margin-right: 15px;
}
#sticky_header #sticky_content .title {
}
#sticky_header #sticky_content .prev {
}
#sticky_header #sticky_content .play {
display: block;
}
#sticky_header #sticky_content .playing .play {
display: none;
}
#sticky_header #sticky_content .pause {
display: none;
}
#sticky_header #sticky_content .playing .pause {
display: block;
}
#sticky_header #sticky_content .next {}
to get an image you can use this code:
//get element by id from your iframe
var widget = SC.Widget(document.getElementById('soundcloud_widget'));
widget.getCurrentSound(function(music){
artwork_url = music.artwork_url.replace('-large', '-t200x200');
$('#song1').css('background', 'url(\"'+artwork_url+'\") ');
});
normaly you get a link with "-large" at the end and the size is 100x100. If you want other sizes you have to change the end with ".replace" like I did. A list with available sizes can you find here:
https://developers.soundcloud.com/docs/api/reference#tracks
(my size 200x200 is not listed but works. Maybe there are more sizes like every hundred px.)
at the moment the code works only for the actual playing song. For me it's not a solution, because i need all images from my playlist.
Here's where iterating over the tracks retrieved from the API happeninng:
// Loop through each of the tracks
$.each(playlist.tracks, function(index, track) {
// Create a list item for each track and associate the track *data* with it.
$('<li>' + track.title + '</li>').data('track', track).appendTo('.tracks');
Inside of the iterator function you can now access track.artwork_url and possibly set it as a background image or perhaps background for some element, maybe something like:
$('<li><img src=" + track.artwork_url + "></img>' + track.title + '</li>').data('track', track).appendTo('.tracks');
I hope this helps.
UPD. In your updated code, you should refer to track.artwork_url instead of playlist – then you'll get each track's individual artwork.

Drag and Drop into Fabric.js canvas

How can I drop items (like image, or other object from other canvas) into canvas which is managed by fabricjs? I have found many examples how to move items inside canvas but I would like to drag and drop item from outer element into canvas.
Since you asked for an example and I haven't tried it out myself yet, here goes:
Example Fiddle
Markup
<div id="images">
<img draggable="true" src="http://i.imgur.com/8rmMZI3.jpg" width="250" height="250"></img>
<img draggable="true" src="http://i.imgur.com/q9aLMza.png" width="252" height="295"></img>
<img draggable="true" src="http://i.imgur.com/wMU4SFn.jpg" width="238" height="319"></img>
</div>
<div id="canvas-container">
<canvas id="canvas" width="800" height="600"></canvas>
</div>
JS Breakdown
1. Fabric.canvas instance
First we want our canvas, of course:
var canvas = new fabric.Canvas('c');
2. Feature Detection (optional)
Not sure this is necessary, since the fact that you have a canvas makes it very likely that the browser has Drag and Drop as well. Were you to use it, you can do so like this, using Modernizr:
if (Modernizr.draganddrop) {
// Browser supports HTML5 DnD.
// Bind the event listeners for the image elements
// Bind the event listeners for the canvas
} else {
// Replace with a fallback to a library solution.
alert("This browser doesn't support the HTML5 Drag and Drop API.");
}
3. Events
Again, unlike the source article I below, the source and target elements are different (in that articles's example, you just move divs around within the same parent container), so I failed to notice that some of the events are meant for the element being dragged, but most are bound to the element into which you are dropping.
NOTE: I know this is technically a question about Fabric.js, but it's really kind of a question about Drag and Drop in the context of adding objects to a <canvas> with Fabric.js, which is why I'm going a bit more in depth about the DnD stuff now.
For the <img>
dragstart (I added a class here to lower the opacity)
dragend (and removed that class here)
For #canvas-container:
dragenter (added a class to give the canvas container that nifty dotted line)
dragover: Here you can set the event.dataTransfer.dropEffect property to show one of the native cursor types. The default would be 'move' here, but I set it to 'copy' since I don't actually remove the <img> element (in fact in the fiddle you can, for example create several McClures).
dragleave (removed the dotted line here)
drop: The handler for this event creates and adds the fabric.Image object (see the fiddle).
if (Modernizr.draganddrop) {
// Browser supports HTML5 DnD.
// Bind the event listeners for the image elements
var images = document.querySelectorAll('#images img');
[].forEach.call(images, function (img) {
img.addEventListener('dragstart', handleDragStart, false);
img.addEventListener('dragend', handleDragEnd, false);
});
// Bind the event listeners for the canvas
var canvasContainer = document.getElementById('canvas-container');
canvasContainer.addEventListener('dragenter', handleDragEnter, false);
canvasContainer.addEventListener('dragover', handleDragOver, false);
canvasContainer.addEventListener('dragleave', handleDragLeave, false);
canvasContainer.addEventListener('drop', handleDrop, false);
} else {
// Replace with a fallback to a library solution.
alert("This browser doesn't support the HTML5 Drag and Drop API.");
}
Sources:
HTML5 Rocks - Native HTML5 Drag and Drop
Modernizr
Web Platform Docs > DOM > Properties - dropEffect
Web Platform Docs > DOM > Events
dragstart
dragend
dragenter
dragover
dragleave
drop
I had gone through fiddle of #natchiketa, And fixed the problem , just check this fiddle..
http://jsfiddle.net/Ahammadalipk/w8kkc/185/
window.onload = function () {
var canvas = new fabric.Canvas('canvas');
/*
NOTE: the start and end handlers are events for the <img> elements; the rest are bound to
the canvas container.
*/
function handleDragStart(e) {
[].forEach.call(images, function (img) {
img.classList.remove('img_dragging');
});
this.classList.add('img_dragging');
}
function handleDragOver(e) {
if (e.preventDefault) {
e.preventDefault();
}
e.dataTransfer.dropEffect = 'copy';
return false;
}
function handleDragEnter(e) {
this.classList.add('over');
}
function handleDragLeave(e) {
this.classList.remove('over');
}
function handleDrop(e) {
if (e.stopPropagation) {
e.stopPropagation(); // stops the browser from redirecting.
}
var img = document.querySelector('#images img.img_dragging');
var newImage = new fabric.Image(img, {
width: img.width,
height: img.height,
// Set the center of the new object based on the event coordinates relative
// to the canvas container.
left: e.layerX,
top: e.layerY
});
newImage.hasControls = newImage.hasBorders = false;
canvas.add(newImage);
return false;
}
function handleDragEnd(e) {
// this/e.target is the source node.
[].forEach.call(images, function (img) {
img.classList.remove('img_dragging');
});
}
if (Modernizr.draganddrop) {
var images = document.querySelectorAll('#images img');
[].forEach.call(images, function (img) {
img.addEventListener('dragstart', handleDragStart, false);
img.addEventListener('dragend', handleDragEnd, false);
});
var canvasContainer = document.getElementById("canvas-container");
canvasContainer.addEventListener('dragenter', handleDragEnter, false);
canvasContainer.addEventListener('dragover', handleDragOver, false);
canvasContainer.addEventListener('dragleave', handleDragLeave, false);
canvasContainer.addEventListener('drop', handleDrop, false);
} else {
alert("This browser doesn't support the HTML5 Drag and Drop API.");
}
}
Thanks
Well, the question is quite old^^
I have updateted the fiddle, no it will work in Firefox as well.
Fiddle
function handleDrop(e) {
// this / e.target is current target element.
e.preventDefault(); //I've altert this line for FireFox
As far as I understand, drag and drop is not already provided by fabricjs,
fiddling it will be quite interesting.
Well I m new to javascript and fabricJS but I think this fiddle might help
fiddle
html code
<div class="img_cont">
<img class="img" id="ele1" draggable="true" ondragstart="dragElement(event)" src="https://webkit.org/wp-content/uploads/STP-300x300.png">
<img class="img" id="ele2" draggable="true" ondragstart="dragElement(event)" src="https://webkit.org/wp-content/uploads/ephy-webkit-graphic.png">
<img class="img" id="ele3" draggable="true" ondragstart="dragElement(event)" src="https://res.cloudinary.com/css-tricks/image/upload/w_600,q_auto,f_auto/buysellads/uu/7/112766/1646327381-MC_CSSTricks_Logo_600x600-_1_.png">
<img class="img" id="ele4" draggable="true" ondragstart="dragElement(event)" src="https://miro.medium.com/max/1400/1*9hd_8qR0CMZ8L0pVbFLjDw.png">
</div>
<br>
<div id="canvas_cont" ondragover="allowDrop(event)" ondrop="dropElement(event)">
<canvas id="canvas" width="650" height="350" ></canvas>
</div>
javascript code
// allowDrop function called on ondragover event.
function allowDrop(e) {
e.preventDefault();
}
//dragElement function called on ondrag event.
function dragElement(e) {
e.dataTransfer.setData("id", e.target.id); //transfer the "data" i.e. id of the target dragged.
}
//Initializing fabric canvas on window load event.
var canvas;
window.onload = function(){
canvas = new fabric.Canvas(document.getElementById("canvas"));
}
//dropElement function called on ondrop event.
function dropElement(e) {
e.preventDefault();
var data = e.dataTransfer.getData("id"); //receiving the "data" i.e. id of the target dropped.
var imag = document.getElementById(data); //getting the target image info through its id.
var img = new fabric.Image(imag, { //initializing the fabric image.
left: e.layerX - 80, //positioning the target on exact position of mouse event drop through event.layerX,Y.
top: e.layerY - 40,
});
img.scaleToWidth(imag.width); //scaling the image height and width with target height and width, scaleToWidth, scaleToHeight fabric inbuilt function.
img.scaleToHeight(imag.height);
canvas.add(img);
}
The accepted answer no longer works.
This is for drag and drop from desktop using the dataTransfer interface.
canvas.on('drop', function(event) {
// prevent the file to open in new tab
event.e.stopPropagation();
event.e.stopImmediatePropagation();
event.e.preventDefault();
// Use DataTransfer interface to access the file(s)
if(event.e.dataTransfer.files.length > 0){
var files = event.e.dataTransfer.files;
for (var i = 0, f; f = files[i]; i++) {
// Only process image files.
if (f.type.match('image.*')) {
// Read the File objects in this FileList.
var reader = new FileReader();
// listener for the onload event
reader.onload = function(evt) {
// put image on canvas
fabric.Image.fromURL(evt.target.result, function(obj) {
obj.scaleToHeight(canvas.height);
obj.set('strokeWidth',0);
canvas.add(obj);
});
};
// Read in the image file as a data URL.
reader.readAsDataURL(f);
}
}
}
});
Resources
https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API/File_drag_and_drop

Does codemirror provide Cut, Copy and Paste API?

From http://codemirror.net/doc/manual.html, I only find getRange(),
undo(), redo() etc, and I can't find cut(), copy() and paste API,
and more when I try to run editor.execCommand("cut"), I get the error.
Could you help me? Thanks!
Using clipboard.js, you can define the text() function to grab the value of the CodeMirror's inner document.
Store a reference to the (<textarea>) editor's selector for convenience.
var editorSelector = '#editor' // or '#editor + .CodeMirror';
Instantiate a new ClipBoard object with reference to your button.
new Clipboard('.clip-btn-native', {
text: function(trigger) {
return getCodeMirrorNative(editorSelector).getDoc().getValue();
}
});
Retrieve a CodeMirror Instance via native JavaScript.
function getCodeMirrorNative(target) {
var _target = target;
if (typeof _target === 'string') {
_target = document.querySelector(_target);
}
if (_target === null || !_target.tagName === undefined) {
throw new Error('Element does not reference a CodeMirror instance.');
}
if (_target.className.indexOf('CodeMirror') > -1) {
return _target.CodeMirror;
}
if (_target.tagName === 'TEXTAREA') {
return _target.nextSibling.CodeMirror;
}
return null;
};
Demo
Please see complete; in-depth demo over at JSFiddle.
There are no CodeMirror APIs for cut/copy/paste because browser security restrictions forbid JavaScript from accessing the clipboard programmatically. Paste could be used to steal private data and Cut/Copy can be used as a more elaborate attack vector.
The browser's own native code handles user gestures that access the clipboard (keyboard shortcuts and context menu items), based solely on the currently selected text or focused text field.
This SO thread has a good summary of attempts to work around these restrictions. CodeMirror's approach is the first bullet: it uses a hidden textarea to ensure that user clipboard gestures work, but that still doesn't support programmatic APIs.
But there is a partial workaround: use a small Flash widget (this is the 2nd bullet in the thread above). Flash relaxes the restrictions on Copy/Cut (but not Paste) a bit. It still has to be triggered by some user event, but it could be something like clicking a button in your HTML UI. Wrappers like ZeroClipboard and Clippy make it simple to access to these capabilities without needing to know Flash. You'd need to write a little glue code to pull the appropriate string from CodeMirror when copying, but it shouldn't be too bad.
Add a hidden contenteditable div to your textarea editor wrapper. Contenteditable divs respect new lines and tabs, which we need when copying code.
Here is my CodePen demo
var content = $('.content');
var toCopy = content.find('.copy-this');
// initialize the editor
var editorOptions = {
autoRefresh: true,
firstLineNumber: 1,
lineNumbers: true,
smartIndent: true,
lineWrapping: true,
indentWithTabs: true,
refresh: true,
mode: 'javascript'
};
var editor = CodeMirror.fromTextArea(content.find(".editor")[0], editorOptions);
content[0].editor = editor;
editor.save();
// set editors value from the textarea
var text = content.find('.editor').text();
editor.setValue(text);
// setting with editor.getValue() so that it respects \n and \t
toCopy.text(editor.getValue());
$(document).on('click', '.copy-code', function() {
var content = $(this).closest('.content');
var editor = content[0].editor;
var toCopy = content.find('.copy-this')[0];
var innerText = toCopy.innerText // using innerText here because it preserves newlines
// write the text to the clipboard
navigator.clipboard.writeText(innerText);
});
.content {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.CodeMirror {
height: fit-content !important;
}
.copy-code {
background: #339af0;
width: fit-content;
cursor: pointer;
}
<!-- resources -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.35.0/codemirror.css" />
<script src="https://codemirror.net/lib/codemirror.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.40.0/mode/javascript/javascript.min.js"></script>
<script src=""></script>
<script src=""></script>
<script src=""></script>
<script src=""></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="content">
<!-- button to copy the editor -->
<div class="copy-code" title="copy code">copy</div>
<!-- add contenteditable div as it respects new lines when copying unlike textarea -->
<div class="copy-this" contenteditable style="display: none"></div>
<textarea class="editor" style="display: none;">// here is a comment
// here is another comment
</textarea>
</div>

can I build a css class on the fly in tiny mce?

I'm using tiny mce, but I found it adds multiple spans with inline styles to the content for any applied style. Inline styles are not W3c Compliant, so must avoid inline css. Is it possible to create css class on the fly and apply to the selection, while editing content in tiny mce ?
Yes that is possible, but it took me some effort. What needs to be done is to write the class into the head of the editors iframe. Here is some example code which should work for IE,FF, Safari and point you into the right direction:
fonturl = "http://myfonts.com/arial.ttf"
csstext_to_add = '#font-face {font-family: "ownfont";src: url("'+fonturl+'");}'; // example
iframe_id = ed.id;
with(document.getElementById(iframe_id).contentWindow){
var h=document.getElementsByTagName("head");
if (!h.length) {
return;
}
var newStyleSheet=document.createElement("style");
newStyleSheet.type="text/css";
h[0].appendChild(newStyleSheet);
try{
if (typeof newStyleSheet.styleSheet !== "undefined") {
newStyleSheet.styleSheet.cssText = csstext_to_add;
}
else {
newStyleSheet.appendChild(document.createTextNode(csstext_to_add));
newStyleSheet.innerHTML=csstext_to_add;
}
}
catch(e){}
}
It is also possible to add that class as option into a dropdown (what takes some effort).
Thariama's answer was perfect. I'm using the tinyMCE jQuery connector for some of my pages and I have multiple instances of tinyMCE on the page. I made some modifications, but essentially its the same thing. I've created a text area field on the page that people can provide their own CSS. Also, I needed to change some CSS rules on the fly...
// function to change tinyMCE css on the fly
function checkCustomCSS() {
var $css = $('#page_css'),
newcss;
if ($css.val().length > 0) {
// since front end, we are wrapping their HTML in a wrapper and
// the tinyMCE edit iFrame is just using <body>, we need to change
// some rules so they can see the changes
newcss = $css.val().replace('#content_wrapper', 'body');
// loop through each tinyMCE editor and apply the code changes
// You could check the editor.id to make sure that the correct
// editor gets the appropriate changes.
$.each(tinyMCE.editors, function() {
var $this = $(this),
editorID = $this[0].id,
$ifrm = $('#' + editorID+ '_ifr'),
cwin, head, sheet;
if ($ifrm.length > 0 /* && editorID === 'OUR_EDITOR_ID_NAME' */) {
cwin = $ifrm[0].contentWindow;
head = cwin.document.getElementsByTagName("head");
if (!head.length) {
return;
}
sheet = cwin.document.createElement("style");
sheet.type = "text/css";
head[0].appendChild(sheet);
try {
if (typeof sheet.styleSheet !== "undefined") {
sheet.styleSheet.cssText = newcss;
} else {
sheet.appendChild(cwin.document.createTextNode(newcss));
sheet.innerHTML = newcss;
}
} catch (e) {}
}
});
}
}
Then in the tinyMCE init call I added and onInit call to setup changes to the #page_css , like this:
oninit: function() {
$('#page_css').on('change', function() {
checkCustomCSS();
});
}
Works like a charm.