I developed a map displaying a large number of datasets (around 70 different layers at this point) using leaflet. To toggle each of these layers I am using a Javascript function and switch case within that to add/remove layers based on checkbox status. To toggle 3 layers I had to write 6 case statements (two lines of code for each layer), for 70 layers in my primary project I had to write 140 case statements. Tried different things like loops, variable switching and others to reduce this code but couldn't crack it just wanted to know if there is an efficient way of doing this. I consciously named my layers to match the name with checkbox IDs and the argument passed when the function toggleLayer() from HTML is called to take advantage of it.
Below is a basic example of what I am doing in my primary project. Here is my HTML part of the code
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Simple Example</title>
<link href="https://fonts.googleapis.com/css2?family=Nunito+Sans:wght#200&display=swap" rel="stylesheet">
<link rel="stylesheet"
href="https://unpkg.com/leaflet#1.8.0/dist/leaflet.css"
integrity="sha512-hoalWLoI8r4UszCkZ5kL8vayOGVae1oxXe/2A4AO6J9+580uKHDO3JdHb7NzwwzK5xr/Fs0W40kiNHxM9vyTtQ=="
crossorigin=""/>
<link rel="stylesheet" href="style.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
</head>
<body>
<main>
<div class="address-list">
<p>Use checkboxs to toggle layers</p><br>
<div class="inputs" id="years">
<input type="checkbox" id="layer_points_state" onclick='toggleLayer("layer_points_state")' checked="true">States<br>
<input type="checkbox" id="layer_points_territory" onclick='toggleLayer("layer_points_territory")' checked="true">Territories<br>
<input type="checkbox" id="layer_polygon" onclick='toggleLayer("layer_polygon")' checked="true">State Polygons
</div>
</div>
<div id="map"></div>
</main>
<!-- Leaflet js Packages/libraries -->
<script src="https://unpkg.com/leaflet#1.8.0/dist/leaflet.js"
integrity="sha512-BB3hKbKWOc9Ez/TAwyWxNXeoV9c1v6FIeYiBieIWkpLjauysF18NzgR1MBNBXf8/KABdlkX68nAhlwcDFLGPCQ=="
crossorigin=""></script>
<script src="./leaflet-providers.js"></script>
<!-- data files -->
<script src="state_points.js"></script>
<script src="state_polygons.js"></script>
<script src="script.js"></script>
</body>
</html>
and JS file
var osmMapHum = L.tileLayer.provider('OpenStreetMap.HOT');
var baseMaps = {'OSM Humanitarian':osmMapHum,}
var map = L.map('map', {
center: [-26.750867654966356, 136.22808234118338],
zoom:4.8,
worldCopyJump: false,
layers:[osmMapHum]
});
// Start of States Points
function filter_state(feature){
if (feature.properties.type === 'State')
return true
}
var layer_points_state = new L.GeoJSON(json_state_point, {filter: filter_state}).addTo(map);
function filter_territory(feature){
if (feature.properties.type === 'Territory')
return true
}
var layer_points_territory = new L.GeoJSON(json_state_point, {filter: filter_territory}).addTo(map);
// Start of States Polygon
var layer_polygon = new L.GeoJSON(json_state_polygon, {}).addTo(map);
var overlayMaps = {
"States": layer_points_state,
"Territories": layer_points_territory,
"State Polygon": layer_polygon,
};
var layerControl = L.control.layers(baseMaps, overlayMaps, {collapsed:false}).addTo(map);
//toggle layers with checkbox status
function toggleLayer(layer_toggled){
id_to_check = '#' + layer_toggled
const cb = document.querySelector(id_to_check);
if (cb.checked === true){
switch (layer_toggled){
case 'layer_points_state': return map.addLayer(layer_points_state);
case 'layer_points_territory': return map.addLayer(layer_points_territory);
case 'layer_polygon': return map.addLayer(layer_polygon);
}
}else{
switch (layer_toggled){
case 'layer_points_state': return map.removeLayer(layer_points_state);
case 'layer_points_territory': return map.removeLayer(layer_points_territory);
case 'layer_polygon': return map.removeLayer(layer_polygon);
}
}
}
This is how the map looks
How Project looks
Thanks in advance for the suggestions
You can create the html and the logic dynamically. Just add in layers your wanted filters.
var layerContainer = document.getElementById('years');
var layers = [
{
filter: 'State',
displayName: 'States'
},
{
filter: 'Territory',
displayName: 'Territories'
}
];
var overlays = {};
function prepareFilter(filterType){
return function(feature) {
if (feature.properties.type === filterType)
return true
}
}
//toggle layers with checkbox status
function toggleLayer(e){
var elem = e.target;
var leafletId = parseInt(elem.id);
var layerObj = layers.find(x=>x.id === leafletId);
if(!layerObj){
console.error('Layer '+leafletId+' not found!');
return;
}
var layer = layerObj.layer;
if(!elem.checked){
layer.removeFrom(map);
} else {
layer.addTo(map);
}
}
layers.forEach((layerObj)=>{
var geojsonLayer = new L.GeoJSON(json_state_point, {filter: prepareFilter(layerObj.filter)}).addTo(map);
// to hide inputs which are not found
if(geojsonLayer.getLayers().length === 0){
return;
}
overlays[layerObj.filter] = geojsonLayer;
layerObj.id = L.stamp(geojsonLayer);
layerObj.layer = geojsonLayer;
var input = document.createElement('input');
input.type = 'checkbox';
input.id = L.stamp(geojsonLayer); //unique leaflet id of the layer
input.checked = true;
layerContainer.appendChild(input);
L.DomEvent.on(input, 'click', toggleLayer);
var label = document.createElement('label');
label.innerHTML = layerObj.displayName;
label.htmlFor = input.id;
layerContainer.appendChild(label);
layerContainer.appendChild(document.createElement('br'));
});
var layer_polygon = new L.GeoJSON(json_state_polygon, {}).addTo(map);
overlays["State Polygon"] = layer_polygon;
var layerControl = L.control.layers({}, overlays, {collapsed:false}).addTo(map);
https://plnkr.co/edit/ddYaazIpSkbZKplC
Related
This question already has answers here:
Why is my variable unaltered after I modify it inside of a function? - Asynchronous code reference
(7 answers)
How to return value from an asynchronous callback function? [duplicate]
(3 answers)
Closed 10 months ago.
I am learning javascript and I create a project in which when a city is entered in the field, it will display its pollution level and what country it is in.
And the last thing is that after entering this city, it should automatically display marked with a marker on the map along with the level of pollution fetched from the Open AQ API.
I need to get variable city and air with data fetched from loop. And use variable city, with name of the searched city, in String format, outside function in another variable called query_addr. Thanks to this, the city will be immediately marked on the map.
Many thanks for any help.
Here is the whole project:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Laboratory Leaflet and OpenAQ</title>
<meta http-equiv="refresh" content="s">
<script src="https://code.jquery.com/jquery-1.12.4.min.js"></script>
<script src="https://unpkg.com/leaflet-geosearch#3.0.0/dist/geosearch.umd.js"></script>
<link rel = "stylesheet" href = "http://cdn.leafletjs.com/leaflet-0.7.3/leaflet.css"/>
<script src = "http://cdn.leafletjs.com/leaflet-0.7.3/leaflet.js"></script>
<link rel="stylesheet" href="https://unpkg.com/leaflet-control-geocoder/dist/Control.Geocoder.css" />
<script src="https://unpkg.com/leaflet-control-geocoder/dist/Control.Geocoder.js"></script>
</script>
</head>
<body>
<input id="mySearchField" name="search" placeholder="Search.." type="text">
<button id="mySearchButton">Search</button>
<div id="myContentArea"></div>
<script>
$(function() {
var _myContentArea = document.getElementById("myContentArea");
var _mySearchButton = document.getElementById("mySearchButton");
_mySearchButton.onclick = getData;
var city;
var air;
function getData(){
var _mySearchField = document.getElementById("mySearchField");
$.ajax({
url: "https://api.openaq.org/v1/cities?city="+_mySearchField.value,
method: "GET",
dataType: "json",
success: function(data) {
var str = "";
for(var i= 0; i < data.results.length; i++){
str +='City : '+data.results[i].city+' <br> Country : '+data.results[i].country+'<br>Count : '+data.results[i].count+'<br>';
city = data.results[i].city;
air = data.results[i].count;
}
_myContentArea.innerHTML = str;
}
});
}
var map = L.map( 'map', {
center: [ 51.5, -0.1],
zoom: 12
});
// Add tiles (streets, etc)
L.tileLayer( 'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap',
subdomains: ['a','b','c']
}).addTo( map );
var query_addr = city;
const provider = new window.GeoSearch.OpenStreetMapProvider()
var query_promise = provider.search({ query: query_addr});
query_promise.then( value => {
for(i=0;i < value.length; i++){
var x_coor = value[i].x;
var y_coor = value[i].y;
var label = value[i].label;
var marker = L.marker([y_coor,x_coor]).addTo(map)
marker.bindPopup("<b>Found location</b><br>"+label+"The level of air pollution"+ air).openPopup();
};
}, reason => {
console.log(reason);
} );
});
</script>
<div id = "map" style = "width: 600px; height: 400px"></div>
</body>
</html>
I don't know how to make this variable outside the function and the loop have a value which is the name of the city from the api
i'm having trouble using my delete button with my code. Instead of deleting an object, it creates a new one. code is under render items function. i've tried several things but it seems like my biggest problem is the placement of the rederItems function in the if statement. I also added the html to show you how that looks like too. Thanks!
// selectors
var nameOfItem = document.getElementById("nameOfItem");
var saveOfItem = document.getElementById("nameOfItem");
var shoppingList = document.getElementById("shoppingListContainer");
var nameArray = ["tea","bread","rice"]
// var nameArray = []
var setCart = [];
var getIngredientsForCart = localStorage.getItem
var emptyListText = document.getElementById("emptyList")
var removeButton = document.getElementById('removeItem');
var saveItems = document.getElementById("saveItems");
var cart = document.getElementById("shoppingListContainer")
cart.style.visibility = 'hidden';
saveItems.style.visibility = 'hidden'
saveItems.addEventListener('click', function() {
console.log('saved')
setCart.push(entry);
localStorage.setItem('cart', JSON.stringify(newArray));
});
/*
<li class="list-group-item" id="numberOfItems">1</li>
<li class="list-group-item" id="nameOfItem"></li>
<li class="list-group-item" id="removeItem"></li>
*/
// get from local storage
// var food = JSON.parse(localStorage.getItem("Ingredients"))
// console.log(food)
// nameArray = food
// --------------------------------
// render Item function
// --------------------------------
function renderItems() {
for (i = 0; i < nameArray.length; i++) {
var row = document.createElement("div");
row.setAttribute("class", "row");
var col2 = document.createElement("div");
var col3 = document.createElement("div");
col2.setAttribute("class", "col-8 item-name");
col3.setAttribute("class", "col-2 item-delete");
var newButton = document.createElement('button');
newButton.textContent = 'Delete';
newButton.setAttribute("data-item-idx", i);
newButton.addEventListener('click', function(event){
console.log(event.target.dataset.itemIdx)
var selectedItem = parseInt(event.target.dataset.itemIdx);
if(nameArray.splice(selectedItem, 1)){
console.log(nameArray)
renderItems()
}
})
col2.textContent = nameArray[i];
col3.appendChild(newButton);
row.appendChild(col2);
row.appendChild(col3);
shoppingList.appendChild(row);
}
}
// --------------------------------
// shopping Cart function
// --------------------------------
function shoppingCart() {
emptyListText.style.visibility = 'hidden';
cart.style.visibility = 'visible';
saveItems.style.visibility = 'visible'
renderItems()
}
// execute Function
shoppingCart()
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" />
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.1/css/all.css" integrity="sha384-50oBUHEmvpQ+1lW4y57PTFmhCaXp0ML5d60M1M7uH2+nqUivzIebhndOJK28anvf" crossorigin="anonymous" />
<link href="https://fonts.googleapis.com/css?family=Open+Sans&display=swap" rel="stylesheet" />
<title>Meal Plan</title>
<link rel="stylesheet" href="../css/shoppinglist.css">
</head>
<body>
<div>
<h1>Shopping List</h1>
search recipes
</div>
<h1 id="emptyList">Cart is Empty :(</h1>
<section id="shoppingListContainer" class="container">
</section>
<button id="saveItems">Save Items</button>
</body>
<!-- <script src="/projects/mealPlan/assets/js/script.js"></script> -->
<script src="../js/shoppingList.js"></script>
</html>
You are calling renderItems() again on delete but you are never actually clearing the existing rendered html.
Simply adding shoppingList.innerHTML = ""; to the start of the renderItems() function will clear the html each time render runs.
I've been working on the Search CRUD using Google WebApp Script via watching a YouTube tutorial, I'm almost done but I'm stuck in a place I couldn't figure out to sort the issue.
I want to load the search field and the data on first page load. but based on this code I need to click on the Search Tab and then get the search field to find the data. How do I get rid of the Search Tab and get straight into the search bar and data.
On Page load
Second Occurrence (After the Click)
My code
<!doctype html>
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap#4.5.3/dist/css/bootstrap.min.css" integrity="sha384-TX8t27EcRE3e/ihU7zmQxVncDAy5uIKz4rEkgIXeMed4M0jlfIDPvg6uqKI2xXr2" crossorigin="anonymous">
<style>
.nav-link {
cursor: pointer;
}
</style>
</head>
<body>
<div class="container">
<ul class="nav nav-tabs">
<li class="nav-item">
<div class="nav-link"id="search-link">Search</div>
</li>
</ul>
<div id="app"></div>
<!-- Content here -->
</div>
<!-- Option 1: jQuery and Bootstrap Bundle (includes Popper) -->
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap#4.5.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ho+j7jyWK8fNQe+A12Hb8AhRq26LrZ/JpcUGGOn+Y7RsweNrtN/tE3MoK7ZeZDyx" crossorigin="anonymous"></script>
<script>
var data;
function loadView(options){
var id = typeof options.id === "undefined" ? "app" : options.id;
var cb = typeof options.callback === "undefined" ? function(){} : options.callback;
google.script.run.withSuccessHandler(function(html){
document.getElementById("app").innerHTML = html;
typeof options.params === "undefined" ? cb() : cb(options.params);
})[options.func]();
}
function setDataForSearch(){
google.script.run.withSuccessHandler(function(dataReturned){
data = dataReturned.slice();
}).getDataForSearch();
}
function search(){
var searchinput = document.getElementById("searchinput").value.toString().toLowerCase().trim();
var searchWords = searchinput.split(/\s+/);
var searchColumns = [0,1,2,3,4,5,6,7];
// and or
var resultsArray = data.filter(function(r){
return searchWords.every(function(word){
return searchColumns.some(function(colIndex){
return r[colIndex].toString().toLowerCase().indexOf(word) !== -1
});
});
});
var searchResultsBox = document.getElementById("searchResults");
var templateBox = document.getElementById("rowTemplate");
var template = templateBox.content;
searchResultsBox.innerHTML = "";
resultsArray.forEach(function(r){
var tr = template.cloneNode(true);
var hinmokuColumn = tr.querySelector(".hinmoku");
var buhinCodeuColumn = tr.querySelector(".buhinCode");
var buhinNameColumn = tr.querySelector(".buhinName");
var hitsuyoColumn = tr.querySelector(".hitsuyo");
var genkaColumn = tr.querySelector(".genka");
var kobaiColumn = tr.querySelector(".kobai");
var sagakuColumn = tr.querySelector(".sagaku");
var kenshoColumn = tr.querySelector(".kensho");
hinmokuColumn.textContent = r[0];
buhinCodeuColumn.textContent = r[1];
buhinNameColumn.textContent = r[2];
hitsuyoColumn.textContent = r[3];
genkaColumn.textContent = r[4];
kobaiColumn.textContent = r[5];
sagakuColumn.textContent = r[6];
kenshoColumn.textContent = r[7];
searchResultsBox.appendChild(tr);
});
}
function loadSearchView(){
loadView({func:"loadSearchView", callback: setDataForSearch});
}
document.getElementById("search-link").addEventListener("click",loadSearchView);
function inputEventHandler(e){
if (e.target.matches("#searchinput")){
search();
}
}
document.getElementById("app").addEventListener("input",inputEventHandler);
</script>
</body>
</html>
Server Side Code
function getDataForSearch(){
const ss = SpreadsheetApp.getActiveSpreadsheet();
const ws = ss.getSheetByName("Array");
return ws.getRange(2, 1, ws.getLastRow(),8).getValues();
}
I need to type letters in order to data display.
Screen Shot 3
Issue:
There are some actions that are currently happening when the Search tab is clicked.
You want these actions to happen when the page loads.
Solution:
In the HTML you provided, there's a click event listener attached to the Search tab you mention (id="search-link"):
document.getElementById("search-link").addEventListener("click",loadSearchView);
This means the function loadSearchView is gonna execute when the Search tab is clicked.
If I understand you correctly, you want loadSearchView to execute when the page loads. In that case, you could just add the following event listener:
window.addEventListener("load", loadSearchView);
Notes:
Since you didn't provide server-side code, I cannot know whether loadSearchView will do what you intend it to do. This solution just makes sure loadSearchView is executed when the page loads.
If you want to get rid of the Search tab, just remove it from your HTML (<div class="nav-link"id="search-link">Search</div> and its container elements).
Reference:
Window: load event
I've been struggling with this one error for a project I'm doing in a web design class for about a day now and I am nowhere close to figuring out how to solve it. For the project, I'm using a combination of two API's to read in a user specified location, get the latitude and longitude of that location using the Google GeoCoding API, and then using that latitude and longitude display the current weather of that location. The problem is, no matter what I do, the function I made to return a Location object (name, latitude, and longitude) to the main index.html file script never returns properly, and I believe it might be because it is running asynchronously. I'm a bit new to javascript, so my apologies in advance if I ask a lot of questions to your answers.
Here's the code from my location.js file:
class Location
{
constructor(name = "Dummy Location", lat = 0.0, lng = 0.0)
{
this.name = name;
this.lat = lat;
this.lng = lng;
}
}
function geocode(location)
{
axios.get('https://maps.googleapis.com/maps/api/geocode/json?',
{
params: {
address: location,
key: googleAPIKey
}
})
// Get a reponse from that request
.then(function(response)
{
///Format the address
var formattedAddress = response.data.results[0].formatted_address;
// Get the various address components
var locationName = `${response.data.results[0].address_components[0].long_name}, ${response.data.results[0].address_components[2].short_name}`;
var locationLat = response.data.results[0].geometry.location.lat;
var locationLng = response.data.results[0].geometry.location.lng;
// Create a new Location based on those components
var geoLocation = new Location(locationName, locationLat, locationLng);
// (Debug) Log that location
console.log(geoLocation);
// Return that location
return geoLocation;
})
.catch(function(error)
{
console.log(error);
})
}
And here's the code from my index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Cool Weather App</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
<link rel = "stylesheet" href="css/styles.css">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
</head>
<body>
<header>
<div id = "locationinputcontainer">
<input id="locationfield" type="text" value="Location">
<input id = "submitbutton" type="submit" value="Submit">
<input id = "savelocationbutton" type = "submit" value = "Save Location">
</div>
</header>
<div id = "locationweathercontainer">
<h1 id = "locationname">Dummy Value</h1>
<h2 id = "weather">Weather</h2>
<h1 id = "temperature">68°F</h1>
<h3 id = "precipitation">Precipitation:</h3>
<h3 id = "humidity">Humidity: </h3>
<h3 id = "windspeed">Wind Speed: </h3>
</div>
<div id = "mylocationscontainer" class = "dropdowncontainer" style = "float:left">
<h1>My Locations</h1>
<ol>
</ol>
</div>
<div id = "settingscontainer" class = "dropdowncontainer" style = "float:right">
<h1>Settings</h1>
</div>
</body>
<script src = "js/location.js"></script>
<script>
const locationField = document.querySelector("#locationfield");
const submitButton = document.querySelector("#submitbutton");
const saveButton = document.querySelector("#savelocationbutton");
const locationNameElement = document.querySelector("#locationname");
const locationsListElement = document.querySelector("#mylocationscontainer");
const prefix = "asb9599-";
const storedLocationsKey = prefix + "storedLocations";
const storedCurrentLocationKey = prefix + "storedCurrentLocation";
let currentLocation;
let locationInput;
/* Functions */
function UpdateCurrentLocation()
{
currentLocation = geocode(locationInput);
console.log("(index.html) Current Location: " + currentLocation);
locationNameElement.innerHTML = currentLocation.name;
}
/* Events */
submitButton.onclick = UpdateCurrentLocation;
locationField.onchange = e => {locationInput = e.target.value};
</script>
</html>
Andddd here's the response I'm getting:
Sample Response Image
params: {
address: location,
key: googleAPIKey(paste your apikey here)
}
Get the api key from google and paste it in the place of key.Otherwise it won't work.
I created a custom application in Rally that is a modified version of the Catalog App Kanban Board. I took the StandardCardRendered and extended it by adding fields, changing formatting, and hiding objects. I'm trying to duplicate the "Days Since Last Column Move" code and my RevisionHistory object appears to be empty, so I'm really just calculating the "Days Since Story Created". How do I correctly calculate the "Days Since List Column Move"?
All my calculation logic is stored in the this._getColumnAgeDays function and I included CreationDate and RevisionHistory in my Fetch, but these fields weren't necessary in the code for Catalog App Kanban Board. Below is a sample of the code.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
<title>App Example: Test</title>
<meta name="Name" content="App Example: Test" />
<meta name="Vendor" content="Test" />
<script type="text/javascript" src="/apps/1.26/sdk.js"></script>
<script type="text/javascript">
var EnhancedCardRenderer = function(column, item, options)
{
rally.sdk.ui.cardboard.BasicCardRenderer.call(this, column, item, options);
var that = this;
this.getCardBody = function()
{
var card = document.createElement("div");
card.innerHTML = item.Name;
// Add card footer.
var CardFooterDiv = document.createElement("div");
dojo.addClass(CardFooterDiv, 'footerCardBorder');
dojo.addClass(CardFooterDiv, 'footerCardFormat');
var DaysMessage = "Days: " + that._getColumnAgeDays();
CardFooterDiv.appendChild(document.createTextNode(DaysMessage));
card.appendChild(CardFooterDiv);
return card;
};
this._getColumnAgeDays = function()
{
var daysOld = 0;
function getLastStateChange() {
var revisions = item.RevisionHistory.Revisions;
var lastStateChangeDate = "";
rally.forEach(revisions, function(revision) {
if (lastStateChangeDate.length === 0) {
var attr = options.attribute.toUpperCase();
if (revision.Description.indexOf(attr + " changed from") !== -1) {
lastStateChangeDate = revision.CreationDate;
}
if (revision.Description.indexOf(attr + " added") !== -1) {
lastStateChangeDate = revision.CreationDate;
}
}
});
return lastStateChangeDate || item.CreationDate;
}
var lastStateDate = getLastStateChange();
var lastUpdateDate = rally.sdk.util.DateTime.fromIsoString(lastStateDate);
return rally.sdk.util.DateTime.getDifference(new Date(), lastUpdateDate, "day");
};
};
function onLoad() {
var cardboard;
var rallyDataSource = new rally.sdk.data.RallyDataSource('__WORKSPACE_OID__',
'__PROJECT_OID__',
'__PROJECT_SCOPING_UP__',
'__PROJECT_SCOPING_DOWN__');
var cardboardConfig = {
attribute: "Kanban",
cardRenderer:EnhancedCardRenderer,
fetch:"Name,FormattedID,Owner,ObjectID,CreationDate,RevisionHistory,Revisions"
};
cardboardConfig.cardOptions = { attribute: cardboardConfig.attribute };
cardboard = new rally.sdk.ui.CardBoard(cardboardConfig, rallyDataSource);
cardboard.display(dojo.body());
}
rally.addOnLoad(onLoad);
</script>
<style type="text/css">
</style>
</head>
<body>
</body>
</html>
You'll want to add Revisions to your fetch. The reason this works in the Kanban app is because the CardBoard component on which it is built is doing this behind the scenes automatically.
Note that fetching Revision History/Revisions can be an expensive operation- that is the reason the Kanban does the initial data load first and then once the board is rendered makes secondary requests to gather aging data from the revision history.