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.
Related
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
I'm attempting to create a simple to-do list and I've encountered two problems:
After refreshing the page, all the created elements are no longer visible on the page despite being in local storage.
After refreshing the page and submitting new values to the input, localStorage overwrites itself.
Despite that, the items displayed from the input fields are from the previous localStorage, which no longer exists (I really hope this makes sense).
const inputEl = document.getElementById("inputEl")
const submitBtn = document.getElementById("submit")
const clearBtn = document.getElementById("clearBtn")
const todoListContainer = document.getElementById("todoList")
const taskContainer = document.querySelector(".task")
const cancelBtn = document.querySelector(".cancelBtn")
const doneBtn = document.querySelector(".doneBtn")
const errorMsg = document.querySelector(".error")
let localStorageContent = localStorage.getItem("tasks")
let tasksItem = JSON.parse(localStorageContent)
let tasks = []
function createTask() {
if (inputEl.value.length != 0) {
const newDiv = document.createElement("div")
newDiv.classList.add("task")
const newParagraph = document.createElement("p")
const newCancelBtn = document.createElement("button")
newCancelBtn.classList.add("cancelBtn")
newCancelBtn.textContent = "X"
const newDoneBtn = document.createElement("button")
newDoneBtn.classList.add("doneBtn")
newDoneBtn.textContent = "Done"
todoListContainer.appendChild(newDiv)
newDiv.appendChild(newParagraph)
newDiv.appendChild(newCancelBtn)
newDiv.appendChild(newDoneBtn)
//^^ Creating a container for a new task, with all its elements and assigning the classes^^
tasks.push(inputEl.value)
inputEl.value = ""
for (let i = 0; i < tasks.length; i++) {
localStorage.setItem("tasks", JSON.stringify(tasks))
newParagraph.textContent = JSON.parse(localStorageContent)[i]
}
errorMsg.textContent = ""
} else {
errorMsg.textContent = "You have to type something in!"
errorMsg.classList.toggle("visibility")
}
}
submitBtn.addEventListener("click", () => {
createTask()
})
clearBtn.addEventListener("click", () => {
localStorage.clear()
})
HTML code below:
<!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="/style.css">
<script src="/script.js" defer></script>
<title>To-do list</title>
</head>
<body>
<h2 class="error visibility"></h2>
<div id="todoList">
<h1>To-Do List</h1>
<input type="text" name="" id="inputEl" placeholder="Add an item!">
<button type="submitBtn" id="submit">Submit</button>
<button id="clearBtn">Clear list</button>
<div class="task">
</div>
</div>
</body>
</html>
After refreshing the page, all the created elements are no longer visible on the page despite being in local storage
That is because you are rendering the HTML only after the click event and not on page load. To render the HTML for existing tasks stored in the localStorage you have to write a code that loops over your existing tasks in the tasksItem and applies the rendering logic to it.
I would suggest splitting the rendering code from your createTask() function and create a new function for it (for example renderTask()), then you can use it inside a loop on page load and also call the function once a new task is created in the createTask() function.
window.addEventListener('load', (event) => {
// Your read, loop and render logic goes here
})
After refreshing the page and submitting new values to the input, localStorage overwrites itself.
That's because you are actually overriding the tasks in the localStorage. To keep existing tasks, you have to use your tasksItem variable instead of the blank tasks array to create your tasks in and save them to the localStorage.
So, instead of:
tasks.push(inputEl.value)
You would use:
tasksItem.push(inputEl.value)
The same goes for:
for (let i = 0; i < tasksItem.length; i++) {
localStorage.setItem("tasks", JSON.stringify(tasksItem))
// …
}
it was a long time ago that I didn’t program in javascript so I decided to make a project of a "bookcase" to manage read books and that I want to read more I have difficulty with how to separate the elements to personalize the style because it selects all the results of the api in one just div.
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="script.js"></script>
<link rel="stylesheet" href="./css/bookcase.css">
<title>project</title>
</head>
<body>
<div id="content">
</div>
<script src="https://www.googleapis.com/books/v1/volumes?q=clean+code&callback=handleResponse></script>
</body>
</html>
js
function handleResponse(response) {
for (var i = 0; i < response.items.length; i++) {
var item = response.items[i];
var book = document.getElementById('content')
book.innerHTML += "<br>" + '<img src=' + response.items[i].volumeInfo.imageLinks.thumbnail + '>';
book..innerHTML += "<br>" + item.volumeInfo.title;
book..innerHTML += "<br>" + item.volumeInfo.authors;
Clean answer - you should use document.appendChild(child) instead of innerHTML method.
Also, there are few recently added js methods that can help you operate large JSON objects - map, reduce, filter.
I added example, how you can clean original object to smaller array, and insert items from that array into html-page.
function demo (obj) {
// getting all items from object
const book = Object.keys(obj).map(item => obj['items']).reduce(
(acc,rec, id, array) => {
// getting Cover, Title, Author from each item
let singleBookCover = rec[id].volumeInfo.imageLinks.thumbnail;
let singleBookTitle = rec[id].volumeInfo.title;
let singleBookAuthor = rec[id].volumeInfo.authors[0];
// Creating new array only with Cover, Title, Author
return [...acc, {singleBookCover, singleBookTitle, singleBookAuthor}]
},
[]
).forEach( item => {
// For each item on our array, we creating h1
let title = document.createElement('h1');
title.textContent = `${item.singleBookTitle} by ${item.singleBookAuthor}`;
// img
let img = document.createElement('img');
img.src = item.singleBookCover;
img.alt = `${item.singleBookTitle} by ${item.singleBookAuthor}`;
// and div wrapper
let container = document.createElement('div');
// adding our child elements to wrapper
container.appendChild(title).appendChild(img);
// adding our wrapper to body
document.body.appendChild(container);
})
return book
}
Hope my answer will help you)
function handleResponse(obj) {
const container = document.getElementById("container")
obj.items.forEach((rec, index) => {
let singleBookCover =
rec.volumeInfo.imageLinks && rec.volumeInfo.imageLinks.smallThumbnail;
let singleBookTitle = rec.volumeInfo.title;
let singleBookAuthor = rec.volumeInfo.authors[0];
let book = document.createElement("div");
book.className = "book"
book.innerHTML = `
<div><h1>${singleBookTitle}<h1>
<p>${singleBookAuthor}</p>
<img src="${singleBookCover}"></img>
</div>
`
content.appendChild(book)
});
}
<div id="content" class="books">
</div>
<script src="https://www.googleapis.com/books/v1/volumes?q=clean+code&callback=handleResponse"></script>
I am trying to write a script that changes the color of the text if it is an active screen (there are probably more efficient ways to do this). The error I am getting is Uncaught TypeError: Cannot set property 'innerHTML' of null My JavaScript (the entire page)
function main() {
var cardDiv = '<div id ="cardScreen"><a href="cardScreen.html">';
var card = "Card";
var closer = "</a></div>";
var color = (function color1(Check) {
if (window.location.href.indexOf(Check))
return "red";
else
return "white";
});
card.fontcolor = color("cardScreen");
var cardDivPrint = cardDiv + card + closer;
window.onload=document.getElementById("header").innerHTML= cardDivPrint;
}
main();
The HTML
<!DOCTYPE html>
<link href="../css/MasterSheet.css" rel="stylesheet" />
<html>
<head>
<title>
</title>
</head>
<body>
<div id="header">
</div>
<div>Content goes here.</div>
<script src="../scripts/essentials.js"></script>
</body>
</html>
The IDE (Visual Studio 2015 Cordova) says that the error is on this line in the JavaScript "var cardDivPrint = cardDiv + card + closer;" I have looked at multiple similar problems and applied what was relevant (also tried changing window.onload to document.onload) but it still throws the same error.
onload expects function to be executed after page is completely loaded. Otherwise it'll treat it as simple assignment statement and execute. Use function as follow:
window.onload = function() {
document.getElementById("header").innerHTML = cardDivPrint;
};
UPDATE
Instead of using main(), use DOMContentLoaded event.
document.addEventListener("DOMContentLoaded", function(event) {
console.log("DOM fully loaded and parsed");
var cardDiv = '<div id ="cardScreen"><a href="cardScreen.html">';
var card = "Card";
var closer = "</a></div>";
var color = window.location.href.indexOf(Check) !== -1 ? "red" : "white";
card.fontcolor = color("cardScreen");
var cardDivPrint = cardDiv + card + closer;
document.getElementById("header").innerHTML = cardDivPrint;
});
Call the main function at the end of your body content
You are getting this error just because the element dose not exists at the time of its selection by JS DOM
<!DOCTYPE html>
<html>
<head>
<title>
</title>
<link href="../css/MasterSheet.css" rel="stylesheet" />
<script>
function main() {
var cardDiv = '<div id ="cardScreen"><a href="cardScreen.html">';
var card = "Card";
var closer = "</a></div>";
var color = (function color1(Check) {
if (window.location.href.indexOf(Check))
return "red";
else
return "white";
});
card.fontcolor = color("cardScreen");
var cardDivPrint = cardDiv + card + closer;
window.onload=document.getElementById("header").innerHTML= cardDivPrint;
}
</script>
</head>
<body>
<div id="header">
</div>
<div>Content goes here.</div>
<script>main();</script>
</body>
</html>
I am currently researching the possibility to grabbing data from the Tableau report(s) via the JavaScript API but the closet I can get to grabbing values from a graph after filtering is selecting the value via the selectSingleValue() method.
For example: JavaScript API Tutorial
In the API tutorial tab called 'Select'. One of the examples selects the row "Marcao Sao, China". Is it possible to extract that numerical value of $52.0k ?
I have tried looking into the Objects returned (via FireBug) but I cannot seem to locate the right object. My recent location was in getActiveSheets().
Any help would be appreciated.
In the JavaScript API tutorial tab 'Events' it shows you how to add an event listener to return the selected marks. You can then loop through the marks to get the values you want.
Copy the below code block into a file, save as html and open in your favourite web browser (tested on ie11).
<html>
<head>
<meta charset="utf-8">
<title>Tableau 8 Javascrip API</title>
<script type="text/javascript" src="http://public.tableausoftware.com/javascripts/api/tableau_v8.js"></script>
<script type="text/javascript">
/////////////////////
// Global variables
var viz, workbook, activeSheet
// function called by viz on marks being selected in the workbook
function onMarksSelection(marksEvent) {
return marksEvent.getMarksAsync().then(reportSelectedMarks);
}
function reportSelectedMarks(marks) {
for (var markIndex = 0; markIndex < marks.length; markIndex++) {
var pairs = marks[markIndex].getPairs();
for (var pairIndex = 0; pairIndex < pairs.length; pairIndex++) {
var pair = pairs[pairIndex];
if (pair.fieldName == "AVG(F: GDP per capita (curr $))") {
alert("You selected a country with an avg GPD per capita of " + pair.formattedValue);
}
}
}
}
// Initialise the viz to hold the workbook
function initializeViz(){
var placeholderDiv = document.getElementById("tableauViz");
var url = "http://public.tableausoftware.com/views/WorldIndicators/GDPpercapita?Region=";
var options = {
width: "800px", //width: placeholderDiv.offsetWidth,
height: "400px", //height: placeholderDiv.offsetHeight,
hideTabs: true,
hideToolbar: true,
onFirstInteractive: function () {
workbook = viz.getWorkbook();
activeSheet = workbook.getActiveSheet();
}
};
viz = new tableauSoftware.Viz(placeholderDiv, url, options);
// Add event listener
viz.addEventListener(tableauSoftware.TableauEventName.MARKS_SELECTION, onMarksSelection);
}
</script>
</head>
<body>
<!-- Tableau view goes here -->
<div id="tableauViz" style="height:1200px; width:1200px"\></div>
<script type='text/javascript'>
//Initialize the viz after the div is created
initializeViz();
</script>
</body>
</html>