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).
Related
I'm using moment.js and jquery datatables. Specifically, I have a a list of cells that all contain a Unix Timestamp.
What I'd like to do is convert this timestamp to the user's localized time (based on his/her timezone).
I am able to get the timezone to localize, but it only works for the first group of paginated results in my table...if I navigate to another page, the timestamp still shows up as the raw unix value.
I've made a JS fiddle to illustrate.
Could someone kindly let me know 1) if there's a better way to do what I'm doing 2) how I can localize my times even after actions like a) searching the table 2) sorting the table 3) paginating the table?
Huge thanks in advance!
My JS:
// Do Datatables
$('.my-datatable').DataTable({
"order": [[ 1, 'desc' ],],
"aoColumnDefs": [
{ "bSortable": false, "aTargets": [ 0 ] }
]
});
// Loop through class to localize unix time based on user's time zone
function localizeTime(){
$( ".localize_time" ).each(function() {
if (typeof moment !== 'undefined' && $.isFunction(moment)) {
var userMomentTz = moment().format("Z");
var userTimeZone = userMomentTz.replace(":", "");
var elementSiteUnixTimeText = $(this).find('.localize_time_unix').text();
var elementSiteUnixTimeVal = parseInt(elementSiteUnixTimeText.trim());
if (userTimeZone.substring(0, 1) == "-") {
var userTimeZoneHr = parseInt(userTimeZone.substring(1,3));
var userTimeZoneMin = parseInt(userTimeZone.slice(-2));
var userTimeOffset = (userTimeZoneHr + '.' + (userTimeZoneMin/60))*(-1);
} else {
var userTimeZoneHr = parseInt(userTimeZone.substring(0,2));
var userTimeZoneMin = parseInt(userTimeZone.slice(-2));
var userTimeOffset = userTimeZoneHr + '.' + (userTimeZoneMin/60);
}
var momentDateUserOffset = moment.unix(elementSiteUnixTimeVal).utcOffset(userTimeOffset);
var momentDateFormattedOffset = moment(momentDateUserOffset).format('ddd, D MMM YYYY, h:mm A');
$(this).find('.localize_time_display').text(momentDateFormattedOffset);
};
});
};
// Run time localization function
if ( $( ".localize_time" ).length ) {
localizeTime()
};
My HTML
<table class="my-datatable">
<thead>
<tr>
<th>Time</th>
<th>Stuff</th>
</tr>
</thead>
<tbody>
<tr>
<td>Stuff</td>
<td>
<span class="localize_time">
<span class="localize_time_unix">UNIX Time n++</span>
<span class="localize_time_display"></span>
</span>
</td>
</tr>
</tbody>
</table>
Ok, well fortunately this was easier than I thought using 'data rendering'
Working JS Fiddle
Hope this helps someone!
My updated JS
// Do Datatables
$('.my-datatable').DataTable( {
"order": [[ 1, 'desc' ],],
"columnDefs": [{
"targets": 1,
"render": function (data, type, full, meta) {
if (typeof moment !== 'undefined' && $.isFunction(moment)) {
var userMomentTz = moment().format("Z");
var userTimeZone = userMomentTz.replace(":", "");
var elementSiteUnixTimeText = data;
var elementSiteUnixTimeVal = parseInt(elementSiteUnixTimeText.trim());
if (userTimeZone.substring(0, 1) == "-") {
var userTimeZoneHr = parseInt(userTimeZone.substring(1,3));
var userTimeZoneMin = parseInt(userTimeZone.slice(-2));
var userTimeOffset = (userTimeZoneHr + '.' + (userTimeZoneMin/60))*(-1);
} else {
var userTimeZoneHr = parseInt(userTimeZone.substring(0,2));
var userTimeZoneMin = parseInt(userTimeZone.slice(-2));
var userTimeOffset = userTimeZoneHr + '.' + (userTimeZoneMin/60);
}
var momentDateUserOffset = moment.unix(elementSiteUnixTimeVal).utcOffset(userTimeOffset);
var momentDateFormattedOffset = moment(momentDateUserOffset).format('ddd, D MMM YYYY, h:mm A');
$(this).find('.localize_time_display').text(momentDateFormattedOffset);
return momentDateFormattedOffset;
};
}
}]
} );
I am trying to display and refresh periodically some server data using dgrid and derivative of Request dstore. The data are paginated and needs to be updated periodically. As a first naive approach I tried to call Dgrid.refresh() with setInterval. However, that results in complete rebuilding of grid rows which visually creates flickering effect. Using Trackable to the store does not help. Can anyone advise me how to refresh rows in the dgrid which would only update changed rows?
Here is my code reproducing the issue:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>DGrid flickering on slow updates</title>
<style>
#import "./dojo-release-1.11.1/dojo/resources/dojo.css";
#import "./dojo-release-1.11.1/dijit/themes/claro/claro.css";
#import "./META-INF/resources/webjars/dgrid/1.0.0/css/dgrid.css";
#import "./META-INF/resources/webjars/dgrid/1.0.0/css/skins/claro.css";
html, body {
padding: 10px;
width: 100%;
height: 100%;
}
</style>
<script>
var dojoConfig = {
async:true,
baseUrl: "./",
packages:[
{ name:"dojo", location:"dojo-release-1.11.1/dojo" },
{ name:"dijit", location:"dojo-release-1.11.1/dijit" },
{ name:"dgrid", location:"META-INF/resources/webjars/dgrid/1.0.0" },
{ name:"dstore", location:"META-INF/resources/webjars/dstore/1.1.1" }
]
};
</script>
<script src="dojo-release-1.11.1/dojo/dojo.js"></script>
<script>
require(["dojo/parser",
"dojo/dom",
"dojo/_base/declare",
"dstore/Store",
"dstore/Trackable",
"dstore/Cache",
"dstore/Memory",
"dstore/QueryResults",
"dojo/Deferred",
"dgrid/Grid",
"dgrid/Keyboard",
"dgrid/Selection",
"dgrid/extensions/Pagination",
"dojo/domReady!"],
function(parser, dom, declare, Store, Trackable, Cache, Memory, QueryResults, Deferred, Grid, Keyboard, Selection, Pagination)
{
parser.parse();
console.log("Parsed");
var makeSlowRequest =
function(kwArgs)
{
var responseDeferred = new Deferred();
var responsePromise = responseDeferred.promise;
// resolve promise in 2 seconds to simulate slow network connection
setTimeout(function ()
{
console.log("Generating response");
var data = {items: [], total: 100};
kwArgs = kwArgs || {start:0, end:100};
for(var i = kwArgs.start; i < kwArgs.end; i++)
{
data.items.push({id: "id-" + i,
name: "test-" + i,
value: Math.floor((Math.random() * 10) + kwArgs.start)
});
}
responseDeferred.resolve(data);
}, 2000);
return new QueryResults(responsePromise.then(function (data) { return data.items; }),
{ totalLength: responsePromise.then(function (data) { return data.total;}) });
};
var SlowStore = declare("SlowStore",
[Store, Trackable],
{ fetch: function (kwArgs) { return makeSlowRequest(kwArgs); },
fetchRange: function (kwArgs) { return makeSlowRequest(kwArgs); }
});
var store = new SlowStore();
var TestGrid = declare([Grid, Keyboard, Selection, Pagination]);
var grid = new TestGrid({
collection: store,
columns: {name: "Name", value: "Value"},
rowsPerPage: 10,
selectionMode: 'single',
cellNavigation: false,
className: 'dgrid-autoheight',
pageSizeOptions: [10, 20],
adjustLastColumn: true
}, dom.byId("grid"));
grid.startup();
// update grid every 5 seconds
var timer = setInterval(function(){console.log("Refreshing grid"); grid.refresh();}, 5000);
});
</script>
</head>
<body class="claro">
<div id="grid"></div>
</body>
</html>
I am guessing that instead of calling refresh I need get the request range from the dgrid (possibly using aspect on gotoPage) and call store.fetchRange(), iterate over the results, compare each item with previous results and invoke store.update or store.add or store.delete. I suppose that would give me what I want but before taking this approach I wondering if there is an easier way to refresh dgrid with updated data. Using Cache store does not work as it expects to fetch all the data from the server:
var store = Cache.create(new SlowStore(), {
cachingStore: new (Memory.createSubclass(Trackable))()
});
I am using Node.js to send a confirmation email when a user submits an order. I would like the email to include include all items that the user submits. The number of items will vary with each submission. I'd like the email body to include a table. Can a lodash template be used for this? Or should this be handled differently?
When I used the following code, the resulting email includes what I assume to be uncompiled code.
var tpl = _.template('<% _.forEach(items, function(item) { %><td><%- item %></td><% }); %>');
tpl({ 'items': ['Guitar', 'Harmonica'] });
var data = {
from: 'support#example.com',
to: email,
subject: 'Your Order Confirmation',
html: '<p>Thank you for submitting your order.</p>
<table>
<tr>
<thead>
<tr>
<th><strong>Items</strong></th>'
+ tpl +
// The template should insert each item here
// <td>Guitar</td>
// <td>Harmonica</td>
'</tr>
</thead>
</tr>
</table>'
};
Output in actual email sent:
function (obj) { obj || (obj = {}); var __t, __p = '', __e = _.escape,
__j = Array.prototype.join; function print() { __p +=
__j.call(arguments, '') } with (obj) { _.forEach(items,
function(item) { ; __p += ' ' + __e( item ) + ' '; }); ; } return
__p }
change:
tpl({ 'items': ['Guitar', 'Harmonica'] });
to
var html = tpl({ 'items': ['Guitar', 'Harmonica'] });
and
ng>Items</strong></th>'
+ tpl +
to
ng>Items</strong></th>'
+ html +
if you look at the docs at: https://lodash.com/docs#template
you will see the compiled function returns an output which you need to use.
You instead of using the output, you used the actual function itself.
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'));
When should destroy be called? Does it ever get called automatically by YUI lifecycle? Does the page unload cause the YUI lifecycle to call destroy on all objects created during the page processing? I have been working under the assumption that I need to make all my own calls to destroy but that gets hairy when ajax calls replace sections of code that I had progressively enhanced. For example:
<div id="replaceMe">
<table>
<tr>
<td>1</td>
</tr>
<tr>
<td>2</td>
</tr>
</table>
<script>
YUI().use('my-lib', function(Y) {
Y.mypackage.enhanceTable("replaceMe");
});
<script>
</div>
The my-lib module basically adds a click handler and mouseover for each row:
YUI.add('my-lib', function(Y) {
function EnhancedTable(config) {
EnhancedTable.superclass.constructor.apply(this, arguments);
}
EnhancedTable.NAME = "enhanced-table";
EnhancedTable.ATTRS = {
containerId : {},
onClickHandler : {},
onMouseoverHandler : {},
onMouseoutHandler : {}
};
Y.extend(EnhancedTable, Y.Base, {
_click : function(e) {
//... submit action
},
destructor : function() {
var onClickHandler = this.get("onClickHandler"),
onMouseoverHandler = this.get("onMouseoverHandler"),
onMouseoutHandler = this.get("onMouseoutHandler");
onClickHandler && onClickHandler.detach();
onMouseoverHandler && onMouseoverHandler.detach();
onMouseoutHandler && onMouseoutHandler.detach();
},
initializer : function(config) {
var container = Y.one("[id=" + this.get("containerId") + "]");
this.set("container", container);
this.set("onMouseoverHandler", container.delegate("mouseover",
this._mouseover, "tr", this ));
this.set("onMouseoutHandler", container.delegate("mouseout",
this._mouseout, "tr", this ));
this.set("onClickHandler", container.delegate("click",
this._click, "tr", this ));
},
_mouseout : function(e) {
e.currentTarget.removeClass("indicated");
},
_mouseover : function(e) {
e.currentTarget.addClass("indicated");
}
});
Y.namespace("mypackage");
Y.mypackage.enhanceTable = function(containerId) {
var enhancedTable new EnhancedTable({containerId:containerId});
};
}, '0.0.1', {
requires : [ 'base', 'node' ]
});
The click handler would submit a request back to my application that would change the page. Do I need to remember all the enhancedTable objects and have an onunload handler call the destroy method of each? Or does the YUI framework take care of this?
The last part of this quesiton is, I also have code outside of this that replaces the whole table by replacing the content of the <div id="replaceMe">. In doing so, the script would get re-run and augment the new <table> with a new EnhancedTable. Do I need to remember the old table, and destroy it before the new table clobbers it?
Instead of setting handlers as attributes I'd store them all in an array like this:
this._handlers = [
container.delegate("mouseover", this._mouseover, "tr", this ),
container.delegate("mouseout", this._mouseout, "tr", this ),
container.delegate("click", this._click, "tr", this )
];
Then add a destructor method that does the following
destructor : function() {
new Y.EventTarget(this._handlers).detach();
}
It accomplishes the same thing but with way less work on your part!
Ideally instead of running this against each table you'd attach all your delegates to #replaceMe so that it wouldn't need to be recreated each time you changed the content, no matter where that happened from.
YUI won't automatically call .destroy() for you on unload, it will clean up DOM subs though. The above is extra credit that's really only necessary if you are going to be destroying the object yourself.