I am facing an issue with the numbered list in ckeditor. When I try to bold some text in li, only the text is getting bold, without the preceding number. This is how it looks like,
One
Two
Three
It should be like this
2. Two
When I check the source, I found the code like below
<li><strong>Two</strong></li>
I would like to know is there any way to change the working of bold button, so that it will add something like below
<li style="font-weight:bold">Two</li>
<p> Hello <strong>World</strong></p>
I tried to solve your problem.
My solution isn't the best, because I guess that create a bold plugin, that takes care about list items would be the best solution.
I make it without using jQuery; however, using it the code should became simpler and more readable.
First of all, we need to define something useful for the main task:
String trim. See this.
if (!String.prototype.trim) {
String.prototype.trim = function() {
return this.replace(/^\s+|\s+$/g, '');
};
}
String contains. See this
String.prototype.contains = function(it) {
return this.indexOf(it) != -1;
};
First child element. The following function obtains the first child element, or not-empty text node, of the element passed as argument
function getFirstChildNotEmpty(el) {
var firstChild = el.firstChild;
while(firstChild) {
if(firstChild.nodeType == 3 && firstChild.nodeValue && firstChild.nodeValue.trim() != '') {
return firstChild;
} else if (firstChild.nodeType == 1) {
return firstChild;
}
firstChild = firstChild.nextSibling;
}
return firstChild;
}
Now, we can define the main two functions we need:
function removeBoldIfPresent(el) {
el = el.$;
var elStyle = el.getAttribute("style");
elStyle = (elStyle) ? elStyle : '';
if(elStyle.trim() != '' && elStyle.contains("font-weight:bold")) {
el.setAttribute("style", elStyle.replace("font-weight:bold", ''));
}
}
CKEDITOR.instances.yourinstance.on("change", function(ev) {
var liEls = ev.editor.document.find("ol li");
for(var i=0; i<liEls.count(); ++i) {
var el = liEls.getItem(i);
var nativeEl = el.$.cloneNode(true);
nativeEl.normalize();
var firstChild = getFirstChildNotEmpty(nativeEl);
if(firstChild.nodeType != 1) {
removeBoldIfPresent(el);
continue;
}
var firstChildTagName = firstChild.tagName.toLowerCase()
if(firstChildTagName == 'b' || firstChildTagName == 'strong') {
//TODO: you also need to consider the case in which the bold is done using the style property
//My suggest is to use jQuery; you can follow this question: https://stackoverflow.com/questions/10877903/check-if-text-in-cell-is-bold
var textOfFirstChild = (new CKEDITOR.dom.element(firstChild)).getText().trim();
var textOfLi = el.getText().trim();
if(textOfFirstChild == textOfLi) {
//Need to make bold
var elStyle = el.getAttribute("style");
elStyle = (elStyle) ? elStyle : '';
if(elStyle.trim() == '' || !elStyle.contains("font-weight:bold")) {
el.setAttribute("style", elStyle + ";font-weight:bold;");
}
} else {
removeBoldIfPresent(el);
}
} else {
removeBoldIfPresent(el);
}
}
});
You need to use the last release of CkEditor (version 4.3), and the onchange plugin (that is included by default in the full package).
CKEditor 4.1 remove your classes, styles, and attributes that is not specified in its rules.
If that's the problem, you might want to disable it by adding this line:
CKEDITOR.config.allowedContent = true;
Here is full code to use it:
window.onload = function() {
CKEDITOR.replace( 'txt_description' );
CKEDITOR.config.allowedContent = true; //please add this line after your CKEditor initialized
};
Please check it out here
<ul class="test">
<li><span>hello</span></li>
</ul>
.test li
{
font-weight:bold;
}
.test li span
{
font-weight:normal;
}
Related
I have a multiple select like the following.
But what I now need is to display it like a normal "dropdown" select, because there are many normal selects on my website and it looks very strange if theres a select with size more than 1.
So the multiple select should also look like here.
I don't want to include an extra jQuery plugin or something else. I look for a easy html/css/js solution.
What you ask for is simply not possible. Your target look is a select box rendered by the web browser. You simply can not modify a multiple select to be rendered the same way. How would the user select multiple items?
For usability reasons I strongly suggest looking at another solution. Multiple selects are really hard for users to understand. A far more usable solution would be to use a group of checkboxes.
Or as WebAIM states it:
It is recommended that multiple select menus be avoided. Not all browsers provide intuitive keyboard navigation for multiple select menus. Many users do not know to use CTRL/Command or Shift + click to select multiple items. Typically, a set of check box options can provide similar, yet more accessible functionality.
More regarding usability and multiple select, see for example :
http://www.90percentofeverything.com/2008/12/03/multiple-select-controls-must-evolve-or-die/ or
http://alistapart.com/article/sensibleforms
This being said, there are multiple libraries which creates a custom experience, such as https://github.com/bsara/multi-select-dropdown.js (no dependency)
I made a quick class (pure JavaScript) that should behave as you wish. You'll probably want to add some more functionality.
EDIT: Updated the code, forgot you wanted a dropdown
https://jsfiddle.net/xLtfsgbd/1/
( function()
{
var MultipleSelectDropDown = function()
{
// constructor
}
MultipleSelectDropDown.prototype =
{
options: [],
selected: [],
size: 0,
displayed: false,
add: function(element)
{
this.options.push(element);
this.size = this.size + 1;
},
remove: function(element)
{
for(var stored in this.options)
{
if(this.options[stored] == element)
{
this.options.splice(stored, 1);
this.size = this.size - 1;
break;
}
}
},
render: function(onIdSelector)
{
var container = document.getElementById(onIdSelector);
if(typeof container == 'undefined')
{
window.alert('The list container is undefined!')
return;
}
var mainDiv = document.createElement('div');
mainDiv.setAttribute('style', 'width:300px; height: 50px display:block;');
var firstItem = document.createElement('div');
firstItem.setAttribute('style', 'width:250px; height:25px;');
firstItem.appendChild(document.createTextNode(this.options[0]));
var dropDownArrow = document.createElement('img');
dropDownArrow.setAttribute('src', 'https://cdn4.iconfinder.com/data/icons/ionicons/512/icon-ios7-arrow-down-128.png');
dropDownArrow.setAttribute('style', 'width:25px; height:25px;');
_self = this;
dropDownArrow.onclick = function(e)
{
if(_self.displayed)
{
_self.displayed = false;
var displayedList = document.getElementById('daList');
document.body.removeChild(displayedList);
}
else
{
var rect = this.getBoundingClientRect();
_self.renderList(rect.left, rect.top);
_self.displayed = true;
}
};
firstItem.appendChild(dropDownArrow);
mainDiv.appendChild(firstItem);
//mainDiv.appendChild(dropDownArrow);
container.appendChild(mainDiv);
},
renderList: function(x, y)
{
var div = document.createElement('div');
div.setAttribute('id', 'daList');
div.setAttribute('style', 'positon:absolute; top:'+y+'; left:'+x+'; height:100px; width:200px; overflow-y:scroll; background-color:#fff;');
var list = document.createElement('ul');
var _self = this;
for(var stored in this.options)
{
var option = this.options[stored];
var listItem = document.createElement('li');
listItem.setAttribute('index', stored);
listItem.appendChild(document.createTextNode(option));
listItem.onclick = function(e)
{
var index = this.getAttribute('index');
if(index < 0 || index > _self.size)
{
return;
}
var selected = this.getAttribute('selected');
// Item not selected
if(selected == null || selected == '' || selected == 'false')
{
this.setAttribute('selected', 'true');
this.setAttribute('style', 'background-color:#0099ff;');
selected.push(option);
}
else // Item selected
{
this.setAttribute('selected', 'false');
this.setAttribute('style', 'background-color:#fff;');
for(var sel in _self.selected)
{
if(_self.selected[sel] == option)
{
_self.selected.splice(sel, 1);
}
}
}
};
list.appendChild(listItem);
}
div.appendChild(list);
document.body.appendChild(div);
}
}
SelectDrop = MultipleSelectDropDown;
})();
Given an element and any selector, I need to find the closest element which matches it, not matter if it's inside the element or outside of it.
Currently jQuery doesn't provide such traversing functionality, but there is a need. Here is the scenario:
A list of many items where the <button> element reside inside <a>
<ul>
<li>
<a>
<button>click me</button>
<img src="..." />
</a>
</li>
<li>
<a>
<button>click me</button>
<img src="..." />
</a>
</li>
...
</ul>
Or the <button> element might reside outside of the <a> element
<ul>
<li>
<a>
<img src="..." />
</a>
<button>click me</button>
</li>
<li>
<a>
<img src="..." />
</a>
<button>click me</button>
</li>
...
</ul>
The very very basic code would look like this:
$('a').closest1('button'); // where `closest1` is a new custom function
// or
$('a').select('> button') // where `select` can parse any selector relative to the object, so it would also know this:
$('a').select('~ button') // where the button is a sibling to the element
the known element is <a> and anything else can change. I want to locate the nearest <button> element for a given <a> element, no matter if that button is inside or outside of <a>'s DOM tree.
It would be very logical that native jQuery function "closest" would do as the name suggests and find the closest, but it only searches upwards as you all know. (it should have been named differently IMO).
Does anyone know any custom traversing function which does the above?
Thanks. (i'm asking you people because someone must have written this for sure but I was unlucky to find a lead on the internet)
Here is another attempt using the idea I mentioned in comment:
$(this).parents(':has(button):first').find('button').css({
"border": '3px solid red'
});
JSFiddle: http://jsfiddle.net/TrueBlueAussie/z3vwk1ko/40/
It basically looks for the first ancestor that contains both the elements (clicked and target), then finds the target.
Performance:
With regard to speed, this is used at human interaction speeds, i.e. a few times per second maximum, so being a "slow selector" is irrelevant if it solves the problem, in a reasonably obvious way, with minimal code. You would have to click 100s of times per second to notice any different compared to a fast selector :)
None of the built-in selectors allow searching up and down the tree. I did create a custom findThis extension that allows you to do things like $elementClicked.('li:has(this) button') which would allow you to do something similar.
// Add findThis method to jQuery (with a custom :this check)
jQuery.fn.findThis = function (selector) {
// If we have a :this selector
if (selector.indexOf(':this') > 0) {
var ret = $();
for (var i = 0; i < this.length; i++) {
var el = this[i];
var id = el.id;
// If not id already, put in a temp (unique) id
el.id = 'id' + new Date().getTime();
var selector2 = selector.replace(':this', '#' + el.id);
ret = ret.add(jQuery(selector2, document));
// restore any original id
el.id = id;
}
ret.selector = selector;
return ret;
}
// do a normal find instead
return this.find(selector);
}
// Test case
$(function () {
$('a').click(function () {
$(this).findThis('li:has(:this) button').css({
"border": '3px solid red'
});
});
});
JSFiddle: http://jsfiddle.net/TrueBlueAussie/z3vwk1ko/38/
note: Click the images/links to test.
A while ago I wanted to do the same for a completely DOM-based text editor, and needed to find the previous (ARR LEFT) and next (ARR RIGHT) text nodes, both up and down the tree. Based on this code I have made an adaptation suiting this question. Be warned, it's quite performance-heavy, but it is adapted to any scenario. There are two functions findPrevElementNode and findNextElementNode which both return an object with properties:
match - returns the closest matching node for the search or FALSE if none is found
iterations - returns the number of iterations done to find the node. This allows you to check whether the previous node is closer than the next or vice-versa.
The parameters are as follows:
//#param {HTMLElement} referenceNode - The node from which to start the search
//#param {function} truthTest - A function that returns true for the given element
//#param {HTMLElement} [limitNode=document.body] - The limit up to which to search to
var domUtils = {
findPrevElementNode: function(referenceNode, truthTest, limitNode) {
var element = 1,
iterations = 0,
limit = limitNode || document.body,
node = referenceNode;
while (!truthTest(node) && node !== limit) {
if (node.previousSibling) {
node = node.previousSibling;
iterations++;
if (node.lastChild) {
while (node.lastChild) {
node = node.lastChild;
iterations++;
}
}
} else {
if (node.parentNode) {
node = node.parentNode;
iterations++;
} else {
return false;
}
}
}
return {match: node === limit ? false : node, iterations: iterations};
},
findNextElementNode: function(referenceNode, truthTest, limitNode) {
var element = 1,
iterations = 0,
limit = limitNode || document.body,
node = referenceNode;
while (!truthTest(node) && node !== limit) {
if (node.nextSibling) {
node = node.nextSibling;
iterations++;
if (node.firstChild) {
while (node.firstChild) {
node = node.firstChild;
iterations++;
}
}
} else {
if (node.parentNode) {
node = node.parentNode;
iterations++;
} else {
return false;
}
}
}
return {match: node === limit ? false : node, iterations: iterations};
}
};
In your case, you could do:
var a = domUtils.findNextElementNode(
document.getElementsByTagName('a')[0], // known element
function(node) { return (node.nodeName === 'BUTTON'); }
);
var b = domUtils.findPrevElementNode(
document.getElementsByTagName('a')[0], // known element
function(node) { return (node.nodeName === 'BUTTON'); }
);
var result = a.match ? (b.match ? (a.iterations < b.iterations ? a.match :
(a.iterations === b.iterations ? fnToHandleEqualDistance() : b.match)) : a.match) :
(b.match ? b.match : false);
See it in action.
DEMO PAGE / GIST
I have solved working by logic, so I would first look inside the elements, then their siblings, and last, if there are still unfound items, I would do a recursive search on the parents.
JS CODE:
jQuery.fn.findClosest = function (selector) {
// If we have a :this selector
var output = $(),
down = this.find(selector),
siblings,
recSearch,
foundCount = 0;
if(down.length) {
output = output.add(down);
foundCount += down.length;
}
// if all elements were found, return at this point
if( foundCount == this.length )
return output;
siblings = this.siblings(selector);
if( siblings.length) {
output = output.add(siblings);
foundCount += siblings.length;
}
// this is the expensive search path if there are still unfound elements
if(foundCount < this.length){
recSearch = rec(this.parent().parent());
if( recSearch )
output = output.add(recSearch);
}
function rec(elm){
var result = elm.find(selector);
if( result.length )
return result;
else
rec(elm.parent());
}
return output;
};
// Test case
var buttons = $('a').findClosest('button');
console.log(buttons);
buttons.click(function(){
this.style.outline = "1px solid red";
})
I think using sibling selector (~) or child selector (>) will solve your purpose(What ever your case is!!).
I know nothing about JavaScript, and I'm sure this is an easy thing to do, but I've been bashing my brain for the past three hours trying to figure it out.
What I want is to have some text, say Test.com, that when clicked will transform all the letters to uppercase (TEST.COM). If the user clicks again, the text will go to all lowercase(text.com). On the third click the text goes back to the original form (Test.com).
Is this possible?
var count = 1;
$('.text').click(function() {
$(this).toggleClass('uppercase', count === 2);
$(this).toggleClass('lowercase', count === 3);
if (count === 3) {
count = 0
}
count++;
});
.uppercase {
text-transform: uppercase;
}
.lowercase {
text-transform: lowercase;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<span class="text">CamelCase 1 </span>
Here's the long-winded vanilla JS version:
// grab the element containing the text
var div = document.querySelector('#test');
// grab the text
var text = div.innerHTML;
// save a copy of the text
var originalText = div.innerHTML;
// set `toggle` to O for original
// L = lowercase, U = uppercase
var toggle = 'O';
// add an click listener to the element containing the text
div.addEventListener('click', function () {
// toggle between the states updating the text
// of the element with each new click
switch (toggle) {
case 'O':
div.innerHTML = text.toUpperCase();
toggle = 'U';
break;
case 'U':
div.innerHTML = text.toLowerCase();
toggle = 'L';
break;
case 'L':
div.innerHTML = originalText;
toggle = 'O';
break;
}
}, false)
DEMO
And if you wanted to get this working for multiple instances of text on the page, something like this would work. Ideally you'd want to use event propagation for this, but since the layout of your page might change in the future it's probably not worth the risk:
;(function () {
// grab all the elements
var divs = document.querySelectorAll('.test');
// status is used to keep track of where in the cycle each text item is
var status = {};
for (var i = 0, l = divs.length; i < l; i++) {
var div = divs[i];
var key = div.innerHTML.toLowerCase();
// update the status object with the initial text values
status[key] = {
status: 'O',
originalText: div.innerHTML
};
// add the listener like last time
// except this time we monitor the status object for
// for each text instance
div.addEventListener('click', function () {
var text = this.innerHTML.toLowerCase();
var key = status[text].originalText.toLowerCase();
var toggle = status[key].status;
var originalText = status[key].originalText;
switch (toggle) {
case 'O':
this.innerHTML = originalText.toUpperCase();
status[key].status = 'U';
break;
case 'U':
this.innerHTML = originalText.toLowerCase();
status[key].status = 'L';
break;
case 'L':
this.innerHTML = status[key].originalText;
status[key].status = 'O';
break;
}
}, false);
}
}());
DEMO
Define 2 CSS classes.
.ucase {text-transform:uppercase;}
.lcase {text-transform:lowercase;}
Use a combination of jQuery .hasClass() .removeClass() and .addClass() to switch the cases.
Remove all classes from the text to return to original state.
jQuery(document).ready(function() {
jQuery(".textitem").click(function() {
$t = jQuery(this);
if($t.hasClass("ucase")) {
$t.removeClass("ucase").addClass("lcase");
} else if($t.hasClass("lcase")) {
$t.removeClass("lcase");
} else {
$t.addClass("ucase");
}
});
});
Fiddle: https://jsfiddle.net/ptj1ke8y/
Perhaps not the most elegant solution, but you can count the clicks (modulus 3), and adjust the text according to the number and cycle through your 3 cases.
var i = 0;
var cl = $('#text').text();
$('#text').click(function() {
var str = $(this).text();
if (i % 3 == 0) {
$('#text').html(str.toUpperCase());
}
if (i % 3 == 1) {
$('#text').html(str.toLowerCase());
}
if (i % 3 == 2) {
$('#text').html(cl);
}
i++
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div id="text">Text</div>
You can use .toUpperCase() and .toLowerCase() JavaScript methods to change the case of any text.
Since you're using jQuery then you can use the .text() jQuery method to get and set the text content of any DOM node.
You need to store somewhere the original value of your text so that you can get it after the third click, and the jQuery .data() method would be good for this because that way you can easily have few elements on one page that behaves this way.
You can write something like this to make all elements with class "test" behave in a way that you have described:
$('.test').click(function () {
var $this = $(this),
data = $this.data('clicker');
if (!data || !data.text) {
data = {
text: [
$this.text(),
$this.text().toUpperCase(),
$this.text().toLowerCase()
],
step: 0
};
}
data.step = (data.step + 1) % 3;
$this.text(data.text[data.step]);
$this.data('clicker', data);
});
See: DEMO for an example of how it works with 3 elements simultaneously.
First use click() to event handler to the "click" and then use .hasClass(), removeClass() and addClass() to do and use css text-transform
HTML file
<div id="trigger">click me to transform</div>
CSS File
.uppercase { text-transform: uppercase; }
.lowercase { text-transform:lowercase; }
Javascript file
$( document ).ready(function() {
$( "#trigger" ).click(function() {
if($( "#trigger" ).hasClass( "uppercase" )) {
$(this).removeClass('uppercase').addClass('lowercase');
}
else {
$(this).removeClass('lowercase').addClass('uppercase');
}
});
});
Try to play the Sandbox
I think this is what you want (button caption changes to tell what is it going to do and changes the text):
<form name="form1" method="post">
<input name="instring" id="instring" type="text" value="this is the text string" size="30">
<input type="button" id="button" name="Convert" value="Make Uppercase >>" onClick="makeUppercase();">
<input name="outstring" type="text" id="outstring" value="" size="30">
</form>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>
<script>
function makeUppercase(){
if( $('#button').val()=="Make Uppercase >>"){
$('#button').attr("value","Make Lowercase >>");
$('#outstring').attr('value', $('#instring').attr('value').toUpperCase());
} else if($('#button').val()=="Make Lowercase >>") {
$('#button').attr("value","Make Original >>");
$('#outstring').attr('value', $('#instring').attr('value').toLowerCase());
} else{
$('#button').attr("value","Make Uppercase >>");
$('#outstring').attr('value', $('#instring').attr('value'));
}
}
</script>
I have pasted the javascript below but also a link to my codepen so you can see exactly what I am talking about.
I would like the heading to be clicked and expose the text below. On another click I would like for the text to go back to hidden. Multiple headings can be opened at the same time. What is happening with my current setup is you can click once to show, click again to hide and then when you click again to show nothing shows, if you keep clicking the text and headings below are eaten/dissapear. I would prefer to do this without jquery. thanks for any help.
http://codepen.io/jrutishauser/pen/YPrrNa
var clickToShow = function () {
if (this.nextElementSibling.className === 'open'){
this.nextElementSibling.remove('open');
} else if (this.nextElementSibling.className != 'open') {
this.nextElementSibling.className = 'open';
}
};
var articleHeadings = document.getElementsByTagName('h3');
for (var index = 0; index < articleHeadings.length; index++){
articleHeadings[index].onclick = clickToShow;
}
var subArticleHeadings = document.getElementsByTagName('h4');
for (var index2 = 0; index2 < subArticleHeadings.length; index2++){
subArticleHeadings[index2].onclick = clickToShow;
}
Change this.nextElementSibling.remove('open') to this.nextElementSibling.className = ''. I believe remove() method removes the element, not the class.
You can do it like this also. This is the correct way of doing it.
var clickToShow = function () {
element=this.nextElementSibling;
if (element.className === 'open'){
element.className=element.className.replace('open','');
} else if (element.className != 'open') {
element.className = 'open';
}
};
I want to change this from jquery to javascript:
$('#id_of_element').children('div').addClass('some_class');
So far all i have is this(not working):
document.getElementById('id_of_element').getElementsByTagName('div').addClass('some_class');
I have to change all my code from jquery to javascript. Is there any site with have examples of functions in javascript next to jquery? Thanks in advance for all help :)
Try
var el = document.getElementById('elem'),
//modern browsers IE >= 10
classList = 'classList' in el;
for (var i = 0; i < el.children.length; i++) {
var child = el.children[i];
if (child.tagName == 'DIV') {
if (classList) {
child.classList.add('test');
} else {
child.className += ' test'
}
}
}
Demo: Fiddle
If anyone wants to loop through all children, this worked for me:
const addClassList = (element) => {
Object.values(element.children).forEach((e) => {
e.classList.add('myClass');
if (e.children.length > 0) addClassList(e);
});
};
addClassList(myElement);
NOTE:
Does not work with conditional rendering
Remove the hash(#): in javascript you don't need to use # when selecting id.
document.getElementById('id_of_element').getElementsByTagName('div').addClass('some_class');