jQuery not recognizing dynamically-inserted DOM element? - javascript

Yet another "dynamically change select options based on parent option selected" question.
I have the select values changing dynamically - but after my rails render of the child select - I lose the Chosen styling (the jQuery Chosen plugin) and I cannot operate on this newly injected element.
Here is where the code is right now - it's gone through dozens of iterations -
$('#vendor_block').on('change', '#vendor_name', function(){
overlay.show();
var v = $(this).val();
vndr_json = {};
vndr_json["v"] = v;
$.ajax({
type : "GET",
url : "/purchaseorders/upd_vndr_locs",
data : vndr_json,
success: function(res) {
overlay.hide();
// console.log(typeof(res),res);
jQuery("#vndrAddrOpts").html(res);
}
});
$("#vendor_addresses").chosen(); // WHY DON'T YOU RENDER CHOSEN BOX?!
});
I get this new select box on my page - and I want to fire an event when it changes, but the DOM has already loaded, so it doesn't "see" this element I'm guessing.
Also - the Chosen plugin doesn't render on the element. Not sure why - probably the same reason.
I'm using jQuery's .on() like every post on SO says I should. But it doesn't "reload" the elements inside this parent (and 'vendor_block' is the parent div of 'vendor_name' and 'vendor_addresses').
You can see the difference in the select boxes here:
Any help would be great?
UPDATE:
Adding before and after HTML :
<div id="vndrAddrOpts">
<select class="chzn-select vndrLocs span12" id="vendor_addresses" name="vendor_addresses"><option value="">Select Location</option></select>
</div>
That is the raw HTML - but Chosen does the following when the DOM loads:
<div id="vendor_addresses_chzn" class="chzn-container chzn-container-single chzn-with-drop chzn-container-active" style="width: 100%; margin-bottom: 10px;" title=""><span>Select Location</span><div><b></b></div><div class="chzn-drop"><div class="chzn-search"><input type="text" autocomplete="off"></div><ul class="chzn-results"><li id="vendor_addresses_chzn_o_0" class="active-result result-selected highlighted" style="">Select Location</li></ul></div></div>
This is all fine and well - this is what's supposed to happen.
This is the raw HTML after the select box has been injected:
<div id="vndrAddrOpts">
<select class="chzn-select vndrLocs span12" id="vendor_addresses" name="vendor_addresses"><option value="">Select Location</option></select>
</div>
And here is the rendered box - sans Chosen stuff.
<select class="chzn-select vndrLocs span12" id="vendor_addresses" name="vendor_addresses"><option value="">Select Location</option><option value="532757b4963e6505bc000003">Honolulu</option>
<option value="532768d0963e6505bc000004">Waipahu</option></select>

I found the answer here :
Is there a way to dynamically ajax add elements through jquery chosen plugin?
I actually was approaching this problem in an overly complex way - trying to inject and element instead of just starting with the element and adding options to it.
My AJAX looks like this now:
$.ajax({
type : "GET",
url : "/purchaseorders/upd_vndr_locs",
data : vndr_json,
success: function(res) {
overlay.hide();
var va = $('#vendor_addresses');
// console.log(typeof(res),res);
for (var i=0; i < res.length; i++) {
va.append(
$('<option></option>')
.val(res[i].id)
.html(res[i].name)
);
}
va.trigger("liszt:updated");
// jQuery("#vndrAddrOpts").html(res);
}
});
So instead of even worrying about rebuilding the chosen element from an injected element - we just use the built-in "updated" trigger and it works great.

You are inserting the result of your ajax call into the DOM it's success callback, which is executed whenever it finishes (independent of the script's execution). In this case, your ajax request is being made, the code after it begins executing, and then the callback. The odds of the success callback being called before the next line of code are slim, as the ajax call is an http request which takes much longer than a line of JavaScript executing.
You want to put the code in the success call back, such as:
$.ajax({
type : "GET",
url : "/purchaseorders/upd_vndr_locs",
data : vndr_json,
success: function(res) {
overlay.hide();
$("#vndrAddrOpts").html(res);
$("#vendor_addresses").chosen();
}
});

I think the chosen method is firing before the element has actually been rendered on the page, and jQuery can't find it. Try putting $("#vendor_addresses").chosen(); as part of the AJAX success callback. Failing that, try commenting out the chosen() method, running the AJAX script, then manually running the chosen() method. If it works that way, you have to delay it a little bit.
EDIT:
Actually, looking more closely at your code, it appears you're using an ID tag instead of a class. Do multiple HTML elements have the #vendor_address id? If so, use a class instead, and use $('.vendor_addresses').last().chosen();. If you use an ID, and use an ID selector, jQuery will pick the first element if finds with that ID, and stop there.
Lesson to be learned? Use an ID for UNIQUE elements, and classes for multiple elements of the same 'class'.

Related

QBO V3 Behavior order of operations

I'm running into a problem creating a "depends" behavior where the object to depend on is an <option/> tag that has not loaded up yet because it loads up via an ajax call. There appears to be a race condition where the depends behavior is trying to load up before the ajax call is complete. Here is the element markup that failing to load the depends behavior:
<div class="control-group" data-behavior="Depend" data-depend-options="{{'depends': 'W9', 'required': true}}">
"W9" will be the ID of the <option/> tag. This option tag will be loaded up by the following code:
<select name="ProfessionalLicenseType" id="ProfessionalLicenseType" class="required" data-behavior="Dropdown" data-dropdown-options="{{ 'type': 'ObjectType', 'data': {{ 'Object': 'ProfessionalLicense' }}, 'selected': '{AttachmentType}', 'id': 'ObjectType' }}">
Should I not use the HTML markup to create the dependency behavior? Should I instead try to use javascript to create this dependency?
Thanks in advance.
The Depend behavior support binding to a select tag's value, rather than an option tag:
<div class="control-group" data-behavior="Depend" data-depend-options="{{'depends': 'ProfessionalLicenseType=W9', 'required': true}}">
The Depend behavior, on initialization, will check the value of ProfessionalLicenseType, and find that it's empty, disabling/hiding your dependent div tag. It will also add a change event handler to the ProfessionalLicenseType dropdown (qbo.Depends.js line 45):
source.addEvent('change', qbo3.dependencyCheck.pass([source, element, options, depends]));
The Dropdown behavior will later (asynchronously) load your options via AJAX, and if the behavior's options.selected is set, will set the matching option.selected=true (qbo.Dropdown.js line 105):
if ((row[value] || row) == options.selected) {
target.options[target.options.length - 1].selected = true;
target.defaultIndex = target.options.length - 1;
target.fireEvent('change');
}
The to this working is the target.fireEvent('change') noted above; that will trigger the Depend behavior to re-evaluate the dependency and react appropriately.

CKEditor SetData JQuery

I have a DropDownList where onChange sets the content of the TextArea which is my CKEditor control.
When the editor is not in use I run this bit of code for onChange:
$(".setBody").change(function() {
//
var className = "." + $(this).attr("sExternalId");
var text = $(this).val();
//
$(className).val(text);
});
I'm not very experienced with Javascript/JQuery and I just can't figure out how to perform the same using CKEditor's setData() function. This is what I've tried:
$(".setCKBody").change(function() {
//
var className = "." + $(this).attr("sExternalId");
var text = $(this).val();
//
var editor = $(className).ckeditorGet();
editor.setData(text, function() {
alert("The content was set");
});
});
$(".setCKBody").change(function() {
//
var className = "." + $(this).attr("sExternalId");
var text = $(this).val();
//
CKEDITOR.instances[$(className)].setData(text, function() {
alert("The content was set");
});
});
Am I close? I think one of the main limitations is that I have multiple editor controls with the same id and name, only the class can tell them apart which is why I'm using that with the JQuery. I've tried working through some examples online, but I'm not sure how to apply them to this scenario - that's probably my inexperience coming through there...
Thanks.
EDIT
This is how my textarea and dropdownlist appears in view source:
<textarea class="editArea M3" cols="20" id="Body" name="Body" rows="5">
*some text*
</textarea>
<select class="setCKBody" id="Templates" name="Templates" sExternalId="M3">
<option value="some value">Option 1</option>
<option value="some value">Option 2</option>
</select>
The onChange event above is triggered from the dropDownList changing and is linked to the textArea via the "sExternalId" attribute. I realised I used "id" as the attribute name in the example above which was in error, so I changed that.
I use this to set it as a CKEditor control:
<script>CKEDITOR.replaceAll('editArea');</script>
I have between 2 to 6 textarea controls on the same page, created with razor like this:
#Html.TextAreaFor(m => m.Body, new { #class = "span12 editArea " + Model.ExternalId, rows = 5 })
They are contained within a partial view that is used like this:
#foreach (MailTemplateModel oTemplate in Model.Templates)
{
#Html.Partial("_MailPartial", oTemplate)
}
This is why each text area has "Body" set as the id and name. I think this is the heart of the problem, with there being multiple elements with the same id and name CKEditor is not able to select the correct one. I've tried to do CKEDITOR.instances["className"] but that was undefined, whereas doing CKEDITOR.instances.Body did work, but would only ever return the same value.
I'm going to restructure the way the page is created to avoid this, hopefully my issues will be solved at the same time.
Here's a few pointers.
Use class="foo" if you have many things that you refer to as a group, like like here it looks like you would have many setCKBody elements you listen to for change events.
Use id="foo" if you have one single specific thing.
Using the same id and class for one element usually is not the right thing to do.
CKEDITOR.instances[xxx] <-- xxx should be a string, not a jquery object - so CKEDITOR.instances[className] might work better (I can't say not having seen your HTML).
It would help if we saw your HTML; textarea definitions and setCKBody definitions. Do you have many ckeditors and many setCKBody elements?
My original approach to this scenario was all wrong, I had a model that contained multiple mail templates and so I rendered each one via a partial view within the same page so that the user could click to edit any one of them and the details would appear in a modal popup - within the same window. What I wanted to avoid was forcing the user to navigate to another window to edit a mail template, but this lead to multiple elements having the same id and name attributes which prevented me from accessing them correctly.
I've now added a list box where the user can select a template to edit, the selected template is rendered underneath and so avoids the multiple name and id issue. Now I know there is only ever 1 CKEditor control so I can access it in my js like this:
var editor = CKEDITOR.instances.SelectedTemplate_Body;
SelectedTemplate_Body is the name and id of the element I made into a CKEditor control. The onChange function I wrote for the dropdownlist is now written like this:
$(document).ready(function() {
//
$(".setBody").change(function() {
//
var templateId = $(this).val();
//
$.ajax({
type: "GET",
url: msHost + "MailTemplates/UpdateBody",
data: { "templateId": templateId },
cache: false,
dataType: "text",
success: function (data) {
CKEDITOR.instances.SelectedTemplate_Body.setData(data);
}
})
});
});
The tempalteId attribute is the value associated to the dropdownlist selection, this lets me know which template to use for setting the content of my editor control.
MailTemplates/UpdateBody points to a public method in my MailTemplates controller which runs a search on available mail templates and matches against the template Id passed in, the method then returns the body of the template as a string.
public string UpdateBody(string tempalteId)
{
TemplateQuery oQuery;
//
oQuery = new TemplateQuery();
oQuery.Execute();
foreach (MailTemplate oTemplate in oQuery.Results)
if (oTemplate.Id.Equals(templateId))
return oTemplate.Body;
//
return string.Empty;
}
This line replaces the contents of the CKEditor control with the response from the controller method.
CKEDITOR.instances.SelectedTemplate_Body.setData(data);
Thanks #Nenotlep for trying to help out, you gave me a few things to think about there.

Getting element by tag name after getting a page using jQuery $.get

I am requesting a full page using $.get in jQuery and would like to get the content of a specific element. Separately, here is how things look:
$.get( "/page.html").done(function( data ) {
// get textArea.
});
and I want to get:
document.getElementByTagName("textArea")[0].value;
but I can't do getElementByTagName on data so what is the best way to do this?
I tried using find but that did not work so I ended up using filter and that returned the value of textArea that I needed:
$.get( "/page.html").done(function( data ) {
var textArea = $(data).filter("textarea")[0].innerText;
});
It's slightly different of what you are doing but i think it can help. You can call .load instead of get and add the whole page to a div say <div id="mydiv"></div>
var value;
$('#mydiv').load('xyz.html',function(){value=$('#mydiv').find('#mytextarea').val()})
however if you do not want mydiv to show you can hide at the beginning once the main page gets loaded and if you also don't want this div on your page you can remove it after the above task is performed.
$(document).ready(function(){
$('#mydiv').hide();
var value;
$('#mydiv').load('xyz.html',function(){value=$('#mydiv').find('#mytextarea').val()});
$('#mydiv').remove();
})
//str represents page.html
var str = 'gibberish gibberish <textarea class="test">hello world</textarea>gibberish';
$.each( $.parseHTML(str), function( i, el ) {
if(el.firstChild) console.log(el.firstChild);
});
Fiddle: http://jsfiddle.net/ez666/7DKDk/
You could try jquery load() function.
It will load from remote server and insert document into selected element.
It also allow us to specify a portion of remote document to be inserted.
Assume your remote textarea's id is "remote" and you want to fetch the remote content into a textarea which id is "local"
var result="";
$("#local").load("/page.html #remote", function(response, status, xhr){
result=$(this).find("#remote").val();
});
I'm not sure if you want to get the remote textarea and insert into the element of the current document.
If you just want to get the value of the remote textarea, you could just hide the load function invoking element
Hope this is helpful for you.
Since you're using jQuery anyway… have you tried $(data).find('textarea').first().val() yet?
This is assuming that data is a fragment. If it is not you will want to wrap it in a div or something first.

selectmenu ('refresh', true)

I get form from zend framework site and put it in response in new file in function written by jquery mobile, but I get this error:
uncaught exception: cannot call methods on selectmenu prior to
initialization; attempted to call method 'refresh' .
Code of function this file:
function addItem(id) {
$.ajax({
url:'http://zf.darina.php.nixsolutions.com/order/index/create-order-mobile',
dataType:"jsonp",
data:{id_good:id},
success:function (resp) {
console.log(resp);
$('.product-table').empty();
$('.product-table').append(resp.prod);
$('.product-table').append(resp.form);
$('.add-order-btn').button();
$('.mob-size').selectmenu('refresh', true);
$('#block').page();
}
})
}
Force initialize the selectmenu(s) first:
$('.mob-size').selectmenu(); // Initializes
$('.mob-size').selectmenu('refresh', true);
or use this for short
$('.mob-size').selectmenu().selectmenu('refresh', true);
In my case, if I was not initializing the select before invoking the 'disable' method I got the error, while if I was initializing it, the select didn't disable but duplicate itself - I tried to select the object by TAG NAME instead of by CLASS or ID NAME,
$('select').selectmenu('disable');
instead of
$('.select-class-name').selectmenu('disable');
and it worked without forced initialization
you do this in your custom refresh delegation function:
var w = $("#yourinput");
if( w.data("mobile-selectmenu") === undefined) {
// not initialized yet, lets do so
w.selectmenu();
}
w.selectmenu("refresh",true);
according to enhancement resolution here
I found the same problem, but a more involved solution. When jqm wraps the select element, it puts it in a <span> element with the same class list as the select element. I changed my reference to it so that instead of reading:
row.find(".selectCompletion").selectmenu("disable");
it now reads:
row.find("select.selectCompletion").selectmenu("disable");
Specifying that jquery should only find the select element matching the class name, rather than all elements in .row that match the class name, solved the problem.
This happened to me when cloning existing select element in order to duplicate the original section multiple times.
Calling 'refresh' for the original element, worked fine, while calling it for the cloned sections was leading to the error appearing in the question.
However, calling selectmenu() was causing a 'vandalisation' to the form, as can be seen in the following image:
Explanation: top = original. bottom = vandalised cloned element right after calling selectmenu.
Solution:
The following code solved this vandalisation problem:
cloned_elem.find('select[name=MyClass]').selectmenu().selectmenu("destroy").selectmenu();
This is not an ideal solution because we must call the first selectmenu() in order to call selectmenu("destroy"), so I would be glad to hear of a cleaner solution.

jquery .html() doesnt work

I am using jquery .html() to append some tags dynamically by calling a REST url, but it doesn't work.
<div style="display: none;" id="tables">
<form:select id="table" name="table" path="table">
<form:option value="">Choose</form:option>
<div id="tables-select">
<!-- The available tables for update will be added here -->
</div>
</form:select>
<script type="text/javascript">
function getTables(type) {
$.getJSON('/web/tables/db/' + type,
{
ajax : 'true'
},function(data) {
html ='';
var len = data.length;
for ( var i=0; i<len; i++) {
html += '<option value="'+data[i]+'" >'+data[i]+'</option>';
}
$('#tables-select').html(html);
});
}
</script>
</div>
I see the REST call is going through, but nothing is happening.
After debugging and placing break points, I see that the java script jumps from line 10 directly to end of java script function (line 20).
Any idea what is happening here?
I have bunch of same kind of functions in my page and all those work expect for this one.
EDIT:
This is how I am calling the function
<script type="text/javascript">
$(document).ready(function(){
$("#type").live('change', function(){
var type = $(this).val();
getSites(type);
getTabless(type);
});
});
</script>
As it stands, this code is wrong: there's a missing close brace for getTables(). Also, I assume you have some code somewhere that actually calls getTables(), otherwise this code does nothing except declare a function.
When debugging, the $.getJSON call will indeed execute quickly. The whole purpose of AJAX is that it is asynchronous. The debugger will thus step over the $.getJSON call without entering the callback, because the callback is invoked at a later time. If you put a breakpoint inside the callback then it should be triggered.
This whole <DIV> has a style of display:none. Unless you are somewhere calling $('#tables').show() or otherwise doing something to make it visible, then the result won't ever be visible, except in the DOM, or via a tool like firebug.
Also, you cannot put a <DIV> tag inside a <SELECT> tag. Either use an <OPTGROUP> tag for the tables-select element, or replace the whole contents of the <SELECT> element, including the initial "Choose" option.

Categories