I am using Quill JS as a rich text editor. I want to be able to highlight specific words in the text, if the user clicks a button. But it seems as Quill JS does not give permission to edit its contents by another plugin (?)
I tried Mark.js and Highlight.js, but they will only work on elements which are not in the Quill JS editor. The trigger seems to work though, since the line with the correct keyword flashes in Firefox Inspector, like there is something accessing that line, but in the browser there is no real change.
I posted a little example code (please ignore the first part of the Javascript since it is just the highlight.js plugin I had to put there. My custom code is at the very end):
$( document ).ready(function() {
// My custom code is at the very end. This is the highlight.js plugin code:
// Copyright (c) 2009 Bartek Szopka
jQuery.extend({
highlight: function (node, re, nodeName, className) {
if (node.nodeType === 3) {
var match = node.data.match(re);
if (match) {
var highlight = document.createElement(nodeName || 'span');
highlight.className = className || 'highlight';
var wordNode = node.splitText(match.index);
wordNode.splitText(match[0].length);
var wordClone = wordNode.cloneNode(true);
highlight.appendChild(wordClone);
wordNode.parentNode.replaceChild(highlight, wordNode);
return 1; //skip added node in parent
}
} else if ((node.nodeType === 1 && node.childNodes) && // only element nodes that have children
!/(script|style)/i.test(node.tagName) && // ignore script and style nodes
!(node.tagName === nodeName.toUpperCase() && node.className === className)) { // skip if already highlighted
for (var i = 0; i < node.childNodes.length; i++) {
i += jQuery.highlight(node.childNodes[i], re, nodeName, className);
}
}
return 0;
}
});
jQuery.fn.unhighlight = function (options) {
var settings = { className: 'highlight', element: 'span' };
jQuery.extend(settings, options);
return this.find(settings.element + "." + settings.className).each(function () {
var parent = this.parentNode;
parent.replaceChild(this.firstChild, this);
parent.normalize();
}).end();
};
jQuery.fn.highlight = function (words, options) {
var settings = { className: 'highlight', element: 'span', caseSensitive: false, wordsOnly: false };
jQuery.extend(settings, options);
if (words.constructor === String) {
words = [words];
}
words = jQuery.grep(words, function(word, i){
return word != '';
});
words = jQuery.map(words, function(word, i) {
return word.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
});
if (words.length == 0) { return this; };
var flag = settings.caseSensitive ? "" : "i";
var pattern = "(" + words.join("|") + ")";
if (settings.wordsOnly) {
pattern = "\\b" + pattern + "\\b";
}
var re = new RegExp(pattern, flag);
return this.each(function () {
jQuery.highlight(this, re, settings.element, settings.className);
});
};
// -------------------------------------------------------------------------
// My custom code
// -------------------------------------------------------------------------
// Initialize Quill editor options
var toolbarOptions = [
['bold', 'italic', 'underline', 'strike'], // toggled buttons
];
// Setup Quill editor
var options = {
modules: {
toolbar: toolbarOptions
},
placeholder: 'Start writing demo and another word then hit the green div...',
theme: 'snow'
};
var editor = new Quill('#editor', options);
// Use highlight js to highlight every "demo" in the text
$("#clicker").on("click", function() {
$("p").highlight("demo");
});
});
#clicker {
background-color: #ddffdd;
display: block;
padding: 32px;
text-align: center;
}
.highlight {
background-color: #FFFF88;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Prototype</title>
<meta name="description" content="This is a demo page.">
<link href="https://cdn.quilljs.com/1.1.5/quill.snow.css" rel="stylesheet">
<script type="text/javascript" src="https://cdn.quilljs.com/1.1.5/quill.js"></script>
</head>
<body>
<div id="clicker" style="cursor: pointer">Mark "demo" in Text by clicking here</div>
<main>
<div id="editor"></div>
<div>
<p>demo outside of editor</p>
</div>
</main>
</body>
</html>
Arbitrary changes through DOM APIs are not supported. To make changes to Quill's editor contents, you have to use its API. This Cloning Medium with Parchment guide is useful for creating new formats like what you are describing.
Related
Auto-complete is not getting fired when pressing white-space in the editor, Is there an option to fire Autocomplete when white space is pressed?
Please have a look at this demo:
[jsfiddle] https://jsfiddle.net/xbaha/cbfe6tx4/53/
I want the auto complete to be fired when i type "my name is " (note there is a space after "is"), then using onchange event i added the suggestions that should pop up, but it does not work, i must type a letter character so that autocomplete is getting fired.
is there a solution for this?
Instead of using enableLiveAutocomplete option you can use a custom handler for afterExec event https://github.com/ajaxorg/ace/blob/v1.4.7/lib/ace/ext/language_tools.js#L149
var editor;
var fieldsList = [];
function initAceEditor() {
editor = ace.edit("aceEditor", {
theme: "ace/theme/solarized_light",
mode: "ace/mode/text",
showPrintMargin: false,
fontSize: "24px",
minLines: 3,
maxLines: 8,
wrap: true,
// do not use live autocomplete since we want to invoke more often
// enableLiveAutocompletion: true,
enableBasicAutocompletion: true,
});
var langTools = ace.require("ace/ext/language_tools");
var stepLineCompleter = {
getCompletions: function(editor, session, pos, prefix, callback) {
var completions = [];
console.log('before foreach....', this.fields, fieldsList);
var cursor = session.selection.cursor
var line = session.getLine(cursor.row).slice(0, cursor.column - prefix.length)
var fieldsList
if (line.endsWith("name is ")) {
fieldsList = ["bob", "james", "alex", "jimmy"];
} else {
fieldsList = ["my name is", "completion2", "completion3", "one more"]
}
fieldsList.forEach(function(w) {
completions.push({
value: w,
});
});
callback(null, completions);
}
}
langTools.setCompleters([stepLineCompleter]);
langTools.addCompleter(stepLineCompleter);
var Autocomplete = ace.require("ace/autocomplete").Autocomplete
var doLiveAutocomplete = function(e) {
var editor = e.editor;
var hasCompleter = editor.completer && editor.completer.activated;
var session = editor.session
var cursor = session.selection.cursor
var line = session.getLine(cursor.row).slice(0, cursor.column)
// We don't want to autocomplete with no prefix
if (e.command.name === "backspace") {
// do not hide after backspace
} else if (e.command.name === "insertstring") {
if (!hasCompleter) {
// always start completer
var completer = Autocomplete.for(editor);
// Disable autoInsert
completer.autoInsert = false;
completer.showPopup(editor);
}
}
};
editor.commands.on('afterExec', doLiveAutocomplete);
}
initAceEditor()
#aceEditor {
position: absolute;
top: 10%;
right: 0;
bottom: 30%;
left: 0;
}
<script src="https://rawgithub.com/ajaxorg/ace-builds/master/src/ace.js" type="text/javascript" charset="utf-8"></script>
<script src="https://rawgithub.com/ajaxorg/ace-builds/master/src/ext-language_tools.js" type="text/javascript" charset="utf-8"></script>
<div id="aceEditor"></div>
I'm struggling to make a textarea adjust its height if needed after some text is added to it by click.
So, I use eventlistener for my textarea in order to determine when some amount of characters is added and a new line is needed further so that textarea is resized vertically. It works fine with manually added characters, but I want to use the so-called "bb-tags" as well for formatting reasons. Say, I use a button that adds red color formatting tag: [color=red][/color].
The problem is that when I add [color=red][/color] by click, my textarea won't add new lines automatically.
I made the following snippet for testing my codes.
// Autoresize textarea
const textarea = document.getElementById('shoutbox-comment');
textarea.addEventListener('input', function () {
this.rows = 2;
this.rows = countRows(this.scrollHeight);
});
function countRows(scrollHeight) {
return Math.floor(scrollHeight / 18); // 18px = line-height
}
// bbtags formatting
function bbtags(h, a, i) {
var g = document.getElementById(h);
g.focus();
if (g.setSelectionRange) {
var c = g.scrollTop;
var e = g.selectionStart;
var f = g.selectionEnd;
g.value = g.value.substring(0, g.selectionStart) + a + g.value.substring(g.selectionStart, g.selectionEnd) + i + g.value.substring(g.selectionEnd, g.value.length);
g.selectionStart = e;
g.selectionEnd = f + a.length + i.length;
g.scrollTop = c;
} else {
if (document.selection && document.selection.createRange) {
g.focus();
var b = document.selection.createRange();
if (b.text != "") {
b.text = a + b.text + i;
} else {
b.text = a + "REPLACE" + i;
}
g.focus();
}
}
}
// insert bbtag on click
bb_red.onclick = function() {
javascript:bbtags("shoutbox-comment", "[color=red]", "[/color]");
}
#shoutbox-comment {
width: 270px;
line-height: 18px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<textarea id="shoutbox-comment"></textarea>
<input type="button" id="bb_red" class="bbtag-color-red" value="red">
I wonder if I need to specify something for eventlistener or else.
Jquery may be used.
Thank you.
replace code of function bb_red.onclick with this :
bb_red.onclick = function() {
javascript:bbtags("shoutbox-comment", "[color=red]", "[/color]");
textarea.rows = countRows(textarea.scrollHeight);
}
I have 2 plugins that I want to work together: Leaflet Tag Filter Button made by maydemirx and MarkerCluster.LayerSupport made by ghybs (both awesome people and plugins btw). What I want to happen is when I click a filter on the tag filter button, I would like the marker clusters to update based on the new filter (like this). So a cluster of 5 points becomes 2, or a cluster of 10 becomes 1 marker. I succeeded in adding the layerSupported cluster to my map, so there's no hiccup there. I am unsure, though, how to integrate the supported cluster with the tag filter buttons, as they are two separate entities.
The Leaflet Tag Filter Button does support an update() method and an enablePruneCluster method, both of which sound like they could be used to achieve what I'm looking for. Yet, when I apply them individually to the filter buttons, they don't work. I'm either applying the filter button methods incorrectly, creating the layerSupported cluster inaccurately, and/or the plugins were not made to be compatible with each other.
Here is my code for generating the layer supported marker cluster group:
var clusters = L.markerClusterGroup.layerSupport({maxClusterRadius:75}),
group1 = L.layerGroup();
var getjson = $.getJSON("map-v2.geojson",function(data){
var bev = L.geoJson(data,{
pointToLayer: function(feature,latlng){
var marker = L.marker(latlng, { tags: feature.properties.Genres.concat(feature.properties.Creator)});
marker.bindPopup('<p align=center>' + '<strong>Title: </strong>' + feature.properties.Title + '<br/><img src="' + feature.properties.Thumbnail_URL + '"/><br/>' + '<strong>Date: </strong>' + feature.properties.Date + '<br/>' + '<strong>Creator: </strong>' + feature.properties.Creator, {minWidth : 250});
return marker;
}
});
bev.addTo(group1);
clusters.addLayer(group1);
map.addLayer(clusters);
});
// Here is where I add the layer supported clusters to the map.
clusters.checkIn(group1);
clusters.addTo(map);
Here is the section where I generate the tag filter buttons:
// Here is the code block for the Tag Filter Button. I start by accessing a tags file that has the data that I use for filter options. I should note that the genres.addToRelated is not working (it is supposed to link the 2 buttons together to work in conjunction with each other).
$.getJSON('tags.json', function(data) {
var genres = L.control.tagFilterButton({
data: data.genres,
filterOnEveryClick: true,
icon: '<i class="fas fa-tags"></i>',
}).addTo(map);
var creators = L.control.tagFilterButton({
data: data.creators,
filterOnEveryClick: true,
icon: '<i class="fas fa-user-edit"></i>',
}).addTo(map);
jQuery('.easy-button-button').click(function() {
target = jQuery('.easy-button-button').not(this);
target.parent().find('.tag-filter-tags-container').css({
'display' : 'none',
});
});
genres.addToRelated(creators);
genres.update(clusters);
genres.enablePruneCluster(clusters);
});
If you'd like to see it all in action, here is a plunker of the code.
Strangely the Leaflet Tag Filter Button plugin and/or latest Leaflet version look to have some bugs / listeners that may pause the script (hence the browser) when the Web Console is open.
Once those bugs are fixed, there are still bugs with the "addToReleated" method. Since I do not know what it is supposed to do, I will just ignore it for now, and let you possibly fix it with the plugin author.
As for integration with the Leaflet.markercluster plugin, it really does not look like the 1st plugin is supposed to support it. PruneCluster plugin (for which the enablePruneCluster method of Tag Filter Button is intended) works very differently from Leaflet.markercluster.
By having a look into the source code of Tag Filter Button, it seems that you could implement it by adapting the enablePruneCluster code and the hide function of the call to registerCustomSource in the default _prepareLayerSources. The idea is to avoid using directly the _map, and use an MCG instead.
Since you can directly handle calls to MCG addLayers and removeLayers within the hide function, there is really no need for the Leaflet.MarkerCluster.LayerSupport plugin at all.
Here is a quick and dirty implementation, called "enableMCG":
////////////////////////////////////////////////
// Quick and dirty implementation of enableMCG
////////////////////////////////////////////////
L.Control.TagFilterButton.include({
// Goal: read from MCG instead of from _map
enableMCG: function(mcgInstance) {
this.registerCustomSource({
name: 'mcg',
source: {
mcg: mcgInstance,
hide: function(layerSource) {
var releatedLayers = [];
for (
var r = 0; r < this._releatedFilterButtons.length; r++
) {
releatedLayers = releatedLayers.concat(
this._releatedFilterButtons[r].getInvisibles()
);
}
var toBeRemovedFromInvisibles = [],
i,
toAdd = [];
for (var i = 0; i < this._invisibles.length; i++) {
if (releatedLayers.indexOf(this._invisibles[i]) == -1) {
for (
var j = 0; j < this._invisibles[i].options.tags.length; j++
) {
if (
this._selectedTags.length == 0 ||
this._selectedTags.indexOf(
this._invisibles[i].options.tags[j]
) !== -1
) {
//this._map.addLayer(this._invisibles[i]);
toAdd.push(this._invisibles[i]);
toBeRemovedFromInvisibles.push(i);
break;
}
}
}
}
// Batch add into MCG
layerSource.mcg.addLayers(toAdd);
while (toBeRemovedFromInvisibles.length > 0) {
this._invisibles.splice(
toBeRemovedFromInvisibles.pop(),
1
);
}
var removedMarkers = [];
var totalCount = 0;
if (this._selectedTags.length > 0) {
//this._map.eachLayer(
layerSource.mcg.eachLayer(
function(layer) {
if (
layer &&
layer.options &&
layer.options.tags
) {
totalCount++;
if (releatedLayers.indexOf(layer) == -1) {
var found = false;
for (
var i = 0; i < layer.options.tags.length; i++
) {
found =
this._selectedTags.indexOf(
layer.options.tags[i]
) !== -1;
if (found) {
break;
}
}
if (!found) {
removedMarkers.push(layer);
}
}
}
}.bind(this)
);
for (i = 0; i < removedMarkers.length; i++) {
//this._map.removeLayer(removedMarkers[i]);
this._invisibles.push(removedMarkers[i]);
}
// Batch remove from MCG
layerSource.mcg.removeLayers(removedMarkers);
}
return totalCount - removedMarkers.length;
},
},
});
this.layerSources.currentSource = this.layerSources.sources[
'mcg'
];
},
});
////////////////////////////////////////////////
// Fix for TagFilterButton
////////////////////////////////////////////////
L.Control.TagFilterButton.include({
_prepareLayerSources: function() {
this.layerSources = new Object();
this.layerSources['sources'] = new Object();
this.registerCustomSource({
name: 'default',
source: {
hide: function() {
var releatedLayers = [];
for (var r = 0; r < this._releatedFilterButtons.length; r++) {
releatedLayers = releatedLayers.concat(
this._releatedFilterButtons[r].getInvisibles()
);
}
var toBeRemovedFromInvisibles = [],
i;
// "Fix": add var
for (var i = 0; i < this._invisibles.length; i++) {
if (releatedLayers.indexOf(this._invisibles[i]) == -1) {
// "Fix": add var
for (var j = 0; j < this._invisibles[i].options.tags.length; j++) {
if (
this._selectedTags.length == 0 ||
this._selectedTags.indexOf(
this._invisibles[i].options.tags[j]
) !== -1
) {
this._map.addLayer(this._invisibles[i]);
toBeRemovedFromInvisibles.push(i);
break;
}
}
}
}
while (toBeRemovedFromInvisibles.length > 0) {
this._invisibles.splice(toBeRemovedFromInvisibles.pop(), 1);
}
var removedMarkers = [];
var totalCount = 0;
if (this._selectedTags.length > 0) {
this._map.eachLayer(
function(layer) {
if (layer && layer.options && layer.options.tags) {
totalCount++;
if (releatedLayers.indexOf(layer) == -1) {
var found = false;
for (var i = 0; i < layer.options.tags.length; i++) {
found =
this._selectedTags.indexOf(layer.options.tags[i]) !==
-1;
if (found) {
break;
}
}
if (!found) {
removedMarkers.push(layer);
}
}
}
}.bind(this)
);
for (i = 0; i < removedMarkers.length; i++) {
this._map.removeLayer(removedMarkers[i]);
this._invisibles.push(removedMarkers[i]);
}
}
return totalCount - removedMarkers.length;
},
},
});
this.layerSources.currentSource = this.layerSources.sources['default'];
},
});
////////////////////////////////////////////////
// Adapted from TagFilterButton demo
// https://github.com/maydemirx/leaflet-tag-filter-button/blob/0.0.4/docs/assets/js/main.js
////////////////////////////////////////////////
var osmUrl = 'http://{s}.tile.osm.org/{z}/{x}/{y}.png',
osmAttrib =
'© OpenStreetMap contributors',
osm = L.tileLayer(osmUrl, {
maxZoom: 18,
attribution: osmAttrib,
});
// initialize the map on the "map" div with a given center and zoom
var releatedUsageMap = L.map('releated-usage-map')
.setView([50.5, 30.5], 12)
.addLayer(osm);
var mcg = L.markerClusterGroup().addTo(releatedUsageMap);
L.marker([50.521, 30.52], {
tags: ['tomato', 'active']
})
.bindPopup('tomato, active')
.addTo(mcg);
L.marker([50.487, 30.54], {
tags: ['tomato', 'ended']
})
.bindPopup('tomato, ended')
.addTo(mcg);
L.marker([50.533, 30.5], {
tags: ['tomato', 'ended']
})
.bindPopup('tomato, ended')
.addTo(mcg);
L.marker([50.54, 30.48], {
tags: ['strawberry', 'active']
})
.bindPopup('strawberry, active')
.addTo(mcg);
L.marker([50.505, 30.46], {
tags: ['strawberry', 'ended']
})
.bindPopup('strawberry, ended')
.addTo(mcg);
L.marker([50.5, 30.43], {
tags: ['cherry', 'active']
})
.bindPopup('cherry, active')
.addTo(mcg);
L.marker([50.48, 30.5], {
tags: ['cherry', 'ended']
})
.bindPopup('cherry, ended')
.addTo(mcg);
var statusFilterButton = L.control
.tagFilterButton({
data: ['active', 'ended'],
filterOnEveryClick: true,
icon: '<span>suitcase</span>',
})
.addTo(releatedUsageMap);
// Enable MCG integration
statusFilterButton.enableMCG(mcg);
/*var foodFilterButton = L.control
.tagFilterButton({
data: ['tomato', 'cherry', 'strawberry'],
filterOnEveryClick: true,
icon: '<i class="fa fa-pagelines"></i>',
})
.addTo(releatedUsageMap);
foodFilterButton.addToReleated(statusFilterButton);*/
html,
body,
#releated-usage-map {
height: 100%;
margin: 0;
}
<!-- Leaflet -->
<link href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.3.3/leaflet.css" media="screen, print" rel="stylesheet" integrity="sha512-Rksm5RenBEKSKFjgI3a41vrjkw4EVPlJ3+OiI65vTjIdo9brlAacEuKOiQ5OFh7cOI1bkDwLqdLw3Zg0cRJAAQ==" crossorigin="">
<script type='text/javascript' src='https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.3.3/leaflet.js' integrity="sha512-tAGcCfR4Sc5ZP5ZoVz0quoZDYX5aCtEm/eu1KhSLj2c9eFrylXZknQYmxUssFaVJKvvc0dJQixhGjG2yXWiV9Q==" crossorigin=""></script>
<!-- MarkerCluster Plugin -->
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet.markercluster/1.3.0/MarkerCluster.css" />
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet.markercluster/1.3.0/MarkerCluster.Default.css" />
<script type='text/javascript' src='https://cdnjs.cloudflare.com/ajax/libs/leaflet.markercluster/1.3.0/leaflet.markercluster.js'></script>
<!-- EasyButton Plugin (compatibility for tagFilterButton) -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/leaflet-easybutton#2/src/easy-button.css">
<script src="https://cdn.jsdelivr.net/npm/leaflet-easybutton#2/src/easy-button.js"></script>
<!-- tagFilterButton Plugin -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/leaflet-tag-filter-button#0.0.4/src/leaflet-tag-filter-button.css">
<script src="https://cdn.jsdelivr.net/npm/leaflet-tag-filter-button#0.0.4/src/leaflet-tag-filter-button.js"></script>
<div id="releated-usage-map"></div>
My goal using htmlbuttons addon for CKeditor is to insert the html code at the very beginning of the textbox content, despite the position of the caret. I adapted the script shown below (found here), but it does not work, so I need to know what could be wrong.
CKEDITOR.plugins.add( 'htmlbuttons',
{ init : function( editor )
{ for (name in CKEDITOR.instances) {
var instance = CKEDITOR.instances[name]; }
above I get the id (which is the same as the name) - verified
function setSelectionRange(input, selectionStart, selectionEnd) {
if (input.setSelectionRange) {
input.focus();
input.setSelectionRange(selectionStart, selectionEnd);
}
else if (input.createTextRange) {
var range = input.createTextRange();
range.collapse(true);
range.moveEnd('character', selectionEnd);
range.moveStart('character', selectionStart);
range.select();
}
}
function setCaretToPos (input, pos) {
setSelectionRange(input, pos, pos);
}
above is the script I found here to reposition the caret (cursor).
var buttonsConfig = editor.config.htmlbuttons;
if (!buttonsConfig)
return;
function createCommand( definition )
{
return {
exec: function( editor ) {
instanceId = (instance.name);
setCaretToPos(document.getElementById(instanceId),0);
line above should position the caret at the beginning of ckeditor textbox, but it does not work.
editor.insertHtml( definition.html );
}
};
}
// Create the command for each button
for(var i=0; i<buttonsConfig.length; i++)
{
var button = buttonsConfig[ i ];
var commandName = button.name;
editor.addCommand( commandName, createCommand(button, editor) );
editor.ui.addButton( commandName,
{
label : button.title,
command : commandName,
icon : this.path + button.icon
});
}
} //Init
} );
I found my answer - replace the line "SetCaretToPos..." with this code:
editor.focus();
var selection = editor.getSelection();
var range = selection.getRanges()[0];
var pCon = range.startContainer.getAscendant('p',true);
var newRange = new CKEDITOR.dom.range(range.document);
newRange.moveToPosition(pCon, CKEDITOR.POSITION_BEFORE_START);
newRange.select();
that's it. It inserts the code at the very begining - disregarding the cursor position.
This is a better solution:
Replace the line "SetCaretToPos..." with this code:
(function( cursorManager ) {
var range = editor.createRange();
range.moveToPosition( range.root, CKEDITOR.POSITION_BEFORE_END );
//OR range.moveToPosition( range.root, CKEDITOR.POSITION_AFTER_START );
editor.getSelection().selectRanges( [ range ] );
}( window.cursorManager = window.cursorManager || {}));
As if we make a div element with it contenteditable="true" and then when if this is seen in console then
1st. In console there is simple a div tag.
<div id="typbody" contenteditable="true" style="width:100%; height:200px; border:1px solid #ccc; "></div>
2nd. If I press enter in div tag then in console it is written <div><br></div>!
and If I write anything in that the it is also written inside <div>.
so my question is:
1) Is there any way to give these newly created div tag an id ?
2) Is there also any way to give them different ids or class to all new div's?
Do I have to learn any other language other than javascript or php?
"Is there is any way to give these newly created div tag an id ?"
"Is there is also any way to give them different ids or class to all new div's?"
Yes, there is!
Look at MutationObserver MDN,
and DOM MutationObserver – reacting to DOM changes without killing browser performance.
Demonstration with JS
(function () {
"use strict";
var target = document.getElementById("typbody"),
config = {
childList: true,
},
eCollection = [],
i = 0,
id = target.id + "_",
observer = new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
if (mutation.addedNodes) {
[].forEach.call(mutation.addedNodes, function (node) {
if (node.nodeName.toLowerCase() === "div") {
var index = eCollection.indexOf(node.nextSibling);
node.id = id + i++;
eCollection.push(node);
if (node.nextSibling && index > -1) {
node.id = node.nextSibling.id;
for (var j = index; j < eCollection.length - 1; j++) {
eCollection[j].id = id + (+eCollection[j].id.substr(id.length) + 1);
}
}
eCollection.sort(sortC);
}
});
}
if (mutation.removedNodes) {
[].forEach.call(mutation.removedNodes, function (node) {
if (node.nodeName.toLowerCase() === "div") {
var index = eCollection.indexOf(node);
eCollection.splice(index, 1);
for (var j = index; j < eCollection.length; j++) {
eCollection[j].id = id + (eCollection[j].id.substr(id.length) - 1);
}
i--;
eCollection.sort(sortC);
}
});
}
});
});
observer.observe(target, config);
function sortC(a, b) {
return a.id.substr(id.length) - b.id.substr(id.length);
}
}());
But why you want such behavior?
If you want to add some style to those elements, why not simply use css:
Demo with CSS
#typbody > div:nth-child(odd) {
color: green;
}
#typbody > div:nth-child(even) {
color: blue;
}
Add the id when you create it. If you want it to be dynamic, just make the id variable dynamic.
var frag = document.createDocumentFragment(),
var item = document.createElement('div');
item.id = whatever;
frag.appendChild(item);
and then insert the frag into your DOM wherever you want. For instance, after an element 'foo':
foo.parentNode.insertBefore(frag, foo.nextSibling);