CKEditor: Modifying view without changing data - ckeditor

I have content that references Images by ID within a placeholder (e.g. "$m(12345)" ). I have a REST call that will return an img-tag for the placeholder.
I would like CKEditor to display the image when the content is opened in editor, or a placeholder is inserted. But I want the placeholder to remain in the content (including when switching to the Source view)
I've tried to do this by adding a rule to the dataFilter:
CKEDITOR.on('instanceLoaded', function(ckeditor){
var mediaPlaceholderRegex = /\$m\(.*\)/;
ckeditor.editor.dataProcessor.dataFilter.addRules({
text: function( text, node ) {
return text.replace( mediaPlaceholderRegex, function( match ) {
var params = "placeholder="+match;
var xhttp = new XMLHttpRequest();
xhttp.open("POST", url, false);
xhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
xhttp.send(params);
return xhttp.responseText;
} );
}
});
});
It does the job of replacing the placeholder with the image tag, but the img-tag is also there when switching to the source view.
Is there an easy way to only apply a filter to the wysiwyg view.
The only way I see is to add a htmlFilter that would revert the img-tag back to a placeholder.

Is there an easy way to only apply a filter to the wysiwyg view. The only way I see is to add a htmlFilter that would revert the img-tag back to a placeholder.
Good thinking. Either that of if you don't want your images to be removed/fetched from the server on every mode change, you can for example put your placeholder into the data- attribute for the image tag. It all depends on your use case but the bottom line is that dataFilter is used when you load data into the editor and htmlFilter when you get data from the editor (same methods are used when getting data and switching to source mode so htmlFilter applies here).

Related

Insert image in editor after upload

I've managed to upload images through drag & drop to a SP 2013 library by intercepting the paste and fileUploadrequest events (+ added mandatory headers and used /_api/web/getfolderbyserverrelativeurl(\'/sites/theSite/theLibrary\')/files/add(overwrite=true,%20url=\'aDynamicFilename.jpg\') as the request's URL).
The problem with this approach is that even if the image is uploaded, the image is not inserted in the editor (no error). I'm not setting config.uploadUrl for this approach.
Q#1: Is there any step which I should go through after the image is uploaded? Like telling the CKEDITOR instance to insert the image?
Later on, I've noticed that if I'm setting the config.uploadUrl to the same URL as I'm using above, the editor inserts successfully the image. The problem is that, from my trials, the config.uploadUrl is initialized together with the CKEDITOR instance (therefore, can't be assigned dynamically for each image, in case that multiple images are dragged and dropped on the editor).
Q#2: Is there another way to configure the uploadUrl or maybe some other config property that would allow the custom upload to work and insert the image in the editor?
Eventually made it work by following a similar approach as the on in this repo:
RyanSiu1995/ckeditor-ImageUploader
Use a FileReader and start loading the image when it's pasted to the
CKEditor
On the fileReader's onload event, create a img element in the
editor's document object with some opacity and with the fileReader's
Base64 string as the src
On the fileLoader's uploaded event, remove
the img and re-add it with the actual file's url (changing the src
attribute on the img was not triggering the editor's change event, which I was hooking into,so I chose to replace the whole element)
Here's the relevant section from the ckeditor-ImageUploader plugin:
fileDialog.on('change', function (e) {
var fileTools = CKEDITOR.fileTools,
uploadUrl = fileTools.getUploadUrl( editor.config, 'image' ),
file = e.target.files[0],
loader = editor.uploadRepository.create(file),
reader = new FileReader(),
notification,
img;
// verify
if (!/image/i.test(file.type)) {
notification = editor.showNotification( 'Please check the correct format.', 'warning' );
setTimeout(function() {
notification.hide()
}, 2000);
return false
}
loader.upload(uploadUrl);
// preview image
reader.readAsDataURL(e.target.files[0]);
reader.onload = function (e) {
img = editor.document.createElement('img');
img.setAttribute('src', e.target.result);
img.setStyle('opacity', 0.3);
editor.insertElement(img);
}
loader.on('uploaded', function(evt) {
editor.widgets.initOn(img, 'image', {
src: evt.sender.url
});
img.setAttribute('src', evt.sender.url);
img.setStyle('opacity', 1);
});
loader.on('error', function() {
img.$ && img.$.remove();
});
fileTools.bindNotifications(editor, loader);
// empty input
fileDialog[0].value = "";

Manually load kendo mobile view

I'm working on a Kendo Mobile project with a number of:
Kendo Views (external to root html)
Modal Views (in the root html).
The external files load on demand and everything works fine. But i'd like to have the same load on demand behavior for some of the modal views, because the root/based html file is becoming too large and not manageable.
Is there a way to either:
Store a modal view in an external file? If so is it possible to load via javascript syntax (app.navigate()) rather than the declarative syntax (href='externalmodal').
Manually pre-load an external view without navigating to it first.
This code lets you manually create a view:
var viewUrl = 'blahblahblah';
var element = $.parseHTML('<div data-role=view>test</div>')[0];
element.style.display = 'none';
$(document.body).append(element);
var options = $.extend({}, kendo.parseOptions(element, kendo.mobile.ui.View.fn.options));
var view = new kendo.mobile.ui.View(element, options);
view.element[0].setAttribute('data-url', viewUrl);
kendo.mobile.application.navigate(viewUrl, '');
Depending on what features you use, you may need to instead use code similar that that used for ModalView below so that Kendo creates the subclass (changes: substitute View for ModalView, substitute view for modalview, add data-url, remove call to show(), maybe check that view not already created by checking for element with matching data-url). We haven't tested setting roles.view this way, but we did something similar while testing this stuff out and it worked.
Don't try settings the options - Kendo got confused (at least trying to set useNativeScrolling didn't work, also don't try setting the options object on the subclass unless you really know what you are doing).
Caveat: This was using browserHistory:false (which disables routing) when the kendo.mobile.Application was created. The technique should still work when using browser history if you use a valid url fragment (same as would be created by Kendo for the pushstate/hashchange url).
This is a also way to cleanly subclass kendo.mobile.ui.View that works well - although you must still use data-role=view even though your subclass is a "different" component. Note that you can't just use you cant use your own subclassed component with its own name like role=myview to subclass a view because there are hard-coded checks specifically for data-role=view in the kendo codebase. Same if you wish to subclass: layout modalview drawer splitview page (amongst other hard-coded kendo ui component names - search kendo code for kendo.roleSelector - ugly). e.g.
MyView = kendo.mobile.ui.View.extend({
init: function(element, options) {
kendo.mobile.ui.View.prototype.init.apply(this, arguments);
...
var myView = new MyView('<div data-role=view>test</div>');
Why it works: The relevant function in the Kendo source code is _findViewElement which does element = this.container.children("[" + attr("url") + "='" + urlPath + "']"); to see if the view already exists for a url, before creating a new one. A unique init function is always required as it ends up being the constructor function.
If you want to subclass a modalview, you need to do something different due to the way kendo works:
var MyModalView = kendo.mobile.ui.ModalView.extend({
html: '<div data-role=modalview style="width:90%;display:none;">Foobar</div>',
init: function() {
kendo.mobile.ui.ModalView.prototype.init.apply(this, arguments);
}
});
function makeModalView() {
$(document.body).append($.parseHTML(MyModalView.prototype.html));
var roles = $.extend({}, kendo.mobile.ui.roles);
roles.modalview = MyModalView;
var modalView = kendo.initWidget($(element), {}, roles);
modalView.open();
return modalView;
}

Save edited inline text from CKEditor 4 asp net

I am trying to implement CKEditor 4 into an ASP NET website that I am working on, but I cannot figure out how I would save the edited material from the inline editor I know how to do it with the the old version, but I just don't understand the process for this.
I have looked in the forums... There is not v4 forum.
I looked in for the documentation.... Couldn't find it.
I have a feeling that this is a simple task, but I just don't know how.
You can get your data with CKEDITOR.instances.editor1.getData(). Then you can send it via AJAX or store it as a value of some input field. To do this periodically, follow this method:
CKEDITOR.disableAutoInline = true;
var editor = CKEDITOR.inline( 'editable' );
var savedData, newData;
function saveEditorData() {
setTimeout( function() {
newData = editor.getData();
if ( newData !== savedData ) {
savedData = newData;
// Send it with jQuery Ajax
$.ajax({
url: 'yourUrl',
data: savedData
});
// Or store it anywhere...
// ...
// ...
}
saveEditorData();
}, 500 );
};
// Start observing the data.
saveEditorData();
You can also observe the submit event and update some (hidden) form field with your data.
Have fun!
Are you trying to get it with AJAX or send with a form? The value of for example the top right inline editor area with Lorem Ipsum can be gotten like in the older version with simply
CKEDITOR.instances.editor1.getData().
In the XHTML output example they have a simple form that seems to work and I believe that using an (static) inline editor is just the same.
If you transform elements into editors inline dynamically, I would try to bind to the submit event and before submitting loop through all CKEDITOR.instances, get their data into hidden from fields. As for the hidden field naming or identifying which hidden field corresponds to which dynamic editor you'll have to figure out yourself :)

TinyMCE not working in http request xhr ajax generated page

So i I have a page that contains links that call an httpRequest. The request calls a php file that grabs data from mysql and pre populates a form which is then returned to the browser/webpage. My problem is that when the page is returned to the browser via the httpRequest/ajax the text area does not display the tinymce editor, it just displays a normal text area. It looks like my request and ajax is working fine the text area just doesn't have the tinycme editor on it.
When i don't use ajax it works fine but when i put it in a separate file and call it via ajax it doesn't bring in the tinymce editor.
Does anyone know how to fix this problem so that my ajax generated page displays the text area with the tinymce editor. Thank you.
Lets presume that your thinyMCE instance is initialized with code below
// initialize tinyMCE in page
tinyMCE.init({
mode: "textareas",
theme: "advanced"
});
and you have some kind of button somewhere in the page. For purpose of this tip, i will not give it any ID but you may. Now, using jQuery you can easily attach event handler to that button which will call through AJAX your server and take content which you want to put tinyMCE editor. Code which will do such job would look somehow like below.
$(function() {
$("button").bind("click", function() {
var ed = tinyMCE.get('content');
ed.setProgressState(1); // Show progress
$.getJSON('/page/12.json', { /* your data */
}, function(data) {
ed.setProgressState(0); // Hide progress
ed.setContent(data["body"]);
}
});
});
});
You can see that on button.click ajax will call url /page/12.json which will return JSON as response. bare minimum of that response could be:
{
title: "Page title",
body: "<html><head><title>Page title</title>......</html>"
}
I attached anonymous function as callback which will handle response from server. and hide progress indicator which is shown before ajax call.
About JSON
JSON is shorten of JavaScript Object Notation. It is JavaScript code!!! So don't be confused about it. Using JSON you can make javascript object which can have attributes you can use later in your code to access particular peace of data which that object "holds". You can look at it as some kind of data structure if it is easier to you.
Anyway, to show you how this JSON can be created by hand look at examples below
var data = new Object();
data.title = "Page title";
data.body = "<html....";
or
var data = {
title: "page title",
body: "<html...."
};
it is very same thing.
If you want to learn more about JSON point your browser to http://json.org.
===== alternative =====
Alternative to json solution could be just plane ajax call to server and response can be plain HTML (from your question I can assume that you have something like this already). So instad of calling $.getJSON you can use $.get(url, callback); to do same thing. The code at the top of my answer will not dramatically change. Instead of geting JSON in response you will get string which is HTML.
----------- BOTTOM LINE -------
I prefer JSON since it can be easily extended later with other attributes, so there is no painful code changes later ;)
Problem here will be that when you return the full page and render it using the ajax response, your tinymce instance has not been shut down before.
In order to do this you can call this small piece of code before you render the ajax response:
tinymce.execCommand('mceRemoveControl',true,'editor_id');
In this case the editor should initialize correctly. You are not allowed to initialize a tinymce editor with the same id before shutting the first one down.
Strangely i ran into this problem yesterday. Following code should work, but YMMV. Trick is to use the correct steps in ajax events. I used the Regular TinyMCE and made use of the jQuery library already included.
Following goes into your tinyMCE initialization tinyMCE.init() . All of the below block should be outside the document.ready.
myTinyInit = {
//.......All essential keys/values ...........
setup : function(ed) {
ed.onChange.add(function( ed ) {
tinyMCE.triggerSave();
}) }
//.....................
};
// Init the tinyMCE
tinyMCE.init(myTinyInit);
This ensures the content is being saved regularly onto the textarea that holds the value. Next step is setting up the request events.
Normally tinyMCE mceAddControl before the ajax post and mceRemoveControl after the ajax success should do the trick. But I found that often does not work.
I used the form as the jQuery selector in my case.
jQuery( '.myForm' )
.find( 'textarea#myTextArea' )
.ajaxStart(function() {
// If you need to copy over the values, you can do it here.
// If you are using jQuery form plugin you can bind to form-pre-serialize event instead.
// jQuery( this ).val( tinyMCE.get( jQuery( this ).attr( 'id' )).getContent() );
}).ajaxSend( function() {
// ! - step 2
// My case was multiple editors.
myEds = tinyMCE.editors;
for( edd in myEds ) {
myEds[ eds ].remove();
}
// tinyMCE.get( 'myTextarea' ).remove();
// strangely mceRemoveControl didnt work for me.
// tinyMCE.execCommand( 'mceRemoveControl', false, jQuery( this ).attr('id'));
}).ajaxSuccess(function() {
// Now we got the form again, Let's put up tinyMCE again.
txtID = jQuery( this ).attr( 'id' );
// ! - step 3
tinyMCE.execCommand( 'mceAddControl', false, txtID );
// Restore the contents into TinyMCE.
tinyMCE.get( txtID ).setContent( jQuery( this ).val());
});
Problems i came across :
Using mceRemoveControl always gave me r is undefined error persistently.
If you get a blank tinyMCE editor, check the DOM whether the ID of the textarea is replaced with something like mce_02, this means that TinyMCE is being initialized again or something is wrong with the order. If so, the tinyMCE is duplicated with each save.
if you are new to JS, I recommend using jQuery with the form plugin, it might be easier for you. But do use the regular non-jquery tinyMCE, as it is well documented.
I fixed this problem by recalling the function after the ajax call. In this part of my ajax:
if (xmlhttp.readyState==4 && xmlhttp.status==200)
{
document.getElementById("Content").innerHTML=xmlhttp.responseText;
tinymce();
Now it works fine.

Trigger effect after changing src attribute of img tag

Im using this code to change the src attribute of an image tag (using prototype and scriptaculous):
new Effect.Fade("images",{afterFinish:
function()
{
$("detail").setAttribute("src", "img/02.jpg");
new Effect.Appear("images",{duration:0.8});
}
});
Where "images" is the container (a div) and "detail" is the img tag
The result is the current image fades, appears and then the new image suddenly shows.
My question is, how can i check the new image is fully loaded by the browser to trigger the Effect.Appear after?
Is there another way to do this?
Images have an onload event you can hook up to:
$("detail").onload = function()
{ do_stuff(); } // Remember to do this BEFORE changing the src
In my experience, it is a bit flaky sometimes (at times doesn't seem to get executed). It would be better to pre-load the image and allow this effect only after the full document has loaded (onload instead of dom:load or ready).
Replace
new Effect.Appear("images",{duration:0.8});
with
Event.observe("detail", 'load', function() {
new Effect.Appear("images",{duration:0.8});
Event.stopObserving('detail', 'load');
});
To tell the user that you are loading the image, you could set a css background to the image, with a spinnging circle or whatever suits.

Resources