NetSuite: How can I add a record to a Advanced PDF/HMTL Template? - netsuite

So I know that I can use the N/render to generate a template and I can use the addRecord to add record objects to the print template to make them available in the FTL.
My question is if I can do something similar when the native print button is clicked and prints a Advanced PDF/HTML Template. I know that I can catch the PRINT event in the User Event script but beyond that I am stuck.
I know the question is a little general I will add context on request. I just don't know which way to go.
EDIT: I am familiar with the option of adding a custpage field to the form and then extracting the JSON in the FTL.
In this specific situation it would be much more convenient if I could simply add a full record. Meaning I am on a Item Fulfillment print and want to add the FULL parent Sales Order record to the print so that I can access it in the FTL by salesorder.memo etc. Something similar to:
require(['N/render'], function(render) {
var renderer = render.create();
renderer.addRecord('customer', record.load({ type: record.Type.CUSTOMER, id: customer }));
})
The issue is that I only know how to do this for completely custom prints but not prints that are printed from the Native print buttons on transactions.
I need this to do line matching from the Sales Order lines to the Item Fulfillment lines and would rather do it this way if possible instead of creating a custpage and inserting a custom made object.

I refer to one of my previous answer.
Use the beforeLoad hook in a UserEventScript to set extra data on the context.form. You'll be able to access this data on the template.
/**
* #NApiVersion 2.x
* #NScriptType UserEventScript
*/
define(['N/ui/serverWidget'], function(serverWidget) {
function beforeLoad(context) {
// var request = context.request;
// var newRecord = context.newRecord;
var form = context.form;
var type = context.type;
var UserEventType = context.UserEventType;
// only execute during printing...
if (type != UserEventType.PRINT) return
var customData = {
hello: 'world'
}
var field = form.addField({
id : 'custpage_custom_data',
label: 'Custom Data',
type : serverWidget.FieldType.LONGTEXT
});
field.defaultValue = JSON.stringify(customData);
}
return {
beforeLoad: beforeLoad
};
})
You can access the data within the template through:
<#if record.custpage_custom_data?has_content>
<#assign custom_data = record.custpage_custom_data?eval />
</#if>

As per your question you want to add item sublist data also from sales order on print of item fulfillment. if it is so, then here I have used for same situation.
Steps:
Write a user event before load script on print mode only and then create a saved search to get the data of item and save it in custom field with long text type with space as label.
Customize your standard pdf template that is attached to item fulfillment record.
GoTo- customization- forms- Advanced pdf template-Customize preferred template for item fulfillment.
Add a table there with that custom field.
It will work on standard print button. I have done it for work order record. You may edit in search using sales order saved search.
UserEvent
/**
*#NApiVersion 2.x
*#NScriptType UserEventScript
*/
define(['N/record', 'N/search', 'N/ui/serverWidget'], function (record, search, serverWidget) {
function beforeLoad(scriptContext) {
try {
if (scriptContext.type == 'print') {
var currentRec = scriptContext.newRecord;
var recid = currentRec.id;
columns[0] = search.createColumn({
name: "sequence",
join: "manufacturingOperationTask",
sort: search.Sort.ASC,
label: "Operation Sequence"
});
columns[1] = search.createColumn({
name: "custevent_custom_op_name",
join: "manufacturingOperationTask",
label: "Operation Name(Instruction)"
});
columns[2] = search.createColumn({
name: "manufacturingworkcenter",
join: "manufacturingOperationTask",
label: "Manufacturing Work Center"
});
columns[3] = search.createColumn({
name: "formulanumeric",
formula: "Round({manufacturingoperationtask.runrate}*{quantity}/60,2)",
label: "BudgetHours"
});
//Creating search to get all the values for work order
var mySearch = search.create({
type: "workorder",
filters:
[
["type", "anyof", "WorkOrd"],
"AND",
["internalid", "anyof", recid],
"AND",
["mainline", "is", "T"]
],
columns: columns
});
var searchResultCount = mySearch.runPaged().count;
mySearch.run().each(function (result) {
// .run().each has a limit of 4,000 results
results.push(result);
return true;
});
//populate current printout with custom record entries
var customRecords = { columns: columns, results: results };
var columns = customRecords.columns, results = customRecords.results;
var custrecord = scriptContext.form.addField({ id: 'custpage_custrecord_to_print', type: serverWidget.FieldType.LONGTEXT, label: " " }),
custrecordArray = [];
if (results && results instanceof Array) {
for (var i = 0; i < results.length; i++) {
var singleLine = {};
for (var j = 0; j < columns.length; j++) {
if (i == i && j == 2) {
var value = results[i].getText(columns[j]);
} else {
var value = results[i].getValue(columns[j]);
}
if (j == 0 || j == 1 || j == 2) {
if (value.indexOf('.') == 0 || value.indexOf(',') == 0 || value.indexOf('-.') == 0 || value.indexOf('-,') == 0) {
value = '0' + value;
}
}
singleLine["col" + j] = (value) ? value : '';
}
custrecordArray.push(singleLine);
}
custrecord.defaultValue = JSON.stringify(custrecordArray);
}
}
} catch (e) {
log.error("ERROR", e);
}
}
return {
beforeLoad: beforeLoad,
};
});
In Advanced Pdf Template:-
<#if record.custpage_custrecord_to_print?has_content>
<#assign customrecord = record.custpage_custrecord_to_print?eval />
<table width="100%" class="second_table" style="page-break-inside: auto; width: 100%; margin-top: 2px; padding-top: 0px">
<#list customrecord as customrecord_line>
<tr width="100%" border-top="solid black" margin-top="10px" style="margin-top:10px; page-break-before: auto;">
<th width="25%" align="left" style="padding: 2px 2px;">Step</th>
<th width="25%" align="center" style="padding: 2px 2px;">Activity</th>
<th width="25%" align="center" style="padding: 2px 2px;">Run Rate(Min/Unit)</th>
<th width="25%" align="center" style="padding: 2px 2px;">BudgetHours</th></tr>
<tr width="100%" style="page-break-inside: auto;">
<td width="25%" align="left" style="padding: 2px 2px;">0${customrecord_line.col0}</td>
<td width="25%" align="center" style="padding: 2px 2px;">${customrecord_line.col2}</td>
<td width="25%" align="center" style="padding: 2px 2px;">${customrecord_line.col3}</td>
<td width="25%" align="center" style="padding: 2px 2px;">${customrecord_line.col4}</td>
</tr>
</list>
</table>
</#if>
It will be helpful.
Thanks,

Related

KnockoutJS: components binding, working with objects as data type

I'm new to Knockout JS and I find this library very powerful, but quite hard to understand sometimes. The documentation is hudge, but it's always (too) small code snippets, so it's difficult to have the big picture, unless your coding style & philosophy paradigm are the same as KO developers.
I come from angular world, and I'm used to have an array where each item is an object with properties (id, name, etc). When I click a button, I "send" this object to a new component that will render it in a form.
I'm sure I'm missing something obvious, but I don't understand how to make things work, even with plugins like ko.mapping and ko.postbox.
Does anyone can help me to find the solution? In the working code above, I've posted my 3 very specific questions in the javascript area.
EDIT: I answered to them, but I don't know if it's a best practice or not.
var
// User class to give to each property the observable capacity
User = function (rawData) {
var self = this,
data = rawData || {};
self.id = ko.observable(data.id);
self.name = ko.observable(data.name);
},
// List component. initially in a separate file
// (small modifs so all can be in the same file for this demo)
cmplist = {
viewModel: function () {
var self = this;
self.users = ko.observableArray([
new User({id: 1, name: 'John'}),
new User({id: 2, name: 'Jack'}),
new User({id: 3, name: 'Smith'})
]);
// #ANSWER 1: initialize postbox event
self.user = ko.observable(new User()).publishOn('userEdit');
self.showEdit = function (user) {
// #QUESTION 1: how do I send this object to the
// users-form component. ko.postbox?
// #ANSWER 1: change the observable
self.user(user);
console.log('show', user);
};
},
template: ''
+ '<ul data-bind="foreach: users">'
+ '<li>'
+ '<button data-bind="click: $parent.showEdit">Edit</button>'
+ ' <span data-bind="text: name"></span>'
+ '</li>'
+ '</ul>'
},
// Form component, initially in a separate file
// (small modifs so all can be in the same file for this demo)
cmpform = {
viewModel: function () {
var self = this;
// #QUESTION 2: how do I recept the object sent by the
// list?
// #ANSWER 2: make the observable subscribe to event
self.user = ko.observable(new User()).subscribeTo('userEdit');
self.save = function () {
// #QUESTION 3: how do I notify the users-list cmp
// that object has changed? ko.postbox?
window.alert('save ' + ko.toJSON(self.user()));
console.log('save');
};
},
// #ANSWER 2: call user() with parenthesis to access properties
template: ''
+ '<form>'
+ '<p>Edit user <span data-bind="text: user().name"></span> '
+ 'with id <span data-bind="text: user().id"></span></p>'
+ '<input data-bind="textInput: user().name" />'
+ '<button data-bind="click: save">Save</button>'
+ '</form>'
};
// KO bootstrap, initially in a 3rd file
// (small modifs so all can be in the same file for this demo)
ko.components.register('users-list', cmplist);
ko.components.register('users-form', cmpform);
ko.applyBindings({});
ul {
border: 1px solid blue;
list-style: none;
float: left;
}
li {
border: 1px solid green;
}
form {
border: 1px solid red;
float: right;
margin-top: 20px;
}
ul, li, form {
padding: 5px;
}
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout-postbox/0.5.2/knockout-postbox.min.js"></script>
</head>
<body>
<users-list></users-list>
<users-form></users-form>
</body>
</html>

Building Pagination in Reactjs

So the way I am building pagination in Reactjs is a bit odd, but it works for me, I, How ever would like to say show me the first 5 (1-5) on the 5th page show me 5-max. But I am unclear on how to do that.
this is what I currently have:
render: function() {
// Do we have more then one page?
if (this.props.maxPages > 0){
// We have to add one to the max pages that come back.
var pageLink = this.props.maxPages + 1;
var currentPage = this.props.currentPage;
var liElements = []
// Build [<<][<] for the user.
if (pageLink > 1) {
liElements.push(<li><<</li>);
liElements.push(<li><a href={this.pageSubtraction(currentPage, pageLink)}><</a></li>);
}
// Build the individual [x][y][z] links.
for (var i = 1; i <= pageLink; i++) {
liElements.push(<li key={i} id={i}><a href={"#posts?page="+i}>{i}</a></li>);
}
// Build the [>][>>] for the user.
if (pageLink > 1) {
liElements.push(<li><a href={this.pageAddition(currentPage, pageLink)}>></a></li>);
liElements.push(<li><a href={"#posts?page="+pageLink}>>></a></li>);
}
return (<ul className="pagination">{liElements}</ul>);
}else{
// Return nothing.
return ( <div></div> );
}
}
This will build me [<<][<][1][2][3] ... [>][>>] which is great but their is no limit on it.
At this time:
pageLink = 6 (the max number of pages - I know horrible variable name)
currentPage = 1 (the current page you are on)
So what I need is:
[<<][<][1][2][3][4][5][>][>>] Select Page 5 [<<][<][5][6][>][>>] But I am not sure if my current set up will allow me to do that.
This is a somewhat complicated algorithm (and not all of the details are provided). Rather than worrying about markup here, it might be simpler to start with a pure data structure representing what should be drawn.
Pagination = function(props){
var pages = props.maxPages + 1;
var current = props.currentPage;
var links = [];
// leading arrows
if (current > 0) {
links.push([0, "<<"]);
links.push([current - 1, "<"]);
}
for (var i=current-3; i<current+4; i++) {
if (i > 0 && i < pages) {
links.push([i, i]);
}
}
// tailing arrows
if (current < pages) {
links.push([current + 1, ">"]);
links.push([pages - 1, ">>"]);
}
return JSON.stringify(links, null, 4);
};
Now we get something like this (jsbin). You could also easily write unit tests to ensure this gives the correct results.
[
[
0,
"<<"
],
[
1,
"<"
],
[
1,
1
],
[
2,
2
],
[
3,
3
],
[
4,
4
],
[
5,
5
],
[
3,
">"
],
[
7,
">>"
]
]
Once you're getting the right data here, you can map that data through a presentation function.
function PageLink(i, char){
character = character || String(i);
return (
<li key={char}>
<a href={"#posts?page="+i}>{char}</a>
</li>
);
}
Pagination = function(props){
/* same code as before */
return links.map(function(x){
return PageLink(x[0], x[1]);
});;
};
P.s. when you do get it to match your requirements, please post an answer here so others can use it as a base for their pagination.
Below is the complete code for creating a paging option.Full post is available here.
var pager = React.createClass({
render : function(){
var li = [];
var pageCount = props.Size;
for(var i = 1; i <=pageCount; i++){
if(props.currentPage == i){
li.push(<li key={i} className="active">{i}</li>);
}
else{
li.push(<li key={i} ><a href="#" onClick={props.onPageChanged.bind(null,i)}>{i}</a></li>);
}
}
return (<ul className="pagination">{li}</ul>);
}
});
var dataGrid = React.createClass({
render : function(){
return (
<tr>
<td>{props.item.Name}</td>
<td>{props.item.Address}</td>
<td>...</td>
.....
</tr>
);
}
});
var EmployeeGridTable = React.createClass({
getInitialState : function(){
return {
Data : {
List : [],
totalPage : 0,
sortColumnName : null,
sortOrder : null,
currentPage : 1,
pageSize : 3
}
}
},
componentDidMount : function(){
this.populateData();
},
populateData: function(){
var params = {
pageSize : this.state.Data.pageSize,
currentPage : this.state.Data.currentPage
}
if(this.state.Data.sortColumnName){
params.sortColumnName = this.state.Data.sortColumnName;
}
if(this.state.Data.sortOrder){
params.sortOrder = this.state.Data.sortOrder;
}
$.ajax({
url : this.props.dataUrl,
type : 'GET',
data : params,
success : function(data){
if(this.isMounted()){
this.setState({
Data : data
});
}
}.bind(this),
error: function(err){
alert('Error');
}.bind(this)
});
},
pageChanged:function(pageNumber,e){
e.preventDefault();
this.state.Data.currentPage = pageNumber;
this.populateData();
},
sortChanged : function(sortColumnName, order , e){
e.preventDefault();
this.state.Data.sortColumnName = sortColumnName;
this.state.Data.currentPage = 1;
this.state.Data.sortOrder = order.toString().toLowerCase() == 'asc' ? 'desc':'asc';
this.populateData();
},
_sortClass : function(filterName){
return "fa fa-fw " + ((filterName == this.state.Data.sortColumnName) ? ("fa-sort-" + this.state.Data.sortOrder) : "fa-sort");
},
render : function(){
var rows = [];
this.state.Data.List.forEach(function(item){
rows.push(<dataGrid key={item.EmployeeID} item={item}/>);
});
return (
<div>
<table className="table table-responsive table-bordered">
<thead>
<tr>
<th onClick={this.sortChanged.bind(this,'FirstName',this.state.Data.sortOrder)}>First Name
<i className={this._sortClass('FirstName')}></i></th>
<th onClick={this.sortChanged.bind(this,'LastName',this.state.Data.sortOrder)}>
Last Name
<i className={this._sortClass('LastName')}></i></th>
<th onClick={this.sortChanged.bind(this,'EmailID',this.state.Data.sortOrder)}>
Email
<i className={this._sortClass('EmailID')}></i>
</th>
<th onClick={this.sortChanged.bind(this,'Country',this.state.Data.sortOrder)}>
Country
<i className={this._sortClass('Country')}></i>
</th>
<th onClick={this.sortChanged.bind(this,'City',this.state.Data.sortOrder)}>
City
<i className={this._sortClass('City')}></i>
</th>
</tr>
</thead>
<tbody>{rows}</tbody>
</table>
<pager Size={this.state.Data.totalPage} onPageChanged={this.pageChanged} currentPage={this.state.Data.currentPage}/>
</div>
);
}
});
ReactDOM.render(<EmployeeGridTable dataUrl="/home/getEmployeeList"/>, document.getElementById('griddata'));

How to efficiently do web scraping in Node.js?

I am trying to scrape some data from a shopping site Express.com. Here's 1 of many products that contains image, price, title, color(s).
<div class="cat-thu-product cat-thu-product-all item-1">
<div class="cat-thu-p-cont reg-thumb" id="p-50715" style="position: relative;"><img class="cat-thu-p-ima widget-app-quickview" src="http://t.express.com/com/scene7/s7d5/=/is/image/expressfashion/25_323_2516_900/i81?$dcat191$" alt="ROCCO SLIM FIT SKINNY LEG CORDUROY JEAN"><img id="widget-quickview-but" class="widget-ie6png glo-but-css-off2" src="/assets/images/but/cat/but-cat-quickview.png" alt="Express View" style="position: absolute; left: 50px;"></div>
<ul>
<li class="cat-cat-more-colors">
<div class="productId-50715">
<img class="js-swatchLinkQuickview" title="INK BLUE" src="http://t.express.com/com/scene7/s7d5/=/is/image/expressfashion/25_323_2516_900_s/i81?$swatch$" width="16" height="6" alt="INK BLUE">
<img class="js-swatchLinkQuickview" title="GRAPHITE" src="http://t.express.com/com/scene7/s7d5/=/is/image/expressfashion/25_323_2516_924_s/i81?$swatch$" width="16" height="6" alt="GRAPHITE">
<img class="js-swatchLinkQuickview" title="MERCURY GRAY" src="http://t.express.com/com/scene7/s7d5/=/is/image/expressfashion/25_323_2516_930_s/i81?$swatch$" width="16" height="6" alt="MERCURY GRAY">
<img class="js-swatchLinkQuickview" title="HARVARD RED" src="http://t.express.com/com/scene7/s7d5/=/is/image/expressfashion/25_323_2516_853_s/i81?$swatch$" width="16" height="6" alt="HARVARD RED">
</div>
</li>
<li class="cat-thu-name"><a href="/rocco-slim-fit-skinny-leg-corduroy-jean-50715-647/control/show/3/index.pro" onclick="var x=".tl(";s_objectID="http://www.express.com/rocco-slim-fit-skinny-leg-corduroy-jean-50715-647/control/show/3/index.pro_2";return this.s_oc?this.s_oc(e):true">ROCCO SLIM FIT SKINNY LEG CORDUROY JEAN
</a></li>
<li>
<strong>$88.00</strong>
</li>
<li class="cat-thu-promo-text"><font color="BLACK" style="font-weight:normal">Buy 1, Get 1 50% Off</font>
</li>
</ul>
The very naive and possibly error-prone approach I've done is to first to grab all prices, images, titles and colors:
var price_objects = $('.cat-thu-product li strong');
var image_objects = $('.cat-thu-p-ima');
var name_objects = $('.cat-thu-name a');
var color_objects = $('.cat-cat-more-colors div');
Next, I populate arrays with the data from DOM extracted using jsdom or cheerio scraping libraries for node.js. (Cheerio in this case).
// price info
for (var i = 0; i < price_objects.length; i++) {
prices.push(price_objects[i].children[0].data);
}
// image links
for (var i = 0; i < image_objects.length; i++) {
images.push(image_objects[i].attribs.src.slice(0, -10));
}
// name info
for (var i = 0; i < name_objects.length; i++) {
names.push(name_objects[i].children[0].data);
}
// color info
for (var i = 0; i < color_objects.length; i++) {
colors.push(color_objects[i].attribs.src);
}
Lastly, based on the assumption that price, title, image and colors will match up create a product object:
for (var i = 0; i < images.length; i++) {
items.push({
id: i,
name: names[i],
price: prices[i],
image: images[i],
colors: colors[i]
});
}
This method is slow, error-prone, and very anti-DRY. I was thinking it would be nice if we could grab $('.cat-thu-product') and using a single for-loop extract relevant information from a single product a time.
But have you ever tried traversing the DOM in jsdom or cheerio? I am not sure how anyone can even comprehend it. Could someone show how would I use this proposed method of scraping, by grabbing $('.cat-thu-product') div element containing all relevant information and then extract necessary data?
Or perhaps there is a better way to do this?
I would suggest still using jQuery (because it's easy, fast and secure) with one .each example:
var items = [];
$('div.cat-thu-product').each(function(index, productElement) {
var product = {
id: $('div.cat-thu-p-cont', productElement).attr('id'),
name: $('li.cat-thu-name a', productElement).text().trim(),
price: $('ul li strong', productElement).text(),
image: $('.cat-thu-p-ima', productElement).attr('src'),
colors: []
};
// Adding colors array
$('.cat-cat-more-colors div img', productElement).each(function(index, colorElement) {
product.colors.push({name: $(colorElement).attr('alt'), imageUrl: $(colorElement).attr('src')});
});
items.push(product);
});
console.log(items);
And to validate that you have all the required fields, you can write easilly validator or test. But if you are using different library, you still should loop through "div.cat-thu-product" elements.
Try node.io https://github.com/chriso/node.io/wiki
This will be a good approach of doing what you are trying to do.
using https://github.com/rc0x03/node-promise-parser
products = [];
pp('website.com/products')
.find('div.cat-thu-product')
.set({
'id': 'div.cat-thu-p-cont #id',
'name': 'li.cat-thu-name a',
'price': 'ul li strong',
'image': '.cat-thu-p-ima',
'colors[]': '.cat-cat-more-colors div img #alt',
})
.get(function(product) {
console.log(product);
products.push(product);
})

Create javascript object after jquery/AJAX database query

Hi this is an example of the code i want to run:
$('#search1').submit(function(){
var date = $('#date').val();
var location = $('#location').val();
var datastring = 'date=' + date + '&location=' + location;
$.ajax({
type: "POST",
cache: "true",
url: "search.php",
dataType:"json",
data: datastring,
success: function(data){
$('#main').html('')
for ($i = 0, $j = data.bus.length; $i < $j; $i++) {
//Create an object for each successful query result that holds information such as departure time, location, seats open...
$('#main').append(html);
}
How would I go about coding the success function? I want the object to store each bus' information so that the info can be displayed in the search result as well as being able to be referenced when the user confirms his RSVP later on. Thanks ahead of time
You can declare an object to use as a map in the containing scope:
var busInfo = {};
...and then if the bus entries have some form of unique identifier, you can record them like this:
success: function(data){
var $i, $j, bus;
$('#main').html('')
for ($i = 0, $j = data.bus.length; $i < $j; $i++) {
// Remember this bus by ID
bus = data.bus[$i];
busInfo[bus.id] = bus;
$('#main').append(html);
}
}
And then later, when the user chooses a bus, use the chosen ID to get the full bus information:
var bus = busInfo[theChosenId];
This works because all JavaScript objects are key/value maps. Keys are always strings, but the interpreter will happily make strings out of what you give it (e.g., busInfo[42] = ... will work, 42 will become "42" implicitly).
If you just want an array, your data.bus already is one, right?
var busInfo = [];
// ....
success: function(data){
var $i, $j;
// Remember it
busInfo = data.bus;
$('#main').html('')
for ($i = 0, $j = data.bus.length; $i < $j; $i++) {
$('#main').append(html);
}
}
(Note that JavaScript arrays aren't really arrays, they too are name/value maps.)
Update: I dashed off a quick example of the keyed object (live copy):
HTML:
<input type='button' id='btnLoad' value='Load Buses'>
<br>...and then click a bus below:
<ul id="busList"></ul>
...to see details here:
<table style="border: 1px solid #aaa;">
<tbody>
<tr>
<th>ID:</th>
<td id="busId">--</td>
</tr>
<tr>
<th>Name:</th>
<td id="busName">--</td>
</tr>
<tr>
<th>Route:</th>
<td id="busRoute">--</td>
</tr>
</tbody>
</table>
JavaScript with jQuery:
jQuery(function($) {
// Our bus information -- note that it's within a function,
// not at global scope. Global scope is *way* too crowded.
var busInfo = {};
// Load the buses on click
$("#btnLoad").click(function() {
$.ajax({
url: "http://jsbin.com/ulawem",
dataType: "json",
success: function(data) {
var busList = $("#busList");
// Clear old bus info
busInfo = {};
// Show and remember the buses
if (!data.buses) {
display("Invalid bus information received");
}
else {
$.each(data.buses, function(index, bus) {
// Remember this bus
busInfo[bus.id] = bus;
// Show it
$("<li class='businfo'>")
.text(bus.name)
.attr("data-id", bus.id)
.appendTo(busList);
});
}
},
error: function() {
display("Error loading bus information");
}
});
});
// When the user clicks a bus in the list, show its deatils
$("#busList").delegate(".businfo", "click", function() {
var id = $(this).attr("data-id"),
bus = id ? busInfo[id] : null;
if (id) {
if (bus) {
$("#busId").text(bus.id);
$("#busName").text(bus.name);
$("#busRoute").text(bus.route);
}
else {
$("#busId, #busName, #busRoute").text("--");
}
}
});
});
Data:
{"buses": [
{"id": 42, "name": "Number 42", "route": "Highgate to Wycombe"},
{"id": 67, "name": "Old Coach Express", "route": "There and Back"}
]}
Off-topic: Note that I've added var $i, $j; to your success function. Without it, you're falling prey to The Horror of Implicit Globals, which you can tell from the name is a Bad Thing(tm).

YUI Uploader hangs after choosing file

Below is my entire code from a User control that contains the YUI Uploader. Is there something I'm missing. Right now, when I step through the javascript code in Firebug, it hangs on the first line of the upload() function. I have a breakpoint on the first line of the ashx that handles the file, but it is never called. So, it doesn't get that far. I figure I'm just missing something stupid. I've used this control many times before with no issues. I'm using all the css files and graphics provided by the samples folder in the YUI download.
If I'm not missing anything, is there a more comprehensive way of debuging this issue then through stepping through the javascript with FireBug. I've tried turning the logging for YUI on and off, and never get any logs anywhere. I'm not sure where to go now.
<style type="text/css">
#divFile
{
background-color:White;
border:2px inset Ivory;
height:21px;
margin-left:-2px;
margin-right:9px;
width:125px;
}
</style>
<ajaxToolkit:RoundedCornersExtender runat="server" Corners="All" Radius="6" ID="rceContainer" TargetControlID="pnlMMAdmin" />
<asp:Panel ID="pnlMMAdmin" runat="server"
Width="100%" BackColor="Silver" ForeColor="#ffffff" Font-Bold="true" Font-Size="16px">
<div style="padding: 5px; text-align:center; width: 100%;">
<table style="width: 100% ; border: none; text-align: left;">
<tr>
<td style="width: 460px; vertical-align: top;">
<!-- information panel -->
<ajaxToolkit:RoundedCornersExtender runat="server" Corners="All" Radius="6" ID="RoundedCornersExtender1" TargetControlID="pnlInfo" />
<asp:Panel ID="pnlInfo" runat="server"
Width="100%" BackColor="Silver" ForeColor="#ffffff" Font-Bold="true" Font-Size="16px">
<div id="infoPanel" style="padding: 5px; text-align:left; width: 100%;">
<table>
<tr><td>Chart</td><td>
<table><tr><td><div id="divFile" ></div></td><td><div id="uploaderContainer" style="width:60px; height:25px"></div></td></tr>
<tr><td colspan="2"><div id="progressBar"></div></td></tr></table>
</td></tr>
</table>
</div></asp:Panel>
<script type="text/javascript" language="javascript">
WYSIWYG.attach('<%= txtComment.ClientID %>', full);
var uploader = new YAHOO.widget.Uploader("uploaderContainer", "assets/buttonSkin.jpg");
uploader.addListener('contentReady', handleContentReady);
uploader.addListener('fileSelect', onFileSelect)
uploader.addListener('uploadStart', onUploadStart);
uploader.addListener('uploadProgress', onUploadProgress);
uploader.addListener('uploadCancel', onUploadCancel);
uploader.addListener('uploadComplete', onUploadComplete);
uploader.addListener('uploadCompleteData', onUploadResponse);
uploader.addListener('uploadError', onUploadError);
function handleContentReady() {
// Allows the uploader to send log messages to trace, as well as to YAHOO.log
uploader.setAllowLogging(false);
// Restrict selection to a single file (that's what it is by default,
// just demonstrating how).
uploader.setAllowMultipleFiles(false);
// New set of file filters.
var ff = new Array({ description: "Images", extensions: "*.jpg;*.png;*.gif" });
// Apply new set of file filters to the uploader.
uploader.setFileFilters(ff);
}
var fileID;
function onFileSelect(event) {
for (var item in event.fileList) {
if (YAHOO.lang.hasOwnProperty(event.fileList, item)) {
YAHOO.log(event.fileList[item].id);
fileID = event.fileList[item].id;
}
}
uploader.disable();
var filename = document.getElementById("divFile");
filename.innerHTML = event.fileList[fileID].name;
var progressbar = document.getElementById("progressBar");
progressbar.innerHTML = "Please wait... Starting upload.... ";
upload(fileID);
}
function upload(idFile) {
// file hangs right here. **************************
progressBar.innerHTML = "Upload starting... ";
if (idFile != null) {
uploader.upload(idFile, "AdminFileUploader.ashx", "POST");
fileID = null;
}
}
function handleClearFiles() {
uploader.clearFileList();
uploader.enable();
fileID = null;
var filename = document.getElementById("divFile");
filename.innerHTML = "";
var progressbar = document.getElementById("progressBar");
progressbar.innerHTML = "";
}
function onUploadProgress(event) {
prog = Math.round(300 * (event["bytesLoaded"] / event["bytesTotal"]));
progbar = "<div style=\"background-color: #f00; height: 5px; width: " + prog + "px\"/>";
var progressbar = document.getElementById("progressBar");
progressbar.innerHTML = progbar;
}
function onUploadComplete(event) {
uploader.clearFileList();
uploader.enable();
progbar = "<div style=\"background-color: #f00; height: 5px; width: 300px\"/>";
var progressbar = document.getElementById("progressBar");
progressbar.innerHTML = progbar;
alert('File Uploaded');
}
function onUploadStart(event) {
alert('upload start');
}
function onUploadError(event) {
alert('upload error');
}
function onUploadCancel(event) {
alert('upload cancel');
}
function onUploadResponse(event) {
alert('upload response');
}
</script>
It seems that there is a case mismatch in the name of the progressbar variable: you refer to it as progressbar everywhere else, but as progressBar in the upload() function.
An even bigger problem is that you define the progressbar variable inside the onFileSelect function. Because of that, the variable is limited in scope and should not be accessible anywhere else.
See if moving the definition for progressbar out of there (or freshly grabbing it from the DOM everywhere it's used by using getElementById) and fixing the case mismatch solves your issues.
YUI 2.8 has issues with events and the uploader. It won't work unless you use 2.9 event and uploader. I wasted more time than I want to admit trying to get 2.8 to work. I hope this saves someone that time.

Resources