Dynamic menu for a RichCombo box in CKEditor - javascript

I've written a plugin which adds a RichCombo box to my CKEditor. I want to be able to update the content in the ListBox within this RichCombo
Here's my code.
var merge_fields = [];
CKEDITOR.plugins.add('mergefields',
{
requires: ['richcombo'], //, 'styles' ],
init: function (editor) {
var config = editor.config,
lang = editor.lang.format;
// Gets the list of tags from the settings.
var tags = merge_fields; //new Array();
// Create style objects for all defined styles.
editor.ui.addRichCombo('tokens',
{
label: "Merge",
title: "title",
voiceLabel: "voiceLabel",
className: 'cke_format',
multiSelect: false,
panel:
{
css: [config.contentsCss, CKEDITOR.getUrl(CKEDITOR.skin.getPath('editor') + 'editor.css')],
voiceLabel: lang.panelVoiceLabel
},
init: function () {
// this.startGroup("mergefields");
for (var this_tag in tags) {
this.add(tags[this_tag], tags[this_tag], tags[this_tag]);
}
},
onClick: function (value) {
editor.focus();
editor.fire('saveSnapshot');
editor.insertText(value);
editor.fire('saveSnapshot');
}
});
}
});
Unfortunately this list is not update when merge_fields changes. Is there a way to reinitialize the plugin, or failing that remove it and re-add it with updated content?
Note Id prefer NOT to have to remove the entire editor and replace it, as this looks very unpleasant to the user
UPDATE
As requested, here's a jsfiddle to help
http://jsfiddle.net/q8r0dkc4/
In this JSFiddle, you'll see that the menu is dynamically created the first time it is accessed. It should should the checkboxes which are selected. However, every subsequent time it is accessed, it keeps the same values and is not updated. The only way to update it is to reinitialise the editor using the reinit button I have provided, but this causes the editor to disappear and reappear, so I don't want to have to do this.
200 points of a bounty to someone who can make the dropdown dynamically update EVERY TIME it is called.

How about using CKEditors custom events something like this ?
First get reference to CKEditors instance
var myinstance = CKEDITOR.instances.editor1;
Since the checkboxes are outside the scope of CKEditor add a change handler to checkboxes
$(':checkbox').change(function () {
myinstance.fire('updateList'); // here is where you will fire the custom event
});
Inside plugin definition add a event listener on the editor like this
editor.on("updateList", function () { // this is the checkbox change listener
self.buildList(); // the entire build is created again here
});
Instead of directly attaching events on checkboxes (which are outside the scope of CKEditor) inside the plugin, I am using CKEditor's custom events. Now the editor instance and checkboxes are decoupled.
Here is a DEMO
Hope this helps
Update
Option 2
Plugin's methods can directly be called like this
$(':checkbox').change(function () {
CKEDITOR.instances.editor1.ui.instances.Merge.buildList(); //this method should build the entire list again
});
Even though this seems very straightforward, I don't think it is completely decoupled. (But still works)
Here is a demo for option 2

I think I've got a fix for you.
In your richCombo's init function add this line:
$('input').on('click', rebuildList);
Pretty simple JQuery fix, but every time I click those input boxes it will rebuild the list.
Fiddle for proof: https://jsfiddle.net/q8r0dkc4/7/

Related

Multiple tippy.js instances on a single html element with different trigger options

I am trying to do something very similar (if not the same) to what's been asked in the question here (the answer didn't work for me, it seems like the answerer/creator didn't understand the question).
My goal is to have two tippy tooltip instances on a single html element with different trigger options:
one which will be triggered on mouseenter event (the default one, created using default tippy constructor without using the trigger option at all), and
one which will be triggered on click event (using trigger option manual and calling the tippy show() function afterwards).
This is how I did it:
var myelement = document.getElementById('myelementid');
// Default way of creating tippy tooltips
tippy(myelement, {
content: 'Shown on hover.'
});
// Creating a tooltip which will be triggered manually/programmatically
var mytippy = tippy(myelement, {
content: 'Shown on click.',
trigger: 'manual'
});
myelement.addEventListener("click", function() {
mytippy.show(300);
setTimeout(function(){ mytippy.hide(300); }, 1500);
});
And for some reason it won't show the manually triggered tooltip on that element at all. I get this exception: Uncaught TypeError: Cannot read property 'show' of undefined at HTMLImageElement.<anonymous> (refers to the tippy show() function). But when I delete one of them (tippy instances), the other one works perfectly.
It looks like Tippy.js uses an HTML attribute on elements for the tooltip (i.e., title or data-tippy). Since duplicate attributes would be invalid markup, a workaround might be to change the tooltip text when the element is clicked. Then, after the user moves away from the element, you could change the tooltip text back.
For example:
let myelement = document.getElementById('myelementid');
let to;
let text = "Show on hover."
let tip = tippy(myelement, {
content: text
});
myelement.addEventListener("click", handleClick);
myelement.addEventListener("mouseout", moveOut);
function moveOut () {
// when the user moves their mouse away from the button
// cancel showing the alternate tooltip text
clearTimeout(to);
// slight delay to prevent "flash" of original tooltip text
setTimeout(function () {
// set the tooltip text back to the original
tip.setContent(text);
}, 200);
}
function handleClick () {
tip.setContent("Click");
tip.show(300);
to = setTimeout(function() {
tip.hide(300);
}, 1500);
}
Here's a fiddle demonstrating this: https://jsfiddle.net/g6odqukr/
In the meantime I've came to an idea to put one tippy tooltip on the element myelement itself and another one on its parentNode element, and at the moment it appears to be the most simple solution (to understand and to write). It is simple as writing two totaly different tooltips. It requires that the parentNode element has the same size as the myelement itself for it to look like the tooltip actually belongs to the same element.
Here the code:
var myelement = document.getElementById('myelementid');
// Default way of creating tippy tooltips
tippy(myelement, {
content: 'Shown on hover.'
});
// Creating a tooltip which will be triggered on click
tippy(myelement.parentNode, {
content: 'Shown on click.',
trigger: 'click'
});
Here a little bit more advanced version: https://jsfiddle.net/zbhf48gn/

jQuery Text Editor (jqte), using change event

I am trying to write some code for change() event using jQuery Text Editor (jqte), I have two functions which give jqte functionality to textarea's
One for editors loaded with JavaScript, when clicking some elements in a page:
function onLoadEditor(){
jQuery(".comment-editor").jqte({
// some jqte params, such as fsize: false,indent: false...
change: function(){ observeEditor(); }
});
}
And other, generic function, for pages with one single editor
jQuery(function() {
jQuery(".comment-editor").jqte({
// some jqte params, such as fsize: false,indent: false...
change: function(){ observeEditor(); }
});
});
I want to access the id of the concrete textarea (all textareas in the page have an id) which has fired the change() event
How should I write observeEditor() function to achieve this? Or... how I should define the function in jqte change property?
After reading this jQuery blur event with ID and value I have solved it, with following code (simplified)
function onLoadEditor(){
jQuery(".comment-editor").each(function(idx, elem) {
jQuery(this).jqte({
// some jqte params, such as fsize: false,indent: false...
change: observeEditor(elem.id),
});
}
jQuery(function() {
onLoadEditor();
});
But now I have another problem...
As you can read in the original question, onLoadEditor() is called when clicking some elements in a page. Then another javascript function jsComment() is called, builds a form (with a textarea.comment-editor field included) and it is rendered this way
function jsComment(){
...
var form = '<div class="comments_wrapper ... ';
jQuery(form).insertAfter(some_element).fadeIn('fast');
onLoadEditor();
}
Problem is change() event is being fired only once, when form fades in, while the idea is the opposite, event should fire when user adds some text, not when appearing... Any tips?
UPDATE
After reading Event binding on dynamically created elements? I have solved it this way
function onLoadEditor(){
jQuery('.comment-editor').each(function(idx, elem) {
jQuery(this).jqte({
// some jqte params, such as fsize: false,indent: false...
});
jQuery(document).on('change',
jQuery('.comment-editor'),
function(){
observeEditor(elem.id);
}
);
});
}
jQuery(function() {
onLoadEditor();
});
Although finally I am not using change() event, as it was being fired constantly. Performing better with keyup() & paste(), for instance

jQuery UI - Slider unbinds after one use

I'm still a novice, so please go easy on me!
I'm making a JavaScript game. The game works fine, as do the basics of the user interface, like making menu selections or switching screens. But I'm also trying to implement jQuery UI sliders in one of my options menus, which is where I run into trouble.
I can only use the slider once, after which it becomes "stuck." It responds to mouseover - it'll highlight as though it's ready to scroll - but will not budge if I try to move it again.
So far, I've ruled out any problems with the build of jQuery/jQUI I'm using; the demo page works fine.
I have no idea what the problem might be, but I suspect it has something to do with the way I've put together my UI. The way my UI works is by creating a "View" object that contains pointers to a parent DOM element. I then use jQuery to construct its children and use the "loadElement" method to add it to the view's list of children elements:
function CView (parent, target, visible, jQElements) {
this.parent = parent;
this.visible = visible;
this.parentDisplay = parent.css("display");
this.parentPosition = parent.css("position");
this.elements = [];
for(element in jQElements) {
this.elements.push(element);
}
if (!this.visible) {
this.parent.css({ // Default to hidden state
"opacity": 0,
"display": "none"
});
}
this.parent.appendTo(target);
};
CView.prototype.loadElement = function(element) {
element.appendTo(this.parent);
this.elements.push(element);
return element;
};
All these elements can be shown and hidden together with a method call on the View object. Currently, hiding a view unbinds all event listeners in the elements of that view. I don't think this is the problem, since I get this problem immediately after creating a new view.
The issue, I think, might be in this code, which is for swapping views- Perhaps I'm unbinding some kind of document-level listener that jQUI uses?
var swapView = GameUI.swapView = function(view, callbacks) {
$(document).off(); // unbind key listeners
currentView && currentView.hideView(); // also unbinds event listeners
currentView = view;
view.showView(callbacks);
};
There's one more thing that might be relevant, the way I construct the slider and put it in:
var $volumeSlider = jQuery("<div/>", {
class: "options-menu-volume-slider"
});
var resetVolumeSlider = function () {
$volumeSlider.slider({
range: "min",
value: GameUI.options.volume,
min: 0,
max: 100
})
};
resetVolumeSlider();
If you need to see more code, let me know. I really am not sure what's going wrong here. Any and all help is appreciated. (Also, I don't know how to host my game online to demo it. It's basically just an HTML page that runs a bunch of JS.)
It turns out that this problem was caused by my call to $(document).off(), which I used to remove potentially dangling document-level keypress handlers. This had the unfortunate result of also destroying event handlers for jQuery UI.
In the future, my views will have keypresses bound at the parent div level with tab indices set for each div, so that I don't have to make the call to $(document).off() and can simply use hideView() to unbind.

JQuery register click events for treeview plugin on dynamic HTML

I'm using JQUery treeview plugin to display my data as a tree structure.
The problem is I'm generating my HTML dynamically from the JSON response objects.
This never works as expected. The elements are not getting registered for click events.
If I hardcode the dynamic HTML, I'm able to see the results as expected. I debugged and understood that JQuery registers the click events for existing HTML elements, but not dynamically formed elements. Please let me know how can I register them for click events.
function show(jsonResp) {
//Parsing the JSON response and building HTML on the fly.
$red = '';
for (var i = 0; i < treeElements.length; i++) {
$red = $red + treeElements[i];
}
$("#user-records").append($red);
$("#red").treeview({
animated: "fast",
collapsed: true,
unique: true,
persist: "cookie",
toggle: function () {
window.console && console.log("%o was toggled", this);
}
});
}
This treeview code is calling the functions to expand and collapse as per the user click events. All of these events are getting registered in treeview.js
this.find("div." + CLASSES.hitarea).click( toggler );
I debugged in chrome and checked that my new div's are getting registered with the click events. But nothing is happening when I click.
P.S : If I copy the console log of the HTML element generated, and create a different page, it works perfectly. Only problem is when dynamically adding the HTML code. I'm clueless where to register the click events for this newly added dynamic HTML code.
Did you this?
$(document).on("click", ".selector", function() {
// your code
});
With this code, you can bind events on generated content. Here's an example: http://jsfiddle.net/cB8Tf/
Change this
this.find("div." + CLASSES.hitarea).click( toggler );
to this
$(document).on('click', "div." + CLASSES.hitarea, toggler);
This will cause all future instances of "div." + CLASSES.hitarea to respect the click handler you've given.

preventing effects to be applied to the same object twice when adding objects with ajax

I'm having a little issue with an application I'm making. I have a page where the user edits a document via dragging modules into the page or "canvas" area.
http://thinktankdesign.ca/temp_img.jpg
When the page is loaded, javascript haves the modules collapsible (like above). However after the user drags in a new module the effect is applied again some new modules can collapse as well. here is the problem. each time a module loads the same effect gets applied to the modules that already can collapse. It ends up breaking their animations.
heres the code that gets executed on page load.
//make colapsible
$("h1.handle").click(function() {
var object = $(this);
v$(this).next().toggle("fast", colapsible_class(object));
vreturn false;
}).addClass("open");
and heres the code that gets executed in the creation of a module via ajax
function get_module(id){
var template = $('input[name=template]').val();
$.post(window.location.href, { template: template, module: id, mode: 'create' },
function(data){
$(data).insertBefore(".target_wrapper");
//enable deletion of module
$(".js_no_modules").slideUp("slow");
$(enable_module_deletion());
//show delete button
$("button[name=delete]").show();
//make colapsible
$("h1.handle").click(function() {
var object = $(this);
$(this).next().toggle("fast", colapsible_class(object));
return false;
}).addClass("open");
}
);
}
I need a solid way of preventing the toggle effect to be applied to the same module twice
Use jQuery 1.3 live events instead.
//make colapsible
$("h1.handle").live("click", function() {
var object = $(this);
v$(this).next().toggle("fast", colapsible_class(object));
vreturn false;
}).addClass("open");
and then eliminate the click declaration in the second block of code, changing it to $("h1.handle").addClass("open");
Live events bind all current and future matching elements with an event.
In your Ajax success handler try the following:
//make collapsible
$("h1.handle:not(.open)").click(function() {
var object = $(this);
$(this).next().toggle("fast", colapsible_class(object));
return false;
}).addClass("open");
The best way to solve your problem is, instead of using $("h1.handle") on the AJAX callback, go for $(data).find("h1.handle"). Something like,
var x = $(data);
x.insertBefore(...);
/* your other code */
x.find('h1.handle').click(...).addClass(...);
Like that, only the newly added items will have the event bounded. The already present ones will not be touched.
If we want to answer your question instead of just solving your problem, then we have several alternatives, such as:
store, in your objects, that the onclick event handler has been set so that you don't set it twice
always bind the onclick event, but always unbind it first
use jQuery's live events and the addClass open only on the newly created items.
IMO, the first one is the easiest. You can accomplish it by using jQuery's data(). Then you could do something like:
$("h1.handle").each(function() {
var me = $(this);
// if already has click handler, don't do anything
if (me.data('click_set') != null) { return true; }
// otherwise, store the data and bind the click event
me.data('click_set', true).click(function() {
/* the code you already have on the click handler */
}).addClass('open');
}
The second alternative involves storing the function that you pass inline to the click event binder in a variable, and then using jQuery's unbind to disable it.

Categories