how to preserve style when cloning a node - javascript

I want to take an html document (a book chapter) and separate it into pages (an array of DIV, each containing a page of html content that will fit within the prescribed dimensions of the DIV). I walk the DOM with the following code (found on this site!).
function walk(node, func)
{
func(node);
node = node.firstChild;
while (node)
{
walk(node, func);
node = node.nextSibling;
}
};
The func function is called test and is below.
function test(node)
{
var copy=node.cloneNode(false);
currentPageInArray.appendChild(copy);
//Test if we still fit
if( $(currentPageInArray).height() <= maxPageHeight )
{
//All good
}
else
{
//We dont fit anymore
//Remove node that made us exceed the height
currentPageInArray.removeChild(copy);
createNewPage();
currentPageInArray.appendChild(copy); //into new page
}
}
My pages get generated correctly, however, I lose all styles such as italic, bold, headers, etc. If I try clone(true), many elements get duplicated multiple times. How can I fix this? Thanks in advance.

You may retrieve the current layout of every element using currentStyle(IE<9) or getComputedStyle(Others) and apply it to the cloned elements.

Related

Efficient OOP way to generate DOM elements dynamically with JavaScript?

I'm tinkering with writing a more efficient methodology in the creation of dynamically generated DOM elements via JavaScript. This is something I intend to add into my own JS framework later on. Looking for other OOP devs that could help better refine what I do have.
Here's a link to the working CodePen:
http://codepen.io/DaneTheory/pen/yeLvmm/
Here's the JS:
function CreateDOMEl() {};
CreateDOMEl.prototype.uiFrag = document.createDocumentFragment();
CreateDOMEl.prototype.elParent = function(elParent, index) {
this.elParent = document.getElementsByTagName(elParent)[index];
}
CreateDOMEl.prototype.elType = function(type) {
newEl = document.createElement(type);
this.uiFrag.appendChild(newEl);
}
CreateDOMEl.prototype.elContent = function(elContent) {
this.elContent = elContent;
newEl.textContent = elContent;
}
CreateDOMEl.prototype.buildEl = function() {
this.elParent.appendChild(this.uiFrag);
}
var div = new CreateDOMEl();
div.elParent('body', 0);
div.elType('DIV');
div.elContent('OK');
div.buildEl();
console.log(div);
var bttn = new CreateDOMEl();
bttn.elParent('body', 0);
bttn.elType('BUTTON');
bttn.elContent('SUBMIT');
bttn.buildEl();
console.log(bttn);
And some CSS to get elements to appear on page:
div {
width:100px;
height:100px;
border: 1px solid red;
}
My thoughts:
For performance, using the prototype to build methods versus placing all the logic in the constructor.
Rather than directly appending elements to the page, append to a single Document Fragment. Once the element is built out as a Doc Frag, appending the Doc Frag to to the DOM. I like this method for performance, but would like to improve upon it. Any useful implementations of requestnimationFrame, or using range and other versions of the document fragment method?
Silly, but I think for debugging it'd be nice to see the generated Element type within the Object property's on console log. As of right now, console logging a created element will show the elements parent and text content. It'd be great to show the elements type as well.
Creating more than one element at a time is another piece of functionality I'd like to offer as an option. For instance, creating a div element creates one div element. What's a good way to add another optional method to create multiple instances of div's.
div.elType('DIV');
// After calling the elType method, do something like this:
div.elCount(20);
// This would create 20 of the same divs
Lastly, a nice clean way to optionally add attributes (i.e: classes, an ID, value, a placeholder, custom attributes, data-* attributes, etc.). I've got a nice helper function I use that adds multiple attributes to an element in an object literal syntax looking way. Adding this as a method of the constructor would be ideal. Here's that function:
function setAttributes(el, attrs) {
for(var key in attrs) {
el.setAttribute(key, attrs[key]);
}
}
// A use case using the above
// function would be:
var anInputElement = document.createElement("TEXTAREA");
setAttributes(anInputElement, {
"type": "text",
"id": "awesomeID",
"name": "coolName",
"placeholder": "Hey I'm some placeholder example text",
"class": "awesome"
});
// Which creates the following HTML snippet:
<textarea type="text" id="awesomeID" name="coolName" placeholder="Hey I'm some placeholder example text" class="awesome">
As a side note, realizing now that the above helper function needs rewritten so that multiple classes could be created.
Respectfully, I believe you may be overthinking it. Just use the tools available in JavaScript and get 'er done. In terms of performance, computers are so fast at running your JavaScript that you (and me) are unable to perceive, or even comprehend, the speed. Here's how I add a link to an MDL nav menu, for example. It's just vanilla JS. Don't forget to add event listeners.
function navMenuAdd(type,text){
var newAnchor = doc.createElement("anchor");
newAnchor.classList.add('mdl-navigation__link');
newAnchor.classList.add(type);
newAnchor.href = "javascript:void(0)";
var anchorContent = doc.createTextNode(text);
newAnchor.appendChild(anchorContent);
newAnchor.addEventListener('click', navMenuClickHandler, false);
//newAnchor.style.display = 'none';
if (type === 'Thingy A'){
//insertAfter(newAnchor, navMenuCredentials);
navMenuCredentialsPanel.appendChild(newAnchor);
} else if (type === 'Thingy B'){
//insertAfter(newAnchor, navMenuDevices);
navMenuDevicesPanel.appendChild(newAnchor);
}
}

Kendo Treeview Expanding big tree issue

I have created a tree control using kendo TreeView.it has more than 10,000 nodes and i have used loadOnDemand false when creating Tree.
I am providing a feature to expand the tree by its level, for this i have created a method which takes the parameter "level" as number and expand it accordingly and user can enter 15 (max level) into the method, it works fine with 500 to 600 nodes for all the levels but when tree has more than 5000 nodes than if user is trying to expand above the 2nd level nodes then browser hangs and shows not responding error.
Method which i have created to expand the tree is :-
function ExapandByLevel(level, currentLevel) {
if (!currentLevel) {
currentLevel = 0;
}
if (level != currentLevel) {
var collapsedItems = $("#treeView").find(".k-plus:visible");
if (collapsedItems.length > 0) {
setTimeout(function () {
currentLevel++;
var $tree = $("#treeView");
var treeView = $tree.data("kendoTreeView");
var collapsedItemsLength = collapsedItems.length;
for (var i = 0; i < collapsedItemsLength; i++) {
treeView.expand($(collapsedItems[i]).closest(".k-item"));
}
ExapandByLevel(level, currentLevel);
}, 100);
}
else {
//console.timeEnd("ExapandByLevel");
hideLoading();
}
}
if (level == currentLevel) {
hideLoading();
}
}
call above given method like this:-
ExapandByLevel(15);
here 15 is level to expand in tree.
when tree has more than 5000 nodes than if user is trying to expand above the 2nd level nodes then browser hangs and shows not responding error.
please suggest any way to do this,what i want is expand the tree which can contains more than 5000 nodes.
I had a similar problem with kendo TreeView, when I wanted to load a tree with 30,000 nodes. The browser would freeze for a long time to load this number of nodes even when loadOnDemand was set to true.
So we decided to implement the server-side functionality for expanding nodes, and that's what you should do. You need to have 2 changes in your existing code.
Change your tree use server side Expand method.
When you call expand, you should make sure the node is expanded.
These two steps will be explained below. The thing you should know is, this way your browser doesn't hang at all, but it may take some time to complete the operation, because there will be so many webservice calls to the server.
Change your tree to use server side Expand method:
Please see Kendo UI's demos for Binding to Remote Data in this
link. Note that loadOnDemand should be set to true. In addition the server side Expand web service should be implemented too.
When you call expand, you should make sure the node is expanded:
In order to do this, there should be an event like Expanded defined in Kendo UI TreeView, but unfortunately there is none, except Expanding event. Using setTimeout in this case is not reliable, because the network is not reliable. So we ended up using a while statement to check that the node's children are created or not. There might be a better solution for this, however this satisfies our current requirement. Here's the change you should make when expanding nodes:
if (collapsedItems.length > 0) {
currentLevel++;
var $tree = $("#treeView");
var treeView = $tree.data("kendoTreeView");
var collapsedItemsLength = collapsedItems.length;
for (var i = 0; i < collapsedItemsLength; i++) {
var node = $(collapsedItems[i]).closest(".k-item")
if (!node.hasChildren)
continue; // do not expand if the node does not have children
treeView.expand(node);
// wait until the node is expanded
while (!node.Children || node.Children.length == 0);
}
ExapandByLevel(level, currentLevel);
}
You can also do the expand calls in a parallel way in order to decrease the loading time, but then you should change the way you check if all the nodes are expanded or not. I just wrote a sample code here that should work fine.
Hope this helps.
The solution to your problem is pretty simple: Update the version of Kendo UI that you are using since they have optimized (a loooooooooot) the code for HierarchicalDataSource and for TreeView.
Check this: http://jsfiddle.net/OnaBai/GHdwR/135/
This is your code where I've change the version of kendoui.all.min.js to v2014.1.318. I didn't even changed the CSS (despite you should). You will see that opening those 5000 nodes is pretty fast.
Nevertheless, if you go to 10000 elements you will very likely consider it slow but sorry for challenging you: do you really think that 10000 nodes tree is User Friendly? Is a Tree the correct way of presenting such a huge amount of data?

JS vs DOM timing: .remove() element visually happens, but travesal still includes it

The short description of the functionality that we are trying to achieve: we have a list of source objects on the left, a person can drag new items from the list to a list on the right, items thus get added to the list on the right; they can also remove items from the list on the right. The list on the right then gets saved whenever it is changed. (I don't think the specifics of how/where it is being saved matter...)
I am having a problem with a bit of timing in the JavaScript vs. DOM elements realm of things. Items that are already on the list on the right can be removed. We have some code that fires on a 'remove/delete' type icon/button on a DOM element, that is supposed to remove the element from the DOM visually and permanently (i.e. it doesn't need to be brought back with a 'show'). This visual change should then also show up in the JSON object that is built when the JS traverses the DOM tree to build the new updated list.
However, this chunk of JS code that runs immediately after this .remove() is called, the element that should have just been removed still shows up in the JSON object. This is not good.
Here are what I believe to be the relevant bits of code operating here. This lives in a web browser; much of this is in the document.ready() function. A given list can also have subsections, hence the sub-list parts and loops.
The on-click definition:
$('body').on('click', '.removeLine', function() {
var parent=$(this).parent().parent().parent(); //The button is a few DIVs shy of the outer container
var List=$(this).closest('article'); //Another parent object, containing all the
parent.fadeOut( 300,
function() {
parent.slideUp(300);
parent.remove();
}
);
sendList(List); // This builds and stores the list based on the DOM elements
});
And then later on, this function definition:
function sendList(List) {
var ListArray=[],
subListArray=[],
itemsArray = [],
subListName = "";
var ListTitle = encodeText(List.find('.title').html());
// loop through the subLists
List.find('.subList').each(
function(index, element) {
subListName=($(this).find('header > .title').html()); // Get sublist Title
subListID=($(this).attr('id')); // Get subList ID
// loop through the line items
itemsArray=[];
$(this).find('.itemSearchResult').each(
function(index, element) {
// Build item Array
if( $(this).attr('data-itemid')!= item ) {
itemArray.push( $(this).attr('data-itemid'));
}
}
);
// Build SubList Array with items Array
subListArray.push(
{
"subListName": subListName,
"subListID" : subListID,
"items" : itemsArray
}
);
}
); <!-- end SubList Loop -->
// Complete List Array with subListArray
ListArray ={
"ListName": ListTitle,
"ListID": List.attr('id'),
"subLists": subListArray
};
// Send New List to DataLists Object - the local version of storage
updateDataLists(ListArray);
// Update remote storage
window.location= URLstring + "&$Type=List" + "&$JSON=" + JSON.stringify(ListArray) + "&$objectID=" + ListArray.ListID;
};
It seems to be the interaction of the 'parent.remove()' step and then the call to 'sendList()' that get their wires crossed. Visually, the object on screen looks right, but if we check the data being sent to the storage, it comes through WITH the object that was visually removed.
Thanks,
J
PS. As you can probably tell, we are new at the Javascript thing, so our code may not be terribly efficient or proper. But...it works! (Well, except for this issue. And we have run into this issue a few times. We have a workaround for it, but I would rather understand what is going on here. Learn the deeper workings of JS so we don't create these problems in the first place.)
There's a few things going on here, but I'm going to explain it by approaching it from an asynchronous programming perspective.
You are calling sendList before the element gets removed from the DOM. Your element doesn't get removed from the DOM until after your fadeOut callback gets executed (which takes 300ms).
Your sendList function gets called immediately after you begin the fadeOut, but your program doesn't wait to call sendList until your fadeOut is finished - that's what the callback is for.
So I would approach it by calling sendList in the callback, after your DOM element has been removed like this:
$('body').on('click', '.removeLine', function() {
var el = $(this); //maintain a reference to $(this) to use in the callback
var parent=$(this).parent().parent().parent(); //The button is a few DIVs shy of the outer container
parent.fadeOut( 300,
function() {
parent.slideUp(300);
parent.remove();
sendList(el.closest('article'));
}
);
});

How do I prevent the Javascript InfoVis SpaceTree `ST.select()` method from collapsing nodes?

I'm using the JavaScript InfoVis Toolkit and in particular the SpaceTree visualisation.
I need to expand all of the tree and then show a path from a particular leaf node back to the root.
I've got the tree to expand just fine but it's the selection of a leaf node and highlighting the path back to the root that's causing me some problems.
I'm using the ST.select(node, onComplete) function to select the leaf node I'm interested in and indeed the path back to the root (lines and nodes) are highlighted.
To do this I implemented the onBeforePlotNode and onBeforePlotLine ST.Controller methods to allow me to highlight the nodes back to the root and their plotlines:
onBeforePlotNode: function(node){
//add some color to the nodes in the path between the
//root node and the selected node.
if (node.selected) {
node.data.$color = "#dddddd";
} else {
delete node.data.$color;
}
},
onBeforePlotLine: function(adj){
if (adj.nodeFrom.selected && adj.nodeTo.selected) {
adj.data.$color = "#33CC33";
adj.data.$lineWidth = 5;
} else {
delete adj.data.$color;
delete adj.data.$lineWidth;
}
}
The problem is that when I call ST.select() to highlight the leaf node all child nodes beneath this level are collapsed/hidden.
To see this in action I've uploaded a couple of examples:
Full tree expansion - leaf not selected
Leaf selected - path shown, but all children below node N2 missing
You may need to scroll down if your browser window is a bit small.
So my question is, how do I show nodes from a leaf node back to the root node in JavaScript InfoVis without collapsing level 3's children (level 1 being the root)?
If there was a way to find my leafe node and walk the tree back to the root (setting styles on the way) then I'd be happing doing that
OK after digging through all that code, cluttering it with console.log() calls and breakpoints, I found it.
It has to do with the inital onClick call, the fact that the graph as an update loop that's running in the background and the fact that everything besides onClick seems to ignore the busy state of the graph.
What happens
onClick gets called and triggers a chain of events, part of them is asynchronous
select is being called which is more or less synchronous and does its work
onClick finally gets done and one of it's side effects is that it re-expands the graph
select has set clickedNode and now onClick uses the newly set value of it and screws up
Solution
We need to redesign select so it respects the busy state of the graph:
select: function(id, onComplete) {
var that = this;
if (this.busy) {
window.setTimeout(function() {
that.select(id, onComplete);
}, 1);
return;
}
// original select code follows here (remove the old var that = this; assignment)
That's all, we simply check for the busy state and delay select until it's false.
This should also be applied to all other function besides onClick that are called from the outside, the library designer here did a bad job of indicating what has side effects and what has not though.
Did you try setting "constrained: false" in ST's properties? That solved it for me.
http://thejit.org/static/v20/Docs/files/Visualizations/Spacetree-js.html

Dojo extending dojo.dnd.Source, move not happening. Ideas?

NOTICE: THIS IS SOLVED, I WILL PUBLISH THE SOLUTION HERE ASAP.
Hey all,
Ok... I have a simple dojo page with the bare essentials. Three UL's with some LI's in them. The idea si to allow drag-n-drop among them but if any UL goes empty due to the last item being dragged out, I will put up a message to the user to gie them some instructions.
In order to do that, I wanted to extend the dojo.dnd.Source dijit and add some intelligence. It seemed easy enough. To keep things simple (I am loading Dojo from a CDN) I am simply declating my extension as opposed to doing full on module load. The declaration function is here...
function declare_mockupSmartDndUl(){
dojo.require("dojo.dnd.Source");
dojo.provide("mockup.SmartDndUl");
dojo.declare("mockup.SmartDndUl", dojo.dnd.Source, {
markupFactory: function(params, node){
//params._skipStartup = true;
return new mockup.SmartDndUl(node, params);
},
onDndDrop: function(source, nodes, copy){
console.debug('onDndDrop!');
if(this == source){
// reordering items
console.debug('moving items from us');
// DO SOMETHING HERE
}else{
// moving items to us
console.debug('moving items to us');
// DO SOMETHING HERE
}
console.debug('this = ' + this );
console.debug('source = ' + source );
console.debug('nodes = ' + nodes);
console.debug('copy = ' + copy);
return dojo.dnd.Source.prototype.onDndDrop.call(this, source, nodes, copy);
}
});
}
I have a init function to use this to decorate the lists...
dojo.addOnLoad(function(){
declare_mockupSmartDndUl();
if(dojo.byId('list1')){
//new mockup.SmartDndUl(dojo.byId('list1'));
new dojo.dnd.Source(dojo.byId('list1'));
}
if(dojo.byId('list2')){
new mockup.SmartDndUl(dojo.byId('list2'));
//new dojo.dnd.Source(dojo.byId('list2'));
}
if(dojo.byId('list3')){
new mockup.SmartDndUl(dojo.byId('list3'));
//new dojo.dnd.Source(dojo.byId('list3'));
}
});
It is fine as far as it goes, you will notice I left "list1" as a standard dojo dnd source for testing.
The problem is this - list1 will happily accept items from lists 2 & 3 who will move or copy as apprriate. However lists 2 & 3 refuce to accept items from list1. It is as if the DND operation is being cancelled, but the debugger does show the dojo.dnd.Source.prototype.onDndDrop.call happening, and the paramaters do look ok to me.
Now, the documentation here is really weak, so the example I took some of this from may be way out of date (I am using 1.4).
Can anyone fill me in on what might be the issue with my extension dijit?
Thanks!
If you use Dojo XD loader (used with CDNs), all dojo.require() are asynchronous. Yet declare_mockupSmartDndUl() assumes that as soon as it requires dojo.dnd.Source it is available. Generally it is not guaranteed.
Another nitpicking: dojo.dnd.Source is not a widget/dijit, while it is scriptable and can be used with the Dojo Markup, it doesn't implement any Dijit's interfaces.
Now the problem — the method you are overriding has following definition in 1.4:
onDndDrop: function(source, nodes, copy, target){
// summary:
// topic event processor for /dnd/drop, called to finish the DnD operation
// source: Object
// the source which provides items
// nodes: Array
// the list of transferred items
// copy: Boolean
// copy items, if true, move items otherwise
// target: Object
// the target which accepts items
if(this == target){
// this one is for us => move nodes!
this.onDrop(source, nodes, copy);
}
this.onDndCancel();
},
Notice that it has 4 arguments, not 3. As you can see if you do not pass the 4th argument, onDrop is never going to be called by the parent method.
Fix these two problems and most probably you'll get what you want.
In the end, I hit the Dojo IRC (great folks!) and we ended up (so far) with this...
function declare_mockupSmartDndUl(){
dojo.require("dojo.dnd.Source");
dojo.provide("mockup.SmartDndUl");
dojo.declare("mockup.SmartDndUl", dojo.dnd.Source, {
markupFactory: function(params, node){
//params._skipStartup = true;
return new mockup.SmartDndUl(node, params);
},
onDropExternal: function(source, nodes, copy){
console.debug('onDropExternal called...');
// dojo.destroy(this.getAllNodes().query(".dndInstructions"));
this.inherited(arguments);
var x = source.getAllNodes().length;
if( x == 0 ){
newnode = document.createElement('li');
newnode.innerHTML = "You can drag stuff here!";
dojo.addClass(newnode,"dndInstructions");
source.node.appendChild(newnode);
}
return true;
// return dojo.dnd.Source.prototype.onDropExternal.call(this, source, nodes, copy);
}
});
}
And you can see where I am heading, I put in a message when the source is empty (client specs, ug!) and I need to find a way to kill it when something gets dragged in (since it is not, by definition, empty any more ona incomming drag!). That part isnt workign so well.
Anyway, the magic was not to use the onDnd_____ functions, but the higher level one and then call this.inherited(arguments) to fire off the built in functionality.
Thanks!
dojo.require("dojo.dnd.Source");
dojo.provide("mockup.SmartDndUl");
dojo.declare("mockup.SmartDndUl", dojo.dnd.Source, {
Dojo require statement and declare statement are next to next. I think that will cause dependencies problem.
the dojo require statement should go outside onload block and the declare statement should be in onload block.

Categories