TinyMCE -> Cannot read property 'setAttribute' of null - javascript

so I'm making an MVC site that needs a HTML input box.
I have a text area that loads from an ajax dialog window. I understand that TinyMCE needs me to remove the control when i hide the dialog, that is fine.
I cannot however get the editor to load at all. I am using version 4.1.9 (2015-03-10) with the jquery module.
i.e. both tinymce.jquery.js & jquery.tinymce.min.js
Once the dialog window has opened i call this;
$("textarea").tinymce({
// General options
mode: "textareas",
theme: "modern",
// Theme options
menubar: false,
toolbar: "bold,italic,underline,|,bullist,numlist",
statusbar: false,
init_instance_callback: function (editor) {
console.log("tinymce init: " + editor.id);
}
});
But i get an exception in the javascript in the following method, it appears self.ariaTarget is undefined leading to the line starting elm.setAttribute failing because elm is null.
I don't understand what i have done wrong.
/**
* Sets the specified aria property.
*
* #method aria
* #param {String} name Name of the aria property to set.
* #param {String} value Value of the aria property.
* #return {tinymce.ui.Control} Current control instance.
*/
aria: function(name, value) {
var self = this, elm = self.getEl(self.ariaTarget);
if (typeof value === "undefined") {
return self._aria[name];
} else {
self._aria[name] = value;
}
if (self._rendered) {
elm.setAttribute(name == 'role' ? name : 'aria-' + name, value);
}
return self;
},
Thanks for the help.
Mark
Edit:
I have been following this jsfiddle http://jsfiddle.net/thomi_ch/m0aLmh3n/19/ and instead of loading tinymce in with the document, it loads it from a url when it initialises (see below). I've changed my code to use the same script_url as the example and it works to render the editor (missing icons etc as the css cant be found) but this implies to me that there is something wrong with the version of my tinymce.jquery.js file.
$('textarea').tinymce({
script_url : 'http://demo.nilooma.com/system/plugins/tinymce/4.1.6/tiny_mce/tinymce.gzip.php',
toolbar: 'link',
plugins: 'link',
forced_root_block : '',
init_instance_callback: function(editor) {
console.log('tinymce init: '+editor.id);
}
});
I have tried both tinymce.jquery.js & tinymce.js from versions 4.1.9 & 4.1.6 as the jsfiddle uses and all the combinations give me the same error.
Is it possible that there is an incompatibility with another library?

I found a resolution to the issue. I believe it was caused by trying to initialise the element multiple times, and after that I also found a flaw that the element wasn't displaying the editor as it was initialised when it was hidden.
The code I've used to initialise the bootstrap modal is this;
$("#myModal").modal({
keyboard: true
}, "show");
//Bind open
$("#myModal").on("show.bs.modal", function () {
$(document).trigger("DialogLoaded");
});
//Bind close
$("#myModal").on("hidden.bs.modal", function () {
$(document).trigger("DialogClosed");
});
Then I listen to the events on the document;
tinyMCE.init({
// General options
mode: "textareas",
theme: "modern",
// Theme options
menubar: false,
toolbar: "bold,italic,underline,|,bullist,numlist",
statusbar: false,
init_instance_callback: function(editor) {
console.log("tinymce init: " + editor.id);
}
});
$(document).on("DialogLoaded", function () {
var textAreas = $("textarea", $("#myModal"));
for (var i = 0; i < textAreas.length; i++) {
//Check if element already has editor enabled
if (tinymce.get(textAreas[i].id)) {
//Remove existing editor
tinyMCE.execCommand("mceRemoveEditor", false, textAreas[i].id);
}
//Add editor
tinyMCE.execCommand("mceAddEditor", false, textAreas[i].id);
}
});
$(document).on("DialogClosed", function () {
//Remove all editors in dialog
var textAreas = $("textarea", $("#myModal"));
for (var i = 0; i < textAreas.length; i++) {
//Check if element already has editor enabled
if (tinymce.get(textAreas[i].id))
tinyMCE.execCommand("mceRemoveEditor", false, textAreas[i].id);
}
});
I guess there are several points to remember;
Only load tinyMCE on visible elements
Initialise tinyMCE first
Ensure its only loaded each element once
Make sure each textarea has a unique id (and remove it once hidden)
I hope this helps someone else having any trouble with TinyMCE.
Thanks,
Mark.

Related

Open Ace.js code editor and select content on click

I have a page containing multiple instances of ace.js editors, all on read-only, displaying code.
I wish to open the editor like so and highlight its content to allow my user to easily copy it, like so:
My current approach does not show the wanted result, it does open an editor with the code inside of it BUT its height is not at the size of its content:
Heres's how I do it:
function openCodeEditor(event, domElement, language) {
event.preventDefault();
getInstance().then((ace) => {
ace.edit(domElement, {
mode: `ace/mode/${language}`,
theme: 'ace/theme/monokai',
startLineNumber: 1,
trim: true,
});
});
}
// STARTS HERE
// Called on page load to highlight each block of code
function syntaxHighlight(domElement, language, showGutter) {
return getInstance().then((ace) => {
ace.require('ace/ext/static_highlight')(domElement, {
mode: `ace/mode/${language}`,
theme: 'ace/theme/monokai',
startLineNumber: 1,
showGutter,
trim: true,
});
// Catch clicks here, and open the editor for selection
domElement.addEventListener('click', (event) =>
openCodeEditor(event, domElement, language)
);
});
}
Does anyone of you have recommendations or perhaps my approach is faulty?
Thank you !
You can set the height of editor to be same as the height of replaced element.
var editor = ace.edit(null, {
value: domElement.textContent,
theme: 'ace/theme/monokai',
mode: `ace/mode/${language}`,
});
editor.container.style.height = domElement.clientHeight + "px"
domElement.replaceWith(editor.container)
but if the goal is simply to allow copying, selecting contents of domElement should work too.

Set Kendo UI Window values globally

I'm working with a lot of Kendo UI windows. Is there some way to specify default values somehow globally? Or maybe a more realistic version, can I create some parent with predefined values and then just overwrite the values I need to change?
For example, I want the same error behavior and a modal parameter for all of the windows, so I would like to do something like:
$("#parentWindow").kendoWindow({
modal: true,
error: function () {
this.close();
new Notification().error();
}
});
And then use the parent window as a base for new windows:
$("#newWindow").kendoWindow({
title: "This window should have the options (modal and error) of the parentWindow",
}).??getTheRestOfTheValuesFromParent()??;
Or rewrite some parameter:
$("#newWindow2").kendoWindow({
modal: false,
title: "A window with overwritten modal parameter",
}).??getTheRestOfTheValuesFromParent()??;
Is it somehow possible to achieve this, is there any possibility of something like C# inheritance?
Maybe it's a stupid question, but I'm not so familiar with JS.
I highly encourage you to create your own wrapper code over all - or at least those more complex - kendo widgets. My team has been doing it for years in a project we use kendo for everything and we are having very positivelly results. The main purpose is what you need: a global behaviour. If after thousand windows coded over your project, you need to change them all, just change the wrapper. It's just a simple jQuery function:
$.fn.MyWindow = function(options) {
var $target = $(this);
var widget = {
_defaultOptions: {
actions: ["Minimize", "Maximize", "Close"],
visible: false,
width: 400,
height: 400,
modal: true
},
_options: options || {},
_target: $target,
_widget: null,
_init: function() {
this._manageOptions();
this._createWidget();
return this;
},
_manageOptions: function() {
// Here you can perform some validations like displaying an error when a parameter is missing or whatever
this._options = $.extend(this._options, this._defaultOptions);
},
_createWidget: function() {
this._widget = this._target.kendoWindow(this._options).data("kendoWindow");
// Create here some behaviours that the widget doesn't haves, like closing the window when user click the black overlay
if (this._options.closeOnOverlayClick) {
$('body').off('click', '.k-overlay').on('click', '.k-overlay', function() {
this._widget.close();
}.bind(this));
}
},
Show: function(center) {
if (center) {
this._widget.center();
}
this._widget.open();
}
};
return widget._init();
};
var wnd = $("#wnd").MyWindow({
title: "My first window",
closeOnOverlayClick: true // Your own parameter
});
// Now you work with your own functions:
wnd.Show(true);
Demo.
There are so many customizations, like your own events - some of those kendo's widgets doesn't haves - etc..
I will just add that there is an article(here) about creating custom Kendo widgets where you can find more information about the specifics of different scenarios that may be implemented.
Ι had a case like yours with kendo windows, kendo grids and kendo dropdownlists. For that I created HtmlHelpers for all my elements and called them when I needed to. Since you are using kendo asp.net-mvc I would recommend to look at this way.
public static WindowBuilder GlobalKendoWindow(this HtmlHelper helper)
{
return helper.Kendo().Window()
.Draggable()
.Animation(true)
.Visible(false)
.AutoFocus(true)
.Modal(true)
.Scrollable(true)
.HtmlAttributes(new { #class = "atn-modal-container" })
.Actions(actions => actions.Minimize().Close())
.Deferred();
}
and render it in my Html like this
#(Html.GlobalKendoWindow()
.Name("addCandidateDialog")
.Title(Html.GetResource(cps, "AddCandidateDialogTitle"))
.LoadContentFrom("AddCandidate", "Candidate")
.Events(events => events.Open("athena.addCandidacy.onAddCandidateOpen").Close("athena.addCandidacy.onAddCandidateClose"))
)

TinyMCE Enable button while in read only mode

I have a TinyMCE 4.x instance where the text should be in read only mode. But I still have some buttons that I want to have enabled. For example, one button could provide a character count for the part of the text I've selected.
But when I turn on read only mode for TinyMCE all buttons are disabled. Can I enable just my buttons while still retaining read only mode?
It's probably too late for you but other people may pass by here.
I came up by writing this function
function enableTinyMceEditorPlugin(editorId, pluginName, commandName) {
var htmlEditorDiv = document.getElementById(editorId).previousSibling;
var editor = tinymce.get(editorId);
var buttonDiv = htmlEditorDiv.querySelectorAll('.mce-i-' + pluginName.toLowerCase())[0].parentElement.parentElement;
buttonDiv.className = buttonDiv.className.replace(' mce-disabled', '');
buttonDiv.removeAttribute('aria-disabled');
buttonDiv.firstChild.onclick = function () {
editor.execCommand(commandName);
};
}
It does the trick in 2 steps:
make the button clickable (remove mce-disabled CSS class and remove the aria-disabled property)
assign the good command to the click event
And in my editor init event I call the function.
editor.on('init', function () {
if (readOnly) {
editor.setMode('readonly');
enableTinyMceEditorPlugin(htmlEditorId, 'preview', 'mcePreview');
enableTinyMceEditorPlugin(htmlEditorId, 'code', 'mceCodeEditor');
}
});
Current version of TinyMCE for which I wrote this code is 4.4.3. It may break in a future version, specifically about the selectors to get and modify the good HTML elements.
Command identifiers can be found at this page otherwise you can also find them under tinymce\plugins\PluginName\plugin(.min).js
Here is a simple way to enable your custom toolbar button and attach a click event handler inside a read only TinyMCE editor using JQUERY:
//Initialize read only Tinymce editor so that Lock button is also disabled
function initReadOnlyTinyMCE() {
tinymce.init({
selector: '#main'
, toolbar: 'myLockButton'
, body_class: 'main-div'
, content_css: 'stylesheets/index.css'
, readonly: true
, setup: function (readOnlyMain) {
readOnlyMain.addButton('myLockButton', { //Lock button is disabled because readonly is set to true
image: 'images/lock.png'
, tooltip: 'Lock Editor'
});
}
});
}
function displayReadOnlyTinyMCEwithLockButtonEnabled() {
var edContent = $('main').html();
$("#main").empty();
initReadOnlyTinyMCE(true);
tinyMCE.activeEditor.setContent(edContent);
//enable the lock button and attach a click event handler
$('[aria-label="Lock Editor"]').removeClass("mce-disabled");
$('[aria-label="Lock Editor"]').removeAttr("aria-disabled");
$('[aria-label="Lock Editor"]').attr("onclick", "LockEditor()");
}
function LockEditor() {
alert("Tiny mce editor is locked by the current user!!");
//Write your logic to lock the editor...
}
I couldn't find an easy way to do this. The simplest way is to remove the contenteditable attribute from the iframe body instead and substitute a read only toolbar set. It also means that people will still be able to copy content from the editor.
$("iframe").contents().find("body").removeAttr("contenteditable");
How about this :
editor.addButton('yourButton', {
title: 'One can Enable/disable TinyMCE',
text: "Disable",
onclick: function (ee) {
editor.setMode('readonly');
if($(ee.target).text() == "Disable"){
var theEle = $(ee.target).toggle();
var edit = editor;
var newBut = "<input type='button' style='opacity:1;color:white; background-color:orange;' value='Enable'/>";
$(newBut).prependTo($(theEle).closest("div")).click(function(e){
edit.setMode('design');
$(e.target).remove();
$(theEle).toggle();
});
}
}
});
You can try to run the code below:
$("#tinymce").contentEditable="false";
if you have more than one editors, you can use their id like below
$("#tinymce[data-id='idOfTheEditor']").contentEditable="false";

Trying to make a widget with Non-Native , qooxdoo-Themed scrollbars for qx.ui.embed.Html

I am trying to implement qx.ui.embed.Html with Qooxdoo themed scroll-bars.
I wanted to implement it inside vitrual list .
All i need is a qx.ui.embed.Html widget with Custom (qx themed) scroll-bars because it is so ugly in native scrolls.
Here is my test :
http://tinyurl.com/kcouvj2
It works in playground.
But I tried to make it a widget:
(i am not an expert in doing so):
qx.Class.define("phwabe.view.ChatView.PostItem",
{
extend : qx.ui.container.Scroll,
properties :
{
/** Any text string which can contain HTML, too */
html :
{
check : "String",
apply : "_applyHtml",
event : "changeHtml",
nullable : true
}
},
members :
{
construct : function()
{
this.base(arguments)
},
_createChildControlImpl : function(id)
{
var control;
switch(id)
{
//case "icon":
// control = new qx.ui.basic.Image(this.getIcon());
// control.setAnonymous(true);
// this._add(control, {row: 0, column: 0, rowSpan: 2});
// break;
case "html":
control = new qx.ui.embed.Html()
control.setAllowGrowX(true);
control.setOverflowY('hidden');
control.setAllowShrinkY(false)
this.add(control)
}
},
_applyHtml : function(value, old)
{
var post = this.getChildControl("html");
// Workaround for http://bugzilla.qooxdoo.org/show_bug.cgi?id=7679
// Insert HTML content
post.setHtml(value||"");
}
}
})
But that is failing hard with :
Error: Exception while creating child control 'html' of widget
phwabe.view.ChatView.PostItem[446-0]: Unsupported control: pane
I am doing it wrong obviously. Any Proper way of implementing it in qooxdoo?
You need to return the created control - and if none matched (in your case "pane"), return the base class method:
// overridden
_createChildControlImpl : function(id)
{
var control;
...
return control || this.base(arguments, id);
}
Here is a changed playground example: http://tinyurl.com/loljtz6
You need to add a mechanism to adjust the content size of the qx.ui.embed.Html widget automatically if the height is unknown since there is no automatic resizing.

jQuery custom plugin - setting private options for multiple instances

Hi folks!
I'm currently developing a client project where I saw myself doing the same javascript code over and over again. So I though it would be useful to wrap the logic inside a custom jQuery plugin. I've achieved it for a single instance of the plugin, but for multiple instances, I think I'm having a problem with the properties of each instance overwriting each other.
Well, let's get to the code! Here is the currently code that I have for the plugin:
// RESPONSIVE MENU ===========================//
// wrapper for a responsive menu plugin, //
// made by Favolla Comunicação //
//============================================//
/* INSTRUCTIONS
Apply the plugin on the main wrapper of the responsive menu. For example:
$(#menu).responsiveMenu($(#trigger));
The plugin just toggles the classes, leaving the effects and layout for the css
CONFIG
- trigger: the selector of the button that will activate the menu (required)
- activeClass: class name to be injectet when the toggle is activated (default: active)
- submenuTrigger: the selector of the buttons that will activate the submenus, if the menu will have another levels (default: $('sub-toggle'))
- submenu: the selector of the submenus (default: $('.submenu'))
- submenuActiveClass: class name to be injected on the submenus when they are activated (default: open)
- breakpoint: max window whidth where the plugin will work (default: 720)
- timeOut: time in milissegundos to limite the onResize repeat. (default: 100)
- moveCanvas: option to activate the "off canvas" pattern or not (just puts a class on the main elements of the page). (default: false)
- canvas: class name of the elements that build the "canvas" (default: null)
*/
;(function ( $, window, document, undefined ) {
$.fn.responsiveMenu = function(settings){
var config = {
'trigger': '',
'activeClass': 'active',
'submenuTrigger': $('.sub-toggle'),
'submenu': false,
'submenuActiveClass': 'open',
'breakpoint': 720,
'timeOut': 100,
'moveCanvas': false,
'canvas': '',
};
if (settings){$.extend(config, settings);}
// plugin variables
var mTrigger,
menu = $(this),
active = config.activeClass,
button = config.trigger,
bpoint = config.breakpoint,
submTrigger = config.submenuTrigger,
submenu = config.submenu,
submenuActive = config.submenuActiveClass;
canvasOn = config.moveCanvas;
canvas = config.canvas;
time = config.timeOut;
return this.each(function () {
if($(window).width() > bpoint){
mTrigger = false;
} else {
mTrigger = true;
}
onChange = function(){
clearTimeout(resizeTimer);
var resizeTimer = setTimeout(function(){
if($(window).width() > bpoint){
mTrigger = false;
menu.removeClass(active);
button.removeClass(active);
if(canvasOn){
canvas.removeClass(active);
}
} else {
mTrigger = true;
}
}, time);
}
$(window).bind('resize',onChange);
$(document).ready(onChange);
button.click(function(){
if(mTrigger) {
menu.toggleClass(active);
button.toggleClass(active);
if(canvasOn){
canvas.toggleClass(active);
}
}
});
if(submenu){
var submenuClass = '.' + submenu.prop('class');
// toggle for the submenus
submTrigger.click(function(){
if(mTrigger) {
if($(this).hasClass(active)){
submTrigger.removeClass(active);
submenu.removeClass(submenuActive);
} else {
submTrigger.removeClass(active);
$(this).addClass(active);
submenu.removeClass(submenuActive);
$(this).next(submenuClass).addClass(submenuActive);
}
}
});
}
});
}
})( jQuery, window, document );
And then, when I want to apply the plugin, I make like this:
$('#menu-wrapper').responsiveMenu({
trigger: $('#nav-toggle'),
submenu: $('.submenu'),
submenuTrigger: $('.submenu-toggle'),
moveCanvas: true,
canvas: $('.canvas'),
breakpoint: 862
});
$('#search').responsiveMenu({
trigger: $('#search-toggle'),
breakpoint: 862
});
The main issue here is when I set to instances of the responsiveMenu();, it seems like some options are overwriting. For example, the first instance set moveCanvas to true, and it works, but when I leave it blank for the second instance (which leaves the moveCanvas option set to false for this element, this options for the first instance don't work anymore.
I know that maybe I'm not following the jQuery plugin best pratices, and I even read something about the jQuery Boilerplate, which looks great, but I'm not an advanced javascript developer, so there a lot of things that I could do better, but I just don't now how to do.
Anyway, any help with this issue (and opinions about the plugin) will be very welcome!
var mTrigger,
menu = $(this),
active = config.activeClass,
button = config.trigger,
bpoint = config.breakpoint,
submTrigger = config.submenuTrigger,
submenu = config.submenu,
Until here you were doing correct.
submenuActive = config.submenuActiveClass;
canvasOn = config.moveCanvas;
canvas = config.canvas;
time = config.timeOut;
Then, you introduced a semicolon - which leads to the further assignments (canvasOn, canvas and time) not being part of the var statement any more. They're no variable declarations, you assign to global variables here - and that way you overwrite the settings of the first plugin instance.
Change every but the last semicolon to commata.
You need to encapsulate your settings in a class or closure and store them for each element the plugin is called on.
return this.each(function () {
...
$(this).data('some-key', settings);
...
});
If you'd like to learn about jQuery plugin authoring, they have an article on it here http://learn.jquery.com/plugins/basic-plugin-creation/

Categories

Resources