When a user enters a table on the Ckeditor, I want to wrap a div around it with a class but I can't find a way to get this table HTML element. What is the best way to go about it?
I've tried creating a plugin to extend the table dialog onOk function (see code). This gives me all the properties from the table dialog but I don't want to have to create the whole table element again with all the properties as I don't want to re-write the existing table plugin.
I just need to get the code this plugin adds and wrap it in a div.
I thought about doing it in my projects javascript, when page loads, get all tables and wrap it in a div. However, this doesn't seem like the best way to do it at all. I thought there must be a way via ckeditor?
CKEDITOR.plugins.add( 'responsivetables', {
// The plugin initialization logic
init: function(editor) {
vsAddResponsiveTables(editor);
}
});
function vsAddResponsiveTables(editor){
CKEDITOR.on( 'dialogDefinition', function( ev ) {
var dialogName = ev.data.name;
var dialogDefinition = ev.data.definition;
if ( dialogName == 'table') {
addTableHandler(dialogDefinition, editor);
}
});
}
function addTableHandler(dialogDefinition, editor){
dialogDefinition.onOk = function (a) {
// get table element and wrap in div?
}
}
I found the answer so for anyone else that needs it, this is what I did:
I used the insertElement event instead of when dialog was closed, only doing what I need if its a table that's being added.
// Register the plugin within the editor.
CKEDITOR.plugins.add( 'responsivetables', {
// The plugin initialization logic goes inside this method.
init: function(editor) {
vsAddResponsiveTables(editor);
}
});
function vsAddResponsiveTables(editor){
// React to the insertElement event.
editor.on('insertElement', function(event) {
if (event.data.getName() != 'table') {
return;
}
// Create a new div element to use as a wrapper.
var div = new CKEDITOR.dom.element('div').addClass('table-scroll');
// Append the original element to the new wrapper.
event.data.appendTo(div);
// Replace the original element with the wrapper.
event.data = div;
}, null, null, 1);
}
To the previous answer by 'gemmalouise' need to add one more line of code
CKEDITOR.editorConfig = function( config ) {
config.extraPlugins = 'responsivetables';
}
Otherwise it will not work (I cannot indicate this in the comment, because lack of 50 reputation).
And more compact code of this fuctional:
CKEDITOR.plugins.add('responsivetables', {
init: function (editor) {
editor.on('insertElement', function (event) {
if (event.data.getName() === 'table') {
var div = new CKEDITOR.dom.element('div').addClass('table-scroll'); // Create a new div element to use as a wrapper.
div.append(event.data); // Append the original element to the new wrapper.
event.data = div; // Replace the original element with the wrapper.
}
}, null, null, 1);
}
});
Related
I'd like a div to appear for a short duration of time and then go away.
So, I dynamically create the div on the click of a button, and then after some work is done, I'd like it to be removed from the DOM.
So, I set up a timer like so:
var contentJoinTab = $("#...");
var divIdSubscribePleaseWait = "div-subscribe-pleasewait";
btnSubscribe.on("click", function (event) {
displaySubscriptionWait();
postMailingListSubscription();
});
function displaySubscriptionWait() {
var s = `<div id = ${divIdSubscribePleaseWait} class = "${classMailingListPleaseWait}">Please wait...</div>`;
contentJoinTab.append(s);
};
function postMailingListSubscription() {
// fake for now
window.setTimeout(function() {
removeSubscriptionWait();
}, 4000);
};
function removeSubscriptionWait() {
contentJoinTab.parent(`${divIdSubscribePleaseWait}`).remove();
// I've even tried the following to no avail
// $(`${divIdSubscribePleaseWait}`).remove();
// contentJoinTab.find(`${divIdSubscribePleaseWait}`).remove();
};
However, even though there is no error in the call to the remove() method, the div I am trying to remove remains in the DOM and is visible.
I do understand event propagation but my understanding is that that's not relevant here. That would have been relevant if I wanted to attach an event to the click (or any other event) of the dynamically created div or any of its parent.
You may be missing # when calling removeSubscriptionWait And also need "" for id = ${divIdSubscribePleaseWait}.
Please see changes below in case it isn't clear:
function displaySubscriptionWait() {
var s = `<div id = "${divIdSubscribePleaseWait}" class = "${classMailingListPleaseWait}">Please wait...</div>`;
contentJoinTab.append(s);
};
function postMailingListSubscription() {
// fake for now
window.setTimeout(function() {
removeSubscriptionWait();
}, 4000);
};
function removeSubscriptionWait() {
contentJoinTab.parent(`#${divIdSubscribePleaseWait}`).remove();
// I've even tried the following to no avail
$(`#${divIdSubscribePleaseWait}`).remove();
};
You can do that by setting outerHTML of that div to null
function addDiv() {
let s = "<div id='tempDiv'>Temporary Div</div>"
let root = document.getElementById("root")
root.innerHTML += s;
}
function removeDiv() {
let theDiv = document.getElementById("tempDiv");
theDiv.outerHTML=""
}
addDiv()
setTimeout(removeDiv,2000)
<div id=root>
</div>
You have appended that div as a child of contentJoinTab but when you go to remove it you are looking for it as being parent of contentJoinTab
You also need to add the ID prefix in selector
try changing
contentJoinTab.parent(`${divIdSubscribePleaseWait}`).remove();
To
contentJoinTab.find(`#${divIdSubscribePleaseWait}`).remove();
update removeSubscriptionWait function :
function removeSubscriptionWait() {
contentJoinTab.find('#'+`${divIdSubscribePleaseWait}`).remove();
};
I am using $.observable(array).insert() to append items to a list. This is updating my view as it should: new list items are rendered to the DOM. However, I would like to issue a click event on the new DOM node (I'm relying on the event to add a class to expand the item and attach another listener to the body so the area can be closed).
I have tried both
$.observable(_model.leadTimes).insert(leadTime);
$leadTimes.find('.lead-time-data').last().find('.start-editing').click();
...and
function watchLeadTimes() {
var changeHandler = function (ev, eventArgs) {
if (eventArgs.change === 'insert') {
$leadTimes.find('.lead-time-data').last().find('.start-editing').click();
}
};
$.observe(_model.leadTimes, changeHandler);
}
And neither of them worked, however, if I wrap the jQuery method in a setTimout, like setTimeout(function () { $leadTimes.find('.lead-time-data').last().find('.start-editing').click(); }, 400);, it does work, leading me to believe this is an issue of timing with the DOM render somehow not finishing before my jQuery click() method is invoked.
Since the odds are decent that you will see this, Borris, thank you for the library and all that you do! I think jsViews is an excellent middle ground between the monolithic frameworks out there and plain old jQuery noodling!
Edit 02/09/17
It turns out my issue was overlapping click events--I was inadvertently handling a click to deselect my element immediately after it was selected. However I took the opportunity to rewrite things to use a more declarative approach following Borris' linked example.
Now in my template I am using a computed observable, isSelected to toggle the .editing class:
{^{for leadTimes}}
<tr class="lead-time-data" data-link="class{merge:~isSelected() toggle='editing'}">
<span>{^{:daysLead}}</span>
</tr>
{{/for}}
And this JS:
function addNewLeadTimeClickHandler() {
var onNewLeadTimeClick = function () {
e.stopPropagation(); // this is what I was missing
var leadTime = {
daysLead: 1,
description: ''
};
$.observable(_model.activityMapping.leadTimes).insert(leadTime);
selectLeadtime(_model.activityMapping.leadTimes.length -1);
}
$leadTimes.on('click', '.add', onNewLeadTimeClick);
}
function selectLeadtime(index) {
var addStopEditingClickHandler = function () {
var onClickHandler = function (event) {
if ($(event.target).closest('tr').hasClass('editing')) {
setHandler();
return;
}
selectLeadtime(-1)
};
function setHandler() {
var clickEvent = 'click.ActivityChangeRequestDetailController-outside-edit-row';
$('html:not(.edit)').off(clickEvent).one(clickEvent, onClickHandler);
};
setHandler();
}
if (_model.selectedLeadtimeIndex !== index) {
$.observable(_model).setProperty('selectedLeadtimeIndex', index)
addStopEditingClickHandler();
}
}
function isSelected() {
var view = this;
return this.index === _model.selectedLeadtimeIndex;
}
// isSelected.depends = ["_model^selectedLeadtimeIndex"];
// for some reason I could not get the above .depends syntax to work
// ...or "_model.selectedLeadtimeIndex" or "_model.selectedLeadtimeIndex"
// but this worked ...
isSelected.depends = function() {return [_model, "selectedLeadtimeIndex"]};
The observable insert() method is synchronous. If your list items are rendered simply using {^{for}}, then that is also synchronous, so you should not need to use setTimeout, or a callback. (There are such callbacks available, but you should not need them for this scenario.)
See for example http://www.jsviews.com/#samples/editable/tags (code here):
$.observable(movies).insert({...});
// Set selection on the added item
app.select($.view(".movies tr:last").index);
The selection is getting added, synchronously, on the newly inserted item.
Do you have other asynchronous code somewhere in your rendering?
BTW generally you don't need to add new click handlers to added elements, if you use the delegate pattern. For example, in the same sample, a click handler to remove a movie is added initially to the container "#movieList" with a delegate selector ".removeMovie" (See code). That will work even for movies added later.
The same scenario works using {{on}} See http://www.jsviews.com/#link-events: "The selector argument can target elements that are added later"
So I'm fairly novice with jquery and js, so I apologise if this is a stupid error but after researching I can't figure it out.
So I have a list of data loaded initially in a template, one part of which is a dropdown box that lets you filter the data. My issue is that the filtering only works once? As in, the .change function inside $(document).ready() only fires the once.
There are two ways to reload the data, either click the logo and reload it all, or use the search bar. Doing either of these at any time also means the .change function never fires again. Not until you refresh the page.
var list_template, article_template, modal_template;
var current_article = list.heroes[0];
function showTemplate(template, data)
{
var html = template(data);
$("#content").html(html);
}
$(document).ready(function()
{
var source = $("#list-template").html();
list_template = Handlebars.compile(source);
source = $("#article-template").html();
article_template = Handlebars.compile(source);
source = $("#modal-template").html();
modal_template = Handlebars.compile(source);
showTemplate(list_template,list);
$(".articleButton").click(function()
{
var index = $(this).data("id");
current_article = list.heroes[index];
showTemplate(article_template,current_article);
$('.poseThumb').click(displayModal);
});
$("#classFilter").change(function()
{
console.log("WOW!");
var classToFilter = this.value;
var filteredData =
{
heroes: list.heroes.filter(function(d)
{
if (d.heroClass.search(classToFilter) > -1)
{
return true;
}
return false;
})
};
console.log(filteredData);
showTemplate(list_template,filteredData);
$(".articleButton").click(function()
{
var index = $(this).data("id");
current_article = filteredData.heroes[index];
showTemplate(article_template,current_article);
$('.poseThumb').click(displayModal);
});
});
$("#searchbox").keypress(function (e)
{
if(e.which == 13)
{
var rawSearchText = $('#searchbox').val();
var search_text = rawSearchText.toLowerCase();
var filteredData =
{
heroes: list.heroes.filter(function(d)
{
if (d.name.search(search_text) > -1)
{
return true;
}
return false;
})
};
console.log(filteredData);
showTemplate(list_template,filteredData);
$(".articleButton").click(function()
{
var index = $(this).data("id");
current_article = filteredData.heroes[index];
showTemplate(article_template,current_article);
$('.poseThumb').click(displayModal);
});
}
});
$("#logo").click(function()
{
showTemplate(list_template,list);
$(".articleButton").click(function()
{
var index = $(this).data("id");
current_article = list.heroes[index];
showTemplate(article_template,current_article);
$('.poseThumb').click(displayModal);
});
});
//$("#logo").click();
});
function displayModal(event)
{
var imageNumber = $(this).data("id");
console.log(imageNumber);
var html = modal_template(current_article.article[0].vicPose[imageNumber]);
$('#modal-container').html(html);
$("#imageModal").modal('show');
}
I should note two things: first, that the search bar works perfectly, and the anonymous function inside both of them is nearly identical, and like I said, the filtering works perfectly if you try it after the initial load. The second is that the same problem occurs replacing .change(anonymous function) with .on("change",anonymous function)
Any help or advice would be greatly appreciated. Thanks.
I agree with Fernando Urban's answer, but it doesn't actually explain what's going on.
You've created a handler attached to an HTML element (id="classFilter") which causes part of the HTML to be rewritten. I suspect that the handler overwrites the HTML which contains the element with the handler on it. So after this the user is clicking on a new HTML element, which looks like the old one but doesn't have a handler.
There are two ways round this. You could add code inside the handler which adds the handler to the new element which has just been created. In this case, that would mean making the handler a named function which refers to itself. Or (the easier way) you could do what Fernando did. If you do this, the event handler is attached to the body, but it only responds to clicks on the #classFilter element inside the body. In other words, when the user clicks anywhere on the body, jQuery checks whether the click happened on a body #classFilter element. This way, it doesn't matter whether the #classFilter existed when the handler was set. See "Direct and delegated events" in jQuery docs for .on method.
Try to use some reference like 'body' in the event listeners inside your DOM like:
$('body').on('click','.articleButton', function() {
//Do your stuff...
})
$('body').on('click','#classFilter', function() {
//Do your stuff...
})
$('body').on('keypress','#searchbox', function() {
//Do your stuff...
})
$('body').on('click','#logo', function() {
//Do your stuff...
})
This will work that you can fire it more than once.
So right now I have a popup div (this is for a chrome extension) and right now the div pops up just fine.
What I would like to do is to inject this layout file that I created (lots of itty bitty design parts) into the div. How would I go about doing this. I tried to set the innerHTML property to index.html (the layout file)
jQuery(function($) {
// Mouse listener for any move event on the current document.
console.log("started"); //debug for starting
var popupStatus = 0; // set value
document.addEventListener('mousemove', function (e) {
var srcElement = e.srcElement;
// Lets check if our underlying element is a DIV.
if (srcElement.nodeName == 'A' || srcElement.nodeName == 'STRONG') {
loadPopup();
}
else{
disablePopup();
}
}, true);
function loadPopup() {
var x = document.getElementById("index");
if(popupStatus == 0) { // if value is 0, show popup
$('<div/>', {
id: 'cover',
innerHTML: index.html //line in question (making it "index.html" doesn't work)
}).appendTo(document.documentElement);
$('#cover').fadeTo("slow",1);
popupStatus = 1; // and set value to 1
}
}
function disablePopup() {
if(popupStatus == 1) { // if value is 1, close popup
$('#cover').fadeTo("slow",0);
$('#cover').remove();
popupStatus = 0;
}
}
});
Maybe you could use ajax to get the content of the file 'index.html'.
A snippet like this:
$.get("index.html", function( data ) {
$.('body').append('<div id="cover">' + data + '</div>');
});
could be placed at the line in question. Replace 'body' with whatever suits to your needs. The snippet can be certainly improved, but for now it should give you an idea.
I'm not aware of other possibilities to read files with JavaScript on the client side.
Or now I see, better make it:
$.get("index.html", function( data ) {
$('<div/>', {
id: 'cover',
innerHTML: data
}).appendTo(document.documentElement);
});
within the if-condition.
I'm not completely sure what you want to make, but it seems that you want to append it to the element with ID 'index'. Than you should change that appropriately in the last line.
EDIT:
Here are some slight changes:
$.get("index.html", function( data ) {
$('<div/>', {
id: 'cover',
html: data // this was making the problem
}).appendTo(x); // append to the element saved in the variable x
}, "html"); // state explicitly that the payload of data is HTML
that make the snippet working for me.
i was trying to organize my jquery code so i created an object literal, but now the focusTextArea is not working and my textarea value is not updating.
Thanks for your help.
html
<textarea id="test"></textarea>
javascript
(function($,window,document,undefined){
var TEX = {
inputField: $("textarea#test"),
/* Init all functions */
init: function()
{
this.focusTextArea();
},
/* Function update textarea */
focusTextArea: function()
{
this.inputField.text('test');
},
}
$(document).ready(function(){
TEX.init();
});
})(jQuery,window,document);
jsfiddle
http://jsfiddle.net/vBvZ8/1/
First of all, you haven't included jQuery correctly in the fiddle. Also, I think you mean to place the code in the head of the document (because of the document.ready handler).
More importantly perhaps the selector $("textarea#test") is run before the document is ready and therefore won't actually find the element correctly. I would recommend assigning inputField in TEX.init:
(function($,window,document,undefined){
var TEX = {
/* Init all functions */
init: function()
{
this.inputField = $("#test");
this.focusTextArea();
},
/* Function update textarea */
focusTextArea: function()
{
this.inputField.text('test');
},
}
$(document).ready(function(){
TEX.init();
});
})(jQuery,window,document);
Updated example: http://jsfiddle.net/xntA2/1/
As a side note, textarea#test should be changed to just #test. The textarea bit is superfluous since there should be only one element on the page with id=test.
Alternative syntax to avoid looking for an element before it exists is to return the element from a function:
(function($,window,document,undefined){
var TEX = {
/* function won't look for element until called*/
inputField:function(){
return $("textarea#test")
},
init: function()
{
this.focusTextArea();
},
focusTextArea: function()
{
this.inputField().text('test');
},
}
$(document).ready(function(){
TEX.init();
});
})(jQuery,window,document);
DEMO: http://jsfiddle.net/vBvZ8/5/
I realize this is a simplified example...but you are also very close to creating a jQuery plugin and that may also be of benefit. Following provides same functionality as example:
(function($, window, document, undefined) {
$.fn.focusTextArea = function() {
return this.each(function(){
$(this).text('test');
})
};
})(jQuery, window, document);
$(function() {
$('textarea').focusTextArea()
});
DEMO: http://jsfiddle.net/vBvZ8/8/