Trying to copy the text of a web page into Excel - excel

I am using this code to open Edge so that I can grab the text of the web page. I can get Edge to open and display the page, and I can copy the text of the page manually and then finish executing the code and it pastes it as needed, but I'm not at the skill level to figure out how to make Excel copy the text. It's the last step that I need.
Any help would be greatly appreciated.
I'm looking to copy the entire page, and not just one section of it.
Sub LOADEdge()
Set obj = CreateObject("Shell.Application")
obj.ShellExecute "microsoft-edge:https://maps.googleapis.com/maps/api/directions/json?origin=Disneyland&destination=Universal+Studios+HOllywood&key=your_API_Key"
'This is where I manually grab the text from the page.
MsgBox "wiating"
Worksheets("sheet1").Range("A1").PasteSpecial Page = xlpagevalues
End Sub
I've tried to research how to copy web pages but most require Selenium to be installed and I can't get my IT department to do that for me. I've found work arounds on how to open the browser but I can't find anything that shows me how to copy the text of the page other than:
IE.ExecWB 17, 0 '// SelectAll
IE.ExecWB 12, 2 '// Copy selection
but that doesn't work.

While it is possible, I am not sure that VBA is a good tool for web scraping. I tried it a few times, and it never gave me the results I was looking for. Here are some alternatives to look at:
Using Google Sheets
Using Python
There is also an add-in for Microsoft One-Note that lets you save the entire content of a webpage to OneNote, and it works quite well.

It is node.js with exceljs and axios library.
This code will write excel file.
It needs to paste your 'API_KEY' string at line 4.
Save as get-map.js
const axios = require('axios')
const Excel = require('exceljs');
const API_KEY ='your_API_Key'
const columnName = (index) => {
var cname = String.fromCharCode(65 + ((index - 1) % 26))
if (index > 26)
cname = String.fromCharCode(64 + (index - 1) / 26) + cname
return cname;
}
const getMapInformation = async (origin, destination) => {
try {
origin = origin.replace(/ /g,"+") // replace from space to '+'
destination = destination.replace(/ /g,"+") // replace from space to '+'
const resp = await axios.get(
`https://maps.googleapis.com/maps/api/directions/json?origin=${origin}&destination=${destination}&key=${API_KEY}`
);
return Promise.resolve(resp.data);
} catch (err) {
return Promise.reject(error);
}
}
const writeMapInformation = (fileName, data) => {
try {
const workbook = new Excel.Workbook();
// make workbook with 'map' name
const ws = workbook.addWorksheet("map")
// Start Cell A1 for title column
let headerColumn = 1
let row_number = 1
// #1 geocoded_waypoints section
for (let waypoint in data.geocoded_waypoints) {
for (let key in data.geocoded_waypoints[waypoint]) {
ws.getCell(columnName(headerColumn) + String(row_number)).value = key
ws.getCell(columnName(headerColumn + 1) + String(row_number)).value = data.geocoded_waypoints[waypoint][key]
row_number++;
}
row_number++; // skip one row
}
// #2 routes section
for (let index in data.routes) {
for (let key in data.routes[index]) {
if(key == 'bounds') {
ws.getCell(columnName(headerColumn) + String(row_number)).value = key
for (let sub_key in data.routes[index][key]) {
ws.getCell(columnName(headerColumn + 1) + String(row_number)).value = sub_key
ws.getCell(columnName(headerColumn + 2) + String(row_number)).value = data.routes[index][key][sub_key]
row_number++;
}
}
if(key == 'summary') {
ws.getCell(columnName(headerColumn) + String(row_number)).value = key
ws.getCell(columnName(headerColumn + 1) + String(row_number)).value = data.routes[index][key]
}
if(key == 'legs') {
ws.getCell(columnName(headerColumn) + String(row_number)).value = key
row_number++;
for (let sub_key in data.routes[index][key][0]) {
if(sub_key != 'steps') {
ws.getCell(columnName(headerColumn + 1) + String(row_number)).value = sub_key
ws.getCell(columnName(headerColumn + 2) + String(row_number)).value = data.routes[index][key][0][sub_key]
row_number++;
}
if(sub_key == 'steps') {
for (let sub_child_key in data.routes[index][key][0][sub_key]) {
for (let grand_key in data.routes[index][key][0][sub_key][sub_child_key]) {
// skip two fileds (html_instructions & polyline)
if(grand_key != 'html_instructions' && grand_key != 'polyline') {
ws.getCell(columnName(headerColumn + 1) + String(row_number)).value = grand_key
ws.getCell(columnName(headerColumn + 2) + String(row_number)).value = data.routes[index][key][0][sub_key][sub_child_key][grand_key]
row_number++;
}
}
}
}
}
row_number++;
}
}
}
workbook.xlsx
.writeFile(fileName)
.then(() => {
console.log('file created');
})
.catch(err => {
console.log(err.message);
});
} catch (err) {
console.log(err.message);
}
}
// get map direction (from, to)
getMapInformation('Disneyland', 'Universal Studios Hollywood')
.then((result) => {
writeMapInformation('map.xlsx', result)
})
Install libraries
npm install axios exceljs
Run it
node get-map.js
Results

Related

Invalid cell coordinate AAAA4 PhpOffice\PhpSpreadsheet\Exception error on Laravel excel

I do excel on Laravel before but I didn't face this error. I check my excel export file. Everything is okay there. When I get so many data then It show this error. How could I solve this? Thanks in advance.
D:\xampp\htdocs\bangla-tissue-latest-version-update\vendor\phpoffice\phpspreadsheet\src\PhpSpreadsheet\Cell\Coordinate.php:40
public static function coordinateFromString($pCoordinateString)
{
if (preg_match('/^([$]?[A-Z]{1,3})([$]?\\d{1,7})$/', $pCoordinateString, $matches)) {
return [$matches[1], $matches[2]];
} elseif (self::coordinateIsRange($pCoordinateString)) {
throw new Exception('Cell coordinate string can not be a range of cells');
} elseif ($pCoordinateString == '') {
throw new Exception('Cell coordinate can not be zero-length string');
}
throw new Exception('Invalid cell coordinate ' . $pCoordinateString);
}
next portion, Where I write about 26 index. But It doesn't work at all.
I trying to search many way but doesn't work at all.
public static function stringFromColumnIndex($columnIndex = 0)
{
static $indexCache = array();
if (!isset($indexCache[$columnIndex])) {
// $indexValue = $columnIndex;
// $base26 = null;
// do {
// $characterValue = ($indexValue % 26) ?: 26;
// $indexValue = ($indexValue - $characterValue) / 26;
// $base26 = chr($characterValue + 64) . ($base26 ?: '');
// } while ($indexValue > 0);
// $indexCache[$columnIndex] = $base26;
if($columnIndex < 26)
{
$indexCache[$columnIndex] = chr(65 + $columnIndex);
}elseif($columnIndex < 720)
{
$indexCache[$columnIndex] = chr(64 + ($columnIndex/26)).chr(65 + ($columnIndex % 26)) ;
}else
{
$indexCache[$columnIndex] = chr(64 + (($columnIndex-26)/676)).chr(65 + ((($columnIndex - 26)% 676) /26)). chr(65 + ($columnIndex % 26));
}
}
return $indexCache[$columnIndex];
}
I want to show multiple tables data on excels.

getFileAsync causing Excel to crash

I'm building an office-js add-in for Excel. I need to upload the current workbook to a back end server. I've implemented an example from the Micrsoft Documentation, which seems to work fine the first time I call it, but on subsequent calls, it causes Excel to crash. I'm using Excel 365 version 1812 (build 11126.20132)
Here is the link to the example in the MS docs:
https://learn.microsoft.com/en-us/javascript/api/office/office.document
There are many examples on this page, to find the one I'm working from search for "The following example gets the document in Office Open XML" I've included the example below for ease of reference.
The code just get's the current file and dumps the characters to the console's log. It works fine the first but crashes Excel the second time--after it has shown the length of FileContent.
export function getDocumentAsCompressed() {
Office.context.document.getFileAsync(Office.FileType.Compressed, { sliceSize: 65536 /*64 KB*/ },
function (result) {
if (result.status == "succeeded") {
// If the getFileAsync call succeeded, then
// result.value will return a valid File Object.
var myFile = result.value;
var sliceCount = myFile.sliceCount;
var slicesReceived = 0, gotAllSlices = true, docdataSlices = [];
console.log("File size:" + myFile.size + " #Slices: " + sliceCount);
// Get the file slices.
getSliceAsync(myFile, 0, sliceCount, gotAllSlices, docdataSlices, slicesReceived);
}else {
console.log("Error:", result.error.message);
}
});
}
function getSliceAsync(file, nextSlice, sliceCount, gotAllSlices, docdataSlices, slicesReceived) {
file.getSliceAsync(nextSlice, function (sliceResult) {
if (sliceResult.status == "succeeded") {
if (!gotAllSlices) { // Failed to get all slices, no need to continue.
return;
}
// Got one slice, store it in a temporary array.
// (Or you can do something else, such as
// send it to a third-party server.)
// console.log("file part",sliceResult.value.data)
docdataSlices[sliceResult.value.index] = sliceResult.value.data;
if (++slicesReceived == sliceCount) {
// All slices have been received.
file.closeAsync();
onGotAllSlices(docdataSlices);
}
else {
getSliceAsync(file, ++nextSlice, sliceCount, gotAllSlices, docdataSlices, slicesReceived);
}
}
else {
gotAllSlices = false;
file.closeAsync();
console.log("getSliceAsync Error:", sliceResult.error.message);
}
});
}
function onGotAllSlices(docdataSlices) {
var docdata = [];
for (var i = 0; i < docdataSlices.length; i++) {
docdata = docdata.concat(docdataSlices[i]);
}
var fileContent = new String();
for (var j = 0; j < docdata.length; j++) {
fileContent += String.fromCharCode(docdata[j]);
}
console.log("fileContent.length",fileContent.length)
// Now all the file content is stored in 'fileContent' variable,
// you can do something with it, such as print, fax...
}
Here is the result
File size:21489 #Slices: 1
fileContent.length 21489
Original example from Microsoft documentation (https://learn.microsoft.com/en-us/javascript/api/office/office.document)
// The following example gets the document in Office Open XML ("compressed") format in 65536 bytes (64 KB) slices.
// Note: The implementation of app.showNotification in this example is from the Visual Studio template for Office Add-ins.
function getDocumentAsCompressed() {
Office.context.document.getFileAsync(Office.FileType.Compressed, { sliceSize: 65536 /*64 KB*/ },
function (result) {
if (result.status == "succeeded") {
// If the getFileAsync call succeeded, then
// result.value will return a valid File Object.
var myFile = result.value;
var sliceCount = myFile.sliceCount;
var slicesReceived = 0, gotAllSlices = true, docdataSlices = [];
app.showNotification("File size:" + myFile.size + " #Slices: " + sliceCount);
// Get the file slices.
getSliceAsync(myFile, 0, sliceCount, gotAllSlices, docdataSlices, slicesReceived);
}
else {
app.showNotification("Error:", result.error.message);
}
});
}
function getSliceAsync(file, nextSlice, sliceCount, gotAllSlices, docdataSlices, slicesReceived) {
file.getSliceAsync(nextSlice, function (sliceResult) {
if (sliceResult.status == "succeeded") {
if (!gotAllSlices) { // Failed to get all slices, no need to continue.
return;
}
// Got one slice, store it in a temporary array.
// (Or you can do something else, such as
// send it to a third-party server.)
docdataSlices[sliceResult.value.index] = sliceResult.value.data;
if (++slicesReceived == sliceCount) {
// All slices have been received.
file.closeAsync();
onGotAllSlices(docdataSlices);
}
else {
getSliceAsync(file, ++nextSlice, sliceCount, gotAllSlices, docdataSlices, slicesReceived);
}
}
else {
gotAllSlices = false;
file.closeAsync();
app.showNotification("getSliceAsync Error:", sliceResult.error.message);
}
});
}
function onGotAllSlices(docdataSlices) {
var docdata = [];
for (var i = 0; i < docdataSlices.length; i++) {
docdata = docdata.concat(docdataSlices[i]);
}
var fileContent = new String();
for (var j = 0; j < docdata.length; j++) {
fileContent += String.fromCharCode(docdata[j]);
}
// Now all the file content is stored in 'fileContent' variable,
// you can do something with it, such as print, fax...
}
// The following example gets the document in PDF format.
Office.context.document.getFileAsync(Office.FileType.Pdf,
function(result) {
if (result.status == "succeeded") {
var myFile = result.value;
var sliceCount = myFile.sliceCount;
app.showNotification("File size:" + myFile.size + " #Slices: " + sliceCount);
// Now, you can call getSliceAsync to download the files,
// as described in the previous code segment (compressed format).
myFile.closeAsync();
}
else {
app.showNotification("Error:", result.error.message);
}
}
);
Since you're using Excel, have you tried the CreateWorkbork API? Might be a good workaround if the Document API has a bug, like Xuanzhou indicated earlier.
Here's a CreateDocument snippet that you can load into Script Lab. It shows how to create a Workbook copy based on an existing file.
Hope all that is helpful.
We already have a fix for it now. But the fix still need some time to go to production. Please try it several days later and let me know if the issue still exists. Thanks.

How to properly use hideDayPicker() public function from react-day-picker-input?

I'm trying to hide my calendar by clicking the react-day-picker-input element again (it will open when you click it by default). For that, I have a state that records true or false when you click the element. The problem is that when I click again to hide it, it gives me the error below:
TypeError: calendarNode.hideDayPicker is not a function
I tried using showOverlay and hideDayPicker.
I saw a code that works with buttons, but fails to achieve the results when you apply onClick to the DayPickerInput component (see below).
https://codesandbox.io/s/oqm3p4j9rz
Here's my code (summarized):
onKeyPress = (event) => {
event.preventDefault();
}
dateRestriction = () => {
const date = new Date();
const nutrition_offset = date.getTimezoneOffset() + 240;
date.setMinutes(date.getMinutes() + nutrition_offset);
const year = date.getFullYear();
const month = date.getMonth() + 1;
let day = date.getDate();
if ((date.getDay() === 4) || (date.getDay() === 5)) {
if (date.getDate() < 5) {
day = ('0' + (date.getDate() + 5));
} else {
day = date.getDate() + 5;
}
}
if (date.getDay() === 6) {
if (date.getDate() < 6) {
day = ('0' + (date.getDate() + 4));
} else {
day = date.getDate() + 4;
}
}
if ((date.getDay() === 0) || (date.getDay() === 1) || (date.getDay() === 2) || (date.getDay() === 3)) {
if (date.getDate() < 7) {
day = ('0' + (date.getDate() + 3));
} else {
day = date.getDate() + 3;
}
}
let dateRestricted = this.parseDate_(year + '-' + month + '-' + day);
this.setState({
noDay: dateRestricted,
showCalendar: true
});
this.handleDayPickerInputHide();
}
handleDayPickerInputHide = () => {
const calendarNode = document.getElementsByName("date");
if (this.state.showCalendar === false) {
return;
} else {
calendarNode.hideDayPicker();
this.setState = {
showCalendar: false
}
}
}
render () {
const { selectedDay } = this.state;
return (
<div>
<DateObject
inputProps={
{className: 'dib nav pl2 pointer br3 shadow-1 dropdownButtonDate removeCursor bg-transparent pv1 mt2 typefaceFont dropdownText',
onKeyDown: this.onKeyPress,
onClick: this.dateRestriction,
name: 'date',
required: "required"}
}
value={selectedDay}
onDayChange={this.handleDayChange}
dayPickerProps={{
selectedDays: selectedDay,
disabledDays:
[
new Date(2019, 0, 1),
new Date(2019, 11, 24),
new Date(2019, 11, 25),
new Date(2019, 11, 31),
{
daysOfWeek: [0, 6],
},
{
before: new Date(this.state.noDay)
}]
}}
/>
</div>
)
}
Expected:
1. Calendar is hidden initially (default behavior)
2. Click displays calendar (default behavior)
3. Click again to hide the calendar (NEEDED)
4. Click outside hides the calendar as well (default behavior)
5. Choose a date hides the calendar as well (default behavior)
Actual results:
1. Calendar is hidden initially (default behavior)
2. Click displays calendar (default behavior)
3. Click again to hide the calendar (ERROR)
4. Click outside hides the calendar as well (default behavior)
5. Choose a date hides the calendar as well (default behavior)
FIGURED IT OUT!!!
There's no need to update the state, use DOM or attempt to run a public function from your react-app. All you need to do is update react-day-picker from your node_modules.
Man... This needs to go with their next version of react-day-picker...
Check it out!
{
key: 'handleInputClick',
value: function handleInputClick(e) {
//this.showDayPicker();
if (this.props.inputProps.onClick) {
e.persist();
this.props.inputProps.onClick(e);
if (this.state.showOverlay === false) {
this.showDayPicker();
}
if (this.state.showOverlay === true) {
this.hideDayPicker();
}
}
}
}, {
key: 'handleInputFocus',
value: function handleInputFocus(e) {
var _this5 = this;
//this.showDayPicker();
// Set `overlayHasFocus` after a timeout so the overlay can be hidden when
// the input is blurred
this.inputFocusTimeout = setTimeout(function () {
_this5.overlayHasFocus = false;
}, 2);
if (this.props.inputProps.onFocus) {
e.persist();
this.props.inputProps.onFocus(e);
}
}
Navigate to your node_modules directory and find DayPickerInput.js within ../node_modules/react-day-picker/lib/src/DayPickerInput.js
Comment out (or remove) this.showDayPicker() under 'handleInputFocus' and 'handleInputClick'.
Added the conditional below this.props.inputProps.onClick(e) (line 349)
Note: There's no need to update showOverlay, as hideDayPicker() and showDayPicker() already do that.

Excel js api large Data

I am currently working on an Excel web Add-in where I am making an ajax call which sometimes returning a very large amount of data. After the data is returned I am iterating thought the data and loading values to cells in one sync. If the returned data is big enough it would crash the add-in. I have tried loading the data to the cells in batches, meaning for every 500 rows I tried to sync and then continue loading data into cells, but after loading the first 500 rows instead of continuing after the sync it exits out of the loop. I am new to Excel Js API and I am not sure I am doing this portion of my code correctly and I am unable to find any examples of this, any help would be appreciated.
function loadExcelData() {
Excel.run(function (context) {
var WebConnectAPI = "../../Data/GetExcelData";
$("#Info").html("")
var app = context.workbook.application;
app.load("calculationMode");
return context.sync()
.then(function () {
app.suspendApiCalculationUntilNextSync();
$.when(getExcelData(WebConnectAPI)).done(function (data) {
LoadDataV3(data, context);
}).fail(function (jqXHR, textStatus, errorThrown) {
$("#Error").html("Error:" + textStatus);
console.log("jqXHr:" + jqXHR + " Status:" + textStatus + " error:" + errorThrown);
});
});
}).catch (function (error) {
console.log("Error: " + error);
$("#Error").html(error);
if (error instanceof OfficeExtension.Error) {
console.log("Debug info: " + JSON.stringify(error.debugInfo));
}
});
function LoadDataV3(data, context) {
var workSheetName = "Sheet1";
var currentWorksheet = context.workbook.worksheets.getItem(workSheetName);
if (data.data.length != 0) {
for (var x = 0; x < data.data.length; x++) {
var y = 0
if (x % 500 == 0 && x != 0) {
context.sync();
loadCellData(currentWorksheet, data.data, x, y);
}
else {
loadCellData(currentWorksheet, data.data, x, y);
}
}
return context.sync()
.then(function () {
$('#Export-Data').show();
});
}
else {
$("#Info").html("No data was returned for your specific search criteria.")
}
}
function loadCellData(currentWorksheet,excelData,x,y) {
$.each(excelData[x], function (key, value) {
if (x == 0) {
var HeaderCell = currentWorksheet.getRange(tocellAddress(y + 1, x + 1));//Start on first row
var Cell = currentWorksheet.getRange(tocellAddress(y + 1, x + 2));//Start on second row
HeaderCell.values = [[key]];
Cell.values = [[value]];
HeaderCell.format.autofitColumns();
Cell.format.autofitColumns();
}
else {
var Cell = currentWorksheet.getRange(tocellAddress(y + 1, x + 2));//start on third row
Cell.values = [[value]];
Cell.format.autofitColumns();
}
y++;
});
}
The approach I took in my above code was not correct due to my misunderstanding of how the context.sync actually worked, as mentioned in the js api documentation you should really just need one context sync. For loading large amounts of data the data should be queued up in chunks of ranges and then sync once all data is queued.

Sharepoint drop down list doesn't display properly for more than 20 items with Internet Explorer

I had this problem on my three sharepoint sites and i manage to resolve this problem by modifying CSS code (cf http://social.msdn.microsoft.com/Forums/en-US/sharepoint2010general/thread/64796605-bcbb-4a87-9d8d-9d609579577f/) on two of them.
I don't know why this doesn't work on my third one which has same updates, same CSS and same html code...
I tried several solutions such as adding indesign="true"(this can't be use because the lists got more than 75 columns). cf
http://www.codeproject.com/Articles/194254/Advanced-fixing-SharePoint-2010-large-lookup-dropd
The only solutions i found required javascript but i don't really want to use it...
If someone have another solution to propose, i would really appreciate.
EDIT:
Thanks to moontear i found something quite great.
I replace the beginning of the script by :
$(document).ready(function () {
$('.ms-lookuptypeintextbox').each(function(){
OverrideDropDownList($(this).attr('title'));
});
// Main Function
function OverrideDropDownList(columnName) {
...
By this way, i just have to include the script and don't care about what columns got problems...
This script seems to be quite great to solve this problem...
Thank you moontear for your comment
The original script come from : http://sharepointegg.blogspot.de/2010/10/fixing-sharepoint-2010-lookup-drop-down.html
$(document).ready(function () {
$('.ms-lookuptypeintextbox').each(function(){
OverrideDropDownList($(this).attr('title'));
});
// Main Function
function OverrideDropDownList(columnName) {
// Construct a drop down list object
var lookupDDL = new DropDownList(columnName);
// Do this only in complex mode...
if (lookupDDL.Type == "C") {
// Hide the text box and drop down arrow
lookupDDL.Obj.css('display', 'none');
lookupDDL.Obj.next("img").css('display', 'none');
// Construct the simple drop down field with change trigger
var tempDDLName = "tempDDLName_" + columnName;
if (lookupDDL.Obj.parent().find("select[ID='" + tempDDLName + "']").length == 0) {
lookupDDL.Obj.parent().append("<select name='" + tempDDLName + "' id='" + tempDDLName + "' title='" + tempDDLName + "'></select>");
lookupDDL.Obj.parent().find("select[ID='" + tempDDLName + "']").bind("change", function () {
updateOriginalField(columnName, tempDDLName);
});
}
// Get all the options
var splittedChoices = lookupDDL.Obj.attr('choices').split("|");
// get selected value
var hiddenVal = $('input[name=' + lookupDDL.Obj.attr("optHid") + ']').val();
if (hiddenVal == "0") {
hiddenVal = lookupDDL.Obj.attr("value")
}
// Replacing the drop down object with the simple drop down list
lookupDDL = new DropDownList(tempDDLName);
// Populate the drop down list
for (var i = 0; i < splittedChoices.length; i++) {
var optionVal = splittedChoices[i];
i++;
var optionId = splittedChoices[i];
var selected = (optionId == hiddenVal) ? " selected='selected'" : "";
lookupDDL.Obj.append("<option" + selected + " value='" + optionId + "'>" + optionVal + "</option>");
}
}
}
// method to update the original and hidden field.
function updateOriginalField(child, temp) {
var childSelect = new DropDownList(child);
var tempSelect = new DropDownList(temp);
// Set the text box
childSelect.Obj.attr("value", tempSelect.Obj.find("option:selected").val());
// Get Hidden ID
var hiddenId = childSelect.Obj.attr("optHid");
// Update the hidden variable
$('input[name=' + hiddenId + ']').val(tempSelect.Obj.find("option:selected").val());
}
// just to construct a drop down box object. Idea token from SPServces
function DropDownList(colName) {
// Simple - when they are less than 20 items
if ((this.Obj = $("select[Title='" + colName + "']")).html() != null) {
this.Type = "S";
// Compound - when they are more than 20 items
} else if ((this.Obj = $("input[Title='" + colName + "']")).html() != null) {
this.Type = "C";
// Multi-select: This will find the multi-select column control on English and most other languages sites where the Title looks like 'Column Name possible values'
} else if ((this.Obj = $("select[ID$='SelectCandidate'][Title^='" + colName + " ']")).html() != null) {
this.Type = "M";
// Multi-select: This will find the multi-select column control on a Russian site (and perhaps others) where the Title looks like '????????? ????????: Column Name'
} else if ((this.Obj = $("select[ID$='SelectCandidate'][Title$=': " + colName + "']")).html() != null) {
this.Type = "M";
} else
this.Type = null;
} // End of function dropdownCtl
});
The code in the solution above is great, but it has two major shortcomings:
1) Doesn't play nicely with Required Field Validators
2) Doesn't play nicely with Cascading Dropdowns via SPServices.
I've fixed these two problems at work, and put my solution here:
http://craigvsthemachine.blogspot.com/2013/04/fixing-complex-dropdown-bug-in.html

Resources