Using mootools I have a 'builder' class that manufactures form objects, dynamically creating divs as it does so.
Some of the form objects are made up of several different objects. For example, a selection object features a textbox which filters the contents of the selector and a button to save the selection.
In this case I want the filter box and button to be located in a div which is appended to the div of the overall form object so as to have a 'wrapper'.
However, I'm having a problem appending to the div of the dynamically created form object.
After the dom is loaded, the 'builder' class is called:
window.addEvent('domready', function()
{
builder = new Build();
});
Builder creates a new div as such
var div = document.createElement('div');
var div_id = 'the_div_id_for_my_form_object';
div.setAttribute('id', div_id);
It then creates the form object which takes in the div as one of its parameters
var form_obj = superSelector(div);
Inside the form_obj constructor, this div is saved as a member variable, this.div = div.
The filter textbox is created as well as the button.
Here is where I'm seeing a problem. (since the issue is the same for both the filter textbox and the button, I'll describe only the textbox case)
The div of form_obj is passed to the constructor of the filter textbox.
When the filter textbox is created, it creates a div for itself
var div = document.createElement('div');
var div_id = 'the_div_id_for_my_filter_box';
When I attempt to append this div to the div of form_obj, I get a js error saying that I am attempting to append to 'null'
var filterBox = new Class({
initialize: function(name, form_obj)
{
this.name = name;
this.div = document.createElement('div');
this.div.setAttribute('id', name);
document.getElementById(form_obj.div).appendChild(this.div);
}
Yields:
"Uncaught TypeError: Cannot call method 'appendChild' of null"
I'm not sure how to get around this. I get the sense that the div I want to append to doesn't exist at the time I try to append to it. However I see no way of generating an event which tells me when it does exist so that I can postpone the construction of any 'child' divs until that point
right. several things you do that are not mootoolsy.
var div = document.createElement('div');
var div_id = 'the_div_id_for_my_form_object';
div.setAttribute('id', div_id);
should be:
var div = new Element('div', {
id: 'the_div_id_for_my_form_object'
}); // or even new Element('div#foobar');
div.setAttribute('id', div_id); -> div.set('id', div_id);
then appending to the dom:
document.getElementById(form_obj.div).appendChild(this.div);
why? what are you trying to do? grab an element and add to the div in memory?
document.id(form_obj.div).inject(this.div);
// if this element exist, it will be moved as a child to the new div, not safe
// you really ought to rewrite to:
var el = document.id(form_obj.div);
el && el.inject(this.div);
keep in mind this div is not injected to the dom yet at this point.
and so on. read the manual/api - you can always use native js but that kind of defeats the purpose of using a library that fixes things for you.
On a side note, doing what you are doing is not exactly easy, I am currently working with a friend (well, colleague!) of mine on something of a form-builder (for mootools, AMD) and it does what you will probably want to do, more or less - input types, groups, infinite dependencies triggered by values (on all el types), all sorts of form elements and custom looks / feels, validators, default values, placeholders, custom events.. Model/controller like behaviour, default values, server side data / validation, persisted per input data (sessionStorage / window.name)
gets created with AMD builder manifests that support versioning, pagination and languages over twitter bootstrap markup and elements and a single-page restful app via hashtags... basically, it is really a big task.
if we ever decide to open-source it (and I hope we can), and ppl have interest, who knows - you can pretty much build things like interactive tests, survey monkeys, quick forms, complex forms, whatever with it... its extendible and flexible. hope we finish it....
Try sending the "form_obj" parameter to getElementById instead of the div itself. The error seems to be indicating that the div cannot be found in the DOM by the method currently employed. getElementById takes the id attribute of the target div as a string.
e.g.
document.getElementById(form_obj).appendChild(this.div);
getElementById only takes a string parameter. It's returning null because you're passing in a reference to an element to the method.
I'd suggest you to get rid of getElementById. just use the reference you already have to the div you want to append the element to.
form_obj.div.appendChild(this.div);
Hope it helps.
Related
I have a page which is generated and structured as a tree - nested DIVs, etc.. While the user views the page it is possible that some DIVs are updated on the server side and the changes are pushed to the client as JSON data, from which a DIV can be generated.
My problem is that even though I have the old DIV
var oldDiv = $('#foo');
and I have a new DIV generated by
var newDiv = generateDiv(jsonData);
I need to update the old one (both attributes and it's content) without deleting it. I was going to use the jQuery method .replaceWith() as such
oldDiv.replaceWith(newDiv);
but according to the documentation it is implemented as remove&create.
The .replaceWith() method removes content from the DOM and inserts new content in its place with a single call.
How can I update the old DIV without removing it? Is there some nice way to do this, or do I need to do it attribute by attribute?
As you've suggested, you may need to replace the attribute values individually. However, if it reads better, you can actually pass an object to the attr method, and it will update the values you supply.
oldDiv.attr({
attr1: newDiv.attr1,
attr2: newDiv.attr2,
attr3: newDiv.attr3
});
If you wanted to loop through the attributes to build the object, you could do that like this.
var newAttributes = {};
$.each(newDiv[0].attributes, function(index, attribute){
newAttributes[attribute.name] = attribute.value;
});
oldDiv.attr(newAttributes);
It cannot be done since a div element may contain many elements. Why dont u just append the new contents into it.
You can use jquery's append() method.
$(oldDiv).append("#new_div_id");
It will be appended as a child.
If at all you want to update any <p> element, you can use the html() function to get the contents of a tag and then
old_para_contents=("p").html();
$("p").html(old_para_contents+"New contents");
I've come up with one solution so far, but if anyone comes up with a better one, I will gladly assign it as the correct one. I need to make this as clean as possible.
var oldDiv = $('#my-old-div');
var newDiv = generateDiv(data);
oldDiv.attr("id", newDiv.attr("id"));
oldDiv.attr("class", newDiv.attr("class"));
//...
oldDiv.html(newDiv.html());
I have a situation where I have nested divs. I have a parent div (that has an onclick() event) and a few divs inside that are being dynamically populated. I'm given to understand that through 'bubbling,' the onclick() event should propagate up through the DOM, triggering the onclick() event in all parents. All of the research I have done has shown a bunch of people who are trying to PREVENT this, whereas I can't get it to work. The only way I can get the onclick() to work, is to click near the edges of the div, presumably where the child divs don't exist, and I'm clicking directly on the parent.
I've included the applicable code below. There can be as many as 9 of these, so-called "widgets" on the page, but I have removed all code except that referencing the first "widget".
Update: When I try to pull everything out of the JavaScript function, and put it directly in to the HTML code, it works as I would expect. However, doing this would force me to drop desired functionality, so I'm going to try to avoid that workaround.
There is a new fiddle below that shows essentially what I'm going for, even though the events are not calling the JS functions as I would expect.
Update #2: I have created a fiddle (#5 below) that mimics the response I'm seeing in the code. When using the fiddle, you'll notice that no alert is given when clicking in the center of the div, but when you click near the outer boundaries of the div, you finally get a response.
PROBLEM SOLVED:
Per Racheet's answer below, this problem has been solved. I have created a final Fiddle with the fully-functioning code for reference:
http://jsfiddle.net/v3MGX/8/
JAVASCRIPT:
function initializeWidgets(){
var widget1 = "Professional";
widget1 =
"<div class='outer'><div class='middle'>
<div class='inner'><h1>" + widget1 + "</h1></div></div></div>";
document.getElementById("widget1").innerHTML = widget1;
}
function hoverWidgets(widgetID){
var w = new Array();
w[0] = "Work Experience, Educational History, and Resume Download";
w[widgetID-1] = "<div class='outer'><div class='middle'>
<div class='inner'><h2>" + w[widgetID-1] + "</h2></div></div></div>";
document.getElementById("widget"+widgetID).innerHTML = w[widgetID-1];
}
APPLICABLE HTML:
<div class="widget" id="widget1" onclick="alert(1);" onmouseover="hoverWidgets('1')"
onmouseout="initializeWidgets()"></div>
CURRENT FIDDLE:
http://jsfiddle.net/v3MGX/5/
FINAL FIDDLE:
http://jsfiddle.net/v3MGX/8/
I've played with your example a fair bit and worked out the problem. Here is my solution:
http://jsfiddle.net/v3MGX/7/
window.initializeWidgets = (function() {
/*THIS SECTION IS YOUR WIDGET CONSTRUCTOR*/
//Set up your constants for this widget
var widget1 = "Professional";
var widget2 = "Work Experience, Educational History, and Resume Download";
//first you grab your old element
var widgetSmallElement = document.getElementById("widget1");
//then you make your new elements
var outer = document.createElement("div");
outer.setAttribute('class','middleSmall');
var inner = document.createElement("div");
inner.setAttribute('class','innerSmall');
var heading = document.createElement("h1");
heading.innerHTML = widget1;
var subHeading = document.createElement("h2");
subHeading.innerHTML = widget2;
//now you chain the above and then add them to the document
inner.appendChild(heading);
outer.appendChild(inner);
widgetSmallElement.appendChild(outer);
//This is your new, simplified hoverWidgets handler
window.hoverWidgets = function (widgetID) {
inner.replaceChild(subHeading,inner.firstChild);
};
/*THIS SECTION IS THE RETURN VALUE FROM YOUR CONSTRUCTOR*/
return function() {
//this is the function actually given to the onClick and onMouseout handler as the initializeWidgets funciton.
inner.replaceChild(heading,inner.firstChild);
};
})();
The problem you're having is due to the way you are creating your inner divs. When you create a html element in javascript by writing html as a string into a DOM node's innerHTML property the old node that was there is deleted, and a new one is created to replace it.
When that old node is deleted the event handlers that were attached to it are also deleted, so when your mouseovers are running, they're actually deleting and re-creating the various inner divs, and their existing event handlers. Because of this, the onclick handler you assigned in the html doesn't exist for those inner nodes.
It's usually a bad idea to add html to a document by directly writing into the innerHTML property.
I've rewritten the solution so that it creates the nodes natively in JavaScript, and then re-written the initializeWidgets and hoverWidgets functions to simply swap between the <h1> and <h2> nodes inside the inner div.
I've put the whole thing inside a closure to stop it polluting the global scope with anything but the initializeWidgets and hoverWidgets functions. This implementation will only work if it's registered as a handler for the onLoad function, since the constructor part of it will need to run and create those two functions before the html tries to attach them as event handlers.
If you find this solution to complex for your needs, you should still be able to create your own solution by using javascript to create and manipulate the divs and h1/h2 tags natively rather than doing it by writing directly into the innerHTML property.
Here's a guide on how to do that
If your on-click isn't somehow referencing the DIV specifically, could always add the onclick to the other nested divs.... otherwise, you need to post more of your html.
This is the same question as this:
Referring to a div inside a div with the same ID as another inside another
except for one thing.
The reason there are two elements with the same ID is because I'm adding rows to a table, and I'm doing that by making a hidden div with the contents of the row as a template. I make a new div, copy the innerhtml of the template to my new div, and then I just want to edit bits of it, but all the bits have the same ID as the template.
I could dynamically create the row element by element but it's a VERY complex row, and there's only a few things that need to be changed, so it's a lot easier to just copy from a template and change the few things I need to.
So how do I refer to the elements in my copy, rather than the template?
I don't want to mess up the template itself, or I'll never be able to get at the bits for a second use.
Or is there another simpler way to solve the problem?
It will probably just be easiest when manipulating the innerHtml to do a replace on the IDs for that row. Maybe something like...
var copiedRow = templateRow.innerHTML.replace(/id=/g,"$1copy")
This will make the copied divs be prefixed with "copy". You can develop this further for the case that you have multiple copies by keeping a counter and adding that count variable to the replace() call.
When you want to make a template and use it multiple times its best to make it of DOM, in a documentFragment for example.
That way it doesn't respond to document.getElementById() calls in the "live" DOM.
I made an example here: http://jsfiddle.net/PM5544/MXHRr/
id's should be unique on the page.
PM5544...
In reality, there's no use to change the ID to something unique, even though your document may not be valid.
Browsers' selector engines treat IDs pretty much the same as class names. Thus, you may use
document.querySelector('#myCopy #idToLookFor');
to get the copy.
IDs on a page are supposed to be unique, even when you clone them from a template.
If you dynamically create content on your page, then you must change the id of your newly cloned elements to something else. If you want to access all cloned elements, but not the template, you can add a class to them, so you can refer to all elements with that class:
var clonedElement = template.cloneNode(yes); // make a deep copy
clonedElement.setAttribute("id", "somethingElse"); // change the id
clonedElement.setAttribute("class",
clonedElement.getAttribute("class") + " cloned"
);
To access all cloned elements by classname, you can use the getElementsByClassName method (available in newer browsers) or look at this answer for a more in-depth solution: How to getElementByClass instead of GetElementById with Javascript?
Alternatively, if you have jQuery available, you can do this is far less lines of code:
$("#template").clone().attr("id","somethingElse")
.addClass("cloned").appendTo("#someDiv");
The class lookup is even simpler:
$(".cloned").doSomethingWithTheseElements();
Try to avoid using IDs in the child elements of the cloned structure, as all ids of the cloned element should be changed before adding the clone to the page. Instead, you can refer to the parent element using the new id and traverse the rest of the structure using classnames. Class names do not need to be unique, so you can just leave them as they are.
If you really must use ID's (or unique "name" attributes in form fields), I can strongly suggest using a framework like jQuery or Prototype to handle the DOM traversal; otherwise, it is quite a burden to resolve all the cross-browser issues. Here is an example of some changes deeper in the structure, using jQuery:
$("#template").clone().attr("id","somethingElse")
.addClass("cloned") // add a cloned class to the top element
.find("#foo").attr("id","bar").end() // find and modify a child element
.appendTo("#someDiv"); // finally, add the node to the page
Check out my ugly but functional cheese. I wrote a function that works like getelementbyid, but you give it a start node instead of the document. Works like a charm. It may be inefficient but I have great faith in the microprocessors running today's browsers' javascript engines.
function getelement(node, findid)
{
if (node)
if (node.id)
if (node.id == findid)
return node;
node = node.firstChild;
while(node)
{
var r = getelement(node, findid);
if (r != null)
return r;
node = node.nextSibling;
}
return null;
}
When you copy the row, don't you end up having a reference to it? At that point can't you change the ID?
I want to know the most effctive way to dynamically update, insert or remove elements from an html page.
The outcome of this is that, I can change an input element into a div element and vice versa based on a user action.
eg
<form><input type="text" value="Value to save"/></form>
and based on some event, i will change that to
<form><div>Value to Save</div></form>
Tx
I think you could do this task this way (pure JS, without using external frameworks):
//retrieve the <form>
var form = document.getElementsByTagName('form')[0];
//retrieve the <input> inside the form
var input = form.getElementsByTagName('input')[0];
//create a new <div> DOM element
var newElement = document.createElement('div');
//The containing div text is equal to the input value (your case)
newElement.innerHTML = input.value;
//simple empty the form by set innerHTML = ""
form.innerHTML = "";
//append the <div> inside the form
form.appendChild(newElement);
By the way, I sugges you, if you want to manipulate DOM and do stuff like these in an easier way, learn how to do it by using frameworks like jQuery or mootools ;)
This is a general description:
Creating: You can create elements with document.createElement and then use one the various insertion methods to the insert the element at a certain position (e.g. Node.appendChild). You need to get references to related nodes first.
Most browser also support the innerHTML attribute for elements. You can set that attribute to an HTML(or text) string and the content of the element will be updated accordingly.
Updating: It depends on which data you want to update. E.g. an input element has an attribute value. In order to change the value of a text input you need to get a reference to that element first, then you can do element.value = 'new value'. For content, you can use the already mentioned innerHTML attribute.
Have a look at an HTML element reference to see what attributes they have.
Deleting: You want Node.removeChild.
I suggest to also have a look at DOM traversal methods and be aware of browser differences.
Using:
element.removeChild
element.appendChild
By using these methods, you can retain references to the elements in case you want to swap them back over again. Any event handlers in place will remain attached to the elements, too.
This depends on your definition of most effective.
If you mean the simplest way, then you can use a library like jQuery and do it like this:
$('form').html('<dynamic markup>');
If you mean the most performant way you can do the following:
document.getElementByTagName('form').innerHTML = '<dynamic markup>';
I'm trying to make a simple image browser for TinyMCE which I am using in my CMS. As part of this I need to detect whether the user has selected an existing image, so I can display the "edit" form instead of the "choose an image form".
var selected_html = ed.selection.getContent();
var $elem = $(selected_html);
console.log($elem);
The first function returns the user selected text from the editor window as a string of HTML. I then would like to use jQuery (although plain javascript is just ok too) to check if this string contains an img tag before subsequently grabbing the src and title attributes for editing.
Now I've got as far as getting the html to turn into an object. But after this I can't manage to search it for the img element. After reading this (How to manipulate HTML within a jQuery variable?) I tried:
$elem.find('img');
But it just comes out as an "undefined" object...
I think I'm missing something fairly obvious here (it is getting late), but after an hour I still can't figure out how to grab the img tag from the selection. :(
Many thanks in advance.
Because the <img> is at the root of the jQuery object, you need to use .filter() instead of .find().
$elem.filter('img');
The .filter() method looks at the element(s) at the top level of the jQuery object, while .find() looks for elements nested in any of the top level elements.
If you're not sure beforehand where the target element will be, you could place the HTML into a wrapper <div> to search from. That way the HTML given will never be at the top.
var selected_html = ed.selection.getContent();
var $elem = $('<div>').html(selected_html);
var $img = $elem.find('img');
Try to see what is really inside your $elem variable. Just do a console.log($elem) using both Firefox and Firebug and you should be able to manage quite alright! ;)